/* ============================================================================
   MinMax Pro - CLIENT PORTAL v2
   ----------------------------------------------------------------------------
   Loaded after portal-skin.css. That file established "Daylight" - the light,
   professional canvas. This one adds what the portal did not have: the LIVE
   half.

   ── THE IDEA ───────────────────────────────────────────────────────────────
   The Field Portal is an instrument: dark, dense, telemetric, built for the
   person doing the work. This is its other face, and it is deliberately the
   opposite - calm, warm, unhurried, built for someone who is waiting.

   The two are joined by one thing: the VISIT. What a crew member advances on a
   tablet in a driveway is what a customer watches move here, in the same eight
   stages, in their own words. That live tracker is the centrepiece of v2 and
   the reason a customer would open this page at all.

   Type follows the same rule as the Field Portal: a display face for names, a
   body face for sentences, and mono for anything that is a READING - a time, an
   amount, a reference. Consistency across the two surfaces is what makes them
   read as one product rather than two.
   ========================================================================== */

:root[data-portal="on"] {
  --pv-r:      18px;
  --pv-r-sm:   13px;
  --pv-lift:   0 1px 2px rgba(22,29,41,.05), 0 10px 26px -18px rgba(22,29,41,.28);
  --pv-lift-2: 0 2px 4px rgba(22,29,41,.05), 0 28px 56px -28px rgba(22,29,41,.36);
}

/* ══ THE LIVE TRACKER ═════════════════════════════════════════════════════
   What is happening right now, in the customer's language. Reads as a
   delivery tracker because that is the mental model everyone already has. */
.pv-live {
  position: relative; overflow: hidden;
  border-radius: var(--pv-r);
  background:
    radial-gradient(120% 150% at 88% -30%, color-mix(in srgb, var(--accent) 40%, transparent), transparent 62%),
    linear-gradient(140deg, var(--ink), color-mix(in srgb, var(--ink) 78%, #000));
  color: var(--on-teal);
  padding: 24px 24px 26px;
  box-shadow: var(--pv-lift-2);
}
.pv-live::after {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.045) 1px, transparent 1px);
  background-size: 32px 32px;
  -webkit-mask-image: radial-gradient(85% 130% at 50% 0%, #000, transparent 78%);
  mask-image: radial-gradient(85% 130% at 50% 0%, #000, transparent 78%);
  pointer-events: none;
}
.pv-live > * { position: relative; z-index: 1; }

.pv-live-eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--f-mono); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--teal-bright); margin-bottom: 12px;
}
.pv-live-eyebrow .pulse {
  width: 7px; height: 7px; border-radius: 50%; background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: pv-pulse 2.2s cubic-bezier(.66,0,0,1) infinite;
}
@keyframes pv-pulse {
  to { box-shadow: 0 0 0 11px transparent; }
}
@media (prefers-reduced-motion: reduce) { .pv-live-eyebrow .pulse { animation: none; } }

/* `[data-portal="on"]` on the front is not decoration. portal-skin.css sets a
   dark heading ink on every h1–h3 at (0,1,1); a bare `.pv-live h2` ties on
   specificity and loses on source order, which rendered the single most
   important sentence on the page as near-black text on a near-black panel -
   invisible, and invisible in a way a screenshot of the light theme would not
   reveal. */
