/* ==========================================================================
   Pro Contractors KC LLC - portal stylesheet
   --------------------------------------------------------------------------
   Loaded after site.css on every page under /portal/, so it inherits the
   public site's :root tokens, font stack and reset and only adds what the
   portal itself needs. Deliberately echoes the marketing site's brand -
   the navy gradient ribbon, the blue accent, the real logo - so signing in
   feels like the same company, not a separate admin tool.

   Sections:  layout & chrome / auth screens / forms & buttons / cards &
   metrics / tables / quote builder / scheduling / invoices / reviews /
   reporting chart / responsive
   ========================================================================== */

:root {
  --portal-bg: #f4f7fc;
  --portal-line: #e2e9f2;
  --portal-muted: #6b7c91;
  --portal-shadow: 0 1px 2px rgba(7, 28, 54, 0.04), 0 8px 24px rgba(7, 28, 54, 0.06);
  --portal-shadow-lg: 0 2px 4px rgba(7, 28, 54, 0.05), 0 18px 48px rgba(7, 28, 54, 0.1);
}

/* The browser's own [hidden]{display:none} rule lives in the user-agent
   stylesheet, which loses to ANY author rule that sets `display` on the
   same element - regardless of selector specificity, since origin is
   compared before specificity in the cascade. Any class here that sets
   its own `display` (.btn, .detail-grid, .edit-banner, and others found
   live: #resetCards was rendering hidden="" as display:flex) silently
   defeats `el.hidden = true` this way. One `!important` rule, up front,
   is cheaper and more reliable than chasing every class that happens to
   declare `display` one at a time - `hidden` should always mean hidden.
   (.notice-action[hidden], .modal[hidden] already did this locally;
   this makes it universal so nothing new falls into the same trap.) */
[hidden] {
  display: none !important;
}

body.portal-body {
  background: var(--portal-bg);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
/* Signed-in pages become a true fixed-viewport app shell rather than a
   page that scrolls as a whole: height (not min-height) plus overflow:
   hidden means nothing here can grow taller than the screen, so the
   ribbon, header and footer chrome never scroll out of view - only
   .dashboard-shell (the content beside the sidebar) and .sidebar-scroll
   (the nav list, see below) get their own internal scrollbar. Scoped to
   .has-sidebar specifically so the auth screens (sign in, register, etc,
   see portal-header.php's $hasSidebar branch) keep their original
   plain-page behavior - they have no footer-pinning problem to solve and
   no reason to clip a tall page. */
body.portal-body.has-sidebar {
  height: 100vh;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Top chrome: navy ribbon + white header with the logo
   -------------------------------------------------------------------------- */
.portal-ribbon {
  background: linear-gradient(100deg, #061a32, #0a3566);
  color: #b9d2ec;
}
.portal-ribbon .inner {
  width: min(1180px, calc(100% - 40px));
  margin: auto;
  min-height: 38px;
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.portal-ribbon .ribbon-tag strong {
  color: #fff;
}
.portal-ribbon .ribbon-label {
  margin-left: auto;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 10px;
  font-weight: 900;
  color: #7fb4f0;
}

.portal-top {
  background: rgba(255, 255, 255, 0.96);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--portal-line);
  position: sticky;
  top: 0;
  z-index: 50;
}
.portal-top .inner {
  width: min(1180px, calc(100% - 40px));
  margin: auto;
  min-height: 84px;
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}
.portal-brand {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}
.portal-brand img {
  width: 190px;
  height: 62px;
  object-fit: contain;
  object-position: left center;
}

/* --------------------------------------------------------------------------
   App shell: signed-in pages (customer/contractor) get a fixed dark
   sidebar instead of a horizontal link row in the header - one destination
   per row, always visible, current section unmistakable. The auth screens
   never reach this: they have nothing to navigate between yet, so
   portal-header.php skips straight to the plain centered .auth-shell for
   those and none of this applies.
   -------------------------------------------------------------------------- */
:root {
  --sidebar-w: 240px;
  --portal-top-h: 76px;
}

/* Grows to fill the space body.portal-body's flex column leaves between
   the ribbon above and the footer below (rather than min-height: 100vh,
   which would double-count that space and either overshoot the viewport
   or leave a gap above the footer). Itself a flex column so the header
   row stays a fixed height and .portal-cols takes whatever is left. */
.portal-app {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--portal-bg);
}

/* The header for signed-in pages has no .inner wrapper (portal-header.php
   renders it flat) - it owns the full row itself instead of being centered
   under a max-width, which is what lets it sit flush against the sidebar
   on its left edge. */
.portal-app .portal-top {
  flex: 0 0 auto;
  height: var(--portal-top-h);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 22px;
}
.portal-app .portal-brand img {
  width: 168px;
  height: 54px;
}

.sidebar-toggle {
  display: none;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border: 1px solid var(--portal-line);
  border-radius: 9px;
  background: #fff;
  color: var(--navy);
  cursor: pointer;
  transition: 0.15s;
}
.sidebar-toggle:hover {
  border-color: #9cc4f4;
  color: var(--blue);
}
.sidebar-toggle svg {
  width: 19px;
  height: 19px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}

.sidebar-scrim {
  display: none;
}

.portal-cols {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  /* stretch (the default - was previously overridden to "start" back when
     the sidebar sized itself via position:sticky and the page scrolled as
     a whole) - both columns now need to fill this row's exact height so
     the sidebar and .dashboard-shell can each own their own internal
     scroll instead of the page growing taller than the viewport. */
  align-items: stretch;
  position: relative;
}

/* Same navy gradient as the ribbon and the public site's header - the
   sidebar is what makes the portal read as a real application rather
   than a stack of forms, so it gets the strongest brand color in the UI.
   position: relative (not the old sticky) purely so .sidebar-footer's
   position: absolute below still anchors to THIS box - removing position
   entirely would leave it anchoring to .portal-cols instead and stretch
   full-width across both columns. */
.sidebar {
  position: relative;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, #061a32, #082d55);
  color: #c9d8e8;
  padding: 22px 14px 90px;
}
/* Dashboard, pinned above the scrollable block - see .sidebar-scroll. A
   flex item like everything else in .sidebar, but 0 0 auto so it never
   grows or shrinks: it's exactly one link tall, always. */
.sidebar-pinned {
  flex: 0 0 auto;
  margin-bottom: 8px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
/* The one part of the sidebar that scrolls on its own - Dashboard above and
   Tools/Sign Out below stay fixed in place regardless of how long the
   Operations + Property Management lists get. min-height: 0 is required
   for a flex child to be allowed to shrink below its content size at all;
   without it this would just push the sidebar's own height past 100vh
   instead of scrolling internally. */
.sidebar-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* Scrollbar stays invisible until hovered or keyboard-focused, then fades
   in - mouse/trackpad devices only. Two techniques stacked for coverage
   without JS: the standard scrollbar-color/-width (Firefox, and Chromium
   121+) and the -webkit- pseudo-elements (Chrome, Safari, older Edge);
   each browser just uses whichever it understands. Touch devices are
   excluded on purpose - they already get the OS's own auto-hide overlay
   scrollbar, and there's no hover state to fade it in from anyway. */
@media (hover: hover) and (pointer: fine) {
  .sidebar-scroll {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
  }
  .sidebar-scroll:hover,
  .sidebar-scroll:focus-within {
    scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
  }
  .sidebar-scroll::-webkit-scrollbar {
    width: 8px;
  }
  .sidebar-scroll::-webkit-scrollbar-track {
    background: transparent;
  }
  .sidebar-scroll::-webkit-scrollbar-thumb {
    background-color: transparent;
    border-radius: 999px;
    transition: background-color 0.2s ease;
  }
  .sidebar-scroll:hover::-webkit-scrollbar-thumb,
  .sidebar-scroll:focus-within::-webkit-scrollbar-thumb {
    background-color: rgba(255, 255, 255, 0.35);
  }
  .sidebar-scroll::-webkit-scrollbar-thumb:hover {
    background-color: rgba(255, 255, 255, 0.55);
  }
}
.sidebar-label-tools {
  margin-top: 10px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-label {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.17em;
  color: #7fa9d8;
  font-weight: 900;
  padding: 6px 12px 12px;
}
.side-link {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 11px;
  border: 0;
  background: transparent;
  color: #c9d8e8;
  border-radius: 10px;
  padding: 11px 12px;
  margin-bottom: 3px;
  font-size: 12.5px;
  font-weight: 750;
  text-align: left;
  transition: 0.15s;
}
.side-link svg {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.side-link:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}
.side-link.active {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}
.side-link.active::before {
  content: "";
  position: absolute;
  left: -14px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: #4fb0ff;
}
.side-link-badge {
  margin-left: auto;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  background: #e0453c;
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
  line-height: 16px;
  text-align: center;
}
.sidebar-footer {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 18px;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.sidebar-footer .side-link:hover {
  color: #ffb4ab;
  background: rgba(199, 61, 61, 0.18);
}

.portal-top-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}
.user-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  /* Now a link to portal/preferences.php - same "no underline, subtle
     hover" treatment as the other top-bar link (.back-to-site) right
     next to it, so it reads as clickable without looking like body text
     that happens to be blue. */
  text-decoration: none;
  padding: 4px 8px 4px 4px;
  margin: -4px -8px -4px -4px;
  border-radius: 999px;
  transition: 0.15s;
}
.user-chip:hover {
  background: var(--soft-blue, rgba(11, 101, 221, 0.08));
}
.user-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue), #063f8d);
  color: #fff;
  display: grid;
  place-items: center;
  font-weight: 900;
  font-size: 14px;
  flex: 0 0 auto;
}
.user-name {
  font-size: 13px;
  font-weight: 800;
  color: var(--ink);
}
.portal-top-right .back-to-site {
  font-size: 12.5px;
  font-weight: 800;
  color: var(--portal-muted);
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--portal-line);
  transition: 0.15s;
}
.portal-top-right .back-to-site:hover {
  color: var(--blue);
  border-color: #9cc4f4;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.portal-footer {
  margin-top: auto;
  border-top: 1px solid var(--portal-line);
  background: #fff;
  padding: 18px 0;
}
.portal-footer .inner {
  width: min(1180px, calc(100% - 40px));
  margin: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--portal-muted);
}
.portal-footer .spacer {
  flex: 1;
}
.portal-footer .dot {
  opacity: 0.5;
}
.portal-footer a {
  color: var(--portal-muted);
  font-weight: 700;
}
.portal-footer a:hover {
  color: var(--blue);
}

/* --------------------------------------------------------------------------
   Auth screens (sign in, register, activate, verify, reset)
   -------------------------------------------------------------------------- */
.auth-shell {
  flex: 1;
  display: grid;
  place-items: center;
  padding: 48px 20px 56px;
  background:
    radial-gradient(circle at 15% 0%, rgba(11, 101, 221, 0.07), transparent 45%),
    radial-gradient(circle at 100% 100%, rgba(11, 101, 221, 0.09), transparent 50%);
}
.auth-shell .inner {
  width: min(460px, 100%);
  /* .auth-shell is display:grid, and a grid item's default min-width is
     auto, not 0 - it refuses to shrink below its content's intrinsic
     minimum. A wide table inside (quote-response.php's line items) has
     its own min-width, which without this would drag .inner past its
     declared width instead of scrolling inside its own .table-wrap. */
  min-width: 0;
}
/* The quote-response and schedule-response pages need room for a line-
   item summary or a date picker, not just a login form's two fields. */
.auth-shell .inner.wide {
  width: min(640px, 100%);
}
.auth-card {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 20px;
  box-shadow: var(--portal-shadow-lg);
  padding: 38px 36px;
}
.auth-card .kicker {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue);
}
.auth-card h1 {
  font-size: 28px;
  margin: 10px 0 6px;
  letter-spacing: -0.03em;
}
.auth-card .sub {
  font-size: 13.5px;
  color: var(--portal-muted);
  margin: 0 0 24px;
  line-height: 1.55;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #93a3b6;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 22px 0;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  height: 1px;
  background: var(--portal-line);
  flex: 1;
}
.need-account {
  text-align: center;
  font-size: 13px;
  color: var(--portal-muted);
}
.need-account a {
  color: var(--blue);
  font-weight: 850;
}
.need-account a:hover {
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Forms & buttons
   -------------------------------------------------------------------------- */
.field {
  margin-bottom: 17px;
}
.field label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 850;
  margin-bottom: 7px;
  color: var(--ink);
}
.field label a,
.field label button {
  border: 0;
  background: none;
  color: var(--blue);
  font-size: 11px;
  font-weight: 800;
  padding: 0;
  cursor: pointer;
}
.field label a:hover {
  text-decoration: underline;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 12px 13px;
  outline: none;
  font-size: 14px;
  font-family: inherit;
  background: #fff;
  color: var(--ink);
  transition: 0.15s;
}
.field input::placeholder,
.field textarea::placeholder {
  color: #9fb0c4;
}
.field textarea {
  resize: vertical;
  min-height: 100px;
  line-height: 1.55;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12);
}
.field .hint {
  /* Explicit, not inherited: assets/css/site.css has its own bare .hint
     rule (for the public quote form) that sets display:flex - both
     stylesheets load on every portal page, and portal's version never
     overrode `display`, so it silently leaked through here too. Harmless
     on every OTHER .field .hint in the portal, which happen to be plain
     text with no nested tag - flexbox with one anonymous text-only child
     just renders like a normal line. The first .hint with a <strong> in
     the middle (portal/preferences.php) turned that latent bug visible:
     three flex items (text, <strong>, text) laid out as three
     independently-wrapping columns instead of one paragraph. */
  display: block;
  font-size: 11.5px;
  color: var(--portal-muted);
  margin-top: 6px;
}
/* A plain radio group - portal/preferences.php's clock-format choice.
   No existing radio pattern fit: loss-feedback.php's radios are styled
   as filter-style chips for a longer reason list, not a simple 2-option
   pick. */
.radio-row {
  display: flex;
  gap: 18px;
}

/* .ms-select - a checkbox-list dropdown for "Assigned to" on the Add
   Visit form (portal/contractor/quote.php). Not a native <select
   multiple>: that control is tiny on every platform, needs a ctrl/cmd
   click to pick more than one option, and has no way to show a summary
   like "Tyler, Chad" once something's picked - all of which matter a lot
   more here than in a typical dropdown, since a visit is very often
   assigned to more than one person. Toggle/close/summary logic lives in
   portal-contractor.js; the checkboxes underneath are just a normal
   checkbox group with the same name, so submitting the form needs no
   special handling. */
.ms-select {
  position: relative;
}
.ms-select-btn {
  width: 100%;
  /* Fixed, not intrinsic: the chevron flips its own margin-top between
     closed (-3px) and open (+3px) to stay visually centered after its
     45deg/-135deg rotation - see .ms-select-chevron below. Left to its
     own content height, that swing changed the BUTTON's own height by
     up to 6px the moment the panel opened, so this row's one custom
     control (a <button> standing in for the native inputs/selects
     everywhere else in .add-visit-form) would visibly step out of line
     with its neighbors right when someone opened it. Matches
     .add-visit-form button's own explicit 45px for the same reason. */
  height: 45px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 12px 13px;
  background: #fff;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: 0.15s;
  text-align: left;
}
.ms-select-btn:hover {
  border-color: #9cc4f4;
}
.ms-select-btn.is-open {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12);
}
.ms-select-summary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ms-select-summary.is-placeholder {
  color: #9fb0c4;
}
.ms-select-chevron {
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--portal-muted);
  border-bottom: 2px solid var(--portal-muted);
  transform: rotate(45deg);
  margin-top: -3px;
  transition: transform 0.15s;
}
.ms-select-btn.is-open .ms-select-chevron {
  transform: rotate(-135deg);
  margin-top: 3px;
}
.ms-select-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: 240px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  box-shadow: var(--portal-shadow-lg);
  padding: 8px;
}
/* Applied by msPositionPanel() (portal-contractor.js) when the button is
   low enough on the page/card that opening downward would run the
   panel past the viewport - opens above the button instead, the same
   collision-aware behavior a native <select>'s own dropdown gets for
   free from the browser. */
.ms-select-panel.ms-select-panel-up {
  top: auto;
  bottom: calc(100% + 6px);
}
.ms-select-group-label {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--portal-muted);
  padding: 9px 8px 5px;
}
.ms-select-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border-radius: 8px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}
.ms-select-option:hover {
  background: var(--soft-blue);
}
.ms-select-option input {
  width: 16px;
  height: 16px;
  flex: none;
  accent-color: var(--blue);
  cursor: pointer;
}
.ms-select-empty {
  padding: 10px 8px;
  font-size: 13px;
  color: var(--portal-muted);
}

/* .sender-select - a single-select dropdown for the Campaign Studio's
   "Sent from" field (portal/contractor/campaign-studio.php). Sibling of
   .ms-select above: same button/panel/option chrome and the same
   --blue/--soft-blue/--portal-line/--portal-shadow-lg tokens, but
   single-select - clicking a row picks it and closes the panel
   immediately, no checkboxes, no "Tyler, Chad" summary. Each row shows a
   small circular avatar: the contractor's real headshot when one is on
   file, otherwise an initials bubble coloured the same way the
   Contractors roster page colours them (.crew-avatar/.crew-avatar.emp),
   so "employee vs per-job contractor" reads the same way in both places. */
