/* ==========================================================================
   Client portal — "Live Signal" design system.

   Self-contained: the client-facing pages (_ClientLayout, _AuthLayout) link
   ONLY this file, never the admin site.css. The identity is built around the
   subject's core idea — uptime monitoring is a heartbeat — so the accent is a
   "signal" emerald and the signature motif is a live pulse.
   ========================================================================== */

:root {
    --ink: #0f1f19;          /* deep forest-slate — text & headings          */
    --ink-soft: #33453d;     /* secondary headings                            */
    --muted: #5f7268;        /* secondary text (desaturated green-gray)       */
    --faint: #8a998f;        /* tertiary / captions                           */
    --bg: #eef3ef;           /* soft green-tinted paper                       */
    --bg-2: #f6f9f6;         /* lighter band                                  */
    --surface: #ffffff;
    --line: #e0e8e2;         /* hairline borders                              */
    --line-soft: #eef2ee;

    --signal: #12b886;       /* the "alive / up" accent (hero color)          */
    --signal-deep: #0b6b4f;  /* deep emerald — emphasis & brand ink           */
    --signal-tint: #e6f7f0;  /* pale fill behind online states                */
    --teal: #0aa5a5;         /* gradient partner                              */

    --down: #e5484d;
    --down-tint: #fdecec;
    --warn: #c67c0a;
    --warn-tint: #fbf1de;
    --neutral-tint: #eef2ef;

    --radius: 14px;
    --radius-sm: 10px;
    --shadow: 0 1px 2px rgba(15,31,25,.04), 0 6px 20px rgba(15,31,25,.05);
    --shadow-lift: 0 2px 4px rgba(15,31,25,.06), 0 16px 36px rgba(11,107,79,.10);

    --font-display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
    --font-body: "Hanken Grotesk", "Segoe UI", system-ui, -apple-system, sans-serif;
    --font-mono: "Space Mono", ui-monospace, "Cascadia Code", Consolas, monospace;

    /* Wallpaper tile, same size as the admin portal so the two share a rhythm. Laid once — a second
       copy at another scale would cross the first, and crossing contours are not contours. */
    --topo-tile: 1320px;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    min-height: 100%;
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.5;
    /* No -webkit-font-smoothing: antialiased here, deliberately. It is a macOS-era tweak, and on WINDOWS
       it switches Chrome off ClearType subpixel rendering and onto grayscale antialiasing — which is what
       made every letter in both portals look faintly fuzzy. Left at the browser default, Windows renders
       text with subpixel AA and the edges come back crisp. text-rendering: optimizeLegibility went with it
       for the same reason: it buys nothing here and is a known source of soft rasterisation.
       Note this file is read on Windows, macOS and Linux — the default is the right answer on all three. */
}

/* ---- Wallpaper ------------------------------------------------------------
   The admin portal's treatment in Live Signal's own colours. What is shared is
   the SYSTEM — vector terrain, fixed attachment, flat fill — not the palette:
   the ink here is the signal emerald, and no navy or blue reaches this file.

   Same seamless contour geometry as the admin tile, emitted with this palette's
   ink into topo-client.svg — one terrain, two inks, generated together so they
   cannot drift. Iso-lines traced through a height field, so they converge and
   merge but never cross; laid once, because a second copy at another scale would
   cross the first everywhere it overlapped.

   FLAT behind them, no gradient. The emerald/teal blooms that used to provide the
   colour banded: a near-flat gradient over a large area has too few 8-bit steps
   to look smooth, and the dithering Chrome applies to hide that reads as speckle.
   The colour lives in the lines instead.

   Cards are opaque `--surface`, so nothing a client reads sits on the texture.

   On `html`, NOT on `body`: `fixed` attachment positions a background against the
   viewport but still CLIPS it to the element's own box, and this portal's body is
   only as tall as its content — so the wallpaper used to end in a hard horizontal
   line partway down the page. The root element's background is propagated to the
   canvas, so it covers the viewport whatever the content height. */
html {
    background-color: var(--bg);
    background-image: url("../img/topo-client.svg");
    background-size: var(--topo-tile) var(--topo-tile);
    background-repeat: repeat;
    background-attachment: fixed;
}

