/* Poppins, self-hosted. Google Fonts was a render-blocking stylesheet on
   two extra origins (DNS + TLS + fetch) before the page could paint. These are
   the same files Google serves, split the same way: a browser only downloads a
   face whose unicode-range it actually needs, so the bytes on the wire are
   unchanged and the round trips are gone.
   Regenerate with scripts/fetch-fonts.js if the weights in use ever change. */
@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/poppins-400.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/poppins-400-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/poppins-500.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/poppins-500-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/poppins-600.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/poppins-600-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/poppins-700.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/poppins-700-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:normal;font-weight:800;font-display:swap;src:url(/fonts/poppins-800.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:normal;font-weight:800;font-display:swap;src:url(/fonts/poppins-800-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:italic;font-weight:500;font-display:swap;src:url(/fonts/poppins-500-italic.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:italic;font-weight:500;font-display:swap;src:url(/fonts/poppins-500-italic-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:italic;font-weight:700;font-display:swap;src:url(/fonts/poppins-700-italic.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:italic;font-weight:700;font-display:swap;src:url(/fonts/poppins-700-italic-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:Poppins;font-style:italic;font-weight:800;font-display:swap;src:url(/fonts/poppins-800-italic.woff2) format("woff2");unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:Poppins;font-style:italic;font-weight:800;font-display:swap;src:url(/fonts/poppins-800-italic-ext.woff2) format("woff2");unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}

* { box-sizing: border-box; }
/* Scale the whole page with the display: bigger on 1440p/4K, unchanged below 1680px */
@media (min-width: 1680px) { html { zoom: 1.12; } }
@media (min-width: 2200px) { html { zoom: 1.35; } }
@media (min-width: 3000px) { html { zoom: 1.7; } }
/* Our display rules (e.g. .center-wrap { display:flex }) would otherwise beat
   the browser's built-in [hidden] { display:none } — keep hidden authoritative. */
[hidden] { display: none !important; }
body {
  margin: 0;
  font-family: "Poppins", system-ui, -apple-system, "Segoe UI", sans-serif;
  background: #f1f5f9;
  color: #111827;
}
.muted { color: #6b7280; }
.error { color: #dc2626; font-size: .85rem; min-height: 1.2em; margin-top: 8px; }
.center { text-align: center; }

.center-wrap {
  min-height: 100vh;
  min-height: 100dvh; /* mobile: the real visible height as the URL bar shows/hides */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
/* Phones (and landscape): dead-center means the keyboard covers the form and
   the card jumps when it opens — anchor it comfortably near the top instead. */
@media (max-width: 600px), (max-height: 520px) {
  .center-wrap { align-items: flex-start; padding-top: max(10vh, 24px); }
  .login-card { padding: 28px 24px; }
}
.login-card {
  background: #fff;
  border-radius: 14px;
  padding: 34px;
  width: 100%;
  max-width: 380px;
  box-shadow: 0 10px 40px rgba(15, 27, 51, .08);
}
.login-card h1 { margin: 0 0 4px; font-size: 1.5rem; }
.login-card p { margin: 0 0 18px; }
.login-card label { display: block; font-size: .8rem; font-weight: 600; margin: 12px 0 4px; }
.login-card input {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-size: .95rem;
}
/* Overrides the block label above: this one sits beside its checkbox rather
   than above its field, and its text is a sentence, not a field name. */
.login-card label.checkbox {
  display: flex; align-items: center; gap: 8px;
  margin: 16px 0 4px; font-weight: 500; font-size: .85rem; color: #4b5563;
  cursor: pointer;
}
.login-card label.checkbox input {
  width: auto; margin: 0; padding: 0; flex: 0 0 auto;
  accent-color: #2f5fd0;
}

.login-card button {
  margin-top: 16px;
  width: 100%;
  padding: 12px;
  border: 0;
  border-radius: 8px;
  background: #0f1b33;
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
  cursor: pointer;
}

.login-card button.linkish {
  margin-top: 10px;
  width: 100%;
  background: none;
  border: 0;
  color: #6b7280;
  font-size: .85rem;
  font-weight: 400;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px;
}

.topbar {
  background: #0f1b33;
  color: #fff;
  padding: 14px 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.topbar .muted { color: #94a3b8; }
button.ghost {
  background: transparent;
  border: 1px solid #cbd5e1;
  color: inherit;
  border-radius: 8px;
  padding: 7px 14px;
  cursor: pointer;
  font-size: .85rem;
}
main button.ghost { border-color: #cbd5e1; color: #334155; }
/* a.ghost's colour is set for a light background; in the dark topbar (the
   site-analytics page's link back to the dashboard) it has to invert. The
   .small on the end is for specificity — a.ghost.small is declared further
   down and would otherwise win on source order. */
.topbar a.ghost.small { color: #fff; border-color: #4b6796; }
.topbar a.ghost.small:hover { background: #1c2c4d; }
button.ghost.small { padding: 4px 10px; font-size: .8rem; }
a.ghost.small {
  display: inline-block;
  border: 1px solid #cbd5e1;
  color: #334155;
  border-radius: 8px;
  padding: 4px 10px;
  font-size: .8rem;
  text-decoration: none;
  margin-right: 6px;
}
a.cal-link { border-color: #16a34a; color: #15803d; }

main { max-width: 1120px; margin: 0 auto; padding: 28px 20px 60px; }
h2 { font-size: 1.15rem; margin: 34px 0 14px; }

/* Section heading with a control parked on the right (the charts' table-view
   toggle). The h2 keeps its own margins so spacing matches the plain ones. */
.sechead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.sechead h2 { margin-right: auto; }
.small-note { font-size: .78rem; }

.sronly {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- data-viz tokens ------------------------------------------------
   Categorical slots 1-3 validated against this card surface (#fff):
   CVD ΔE 9.2 worst all-pairs, normal-vision 24.0. Aqua sits at 2.82:1
   contrast, so every chart using it ships visible value labels and the
   calls table below stands as the table view.

   --series-context is deliberately outside that categorical set: it is the
   gray in "highlight one, gray the rest" on the booked-vs-calls chart, where
   booked carries the hue and the total is the reference line. Against
   --series-1 it measures CVD ΔE 21.3 and normal-vision ΔE 21.8, and it clears
   3:1 on this surface — it fails only the chroma floor, which is the point. */
:root {
  --surface: #ffffff;
  --series-1: #2a78d6;  /* booked */
  --series-2: #eb6834;  /* transferred */
  --series-3: #1baf7a;  /* handled, not booked */
  --series-mute: #b6bcc8;/* hang-up: an absence, not an outcome */
  --series-context: #52514e; /* totals: a reference, not a category */
  --ink: #111827;
  --ink-2: #52514e;
  --muted-ink: #6b7280;
  --grid: #e8ecf2;
  --axis: #c9d1dc;
}

/* ---- filters --------------------------------------------------------- */
.filters {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 12px;
}
.filter-search { position: relative; flex: 1 1 260px; min-width: 220px; }
.filter-search .search-ico {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; stroke: var(--muted-ink); stroke-width: 2; fill: none;
  stroke-linecap: round; pointer-events: none;
}
.filter-search input {
  width: 100%; padding: 9px 12px 9px 34px; font: inherit; font-size: .9rem;
  border: 1px solid #d1d5db; border-radius: 8px; background: #fff;
}
.filters input[type="date"], .filters select {
  padding: 8px 10px; font: inherit; font-size: .85rem;
  border: 1px solid #d1d5db; border-radius: 8px; background: #fff; color: inherit;
}
.f-date { font-size: .78rem; color: var(--muted-ink); display: flex; align-items: center; gap: 6px; }
/* ---- which site ---------------------------------------------------------
   The one control on this page that changes what every number underneath it
   means, so it is sized like a pair of tabs rather than like a filter chip.
   Two lines per button: the name, and the address it stands for — "HVAC site"
   alone is only obvious to somebody who already knows there are two. */
.site-switch { margin: 18px 0 14px; }
.site-btns {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
  max-width: 720px;
}
.site-btns button {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 14px 12px; font: inherit; cursor: pointer;
  background: #fff; border: 2px solid #d1d5db; border-radius: 12px;
  color: #475569; transition: border-color .15s, background .15s, color .15s;
}
.site-btns button:hover { border-color: #94a3b8; background: #f8fafc; }
.site-btns .ss-name { font-size: 1rem; font-weight: 800; letter-spacing: -.01em; }
.site-btns .ss-url {
  font-size: .74rem; font-weight: 600; opacity: .72;
  /* The address can be longer than the button on a narrow screen; it clips
     rather than wrapping, because a two-line address pushes the pair of
     buttons out of alignment with each other. */
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.site-btns button.on {
  background: #0f1b33; border-color: #0f1b33; color: #fff;
}
.site-btns button.on .ss-url { opacity: .78; }
.site-btns button:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
/* Stacked on a phone: three of these side by side leaves each one too narrow
   to read the address in, which is the half that does the explaining. */
@media (max-width: 640px) {
  .site-btns { grid-template-columns: 1fr; gap: 6px; }
  .site-btns button { flex-direction: row; justify-content: space-between; padding: 12px 14px; }
}
/* Printed, the toggle is not a control — but which site the figures came from
   is exactly the thing a printout must not lose, so the chosen one stays and
   the alternatives go. */
@media print {
  .site-btns button:not(.on) { display: none; }
  .site-btns { grid-template-columns: 1fr; }
  .site-btns button.on { background: #fff; color: #000; border-color: #000; }
}

.range-btns { display: flex; border: 1px solid #d1d5db; border-radius: 8px; overflow: hidden; }
.range-btns button {
  padding: 8px 13px; font: inherit; font-size: .82rem; font-weight: 600;
  background: #fff; border: 0; border-right: 1px solid #e2e8f0; color: #475569; cursor: pointer;
}
.range-btns button:last-child { border-right: 0; }
.range-btns button:hover { background: #f1f5f9; }
.range-btns button.on { background: #0f1b33; color: #fff; }
.filters input:focus-visible, .filters select:focus-visible, .range-btns button:focus-visible {
  outline: 2px solid var(--series-1); outline-offset: 1px;
}

/* ---- admin export bar -------------------------------------------------
   Only ever rendered for a staff account. Deliberately reads as a separate
   band rather than another filter: it acts on a different client's data than
   everything else on the page, and that should be visible at a glance. */
.admin-bar {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 12px; padding: 12px;
  background: #0f1b33; color: #e2e8f0;
  border: 1px solid #0f1b33; border-radius: 12px;
  font-size: .85rem;
}
.admin-bar .muted { color: #94a3b8; }
.admin-tag {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: var(--series-2); color: #fff; border-radius: 5px; padding: 3px 7px;
}
.admin-bar label { color: #cbd5e1; }
.admin-bar select {
  padding: 7px 10px; font: inherit; font-size: .85rem; max-width: 280px;
  border: 1px solid #35507f; border-radius: 8px; background: #fff; color: var(--ink);
}
.admin-bar button.ghost { border-color: #4b6796; color: #fff; }
/* The link to the site analytics page sits in the same row as the export
   buttons and has to read as one of them — a.ghost's own colour is set for a
   light background. */
.admin-bar a.ghost { border-color: #4b6796; color: #fff; margin-right: 0; }
.admin-bar button.ghost:hover:not(:disabled), .admin-bar a.ghost:hover { background: #1c2c4d; }
.admin-bar button.ghost:disabled { opacity: .55; cursor: default; }
.admin-bar select:focus-visible, .admin-bar button:focus-visible {
  outline: 2px solid #fff; outline-offset: 1px;
}
/* Which voice platform and agent the selected client is on. Monospace because
   these are ids you compare character by character against a provider console,
   not prose. */
.admin-agent {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .72rem; color: #94a3b8;
  overflow-wrap: anywhere;
}
/* Actions pushed to the right on a wide bar, wrapping as a group on a narrow
   one so the three buttons never split across lines separately. */
.admin-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 700px) {
  .admin-bar select { flex: 1 1 100%; max-width: none; }
  .admin-actions { margin-left: 0; flex: 1 1 100%; }
  .admin-actions button.ghost { flex: 1 1 auto; }
}

/* Looking at someone else's account. The whole page gets a warm edge and the
   topbar changes colour, because the one genuinely dangerous mistake here is
   reading another client's numbers while believing they are your own. */
body.viewing-other .topbar { background: #7c2d12; }
body.viewing-other .admin-bar { background: #7c2d12; border-color: #9a3412; }
body.viewing-other main { border-top: 3px solid var(--series-2); }

/* ---- stat tiles ------------------------------------------------------ */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px; margin-top: 16px;
}
.stat {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  text-align: center;
  border: 1px solid #e2e8f0;
}
/* Proportional figures: tabular would make a value like 121 look loose here. */
.stat span { display: block; font-size: 1.9rem; font-weight: 700; letter-spacing: -.02em; }
.stat label { font-size: .8rem; color: var(--muted-ink); }

/* ---- chart cards ----------------------------------------------------- */
.charts { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 16px; }
.card { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 18px; margin: 0; }
.chart-card.wide { grid-column: 1 / -1; }
.chart-card figcaption { margin-bottom: 12px; }
.chart-card h3 { font-size: .98rem; margin: 0 0 2px; }
.chart-card figcaption p { margin: 0; font-size: .78rem; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
/* Refetch holds the previous render rather than flashing a skeleton. */
.charts.loading, .stats.loading { opacity: .55; transition: opacity .15s; }

.donut-wrap { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.donut-wrap .chart { flex: 0 0 auto; width: 168px; }
.legend { list-style: none; padding: 0; margin: 0; flex: 1 1 150px; min-width: 150px; }
.legend li { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: .82rem; }
.legend .sw { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.legend .lb { color: var(--ink-2); }
.legend .vl { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
/* Under a full-width plot the legend reads as a row, not a stacked key —
   so the value sits next to its label instead of being pushed to the edge. */
.legend-row { display: flex; flex-wrap: wrap; gap: 2px 20px; margin-top: 10px; }
.legend-row li { padding: 2px 0; }
.legend-row .vl { margin-left: 0; }
.chart-empty { padding: 26px 0; text-align: center; color: var(--muted-ink); font-size: .84rem; }

#daily-table-wrap { margin-top: 16px; }
#daily-table-wrap td { font-variant-numeric: tabular-nums; }

/* Tooltips enhance; every value is also in the legend, labels, or the table. */
.tip {
  position: fixed; z-index: 100; pointer-events: none;
  background: #0f1b33; color: #fff; font-size: .78rem; line-height: 1.45;
  padding: 7px 10px; border-radius: 7px; box-shadow: 0 6px 20px rgba(15,27,51,.22);
  max-width: 220px;
}
.tip b { font-weight: 700; }

.count-pill {
  font-size: .72rem; font-weight: 600; color: var(--muted-ink);
  background: #eef2f7; border-radius: 20px; padding: 3px 9px; vertical-align: middle;
}
.table-scroll { overflow-x: auto; }

/* Present to a screen reader, absent to everything else. The delete column's
   header has no visible label — the buttons under it are self-evident and a
   word there would widen the column for nothing — but a table heading that is
   empty reads as an unnamed column to anyone listening to it. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* Deleting one row off the sheet.

   Faint rather than hidden. This is the one irreversible control on the page
   and it should not be what the eye lands on — but a control that appears only
   on hover cannot be found by someone who does not already know it is there,
   and it is invisible in a screenshot, which is how half of "is that feature
   in yet?" gets answered. So it is always drawn, at a weight that reads as
   available rather than as the point of the row, and it comes up to full
   strength when the row is under the pointer or it takes focus. */
.row-del {
  border: 0; background: none; cursor: pointer; padding: 4px 9px; border-radius: 7px;
  color: #c2c8d2; font-size: 1.05rem; line-height: 1; opacity: .75;
  transition: opacity .12s, background .12s, color .12s;
}
tr:hover .row-del { opacity: 1; color: #8b93a1; }
.row-del:hover { background: #fdecec; color: #b3261e; opacity: 1; }
.row-del:focus-visible { opacity: 1; outline: 2px solid #b3261e; outline-offset: 1px; }
.row-del[disabled] { opacity: .4; cursor: default; background: none; color: #c2c8d2; }

/* First column, not last.
   The sheet is a dozen columns wide and the page scrolls sideways to show it,
   so a delete button at the end of the row sat off-screen until you dragged
   the table across — once per row, on the page whose whole job is working
   through rows. Sticky was the obvious answer and does not work here: what
   overflows is the page rather than the table's own scroller, so there is
   nothing for the cell to stick to. The left edge is always on screen, so
   that is where it goes. Narrow, so it costs the columns that carry the
   actual information almost nothing. */
th.cell-del, td.cell-del { width: 1%; padding-left: 10px; padding-right: 2px; }

/* ---- an opened row on the "said yes" list ---------------------------
   A full-width panel under the row it belongs to, rather than a drawer over
   the page: the reason to open one of these is to compare it with the rows
   around it, and a drawer covers exactly what you are comparing against. */
tr.row-detail > td {
  background: #f8fafc; padding: 4px 18px 18px;
  border-bottom: 1px solid #e6eaf0;
}
.rd { display: grid; gap: 18px; grid-template-columns: 1fr; }
@media (min-width: 1000px) { .rd { grid-template-columns: 1.1fr .7fr 1.2fr; align-items: start; } }
.rd-block h4 {
  margin: 0 0 8px; font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted-ink);
}

/* Question above, answer under it. The answer carries the weight — the
   question is context you read once and the answer is what you came for. */
.rd-qa { margin: 0; }
.rd-qa dt { font-size: .78rem; color: var(--muted-ink); margin-top: 9px; }
.rd-qa dt:first-child { margin-top: 0; }
.rd-qa dd { margin: 1px 0 0; font-size: .88rem; color: var(--ink); }
.rd-qa dd b { font-weight: 700; }
.rd-was { color: #96591a; font-weight: 500; font-size: .8rem; }

.rd-media { list-style: none; margin: 0; padding: 0; }
.rd-media li {
  display: flex; gap: 10px; align-items: baseline; justify-content: space-between;
  font-size: .84rem; padding: 3px 0; color: var(--ink-2);
}
.rd-media b { font-variant-numeric: tabular-nums; color: var(--ink); }

/* The whole visit in order. Capped in height and scrolled inside itself: a
   visit that played four clips and answered six questions runs to fifty
   lines, and an opened row should not push the next one off the screen. */
.rd-timeline {
  list-style: none; margin: 0; padding: 0 6px 0 0;
  max-height: 220px; overflow-y: auto;
}
.rd-timeline li {
  display: flex; gap: 10px; font-size: .82rem; color: var(--ink-2);
  padding: 2px 0; line-height: 1.45;
}
.rd-t {
  flex: 0 0 auto; min-width: 42px; text-align: right;
  color: #98a1ae; font-variant-numeric: tabular-nums; font-size: .76rem;
}
.pill {
  display: inline-block; font-size: .72rem; font-weight: 600;
  padding: 3px 9px; border-radius: 20px; white-space: nowrap;
}
.pill-booked { background: #e7f0fc; color: #1b5296; }
.pill-transferred { background: #fdece5; color: #a8431c; }
.pill-handled { background: #e4f6ef; color: #10704f; }
.pill-hangup { background: #eef0f4; color: #5b6472; }

/* How far through the walkthrough somebody got, on the booked page. A bar and
   the figure together: twelve percentages are read one at a time, twelve bars
   are read at a glance, and the number is still there for the one row being
   looked at properly. The track is fixed-width so the bars line up down the
   column — a bar that sized itself to the cell would make every row its own
   scale and compare nothing. */
.watch-bar { display: flex; align-items: center; gap: 9px; }
/* Fixed-width track, so every bar is drawn against the same scale and the
   column can be compared down its length. Sizing the track to the cell would
   give each row its own scale and compare nothing. */
.watch-bar .track {
  flex: 0 0 auto; width: 86px; height: 7px; border-radius: 4px;
  background: #e8ebf0; overflow: hidden;
}
.watch-bar i { display: block; height: 100%; border-radius: 4px; }
.watch-bar span {
  font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink-2); min-width: 34px;
}
/* Watched most of it, watched some, watched none. Generous at the top because
   nobody sits through the last frame, and grey rather than red at the bottom:
   a booking that watched nothing is a call to prepare for, not a failure. */
.watch-bar i.is-most { background: #1b8a5a; }
.watch-bar i.is-some { background: #d68910; }
.watch-bar i.is-none { background: #cbd5e1; }

/* What one person played, in the enquiries sheet. The clip name is secondary
   and the percentage is the point, so the name is quiet and the figure carries
   the colour: green once they have all but finished, grey when they pressed
   play and went nowhere. */
.watched { display: flex; flex-direction: column; gap: 2px; }
.watched-line {
  display: flex; align-items: baseline; gap: 6px;
  font-size: .8rem; white-space: nowrap;
}
.watched-line span { color: #6b7280; }
.watched-line b { font-weight: 600; font-variant-numeric: tabular-nums; }
.watched-full { color: #10704f; }
.watched-thin { color: #9aa3ae; }

/* Under a lead's phone number in the enquiries sheet, and in a visit's
   profile: whether they agreed to text messages on a website form, one line
   for each of the form's two boxes. "agreed" is the green one and opens onto
   the record (when, the form, the page, the words); "did not agree" stays
   quiet. The sheet's cells do not wrap, so the two lines set how wide the
   column is. The record inside is a long sentence: it wraps to that width
   (width 0, then as wide as the lines above it) instead of stretching the
   column across the screen when somebody opens it. */
.texts-note { margin-top: 4px; font-size: .78rem; line-height: 1.4; }
.texts-note summary { cursor: pointer; color: #10704f; font-weight: 600; }
.texts-note details p {
  margin: 4px 0 0; color: #4b5563;
  white-space: normal; overflow-wrap: anywhere; width: 0; min-width: 100%;
}

/* One visit, in full: a panel over the dashboard rather than a page of its
   own, so closing it puts the reader back exactly where they were in a list
   they may have scrolled a long way down. */
/* Company name with its badges beside it, wrapping rather than widening the
   column when an account carries both. */
.acct-name { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Destructive, and dressed like it — quiet until you go near it, so it does
   not compete with Refresh sitting next to it. */
.ghost.danger { color: #a8431c; border-color: #f0d5cb; }
.ghost.danger:hover:not(:disabled) { background: #fdece5; border-color: #e0a892; }

.row-clickable { cursor: pointer; }
.row-clickable:hover td { background: #f6f8fb; }
.row-clickable:focus-visible { outline: 2px solid #2f5fd0; outline-offset: -2px; }

.drawer { position: fixed; inset: 0; z-index: 60; }
.drawer-scrim { position: absolute; inset: 0; background: rgba(15, 23, 42, .45); }
.drawer-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(560px, 100%); background: #fff;
  box-shadow: -8px 0 32px rgba(15, 23, 42, .18);
  display: flex; flex-direction: column;
}
.drawer-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 20px 22px 14px; border-bottom: 1px solid #e8ecf2;
}
.drawer-head h3 { margin: 0 0 2px; font-size: 1.05rem; }
.drawer-body { overflow-y: auto; padding: 4px 22px 28px; }
.drawer-block { padding: 18px 0; border-bottom: 1px solid #eef1f6; }
.drawer-block:last-child { border-bottom: 0; }
.drawer-block h4 {
  margin: 0 0 8px; font-size: .74rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: #6b7280;
}
/* Two columns on a comfortable panel, one when the phone takes it full-width. */
.drawer-fields { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin: 8px 0 0; }
.drawer-fields dt { color: #6b7280; font-size: .82rem; }
.drawer-fields dd { margin: 0; font-size: .88rem; font-weight: 500; }
body.drawer-open { overflow: hidden; }

/* Every clip, including the ones they skipped — an unplayed row is drawn as an
   empty track rather than left out, because "did not watch" is an answer. */
.clip-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 10px; }
.clip-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px; font-size: .84rem; margin-bottom: 4px;
}
.clip-head b { font-weight: 600; font-variant-numeric: tabular-nums; }
.clip-track { height: 6px; border-radius: 4px; background: #eef1f6; overflow: hidden; }
.clip-track i { display: block; height: 100%; background: #2f5fd0; border-radius: 4px; }
.clip-unplayed .clip-head span { color: #9aa3ae; }

/* Question above, answer below and louder: the answer is what is being read,
   the question is only there so it can be read without guessing. */
.qa-list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 12px; }
.qa-question { margin: 0; font-size: .78rem; color: #6b7280; }
.qa-answer { margin: 2px 0 0; font-size: .92rem; font-weight: 600; color: #111827; }
.qa-was { margin: 2px 0 0; font-size: .76rem; color: #9aa3ae; font-style: italic; }

.timeline { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 6px; }
.timeline li { display: flex; gap: 12px; font-size: .84rem; align-items: baseline; }
.timeline-at {
  color: #9aa3ae; font-variant-numeric: tabular-nums;
  flex: 0 0 auto; min-width: 42px;
}

@media (max-width: 560px) {
  .drawer-fields { grid-template-columns: 1fr; gap: 2px; }
  .drawer-fields dd { margin-bottom: 8px; }
}

@media (max-width: 760px) {
  .charts { grid-template-columns: 1fr; }
  .donut-wrap { justify-content: center; }
}

table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 12px; overflow: hidden; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #f1f5f9; font-size: .9rem; }
th { background: #f8fafc; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; color: #64748b; }
audio { height: 34px; max-width: 220px; }
.transcript-row pre {
  white-space: pre-wrap;
  background: #f8fafc;
  border-radius: 8px;
  padding: 12px;
  margin: 4px 0;
  font-size: .8rem;
  max-height: 260px;
  overflow: auto;
}
@media (max-width: 700px) {
  /* The calls table restacks into one card per call: six columns will not fit
     on a phone, and a sideways-scrolling table is worse than no table. */
  table, thead, tbody, tr, td, th { display: block; }
  thead { display: none; }
  tr { border-bottom: 2px solid #e2e8f0; padding: 8px 0; }
  td { border: 0; padding: 4px 12px; }
  audio { width: 100%; max-width: none; }

  /* Dropping the header row takes the column names with it, which leaves a
     bare "4:34" meaning nothing. Cells that aren't self-evident carry their
     own label here — the date, the outcome pill and the player explain
     themselves, so only the ones that don't are labelled (set in portal.js). */
  td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: .68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #94a3b8;
    margin-bottom: 1px;
  }

  /* Touch targets. These sizes are right for a mouse and too small for a
     thumb — roughly 25-36px against the ~44px a finger needs — so they grow
     only at phone widths and the desktop layout is untouched. */
  button.ghost,
  button.ghost.small,
  a.ghost.small,
  .range-btns button,
  .filters select,
  .filters input[type="date"] {
    min-height: 44px;
  }
  /* Inline-flex so the taller box still centres its label rather than
     stranding the text at the top. */
  button.ghost, button.ghost.small, a.ghost.small {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* Buttons inside a call row read as a row of actions; give them room to be
     tapped apart from each other. */
  td .ghost.small { margin: 2px 6px 2px 0; }
}

/* Legal links, outside the dashboard shell so they show on the login view too. */
.portal-foot {
  text-align: center; padding: 26px 20px 34px; font-size: 13px;
  display: flex; justify-content: center; gap: 22px; flex-wrap: wrap;
}
.portal-foot a { color: var(--muted-ink); text-decoration: none; display: inline-block; padding: 10px 0; }
.portal-foot a:hover { text-decoration: underline; }

/* ---- print ------------------------------------------------------------
   Ctrl/Cmd-P, or the Print button in the admin bar, gives a call report you
   can hand to a client or save as PDF from the browser's own print dialog.
   The screen is an instrument panel; paper is a record, so everything you
   could press, play or type into comes off, and the parts that only make
   sense as pixels (the hover tooltip, the scroll containers) go with it.

   The print header is the one thing added rather than removed: on paper there
   is no topbar naming the client and no filter row stating the period, and a
   table of calls that says neither is close to useless as a record. */
.print-head { display: none; }

@media print {
  /* Backgrounds are dropped by default in most print settings, so state the
     ink explicitly rather than relying on a dark surface that won't be there. */
  body { background: #fff; color: #000; font-size: 10.5pt; }

  /* Controls, navigation and anything interactive. A printed page of buttons
     is noise, and "Sync latest calls" on paper is an instruction to nobody. */
  .topbar, .filters, .admin-bar, .portal-foot, .tip,
  #chart-table-toggle, .play-rec, audio, .cal-link,
  #calls-count, .count-pill,
  td .ghost, td .ghost.small { display: none !important; }

  /* The report header, and only when printReport() has filled it in — a raw
     Ctrl+P before the dashboard has loaded shouldn't print an empty title. */
  .print-head[data-ready] { display: block; margin: 0 0 14pt; }
  .print-head h1 { font-size: 16pt; margin: 0 0 3pt; }
  .print-head p { font-size: 9pt; color: #333; margin: 0; }

  main { max-width: none; padding: 0; border-top: 0 !important; }
  h2 { font-size: 12pt; margin: 14pt 0 6pt; page-break-after: avoid; }
  .sechead { page-break-after: avoid; }

  /* The numbers, as a compact row that can't be split across a page break. */
  .stats { grid-template-columns: repeat(4, 1fr); gap: 8pt; page-break-inside: avoid; }
  .stat { border: 1pt solid #999; border-radius: 0; padding: 8pt; }
  .stat span { font-size: 15pt; }
  .stat label { font-size: 8pt; }

  /* Charts are SVG and print perfectly well, but they are the screen's story.
     On paper the daily table below carries the same numbers as text, which
     survives a photocopier and a fax machine — keep the table, drop the ink. */
  .charts { display: none !important; }
  #daily-table-wrap { display: block !important; }

  /* Both tables print in full: on screen these sit in a fixed-height scroll
     box, and printing that would emit only the rows that happen to be in
     view. Repeating the header on every page is what makes a multi-page call
     list readable. */
  .table-scroll { overflow: visible !important; max-height: none !important; }
  table { width: 100%; border-collapse: collapse; page-break-inside: auto; }
  thead { display: table-header-group; }
  tr { page-break-inside: avoid; }
  th, td {
    border: 1pt solid #bbb; padding: 3pt 5pt; font-size: 9pt;
    text-align: left; color: #000;
  }
  th { background: #eee !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  /* Outcome pills lose their colour in most print settings, so give them an
     outline that survives greyscale rather than a fill that turns into a
     smudge or vanishes entirely. */
  .pill {
    background: none !important; color: #000 !important;
    border: 1pt solid #666; padding: 0 3pt; font-size: 8.5pt;
  }

  /* An expanded transcript is genuinely useful on a printed record, so it
     stays — but it has to wrap rather than run off the edge of the sheet. */
  .transcript-row pre {
    white-space: pre-wrap; word-break: break-word;
    font-size: 8.5pt; background: none; border: 0; padding: 0;
  }

  /* The Recording and Transcript columns come off entirely rather than
     printing as two empty strips: their contents are a play button and a
     button that fetches, and with those hidden the cells left behind would
     read "No recording" beside rows that in fact have one. Columns 5 and 6 of
     the call table only — the transcript row underneath spans all six and
     stays. */
  #calls-table th:nth-child(5), #calls-table td:nth-child(5),
  #calls-table th:nth-child(6), #calls-table td:nth-child(6) { display: none; }

  @page { margin: 14mm; }
}

/* ------------------------------------------------ site analytics (site.html)

   Two forms the client dashboard doesn't have: a secondary stat row that sits
   quieter than the headline one, and a horizontal bar list used everywhere a
   ranking is more useful than a shape (the funnel, sources, devices, pages). */

.sub-stats { margin-top: 10px; }
.sub-stats .stat { padding: 10px 12px; }
.sub-stats .stat span { font-size: 1.25rem; }
.sub-stats .stat label { font-size: .74rem; }

.hbars { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.hbar-head {
  display: flex; align-items: baseline; gap: 10px; margin-bottom: 5px;
}
.hbar-label {
  font-size: .84rem; color: var(--ink-2); overflow-wrap: anywhere;
}
.hbar-value {
  margin-left: auto; font-size: .82rem; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.hbar-track {
  height: 10px; background: #eef2f7; border-radius: 20px; overflow: hidden;
}
.hbar-track i {
  display: block; height: 100%; background: #2f5fd0; border-radius: 20px;
}
/* What was lost between this step and the one above it. Sits under the bar in
   the muted tone: it is the gap that matters, not another number to rank. */
.hbar-drop {
  margin: 5px 0 0; font-size: .74rem; color: var(--muted-ink);
}

/* ------------------------------------------------- enquiries (enquiries.html)

   A sheet, so it behaves like one: the header stays put while the rows scroll
   under it, and anything that arrived since this browser last opened the page
   is badged — the one job the notification email used to do. */

.sheet-scroll { max-height: 72vh; overflow: auto; border-radius: 12px; }
.sheet-scroll thead th { position: sticky; top: 0; z-index: 2; }
.sheet-scroll td { white-space: nowrap; }
/* Notes is the one free-text column and the one that would push the sheet off
   the side of the screen. It wraps instead, so the other eight stay in view. */
.sheet-scroll td:last-child { white-space: normal; min-width: 190px; }
.sheet-scroll td a { color: #1b5296; text-decoration: none; }
.sheet-scroll td a:hover { text-decoration: underline; }

tr.row-new td { background: #f5f9ff; }
.new-badge {
  display: inline-block; margin-left: 8px; vertical-align: middle;
  font-size: .62rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: #1b5296; background: #e7f0fc; border-radius: 20px; padding: 2px 7px;
}

@media (max-width: 700px) {
  /* Same restack as the calls table: nine columns will not fit on a phone. */
  .sheet-scroll { max-height: none; }
  .sheet-scroll td { white-space: normal; }
}
/* The enquiries sheet gets more room than the dashboards: nine columns at the
   portal's usual width would scroll sideways on a screen with space to spare. */
main.wide { max-width: 1400px; }

/* Tabs on the enquiries page: two views of one filtered slice. Underline
   rather than a pill row — the filters below already carry a boxed control,
   and two of those side by side compete. */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid #dbe3ee; margin-bottom: 18px; }
.tabs button {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 10px 14px; margin-bottom: -1px; cursor: pointer;
  font: inherit; font-size: .9rem; font-weight: 600; color: var(--muted-ink);
}
.tabs button:hover { color: var(--ink); }
.tabs button.on { color: #0f1b33; border-bottom-color: #2f5fd0; }
.tabs button:focus-visible { outline: 2px solid #2f5fd0; outline-offset: -2px; }