.sender-select { position: relative; }
.sender-select-btn {
  width: 100%;
  min-height: 45px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 8px 13px;
  background: #fff;
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  cursor: pointer;
  transition: 0.15s;
  text-align: left;
}
.sender-select-btn:hover { border-color: #9cc4f4; }
.sender-select-btn:disabled { cursor: not-allowed; opacity: 0.65; }
.sender-select-btn.is-open { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12); }
.sender-select-current { display: flex; align-items: center; gap: 10px; min-width: 0; overflow: hidden; }
.sender-select-current-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sender-select-current-label.is-placeholder { color: #9fb0c4; }
.sender-select-chevron {
  flex: none; width: 8px; height: 8px;
  border-right: 2px solid var(--portal-muted);
  border-bottom: 2px solid var(--portal-muted);
  transform: rotate(45deg); margin-top: -3px; transition: transform 0.15s;
}
.sender-select-btn.is-open .sender-select-chevron { transform: rotate(-135deg); margin-top: 3px; }
.sender-select-panel {
  position: absolute; z-index: 20; top: calc(100% + 6px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto; background: #fff;
  border: 1px solid var(--portal-line); border-radius: 12px;
  box-shadow: var(--portal-shadow-lg); padding: 8px;
}
.sender-select-panel.sender-select-panel-up { top: auto; bottom: calc(100% + 6px); }
.sender-select-option {
  display: flex; align-items: center; gap: 10px; padding: 8px;
  border-radius: 8px; font-size: 14px; color: var(--ink); cursor: pointer;
}
.sender-select-option:hover { background: var(--soft-blue); }
.sender-select-option.is-selected { background: var(--soft-blue); font-weight: 700; }
.sender-select-option.is-none { border-bottom: 1px solid var(--portal-line); margin-bottom: 4px; padding-bottom: 10px; }
.sender-select-avatar {
  flex: none; width: 28px; height: 28px; border-radius: 50%; object-fit: cover;
  display: grid; place-items: center; font-size: 12px; font-weight: 900; color: #fff;
}
.sender-select-avatar-none { background: var(--soft-blue); color: var(--blue-dark); }
.sender-select-initials { background: var(--soft-blue); color: var(--blue-dark); }
.sender-select-initials.is-emp { background: linear-gradient(135deg, var(--blue), #063f8d); color: #fff; }

/* Campaign Studio canvas mockup - a live-editing APPROXIMATION of the
   real email (see this file's own "HONEST-PREVIEW RULE"), not the
   authoritative render, so these rules are plain browser CSS with no
   Outlook/email constraints. */
.studio-email-head { background: #071c36; color: #fff; padding: 16px 28px; display: flex; align-items: center; gap: 12px; }
.studio-email-head-badge { flex: none; width: 40px; height: 40px; border-radius: 11px; object-fit: cover; }
.studio-email-head-name { font-size: 13.5px; font-weight: bold; letter-spacing: 0.02em; }
.studio-email-head-tagline { font-size: 10.5px; color: #9db9dd; margin-top: 2px; }

.studio-email-signature { display: none; align-items: center; gap: 12px; padding: 14px 28px; background: #f8fafd; border-top: 1px solid #dbe4ef; font-size: 12.5px; color: #4f5f73; }
.studio-email-signature.is-on { display: flex; }
.studio-email-signature img,
.studio-email-signature-fallback { flex: none; width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.studio-email-signature-fallback { background: #0b65dd; color: #fff; display: grid; place-items: center; font-weight: 900; font-size: 14px; }
.studio-email-signature-name { font-weight: bold; color: #071c36; }

.radio-option {
  display: flex;
  align-items: center;
  gap: 7px;
  font-weight: 700;
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 0 20px;
  height: 46px;
  font-weight: 850;
  font-size: 14px;
  width: 100%;
  cursor: pointer;
  transition: 0.16s;
}
.btn.primary {
  background: linear-gradient(135deg, var(--blue), #1675ef);
  color: #fff;
  box-shadow: 0 8px 20px rgba(11, 101, 221, 0.22);
}
.btn.primary:hover {
  background: var(--blue-dark);
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(11, 101, 221, 0.3);
}
.btn.primary:active {
  transform: translateY(0);
}
.btn.primary:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.btn.secondary {
  background: #fff;
  border-color: #9cc4f4;
  color: var(--navy);
}
.btn.secondary:hover {
  border-color: var(--blue);
  color: var(--blue);
  background: var(--soft-blue);
}
.btn.auto {
  width: auto;
}
/* Low-emphasis actions (Undo Start, Restore Walk-Through, Reopen, ...)
   that shouldn't compete with a real primary/danger button next to
   them. Previously undefined anywhere in this file - a bare .btn has no
   background of its own, so every "btn ghost" in the portal (9 of them,
   across quote.php and contractors.php) was silently falling back to
   the browser's native button chrome instead of anything designed. */
.btn.ghost {
  background: transparent;
  border-color: var(--portal-line);
  color: var(--portal-muted);
}
.btn.ghost:hover {
  border-color: #9cc4f4;
  color: var(--navy);
  background: var(--soft-blue);
}
.btn.danger {
  background: linear-gradient(135deg, #d6483c, #b3271d);
  color: #fff;
  box-shadow: 0 8px 20px rgba(179, 39, 29, 0.22);
}
.btn.danger:hover {
  background: #99231b;
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(179, 39, 29, 0.3);
}
.btn.success {
  background: linear-gradient(135deg, #2f9d64, #1c7c46);
  color: #fff;
  box-shadow: 0 8px 20px rgba(28, 124, 70, 0.22);
}
.btn.success:hover {
  background: #17663a;
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(28, 124, 70, 0.3);
}

/* A decision already made (portal/quote-response.php's locked Accept/
   Decline once the customer has answered) - visibly inert rather than
   just non-functional, so it doesn't look clickable while doing
   nothing. Kept in its own success/danger color, dimmed, rather than
   recolored gray - the point is "this already happened", which reads
   better in the color it happened in than in neutral gray. The
   :disabled:hover pair freezes the background at its resting color so
   hovering a disabled button never suggests it's still live. */
.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.btn.success:disabled,
.btn.success:disabled:hover {
  background: linear-gradient(135deg, #2f9d64, #1c7c46);
  box-shadow: none;
  transform: none;
}
.btn.danger:disabled,
.btn.danger:disabled:hover {
  background: linear-gradient(135deg, #d6483c, #b3271d);
  box-shadow: none;
  transform: none;
}

/* --------------------------------------------------------------------------
   Notices
   -------------------------------------------------------------------------- */
.notice {
  border: 1px solid #cfe2f6;
  border-left: 3px solid var(--blue);
  background: #f5faff;
  border-radius: 10px;
  padding: 13px 15px;
  font-size: 12.5px;
  line-height: 1.55;
  color: #43596f;
  margin-bottom: 16px;
}
.notice.good {
  border-color: #b9e3ce;
  border-left-color: #17734f;
  background: #f1fbf6;
  color: #2c644b;
}
.notice.bad {
  border-color: #efbcbc;
  border-left-color: #c73d3d;
  background: #fff5f5;
  color: #a3302f;
}
/* A notice can carry one follow-up action (e.g. "Resend verification
   email") - styled as its own pill so it reads as a deliberate next step,
   not more of the warning text above it. */
.notice .notice-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 7px 13px;
  border-radius: 999px;
  background: #ffffff;
  color: var(--blue);
  font-size: 12px;
  font-weight: 800;
  text-decoration: none;
  border: 1px solid rgba(11, 101, 221, 0.25);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.notice .notice-action::after {
  content: "\2192";
  font-weight: 700;
}
.notice .notice-action:hover {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
}
.notice .notice-action[hidden] {
  display: none;
}
.notice.warn {
  border-color: #ecd8a8;
  border-left-color: #b6790a;
  background: #fffcf5;
  color: #8a5c07;
}

/* --------------------------------------------------------------------------
   Dashboard shell, page headers & cards
   -------------------------------------------------------------------------- */
/* The content pane beside the sidebar (.portal-cols's second grid track).
   min-width: 0 matters here - without it, a grid item's default min-width
   is auto, which means a wide table or the quote builder can push this
   column wider than its track and force the sidebar off-screen. */
.dashboard-shell {
  min-width: 0;
  height: 100%;
  min-height: 0;
  overflow-y: auto;
  padding: 30px 28px 56px;
}
.dashboard-shell .inner {
  width: min(880px, 100%);
  margin: auto;
}
.dashboard-shell .inner.wide {
  width: min(1240px, 100%);
}

.page-head {
  margin-bottom: 22px;
}
.page-head .kicker {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue);
}
.page-head h1 {
  font-size: 30px;
  letter-spacing: -0.035em;
  margin: 8px 0 6px;
}
.page-head p {
  margin: 0;
  font-size: 13.5px;
  color: var(--portal-muted);
}

.card {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 16px;
  padding: 26px 28px;
  box-shadow: var(--portal-shadow);
  margin-bottom: 18px;
}
.card:last-child {
  margin-bottom: 0;
}
.card h1 {
  font-size: 26px;
  letter-spacing: -0.03em;
  margin: 0 0 8px;
}
.card h2 {
  font-size: 18px;
  letter-spacing: -0.02em;
  margin: 0 0 4px;
}
.card .sub {
  font-size: 13px;
  color: var(--portal-muted);
  line-height: 1.6;
  margin: 0 0 18px;
}
.card .sub:last-child {
  margin-bottom: 0;
}
.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 16px;
}
.card-head h2 {
  margin: 0;
}
/* When a .card-head doubles as a <summary> (the collapsible Quote
   Builder card) - same "no native marker, just a pointer cursor"
   treatment as .tax-summary, scoped narrowly so it doesn't affect the
   many plain (non-summary) .card-head divs elsewhere on this page. */
details.card > summary.card-head {
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none;
  user-select: none;
}
details.card > summary.card-head::-webkit-details-marker {
  display: none;
}

.role-pill {
  display: inline-block;
  background: var(--soft-blue);
  color: var(--blue-dark);
  border-radius: 999px;
  padding: 5px 13px;
  font-size: 10.5px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  margin-bottom: 12px;
}

.empty-state {
  font-size: 13.5px;
  color: var(--portal-muted);
  line-height: 1.6;
  padding: 6px 0;
}

/* --------------------------------------------------------------------------
   Status pills
   -------------------------------------------------------------------------- */
.status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 11px;
  font-weight: 850;
  white-space: nowrap;
  letter-spacing: 0.01em;
}
.status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status.submitted,
.status.scheduling,
.status.due,
.status.pending {
  color: #9a6100;
  background: #fff4d8;
}
.status.reviewed,
.status.scheduled {
  color: var(--blue-dark);
  background: var(--soft-blue);
}
.status.completed,
.status.paid,
.status.approved,
.status.good {
  color: #146848;
  background: #e6f6ee;
}
.status.bad,
.status.lost {
  color: #a3302f;
  background: #fff0f0;
}
.status.warn {
  color: #8a5c07;
  background: #fffcf5;
}

/* --------------------------------------------------------------------------
   Metric cards
   -------------------------------------------------------------------------- */
.metric-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-bottom: 18px;
}
.metric-strip .metric {
  position: relative;
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  padding: 20px 22px;
  box-shadow: var(--portal-shadow);
  overflow: hidden;
}
.metric-strip .metric::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--blue), #4fb0ff);
}
.metric-strip .metric-value {
  font-size: 28px;
  font-weight: 900;
  letter-spacing: -0.035em;
  color: var(--ink);
  line-height: 1.1;
}
.metric-strip .metric-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--portal-muted);
  font-weight: 850;
  margin-top: 6px;
}

/* --------------------------------------------------------------------------
   Tables
   -------------------------------------------------------------------------- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  background: #fff;
  box-shadow: var(--portal-shadow);
}
.table-wrap table {
  width: 100%;
  border-collapse: collapse;
  min-width: 700px;
}
.table-wrap th {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--portal-muted);
  background: #f8fafd;
  text-align: left;
  padding: 14px 16px;
  border-bottom: 1px solid var(--portal-line);
  font-weight: 850;
}
.table-wrap td {
  font-size: 13.5px;
  padding: 15px 16px;
  border-bottom: 1px solid #eef2f7;
  color: var(--ink);
}
.table-wrap tr:last-child td {
  border-bottom: 0;
}
.table-wrap tr.clickable {
  cursor: pointer;
  transition: 0.12s;
}
.table-wrap tr.clickable:hover {
  background: #f7fbff;
}
.table-wrap .customer-cell strong {
  display: block;
  font-size: 13.5px;
  font-weight: 850;
}
.table-wrap .customer-cell span {
  font-size: 11.5px;
  color: var(--portal-muted);
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--portal-muted);
  margin-bottom: 16px;
  transition: 0.15s;
}
.back-link:hover {
  color: var(--blue);
  gap: 9px;
}

.detail-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px 24px;
  margin: 18px 0;
  padding: 18px 0;
  border-top: 1px solid #eef2f7;
  border-bottom: 1px solid #eef2f7;
}
.detail-grid b {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
  margin-bottom: 4px;
}
.detail-grid span {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.5;
}
.detail-grid .full {
  grid-column: 1 / -1;
}
/* Requested Scope is reference material, not something needing constant
   attention - collapsed by default like the tax-card <details> pattern,
   but a lighter weight since it's one inline field, not a whole card. */
.detail-scope summary {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
  margin-bottom: 4px;
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none; /* Safari still needs the prefix */
  user-select: none;
}
.detail-scope summary::-webkit-details-marker {
  display: none;
}
.detail-scope summary::after {
  content: " \25BE";
}
.detail-scope[open] summary::after {
  content: " \25B4";
}
.detail-scope summary:hover {
  color: var(--blue);
}

/* --------------------------------------------------------------------------
   Customer quote cards
   -------------------------------------------------------------------------- */
.quote-card {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 16px;
  padding: 24px 26px;
  margin-bottom: 16px;
  box-shadow: var(--portal-shadow);
  transition: 0.16s;
}
.quote-card:hover {
  box-shadow: var(--portal-shadow-lg);
}
.quote-card .quote-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.quote-card h2 {
  font-size: 19px;
  letter-spacing: -0.02em;
  margin: 0 0 4px;
}
.quote-card .meta {
  font-size: 12.5px;
  color: var(--portal-muted);
  margin: 0;
}
.quote-card .pill-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 7px;
}
.quote-card .scope {
  font-size: 13.5px;
  color: #405266;
  white-space: pre-line;
  line-height: 1.6;
  margin-top: 10px;
  padding: 14px 16px;
  background: #f8fafd;
  border-radius: 10px;
}
.quote-card .amount {
  font-size: 13.5px;
  font-weight: 850;
  margin-top: 14px;
  color: var(--ink);
}
.quote-card .amount .label {
  color: var(--portal-muted);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Quote builder
   -------------------------------------------------------------------------- */
.quick-add-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.quick-add-btn {
  border: 1px dashed #a9c9ee;
  background: #f5faff;
  color: var(--blue-dark);
  border-radius: 10px;
  padding: 11px 18px;
  font-size: 13px;
  font-weight: 850;
  cursor: pointer;
  transition: 0.15s;
}
.quick-add-btn:hover {
  background: var(--soft-blue);
  border-color: var(--blue);
  border-style: solid;
  transform: translateY(-1px);
}

/* The builder is an editable 5-column grid with real minimum widths, so on
   a phone it scrolls sideways inside this wrapper rather than forcing the
   whole page to scroll. */
.builder-scroll {
  overflow-x: auto;
  margin-bottom: 18px;
}
.builder-table {
  width: 100%;
  border-collapse: collapse;
  /* Wide enough for the tax-class column too - it scrolls inside
     .builder-scroll rather than pushing the page sideways. */
  min-width: 800px;
}
.builder-table th {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
  text-align: left;
  padding: 0 10px 10px 0;
}
.builder-table td {
  padding: 5px 10px 5px 0;
  vertical-align: middle;
}
.builder-table input,
.builder-table select {
  width: 100%;
  border: 1px solid #cfdae7;
  border-radius: 8px;
  padding: 9px 10px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: 0.15s;
}
.builder-table input:focus,
.builder-table select:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}
.builder-table .col-desc {
  min-width: 170px;
}
.builder-table .col-qty,
.builder-table .col-rate {
  width: 95px;
}
.builder-table .line-total {
  font-size: 13.5px;
  font-weight: 850;
  white-space: nowrap;
  min-width: 85px;
  text-align: right;
  color: var(--ink);
}
.builder-table .remove-row {
  border: 0;
  background: none;
  color: #c08a8a;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 7px;
  border-radius: 6px;
  transition: 0.15s;
}
.builder-table .remove-row:hover {
  color: #a82e2e;
  background: #fff2f2;
}
.builder-empty {
  font-size: 13.5px;
  color: var(--portal-muted);
  padding: 18px;
  text-align: center;
  background: #f8fafd;
  border: 1px dashed var(--portal-line);
  border-radius: 12px;
  margin-bottom: 18px;
}

.tax-rate-row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 750;
  margin: 0 0 18px;
  padding-top: 16px;
  border-top: 1px solid #eef2f7;
}
.tax-rate-row input {
  width: 80px;
  border: 1px solid #cfdae7;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
  outline: none;
}
.tax-rate-row input:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}

.builder-totals {
  margin-left: auto;
  width: min(340px, 100%);
  background: #f8fafd;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  padding: 16px 18px;
}
.builder-totals .total-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  padding: 5px 0;
  color: var(--portal-muted);
}
.builder-totals .total-row b {
  color: var(--ink);
  font-weight: 800;
}
.builder-totals .total-row.grand {
  font-size: 15px;
  font-weight: 900;
  color: var(--ink);
  border-top: 1px solid var(--portal-line);
  margin-top: 8px;
  padding: 12px 10px 10px;
  border-radius: 9px;
  transition: background-color 0.45s ease;
}
.builder-totals .total-row.grand b {
  font-size: 21px;
  letter-spacing: -0.03em;
  font-weight: 900;
}
.builder-totals .total-row.grand.pulse {
  background-color: #dcecff;
}

/* --------------------------------------------------------------------------
   Scheduling & invoices (shared list-row look)
   -------------------------------------------------------------------------- */
.appointment-list,
.invoice-list {
  display: grid;
  gap: 10px;
  margin-bottom: 18px;
}
.appointment-item,
.invoice-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  padding: 14px 16px;
  background: #fbfdff;
  transition: 0.15s;
}
.appointment-item:hover,
.invoice-item:hover {
  border-color: #c9dcf1;
  background: #f7fbff;
}
.appointment-item strong,
.invoice-item strong {
  display: block;
  font-size: 13.5px;
  font-weight: 850;
  color: var(--ink);
}
/* :not(.status) - otherwise this (correctly) targets the plain caption
   span in each row, but ALSO wins the specificity fight against the
   status pill's own .status rule (one class + one tag beats one class),
   which was quietly shrinking every status pill nested in an appointment
   or invoice row from the standard 11px to 11.5px - see the "Walk-through"
   pill in the appointments list and every pill in the invoices list. */
.appointment-item span:not(.status),
.invoice-item span:not(.status) {
  font-size: 11.5px;
  color: var(--portal-muted);
}
.invoice-item .invoice-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}
.invoice-item .mark-paid {
  border: 1px solid #9cc4f4;
  background: #fff;
  color: var(--navy);
  border-radius: 9px;
  padding: 8px 14px;
  font-size: 12px;
  font-weight: 850;
  cursor: pointer;
  white-space: nowrap;
  transition: 0.15s;
}
.invoice-item .mark-paid:hover {
  border-color: var(--blue);
  color: var(--blue);
  background: var(--soft-blue);
}
.appointment-item .remove-row {
  border: 0;
  background: none;
  color: #c08a8a;
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
  padding: 6px 9px;
  border-radius: 7px;
  transition: 0.15s;
}
.appointment-item .remove-row:hover {
  color: #a82e2e;
  background: #fff2f2;
}

/* Top-aligned, not bottom: only some fields carry hint text under them
   (the "Ends" field explains multi-day work), and bottom-alignment would
   shove that field's label and input upward out of line with its
   neighbours. Aligning to the top keeps every label on one line and every
   input on the next, and lets the hint hang below without disturbing
   anything. --field-label-h is the label's own height (12px text at the
   inherited 1.5 line-height, plus its 7px margin) and is what drops the
   submit button - which has no label of its own - down onto the same line
   as the inputs. */
.add-visit-form,
.add-invoice-form {
  --field-label-h: 25px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: flex-start;
  padding-top: 18px;
  border-top: 1px solid #eef2f7;
}
.add-visit-form .field,
.add-invoice-form .field {
  margin-bottom: 0;
  flex: 1 1 165px;
}
/* Direct children only (>): the submit/cancel buttons this is actually
   for (#apptSubmit, #apptCancel, #invoiceSubmit, ...) sit right beside
   the .field divs as siblings, with no label of their own, so this
   pushes them down to align with the labeled inputs next to them. A
   plain descendant selector also caught two buttons that were never
   meant to get this treatment - the "Assigned to" field's own
   .ms-select-btn (already has its own label right above it, and this
   margin was shoving it visibly out of line with every other field in
   the row) and the small inline Cancel inside #invoiceEditBanner. Both
   are nested well below the form's own direct children, so > excludes
   them automatically without needing to list exceptions. */
.add-visit-form > button,
.add-invoice-form > button {
  height: 45px;
  width: auto;
  padding: 0 20px;
  flex: 0 0 auto;
  margin-top: var(--field-label-h);
}

/* --------------------------------------------------------------------------
   Reviews
   -------------------------------------------------------------------------- */
.stars {
  display: flex;
  gap: 5px;
  margin-bottom: 4px;
}
.stars .star {
  border: 0;
  background: none;
  font-size: 30px;
  line-height: 1;
  color: #d3dde8;
  padding: 0;
  cursor: pointer;
  transition: color 0.12s, transform 0.12s;
}
.stars .star.active {
  color: #f2a71b;
}
.stars .star:hover {
  transform: scale(1.15);
}
.stars.readonly .star {
  cursor: default;
  font-size: 19px;
}
.stars.readonly .star:hover {
  transform: none;
}

.referral-toggle {
  display: flex;
  gap: 12px;
}
.referral-toggle button {
  flex: 1;
  border: 1px solid var(--portal-line);
  background: #fff;
  border-radius: 12px;
  padding: 14px 16px;
  font-size: 13px;
  font-weight: 850;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  transition: 0.15s;
}
.referral-toggle button:hover {
  border-color: #9cc4f4;
}
.referral-toggle button span {
  display: block;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--portal-muted);
  margin-top: 3px;
  line-height: 1.45;
}
.referral-toggle button.selected {
  border-color: var(--blue);
  background: #f5faff;
  box-shadow: 0 0 0 2px rgba(11, 101, 221, 0.13);
}