/* Anyone who has asked for more contrast is not asking for atmosphere. */
@media (prefers-contrast: more) {
    html { background-image: none; }
}

a { color: var(--signal-deep); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { font-family: var(--font-display); color: var(--ink); letter-spacing: -0.01em; }

.mono { font-family: var(--font-mono); font-size: .9em; }
.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: 13.5px; }
.hint a { color: var(--signal-deep); font-weight: 600; }

/* ---- Top bar (signed-in client chrome) ------------------------------------ */
.client-topbar {
    display: flex; align-items: center; gap: 14px;
    padding: 14px 26px;
    /* Frosted glass, matching the admin bar: the terrain reads through it blurred, so the bar belongs to
       the field behind it. Same accepted trade — backdrop-filter forces a compositing layer and Chrome
       drops subpixel text antialiasing on a non-opaque one, so this bar's text is fractionally softer than
       the cards below. Green-tinted to match this palette. */
    /* Same reduction as the admin bar, and for the same reason: at 90% opacity there was nothing to see
       through, so the blur had no visible effect. */
    background: linear-gradient(180deg, rgba(237, 246, 241, .68), rgba(211, 229, 220, .58));
    backdrop-filter: saturate(1.5) blur(10px);
    border-bottom: 1px solid rgba(15, 31, 25, .09);
    position: sticky; top: 0; z-index: 20;
}
.client-topbar .brand-lockup { display: flex; align-items: center; gap: 13px; }
.client-topbar .brand-hms { height: 30px; display: block; }
.client-topbar .brand-pidoxa { height: 19px; display: block; }
.client-topbar .brand-div { width: 1px; height: 24px; background: var(--line); display: block; }
.client-topbar .title {
    font-family: var(--font-display); font-weight: 600; font-size: 15px;
    color: var(--ink); letter-spacing: .01em;
}
.client-topbar .title::before {
    /* live dot to the left of the wordmark — the portal itself is "live" */
    content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    background: var(--signal); margin-right: 9px; vertical-align: middle;
    box-shadow: 0 0 0 0 rgba(18,184,134,.5); animation: pulse-ring 2.4s ease-out infinite;
}
.client-topbar .spacer { margin-left: auto; }
.client-topbar .user-email { font-size: 13px; color: var(--muted); }

.client-main { max-width: 1000px; margin: 30px auto 60px; padding: 0 26px; }

/* ---- Page headings -------------------------------------------------------- */
.page-title { font-size: 30px; font-weight: 700; margin: 0 0 4px; line-height: 1.1; }
.page-sub { color: var(--muted); margin: 0 0 26px; font-size: 15px; }

/* ---- Cards ---------------------------------------------------------------- */
.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 22px 24px;
    margin-bottom: 20px;
    box-shadow: var(--shadow);
}
.card h2 {
    font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .09em;
    color: var(--faint); margin: 0 0 16px;
    display: flex; align-items: center; gap: 9px;
}
.card h2 svg { width: 15px; height: 15px; color: var(--signal-deep); flex: 0 0 auto; }

/* ---- Data tables ---------------------------------------------------------- */
table.data { width: 100%; border-collapse: collapse; }
table.data th, table.data td {
    text-align: left; padding: 11px 6px; border-bottom: 1px solid var(--line-soft);
    vertical-align: middle;
}

table.data tr:last-child th, table.data tr:last-child td { border-bottom: none; }

/* A word is never broken to make a table fit — `overflow-wrap` stays normal, so no token is ever snapped
   mid-character; when a table needs more room than its card, the CARD scrolls. Cards have auto height, so
   forcing overflow-x cannot produce a stray vertical scrollbar here.

   The default here is the opposite of the admin sheet's, and deliberately: nearly every table in the client
   portal is label/value — Protection, Certificate, Last backup, a ticket's About — and the value is a
   sentence written for a reader. So label cells hold their line and value cells reflow. There is no dense
   identifier grid on this side to protect; `.nowrap` is available for a cell that turns out to need it. */