[data-portal="on"] .pv-live h2 {
  font-family: var(--f-head); font-weight: 700;
  font-size: clamp(22px, 4.4vw, 29px); letter-spacing: -.025em;
  line-height: 1.15; color: #fff;
}
[data-portal="on"] .pv-live p { margin: 9px 0 0; font-size: 15px; line-height: 1.6; color: rgba(255,255,255,.82); max-width: 52ch; }
[data-portal="on"] .pv-live p b { color: #fff; font-weight: 700; }

/* The stage spine. Horizontal on a desktop, vertical on a phone - a customer
   reading four words per stage on a 360px screen needs the room. */
.pv-track { display: flex; gap: 0; margin-top: 22px; }
.pv-step { flex: 1 1 0; min-width: 0; position: relative; text-align: center; padding: 0 3px; }
.pv-step::before {
  content: ''; position: absolute; top: 11px; left: -50%; right: 50%; height: 2px;
  background: rgba(255,255,255,.16);
}
.pv-step:first-child::before { display: none; }
.pv-step.done::before, .pv-step.now::before { background: var(--teal-bright); }
.pv-step .pip {
  position: relative; z-index: 1;
  width: 22px; height: 22px; margin: 0 auto 9px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 70%, #000);
  border: 2px solid rgba(255,255,255,.22);
  font-family: var(--f-mono); font-size: 10px; color: rgba(255,255,255,.5);
}
.pv-step.done .pip { background: var(--teal-bright); border-color: var(--teal-bright); color: #06231c; }
.pv-step.now .pip {
  border-color: var(--teal-bright); color: var(--teal-bright);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--teal-bright) 22%, transparent);
}
.pv-step .lbl {
  font-size: 11px; line-height: 1.3; font-weight: 600;
  color: rgba(255,255,255,.44);
}
.pv-step.done .lbl { color: rgba(255,255,255,.68); }
.pv-step.now .lbl { color: #fff; font-weight: 700; }
.pv-step .at {
  display: block; font-family: var(--f-mono); font-size: 10px;
  color: rgba(255,255,255,.34); margin-top: 3px;
}

@media (max-width: 560px) {
  .pv-track { flex-direction: column; gap: 0; margin-top: 18px; }
  .pv-step { display: grid; grid-template-columns: 22px 1fr auto; gap: 12px; text-align: left; padding: 7px 0; align-items: center; }
  .pv-step::before { top: -50%; bottom: 50%; left: 10px; right: auto; width: 2px; height: auto; }
  .pv-step .pip { margin: 0; }
  .pv-step .at { margin-top: 0; }
}

.pv-live-who {
  display: flex; align-items: center; gap: 12px; margin-top: 22px;
  padding-top: 18px; border-top: 1px solid rgba(255,255,255,.12);
}
.pv-live-who .av {
  width: 42px; height: 42px; border-radius: 13px; flex: none;
  display: grid; place-items: center;
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.16);
  font-family: var(--f-mono); font-size: 14px; color: #fff;
}
.pv-live-who .nm { font-size: 15px; font-weight: 600; color: #fff; }
.pv-live-who .rl { font-size: 12.5px; color: rgba(255,255,255,.6); }

/* ══ APPROVALS ════════════════════════════════════════════════════════════
   The estimate a technician priced on the doorstep, or a document that needs
   a signature. The one thing on this portal that is genuinely urgent, so it
   gets the one genuinely urgent treatment. */
.pv-action {
  display: flex; gap: 15px; align-items: flex-start;
  padding: 18px 20px; border-radius: var(--pv-r);
  background: linear-gradient(180deg, #fffdf6, var(--card, #fff));
  border: 1px solid #f0dcae;
  box-shadow: var(--pv-lift);
}
.pv-action .ic {
  width: 40px; height: 40px; flex: none; border-radius: 12px;
  display: grid; place-items: center; font-size: 19px;
  background: color-mix(in srgb, var(--gold) 20%, var(--card, #fff));
}
.pv-action .bd { flex: 1; min-width: 0; }
.pv-action b { display: block; font-family: var(--f-head); font-size: 17px; letter-spacing: -.015em; color: var(--head); }
.pv-action p { margin: 5px 0 0; font-size: 14px; line-height: 1.55; color: var(--tx-soft); }
.pv-action .acts { display: flex; gap: 9px; margin-top: 14px; flex-wrap: wrap; }

/* ══ THE VISIT RECORD ═════════════════════════════════════════════════════
   A finished visit, as a receipt: what was done, who did it, what it came to,
   and what was signed. This is the thing customers phone up to ask about. */
.pv-visit {
  border-radius: var(--pv-r); background: var(--card);
  border: 1px solid var(--line); box-shadow: var(--pv-lift);
  overflow: hidden;
}
.pv-visit-top {
  display: grid; grid-template-columns: 1fr auto; gap: 6px 16px;
  padding: 17px 20px; align-items: start;
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(180deg, var(--card-2), var(--card));
}
.pv-visit-top .ttl { font-family: var(--f-head); font-weight: 700; font-size: 17.5px; letter-spacing: -.015em; color: var(--head); }
.pv-visit-top .when { grid-column: 1; font-family: var(--f-mono); font-size: 12px; color: var(--tx-mute); letter-spacing: .02em; }
.pv-visit-top .amt {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
  font-family: var(--f-mono); font-size: 18px; font-weight: 600; color: var(--head);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pv-visit-body { padding: 6px 0; }
.pv-visit-row {
  display: grid; grid-template-columns: 92px 1fr auto; gap: 14px;
  padding: 11px 20px; align-items: baseline;
  border-bottom: 1px solid var(--line-soft); font-size: 14px;
}
.pv-visit-row:last-child { border-bottom: 0; }
.pv-visit-row .k { font-family: var(--f-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--tx-mute); }
.pv-visit-row .v { color: var(--tx); }
.pv-visit-row .v small { display: block; color: var(--tx-mute); font-size: 12.5px; margin-top: 2px; }
@media (max-width: 520px) {
  .pv-visit-row { grid-template-columns: 1fr; gap: 3px; }
}

/* ══ MONEY ════════════════════════════════════════════════════════════════ */
.pv-bal {
  display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center;
  padding: 22px 24px; border-radius: var(--pv-r);
  background: var(--card); border: 1px solid var(--line); box-shadow: var(--pv-lift);
}
.pv-bal .k { font-family: var(--f-mono); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--tx-mute); }
.pv-bal .v {
  display: block; font-family: var(--f-mono); font-size: 34px; font-weight: 600;
  letter-spacing: -.035em; color: var(--head); line-height: 1.05; margin-top: 7px;
  font-variant-numeric: tabular-nums;
}
.pv-bal.clear .v { color:var(--ok-tx, var(--ok)); }
.pv-bal .sub { display: block; font-size: 13px; color: var(--tx-soft); margin-top: 7px; }

/* Payment progress - a bar is the fastest way to answer "how much is left".
   Set into the card rather than laid on it: the inset shadow makes the track a
   channel and the fill an object sitting in it, which is what tells you at a
   glance that the empty part is remaining balance and not just background. The
   fill carries a top-light sheen over its gradient and animates to width, so a
   payment landing reads as the bar advancing. */
.pv-meter {
  height: 9px; border-radius: 999px; background: var(--line-soft); overflow: hidden; margin-top: 11px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,.16);
}
.pv-meter i {
  display: block; height: 100%; border-radius: 999px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.34) 0, rgba(255,255,255,.06) 46%, rgba(0,0,0,.08) 100%),
    linear-gradient(90deg, var(--accent), var(--accent-bright));
  box-shadow: 0 1px 3px color-mix(in srgb, var(--accent) 42%, transparent);
  transition: width .8s cubic-bezier(.22,1,.36,1);
}
.pv-meter.full i {
  background:
    linear-gradient(180deg, rgba(255,255,255,.34) 0, rgba(255,255,255,.06) 46%, rgba(0,0,0,.08) 100%),
    linear-gradient(90deg, var(--ok), color-mix(in srgb, var(--ok) 62%, #fff));
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ok) 42%, transparent);
}
@media (prefers-reduced-motion: reduce) { .pv-meter i { transition: none; } }

/* ══ MESSAGES ═════════════════════════════════════════════════════════════
   The thread widgets (.p3-*) already exist in portal-skin.css for consulting.
   These are the pieces the Field Service thread needed on top. */
.pv-thread-empty {
  text-align: center; padding: 34px 20px; color: var(--tx-soft);
}
.pv-thread-empty .ic {
  width: 58px; height: 58px; margin: 0 auto 14px; border-radius: 18px;
  display: grid; place-items: center; font-size: 24px;
  background: var(--card-2); border: 1px solid var(--line);
}
.pv-thread-empty b { display: block; font-family: var(--f-head); font-size: 18px; color: var(--head); margin-bottom: 5px; }

/* ══ TIMELINE ═════════════════════════════════════════════════════════════ */
.pv-feed { display: grid; gap: 0; }
.pv-feed-row {
  display: grid; grid-template-columns: 26px 1fr auto; gap: 14px;
  padding: 13px 0; align-items: start;
  border-bottom: 1px solid var(--line-soft);
}
.pv-feed-row:last-child { border-bottom: 0; }
/* A visit with a record behind it opens it (audit 2026-09-03 C10): the row is
   a <button>, reset to read exactly like its sibling <div>s. */
button.pv-feed-row { width: 100%; background: none; border: 0; border-bottom: 1px solid var(--line-soft); font: inherit; color: inherit; text-align: left; cursor: pointer; border-radius: 0; }
button.pv-feed-row:last-child { border-bottom: 0; }
button.pv-feed-row:hover .bd b, button.pv-feed-row:focus-visible .bd b { color: var(--accent-deep, var(--accent)); }
button.pv-feed-row:focus-visible { outline: 2px solid var(--accent, var(--teal)); outline-offset: 2px; border-radius: 8px; }
.pv-feed-row .dot {
  width: 26px; height: 26px; border-radius: 9px; display: grid; place-items: center;
  background: var(--card-2); border: 1px solid var(--line); font-size: 13px;
}
.pv-feed-row .bd b { display: block; font-size: 14.5px; font-weight: 600; color: var(--head); }
.pv-feed-row .bd span { font-size: 13px; color: var(--tx-soft); }
.pv-feed-row .at { font-family: var(--f-mono); font-size: 11.5px; color: var(--tx-mute); white-space: nowrap; }

/* ══ REFINEMENTS TO THE EXISTING SHELL ════════════════════════════════════ */

/* Cards: a touch more air and a softer float than the base skin. */
[data-portal="on"] .card { border-radius: var(--pv-r); box-shadow: var(--pv-lift); }

/* The greeting hero carries the mode's accent; give the type real presence. */
.ov-hero { border-radius: var(--pv-r); }
.ov-hero h2 { letter-spacing: -.03em; }

/* Tiles wrap rather than squeeze on a phone - four 80px columns of numbers is
   not a dashboard, it is a puzzle. */
@media (max-width: 720px) {
  .ov-tiles { grid-template-columns: repeat(2, 1fr); }
}

/* Job cards get a status spine, the same visual grammar the crew's board uses. */
.jcard { position: relative; padding-left: 20px !important; }
.jcard::before {
  content: ''; position: absolute; left: 0; top: 14px; bottom: 14px; width: 4px;
  border-radius: 0 4px 4px 0; background: var(--line);
}
.jcard.s-live::before { background: var(--gold); box-shadow: 0 0 12px 0 var(--gold); }
.jcard.s-done::before { background: var(--ok); }
.jcard.s-next::before { background: var(--accent); }
.jcard .ttl { letter-spacing: -.015em; }
.jcard .amt {
  font-family: var(--f-mono); font-size: 14px; font-weight: 600; color: var(--head);
  font-variant-numeric: tabular-nums;
}

/* A tappable card has to say so. */
.jcard.tappable { cursor: pointer; transition: transform .16s cubic-bezier(.22,1,.36,1), box-shadow .16s; }
.jcard.tappable:hover { transform: translateY(-2px); box-shadow: var(--pv-lift-2); }

/* ══ THE OVERVIEW, AS A HUB ═══════════════════════════════════════════════
   The Overview used to answer every question with a COUNT - "Documents 2",
   "Balance $412" - and a count is not an answer. Which two documents? What is
   the $412 for? Every question the page raised had to be re-asked on a tab.

   A SECTION previews instead: three rows, each one tappable straight through,
   and a link to the rest. The tiles above them still exist, because "how much
   do I owe" deserves to be legible from a doorstep in one glance - but they are
   no longer the whole answer. */
.ov-sec-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.ov-sec-h .section-label { margin: 0; }
.ov-sec-link {
  border: 0; background: none; padding: 0; margin: 0; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--accent-deep);
  white-space: nowrap; transition: opacity .16s;
}
.ov-sec-link:hover { opacity: .72; text-decoration: underline; }