.review-item {
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  padding: 20px 22px;
  margin-bottom: 14px;
  background: #fff;
  box-shadow: var(--portal-shadow);
}
.review-item .review-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 8px;
}
.review-item .review-top strong {
  font-size: 14.5px;
  font-weight: 850;
}
.review-item .comment {
  font-size: 13.5px;
  color: #405266;
  margin: 10px 0;
  white-space: pre-line;
  line-height: 1.65;
  padding: 14px 16px;
  background: #f8fafd;
  border-radius: 10px;
}
.referral-badge {
  display: inline-block;
  background: #e6f6ee;
  color: #146848;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 10px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.xml-snippet {
  width: 100%;
  border: 1px solid var(--portal-line);
  border-radius: 10px;
  padding: 13px 15px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.6;
  color: #46586c;
  background: #f8fafd;
  min-height: 170px;
  margin-top: 10px;
  resize: vertical;
}
.xml-snippet:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}

/* --------------------------------------------------------------------------
   Reporting chart (CSS only - no charting library)
   -------------------------------------------------------------------------- */
.chart-bars {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  height: 180px;
  border-bottom: 1px solid var(--portal-line);
  padding: 0 8px;
  margin-top: 14px;
}
.chart-bar-col {
  flex: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 7px;
}
.chart-bar-value {
  font-size: 10.5px;
  color: var(--portal-muted);
  font-weight: 850;
  white-space: nowrap;
}
.chart-bar {
  width: 62%;
  min-height: 4px;
  border-radius: 7px 7px 0 0;
  background: linear-gradient(180deg, #4fb0ff, var(--blue));
  transition: 0.2s;
}
.chart-bar-col:hover .chart-bar {
  background: linear-gradient(180deg, #2580f1, var(--blue-dark));
}
.chart-bar-label {
  font-size: 10.5px;
  color: var(--portal-muted);
  font-weight: 750;
  margin-top: 9px;
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .metric-strip {
    grid-template-columns: repeat(2, 1fr);
  }
  .portal-top .inner {
    min-height: 72px;
    gap: 16px;
  }
  .portal-brand img {
    width: 155px;
    height: 52px;
  }
  .detail-grid {
    grid-template-columns: 1fr;
  }

  /* Sidebar goes off-canvas: fixed, slid left of the viewport, and
     revealed by adding .side-open to #portalApp (portal-footer.php's
     inline script toggles this from the hamburger button). The content
     pane drops to a single column since there's no second track to
     share the row with any more. */
  .sidebar-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .portal-app .portal-brand img {
    width: 148px;
    height: 48px;
  }
  .portal-cols {
    display: block;
  }
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 90;
    width: min(272px, 84vw);
    height: 100vh;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
  }
  #portalApp.side-open .sidebar {
    transform: translateX(0);
  }
  .sidebar-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 85;
    border: 0;
    padding: 0;
    background: rgba(4, 18, 36, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
  }
  #portalApp.side-open .sidebar-scrim {
    opacity: 1;
    pointer-events: auto;
  }
  .dashboard-shell {
    padding: 24px 18px 48px;
  }
}

@media (max-width: 620px) {
  .portal-ribbon .ribbon-tag {
    display: none;
  }
  .portal-ribbon .ribbon-label {
    margin: 0 auto;
  }
  .metric-strip {
    grid-template-columns: 1fr;
  }
  .user-name {
    display: none;
  }
  /* Keeps "Main site" on one line at phone widths - it was wrapping to
     two and pushing against the avatar/hamburger either side of it. */
  .portal-app .portal-top-right {
    gap: 8px;
  }
  .portal-app .back-to-site {
    padding: 8px 10px;
    font-size: 11.5px;
    white-space: nowrap;
  }
  .portal-app .portal-brand img {
    width: 118px;
    height: 38px;
  }
  .card {
    padding: 22px 20px;
  }
  .auth-card {
    padding: 30px 24px;
  }
  .page-head h1 {
    font-size: 25px;
  }
  .quote-card .quote-top {
    flex-direction: column;
  }
  .quote-card .pill-stack {
    flex-direction: row;
    align-items: flex-start;
    /* Must wrap. This row holds THREE pills now - the job gate, Quote
       status and Work status - and a gate label like "Invoice Sent to
       Customer" is wide on its own. Without wrapping they push the card
       past the viewport and put a horizontal scrollbar on the whole page. */
    flex-wrap: wrap;
  }
  .referral-toggle {
    flex-direction: column;
  }
  .builder-totals {
    width: 100%;
  }
  .add-visit-form button,
  .add-invoice-form button {
    width: 100%;
  }
  /* The flex spacer is meaningless once these stack - drop it so the
     contact links can't get pushed past the viewport edge. */
  .portal-footer .inner {
    justify-content: center;
    text-align: center;
  }
  .portal-footer .spacer {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Schedule calendar (portal/contractor/calendar.php)
   -------------------------------------------------------------------------- */
.cal-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.cal-nav {
  display: flex;
  gap: 8px;
}
.cal-nav .btn {
  height: 38px;
  padding: 0 14px;
  font-size: 12.5px;
}
.cal-legend {
  margin-left: auto;
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 11.5px;
  font-weight: 750;
  color: var(--portal-muted);
}
.cal-legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.cal-legend .sw {
  width: 11px;
  height: 11px;
  border-radius: 4px;
  display: inline-block;
}
.cal-legend .sw.current  { background: var(--blue); }
.cal-legend .sw.future   { background: #fff; border: 1.5px solid var(--blue); }
.cal-legend .sw.done     { background: #b9dcc9; }
.cal-legend .sw.overdue  { background: #f0b849; }
.cal-legend .sw.proposed { background: #f3edfb; border: 1.5px dashed #a586dd; }

.cal-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 18px;
  align-items: start;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--portal-line);
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: var(--portal-shadow);
}
.cal-dow {
  background: #f8fafd;
  padding: 10px 8px;
  font-size: 10px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  text-align: center;
}
.cal-day {
  background: #fff;
  min-height: 108px;
  padding: 7px 7px 9px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cal-day.muted {
  background: #fbfcfe;
}
.cal-day.muted .cal-daynum {
  color: #b6c2d1;
}
.cal-day.open {
  background: repeating-linear-gradient(
    -45deg, #fff, #fff 9px, #fbfdff 9px, #fbfdff 18px
  );
}
.cal-day.today {
  box-shadow: inset 0 0 0 2px var(--blue);
}
.cal-daynum {
  font-size: 12px;
  font-weight: 850;
  color: var(--ink);
}
.cal-day.today .cal-daynum {
  color: var(--blue);
}

.cal-event {
  display: block;
  font-size: 10.5px;
  font-weight: 800;
  line-height: 1.3;
  padding: 4px 6px;
  border-radius: 5px;
  overflow: hidden;
  white-space: nowrap;
  transition: 0.12s;
}
/* Truncation lives on the two lines, not the chip, so the service can
   ellipsis away without taking the client's name with it - the name is
   the whole reason two similar jobs are tellable apart. */
.cal-event-job,
.cal-event-who {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-event-who {
  font-size: 9.5px;
  font-weight: 700;
  opacity: 0.72;
}
.cal-event:hover {
  filter: brightness(0.95);
}
.cal-event.current {
  background: var(--blue);
  color: #fff;
}
.cal-event.future {
  background: var(--soft-blue);
  color: var(--blue-dark);
  box-shadow: inset 0 0 0 1px #a9c9ee;
}
.cal-event.done {
  background: #e6f6ee;
  color: #146848;
}
.cal-event.overdue {
  background: #fff4d8;
  color: #9a6100;
  box-shadow: inset 0 0 0 1px #f0d18a;
}
/* Waiting on the customer to approve or counter a proposed date - a
   dashed outline rather than a solid fill, so it reads as "not locked in
   yet" at a glance next to the solid confirmed states above. */
.cal-event.proposed {
  background: #f3edfb;
  color: #5b3fa3;
  box-shadow: inset 0 0 0 1px #c9b5ec;
  border: 1px dashed #a586dd;
}
.cal-open-tag {
  margin-top: auto;
  font-size: 9px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: #a9b8c9;
}

.need-list {
  display: grid;
  gap: 9px;
}
.need-item {
  display: block;
  border: 1px solid var(--portal-line);
  border-radius: 11px;
  padding: 12px 14px;
  background: #fbfdff;
  transition: 0.15s;
}
.need-item:hover {
  border-color: #9cc4f4;
  background: #f5faff;
}
.need-item strong {
  display: block;
  font-size: 13px;
  font-weight: 850;
  color: var(--ink);
}
.need-item span {
  font-size: 11.5px;
  color: var(--portal-muted);
}

/* Optional-field marker, used by the scheduling form's "Ends" label. */
.field label .opt {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #a3b1c2;
}

@media (max-width: 1000px) {
  .cal-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (max-width: 620px) {
  /* Keep all 7 columns so it still reads as a month, but shrink the cells
     and drop event text to a colour bar - the point on a phone is spotting
     which days are free, not reading job names. */
  .cal-day {
    min-height: 62px;
    padding: 4px 3px 5px;
  }
  .cal-dow {
    padding: 7px 2px;
    font-size: 9px;
  }
  .cal-daynum {
    font-size: 10.5px;
  }
  .cal-event {
    font-size: 0;
    padding: 0;
    height: 6px;
    border-radius: 3px;
  }
  .cal-open-tag {
    display: none;
  }
  .cal-legend {
    margin-left: 0;
    gap: 10px;
  }
}

/* --------------------------------------------------------------------------
   Modal (schedule date picker)
   -------------------------------------------------------------------------- */
body.modal-open {
  overflow: hidden;
}
.modal {
  position: fixed;
  inset: 0;
  background: rgba(6, 20, 38, 0.62);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 24px 16px;
}
.modal[hidden] {
  display: none;
}
.modal-card {
  width: min(940px, 100%);
  max-height: 90vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.3);
  padding: 26px 28px 22px;
}
.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
}
.modal-head .kicker {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue);
}
.modal-head h2 {
  font-size: 24px;
  letter-spacing: -0.03em;
  margin: 6px 0 0;
}
.modal-close {
  border: 0;
  background: none;
  font-size: 26px;
  line-height: 1;
  color: var(--portal-muted);
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 8px;
  transition: 0.15s;
}
.modal-close:hover {
  background: #f2f5f9;
  color: var(--ink);
}
.modal-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--portal-line);
}
.cal-selection {
  margin-right: auto;
  font-size: 13px;
  font-weight: 800;
  color: var(--ink);
}

/* Selectable grid: drag-to-pick states */
.cal-grid.selectable .cal-day {
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  min-height: 76px;
}
.cal-grid.selectable .cal-day:hover {
  background: #f3f8ff;
}
.cal-grid.selectable .cal-event {
  pointer-events: none;
}
.cal-grid.selectable .cal-day.sel {
  background: #d9eaff;
  box-shadow: inset 0 0 0 1px #86b6ee;
}
.cal-grid.selectable .cal-day.sel-start {
  box-shadow: inset 0 0 0 2px var(--blue);
}
.cal-grid.selectable .cal-day.sel-end {
  box-shadow: inset 0 0 0 2px var(--blue);
}
.cal-grid.selectable .cal-day.sel-start.sel-end {
  box-shadow: inset 0 0 0 2px var(--blue);
}

/* --------------------------------------------------------------------------
   Shared dialog (confirm / prompt / alert) - assets/php/portal-footer.php.
   Same .modal/.modal-card shell as the schedule calendar's date picker,
   just narrower: this one only ever holds a short message, not a grid.
   -------------------------------------------------------------------------- */
.dialog-card {
  width: min(440px, 100%);
}
.dialog-message {
  margin: 0 0 4px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
}
.dialog-input {
  width: 100%;
  margin-top: 10px;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 11px 14px;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: 0.15s;
}
.dialog-input:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}
.dialog-input-error {
  margin-top: 10px;
  padding: 8px 12px;
  font-size: 12.5px;
}
/* Right-aligned rather than the calendar modal's default (which reserves
   the left side for the "3 days selected" readout) - a confirm dialog has
   nothing to put there. */
.dialog-actions {
  justify-content: flex-end;
}

@media (max-width: 480px) {
  .dialog-actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }
  .dialog-actions .btn {
    width: 100%;
  }
}

@media (max-width: 620px) {
  .modal-card {
    padding: 20px 16px 18px;
    max-height: 94vh;
  }
  .modal-actions .btn {
    flex: 1;
  }
  .cal-selection {
    margin: 0 0 8px;
    width: 100%;
  }
  .cal-grid.selectable .cal-day {
    min-height: 54px;
  }
}

/* --------------------------------------------------------------------------
   Scheduling: pick an existing visit to edit it
   -------------------------------------------------------------------------- */
.appointment-pick {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.appointment-pick:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 6px;
}
.appointment-item.editing,
.invoice-item.editing {
  border-color: var(--blue);
  background: #f2f8ff;
  box-shadow: inset 0 0 0 1px var(--blue);
}

.edit-banner {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  background: var(--soft-blue);
  border: 1px solid #a9c9ee;
  border-radius: 10px;
  padding: 10px 13px;
  font-size: 12.5px;
  font-weight: 750;
  color: var(--blue-dark);
  margin-bottom: 14px;
}

/* --------------------------------------------------------------------------
   Invoice document (portal/invoice.php)
   -------------------------------------------------------------------------- */
.doc-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.doc-actions .back-link {
  margin-bottom: 0;
  margin-right: auto;
}

.invoice-doc {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 16px;
  box-shadow: var(--portal-shadow);
  padding: 40px 44px 34px;
  color: var(--ink);
}
.doc-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 28px;
  padding-bottom: 24px;
  border-bottom: 2px solid var(--ink);
  flex-wrap: wrap;
}
.doc-from {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}
.doc-logo {
  width: 120px;
  height: 54px;
  object-fit: contain;
  object-position: left top;
}
.doc-from-lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  color: var(--portal-muted);
  line-height: 1.5;
}
.doc-from-lines strong {
  font-size: 15px;
  color: var(--ink);
  margin-bottom: 2px;
}
.doc-meta {
  text-align: right;
  min-width: 220px;
}
.doc-meta h1 {
  font-size: 30px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 8px;
  color: var(--ink);
}
.doc-status {
  display: inline-block;
  border-radius: 6px;
  padding: 4px 12px;
  font-size: 11px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin-bottom: 12px;
}
.doc-status.due {
  color: #9a6100;
  background: #fff4d8;
  border: 1px solid #f0d18a;
}
.doc-status.paid {
  color: #146848;
  background: #e6f6ee;
  border: 1px solid #a9dcc3;
}
.doc-meta dl {
  display: grid;
  grid-template-columns: auto auto;
  gap: 3px 14px;
  justify-content: end;
  margin: 0;
  font-size: 12.5px;
}
.doc-meta dt {
  color: var(--portal-muted);
  font-weight: 750;
}
.doc-meta dd {
  margin: 0;
  font-weight: 850;
}

.doc-parties {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  padding: 22px 0;
  border-bottom: 1px solid var(--portal-line);
}
.doc-parties h2,
.doc-section h2,
.doc-foot h2 {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--portal-muted);
  font-weight: 850;
  margin: 0 0 7px;
}
.doc-parties div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 13px;
  line-height: 1.5;
}
.doc-parties strong {
  font-size: 14px;
  font-weight: 850;
}
.doc-muted {
  color: var(--portal-muted);
}

.doc-section {
  padding: 22px 0;
  border-bottom: 1px solid var(--portal-line);
}
.doc-scope {
  font-size: 13px;
  line-height: 1.65;
  color: #405266;
  margin: 0 0 16px;
  white-space: pre-line;
}

.doc-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 18px;
}
.doc-table th {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--portal-line);
}
.doc-table td {
  font-size: 13px;
  padding: 9px 10px;
  border-bottom: 1px solid #eef2f7;
}
.doc-table .num {
  text-align: right;
  white-space: nowrap;
}

.doc-totals {
  margin-left: auto;
  width: min(340px, 100%);
}
.doc-totals.wide {
  width: min(420px, 100%);
}
.doc-totals .row {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  font-size: 13px;
  padding: 6px 0;
  color: var(--portal-muted);
}
.doc-totals .row b {
  color: var(--ink);
  font-weight: 800;
  white-space: nowrap;
}
.doc-totals .row.grand {
  border-top: 1px solid var(--ink);
  margin-top: 6px;
  padding-top: 10px;
  font-size: 15px;
  color: var(--ink);
  font-weight: 900;
}
.doc-totals .row.grand b {
  font-size: 19px;
  letter-spacing: -0.02em;
  font-weight: 900;
}

.doc-foot {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px;
  /* site.css styles the bare <footer> element with the marketing site's
     navy gradient - reset it, or the invoice's own footer block inherits
     a dark banner mid-document. */
  background: none;
  color: inherit;
  margin-top: 0;
  padding: 22px 0 0;
}
.doc-foot p {
  font-size: 12.5px;
  line-height: 1.6;
  margin: 0 0 6px;
  color: #405266;
  white-space: pre-line;
}
.doc-thanks {
  margin: 22px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--portal-line);
  font-size: 12.5px;
  color: var(--portal-muted);
  text-align: center;
}

@media (max-width: 760px) {
  .invoice-doc { padding: 26px 20px 22px; }
  .doc-parties { grid-template-columns: 1fr; gap: 16px; }
  .doc-foot { grid-template-columns: 1fr; gap: 18px; }
  .doc-meta { text-align: left; min-width: 0; }
  .doc-meta dl { justify-content: start; }
  .doc-totals, .doc-totals.wide { width: 100%; }
  .doc-table { display: block; overflow-x: auto; }
}

/* --------------------------------------------------------------------------
   Print: strip the portal chrome so the invoice prints as a clean sheet.
   Browser "Save as PDF" then produces a real attachable document with no
   PDF library involved.
   -------------------------------------------------------------------------- */