.card:has(table.data) { overflow-x: auto; }
table.data th { white-space: nowrap; }
table.data td { white-space: normal; }
table.data td.nowrap, table.data td .nowrap { white-space: nowrap; }
table.data th {
    font-size: 12px; font-weight: 600; color: var(--faint);
    width: 150px; white-space: nowrap;
}
table.data td { color: var(--ink); }

/* ---- Pills ---------------------------------------------------------------- */
.pill {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 3px 11px 3px 9px; border-radius: 999px;
    font-size: 12.5px; font-weight: 600; font-family: var(--font-body);
    line-height: 1.6;
}
.pill-online   { background: var(--signal-tint);  color: var(--signal-deep); }
.pill-offline  { background: var(--down-tint);    color: var(--down); }
.pill-neutral  { background: var(--neutral-tint);  color: var(--muted); }
.pill-warning  { background: var(--warn-tint);    color: var(--warn); }

/* ---- Status dots + the live pulse (the signature motif) ------------------- */
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--faint); flex: 0 0 auto; display: inline-block; }
.dot-online  { background: var(--signal); }
.dot-offline { background: var(--down); }
.dot-warning { background: var(--warn); }
.dot-unknown { background: var(--faint); }

/* A live dot emits a soft expanding ring — the monitoring heartbeat. */
.dot.live { position: relative; }
.dot.live::after {
    content: ""; position: absolute; inset: 0; border-radius: 50%;
    box-shadow: 0 0 0 0 rgba(18,184,134,.55);
    animation: pulse-ring 2.2s ease-out infinite;
}
@keyframes pulse-ring {
    0%   { box-shadow: 0 0 0 0 rgba(18,184,134,.5); }
    70%  { box-shadow: 0 0 0 9px rgba(18,184,134,0); }
    100% { box-shadow: 0 0 0 0 rgba(18,184,134,0); }
}