.ov-mini-list { display: flex; flex-direction: column; margin-top: 8px; }
.ov-mini {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 2px; border: 0; border-bottom: 1px solid var(--line-soft);
  background: none; font: inherit; color: var(--tx); text-align: left; cursor: default;
  transition: background .16s, padding .16s;
}
button.ov-mini { cursor: pointer; }
.ov-mini:last-child { border-bottom: 0; }
button.ov-mini:hover { background: var(--card-2); padding-inline: 10px; border-radius: 10px; }
/* The marker is the row status, read before any of its words. */
.ov-mini .mk {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: var(--line); box-shadow: 0 0 0 3px color-mix(in srgb, var(--line) 30%, transparent);
}
.ov-mini.t-accent .mk { background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.ov-mini.t-warn   .mk { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 16%, transparent); }
.ov-mini.t-ok     .mk { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 16%, transparent); }
.ov-mini .bd { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ov-mini .bd b { font-size: 14.5px; font-weight: 600; color: var(--head); }
.ov-mini .bd span {
  font-size: 12.5px; color: var(--tx-soft); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ov-mini .rt { flex: none; text-align: right; display: flex; flex-direction: column; gap: 2px; }
.ov-mini .rt b { font-family: var(--f-num); font-size: 14px; font-weight: 700; color: var(--head); white-space: nowrap; }
.ov-mini .rt span { font-size: 11.5px; color: var(--tx-mute); white-space: nowrap; }
.ov-mini .go { flex: none; color: var(--tx-mute); font-size: 17px; line-height: 1; transition: transform .16s, color .16s; }
button.ov-mini:hover .go { transform: translateX(2px); color: var(--accent-deep); }
@media (max-width: 470px) {
  .ov-mini { gap: 10px; }
  .ov-mini .rt b { font-size: 13px; }
}

/* ══ QUOTES ══════════════════════════════════════════════════════════════
   The one card on this page that asks the customer to spend money, so it is
   the one card allowed to be loud. It reuses .pv-action - the same shape as
   the estimate-approval card - because they are the same act at two different
   moments, and a customer should not have to learn two layouts to say yes to
   the same business twice. */
.pv-quote-hero .ic { color: var(--accent-deep); }
.pv-quote-hero .ic svg { width: 20px; height: 20px; }

.pv-qlist { display: flex; flex-direction: column; margin-top: 8px; }
.pv-qrow {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 14px 2px; border: 0; border-bottom: 1px solid var(--line-soft);
  background: none; font: inherit; color: var(--tx); text-align: left; cursor: pointer;
  transition: background .16s, padding .16s;
}
.pv-qrow:last-child { border-bottom: 0; }
.pv-qrow:hover { background: var(--card-2); padding-inline: 10px; border-radius: 12px; }
.pv-qrow .bd { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.pv-qrow .bd b { font-family: var(--f-head); font-size: 16px; font-weight: 700; letter-spacing: -.015em; color: var(--head); }
.pv-qrow .bd .sub { font-size: 12.5px; color: var(--tx-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-qrow .rt { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.pv-qrow .rt .amt { font-family: var(--f-num); font-size: 17px; font-weight: 700; color: var(--head); }
/* An open quote carries the accent down its left edge - the same device the
   job cards use to say "this one is live". */
.pv-qrow.s-open { position: relative; padding-left: 14px; }
.pv-qrow.s-open::before {
  content: ''; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px;
  border-radius: 2px; background: linear-gradient(var(--accent-bright), var(--accent));
}
.pv-qrow.s-declined .bd b, .pv-qrow.s-expired .bd b { color: var(--tx-soft); }

/* ── The quote sheet ─────────────────────────────────────────────────────── */
.pv-qhead { display: flex; align-items: flex-start; gap: 16px; }
.pv-qhead .grow { flex: 1; min-width: 0; }
.pv-qhead .ttl { font-family: var(--f-head); font-size: 19px; font-weight: 700; letter-spacing: -.02em; color: var(--head); }
.pv-qhead .sub { font-size: 12.5px; color: var(--tx-soft); margin-top: 4px; }
.pv-qhead .rt { flex: none; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 7px; }
.pv-qhead .amt { font-family: var(--f-num); font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--head); line-height: 1; }

.pv-qsec { display: flex; flex-direction: column; gap: 2px; }
.pv-qp { font-size: 14px; line-height: 1.65; color: var(--tx); margin: 4px 0 0; white-space: pre-wrap; }
.pv-qul { margin: 6px 0 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.pv-qul li { font-size: 13.5px; line-height: 1.55; color: var(--tx); }
.pv-qul li::marker { color: var(--accent); }
.pv-qnote {
  border: 1px solid var(--line-soft); border-left: 3px solid var(--warn); border-radius: 12px;
  padding: 11px 13px; font-size: 13px; line-height: 1.55; color: var(--tx-soft); background: var(--card-2);
}
.pv-qnote.ok { border-left-color: var(--ok); }

/* The packages. Where there is a choice these are radio buttons in everything
   but name, so they look and behave like one: a mark, a press state, and a
   total that moves when the selection does. */
.pv-qopts { display: flex; flex-direction: column; gap: 9px; margin-top: 8px; }
.pv-qopt {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 13px 15px; border-radius: 13px; border: 1px solid var(--line);
  background: var(--card-2); font: inherit; color: var(--tx); cursor: pointer;
  transition: border-color .16s, background .16s, box-shadow .16s;
}
.pv-qopt.static { cursor: default; }
button.pv-qopt:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); background: var(--card, #fff); }
.pv-qopt.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--card, #fff)); box-shadow: var(--sh-1); }
.pv-qopt .mk {
  flex: none; width: 17px; height: 17px; border-radius: 50%;
  border: 2px solid var(--line); background: var(--card, #fff); transition: border-color .16s, box-shadow .16s;
}
.pv-qopt.on .mk { border-color: var(--accent); box-shadow: inset 0 0 0 3.5px var(--accent); }
.pv-qopt .bd { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pv-qopt .bd b { font-size: 14.5px; font-weight: 650; color: var(--head); }
.pv-qopt .bd .sub { font-size: 11.5px; color: var(--tx-mute); }
.pv-qopt .amt { flex: none; font-family: var(--f-num); font-size: 16px; font-weight: 700; color: var(--head); white-space: nowrap; }
.pv-qhint { font-size: 12px; color: var(--tx-mute); margin-top: 8px; }

.pv-qtl { display: flex; flex-direction: column; margin-top: 6px; }
.pv-qtl-row { display: flex; gap: 14px; padding: 9px 0; border-bottom: 1px solid var(--line-soft); }
.pv-qtl-row:last-child { border-bottom: 0; }
.pv-qtl-row .wh {
  flex: none; width: 108px; font-family: var(--f-mono); font-size: 11.5px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--tx-mute); padding-top: 2px;
}
.pv-qtl-row .bd { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pv-qtl-row .bd b { font-size: 14px; font-weight: 600; color: var(--head); }
.pv-qtl-row .bd span { font-size: 12.5px; line-height: 1.5; color: var(--tx-soft); }
@media (max-width: 520px) {
  .pv-qhead { flex-direction: column; gap: 10px; }
  .pv-qhead .rt { align-items: flex-start; text-align: left; }
  .pv-qtl-row { flex-direction: column; gap: 3px; }
  .pv-qtl-row .wh { width: auto; }
}

/* ── Tab bar ──────────────────────────────────────────────────────────────
   Home services went from five tabs to seven, which stopped fitting a 13"
   laptop, and a horizontally-scrolling tab strip whose scrollbar is hidden
   looks like a truncation bug rather than a scroller. The tabs got tighter and
   the strip got a fade meant to say "there is more this way".

   The fade did not say that. It was a plain 26px white gradient, painted
   whether or not there was anything past it, and it read as a rendering
   artifact sitting to the right of the nav - which is exactly how it was
   reported. A message nobody receives is not an affordance.

   So: an ARROW, and only when the strip actually overflows. `animation-timeline:
   scroll(self inline)` drives it from the scroll position itself, with no
   listener and no class to keep in sync - it fades out as the end is reached,
   and a strip with nothing to scroll has no scroll range, so it never appears
   at all. Browsers without scroll-driven animations keep the cue permanently
   visible on an overflowing strip, which is the honest fallback: it is a
   chevron pointing at more tabs, not a smear. */
.ptabs { position: relative; }
.ptabs button { font-size: 13px; letter-spacing: -.005em; padding-inline: 13px; white-space: nowrap; }
.ptabs .ptab-badge { flex: none; }
@media (max-width: 900px) {
  .ptabs button { padding-inline: 11px; font-size: 12.5px; }
}
/* The cue rides on the SCROLLER, so it stays put while the tabs move under it.
   The gradient is only wide enough to keep the chevron legible over a tab
   label; it is a backdrop for the arrow, not a curtain over the content. */
.ptabs::after {
  content: ''; position: sticky; right: 0; flex: none;
  align-self: stretch; width: 30px; margin-left: -30px; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--card) 62%);
  opacity: 0;
}
/* The chevron itself, masked so it takes the theme's own muted ink rather than
   a color baked into a data URI. Its own element because the backdrop above is
   a gradient and a mask cannot apply to one layer of a background. */
.ptabs::before {
  content: ''; position: sticky; right: 4px; flex: none; order: 99;
  align-self: center; width: 16px; height: 16px; margin-left: -16px;
  pointer-events: none; z-index: 1;
  background: var(--tx-mute, #6b7480);
  -webkit-mask: var(--ptabs-chev) no-repeat 50% / 16px 16px;
  mask: var(--ptabs-chev) no-repeat 50% / 16px 16px;
  opacity: 0;
}
.ptabs {
  --ptabs-chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
}
/* Only a strip that overflows has a scroll range to drive this. */
@supports (animation-timeline: scroll(self inline)) {
  .ptabs::after, .ptabs::before {
    animation: ptabs-more linear both;
    animation-timeline: scroll(self inline);
    /* Gone by the time the last tab is fully in view, rather than hovering
       over the end of a strip that has nothing left to show. */
    animation-range: calc(100% - 44px) 100%;
  }
}
@supports not (animation-timeline: scroll(self inline)) {
  /* No scroll-driven animations: show the cue whenever the strip can scroll.
     `scrollbar-width: none` hides the bar, not the overflow, so this is the
     only signal those browsers get. */
  .ptabs::after, .ptabs::before { opacity: 1; }
}
@keyframes ptabs-more { from { opacity: 1; } to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  /* The cue still appears and disappears - it is state, not decoration - but
     it does so without the interpolated fade. */
  .ptabs::after, .ptabs::before { animation-timing-function: steps(1, end); }
}

/* Footer credit sits quieter. */
[data-portal="on"] .portal-foot { opacity: .9; }

/* ── Narrow phones ────────────────────────────────────────────────────────
   The top bar carries a lockup, a name and a sign-out button. At 390px the
   tenant's own sub-label wrapped to two lines and the customer's name to
   another, turning a 56px header into 110px of chrome above the content they
   came for. Nothing is dropped - the name truncates, the sub-label stays on
   one line - because a white-label surface losing the tenant's name is worse
   than a cramped one. */
@media (max-width: 470px) {
  .portal-top .brand-wrap small,
  .brand-lockup-sub { white-space: nowrap; font-size: 9.5px; letter-spacing: .12em; }
  .portal-top .who { gap: 8px; min-width: 0; }
  .portal-top .who .nm {
    max-width: 11ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .pv-live { padding: 20px 18px 22px; }
  .pv-bal { grid-template-columns: 1fr; }
  .pv-bal .btn { width: 100%; }
  /* The grids collapsed but their horizontal padding did not, so these cards
     still spent 24px a side inside an 18px page gutter - 42px per side of a
     375px screen for a balance and a button. */
  .pv-bal { padding: 18px 16px; }
  .pv-visit-top { padding: 15px 16px; }
  .pv-visit-row { padding: 11px 16px; }
  /* The greeting hero is the first thing a customer sees and it was spending
     96px of a 375px screen on inset for one heading and one sentence. */
  .ov-hero { padding: 22px 18px 20px; border-radius: 16px; }
}

/* Nothing on this surface should ever scroll sideways. */
[data-portal="on"] body { overflow-x: hidden; }