@media print {
  .portal-ribbon,
  .portal-top,
  .portal-footer,
  .sidebar,
  .sidebar-toggle,
  .sidebar-scrim,
  .doc-actions,
  .page-head {
    display: none !important;
  }
  /* The app-shell rules above clip everything to one viewport height so
     the ribbon/header/footer never scroll - exactly wrong for print,
     which needs the FULL document height across as many physical pages
     as it takes. Reset height/overflow back to natural flow here, or a
     multi-page invoice would get clipped to a single page's worth of
     pixels and the rest silently lost. */
  body.portal-body.has-sidebar,
  .portal-app,
  .portal-cols,
  .dashboard-shell {
    height: auto;
    overflow: visible;
  }
  body.portal-body {
    background: #fff;
  }
  .portal-cols {
    display: block;
  }
  .dashboard-shell {
    padding: 0;
  }
  .dashboard-shell .inner,
  .dashboard-shell .inner.wide {
    width: 100%;
    max-width: none;
  }
  .invoice-doc {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
  }
  .doc-section,
  .doc-parties {
    break-inside: avoid;
  }
  .doc-table {
    break-inside: auto;
  }
  .doc-table tr {
    break-inside: avoid;
  }
  a[href]::after {
    content: "";
  }
}

/* The invoice row gained a third control (status + View + Mark as Paid),
   which no longer fits beside the amount on a phone - let it stack. */
@media (max-width: 620px) {
  .invoice-item,
  .appointment-item {
    flex-wrap: wrap;
  }
  .invoice-item .invoice-actions {
    width: 100%;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

/* --------------------------------------------------------------------------
   Sales tax: the treatment card on a quote, the tax note on an invoice,
   and the collected-vs-paid split on the reporting page.
   -------------------------------------------------------------------------- */

/* Three fields to a row on desktop, stacking on a phone. auto-fit rather
   than a fixed count so a row with two fields doesn't leave a hole. */
.tax-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px 18px;
  margin-bottom: 14px;
}
.tax-grid .field {
  min-width: 0;
}
.tax-grid label .opt {
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--portal-muted);
  font-size: 11px;
}

.tax-rules {
  margin: 8px 0 10px;
  padding-left: 18px;
  font-size: 12.5px;
  line-height: 1.6;
}
.tax-rules li + li {
  margin-top: 4px;
}
.tax-disclaimer {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid rgba(0, 0, 0, 0.08);
  font-size: 11.5px;
  line-height: 1.55;
  opacity: 0.85;
}

/* The tax-class select carries long option text, so it needs a real
   minimum or it squeezes the description column to nothing. The table
   already scrolls inside .builder-scroll. */
.builder-table .col-taxclass {
  min-width: 165px;
}
/* Invoice: why the tax line reads the way it does. */
.doc-taxnote {
  margin-top: 20px;
  padding: 14px 16px;
  background: #f6f9fd;
  border: 1px solid var(--portal-line);
  border-radius: 10px;
}
.doc-taxnote h3 {
  margin: 0 0 8px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
}
.doc-taxnote ul {
  margin: 0;
  padding-left: 18px;
}
.doc-taxnote li,
.doc-taxnote p {
  font-size: 12.5px;
  line-height: 1.6;
  color: #405266;
  margin: 0 0 4px;
}

/* Reporting: collected and paid sit side by side and are never summed. */
.tax-report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
}
.tax-report-col {
  min-width: 0;
  padding: 16px 18px;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  background: #fbfdff;
}
.tax-report-col h3 {
  margin: 0 0 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--portal-muted);
  font-weight: 850;
}
.tax-report-col .metric-value {
  margin-bottom: 10px;
}
.tax-report-rows .row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 13px;
  padding: 5px 0;
  border-bottom: 1px solid #eef2f7;
  color: var(--portal-muted);
}
.tax-report-rows .row:last-child {
  border-bottom: 0;
}
.tax-report-rows .row b {
  color: var(--ink);
  font-weight: 800;
  white-space: nowrap;
}
.tax-report-col .hint {
  margin: 8px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--portal-muted);
}

@media print {
  .doc-taxnote {
    background: none;
    border: 0;
    border-top: 1px solid #d7dfe9;
    border-radius: 0;
    padding: 12px 0 0;
  }
}

/* Contractor-only guidance that must never reach a printed customer copy. */
@media print {
  .doc-hide-print {
    display: none !important;
  }
}

/* --------------------------------------------------------------------------
   Collapsed tax-treatment card. The summary has to carry enough for the
   contractor to confirm the treatment without opening anything - state,
   property, project, jurisdiction, and the resulting tax.
   -------------------------------------------------------------------------- */
.tax-card {
  padding: 0;
  overflow: hidden;
}
.tax-summary {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 18px 22px;
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none; /* Safari still needs the prefix */
  user-select: none;
  transition: background 0.15s;
}
.tax-summary:hover {
  background: #f7fafd;
}
.tax-summary::-webkit-details-marker {
  display: none;
}
.tax-summary:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: -2px;
}
.tax-summary-main {
  flex: 1 1 auto;
  min-width: 0;
}
.tax-summary-main h2 {
  margin: 0 0 6px;
  font-size: 16px;
}
.tax-summary-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.tax-chip {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  background: #eaf2fd;
  color: #14498f;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.tax-chip.muted {
  background: #eef2f7;
  color: var(--portal-muted);
}
.tax-chip.warn {
  background: #fdeceb;
  color: #99231b;
}

.tax-summary-verdict {
  flex: 0 0 auto;
  text-align: right;
  line-height: 1.35;
}
.tax-summary-verdict strong {
  display: block;
  font-size: 17px;
  font-weight: 900;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.tax-summary-verdict span {
  display: block;
  font-size: 11.5px;
  color: var(--portal-muted);
}
.tax-summary-verdict.good strong {
  color: #1c7c46;
}
.tax-summary-verdict.bad strong {
  color: #b3271d;
}

.tax-summary-toggle {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 850;
  color: var(--blue);
  white-space: nowrap;
}
.tax-summary-toggle::after {
  content: " ▾";
}
.tax-card[open] .tax-summary {
  border-bottom: 1px solid var(--portal-line);
}
.tax-card[open] .tax-summary-toggle::after {
  content: " ▴";
}
.tax-card-body {
  padding: 20px 22px 22px;
}

@media (max-width: 700px) {
  /* Stacked, and the verdict goes left-aligned with everything else -
     a right-aligned block under a heading reads as a stray fragment. */
  .tax-summary {
    flex-wrap: wrap;
    gap: 10px;
    padding: 16px 18px;
  }
  .tax-summary-main {
    flex: 1 1 100%;
  }
  .tax-summary-verdict {
    flex: 1 1 auto;
    text-align: left;
  }
  .tax-card-body {
    padding: 16px 18px 18px;
  }
}

/* The four customer-supplied classification fields belong on one row -
   split 3 + 1 they read as two unrelated groups. Narrower track minimum
   than .tax-grid so all four fit, still collapsing to 2x2 then 1-up. */
.tax-grid.four {
  grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
}

/* --------------------------------------------------------------------------
   Project template selector. A strip above the quote builder, not a screen
   of its own - templates only matter while you're looking at the line
   items they fill in.
   -------------------------------------------------------------------------- */
.template-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 14px;
  margin-bottom: 12px;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  background: #fbfdff;
}
.template-bar label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 850;
  color: var(--portal-muted);
  white-space: nowrap;
}
.template-bar select {
  flex: 1 1 190px;
  min-width: 0; /* or a long template name blows the flex row out */
  border: 1px solid #cfdae7;
  border-radius: 9px;
  padding: 9px 10px;
  font-size: 13px;
  font-family: inherit;
  background: #fff;
  outline: none;
}
.template-bar select:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}
/* Pushes "Save as template" to the far end - loading and saving are
   opposite actions and shouldn't sit adjacent enough to misclick. */
.template-spacer {
  flex: 1 1 auto;
}
.template-btn {
  border: 1px solid #cfdae7;
  background: #fff;
  color: var(--blue-dark);
  border-radius: 9px;
  padding: 9px 14px;
  font-size: 12.5px;
  font-weight: 850;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: 0.15s;
}
.template-btn:hover {
  border-color: var(--blue);
  background: var(--soft-blue);
}
.template-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.template-btn.danger {
  color: #b3271d;
}
.template-btn.danger:hover {
  border-color: #e0857d;
  background: #fdeceb;
}
.template-hint {
  margin: -4px 0 16px;
  font-size: 11.5px;
  color: var(--portal-muted);
}

@media (max-width: 620px) {
  /* The label owns a full row so the select keeps a usable width. */
  .template-bar label {
    flex: 1 1 100%;
  }
  .template-spacer {
    display: none;
  }
  .template-btn {
    flex: 1 1 auto;
  }
}

/* --------------------------------------------------------------------------
   Unread-message alert banner (portal/contractor/dashboard.php, see
   assets/php/unread-alerts.php) - deliberately louder and more urgent
   than the calm "Needs you today" briefing card just below it. A message
   sitting unanswered is a different kind of urgent than most of what
   that card tracks, and "never missed" only holds if this is impossible
   to scroll past without noticing.
   -------------------------------------------------------------------------- */
.unread-alert-banner {
  background: #fff7ed;
  border: 1px solid #f3c98f;
  border-left: 5px solid #d97a06;
  border-radius: 14px;
  padding: 18px 20px;
  margin-bottom: 20px;
  box-shadow: var(--portal-shadow);
}
.unread-alert-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}
.unread-alert-head svg {
  flex: none;
  width: 22px;
  height: 22px;
  color: #d97a06;
}
.unread-alert-head strong {
  display: block;
  font-size: 16px;
  color: #7a4a05;
}
.unread-alert-head span {
  display: block;
  font-size: 12.5px;
  color: #9c6a1f;
  margin-top: 1px;
}
.unread-alert-list {
  display: grid;
  gap: 8px;
}
.unread-alert-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  background: #fff;
  border: 1px solid #f3d9ab;
  border-radius: 10px;
  padding: 11px 14px;
  text-decoration: none;
  color: var(--ink);
  transition: 0.15s;
}
.unread-alert-row:hover {
  border-color: #d97a06;
  box-shadow: 0 4px 14px rgba(217, 122, 6, 0.14);
}
.unread-alert-type {
  flex: none;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 6px;
  color: #fff;
}
.unread-alert-type-job { background: var(--blue); }
.unread-alert-type-lead { background: #7a5cc0; }
.unread-alert-type-pm { background: #0f9a8e; }
.unread-alert-label {
  font-weight: 800;
  font-size: 13.5px;
  flex: none;
}
.unread-alert-snippet {
  font-size: 13px;
  color: var(--portal-muted);
  font-style: italic;
  overflow-wrap: anywhere;
}
.unread-alert-when {
  margin-left: auto;
  flex: none;
  font-size: 11.5px;
  color: var(--portal-muted);
  white-space: nowrap;
}
@media (max-width: 640px) {
  .unread-alert-row { flex-direction: column; align-items: flex-start; }
  .unread-alert-when { margin-left: 0; }
}

/* --------------------------------------------------------------------------
   "Needs you today" briefing card (portal/contractor/dashboard.php).
   Grouped, color-coded, tap-to-open rows - the one place on the whole
   portal meant to be scanned in five seconds, not read.
   -------------------------------------------------------------------------- */
.briefing-card {
  border-width: 1px;
}
.briefing-group {
  border: 1px solid var(--portal-line);
  border-left-width: 4px;
  border-radius: 10px;
  padding: 12px 14px 6px;
  margin-bottom: 14px;
}
.briefing-group:last-child {
  margin-bottom: 0;
}
.briefing-group.red {
  border-left-color: #c73d3d;
  background: #fffafa;
}
.briefing-group.amber {
  border-left-color: #b6790a;
  background: #fffcf5;
}
.briefing-group.blue {
  border-left-color: var(--blue);
  background: #f8fbff;
}
.briefing-group-title {
  font-size: 13px;
  font-weight: 850;
  color: var(--ink);
  margin-bottom: 6px;
}
.briefing-group.red .briefing-group-title { color: #a3302f; }
.briefing-group.amber .briefing-group-title { color: #8a5c07; }
.briefing-group.blue .briefing-group-title { color: var(--blue-dark); }

.briefing-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 8px 6px;
  margin: 0 -6px;
  border-radius: 7px;
  font-size: 13px;
  color: var(--ink);
  transition: background 0.12s;
}
a.briefing-row:hover {
  background: rgba(11, 101, 221, 0.06);
}
.briefing-row-label {
  font-weight: 750;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.briefing-row-detail {
  flex: 0 0 auto;
  color: var(--portal-muted);
  font-size: 12px;
  white-space: nowrap;
}
.briefing-more {
  font-size: 11.5px;
  color: var(--portal-muted);
  padding: 4px 6px 8px;
}

@media (max-width: 560px) {
  .briefing-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
  .briefing-row-label,
  .briefing-row-detail {
    white-space: normal;
    overflow: visible;
  }
}

/* --------------------------------------------------------------------------
   Quote-response and schedule-response pages (portal/quote-response.php,
   portal/schedule-response.php) - the public, no-login pages an emailed
   decision link lands on.
   -------------------------------------------------------------------------- */
/* The line-item summary on quote-response.php - a bespoke, lighter list
   rather than the shared .table-wrap component, which enforces a 700px
   table min-width tuned for admin data tables with many columns. A
   two-column Description/Amount summary on a 640px-wide card doesn't
   need anywhere near that, and was only getting a pointless horizontal
   scrollbar out of it. */
.quote-lines {
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  overflow: hidden;
  margin: 22px 0;
}
.quote-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 14px 18px;
  font-size: 14px;
}
.quote-line + .quote-line {
  border-top: 1px solid #eef2f7;
}
.quote-line-desc {
  color: var(--ink);
  font-weight: 650;
}
.quote-line-amt {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}

/* The number that actually matters gets its own soft-tinted panel
   instead of sitting as plain text under the line items - the one
   figure a customer's eye should land on. */
.quote-total-panel {
  background: var(--soft-blue);
  border-radius: 14px;
  padding: 4px 20px;
  margin: 0 0 28px;
}
.quote-total-panel .doc-totals {
  width: 100%;
  margin-left: 0;
}
.quote-total-panel .row.grand {
  border-top-color: rgba(11, 101, 221, 0.25);
}
.quote-total-panel .row.grand b {
  color: var(--blue-dark);
}

/* portal/schedule-response.php's "does this date work?" card - the
   date/time equivalent of .quote-total-panel above: one thing the
   customer's eye should land on, given real visual weight instead of
   sitting in a plain sentence. The little day-tile on the left mimics a
   calendar-invite at a glance (day-of-week/day-number/month), which
   reads faster than parsing a full date string first. */
.schedule-proposal-card {
  display: flex;
  align-items: center;
  gap: 20px;
  background: var(--soft-blue);
  border-radius: 16px;
  padding: 20px 24px;
  margin: 20px 0 28px;
}
.schedule-proposal-tile {
  flex: none;
  width: 72px;
  height: 78px;
  background: #fff;
  border-radius: 12px;
  box-shadow: var(--portal-shadow);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.15;
}
.schedule-proposal-dow,
.schedule-proposal-month {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.schedule-proposal-dow {
  color: var(--blue);
}
.schedule-proposal-month {
  color: var(--portal-muted);
}
.schedule-proposal-day {
  font-size: 28px;
  font-weight: 800;
  color: var(--navy);
}
.schedule-proposal-info {
  min-width: 0;
}
.schedule-proposal-when {
  font-size: 16px;
  font-weight: 800;
  color: var(--navy);
  line-height: 1.35;
}
.schedule-proposal-service {
  font-size: 13.5px;
  font-weight: 650;
  color: var(--blue-dark);
  margin-top: 4px;
}
.schedule-proposal-address {
  font-size: 12.5px;
  color: var(--portal-muted);
  margin-top: 3px;
}

/* Accept / Decline share the row at equal width (both a real button,
   just no longer each claiming the full card width and stacking) -
   .decision-actions already handles the generic wrap/gap layout; this
   is the one place both children need to split it evenly regardless of
   whether they're a <form> or a bare <button>. */
.quote-decision-row.decision-actions {
  gap: 12px;
}
.quote-decision-row.decision-actions form,
.quote-decision-row.decision-actions > .btn {
  flex: 1 1 0;
  min-width: 0;
}

/* Visually separates "the decision" from "the ongoing conversation"
   below it, rather than the chat just trailing off the bottom of the
   card with no transition. */
.quote-chat-section {
  margin-top: 34px;
  padding-top: 28px;
  border-top: 1px solid var(--portal-line);
}

.decision-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 22px;
}
.decision-actions form {
  margin: 0;
  flex: 1 1 auto;
}
.decision-actions .btn {
  width: 100%;
}
.clarify-form {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--portal-line);
}
.clarify-form textarea {
  width: 100%;
  border: 1px solid #cbd9e8;
  border-radius: 9px;
  padding: 11px 12px;
  font-family: inherit;
  font-size: 13.5px;
  resize: vertical;
  outline: none;
}
.clarify-form textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.1);
}

/* --------------------------------------------------------------------------
   "Why didn't we get it?" reason pickers - the decline step on
   quote-response.php and the whole of loss-feedback.php.

   Big tappable rows rather than a bare radio list: this is the one moment
   a customer is willing to tell us something useful, usually on a phone,
   and a cramped list of 12px radios is how that goodwill gets lost.
   -------------------------------------------------------------------------- */
.reason-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.reason-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  border: 1px solid #cbd9e8;
  border-radius: 10px;
  font-size: 13.5px;
  cursor: pointer;
  background: #fff;
  transition: border-color 0.12s, background 0.12s;
}
.reason-option:hover {
  border-color: var(--blue);
  background: #f7fbff;
}
.reason-option input {
  accent-color: var(--blue);
  width: 17px;
  height: 17px;
  flex: none;
  margin: 0;
}
/* :has() lets the whole row react to its own radio being chosen without
   any JS. Unsupported browsers just don't get the highlight - the radio
   itself is still visibly checked, so nothing is actually lost. */
.reason-option:has(input:checked) {
  border-color: var(--blue);
  background: var(--soft-blue);
  font-weight: 700;
}
.optional-tag {
  font-weight: 700;
  font-size: 11px;
  color: var(--portal-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-left: 6px;
}

/* Won / Lost / Reopen row on the contractor quote page. */
.outcome-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

@media (max-width: 520px) {
  .decision-actions {
    flex-direction: column;
  }
  .outcome-actions .btn {
    width: 100%;
  }
}

/* --------------------------------------------------------------------------
   Quote decision & scheduling proposal card (portal/contractor/quote.php).
   -------------------------------------------------------------------------- */
.proposal-block {
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  padding: 16px 18px;
  margin-bottom: 16px;
  background: #fbfdff;
}
.proposal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.link-btn {
  border: 0;
  background: none;
  padding: 0;
  color: var(--blue);
  font-weight: 850;
  font-size: 12.5px;
  cursor: pointer;
  text-decoration: underline;
  display: block;
  margin-top: 6px;
}
.link-btn:hover {
  color: var(--blue-dark);
}

/* --------------------------------------------------------------------------
   Customer directory (portal/contractor/customers.php) and its detail modal.

   The modal itself reuses the .modal / .modal-card furniture defined above
   for the schedule date picker - centred, dimmed backdrop, body scroll
   locked - so there is only one modal implementation in the portal.
   -------------------------------------------------------------------------- */

/* Search box that sits in a .card-head, opposite the heading. Same
   border color/radius/focus ring as every .field input - only the
   padding and font-size are its own, deliberately more compact for a
   header search box than a full form field. */
.table-search {
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 9px 12px;
  font-family: inherit;
  font-size: 13px;
  min-width: 240px;
  outline: none;
  background: #fff;
}
.table-search:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12);
}