/* ---- Buttons -------------------------------------------------------------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    border: 1px solid transparent; border-radius: 10px;
    padding: 10px 18px; font-family: var(--font-body); font-size: 14px; font-weight: 600;
    cursor: pointer; transition: transform .12s ease, background-color .15s ease, box-shadow .15s ease, border-color .15s ease;
    text-decoration: none;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(18,184,134,.35); }
.btn-primary { background: var(--signal-deep); color: #fff; }
.btn-primary:hover { background: #0a5b43; color: #fff; box-shadow: var(--shadow-lift); }
.btn-light { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-light:hover { background: var(--bg-2); border-color: #cfdcd4; }
.btn-sm { padding: 7px 14px; font-size: 13px; border-radius: 9px; }
.btn-block { display: flex; width: 100%; }

/* ---- Alerts --------------------------------------------------------------- */
.alert { border-radius: var(--radius-sm); padding: 12px 16px; margin: 0 0 16px; font-size: 14px; border: 1px solid transparent; }
.alert-success { background: var(--signal-tint); color: var(--signal-deep); border-color: #bfe8d7; }
.alert-error   { background: var(--down-tint);   color: #b3282d;          border-color: #f6cdce; }

/* ===========================================================================
   Overview — the signal panel (hero) + site cards
   =========================================================================== */

/* Hero: a live monitor readout. The animated pulse line is the signature; the
   uptime figure is a confident readout beside it, not a giant gradient number. */
.signal-panel {
    position: relative; overflow: hidden;
    /* The gradient is the one long, dark ramp left in either portal, and it banded for the same arithmetic
       reason the sidebar did: roughly 47 steps of blue spread over a ~1000px diagonal is a stripe every
       20-odd pixels, on a dark surface where the eye is most sensitive to them. Rather than flatten the
       hero — the emerald ramp IS this portal's signature — a tileable grain is laid over it. Noise breaks
       the boundaries up so there is no clean edge left to see, which is what dithering has always been for.
       Kept as a background layer rather than a blended pseudo-element on purpose: mix-blend-mode would
       isolate this panel into its own compositing group, and Chrome drops subpixel text antialiasing inside
       one of those — the hero carries the largest type on the page. */
    background:
        url("../img/noise.svg") 0 0 / 220px 220px repeat,
        linear-gradient(120deg, #0e2a20 0%, #103a2c 55%, #0b6b4f 140%);
    color: #eaf5ef; border-radius: var(--radius);
    padding: 26px 28px; margin-bottom: 26px;
    display: flex; align-items: center; justify-content: space-between; gap: 24px;
    box-shadow: var(--shadow);
}
.signal-panel .eyebrow {
    display: inline-flex; align-items: center; gap: 9px;
    font-size: 11.5px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
    color: #8fd9bf;
}
.signal-panel .eyebrow .dot { background: var(--signal); }
.signal-headline { font-family: var(--font-display); font-size: 21px; font-weight: 600; margin: 12px 0 16px; color: #fff; letter-spacing: -.01em; }
.signal-readouts { display: flex; gap: 34px; }
.signal-readouts .ro-n { font-family: var(--font-display); font-size: 40px; font-weight: 700; line-height: 1; color: #fff; letter-spacing: -.02em; }
.signal-readouts .ro-l { font-size: 12px; color: #93c8b3; margin-top: 7px; letter-spacing: .02em; }
.signal-panel .ekg { position: absolute; right: 0; top: 0; bottom: 0; width: 46%; pointer-events: none;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 45%); mask-image: linear-gradient(90deg, transparent, #000 45%); }
.signal-panel .ekg svg { width: 100%; height: 100%; display: block; }
.ekg-track { animation: ekg-scroll 4.5s linear infinite; }
@keyframes ekg-scroll { from { transform: translateX(0); } to { transform: translateX(-120px); } }

/* Per-client heading on the Super User's cross-client landing (the only page that groups sites). */
.client-group-head {
    display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap;
    gap: 4px 14px; margin: 0 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.client-group-name { font-size: 17px; font-weight: 600; margin: 0; }

/* Site cards */
.site-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; }
.site-card {
    position: relative; display: flex; flex-direction: column;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 20px 20px 18px; color: var(--ink); box-shadow: var(--shadow);
    transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
.site-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); border-color: #c7e3d6; text-decoration: none; }
.site-card::before {
    /* a slim signal edge that lights up on hover */
    content: ""; position: absolute; left: 0; top: 16px; bottom: 16px; width: 3px; border-radius: 3px;
    background: var(--line); transition: background-color .16s ease;
}
.site-card:hover::before { background: var(--signal); }
.site-card.is-offline::before { background: var(--down); }
.site-card-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.site-card-name { font-family: var(--font-display); font-size: 18px; font-weight: 600; line-height: 1.2; }
.site-card-host { color: var(--muted); font-size: 13px; margin-top: 3px; word-break: break-all; }
.site-card-foot { display: flex; align-items: baseline; gap: 8px; margin-top: 16px; padding-top: 15px; border-top: 1px solid var(--line-soft); }
.site-card-foot .u-n { font-family: var(--font-display); font-size: 22px; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
.site-card-foot .u-l { font-size: 12px; color: var(--faint); }
.site-card .go { margin-left: auto; color: var(--faint); transition: color .16s ease, transform .16s ease; }
.site-card:hover .go { color: var(--signal-deep); transform: translateX(2px); }

.empty {
    text-align: center; padding: 46px 20px; color: var(--muted);
    background: var(--surface); border: 1px dashed var(--line); border-radius: var(--radius);
}

/* .client-stats removed: no view emitted the class. The overview uses .signal-panel. */

/* ===========================================================================
   Site detail
   =========================================================================== */
.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.detail-head .page-title { margin-bottom: 6px; }
.backlink { display: inline-flex; align-items: center; gap: 6px; font-size: 13.5px; color: var(--muted); font-weight: 600; margin-bottom: 12px; }
.backlink:hover { color: var(--signal-deep); }

.status-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.status-line .detail { color: var(--muted); font-size: 14px; }

/* Uptime figures row on the detail page */
.uptime-row { display: flex; gap: 30px; flex-wrap: wrap; }
.uptime-row .fig-n { font-family: var(--font-display); font-size: 30px; font-weight: 700; line-height: 1; letter-spacing: -.01em; }
.uptime-row .fig-l { font-size: 12px; color: var(--faint); margin-top: 6px; letter-spacing: .04em; text-transform: uppercase; }

.kv td .hint { display: block; margin-top: 2px; }

/* ---- "Something changed" badge -------------------------------------------
   A white exclamation mark in a red circle. On the Support button it hangs off
   the top-right corner (.btn-with-badge); in a ticket list it sits inline next
   to the reference. It exists only while there is something unseen, so it needs
   no empty/hidden state. */
.btn-with-badge { position: relative; display: inline-flex; }
.btn-with-badge > .change-badge { position: absolute; top: -6px; right: -6px; }

.change-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 17px; height: 17px; border-radius: 50%;
    background: var(--down); color: #fff;
    font-family: var(--font-display); font-size: 12px; font-weight: 700; line-height: 1;
    /* A ring in the surface colour keeps the circle legible where it overlaps the button edge. */
    box-shadow: 0 0 0 2px var(--surface);
    user-select: none;
}
/* Inline use (table rows): no ring needed, and it shouldn't drag the row taller. */
table.data .change-badge { box-shadow: none; vertical-align: middle; margin-left: 7px; }

/* ---- Maintenance timeline (with a vertical signal line) ------------------- */
.timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.timeline::before { content: ""; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }
.timeline li { position: relative; padding: 4px 0 18px 26px; }
.timeline li:last-child { padding-bottom: 0; }
.timeline li::before {
    content: ""; position: absolute; left: 0; top: 6px; width: 12px; height: 12px; border-radius: 50%;
    background: var(--surface); border: 2px solid var(--signal);
}
.timeline li.attention::before { border-color: var(--warn); }
.timeline .tl-when { font-family: var(--font-mono); font-size: 12px; color: var(--faint); margin-bottom: 3px; }
.timeline .tl-what { color: var(--ink); font-size: 14.5px; }
.timeline .tl-what .pill { margin-left: 6px; vertical-align: 1px; }

/* Hostnames list */
.hostrow a { font-family: var(--font-mono); font-size: 13.5px; }

/* ===========================================================================
   Auth / welcome — the branded front door
   =========================================================================== */
.auth-wrap {
    min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px;
    position: relative; overflow: hidden;
    background: radial-gradient(1100px 620px at 50% -12%, #12352785 0%, transparent 60%), var(--bg);
}
/* faint monitoring grid drifting behind the card */
.auth-wrap::before {
    content: ""; position: absolute; inset: -40px;
    background-image: linear-gradient(rgba(11,107,79,.05) 1px, transparent 1px), linear-gradient(90deg, rgba(11,107,79,.05) 1px, transparent 1px);
    background-size: 34px 34px; -webkit-mask-image: radial-gradient(circle at 50% 30%, #000, transparent 72%); mask-image: radial-gradient(circle at 50% 30%, #000, transparent 72%);
    animation: grid-drift 26s linear infinite;
}
@keyframes grid-drift { from { transform: translateY(0); } to { transform: translateY(34px); } }
.auth-card {
    position: relative; width: 100%; max-width: 420px;
    background: var(--surface); border: 1px solid var(--line); border-radius: 18px;
    padding: 32px 30px; box-shadow: 0 12px 40px rgba(15,31,25,.12);
}
.auth-brand { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; }
.auth-card .brand-logo { height: 40px; display: block; margin: 0; }
.auth-brand .brand-div { width: 1px; height: 30px; background: var(--line); display: block; }
.auth-brand .auth-pidoxa { height: 22px; display: block; }
.auth-card h1 { font-size: 22px; margin: 0 0 6px; font-weight: 700; }
.auth-card h2 { font-size: 14px; font-weight: 600; }
.auth-card .field { margin: 14px 0; }
.auth-card .field label { display: block; font-size: 13px; font-weight: 600; color: var(--ink-soft); margin-bottom: 6px; }
.field input {
    width: 100%; font-family: var(--font-body); font-size: 14px; color: var(--ink);
    padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg-2);
    transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.field input:focus { outline: none; border-color: var(--signal); background: #fff; box-shadow: 0 0 0 3px rgba(18,184,134,.16); }
.field .hint { display: block; margin-top: 5px; }
.auth-sep { display: flex; align-items: center; gap: 12px; color: var(--faint); font-size: 12px; margin: 20px 0; text-transform: uppercase; letter-spacing: .08em; }
.auth-sep::before, .auth-sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.auth-logo { display: block; height: 44px; margin: 0 auto 18px; }

/* ===========================================================================
   Load-in motion + reduced-motion floor
   =========================================================================== */
.rise { opacity: 0; transform: translateY(10px); }
.rise.in { opacity: 1; transform: none; transition: opacity .5s ease, transform .5s cubic-bezier(.2,.7,.3,1); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition: none !important; }
    .rise { opacity: 1; transform: none; }
    /* The heartbeat is the portal's signature and stays alive even here — deliberately a slow,
       low-contrast, edge-masked ambient loop rather than large-scale motion. */
    .ekg-track { animation: ekg-scroll 4.5s linear infinite !important; }
}

/* ==================================================================================================
   RESPONSIVE SHELL — the client topbar, below 900px.

   Same rule as the admin sheet's equivalent section: max-width queries only, no rule above this line
   edited, so a window wider than 900px renders byte-identically to the version before this existed.
   900 is the shell breakpoint and is stated in four places — twice in site.css, here, and in
   nav-drawer.js. They must stay identical and nothing enforces it.

   ⚠️ THIS SECTION IS INSERTED ABOVE THE PRE-EXISTING @media (max-width: 620px) BLOCK, NOT APPENDED
   AFTER IT, and that placement is load-bearing. Same-specificity ties are broken by SOURCE ORDER, not
   by which query is narrower — so a 900 block placed after the 620 one would win at every width ≤620
   and silently clobber .client-main's 18px padding, .page-title's 25px and the .signal-panel stack.
   Anything added here must stay above that block, and must not restate the three selectors it owns.

   NOTE WHAT IS DELIBERATELY *NOT* HERE. .client-topbar's eight children measure ~957px for an ordinary
   client and ~1047px with the Super User pill, so the bar already overflows — and takes the document
   into horizontal scroll — at any window narrower than about 960px. Fixing that band would mean a rule
   firing above 900, i.e. at a desktop width, and the brief for this pass was that no desktop width may
   change. So 900–960 is left exactly as broken as it is today. If that is ever revisited, the fix is
   this same flex-wrap in a @media (max-width: 1060px) block placed immediately above this one.
   ================================================================================================== */

@media (max-width: 900px) {
    /* flex-wrap is self-activating: it does nothing at all until the content genuinely does not fit, so
       nothing here changes for a bar that still has room. */
    .client-topbar { flex-wrap: wrap; padding: 10px 16px; column-gap: 10px; row-gap: 10px; }

    /* THE SPACER BECOMES THE LINE BREAK. With flex-wrap on, `margin-left: auto` absorbs the free space on
       its own line and shoves the controls hard right until they spill to a left-aligned second line — a
       ragged bar whose break point moves with the length of the signed-in address. Given flex-basis: 100%
       it forces the break itself, deterministically: line one is the brand, line two is the controls. It
       keeps its own selector rather than being replaced, so the desktop rule above is untouched. */
    .client-topbar .spacer { flex: 0 0 100%; margin: 0; height: 0; }

    .client-topbar .user-email {
        min-width: 0; max-width: 22ch;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }

    /* "Client Portal" beside a logo that says HMS Website Hosting, on a page whose <title> also says it.
       Losing it also stops one always-running pulse animation (.title::before) on the device least able to
       spend a compositor frame on it — the ::before never had a text alternative, so nothing announced is
       lost.

       ⚠️ _StaffReportLayout REUSES THIS BAR AND PUTS THE CLIENT'S NAME IN .title, which is the one thing
       on that page saying whose report it is. It is the only layout that also emits a .hint into the bar
       ("As the client sees it"), so :has() distinguishes the two with no markup change and no new class.
       :has() is already load-bearing in both stylesheets, so this adds no new dependency. */
    .client-topbar .title { display: none; }
    .client-topbar:has(> .hint) .title { display: inline; }
}

@media (max-width: 620px) {
    .signal-panel { flex-direction: column; align-items: flex-start; gap: 18px; }
    .signal-panel .ekg { display: none; }
    .page-title { font-size: 25px; }
    .client-main { padding: 0 18px; }
}

/* ==================================================================================================
   NARROW VIEWPORTS — content. A SECOND 620px block, appended after the original rather than merged
   into it, so that block stays byte-identical and provably untouched. Ties go to whichever comes last,
   which is this one, so anything here wins — which is why NONE of the three selectors the block above
   owns (.signal-panel, .page-title, .client-main) is restated down here.

   620 matches the breakpoint that was already in this file, which is why site.css adopted the same
   number: the portal has one phone breakpoint, not two twenty pixels apart.
   ================================================================================================== */

@media (max-width: 620px) {

    /* 22/24 → 18/16 buys 16px of interior, which is the difference between the two signal readouts
       fitting and not on a 320px screen (see the 480 block). */
    .card { padding: 18px 16px; }
    .signal-panel { padding: 22px 18px; }
    .signal-headline { font-size: 19px; margin: 10px 0 14px; }
    .signal-readouts { gap: 22px; }
    .signal-readouts .ro-n { font-size: 34px; }
    .site-card { padding: 18px 16px 16px; }
    .uptime-row { gap: 16px 22px; }
    .uptime-row .fig-n { font-size: 26px; }

    /* ⚠️ `repeat(auto-fill, minmax(280px, 1fr))` DOES NOT SHRINK BELOW 280px — auto-fill decides how MANY
       tracks there are, never how narrow one may get, and the minmax minimum is a hard floor. .client-main's
       interior at a 320px viewport with a scrollbar is 269px, so the single track stays 280px and the site
       cards hang 11px outside the page. Measured, not reasoned: a .grid with a 240px floor in a 200px
       container computes `grid-template-columns: 240px` in Chrome.

       `min(280px, 100%)` only ever lowers the floor to the container's own width, so it is a strict no-op at
       every width where this grid already worked — including the 375–430px phones, where one 280px card
       already fitted. */
    .site-grid { grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }

    /* Shrunk rather than dropped: the endorsement lockup is 136.5px, 38% of a 360px viewport, but the
       relationship is not decoration and the client portal is the surface it matters most on. 24/15 costs
       ~30px and keeps both marks at every width.

       ⚠️ QUALIFIED WITH .client-topbar, and it has to be. The base rules are `.client-topbar .brand-hms`
       and `.client-topbar .brand-pidoxa` — two classes each — so a bare `.brand-hms` here is one class and
       LOSES on specificity no matter that it comes later in the file. Written unqualified first, and the
       marks simply stayed at 30/19 with nothing to show anything was wrong. Also scopes it correctly: the
       auth pages use .brand-logo / .auth-pidoxa in .auth-brand, which are a different lockup at a
       different size and must not be dragged along by this. */
    .client-topbar .brand-hms { height: 24px; }
    .client-topbar .brand-pidoxa { height: 15px; }

    /* ⚠️ THIS SHEET PUTS width: 150px ON EVERY th, NOT JUST kv's. That is fine on a 1000px measure and
       ruinous on a phone: _TicketTable's six headers ask for 900px and Reports' four ask for 600px, so a
       card that would have fitted is forced into a scroll it does not need. Only the kv variant genuinely
       wants a fixed label column, and 110px is measured against the longest label rather than inherited. */
    table.data th { width: auto; }
    table.data.kv th { width: 110px; }

    /* Same affordance as the admin sheet's, for the same reason: mobile overlay scrollbars are invisible
       until you scroll, so a card that scrolls looks like a card that is cut off. And over-scrolling a
       horizontal scroller leftwards navigates BACK on iOS Safari and Android Chrome — a six-column ticket
       table is exactly the thing somebody flings. */
    .card:has(table.data) {
        scrollbar-width: thin;
        scrollbar-color: #c3d3c9 transparent;
        overscroll-behavior-x: contain;
    }
    .card:has(table.data)::-webkit-scrollbar { height: 7px; -webkit-appearance: none; }
    .card:has(table.data)::-webkit-scrollbar-thumb { background: #c3d3c9; border-radius: 4px; }
    .card:has(table.data)::-webkit-scrollbar-track { background: transparent; }

    /* THE iOS ZOOM FIX. Mobile Safari zooms whenever a focused field computes below 16px and DOES NOT ZOOM
       BACK OUT ON BLUR, leaving the visitor at ~1.14× with a horizontal scrollbar and no way to undo it.

       !important here, deliberately and narrowly, and this sheet needs it where site.css does not: THREE
       CLIENT VIEWS SET font-size INLINE ON A FIELD — ClientSupport/New.cshtml's shared `fieldStyle` string
       (14.5px, on the reason and site selects and the message textarea), ClientPortal/Settings.cshtml's
       time-zone select (14px), and ClientPortal/Site.cshtml's uptime range select (13px). An inline
       declaration beats any selector without !important, and those are the three fields a client is most
       likely to touch on a phone. The alternative was editing the three views, which changes desktop.

       max-width: 100% matters more here than in the admin sheet, because this file has no generic
       full-width input rule at all — only .field input. Without it the time-zone select, whose widest
       option is "(UTC-05:00) Eastern Time (US & Canada)" at roughly 300px, overflows its card. */
    select, textarea,
    input:not([type=checkbox], [type=radio], [type=file], [type=submit], [type=button], [type=reset],
              [type=hidden], [type=range], [type=color], [type=image]) {
        font-size: 16px !important;
        max-width: 100%;
    }

    /* The same eight inline min-widths the admin sheet resets; ClientPortal/Settings.cshtml:44 is the one
       on this side. `max-width: 100%` above cannot rescue it — CSS resolves used width as
       clamp(min-width, width, max-width), so min-width is applied LAST and wins. */
    input[style*="min-width"],
    select[style*="min-width"],
    [data-row-list][style*="min-width"] { min-width: 0 !important; }

    /* !important against an inline white-space: nowrap in ClientPortal/Site.cshtml and
       _SiteUptimeStorage.cshtml. A partial-state tooltip reads "1/5, 3:00 PM — Partly down — 66.7% up
       (4/12 failed) · 412 ms", about 330px, inside a card interior of ~307px — and the flip logic in
       uptime-graph.js only ever solves the RIGHT edge, so unwrapped it overflows on both. */
    [data-ug-tip] { white-space: normal !important; max-width: 90%; }
}

@media (max-width: 480px) {
    /* Two .ro-n at 40px with a 34px gap is ~254px, which fits at 375px but CLIPS at 320px — .signal-panel
       carries overflow: hidden, so the second readout would simply be cut off rather than wrapping. */
    .signal-readouts { gap: 18px; }
    .signal-readouts .ro-n { font-size: 30px; }
    .page-title { font-size: 22px; overflow-wrap: anywhere; }
    .uptime-row { gap: 14px 18px; }
    .uptime-row .fig-n { font-size: 24px; }
}

/* Touch targets. Not a width query — see the equivalent block at the end of site.css for why.
   Client checkboxes are deliberately absent: ClientPortal/Settings' two toggles carry an inline
   18px and both sit inside a <label style="display:flex"> wrapping the heading AND the hint, so the
   real target is already a ~70px row and an !important to grow the box would buy nothing. */
@media (pointer: coarse) {
    /* min-height on every .btn; the display change EXCLUDES .btn-block, which is already `display: flex;
       width: 100%` for the full-width auth buttons. Both selectors are one class and this block is later
       in the file, so an unqualified `.btn { display: inline-flex }` would win and turn every sign-in
       button on the front door into an inline box sitting on a text baseline. */
    .btn { min-height: 44px; }
    .btn:not(.btn-block) { display: inline-flex; align-items: center; justify-content: center; }
    .btn-block { align-items: center; justify-content: center; }
    /* 38 rather than 44 for the same reason as the admin sheet: .btn-sm is the per-row action in
       _TicketTable and the topbar's four links, and 44 adds 15px to every row of a ticket list. */
    .btn-sm { min-height: 38px; }
}