/* A link that shouldn't shout - email/phone inside a table cell, where a
   full-strength link colour on every row would turn the table into stripes. */
.plain-link {
  color: inherit;
  text-decoration: none;
}
.plain-link:hover {
  color: var(--blue);
  text-decoration: underline;
}
.muted {
  color: var(--portal-muted);
}

/* "3 projects >" expander next to a customer's name. */
.proj-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 8px;
  padding: 2px 8px;
  border: 1px solid var(--portal-line);
  border-radius: 999px;
  background: #fff;
  color: var(--portal-muted);
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 800;
  cursor: pointer;
  white-space: nowrap;
}
.proj-toggle:hover {
  border-color: var(--blue);
  color: var(--blue);
}
.proj-toggle .chev {
  transition: transform 0.15s;
  font-size: 13px;
  line-height: 1;
}
.proj-toggle[aria-expanded="true"] .chev {
  transform: rotate(90deg);
}
.table-wrap tr.proj-rows td {
  background: #f7fafd;
  padding: 10px 14px;
}

/* Shared by the inline expander and the modal's Jobs/Visits lists. */
.proj-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.proj-item {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  border: 1px solid var(--portal-line);
  border-radius: 9px;
  background: #fff;
  text-decoration: none;
  color: inherit;
  font-size: 12.5px;
}
a.proj-item:hover {
  border-color: var(--blue);
  box-shadow: 0 1px 6px rgba(11, 101, 221, 0.1);
}
.proj-item.static {
  cursor: default;
}
.proj-name {
  font-weight: 800;
}
.proj-when {
  color: var(--portal-muted);
  font-size: 11.5px;
}
.proj-amount {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
}

/* Modal internals. */
.cust-metrics {
  margin-bottom: 14px;
}
.metric.warn .metric-value {
  color: #9a6100;
}
.cust-contact {
  margin-bottom: 4px;
}
.cust-section {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--portal-muted);
  margin: 20px 0 10px;
  padding-top: 14px;
  border-top: 1px solid var(--portal-line);
}
.cust-section .count {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--soft-blue);
  color: var(--blue-dark);
  font-size: 10.5px;
}
.cust-review {
  border: 1px solid var(--portal-line);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 8px;
  background: #fff;
}
.cust-review-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.cust-review .stars {
  color: #e8a317;
  letter-spacing: 1px;
}
.cust-review p {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
}
.cust-review-date {
  margin-top: 6px;
  font-size: 11px;
  color: var(--portal-muted);
}

@media (max-width: 720px) {
  .table-search {
    min-width: 0;
    width: 100%;
    margin-top: 10px;
  }
  /* Four columns of job metadata don't fit a phone - stack them and let
     the status pill sit under the name rather than squeezing all four. */
  .proj-item {
    grid-template-columns: 1fr auto;
    row-gap: 4px;
  }
  .cust-metrics {
    grid-template-columns: 1fr;
  }
}

/* --------------------------------------------------------------------------
   "Would you share that publicly?" prompt, shown on portal/review.php after
   a customer has left a review - whatever rating they gave it.
   -------------------------------------------------------------------------- */
.google-review-prompt {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  background: #f8fbff;
}
.google-review-prompt > div {
  flex: 1 1 260px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.google-review-prompt strong {
  font-size: 13.5px;
}
.google-review-prompt span {
  font-size: 12.5px;
  color: var(--portal-muted);
  line-height: 1.5;
}

/* --------------------------------------------------------------------------
   Job gates - the twelve-step tracker
   --------------------------------------------------------------------------
   One horizontal line from "Quote Received" to "Job Complete", shown to
   the customer and the contractor from the same markup so the two sides
   are literally looking at the same picture. Rendered by
   assets/php/job-gates.php; nothing here decides which gate is current.

   Twelve labels in a row is a lot of text, so four phase bands sit above
   the dots (Quote / Schedule / Work / Billing) and carry the scanning
   load - you find the band first, then the dot.
   -------------------------------------------------------------------------- */
.gate-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin: 18px 0 4px;
}
.gate-now {
  display: flex;
  align-items: center;
  gap: 13px;
  min-width: 0;
}
.gate-num {
  width: 46px;
  height: 46px;
  border-radius: 13px;
  background: var(--soft-blue);
  color: var(--blue-dark);
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 850;
  line-height: 1.15;
  text-align: center;
  flex: 0 0 auto;
}
.gate-num b {
  display: block;
  font-size: 17px;
  line-height: 1;
}
.gate-num.lost {
  background: #fff0f0;
  color: #a3302f;
}
.gate-now h3 {
  font-size: 16.5px;
  letter-spacing: -0.02em;
  margin: 0;
}
.gate-now p {
  font-size: 12px;
  color: var(--portal-muted);
  margin: 3px 0 0;
}
.gate-meter {
  flex: 1 1 200px;
  max-width: 340px;
}
.gate-meter-track {
  height: 7px;
  border-radius: 99px;
  background: #e3eaf3;
  overflow: hidden;
}
.gate-meter-track i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--blue), var(--blue-dark));
}
.gate-meter-track.lost i {
  background: #c9736f;
}
/* Amber fill for a gauge that's climbing but not yet critical - the job
   gate tracker only ever needed on-track (blue) or stopped (.lost, red);
   the Site Reliability quota gauges need a middle "worth watching" state
   too. */
.gate-meter-track.warn i {
  background: linear-gradient(90deg, #d9a13c, #b6790a);
}
.gate-meter small {
  display: block;
  font-size: 10px;
  color: var(--portal-muted);
  margin-top: 6px;
  text-align: right;
  font-weight: 800;
  letter-spacing: 0.03em;
}

.gate-track {
  margin-top: 20px;
}
.gate-bands {
  display: grid;
  grid-template-columns: repeat(var(--gate-cols, 13), 1fr);
  gap: 6px;
  margin-bottom: 13px;
}
.gate-band {
  border-radius: 8px;
  padding: 6px;
  background: #f4f8fd;
  border: 1px solid #e6eef8;
  font-size: 9px;
  font-weight: 850;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #8090a3;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gate-band.on {
  background: var(--soft-blue);
  border-color: #bcdafb;
  color: var(--blue-dark);
}
.gate-band.past {
  background: #edf8f3;
  border-color: #c6e8d8;
  color: #146848;
}
.gate-band.halted {
  background: #fff0f0;
  border-color: #f0c9c8;
  color: #a3302f;
}

.gate-dots {
  display: grid;
  grid-template-columns: repeat(var(--gate-cols, 13), 1fr);
  position: relative;
}
/* The rail sits behind the dots and stops half a column (--gate-inset,
   set alongside --gate-cols on .gate-track above) in from each end so it
   never pokes out past the first and last markers. */
.gate-dots::before,
.gate-dots::after {
  content: "";
  position: absolute;
  top: 15px;
  height: 3px;
  border-radius: 99px;
}
.gate-dots::before {
  left: var(--gate-inset, 3.8461%);
  right: var(--gate-inset, 3.8461%);
  background: #dde7f2;
}
.gate-dots::after {
  left: var(--gate-inset, 3.8461%);
  width: var(--gate-fill, 0%);
  background: linear-gradient(90deg, var(--blue), var(--blue-dark));
}
.gate-track.lost .gate-dots::after {
  background: #c9736f;
}
.gate {
  position: relative;
  z-index: 2;
  padding: 0 3px;
  min-width: 0;
  /* Two belt-and-suspenders fixes for the same complaint: a label not
     sitting centered under its own dot.

     min-width: 0 stops a grid item's automatic minimum width (its
     content's min-content size - the width of the longest unbreakable
     word in a label like "Quote Approval Pending") from forcing that ONE
     track wider than its 1fr share, which would desync this
     var(--gate-cols)-column grid from .gate-bands above it (locked to
     1fr only because overflow:hidden already zeroes ITS automatic
     minimum) and drift the dots out from under their own phase band.

     display: flex + align-items: center is the centering itself, done
     explicitly rather than left to text-align + default block stacking -
     the more robust of the two against any per-browser quirk in how a
     <button> (the interactive tracker on the contractor page) lays out
     its own block children versus a plain <div> (the read-only tracker
     everywhere else). Both element types get identical, guaranteed
     centering this way regardless of UA button defaults. */
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.gate-dot {
  width: 33px;
  height: 33px;
  border-radius: 50%;
  margin: 0 auto 8px;
  background: #fff;
  border: 3px solid #d5e0ec;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 850;
  color: #93a1b2;
}
.gate.done .gate-dot {
  border-color: var(--blue);
  background: var(--blue);
  color: #fff;
}
.gate.active .gate-dot {
  border-color: var(--blue);
  background: #fff;
  color: var(--blue-dark);
  box-shadow: 0 0 0 5px rgba(11, 101, 221, 0.14);
}
.gate.halted .gate-dot {
  border-color: #c0504d;
  background: #fff;
  color: #a3302f;
  box-shadow: 0 0 0 5px rgba(192, 80, 77, 0.13);
}
.gate label {
  display: block;
  font-size: 9px;
  font-weight: 850;
  line-height: 1.3;
  color: #8b99aa;
  overflow-wrap: break-word;
}
.gate.done label {
  color: #4d5f75;
}
.gate.active label,
.gate.halted label {
  color: var(--ink);
}
.gate small {
  display: block;
  font-size: 8.5px;
  color: #97a4b5;
  margin-top: 3px;
  font-weight: 800;
}

/* The "you are here" pill, coloured by phase band rather than by the
   old status vocabulary - it names a gate, not a status, and reusing
   .status here would have made two different things look identical. */
.gate-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 11px;
  font-weight: 850;
  white-space: nowrap;
  letter-spacing: 0.01em;
  color: #9a6100;
  background: #fff4d8;
}
.gate-pill::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.gate-pill.schedule,
.gate-pill.billing {
  color: var(--blue-dark);
  background: var(--soft-blue);
}
.gate-pill.work {
  color: #0d6b78;
  background: #e2f4f7;
}
.gate-pill.done {
  color: #146848;
  background: #e6f6ee;
}

/* Compact form for list rows: pill above, thin bar + "7/12" below. */
.gate-mini {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
  min-width: 130px;
}
.gate-mini-bar {
  flex: 1;
  height: 5px;
  border-radius: 99px;
  background: #e3eaf3;
  overflow: hidden;
  min-width: 44px;
}
.gate-mini-bar i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--blue);
}
.gate-mini.lost .gate-mini-bar i {
  background: #c9736f;
}
.gate-mini b {
  font-size: 10px;
  color: var(--portal-muted);
  white-space: nowrap;
  font-weight: 850;
}

/* The two stored gates' buttons, on the contractor job page. */
.gate-actions {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px dashed var(--portal-line);
}
.gate-actions .hint {
  font-size: 11.5px;
  color: var(--portal-muted);
  margin-right: auto;
  max-width: 380px;
  line-height: 1.45;
}

/* Below ~900px twelve columns stop fitting, so the track scrolls
   sideways at a fixed column width instead of crushing the labels into
   unreadable slivers. The rail is hidden there because a background line
   that scrolls independently of its dots looks broken - the done/active
   dot styling still carries the state on its own. */
@media (max-width: 900px) {
  .gate-track {
    overflow-x: auto;
    padding-bottom: 8px;
  }
  .gate-bands,
  .gate-dots {
    grid-template-columns: repeat(var(--gate-cols, 13), 106px);
  }
  .gate-dots::before,
  .gate-dots::after {
    display: none;
  }
  .gate-summary {
    gap: 12px;
  }
  .gate-meter {
    max-width: none;
  }
}
@media (max-width: 560px) {
  .gate-actions {
    flex-direction: column;
    align-items: stretch;
  }
  .gate-actions .hint {
    max-width: none;
    margin-right: 0;
  }
  .gate-num {
    width: 40px;
    height: 40px;
  }
}

/* --------------------------------------------------------------------------
   Stage cards: clickable gate bubbles + the put-away bar
   -------------------------------------------------------------------------- */
.gate-click {
  font: inherit;
  border: 0;
  background: none;
  padding: 0 3px;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
.gate-click:hover .gate-dot {
  border-color: var(--blue);
  transform: translateY(-1px);
}
.gate-click:hover label {
  color: var(--ink);
}
.gate-click:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 10px;
}
/* A pinned stage gets a ring on its bubble, so the tracker doubles as the
   legend for what's currently on the page. */
.gate-click.pinned .gate-dot {
  box-shadow: 0 0 0 3px #fff, 0 0 0 6px rgba(11, 101, 221, 0.35);
}
.gate-click.pinned label {
  color: var(--blue-dark);
}

.gate-cards-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px dashed var(--portal-line);
}
.gate-hint-line {
  font-size: 12px;
  color: var(--portal-muted);
  margin: 0;
  max-width: 460px;
  line-height: 1.5;
}
.gate-cards-controls {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}
.gate-hidden-count {
  font-size: 11.5px;
  font-weight: 800;
  color: var(--portal-muted);
  white-space: nowrap;
}

/* A card that is only on screen because someone pinned it - distinct from
   one the job actually needs, so the page still reads as "here's what's
   left" at a glance. */
.card.stage-pinned,
details.card.stage-pinned {
  border-left: 3px solid var(--blue);
}
.stage-why {
  margin-top: 14px;
}
@keyframes stageFlash {
  from { box-shadow: 0 0 0 4px rgba(11, 101, 221, 0.28); }
  to   { box-shadow: 0 0 0 4px rgba(11, 101, 221, 0); }
}
.stage-flash {
  animation: stageFlash 1.4s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .stage-flash { animation: none; }
  .gate-click:hover .gate-dot { transform: none; }
}

/* --------------------------------------------------------------------------
   Paying an invoice
   --------------------------------------------------------------------------
   The method chooser is the whole design idea: each way to pay is a card
   carrying its own real total, so a card fee is something you can see and
   compare rather than a percentage in a footnote. That is both the
   compliant way to disclose a surcharge and the reason people cheerfully
   pick the bank option.
   -------------------------------------------------------------------------- */
.pay-card { text-align: left; }
.pay-amount {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: 22px 0 4px;
  padding: 18px 20px;
  border-radius: 14px;
  background: linear-gradient(135deg, var(--soft-blue), #f7fbff);
  border: 1px solid #d6e7fa;
}
.pay-amount-label {
  font-size: 11px;
  font-weight: 850;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--blue-dark);
}
.pay-amount-value {
  font-size: 34px;
  font-weight: 900;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.pay-breakdown { margin-top: 16px; }
.pay-breakdown-row {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  font-size: 13px;
  padding: 9px 0;
  border-bottom: 1px solid var(--portal-line);
}
.pay-breakdown-row:last-child { border-bottom: 0; }
.pay-breakdown-row.now { font-weight: 850; }
.pay-breakdown-row span { color: var(--portal-muted); display: flex; align-items: center; gap: 8px; }
.pay-breakdown-row.now span { color: var(--ink); }

.pay-heading { font-size: 19px; margin: 26px 0 14px; }
.pay-methods { display: grid; gap: 11px; }
.pay-method {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 16px 18px;
  border: 2px solid var(--portal-line);
  border-radius: 14px;
  background: #fff;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.pay-method:hover { border-color: #9cc4f4; transform: translateY(-1px); }
.pay-method:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.pay-method.selected {
  border-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(11, 101, 221, 0.12);
}
.pay-method-top { display: flex; align-items: center; gap: 14px; min-width: 0; }
.pay-method-icon {
  width: 42px; height: 42px; border-radius: 11px; flex: 0 0 auto;
  background: var(--soft-blue) center/20px 20px no-repeat;
  /* Inline SVG data URIs: the payment page must never wait on, or leak a
     request to, a third-party icon host. */
}
.pay-method-icon.card { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230853bd' stroke-width='2'%3E%3Crect x='2' y='5' width='20' height='14' rx='2'/%3E%3Cpath d='M2 10h20'/%3E%3C/svg%3E"); }
.pay-method-icon.bank { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230853bd' stroke-width='2'%3E%3Cpath d='M3 10h18M5 10v9M19 10v9M9 10v9M15 10v9M2 21h20M12 3l9 5H3z'/%3E%3C/svg%3E"); }
.pay-method-icon.link { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230853bd' stroke-width='2'%3E%3Cpath d='M10 13a5 5 0 007.5.5l3-3a5 5 0 00-7-7l-1.5 1.5M14 11a5 5 0 00-7.5-.5l-3 3a5 5 0 007 7L12 19'/%3E%3C/svg%3E"); }
.pay-method-icon.cashapp { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230853bd' stroke-width='2'%3E%3Cpath d='M12 2v20M17 6H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6'/%3E%3C/svg%3E"); }
.pay-method-name b { display: block; font-size: 14.5px; }
.pay-method-name span { display: block; font-size: 11.5px; color: var(--portal-muted); line-height: 1.4; margin-top: 2px; }
.pay-method-money { text-align: right; flex: 0 0 auto; }
.pay-method-money b { display: block; font-size: 19px; font-weight: 900; letter-spacing: -0.02em; }
.pay-fee { display: block; font-size: 10.5px; color: #9a6100; font-weight: 800; margin-top: 2px; }
.pay-save { display: block; font-size: 10.5px; color: #146848; font-weight: 850; margin-top: 2px; }
.pay-fee-none { display: block; font-size: 10.5px; color: var(--portal-muted); font-weight: 800; margin-top: 2px; }
.pay-speed { display: block; font-size: 10px; color: #93a1b2; margin-top: 3px; }
.pay-fee-note { margin-top: 14px; line-height: 1.6; }
.pay-go { margin-top: 20px; width: 100%; justify-content: center; font-size: 15px; }

/* The trust panel. Not decoration - "is it safe to type my card into a
   contractor's website" is the real question at this moment, and the
   honest answer is genuinely reassuring, so it gets room to be read. */
.pay-trust { margin-top: 20px; background: linear-gradient(135deg, #fbfdff, #f4f9ff); }
.pay-trust-head { display: flex; align-items: flex-start; gap: 14px; }
.pay-trust-head h2 { font-size: 17px; margin: 0; }
.pay-lock {
  width: 44px; height: 44px; border-radius: 12px; flex: 0 0 auto;
  background: #e6f6ee; display: grid; place-items: center; font-size: 20px;
}
.pay-trust-list { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 11px; }
.pay-trust-list li {
  font-size: 12.5px; line-height: 1.55; color: var(--portal-muted);
  padding-left: 26px; position: relative;
}
.pay-trust-list li::before {
  content: "\2713";
  position: absolute; left: 0; top: 0;
  width: 18px; height: 18px; border-radius: 50%;
  background: #e6f6ee; color: #146848;
  display: grid; place-items: center; font-size: 10px; font-weight: 900;
}
.pay-trust-list b { color: var(--ink); }

/* Result pages */
.pay-result { text-align: center; }
.pay-result h1 { margin-top: 6px; }
.pay-result .sub { max-width: 560px; margin-left: auto; margin-right: auto; }
.pay-done-mark {
  width: 66px; height: 66px; border-radius: 50%; margin: 0 auto 18px;
  background: #e6f6ee; color: #146848;
  display: grid; place-items: center; font-size: 32px; font-weight: 900;
}
.pay-result.good .pay-done-mark { animation: payPop 0.45s cubic-bezier(0.2, 1.4, 0.4, 1); }
@keyframes payPop { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.pay-next {
  text-align: left; margin: 22px auto 0; max-width: 560px;
  padding: 18px 20px; border-radius: 14px;
  background: var(--soft-blue); border: 1px solid #d6e7fa;
}
.pay-next h2 { font-size: 15px; margin: 0 0 10px; }
.pay-next ol { margin: 0; padding-left: 20px; }
.pay-next li { font-size: 13px; color: var(--portal-muted); line-height: 1.6; margin-bottom: 6px; }
.pay-result-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 22px; }

/* A pay button sitting in the invoice list */
.pay-now-btn { white-space: nowrap; }
.invoice-pay-hint { font-size: 11px; color: var(--portal-muted); margin-top: 4px; }

@media (max-width: 560px) {
  .pay-method { flex-direction: column; align-items: stretch; gap: 12px; }
  .pay-method-money { text-align: left; }
  .pay-amount { flex-direction: column; gap: 6px; }
  .pay-amount-value { font-size: 28px; }
  .pay-result-actions .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .pay-method:hover { transform: none; }
  .pay-result.good .pay-done-mark { animation: none; }
}

/* Deposit / balance panel on the contractor's invoice card */
.pay-plan { display: grid; gap: 10px; margin-bottom: 18px; }
.pay-plan-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  padding: 14px 16px; border-radius: 12px;
  border: 1px solid var(--portal-line); background: #fbfdff;
}
.pay-plan-row b { display: block; font-size: 13.5px; }
.pay-plan-row span { display: block; font-size: 11.5px; color: var(--portal-muted); margin-top: 2px; }

/* --------------------------------------------------------------------------
   The sitewide "payment due" banner (portal-header.php, customer pages)
   --------------------------------------------------------------------------
   Deliberately not dismissible - see the comment on
   payment_due_banner_html() in assets/php/payments.php. Sits above every
   customer page's own content so it's the first thing seen regardless of
   which nav item was clicked to get there.
   -------------------------------------------------------------------------- */
.due-banner {
  border-radius: 16px;
  padding: 20px 22px;
  margin-bottom: 22px;
  background: linear-gradient(135deg, #fff8e8, #fffdf6);
  border: 1px solid #f0d99a;
}
.due-banner-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.due-banner-icon {
  width: 40px;
  height: 40px;
  border-radius: 11px;
  flex: 0 0 auto;
  background: #f7c948;
  color: #5a3d00;
  display: grid;
  place-items: center;
  font-size: 18px;
  font-weight: 900;
}
.due-banner-head h2 {
  font-size: 16.5px;
  margin: 0;
  color: #5a3d00;
}
.due-banner-head p {
  font-size: 13px;
  font-weight: 850;
  color: #8a5c07;
  margin: 3px 0 0;
}
.due-banner-list {
  margin-top: 15px;
  display: grid;
  gap: 9px;
}
.due-banner-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border-radius: 11px;
  background: rgba(255, 255, 255, 0.65);
  border: 1px solid rgba(240, 217, 154, 0.7);
}
.due-banner-row span {
  flex: 1 1 200px;
  font-size: 12.5px;
  font-weight: 750;
  color: #6b5115;
  min-width: 0;
}
.due-banner-row b {
  font-size: 15px;
  font-weight: 900;
  color: #5a3d00;
  white-space: nowrap;
}
.due-banner-row .btn {
  flex: 0 0 auto;
}
.due-banner-note {
  font-size: 11.5px;
  color: #8a7043;
  margin: 12px 0 0;
  line-height: 1.5;
}
@media (max-width: 560px) {
  .due-banner-row { flex-direction: column; align-items: stretch; }
  .due-banner-row b { order: -1; }
  .due-banner-row .btn { width: 100%; }
}

/* --------------------------------------------------------------------------
   Very narrow phones (iPhone SE and similar, ~320px)
   --------------------------------------------------------------------------
   The portal top bar is a fixed-height row of five things - hamburger,
   logo, flexible gap, avatar, "Main site" - and at 320px the sum of their
   intrinsic widths plus the bar's own 22px side padding exceeds the
   viewport, pushing the whole page into a horizontal scroll. Nothing here
   can wrap (a wrapped item in a fixed-height bar overflows vertically
   instead, which is worse), so the fix is to reclaim width: tighter side
   padding and a smaller logo, which together free ~38px - comfortably
   more than the ~21px that was overflowing.
   -------------------------------------------------------------------------- */
@media (max-width: 400px) {
  .portal-app .portal-top {
    padding: 0 12px;
    gap: 10px;
  }
  .portal-app .portal-brand img {
    width: 100px;
    height: 32px;
  }
  .portal-app .portal-top-right {
    gap: 6px;
  }
  .portal-app .back-to-site {
    padding: 7px 8px;
    font-size: 11px;
  }
  .portal-ribbon .inner {
    width: calc(100% - 20px);
  }
}

/* --------------------------------------------------------------------------
   Defensive wrapping for anything holding customer-supplied text
   --------------------------------------------------------------------------
   Headings and summary lines in the portal print values that came from a
   person: a customer's name, a service label, a job address, a pasted URL
   in the details field. A long unbroken run of characters in any of those
   has no wrap opportunity, so the heading grows past the card, drags the
   layout with it and puts a horizontal scrollbar on the whole page - at
   which point every other element on the page looks broken too.

   overflow-wrap: break-word only breaks a word that would ELSE overflow,
   so normal text is completely unaffected. This costs nothing on good
   data and contains the damage from bad data.
   -------------------------------------------------------------------------- */
.page-head h1,
.card-head h1,
.card-head h2,
.card h2,
.card h3,
.quote-card h2,
.quote-card .meta,
.p-sub,
.card .sub,
.page-head p,
.detail-grid span,
.customer-cell strong,
.customer-cell span,
.invoice-item strong,
.invoice-item span,
.gate-now h3 {
  overflow-wrap: break-word;
  word-break: break-word;
}

/* Free-text blocks that preserve the customer's own line breaks with
   white-space: pre-line. pre-line still will not break INSIDE a long
   unbroken run - a pasted URL, a long reference number - so these need
   the same defensive break as the headings above, or one pasted link in a
   project description drags the whole page sideways. */
.quote-card .scope,
.review-item .comment,
.doc-scope,
.doc-foot p,
.notice,
.gate-step span,
.gate-legend-item span,
.due-banner-row span {
  overflow-wrap: break-word;
  word-break: break-word;
}

/* --------------------------------------------------------------------------
   Contractors screen: the crew, and the people who want to join it
   -------------------------------------------------------------------------- */
.metric.metric-alert {
  background: #fff8e8;
  border-color: #f0d99a;
}
.metric.metric-alert .metric-value { color: #8a5c07; }
/* Red tier - .metric-alert/.warn above only ever needed amber; the Site
   Reliability recap tiles need a third, more urgent state on top of that
   (same red used everywhere else in the portal: .notice.bad, .status.bad). */
.metric.bad {
  background: #fff5f5;
  border-color: #efbcbc;
}
.metric.bad .metric-value { color: #a3302f; }
.metric.bad::before { background: linear-gradient(180deg, #c73d3d, #a3302f); }

/* --- current crew --- */
.crew-list { display: grid; gap: 10px; }
.crew-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px 16px;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  background: #fff;
}
.crew-avatar {
  width: 40px; height: 40px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center;
  font-weight: 900; font-size: 15px;
  background: var(--soft-blue); color: var(--blue-dark);
}
/* Employees read as the "full" state, so they get the solid treatment -
   the difference between the two is a permissions difference and worth
   seeing at a glance rather than reading. */
.crew-avatar.emp { background: linear-gradient(135deg, var(--blue), #063f8d); color: #fff; }
.crew-who { flex: 1 1 240px; min-width: 0; }
.crew-who strong { display: block; font-size: 14.5px; }
.crew-who span {
  display: block; font-size: 12px; color: var(--portal-muted);
  margin-top: 2px; overflow-wrap: break-word;
}
.crew-type { flex: 0 0 auto; }
/* display:flex + gap rather than relying on inline-element whitespace
   between the two buttons now sitting here (Move to X, Archive) - that
   whitespace collapses to a font-size-dependent sliver, not a real gap. */
.crew-actions { flex: 0 0 auto; margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* --- leads --- */
.lead-list { display: grid; gap: 10px; }
.lead {
  border: 1px solid var(--portal-line);
  border-radius: 13px;
  background: #fff;
  overflow: hidden;
}
.lead[open] { border-color: #bcdafb; box-shadow: 0 6px 20px rgba(7, 28, 54, 0.05); }
.lead-summary { cursor: pointer; list-style: none; padding: 15px 18px; }
.lead-summary::-webkit-details-marker { display: none; }
.lead-summary:hover { background: #fbfdff; }
.lead-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.lead-who { flex: 1 1 260px; min-width: 0; }
.lead-who strong { display: block; font-size: 15px; overflow-wrap: break-word; }
.lead-who span { display: block; font-size: 12px; color: var(--portal-muted); margin-top: 2px; }
.lead-body { padding: 4px 18px 18px; border-top: 1px solid var(--portal-line); }
.lead-contact { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 18px; }

/* Live chat with one applicant, inline within their own row - see
   assets/js/portal-lead-chat.js. A soft card of its own rather than
   bare in the row, the same reasoning .quote-chat-section's divider
   gave the quote page: this is its own distinct thing, not more of the
   application details around it. */
.lead-chat-section {
  background: var(--portal-bg);
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  padding: 14px 16px;
  margin: 0 0 20px;
}
.lead-chat-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--portal-muted);
  margin-bottom: 10px;
}
.lead-chat-section .chat-thread {
  max-height: 280px;
  background: transparent;
}

.lead-cols { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 24px; }
.lead-cols h4 {
  font-size: 11px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase;
  color: #7b8ca0; margin: 0 0 8px;
}
.lead-cols h4 + p, .lead-cols h4 + ul, .lead-cols h4 + div { margin-bottom: 18px; }
.lead-text {
  font-size: 13.5px; line-height: 1.65; color: #405266; margin: 0;
  background: #f7fafd; border: 1px solid #e6eef8; border-radius: 10px; padding: 12px 14px;
  overflow-wrap: break-word; word-break: break-word;
}
.ref-out { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ref-out li {
  font-size: 12.5px; padding: 10px 12px;
  border: 1px solid var(--portal-line); border-radius: 9px; background: #fbfdff;
}
.ref-out b { display: block; font-size: 13px; }
.ref-out span { display: block; color: var(--portal-muted); margin-top: 2px; overflow-wrap: break-word; }
.ref-out em { display: block; font-size: 11px; color: #93a1b2; font-style: normal; margin-top: 3px; }
.lead-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.lead-photos a { display: block; border-radius: 9px; overflow: hidden; border: 1px solid var(--portal-line); }
.lead-photos img { width: 100%; height: 84px; object-fit: cover; display: block; background: #f4f8fd; }
.lead-photos a:hover { border-color: var(--blue); }

/* --- admin controls --- */
.lead-admin {
  margin-top: 20px; padding-top: 18px;
  border-top: 1px dashed var(--portal-line);
}
.lead-admin-row { display: grid; grid-template-columns: 260px 1fr; gap: 18px; }
.lead-admin .field { margin-bottom: 0; }
.lead-admin .field.grow { min-width: 0; }
.lead-admin .status-hint { margin: 6px 0 0; line-height: 1.5; }
.lead-admin-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-top: 14px;
}

@media (max-width: 860px) {
  .lead-cols { grid-template-columns: 1fr; gap: 4px; }
  .lead-admin-row { grid-template-columns: 1fr; }
  .crew-actions { margin-left: 0; width: 100%; }
}

/* --------------------------------------------------------------------------
   portal/job.php - the unified customer accordion
   --------------------------------------------------------------------------
   Panel base borrows .lead's shape (border/radius/overflow) rather than
   sharing the class outright - .lead's own rules assume a plain
   informational accordion, and these five states (done/action/active/
   upcoming/terminal) need their own left-edge color language on top of
   that shape. .job-help (the small FAQ block) uses the real .lead class
   directly instead - it has nothing to add. */
.job-panel-list { display: grid; gap: 10px; margin: 20px 0 28px; }
.job-panel {
  border: 1px solid var(--portal-line);
  border-left: 4px solid var(--portal-line);
  border-radius: 13px;
  background: #fff;
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.job-panel-summary {
  cursor: pointer;
  list-style: none;
  padding: 15px 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.job-panel-summary::-webkit-details-marker { display: none; }
.job-panel-summary:hover { background: #fbfdff; }
.job-panel-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.job-panel-label { font-size: 15px; font-weight: 800; color: var(--ink); }
.job-panel-preview { font-size: 12.5px; color: var(--portal-muted); }
.job-panel-body { padding: 4px 18px 20px; border-top: 1px solid var(--portal-line); }

.job-panel-badge {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 4px 10px;
  font-size: 10.5px; font-weight: 850; letter-spacing: 0.02em; white-space: nowrap;
}
.job-panel-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.job-panel-badge--done { color: #146848; background: #e6f6ee; }
.job-panel-badge--action { color: #1c7c46; background: #e5f8ed; }
.job-panel-badge--active { color: var(--blue-dark); background: var(--soft-blue); }
.job-panel-badge--upcoming { color: #8b99aa; background: #f1f4f8; }
.job-panel-badge--terminal { color: #a3302f; background: #fff0f0; }

/* Done - quiet, already-settled history. */
.job-panel--done { border-left-color: #bcead2; }

/* Your Turn - the one thing due from the customer right now. Vivid
   green, not the muted "done" green above - this is a call to action,
   and reusing .btn.success's own gradient keeps that meaning consistent
   with Accept Quote/Approve This Date, the very buttons living inside it. */
.job-panel--action {
  border-color: #bfe8cf;
  border-left-color: #1c7c46;
  box-shadow: 0 8px 24px rgba(28, 124, 70, 0.12);
}
.job-panel--action .job-panel-summary { background: linear-gradient(135deg, #eafbf1, #f4fef8); }
.job-panel--action .job-panel-label { color: #145c37; }

/* In Progress - the furthest-along step, but the ball is in OUR court,
   not the customer's. Same blue-glow language the contractor tracker's
   own .gate.active already uses for exactly this "here, not done" idea. */
.job-panel--active {
  border-color: #bcdafb;
  border-left-color: var(--blue);
  box-shadow: 0 0 0 4px rgba(11, 101, 221, 0.08);
}
.job-panel--active .job-panel-label { color: var(--blue-dark); }

/* Coming up - grayed, still readable, still expandable. */
.job-panel--upcoming { opacity: 0.72; }
.job-panel--upcoming .job-panel-label { color: #66748a; }

/* Closed - declined/lost. Never green. */
.job-panel--terminal { border-color: #f0c6c6; border-left-color: #c73d3d; }
.job-panel--terminal .job-panel-label { color: #a3302f; }

.job-help { margin-bottom: 18px; }
.job-help-hint { font-size: 12px; font-weight: 600; color: var(--portal-muted); }
.job-help .lead-body p { margin: 0 0 10px; line-height: 1.6; }
.job-help .lead-body p:last-child { margin-bottom: 0; }

/* --- compact button size ---------------------------------------------
   `.btn.small` - a smaller variant for buttons that sit inline in a
   table row or a tight card footer rather than standing alone as a
   page's main call to action (Call/Email on a lead card, Pay/Resend
   invite in a table cell, Dismiss next to a form, and the security
   page's per-account actions below). Originally written only for the
   security page as `.sec-btn`, then reused by name (`btn ... small`) in
   a dozen other places across the portal that all assumed a shared
   `.btn.small` already existed - it never did, so every one of those
   silently rendered as a full-size button. This is that missing
   definition, given the one shape (`.sec-btn`'s) that was actually
   designed and shipped, so every "small" button in the portal now
   really is the same size. */
.btn.small {
  padding: 7px 11px;
  font-size: 12px;
  border-radius: 9px;
  white-space: nowrap;
}

/* --- per-row grid actions: small icon buttons ------------------------
   Started on the Security page's Accounts table, which packed up to
   four text buttons (unlock/enable/disable/send reset/delete) plus a
   role dropdown into one cell - wide enough to wrap onto a second line
   on every row, which read as cluttered in a dense grid. Now the shared
   pattern for any grid with the same problem (Marketing's Campaigns
   table, a quote's Invoices list, ...): swap each button's visible text
   for a small square icon, with the label moved to a native title=""
   tooltip (and a matching aria-label, since there's no visible text
   left for assistive tech to read). Every one of these conversions kept
   the row's original data-* attributes and class-based JS selectors
   completely unchanged - .icon-btn only ever replaces what's INSIDE a
   button, never what selects it - so no JS needed to change either.
   .row-actions' flex-wrap stays on as a safety net for very narrow
   screens, but a row of ~30px squares fits on one line in the space
   the equivalent text buttons used to fight over. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.icon-btn {
  width: 30px;
  height: 30px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid #cfdae7;
  background: #fff;
  color: var(--portal-muted);
  cursor: pointer;
  transition: 0.15s;
  text-decoration: none;
}
.icon-btn svg {
  width: 15px;
  height: 15px;
}
.icon-btn:hover {
  border-color: var(--blue);
  color: var(--blue);
  background: var(--soft-blue);
}
.icon-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}
.icon-btn.danger {
  border-color: #f0c4c0;
  color: #b3271d;
}
.icon-btn.danger:hover {
  border-color: #d6483c;
  background: #fdecea;
  color: #b3271d;
}
/* For the rare action that's the clear "main" one in its row (Marketing's
   Send, for instance) - filled blue rather than another outlined
   square, so it still reads as the one action that actually matters
   among several quieter/neutral ones next to it. */
.icon-btn.primary {
  border-color: var(--blue);
  background: var(--blue);
  color: #fff;
}
.icon-btn.primary:hover {
  border-color: var(--blue-dark);
  background: var(--blue-dark);
  color: #fff;
}
.sec-role {
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  padding: 6px 8px;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  outline: none;
  background: #fff;
  color: var(--ink);
}
.sec-role:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12);
}
.sec-role:disabled {
  background: #f4f7fb;
  color: var(--portal-muted);
  cursor: not-allowed;
}

/* --- trace page: expandable visit rows -------------------------------
   <details>/<summary> instead of a JS accordion - the contents are
   already in the HTML, so it works with JS off and needs no component. */
.trace-visit {
  border: 1px solid var(--portal-line);
  border-radius: 13px;
  background: #fff;
  margin-bottom: 10px;
  overflow: hidden;
}
.trace-visit > summary {
  display: grid;
  grid-template-columns: 1.4fr 1.2fr 0.8fr 1fr;
  gap: 14px;
  align-items: center;
  padding: 14px 16px;
  cursor: pointer;
  list-style: none;
}
.trace-visit > summary::-webkit-details-marker { display: none; }
.trace-visit > summary:hover { background: #f8fafd; }
.trace-visit[open] > summary {
  background: #f8fafd;
  border-bottom: 1px solid var(--portal-line);
}
.trace-who,
.trace-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.trace-who strong,
.trace-meta .tiny { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trace-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.trace-visit > .table-wrap,
.trace-visit > p { margin: 0 16px 16px; }
.trace-visit > .table-wrap { border-radius: 11px; }

/* Names an IP address in place, right in the (collapsed) visit summary -
   see trace_set_ip_label() and portal-trace.js. Left-aligned and
   block-level rather than an inline icon button: on a screen full of
   these, "+ Label this address" as a visible, readable invitation gets
   used; a bare pencil glyph gets ignored. */
.trace-label-btn {
  align-self: flex-start;
  border: 0;
  background: none;
  padding: 1px 0;
  margin-top: 1px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  color: #9fb0c4;
  cursor: pointer;
  text-align: left;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: 0.14s;
}
.trace-label-btn:hover { color: var(--blue); text-decoration: underline; }
.trace-label-btn.is-labeled { color: var(--blue-dark); }
.trace-label-btn:disabled { opacity: 0.5; cursor: wait; }

@media (max-width: 860px) {
  .trace-visit > summary { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 520px) {
  .trace-visit > summary { grid-template-columns: 1fr; gap: 8px; }
}

/* --- section padding reset ------------------------------------------
   site.css sets `section { padding: 64px 0 }` for the marketing pages'
   full-bleed bands, and portal pages load site.css too (for the shared
   brand tokens and components like .chip-row). The Tools pages - Site
   Reliability, Security, Trace - use <section> purely as anchor targets
   for their jump-links, so they were inheriting 128px of dead space
   between every section. Same class of cross-stylesheet collision as
   the `.hint { display: flex }` one noted on `.field .hint` above:
   invisible until a portal page happened to use the element the public
   stylesheet had already claimed. */
.portal-body section {
  padding: 0;
}

/* --- sticky section chips (Tools pages) -------------------------------
   The Site Reliability / Security / Trace pages are long, and their
   .chip-row is real navigation, not decoration - it changes which part
   of the page you're looking at. Scrolling it off the top left the only
   way between sections as "scroll all the way back up", so it docks
   under the header instead.

   Docks at top: 0, not --portal-top-h: .dashboard-shell is its own fixed-
   height scroll box now (see the app-shell rules above), sitting entirely
   BELOW the fixed ribbon/header rather than scrolling underneath them, so
   there's no header height left to clear inside .dashboard-shell's own
   box - sticking to its very top edge is already exactly where the
   header's bottom edge is. z-index 40 keeps the chips above page content.

   The negative margins cancel the padding, so the resting position is
   exactly where it was before - the padding exists only to give the
   background something to cover content with once it's stuck, and the
   extra 10px each side stops card shadows peeking past its edges. */
.portal-body .chip-row {
  position: sticky;
  top: 0;
  z-index: 40;
  margin: -10px -10px 12px;
  padding: 10px;
}
/* Added by the observer in portal-footer.php once it's actually docked,
   so the row looks flat at rest and lifts off the page only while it's
   holding position over content. A full opaque bar the width of the page
   was overkill for that - a light frosted strip (same backdrop-filter
   blur idea as the shared dialog's scrim) is enough to keep the pills
   readable over whatever just scrolled underneath them. */
.portal-body .chip-row.is-stuck {
  background: rgba(244, 247, 252, 0.82);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--portal-line);
  box-shadow: 0 10px 18px -14px rgba(6, 26, 50, 0.55);
}
/* A page-level filter (Trace's "Everything / Site visitors / Portal
   sign-ins") set once before you scroll, not navigation you need mid-
   scroll the way the section-jump chip-row above is - so it opts out of
   the sticky-docking behavior instead of stacking a second bar on top of
   the one that actually stays docked. Same specificity trick as
   :not([aria-pressed="true"]) above: one extra class beats the bare
   .chip-row rule without needing !important. */
.portal-body .chip-row.chip-row-static {
  position: static;
  margin: 0 0 12px;
  padding: 0;
}
/* The pills themselves carry their own frosted-glass surface rather than
   relying on the row's backdrop: translucent at rest, so the row reads
   light whether it's docked or not, and fully solid white the instant a
   pointer actually lands on one - hover is the one moment a click is
   imminent, so that's the moment it should look fully "there" instead of
   see-through. Scoped to this nav specifically, not bare .chip (site.css)
   - testimonials.html's own filter chips still want full opacity at
   rest. :not([aria-pressed="true"]) carves out marketing.php's chip-row,
   which reuses this same component as real tabs rather than anchor
   nav - the active tab's solid blue fill (.chip[aria-pressed="true"] in
   site.css) needs to stay solid, not turn translucent, and would
   otherwise lose that fight on specificity alone (three classes here
   outrank one class + one attribute there). */
.portal-body .chip-row .chip:not([aria-pressed="true"]) {
  background: rgba(255, 255, 255, 0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.portal-body .chip-row .chip:not([aria-pressed="true"]):hover {
  background: #fff;
}

/* Anchor targets only need to clear the docked chip row now, not the
   header too - .dashboard-shell's own scroll box (see the app-shell rules
   above) already starts below the header, so nothing there overlaps a
   section heading any more. Without this, clicking a chip would still
   scroll the heading underneath the now top:0-docked row. */
.portal-body section[id] {
  scroll-margin-top: 76px;
}

/* ==================== Property Manager Portal (Phase 26) ====================
   SLA badge, priority chip, and chat thread - the three new visual
   pieces this feature needed. Everything else (.card, .status, .field,
   .table-wrap, .lead*, .chip-row) is reused as-is. */

/* .sla-badge - same pill shape as .status, own tone vocabulary
   (assets/php/pm-portal.php's pm_work_order_status() picks the tone). */
.sla-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 11px;
  font-weight: 850;
  white-space: nowrap;
  letter-spacing: 0.01em;
}
.sla-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.sla-badge.gray {
  color: var(--portal-muted);
  background: #eef1f5;
}
.sla-badge.blue {
  color: var(--blue-dark);
  background: var(--soft-blue);
}
.sla-badge.amber {
  color: #9a6100;
  background: #fff4d8;
}
.sla-badge.green {
  color: #146848;
  background: #e6f6ee;
}
.sla-badge.red,
.sla-badge.overdue {
  color: #a3302f;
  background: #fff0f0;
}

/* .priority-chip - urgent/elevated/standard/low, worst-to-mildest. */
.priority-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 11px;
  font-weight: 850;
  white-space: nowrap;
  letter-spacing: 0.01em;
}
.priority-chip.urgent {
  color: #a3302f;
  background: #fff0f0;
}
.priority-chip.elevated {
  color: #9a6100;
  background: #fff4d8;
}
.priority-chip.standard {
  color: var(--blue-dark);
  background: var(--soft-blue);
}
.priority-chip.low {
  color: var(--portal-muted);
  background: #eef1f5;
}

/* .chat-thread - the work order message thread. .mine right-aligns and
   tints blue (the viewer's own messages), same left/right convention as
   every messaging UI, so who-said-what reads at a glance without
   re-reading each author name. */
.chat-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 420px;
  overflow-y: auto;
  padding: 4px 2px;
  margin-bottom: 14px;
}
.chat-msg {
  max-width: 78%;
  align-self: flex-start;
}
.chat-msg.mine {
  align-self: flex-end;
}
.chat-msg-meta {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-bottom: 3px;
  font-size: 10.5px;
  color: var(--portal-muted);
  font-weight: 800;
}
.chat-msg.mine .chat-msg-meta {
  justify-content: flex-end;
}
.chat-msg-body {
  background: #eef1f5;
  border-radius: 14px 14px 14px 4px;
  padding: 10px 13px;
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
}
.chat-msg.mine .chat-msg-body {
  background: var(--soft-blue);
  color: var(--blue-dark);
  border-radius: 14px 14px 4px 14px;
}
/* Applied for one animation cycle to a bubble that just arrived via
   polling (assets/js/portal-quote-chat.js) - a beat of motion so a
   message that appeared without the contractor doing anything (the
   customer typed it) doesn't just silently materialize. */
@keyframes chatMsgIn {
  from { transform: translateY(6px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
.chat-msg.is-new {
  animation: chatMsgIn 0.25s ease;
}
.chat-empty {
  color: var(--portal-muted);
  font-size: 13px;
  padding: 8px 2px 14px;
}
/* "Typing..." (assets/js/portal-quote-chat.js / quote-response-chat.js) -
   sits between the thread and the compose box, [hidden] by default so
   [hidden]{display:none!important} (top of this file) governs it like
   every other toggled element. Reuses .chat-msg-body's own bubble
   treatment at a smaller size rather than inventing a new visual
   language for what is, structurally, a placeholder for the next bubble. */
.chat-typing {
  display: inline-block;
  background: #eef1f5;
  color: var(--portal-muted);
  border-radius: 14px 14px 14px 4px;
  padding: 6px 12px;
  font-size: 12px;
  font-style: italic;
  margin: -4px 0 10px;
}
.chat-form {
  display: flex;
  gap: 10px;
  align-items: flex-end;
}
/* Same look as every other text input in the portal (.field textarea) -
   this was previously bare browser-default styling, since .chat-form
   textarea only ever set layout properties (flex/resize/min-height),
   never border/radius/padding/focus, and .chat-form isn't a .field. */
.chat-form textarea {
  flex: 1;
  resize: vertical;
  min-height: 44px;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 12px 13px;
  outline: none;
  font-size: 14px;
  font-family: inherit;
  line-height: 1.55;
  background: #fff;
  color: var(--ink);
  transition: 0.15s;
}
.chat-form textarea::placeholder {
  color: #9fb0c4;
}
.chat-form textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 101, 221, 0.12);
}

/* .chat-widget - the contractor's floating chat launcher on
   portal/contractor/quote.php (assets/js/portal-quote-chat.js drives the
   open/close/badge behavior; the markup inside the panel - thread, form,
   typing indicator - is the exact same #quoteChatThread/#quoteChatForm/
   #quoteChatTyping this file already styled above, just moved out of
   the "Quote decision & scheduling" card into something that floats over
   every stage instead of being tied to one card's visibility. Fixed
   bottom-right, same corner every messaging widget on the web uses, so
   nothing about it needs explaining the first time someone sees it. */
.chat-widget {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 200;
}
.chat-widget-fab {
  position: relative;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: none;
  background: var(--blue);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 10px 28px rgba(11, 101, 221, 0.38), 0 2px 8px rgba(7, 28, 54, 0.16);
  transition: transform 0.15s, box-shadow 0.15s;
}
.chat-widget-fab:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 34px rgba(11, 101, 221, 0.42), 0 4px 10px rgba(7, 28, 54, 0.18);
}
.chat-widget-fab svg {
  width: 25px;
  height: 25px;
}
/* Stays lit until the contractor actually sends a reply - not cleared
   just by opening the panel to read it - see the badge-visibility logic
   in portal-quote-chat.js for why: an opened-but-unanswered question is
   still a thing nobody has answered. */
.chat-widget-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: 10px;
  background: #d64545;
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid #fff;
  animation: chatWidgetBadgePulse 1.8s ease-in-out infinite;
}
@keyframes chatWidgetBadgePulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.12); }
}
.chat-widget-panel {
  position: absolute;
  right: 0;
  bottom: 72px;
  width: 368px;
  max-width: calc(100vw - 44px);
  max-height: min(560px, calc(100vh - 116px));
  background: #fff;
  border-radius: 18px;
  box-shadow: var(--portal-shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transform-origin: bottom right;
  animation: chatWidgetOpen 0.16s ease;
}
@keyframes chatWidgetOpen {
  from { opacity: 0; transform: scale(0.94) translateY(8px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}
.chat-widget-header {
  flex: none;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 16px 16px 14px 18px;
  background: var(--navy);
  color: #fff;
}
.chat-widget-header strong {
  display: block;
  font-size: 14px;
}
.chat-widget-stage {
  display: block;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #9fc0ef;
  margin-top: 3px;
}
.chat-widget-timestamp {
  display: block;
  font-size: 10px;
  color: #7893b8;
  margin-top: 3px;
}
.chat-widget-close {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s;
}
.chat-widget-close:hover {
  background: rgba(255, 255, 255, 0.24);
}
.chat-widget-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px 0;
}
/* The panel body itself is the one scrolling region (not the thread
   inside it too) - .chat-thread's own max-height/overflow (set above,
   for the still-inline copy on the two public customer pages) would
   otherwise create a scrollbar-inside-a-scrollbar here. */
.chat-widget-body .chat-thread {
  max-height: none;
  overflow-y: visible;
  margin-bottom: 10px;
}
.chat-widget-panel .chat-form {
  flex: none;
  margin: 0;
  padding: 12px 14px;
  border-top: 1px solid var(--portal-line);
}
.chat-widget-panel .chat-form textarea {
  min-height: 38px;
}

/* ==========================================================================
   Campaign Studio (Phase 36) - portal/contractor/campaign-studio.php
   --------------------------------------------------------------------------
   A three-pane builder inside the normal portal shell: block palette on
   the left, the email in the middle, settings/inspector on the right. It
   reuses the portal's own tokens and components (.btn, .field, .modal,
   .chip) rather than introducing a second design language - the Studio
   should read as part of this app, not as a bolted-on product.

   The panes are a CSS grid that collapses to a single column under
   1180px, at which point the rails stack above and below the canvas.
   ========================================================================== */

.studio {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* --- top bar --------------------------------------------------------- */
.studio-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  box-shadow: var(--portal-shadow);
  position: sticky;
  top: 0;
  z-index: 30;
}
.studio-back {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: #f2f6fb;
  color: var(--portal-muted);
  text-decoration: none;
  font-size: 16px;
  transition: 0.15s;
}
.studio-back:hover { background: var(--soft-blue); color: var(--blue); }
.studio-bar-main { flex: 1 1 220px; min-width: 0; }
.studio-title-input {
  width: 100%;
  border: 1px solid transparent;
  background: none;
  font-size: 17px;
  font-weight: 850;
  letter-spacing: -0.02em;
  color: var(--ink);
  padding: 5px 8px;
  border-radius: 9px;
  font-family: inherit;
  outline: none;
  transition: 0.15s;
}
.studio-title-input:hover { background: #f6f9fd; }
.studio-title-input:focus { background: #fff; border-color: var(--blue); box-shadow: 0 0 0 3px rgba(11,101,221,0.12); }
.studio-bar-meta { display: flex; align-items: center; gap: 10px; padding-left: 8px; }
.studio-saved { font-size: 11px; font-weight: 800; color: #146848; }
.studio-bar-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-wrap: wrap; }

/* The score gauge. A conic-gradient sweep driven by --sweep (set in JS)
   rather than an SVG arc: one custom property to drive, and it inherits
   the tone colour without a second element to keep in step. */
.studio-score {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--portal-line);
  background: #fff;
  border-radius: 999px;
  padding: 5px 16px 5px 5px;
  cursor: pointer;
  flex: 0 0 auto;
  transition: 0.15s;
}
.studio-score:hover { border-color: var(--blue); background: #fbfdff; }
.studio-score-ring {
  --sweep: 0deg;
  --ring: #cfdae7;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--ring) var(--sweep), #eef2f7 0deg);
}
.studio-score-ring[data-tone="great"] { --ring: #17a06a; }
.studio-score-ring[data-tone="good"]  { --ring: #0b65dd; }
.studio-score-ring[data-tone="ok"]    { --ring: #d79300; }
.studio-score-ring[data-tone="bad"]   { --ring: #c73d3d; }
.studio-score-num {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #fff;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 900;
  color: var(--ink);
}
.studio-score-label { display: flex; flex-direction: column; line-height: 1.25; text-align: left; }
.studio-score-label b { font-size: 12.5px; }

/* --- three-pane grid -------------------------------------------------- */
.studio-grid {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr) 300px;
  gap: 14px;
  align-items: start;
}
.studio-rail {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  box-shadow: var(--portal-shadow);
  overflow: hidden;
  position: sticky;
  top: 74px;
  max-height: calc(100vh - 96px);
  display: flex;
  flex-direction: column;
}
.studio-rail-tabs {
  display: flex;
  gap: 2px;
  padding: 8px 8px 0;
  border-bottom: 1px solid var(--portal-line);
  flex: 0 0 auto;
}
.studio-rail-tab {
  flex: 1;
  border: 0;
  background: none;
  padding: 9px 6px;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 850;
  color: var(--portal-muted);
  cursor: pointer;
  border-radius: 8px 8px 0 0;
  border-bottom: 2px solid transparent;
  transition: 0.15s;
}
.studio-rail-tab:hover { color: var(--ink); background: #f7fafd; }
.studio-rail-tab.is-on { color: var(--blue); border-bottom-color: var(--blue); }
.studio-tab-badge {
  display: inline-block;
  min-width: 16px;
  padding: 1px 5px;
  border-radius: 999px;
  background: #fff0f0;
  color: #a3302f;
  font-size: 10px;
  margin-left: 3px;
}
.studio-rail-body { padding: 14px; overflow-y: auto; flex: 1 1 auto; }
.studio-hint { font-size: 11.5px; color: var(--portal-muted); margin: 0 0 10px; }

/* --- block palette ---------------------------------------------------- */
.studio-palette { display: grid; gap: 6px; }
.studio-palette-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  border: 1px solid var(--portal-line);
  border-radius: 10px;
  background: #fff;
  cursor: grab;
  font-family: inherit;
  transition: 0.14s;
}
.studio-palette-item:hover:not(:disabled) {
  border-color: var(--blue);
  background: #fbfdff;
  transform: translateX(2px);
}
.studio-palette-item:disabled { opacity: 0.5; cursor: not-allowed; }
.studio-palette-item:active { cursor: grabbing; }
.studio-palette-icon {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--soft-blue);
  color: var(--blue-dark);
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 900;
}
.studio-palette-item > span:last-child { display: flex; flex-direction: column; min-width: 0; }
.studio-palette-item b { font-size: 12.5px; color: var(--ink); }
.studio-palette-item span span { font-size: 11px; color: var(--portal-muted); line-height: 1.4; }

/* --- merge tags ------------------------------------------------------- */
.studio-mergebox {
  margin-top: 16px;
  padding: 12px;
  border: 1px dashed #cbdcee;
  border-radius: 11px;
  background: #fbfdff;
}
.studio-mergebox-head {
  font-size: 10.5px;
  font-weight: 850;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--portal-muted);
  margin-bottom: 6px;
}
.studio-tags { display: flex; flex-wrap: wrap; gap: 5px; }
.studio-tag {
  border: 1px solid #cbdcee;
  background: #fff;
  border-radius: 7px;
  padding: 4px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
  color: var(--blue-dark);
  cursor: pointer;
  transition: 0.14s;
}
.studio-tag:hover { background: var(--soft-blue); border-color: var(--blue); }

/* --- templates -------------------------------------------------------- */
.studio-templates { display: grid; gap: 8px; }
.studio-template {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: left;
  padding: 11px 12px;
  border: 1px solid var(--portal-line);
  border-radius: 11px;
  background: #fff;
  cursor: pointer;
  font-family: inherit;
  transition: 0.14s;
}
.studio-template:hover { border-color: var(--blue); background: #fbfdff; }
.studio-template b { font-size: 12.5px; color: var(--ink); }
.studio-template span { font-size: 11px; color: var(--portal-muted); line-height: 1.45; }
.studio-template-subject {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px dashed var(--portal-line);
  font-style: italic;
  color: #7b8ca1 !important;
}

/* --- library (reviews + images) --------------------------------------- */
.studio-lib-section { margin-bottom: 18px; }
.studio-lib-head {
  font-size: 10.5px;
  font-weight: 850;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--portal-muted);
  margin-bottom: 6px;
}
.studio-lib-list { display: grid; gap: 7px; }
.studio-lib-review {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: left;
  padding: 10px 11px;
  border: 1px solid var(--portal-line);
  border-radius: 10px;
  background: #fff;
  cursor: pointer;
  font-family: inherit;
  transition: 0.14s;
}
.studio-lib-review:hover { border-color: var(--blue); background: #fbfdff; }
.studio-lib-stars { color: #f0a500; font-size: 12px; letter-spacing: 1px; }
.studio-lib-quote { font-size: 11.5px; color: var(--ink); line-height: 1.45; font-style: italic; }
.studio-lib-author { font-size: 10.5px; color: var(--portal-muted); font-weight: 700; }
.studio-upload {
  display: block;
  text-align: center;
  padding: 12px;
  border: 1px dashed #cbdcee;
  border-radius: 10px;
  background: #fbfdff;
  color: var(--blue);
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
  transition: 0.14s;
}
.studio-upload:hover { border-color: var(--blue); background: var(--soft-blue); }
.studio-upload.is-busy { opacity: 0.6; pointer-events: none; }
.studio-lib-images { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 8px; }
.studio-lib-img {
  border: 1px solid var(--portal-line);
  border-radius: 8px;
  overflow: hidden;
  padding: 0;
  background: #fff;
  cursor: pointer;
  aspect-ratio: 1;
  transition: 0.14s;
}
.studio-lib-img:hover { border-color: var(--blue); transform: scale(1.04); }
.studio-lib-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- canvas ----------------------------------------------------------- */
.studio-canvas-wrap { min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.studio-envelope {
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  box-shadow: var(--portal-shadow);
  padding: 16px 18px;
  display: grid;
  gap: 12px;
}
.studio-field label {
  display: block;
  font-size: 11.5px;
  font-weight: 850;
  color: var(--ink);
  margin-bottom: 6px;
}
.studio-field label .opt {
  font-weight: 600;
  color: var(--portal-muted);
  text-transform: none;
  letter-spacing: 0;
}
.studio-field input {
  width: 100%;
  border: 1px solid #cfdae7;
  border-radius: 10px;
  padding: 11px 13px;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: 0.15s;
}
.studio-field input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(11,101,221,0.12); }
.studio-meter { margin-top: 5px; }

.studio-canvas {
  background: #eef3f9;
  border: 1px solid var(--portal-line);
  border-radius: 14px;
  padding: 26px 16px 40px;
  min-height: 420px;
}
.studio-email {
  max-width: 600px;
  margin: 0 auto;
  background: #fff;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 14px 40px rgba(7, 28, 54, 0.12);
  font-family: Arial, Helvetica, sans-serif;
}
.studio-email-head {
  background: #071c36;
  color: #fff;
  padding: 16px 28px;
  font-size: 13.5px;
  font-weight: bold;
  letter-spacing: 0.02em;
}
.studio-blocks { padding: 22px 0 8px; min-height: 120px; }
.studio-email-foot {
  padding: 16px 28px 20px;
  border-top: 1px solid #dbe4ef;
  font-size: 11.5px;
  line-height: 1.6;
  color: #4f5f73;
  background: #fdfdfe;
}
.studio-foot-note {
  margin-top: 8px;
  font-size: 10.5px;
  font-style: italic;
  color: #9fb0c4;
}
.studio-canvas-empty {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  padding: 46px 20px;
  margin: 0 22px;
  border: 2px dashed #d6e2ee;
  border-radius: 12px;
  color: var(--portal-muted);
}
.studio-canvas-empty b { font-size: 14px; color: var(--ink); }
.studio-canvas-empty span { font-size: 12px; }

/* One block on the canvas. The outline and toolbar only appear on hover
   or selection so the resting state reads as the email itself, not as a
   form covered in chrome. */
.studio-block {
  position: relative;
  padding: 2px 0;
  margin: 0 8px;
  border: 2px solid transparent;
  border-radius: 8px;
  transition: 0.12s;
}
.studio-block:hover { border-color: #c7dcf5; background: rgba(237, 246, 255, 0.35); }
.studio-block.is-selected { border-color: var(--blue); background: rgba(237, 246, 255, 0.55); }
.studio-block.is-dragging { opacity: 0.4; }
.studio-block.is-drop-before { box-shadow: 0 -3px 0 -1px var(--blue); }
.studio-block.is-drop-after { box-shadow: 0 3px 0 -1px var(--blue); }
.studio-block-tools {
  position: absolute;
  top: -13px;
  right: 6px;
  display: none;
  gap: 2px;
  background: #fff;
  border: 1px solid var(--portal-line);
  border-radius: 8px;
  padding: 2px;
  box-shadow: var(--portal-shadow);
  z-index: 3;
}
.studio-block:hover .studio-block-tools,
.studio-block.is-selected .studio-block-tools { display: flex; }
.studio-block-tools button {
  border: 0;
  background: none;
  width: 24px;
  height: 22px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 10px;
  color: var(--portal-muted);
  transition: 0.12s;
}
.studio-block-tools button:hover { background: var(--soft-blue); color: var(--blue); }
.studio-block-tools button[data-act="del"]:hover { background: #fff0f0; color: #a3302f; }

/* Canvas approximations of each block type - see blockInnerHtml() in
   assets/js/portal-campaign-studio.js. Kept visually close to
   marketing_render_block()'s real email output so the jump to Preview is
   small. */
.sb-heading { padding: 0 26px 12px; font-weight: bold; line-height: 1.3; }
.sb-text { padding: 0 26px 14px; line-height: 1.6; outline: none; }
.sb-text:focus { background: rgba(255, 255, 255, 0.9); border-radius: 6px; }
.sb-text p { margin: 0 0 12px; }
.sb-text ul, .sb-text ol { margin: 0 0 12px; padding-left: 22px; }
.sb-text a { color: #0b65dd; }
.sb-btn-row { padding: 4px 26px 20px; }
.sb-btn {
  display: inline-block;
  padding: 13px 26px;
  border-radius: 10px;
  font-size: 14.5px;
  font-weight: bold;
}
.sb-image { padding: 2px 26px 16px; }
.sb-image img { max-width: 100%; display: inline-block; }
.sb-image-empty {
  margin: 2px 26px 16px;
  padding: 30px 16px;
  border: 2px dashed #d6e2ee;
  border-radius: 10px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.sb-image-empty b { font-size: 13px; color: var(--ink); }
.sb-image-empty span { font-size: 11.5px; color: var(--portal-muted); }
.sb-warn {
  margin: -10px 26px 14px;
  font-size: 11px;
  color: #9a6100;
  background: #fff4d8;
  border-radius: 6px;
  padding: 4px 8px;
  display: inline-block;
}
.sb-promo {
  margin: 2px 26px 18px;
  padding: 15px 18px;
  border: 2px dashed #0b65dd;
  border-radius: 12px;
  background: #edf6ff;
  text-align: center;
}
.sb-promo-label {
  font-size: 11px;
  font-weight: bold;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #0853bd;
}
.sb-promo-code { font-size: 25px; font-weight: bold; letter-spacing: 0.06em; color: #071c36; margin-top: 4px; }
.sb-promo-note { font-size: 11.5px; color: #4f5f73; margin-top: 5px; }
.sb-review {
  margin: 2px 26px 18px;
  padding: 15px 18px;
  background: #f8fafd;
  border-left: 4px solid #0b65dd;
  border-radius: 0 12px 12px 0;
}
.sb-stars { color: #f0a500; font-size: 15px; letter-spacing: 2px; margin-bottom: 6px; }
.sb-quote { font-size: 14.5px; line-height: 1.6; color: #1f2d3d; font-style: italic; }
.sb-author { font-size: 12.5px; font-weight: bold; color: #4f5f73; margin-top: 8px; }
.sb-divider { padding: 0 26px; }
.sb-divider hr { border: 0; border-top: 1px solid #dbe4ef; margin: 0; }
.sb-spacer {
  margin: 0 26px;
  border: 1px dashed #dfe8f2;
  border-radius: 6px;
  display: grid;
  place-items: center;
  color: #b6c4d4;
  font-size: 10.5px;
}

/* --- right rail: settings -------------------------------------------- */
.studio-empty { text-align: center; padding: 34px 14px; color: var(--portal-muted); }
.studio-empty-icon { font-size: 26px; color: #cfdae7; margin-bottom: 8px; }
.studio-empty p { font-size: 12px; margin: 0; line-height: 1.5; }
.studio-settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--portal-line);
}
.studio-settings-head b { font-size: 13px; }
.studio-settings-del {
  border: 0;
  background: none;
  color: #c08a8a;
  font-size: 11px;
  font-weight: 800;
  cursor: pointer;
  padding: 3px 6px;
  border-radius: 6px;
  transition: 0.14s;
}
.studio-settings-del:hover { background: #fff2f2; color: #a82e2e; }
.studio-field-sm { margin-bottom: 14px; }
.studio-mini-label {
  display: block;
  font-size: 10.5px;
  font-weight: 850;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--portal-muted);
  margin-bottom: 5px;
}
.studio-range-val { float: right; text-transform: none; letter-spacing: 0; color: var(--blue); }
.studio-field-sm input[type="text"],
.studio-field-sm textarea,
.studio-field-sm select {
  width: 100%;
  border: 1px solid #cfdae7;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12.5px;
  font-family: inherit;
  outline: none;
  transition: 0.15s;
}
.studio-field-sm input[type="text"]:focus,
.studio-field-sm textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(11,101,221,0.12); }
.studio-field-sm input[type="range"] { width: 100%; accent-color: var(--blue); }
.studio-seg { display: flex; gap: 3px; }
.studio-seg button {
  flex: 1;
  border: 1px solid var(--portal-line);
  background: #fff;
  padding: 6px 4px;
  font-size: 11px;
  font-weight: 800;
  font-family: inherit;
  color: var(--portal-muted);
  cursor: pointer;
  border-radius: 7px;
  transition: 0.14s;
}
.studio-seg button:hover { border-color: var(--blue); color: var(--blue); }
.studio-seg button.is-on { background: var(--blue); border-color: var(--blue); color: #fff; }
.studio-swatches { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.studio-swatch {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--portal-line);
  cursor: pointer;
  padding: 0;
  transition: 0.14s;
}
.studio-swatch:hover { transform: scale(1.12); }
.studio-swatch.is-on { box-shadow: 0 0 0 2px var(--blue); }
.studio-color-custom {
  width: 26px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--portal-line);
  border-radius: 6px;
  background: none;
  cursor: pointer;
}
.studio-rtb { display: flex; gap: 3px; margin-bottom: 10px; flex-wrap: wrap; }
.studio-rtb button {
  width: 30px;
  height: 28px;
  border: 1px solid var(--portal-line);
  background: #fff;
  border-radius: 7px;
  cursor: pointer;
  font-size: 12px;
  color: var(--ink);
  transition: 0.14s;
}
.studio-rtb button:hover { border-color: var(--blue); background: var(--soft-blue); color: var(--blue); }
.studio-sitelinks { margin-bottom: 14px; }
.studio-linkpicks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.studio-linkpick {
  border: 1px solid var(--portal-line);
  background: #fff;
  border-radius: 6px;
  padding: 3px 7px;
  font-size: 10.5px;
  font-family: inherit;
  color: var(--portal-muted);
  cursor: pointer;
  transition: 0.14s;
}
.studio-linkpick:hover { border-color: var(--blue); color: var(--blue); background: var(--soft-blue); }

/* --- right rail: inspector ------------------------------------------- */
.studio-inbox-sim {
  border: 1px solid var(--portal-line);
  border-radius: 11px;
  overflow: hidden;
  margin-bottom: 14px;
}
.studio-inbox-head {
  background: #f8fafd;
  padding: 7px 11px;
  font-size: 10px;
  font-weight: 850;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--portal-muted);
  border-bottom: 1px solid var(--portal-line);
}
.studio-inbox-row { display: flex; gap: 9px; padding: 11px; background: #fff; }
.studio-inbox-avatar {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--blue), #063f8d);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 900;
}
.studio-inbox-text { min-width: 0; flex: 1; }
.studio-inbox-sender { font-size: 12px; font-weight: 850; color: var(--ink); }
/* Single-line clamp with ellipsis, which is exactly what the real thing
   does - seeing the cut-off point is the whole reason this exists. */
.studio-inbox-subject,
.studio-inbox-preview {
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.studio-inbox-subject { font-weight: 700; color: #1f2d3d; }
.studio-inbox-preview { color: var(--portal-muted); }

.studio-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; margin-bottom: 14px; }
.studio-stat {
  background: #f8fafd;
  border: 1px solid var(--portal-line);
  border-radius: 9px;
  padding: 8px 4px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.studio-stat b { font-size: 14px; color: var(--ink); }
.studio-stat span { font-size: 9.5px; color: var(--portal-muted); text-transform: uppercase; letter-spacing: 0.05em; }

.studio-findings { display: grid; gap: 5px; }
.studio-finding {
  border: 1px solid var(--portal-line);
  border-radius: 9px;
  background: #fff;
  overflow: hidden;
}
.studio-finding summary {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 11px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
  line-height: 1.4;
}
.studio-finding summary::-webkit-details-marker { display: none; }
.studio-finding summary:hover { background: #f8fafd; }
.studio-finding-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-top: 5px;
}
.studio-finding.sev-critical { border-color: #efbcbc; background: #fffafa; }
.studio-finding.sev-critical .studio-finding-dot { background: #c73d3d; }
.studio-finding.sev-warning .studio-finding-dot { background: #d79300; }
.studio-finding.sev-tip .studio-finding-dot { background: #0b65dd; }
.studio-finding.sev-pass .studio-finding-dot { background: #17a06a; }
.studio-finding.sev-pass summary { color: var(--portal-muted); font-weight: 600; }
.studio-finding-body {
  padding: 0 11px 11px 26px;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--portal-muted);
}
.studio-finding-body p { margin: 0 0 7px; }
.studio-finding-body b { color: var(--ink); }
.studio-audience-count {
  padding: 10px 12px;
  background: var(--soft-blue);
  border-radius: 9px;
  font-size: 12px;
  color: var(--blue-dark);
}

/* --- preview + launch modals ------------------------------------------ */
.studio-preview-card { width: min(860px, 100%); }
.studio-preview-toggle { display: flex; gap: 7px; margin-bottom: 14px; }
.studio-preview-toggle .chip.is-on { background: var(--blue); border-color: var(--blue); color: #fff; }
.studio-preview-stage {
  background: #eef3f9;
  border: 1px solid var(--portal-line);
  border-radius: 12px;
  padding: 16px;
  display: grid;
  place-items: start center;
  min-height: 440px;
  max-height: 60vh;
  overflow-y: auto;
}
.studio-preview-stage iframe {
  width: 100%;
  height: 560px;
  border: 0;
  border-radius: 10px;
  background: #fff;
  transition: width 0.25s;
}
/* 390px is an iPhone 15's CSS viewport - the width that actually decides
   whether a layout works, since most of a list this size reads on one. */
.studio-preview-stage[data-mode="mobile"] iframe {
  width: 390px;
  box-shadow: 0 12px 40px rgba(7, 28, 54, 0.2);
}
.studio-preview-stage pre {
  width: 100%;
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.6;
  color: #1f2d3d;
  background: #fff;
  border-radius: 10px;
  padding: 18px;
}

.studio-launch-card { width: min(560px, 100%); }
.studio-launch-grid {
  display: grid;
  gap: 1px;
  background: var(--portal-line);
  border: 1px solid var(--portal-line);
  border-radius: 11px;
  overflow: hidden;
}
.studio-launch-row { display: grid; grid-template-columns: 110px 1fr; gap: 12px; background: #fff; padding: 11px 14px; }
.studio-launch-row > span {
  font-size: 10.5px;
  font-weight: 850;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--portal-muted);
  padding-top: 2px;
}
.studio-launch-row > div { font-size: 13px; color: var(--ink); line-height: 1.5; word-break: break-word; }
.studio-launch-warn {
  margin-top: 12px;
  padding: 10px 13px;
  background: #fff4d8;
  border-radius: 9px;
  font-size: 11.5px;
  color: #9a6100;
  line-height: 1.5;
}
.studio-launch-note { margin-right: auto; }

/* --- toasts ------------------------------------------------------------ */
.studio-toast {
  position: fixed;
  bottom: 22px;
  left: 50%;
  transform: translate(-50%, 16px);
  background: var(--ink);
  color: #fff;
  padding: 11px 20px;
  border-radius: 11px;
  font-size: 13px;
  font-weight: 700;
  box-shadow: 0 16px 44px rgba(7, 28, 54, 0.3);
  z-index: 400;
  opacity: 0;
  transition: 0.24s;
  max-width: min(460px, 90vw);
  text-align: center;
}
.studio-toast.is-in { opacity: 1; transform: translate(-50%, 0); }
.studio-toast.good { background: #146848; }
.studio-toast.bad { background: #a3302f; }

/* --- responsive -------------------------------------------------------- */
@media (max-width: 1180px) {
  .studio-grid { grid-template-columns: 1fr; }
  .studio-rail { position: static; max-height: none; }
  .studio-rail-body { max-height: 320px; }
}
@media (max-width: 720px) {
  .studio-bar { flex-wrap: wrap; }
  .studio-bar-main { flex: 1 1 100%; order: -1; }
  .studio-bar-actions { flex: 1 1 100%; }
  .studio-bar-actions .btn { flex: 1; }
  .studio-block { margin: 0 2px; }
  .studio-canvas { padding: 16px 8px 30px; }
  .studio-stats { grid-template-columns: repeat(2, 1fr); }
  .studio-launch-row { grid-template-columns: 1fr; gap: 3px; }
}

/* The Campaign Studio entry point on the Marketing dashboard. Its own
   rule rather than a plain .card because the whole point is that it
   reads as a door rather than as another panel of information. */
.studio-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  background: linear-gradient(135deg, #ffffff 0%, #f4f9ff 100%);
  border-color: #cfe0f5;
}
.studio-cta h2 { margin: 0; }
.studio-cta .btn { flex: 0 0 auto; }

/* Devices / systems / browsers on the Trace page - three small tallies
   side by side rather than one wide table, since they answer three
   separate questions and none of them needs the full width. Collapses to
   a single column on a narrow screen. */
.trace-platform-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.trace-platform-head {
  font-size: 10.5px;
  font-weight: 850;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--portal-muted);
  margin-bottom: 6px;
}
/* These tables are narrow by design and sit three-across, so the shared
   700px min-width would force a scrollbar inside every one of them. */
.trace-platform-grid .table-wrap table { min-width: 0; }
.trace-platform-grid .table-wrap td { padding: 8px 11px; }

@media (max-width: 900px) {
  .trace-platform-grid { grid-template-columns: 1fr; }
}
