/* ============================================================
   OxiNotify – Customer Service Portal
   wwwroot/css/portal.css
   Extends the Oxit brand tokens from styles.css.
   ============================================================ */

/* Self-hosted fonts (same-origin, no third-party request). See wwwroot/css/fonts.css. */
@import url('/css/fonts.css');

:root {
    --brand-primary: #0d2b55;
    --brand-accent:  #1a6fc4;
    --brand-accent-h:#1259a8;
    --brand-gold:    #e8a020;
    --surface:       #ffffff;
    --surface-2:     #f4f7fb;
    --surface-3:     #eef2f8;
    --border:        #dce4ef;
    --text-primary:  #0d2b55;
    --text-secondary:#5a7099;
    /* FE-08: was #8fa3bf = 2.57:1 on --surface, well under the 4.5:1 AA floor for normal text —
       and it is used as REAL text in 14 places (empty-state copy, timestamps, table meta,
       notification times), not just as decoration. #5f7391 measures 4.83:1. */
    --text-muted:    #5f7391;
    --success:       #1a9e6e;
    --success-bg:    #edfaf5;
    --warning:       #e8a020;
    --warning-bg:    #fdf6e9;
    --error:         #c0392b;
    --error-bg:      #fdf1f0;
    /* Scrollbars. Tinted from the brand's slate rather than neutral grey, so a scrolling panel
       reads as part of the UI instead of as an OS artefact sitting on top of it. Alpha rather
       than a solid colour: the thumb then works over --surface, --surface-2 and --surface-3
       without a variant for each. */
    --scroll-thumb:       rgba(13, 43, 85, 0.22);
    --scroll-thumb-hover: rgba(13, 43, 85, 0.38);
    --radius-sm: 6px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --shadow-card: 0 8px 40px rgba(13,43,85,0.10), 0 2px 8px rgba(13,43,85,0.06);
    --shadow-soft: 0 2px 10px rgba(13,43,85,0.07);
    /* 'Cairo' is the Arabic fallback: DM Sans / DM Serif have no Arabic glyphs, so browsers use Cairo
       only for Arabic text — consistent, on-brand Arabic typography instead of an OS-default fallback. */
    --font-body: 'DM Sans', 'Cairo', sans-serif;
    --font-display: 'DM Serif Display', 'Cairo', serif;
    --transition: 0.18s ease;

    /* Aliases for tokens referenced (without a fallback) by page-level styles/markup. Defined here
       so those references resolve to the design system instead of an invalid/undefined value:
       --muted → muted text, --gold → the brand gold, --brand-muted → the hairline border colour. */
    --muted:        var(--text-muted);
    --gold:         var(--brand-gold);
    --brand-muted:  var(--border);
    color-scheme: light;
}

/* ═══ DARK THEME ════════════════════════════════════════════════════════════════
   The head script in _PortalLayout sets <html data-theme="dark|light"> from the oxi_theme
   cookie (System resolves via prefers-color-scheme) BEFORE first paint, so there's no flash.
   Dark mode is just a token swap — every var()-based rule flips automatically. Below the tokens
   we re-tint only the components that hardcode light-only pastels. --brand-primary and
   --brand-gold are deliberately NOT changed: they're used as dark brand backgrounds (modal/table
   headers) and as text ON gold, which stay correct on dark. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] {
    color-scheme: dark;
    --surface:        #161b26;
    --surface-2:      #0e1219;
    --surface-3:      #212a3a;
    --border:         #2c3648;
    --text-primary:   #e7edf6;
    --text-secondary: #a9b7d0;
    /* FE-08: was #7d8ba3, which passes on --surface (5.00:1) but FAILS on the lighter
       --surface-3 #212a3a at 4.18:1 — and muted text sits on all three. #8e9cb5 is the
       worst-case 5.20:1 across surface / surface-2 / surface-3. */
    --text-muted:     #8e9cb5;
    --brand-accent:   #4d9fe6;
    --brand-accent-h: #6bb0ec;
    --success:        #34c48d;
    --success-bg:     #12271e;
    --warning:        #e8a020;
    --warning-bg:     #2a2213;
    --error:          #e5675c;
    --error-bg:       #2b1512;
    --shadow-card:    0 10px 40px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.4);
    --shadow-soft:    0 2px 12px rgba(0,0,0,.4);
    /* Light thumb on dark surfaces — the slate tint of the light theme would all but vanish here. */
    --scroll-thumb:       rgba(231, 237, 246, 0.20);
    --scroll-thumb-hover: rgba(231, 237, 246, 0.36);
}
/* --brand-primary is a TEXT colour in these spots → use the (now-light) primary text on dark. */
:root[data-theme="dark"] .pt-topbar,
:root[data-theme="dark"] .pt-nav a:hover,
:root[data-theme="dark"] .pt-bell:hover,
:root[data-theme="dark"] .pt-login-head,
:root[data-theme="dark"] .pt-login-head h1 { color: var(--text-primary); }
/* Pastel status pills → dark tints. */
:root[data-theme="dark"] .pt-badge.open,
:root[data-theme="dark"] .pt-badge.prio-normal { background:#17304f; color:#9cc3ef; }
:root[data-theme="dark"] .pt-badge.inprogress,
:root[data-theme="dark"] .pt-badge.prio-high   { background:#332810; color:#e6b354; }
:root[data-theme="dark"] .pt-badge.resolved     { background:#123027; color:#4fd0a1; }
:root[data-theme="dark"] .pt-badge.closed,
:root[data-theme="dark"] .pt-badge.prio-low     { background:#222b3b; color:#aab8d0; }
:root[data-theme="dark"] .pt-badge.autoclosed   { background:#2a2340; color:#b7a8e0; }
:root[data-theme="dark"] .pt-badge.prio-urgent  { background:#3a1714; color:#f0897d; }
/* Alerts: bg follows the darkened tokens; re-tint the hardcoded borders/text. */
:root[data-theme="dark"] .pt-alert.error   { border-color:#5a2a25; }
:root[data-theme="dark"] .pt-alert.success { color:#4fd0a1; border-color:#1f4a3a; }
:root[data-theme="dark"] .pt-alert.warn    { color:#e6b354; border-color:#4a3a1a; }
:root[data-theme="dark"] .pt-alert.info    { background:#152238; border-color:#274063; }
/* Chat surfaces + hover states that hardcode light values. */
:root[data-theme="dark"] .pt-conv-body        { background: var(--surface-2); }
:root[data-theme="dark"] .pt-chat-item.unread { background:#132318; }
:root[data-theme="dark"] .pt-chat-item.active { background:#152238; }
:root[data-theme="dark"] .pt-btn.ghost:hover  { background:#2b3547; }

* , *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
    font-family: var(--font-body);
    background: var(--surface-2);
    color: var(--text-primary);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    /* Long unbroken strings (URLs, long Arabic/English without spaces) wrap instead
       of forcing the page wider than the screen. */
    overflow-wrap: break-word;
}

a { color: var(--brand-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── App shell ────────────────────────────────────────────── */
.pt-shell { display: flex; flex-direction: column; min-height: 100vh; }

.pt-topbar {
    display: flex; align-items: center; gap: 16px;
    /* min-height (not a fixed height): a fully-featured admin with many nav tabs wraps the nav onto a
       second row at tablet/small-laptop widths; a fixed height clipped that row. */
    padding: 0 22px; min-height: 62px;
    background: var(--surface);
    color: var(--brand-primary);
    border-bottom: 2px solid var(--brand-accent);
    box-shadow: 0 2px 12px rgba(13,43,85,0.10);
    /* Breathing room above the bar. It shows at rest and scrolls away when the bar pins itself —
       `top: 0` is kept deliberately rather than `top: 3px`, because a sticky offset would leave a
       3px slot of page content sliding along above the header for the whole scroll.
       Safe from margin collapsing: .pt-shell is a flex container. */
    margin-top: 3px;
    position: sticky; top: 0; z-index: 30;
}
.pt-topbar img {
    height: 34px;
}
/* Brand group — the platform wordmark, the divider and the tenant logo (or the .pt-brand text).
   Grouping them is what lets the mobile breakpoint keep the header on one row; see the long note
   in the max-width:720px block. On desktop it is a plain inline flex row with the topbar's own
   16px gap, so the rendering is identical to when these were direct children of .pt-topbar. */
.pt-brandbar {
    display: flex; align-items: center; gap: 16px;
    /* Does NOT shrink on desktop. When it did, it competed with the tab strip for the squeeze and
       the tenant name lost — measured collapsing to a 15px box, i.e. a bare "…" with no readable
       text, between roughly 900 and 1100px. The strip is the right place for that pressure: it is
       built to scroll, so giving up width costs it nothing, while the brand has nowhere to go.
       (The mobile block below deliberately flips this to `flex: 1 1 0` — there the strip has its own
       row and the brand is the only thing that CAN yield.) */
    flex: 0 0 auto; min-width: 0;
    /* Backstop: this group SHRINKS, so anything inside it that cannot shrink would otherwise
       overflow and paint on top of the next item in the header. The tenant name is handled
       properly with an ellipsis above; this guarantees nothing else can ever leak out either. */
    overflow: hidden;
}
/* Dark-mode logo swap. The "Oxit" wordmark's "Ox" is near-black and would vanish on a dark
   surface, so a variant with a white "Ox" (logo-dark.png; the icon / "notify" / "it" are unchanged)
   is shown in dark mode. The head script always resolves data-theme to light|dark, so no media query. */
.pt-logo-dark { display: none; }
:root[data-theme="dark"] .pt-logo-light { display: none; }
:root[data-theme="dark"] .pt-logo-dark  { display: inline-block; }
.pt-logo-div {
    display: inline-block; width: 1px; height: 26px;
    background: var(--border); margin: 0 4px; opacity: .8;
}
.pt-tenant-logo { height: 30px; max-width: 150px; object-fit: contain; }
.pt-topbar .pt-brand {
    font-family: var(--font-display);
    font-size: 1.2rem; letter-spacing: .3px;
}
.pt-topbar .pt-tenant {
    font-size: .82rem; color: var(--text-secondary);
    padding-left: 14px; margin-left: 2px;
    border-left: 1px solid var(--border);
    /* ONE line, ellipsised. Left to wrap (the default), a long organisation name broke in two or
       three lines as the brand group shrank — measured 75px tall inside a 62px bar at 1024px — and
       once it could no longer wrap it SPILLED past its own box onto whatever sat next to it, which
       is what put the nav's left arrow underneath the name.
       min-width: 0 is what actually enables the ellipsis: a flex item's automatic minimum size is
       its min-content width, so without this the span refuses to shrink and simply overflows.
       max-width caps a very long name so it cannot starve the tab strip when there IS room; the
       full text stays available via the title attribute the layouts set. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    max-width: 220px;
}
.pt-topbar .pt-spacer { flex: 1; }

/* The tab strip can outgrow the topbar — a tenant-admin with every feature
   enabled exposes 7+ links (Chats, Tickets, Feedback, Management, Bot Flows,
   Schedule, Send). Without this it would either wrap raggedly under the brand
   or push the actions cluster off-screen. Instead, let it scroll horizontally
   IN PLACE so the topbar stays a clean single row and the user can swipe to
   reach the off-screen tabs.
   • flex: 0 1 auto + min-width: 0 — intrinsic width on desktop (so .pt-spacer
     still claims the leftover and pushes .pt-actions to the right), shrinkable
     below intrinsic width when the parent runs out of room.
   • Tabs themselves are flex:0 0 auto so each keeps its full label instead
     of squashing to ellipsis.
   • Scrollbar hidden visually (swipe / wheel still works); -webkit-overflow-
     scrolling for momentum-scroll on iOS.
*/
.pt-nav {
    display: flex; gap: 4px;
    flex: 0 1 auto; min-width: 0;
    /* Wrap overflowing tabs onto a second row on desktop instead of scrolling them under a hidden
       scrollbar (they were unreachable). The mobile block below switches this back to a scroll strip. */
    flex-wrap: wrap;
    overflow-x: auto; overflow-y: hidden;
    white-space: nowrap;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;          /* Firefox */
}
.pt-nav::-webkit-scrollbar { display: none; }   /* WebKit / Blink */

/* ── Desktop: scroll the tab strip instead of wrapping the header onto a second row ──────────
   The `flex-wrap: wrap` above is the NO-JAVASCRIPT fallback and stays the default on purpose.
   Horizontal scrolling was tried here before and reverted, for a good reason recorded in the
   comment above: the scrollbar is hidden, and a mouse cannot swipe — so any tab past the right
   edge became unreachable. Wrapping was the lesser evil.
   What makes scrolling viable now is that the strip gains real mouse affordances (arrow buttons
   + wheel-to-scroll, both wired in layout-portal.js), so nothing is ever out of reach. JS adds
   .pt-nav--strip only once those exist, which is what keeps the fallback honest: with scripting
   off the header still wraps and every tab is still clickable. */
.pt-nav--strip { flex-wrap: nowrap; }

.pt-nav-arrow {
    flex: 0 0 auto;
    display: none;                  /* only ever shown when the strip actually overflows */
    align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0;
    border: 1px solid var(--border); border-radius: 50%;
    background: var(--surface); color: var(--text-secondary);
    cursor: pointer; transition: var(--transition);
}
.pt-nav-arrow:hover { background: var(--surface-3); color: var(--brand-primary); }
.pt-nav-arrow.show  { display: inline-flex; }
@media (max-width: 720px) {
    /* Phones swipe the strip directly, so arrows would buy nothing and would steal width from the
       one row that has none to spare. */
    .pt-nav-arrow, .pt-nav-arrow.show { display: none; }
}
.pt-nav a {
    color: var(--text-secondary); font-weight: 500; font-size: .9rem;
    padding: 8px 15px; border-radius: var(--radius-sm);
    transition: var(--transition);
    flex: 0 0 auto;                 /* don't shrink — let the bar scroll instead */
}
.pt-nav a:hover { background: var(--surface-3); text-decoration: none; color: var(--brand-primary); }
.pt-nav a.active { background: var(--brand-accent); color: #fff; }

/* The avatar acts as the dropdown trigger. The whole .pt-user element is
   keyboard- and mouse-clickable; cursor:pointer hints that. */
.pt-user {
    display: flex; align-items: center; gap: 10px;
    font-size: .85rem; cursor: pointer; position: relative;
    user-select: none;
}
.pt-user:hover .pt-avatar,
.pt-user.open .pt-avatar { box-shadow: 0 0 0 2px var(--brand-gold); }
.pt-user .pt-avatar {
    width: 32px; height: 32px; border-radius: 50%;
    background: var(--brand-gold); color: var(--brand-primary);
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .85rem;
    transition: box-shadow .15s ease;
}

/* Account dropdown menu — replaces the standalone Change Password / Sign Out
   buttons that used to live in a secondary header row. Anchored under the
   avatar, opens on click, closes on outside-click / Escape. The signed-in
   identity shows as a non-clickable header at the top — display name over the
   muted email — so the user can always confirm which account they're on
   without needing the hover tooltip.
   ARIA: trigger gets aria-expanded; menu is role="menu" with role="menuitem"
   children for screen-reader navigation. */
.pt-user-menu {
    position: absolute; top: calc(100% + 8px); right: 0;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); box-shadow: var(--shadow-card);
    min-width: 240px; max-width: 92vw; padding: 6px;
    opacity: 0; transform: translateY(-6px); pointer-events: none;
    transition: opacity .15s ease, transform .15s ease;
    z-index: 50;
}
[dir="rtl"] .pt-user-menu { right: auto; left: 0; }
.pt-user.open .pt-user-menu { opacity: 1; transform: translateY(0); pointer-events: auto; }
/* Display-name row, rendered ABOVE the email and only when the signed-in user
   actually has one (a role-1 system admin has no tenant membership, so no name;
   a pre-claim session carries none either). When it's absent the email row below
   is unchanged and stands alone — which is why the top padding lives here rather
   than being taken off .pt-user-menu-email. word-break is `break-word`, not the
   email's `break-all`: a person's name should wrap between words, not mid-word. */
.pt-user-menu-name {
    padding: 8px 12px 0;
    font-size: .85rem; font-weight: 600; color: var(--text-primary);
    cursor: default;
    word-break: break-word; line-height: 1.3;
}
.pt-user-menu-email {
    padding: 8px 12px 10px;
    font-size: .78rem; color: var(--text-muted);
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px; cursor: default;
    word-break: break-all; line-height: 1.35;
}
/* With a name above it the email is the second line of one identity block, not a
   header of its own — tighten the gap so the two read as a pair. */
.pt-user-menu-name + .pt-user-menu-email { padding-top: 2px; }
.pt-user-menu-item {
    display: flex; align-items: center; gap: 9px;
    width: 100%; padding: 9px 12px; border-radius: var(--radius-sm);
    background: transparent; border: none; cursor: pointer;
    font-family: var(--font-body); font-size: .85rem;
    color: var(--text-primary); text-align: left;
    transition: background .12s ease;
}
[dir="rtl"] .pt-user-menu-item { text-align: right; }
.pt-user-menu-item:hover,
.pt-user-menu-item:focus-visible { background: var(--surface-3); outline: none; }
.pt-user-menu-item svg { flex: 0 0 auto; color: var(--text-secondary); }
.pt-user-menu-item.danger { color: var(--error); }
.pt-user-menu-item.danger svg { color: var(--error); }
/* RTL: mirror the door-out arrow so it still reads as "leaving the page". */
[dir="rtl"] .pt-user-menu-item .pt-logout-icon { transform: scaleX(-1); }

/* Right-side control cluster (bell, avatar, change-password, sign-out).
   Grouping these into one flex unit keeps them together so they never wrap
   raggedly on small screens — the whole cluster moves as a block. */
.pt-actions { display: flex; align-items: center; gap: 10px; }

/* App switcher — shown only when the tenant holds >1 Oxi product. A grid-icon
   trigger opens a small menu that jumps between OxiNotify and OxiFlow. Same
   open/outside-click/Escape mechanics + visual language as the account menu. */
.pt-switcher { position: relative; user-select: none; }
.pt-switcher-btn {
    display: flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: var(--radius-sm);
    background: transparent; border: 1px solid transparent;
    color: var(--text-secondary); cursor: pointer;
    transition: background .12s ease, box-shadow .15s ease;
}
.pt-switcher-btn:hover,
.pt-switcher.open .pt-switcher-btn { background: var(--surface-3); box-shadow: 0 0 0 2px var(--brand-gold); }
.pt-switcher-menu {
    position: absolute; top: calc(100% + 8px); right: 0;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); box-shadow: var(--shadow-card);
    min-width: 232px; max-width: calc(100vw - 16px); padding: 6px;
    opacity: 0; transform: translateY(-6px); pointer-events: none;
    transition: opacity .15s ease, transform .15s ease;
    z-index: 50;
}
[dir="rtl"] .pt-switcher-menu { right: auto; left: 0; }
.pt-switcher.open .pt-switcher-menu { opacity: 1; transform: translateY(0); pointer-events: auto; }
.pt-switcher-head {
    padding: 8px 12px 10px; font-size: .72rem; letter-spacing: .04em;
    text-transform: uppercase; color: var(--text-muted);
    border-bottom: 1px solid var(--border); margin-bottom: 4px; cursor: default;
}
.pt-switcher-item {
    display: flex; align-items: center; gap: 11px;
    width: 100%; padding: 9px 12px; border-radius: var(--radius-sm);
    text-decoration: none; color: var(--text-primary);
    font-family: var(--font-body); font-size: .88rem;
    transition: background .12s ease;
}
[dir="rtl"] .pt-switcher-item { text-align: right; }
.pt-switcher-item:hover,
.pt-switcher-item:focus-visible { background: var(--surface-3); outline: none; }
.pt-switcher-item img { height: 30px; width: 30px; object-fit: contain; flex: 0 0 auto; }   /* the swirl app icon (icon_new / icon-flow) */
.pt-switcher-name { font-size: .9rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; }
.pt-switcher-item .pt-switcher-dot {
    margin-left: auto; font-size: .66rem; letter-spacing: .03em; text-transform: uppercase;
    color: var(--text-muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px;
}
[dir="rtl"] .pt-switcher-item .pt-switcher-dot { margin-left: 0; margin-right: auto; }
.pt-switcher-item[aria-current="true"] { background: var(--surface-3); }

.pt-main { flex: 1; padding: 22px; max-width: 1600px; width: 100%; margin: 0 auto; }

/* ── Page heading ─────────────────────────────────────────── */
.pt-pagehead { margin-bottom: 18px; }
.pt-pagehead h1 {
    font-family: var(--font-display); font-size: 1.7rem; font-weight: 400;
}
.pt-pagehead p { color: var(--text-secondary); font-size: .9rem; margin-top: 2px; }
/* Shared here because more than one page uses them: the tab-row on the AI/Flows editors and the
   SuperAdmin section heading (were defined only inside a single page's <style>, so other pages
   using the class rendered unstyled). */
.fb-tabrow { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0 6px; }
.sa-section-title { display: block; font-size: .85rem; font-weight: 600; margin: 0 0 10px; }

/* ── Cards / panels ───────────────────────────────────────── */
.pt-card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); box-shadow: var(--shadow-soft);
}
.pt-card-pad { padding: 18px; }

/* ── Toolbar / filters ────────────────────────────────────── */
.pt-toolbar {
    display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
    margin-bottom: 14px;
}
.pt-toolbar .pt-spacer { flex: 1; }

.pt-input, .pt-select {
    font-family: var(--font-body); font-size: .88rem;
    padding: 9px 12px; border: 1px solid var(--border);
    border-radius: var(--radius-sm); background: var(--surface);
    color: var(--text-primary); outline: none; transition: var(--transition);
    /* NEVER floor the width: a 220px min-width used to defeat every author max-width:<220 and
       overflow narrow flex columns. min-width:0 lets explicit widths win; max-width:100% keeps an
       input inside its container. Fields still fill their column via `.pt-field .pt-input{width:100%}`. */
    min-width: 0; max-width: 100%;
}
.pt-input:focus, .pt-select:focus { border-color: var(--brand-accent); box-shadow: 0 0 0 3px rgba(26,111,196,.12); }

/* ── Buttons ──────────────────────────────────────────────── */
.pt-btn {
    font-family: var(--font-body); font-size: .87rem; font-weight: 600;
    padding: 9px 16px; border-radius: var(--radius-sm); border: none;
    cursor: pointer; transition: var(--transition);
    background: var(--brand-accent); color: #fff;
    display: inline-flex; align-items: center; gap: 7px;
}
.pt-btn:hover { background: var(--brand-accent-h); }
.pt-btn:disabled { opacity: .5; cursor: not-allowed; }
.pt-btn.ghost { background: var(--surface-3); color: var(--text-primary); }
.pt-btn.ghost:hover { background: #e2e9f3; }
.pt-btn.gold { background: var(--brand-gold); color: var(--brand-primary); }
.pt-btn.gold:hover { background: #d6911a; }
.pt-btn.success { background: var(--success); }
.pt-btn.danger  { background: var(--error); }
.pt-btn.sm { padding: 6px 11px; font-size: .8rem; }

/* ── Toggle switches ───────────────────────────────────────────────
   Every on/off checkbox in the portal renders as a modern slider switch. */
input[type="checkbox"] {
    appearance: none; -webkit-appearance: none; margin: 0 2px;
    position: relative; width: 38px; height: 22px; flex-shrink: 0;
    border-radius: 999px; background: var(--border); cursor: pointer;
    vertical-align: middle; transition: background .18s ease;
}
input[type="checkbox"]::after {
    content: ""; position: absolute; top: 2px; left: 2px;
    width: 18px; height: 18px; border-radius: 50%; background: #fff;
    box-shadow: 0 1px 3px rgba(13,43,85,.35); transition: transform .18s ease;
}
input[type="checkbox"]:checked { background: var(--brand-accent); }
input[type="checkbox"]:checked::after { transform: translateX(16px); }
input[type="checkbox"]:disabled { opacity: .55; cursor: not-allowed; }
input[type="checkbox"]:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(26,111,196,.25); }

/* ── Tables ───────────────────────────────────────────────── */
.pt-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.pt-table thead th {
    text-align: left; font-weight: 600; font-size: .76rem;
    text-transform: uppercase; letter-spacing: .5px;
    color: var(--text-muted); padding: 11px 14px;
    border-bottom: 1px solid var(--border);
}
.pt-table tbody td { padding: 12px 14px; border-bottom: 1px solid var(--surface-3); }
.pt-table tbody tr { cursor: pointer; transition: var(--transition); }
.pt-table tbody tr:hover { background: var(--surface-2); }
.pt-table tbody tr:last-child td { border-bottom: none; }

/* ── Row-select pattern (KB list) ─────────────────────────────
   Used on tables where the entire row is the selection target
   (kb-table-selectable). The row gets a visible accent on hover
   and a strong gold highlight when the user has selected it via
   click — gold pops against the default light-blue surface, so a
   selected row reads at a glance on PC + mobile without needing a
   tiny checkbox column. The bold left bar reinforces the state
   for users with subtle-tint colour-vision differences. */
.kb-table-selectable tbody tr.kb-row             { cursor: pointer; }
.kb-table-selectable tbody tr.kb-row-selected,
.kb-table-selectable tbody tr.kb-row-selected:hover {
    background: var(--warning-bg);
    box-shadow: inset 4px 0 0 0 var(--brand-gold);
    font-weight: 600;
}
.kb-table-selectable tbody tr.kb-row-selected td { color: var(--text-primary); }

/* ── Badges ───────────────────────────────────────────────── */
.pt-badge {
    display: inline-block; font-size: .73rem; font-weight: 600;
    padding: 3px 9px; border-radius: 20px; white-space: nowrap;
    /* Default look so a bare `.pt-badge` (no state/prio modifier) still renders as a neutral pill
       instead of transparent-background/inherited-colour text. Modifiers below override these. */
    background: var(--surface-3); color: var(--text-secondary);
}
/* A badge rendered as a <button> (the Customer chip in the chat/inbox header) must look identical
   to the <a>/<span> badges beside it. Neutralise the UA button chrome ONLY — never the badge's own
   font-size/weight, which an inline `font:inherit` used to clobber (making it larger + unbold). */
button.pt-badge {
    font-family: inherit; line-height: inherit; letter-spacing: inherit;
    margin: 0; border: none; cursor: pointer;
    -webkit-appearance: none; appearance: none;
}
.pt-badge.open       { background:#e7eefb; color:#1a4f9c; }
.pt-badge.inprogress { background:#fdf6e9; color:#a9750f; }
.pt-badge.resolved   { background:#edfaf5; color:#0d7a54; }
.pt-badge.closed     { background:#eef2f8; color:#5a7099; }
.pt-badge.autoclosed { background:#f0eef5; color:#6b5a8f; }

.pt-badge.prio-low    { background:#eef2f8; color:#5a7099; }
.pt-badge.prio-normal { background:#e7eefb; color:#1a4f9c; }
.pt-badge.prio-high   { background:#fdf6e9; color:#a9750f; }
.pt-badge.prio-urgent { background:#fdf1f0; color:#b5271a; }

.pt-dot { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; }
.pt-dot.on  { background: var(--success); box-shadow: 0 0 0 3px rgba(26,158,110,.18); }
.pt-dot.off { background: var(--text-muted); }

/* ── Alerts ───────────────────────────────────────────────── */
.pt-alert {
    display: flex; gap: 9px; align-items: flex-start;
    padding: 11px 14px; border-radius: var(--radius-sm);
    font-size: .86rem; margin: 10px 0;
}
.pt-alert.error   { background: var(--error-bg);   color: var(--error);   border:1px solid #f5c6c2; }
.pt-alert.success { background: var(--success-bg); color: #0d7a54; border:1px solid #a8e8cf; }
.pt-alert.warn    { background: var(--warning-bg); color: #a9750f; border:1px solid #f0d9a8; }
.pt-alert.info    { background: #eef4fc; color: var(--brand-accent); border:1px solid #c4dbf3; }

/* ── Empty state ──────────────────────────────────────────── */
.pt-empty { text-align: center; padding: 50px 20px; color: var(--text-muted); }
.pt-empty .pt-empty-icon { font-size: 2.6rem; margin-bottom: 8px; }

/* ── Chat layout ──────────────────────────────────────────── */
.pt-chat-wrap { display: grid; grid-template-columns: 320px 1fr; gap: 16px; min-height: 70vh; }
/* Grid children default to min-width:auto and would grow to fit long content,
   pushing past the viewport. Allow them to shrink so inner text can clip/wrap. */
.pt-chat-wrap > * { min-width: 0; }
.pt-chat-list { max-height: 76vh; overflow-y: auto; }
/* Conversation-list row — shared by the Support Chats and WhatsApp Inbox screens
   (rendered by PC.row() in /js/portal-chat.js). WhatsApp-style: avatar + name/time +
   preview + unread bubble + a row of small status chips. */
.pt-chat-item {
    display: flex; gap: 11px; align-items: center;
    padding: 11px 14px; border-bottom: 1px solid var(--surface-3);
    cursor: pointer; transition: var(--transition); min-width: 0;
    /* FE-06: the row is now a real <button> (it was a click-wired <div>, so it was invisible to
       the keyboard). These four lines strip the native button chrome so it renders exactly as
       before — full-width, left-aligned, inheriting the page font instead of the UA's. */
    width: 100%; text-align: start; background: none; border-left: 0; border-right: 0; border-top: 0;
    font: inherit; color: inherit;
}
/* And a VISIBLE focus ring — a focusable row nobody can see focused is only half the fix.
   :focus-visible so it appears for keyboard users without ringing on every mouse click. */
.pt-chat-item:focus-visible {
    outline: 2px solid var(--brand-accent);
    outline-offset: -2px;
}
.pt-chat-item:hover  { background: var(--surface-2); }
.pt-chat-item.unread { background: #f0faf4; }
.pt-chat-item.active { background: #eef4fc; }
.pt-ci-avatar, .pt-ch-avatar {
    border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 600; box-shadow: var(--shadow-soft); user-select: none;
}
.pt-ci-avatar { width: 44px; height: 44px; font-size: .95rem; letter-spacing: .3px; }
.pt-ch-avatar { width: 38px; height: 38px; font-size: .85rem; }
.pt-ci-main { flex: 1; min-width: 0; }
.pt-ci-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.pt-ci-name { font-weight: 600; font-size: .92rem; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-chat-item.unread .pt-ci-name { font-weight: 700; }
.pt-ci-time { font-size: .72rem; color: var(--text-muted); flex-shrink: 0; white-space: nowrap; }
.pt-ci-row2 { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-top: 2px; }
.pt-ci-preview { font-size: .8rem; color: var(--text-secondary); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-chat-item.unread .pt-ci-preview { color: var(--text-primary); font-weight: 500; }
.pt-ci-badge {
    flex-shrink: 0; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px;
    background: var(--success); color: #fff; font-size: .72rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.pt-ci-dot { flex-shrink: 0; width: 9px; height: 9px; border-radius: 50%; background: var(--error); }
.pt-ci-tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 6px; }
.pt-ci-tags:empty { display: none; }
/* A long assignee email / label in a conversation-row chip must ellipsis, not force sideways scroll. */
.pt-ci-tags .pt-badge { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* Conversation-list search box (list header). */
.pt-search-wrap { padding: 9px 12px; border-bottom: 1px solid var(--border); }
.pt-search {
    width: 100%; padding: 8px 13px; border: 1px solid var(--border); border-radius: 20px;
    font-family: var(--font-body); font-size: .84rem; outline: none;
    background: var(--surface-2); transition: var(--transition);
}
.pt-search:focus { border-color: var(--brand-accent); background: var(--surface); }
/* Small count chip shown next to the list title (total unread conversations). */
.pt-unread-total {
    min-width: 18px; height: 18px; padding: 0 6px; border-radius: 9px;
    background: var(--success); color: #fff; font-size: .7rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}

.pt-conv { display: flex; flex-direction: column; height: 76vh; }
.pt-conv-head {
    padding: 13px 16px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
    flex-wrap: wrap;          /* lets the side group flow to a 2nd line on narrow widths */
    row-gap: 6px;
}
/* The meta column (avatar + name + phone) gets all remaining width, so the phone
   line never collapses to one digit per row. The side group (badges / ticket link)
   flows to a second row when the viewport can't hold both on one line. */
.pt-conv-head .pt-ch-meta { min-width: 0; flex: 1 1 60%; }
.pt-conv-head .pt-ch-side {
    display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
@media (max-width: 540px) {
    /* Force the side group to its own line on phones — even the smallest screen
       keeps the phone number readable. */
    .pt-conv-head .pt-ch-side { flex-basis: 100%; justify-content: flex-start; }
}
/* mobile-only "back to list" button (hidden on desktop, shown in the media query below) */
.pt-conv-back {
    display: none; align-items: center; gap: 5px;
    padding: 6px 12px 6px 9px; border: 1px solid var(--border); border-radius: 999px;
    background: var(--surface); color: var(--text-primary); font-family: var(--font-body);
    font-size: .82rem; font-weight: 600; cursor: pointer; flex-shrink: 0; line-height: 1;
    transition: background var(--transition), border-color var(--transition), color var(--transition), transform .05s;
}
.pt-conv-back svg { display: block; }
.pt-conv-back:hover { background: var(--surface-2); border-color: var(--brand-accent); color: var(--brand-accent); }
.pt-conv-back:active { transform: scale(.96); }
[dir="rtl"] .pt-conv-back { padding: 6px 9px 6px 12px; }
[dir="rtl"] .pt-conv-back svg { transform: scaleX(-1); }

/* generic back button (e.g. Tickets detail) — arrow + label, RTL-aware */
.pt-back { display: inline-flex; align-items: center; gap: 6px; }
.pt-back svg { display: block; }
[dir="rtl"] .pt-back svg { transform: scaleX(-1); }

/* Manual WhatsApp send / broadcast UI (shared by the tenant Send page + the system-admin
   tenant panel; markup = _ManualSend partial, behaviour = /portal-manual-send.js). */
.msg-seg { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.msg-seg-btn { padding: 7px 14px; border: 1px solid var(--border); border-radius: 20px; background: var(--surface-2);
               cursor: pointer; font-family: var(--font-body); font-size: .85rem; transition: var(--transition); }
.msg-seg-btn.active { background: var(--brand-accent); color: #fff; border-color: var(--brand-accent); }
.msg-vrow { display: flex; gap: 6px; margin-bottom: 6px; align-items: center; }
.msg-vrow input { flex: 1; }
.msg-vrow .msg-del { flex-shrink: 0; background: none; border: none; color: var(--error, #c0392b); cursor: pointer; font-size: 1.1rem; line-height: 1; }
.msg-card { border: 1px solid var(--border); border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.msg-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-weight: 600; font-size: .85rem; }
.msg-card-del { background: none; border: none; color: var(--error, #c0392b); cursor: pointer; }
.msg-prog { height: 10px; background: var(--surface-3); border-radius: 6px; overflow: hidden; }
.msg-prog-bar { height: 100%; width: 0; background: var(--brand-accent); transition: width .2s ease; }
.pt-conv-head .pt-ch-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Phone numbers — keep on one line and ellipsise if the column is tight.
   Without `white-space: nowrap` the flex parent on narrow mobile widths was
   wrapping the number one character per line (each digit on its own line). */
/* .pt-ch-sub is the Emails header's subject line: identical treatment, different content.
   Named separately rather than reusing .pt-ch-phone so a future change to phone formatting
   cannot silently restyle a subject — the Emails header hit the very bug described above
   because it hand-rolled its own layout instead of sharing this one. */
.pt-conv-head .pt-ch-phone,
.pt-conv-head .pt-ch-sub {
    font-size: .8rem; color: var(--text-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Pre-handoff intake email line — only rendered when the intake captured one.
   Same single-line ellipsis treatment as the phone row so a long email
   (e.g. very.long.address@subdomain.example.com) doesn't blow out the header
   on mobile. The leading ✉ glyph signals that this is contact info, not a
   message, and matches the conversational tone of the other meta rows. */
.pt-conv-head .pt-ch-email {
    font-size: .8rem; color: var(--text-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    margin-top: 2px;
}
.pt-conv-head .pt-ch-email a { color: inherit; text-decoration: none; }
.pt-conv-head .pt-ch-email a:hover { text-decoration: underline; }
.pt-conv-body {
    flex: 1; overflow-y: auto; padding: 18px;
    background: #e7edf3;
    display: flex; flex-direction: column; gap: 9px;
}
.pt-msg { max-width: 72%; padding: 9px 13px; border-radius: 13px; font-size: .87rem; line-height: 1.4; white-space: pre-wrap; word-wrap: break-word; }
.pt-msg .pt-msg-meta { font-size: .68rem; margin-top: 4px; opacity: .7; }
.pt-msg.inbound  { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
/* Expanded voice-call transcript: its bubbles reuse .pt-msg above, so it must be a flex column
   itself (alignment is align-self and needs a flex parent). The inset line marks where the
   call's turns start and end, keeping them readable as ONE call inside a mixed thread. */
.pt-call-transcript { display: flex; flex-direction: column; gap: 9px; padding-inline-start: 10px; border-inline-start: 2px solid var(--border); }
.pt-msg.bot      { align-self: flex-end; background: var(--surface-3); color: var(--text-secondary); border-bottom-right-radius: 4px; }
.pt-msg.agent    { align-self: flex-end; background: var(--brand-accent); color: #fff; border-bottom-right-radius: 4px; }
/* Outbound (business) delivery ticks — WhatsApp style. */
.pt-tick { margin-inline-start: 5px; font-size: .7rem; letter-spacing: -1px; }
.pt-msg.agent .pt-tick      { color: rgba(255,255,255,.8); }
.pt-msg.agent .pt-tick.read { color: #aef0ff; opacity: 1; }
/* A failed delivery must not read as a quieter tick — it is the opposite of the ✓ it replaces.
   letter-spacing is reset because -1px was there to tighten "✓✓" and only clips a lone glyph.
   The agent-bubble rule is repeated with a bright tone: inheriting the 80%-white tick colour would
   wash the warning out on exactly the bubbles that carry it. */
.pt-tick.failed             { color: var(--error, #c0392b); letter-spacing: 0; font-size: .8rem; cursor: help; }
.pt-msg.agent .pt-tick.failed { color: #ffb4ab; opacity: 1; }
/* Chat video: a constrained tile that never exceeds the bubble; dark backing while the poster frame
   loads (preload="metadata") so it doesn't flash wider than the bubble. */
.pt-msg .pt-chat-vid { display: block; width: 260px; max-width: 100%; max-height: 300px; border-radius: 8px; background: #0b141a; object-fit: contain; }
.pt-conv-foot { padding: 12px 14px; border-top: 1px solid var(--border); background: var(--surface); }
.pt-conv-foot .pt-compose { display: flex; gap: 9px; }
.pt-conv-foot textarea {
    flex: 1; resize: none; font-family: var(--font-body); font-size: .88rem;
    padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    outline: none; min-height: 44px; max-height: 120px;
}
.pt-conv-foot textarea:focus { border-color: var(--brand-accent); }
.pt-conv-placeholder { flex:1; display:flex; align-items:center; justify-content:center; color:var(--text-muted); }

/* Composer "attach photo/video" button. */
.pt-attach {
    flex-shrink: 0; width: 42px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface-2); cursor: pointer; font-size: 1.15rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; transition: var(--transition);
}
.pt-attach:hover { background: var(--surface-3); border-color: var(--brand-accent); }

/* Photo/video preview-before-send overlay. */
.pc-media-overlay {
    position: fixed; inset: 0; background: rgba(13,43,85,0.55); z-index: 1000;
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
.pc-media-card {
    background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-card);
    width: 100%; max-width: 460px; max-height: 90vh; overflow-y: auto; padding: 16px;
    display: flex; flex-direction: column; gap: 12px;
}
.pc-media-head { font-weight: 600; font-size: .9rem; color: var(--text-primary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-media-body { display: flex; justify-content: center; background: var(--surface-2);
    border-radius: var(--radius-sm); padding: 8px; }
.pc-media-preview { max-width: 100%; max-height: 50vh; border-radius: 8px; display: block; }
.pc-media-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* ── Ticket detail ────────────────────────────────────────── */
.pt-detail-grid { display: grid; grid-template-columns: 1fr 340px; gap: 16px; }
.pt-meta-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--surface-3); font-size: .87rem; }
.pt-meta-row:last-child { border-bottom: none; }
.pt-meta-row .pt-meta-label { color: var(--text-muted); }
.pt-timeline { list-style: none; }
.pt-timeline li { padding: 9px 0 9px 18px; border-left: 2px solid var(--border); position: relative; font-size: .84rem; }
.pt-timeline li::before {
    content:''; position:absolute; left:-6px; top:13px;
    width:10px; height:10px; border-radius:50%;
    background: var(--brand-accent); border: 2px solid #fff;
}
.pt-timeline .pt-tl-time { font-size: .73rem; color: var(--text-muted); }

/* ── Modal ────────────────────────────────────────────────── */
.pt-modal-bg {
    position: fixed; inset: 0; background: rgba(13,43,85,.45);
    /* Above the drawer/overlay layer (.cu-overlay and .camp-overlay are both 1000), NOT below it.
       ptConfirm is the GLOBAL confirmation dialog: at z-index 50 it was painted underneath any open
       drawer and click-blocked by that drawer's own backdrop. Every confirm raised FROM the customer
       card — remove identifier, change consent, save fields, delete customer — was therefore
       invisible and unanswerable, and the same was true of the campaign overlays. A global dialog
       has to out-rank the surfaces that raise it. */
    display: none; align-items: center; justify-content: center; z-index: 1100;
}
.pt-modal-bg.show { display: flex; }
.pt-modal {
    background: var(--surface); border-radius: var(--radius-md);
    width: 420px; max-width: 92vw; max-height: 92vh; box-shadow: var(--shadow-card);
    overflow: hidden; display: flex; flex-direction: column;
}
/* The close ✕ is POSITIONED OVER the dialog, not placed inside its header.
   That is not a style choice. Five dialogs put the id on the header div itself
   (stepModalTitle, aiBulkTitle, prModalTitle, aimModalTitle, emModalTitle) and their pages set
   `.textContent` on it when the dialog opens — which deletes every child node. A button parented to
   the header would therefore vanish at the exact moment the dialog appeared, and with backdrop-close
   gone those five had no visible way out at all. Parented to the PANEL, nothing the page writes into
   its header can reach it.

   inset-inline-end, not right: the portal runs Arabic, so the button follows the writing direction. */
.pt-modal-head { background: var(--brand-primary); color: #fff; padding: 15px 18px; font-weight: 600; }
.pt-modal-x {
    position: absolute; top: 10px; inset-inline-end: 10px; z-index: 1;
    background: none; border: 0; cursor: pointer;
    font-size: 1.05rem; line-height: 1; padding: 4px 8px; border-radius: 6px;
    color: var(--text-secondary); opacity: .9;
}
.pt-modal-x:hover { background: var(--surface-2); color: var(--text-primary); opacity: 1; }
/* Over the brand bar the glyph needs to be light, and its hover tint has to be a white wash rather
   than the page's grey surface. */
.pt-modal-x--on-bar { color: #fff; }
.pt-modal-x--on-bar:hover { background: rgba(255, 255, 255, .18); color: #fff; }
.pt-modal-body { padding: 18px; overflow-y: auto; }   /* scroll long forms on small screens */
.pt-modal-foot { padding: 13px 18px; background: var(--surface-2); display: flex; justify-content: flex-end; gap: 9px; }

/* Floating feedback button — tenant-admin only. z-index 40 keeps it above page content but BELOW
   the modal overlay (.pt-modal-bg is z-index 50). inset-inline-end keeps it bottom-right in LTR and
   bottom-left in RTL (Arabic) automatically. */
.pt-fab {
    position: fixed; inset-inline-end: 20px; bottom: 20px; z-index: 40;
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
    background: var(--brand-primary); color: #fff;
    box-shadow: 0 6px 18px rgba(13, 43, 85, .28);
}
.pt-fab:hover { filter: brightness(1.08); }
#fbType.pt-input { height: 40px; }
#fbBody { resize: vertical; min-height: 110px; }

.pt-field { margin-bottom: 13px; }
.pt-field label { display: block; font-size: .82rem; font-weight: 600; margin-bottom: 5px; color: var(--text-secondary); }
.pt-field .pt-input, .pt-field .pt-select, .pt-field textarea { width: 100%; min-width: 0; }

/* ── Password reveal/hide toggle (added to every type=password input) ── */
.pt-pw-wrap { position: relative; display: block; }
.pt-pw-wrap > .pt-input { padding-right: 42px; }
[dir="rtl"] .pt-pw-wrap > .pt-input { padding-right: 12px; padding-left: 42px; }
.pt-pw-toggle {
    position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
    background: none; border: none; padding: 6px; margin: 0; cursor: pointer;
    color: var(--text-secondary); display: inline-flex; align-items: center;
    line-height: 0; border-radius: 6px;
}
[dir="rtl"] .pt-pw-toggle { right: auto; left: 6px; }
.pt-pw-toggle:hover { color: var(--text-primary); background: rgba(0,0,0,.05); }

/* ── Login ────────────────────────────────────────────────── */
.pt-login-page {
    min-height: 100vh; display: flex; align-items: center; justify-content: center;
    background: radial-gradient(ellipse 80% 60% at 50% -10%, rgba(26,111,196,0.12) 0%, transparent 70%), var(--surface-2);
    padding: 20px;
}
.pt-login-card {
    width: 400px; max-width: 94vw; background: var(--surface);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
    border: 1px solid var(--border); overflow: hidden;
    display: flex; flex-direction: column;
}
.pt-login-head {
    background: var(--surface);
    color: var(--brand-primary);
    padding: 30px 26px 22px;
    text-align: center;
    border-bottom: 2px solid var(--brand-accent);
}
.pt-login-head img { height: 48px; margin-bottom: 14px; }
.pt-login-head h1 { font-family: var(--font-display); font-weight: 400; font-size: 1.5rem; color: var(--brand-primary); }
.pt-login-head p  { color: var(--text-secondary); font-size: .85rem; margin-top: 4px; }
.pt-login-body { padding: 26px; flex: 1 0 auto; }

/* Subtle credit footer pinned to the bottom of the login card. */
.pt-login-foot {
    padding: 14px 26px 18px; text-align: center;
    font-size: .78rem; color: var(--text-muted);
    border-top: 1px solid var(--border); background: var(--surface);
}
.pt-login-foot a { color: var(--text-secondary); font-weight: 600; }
.pt-login-foot a:hover { color: var(--brand-accent); text-decoration: none; }

.pt-muted { color: var(--text-muted); }
.pt-small { font-size: .8rem; }
.pt-spin {
    width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.4);
    border-top-color: #fff; border-radius: 50%;
    animation: pt-rot .7s linear infinite; display: inline-block;
}
@keyframes pt-rot { to { transform: rotate(360deg); } }

@media (max-width: 880px) {
    .pt-chat-wrap, .pt-detail-grid { grid-template-columns: 1fr; }
    /* Once the two conversation panes stack into one column (721–880px) they were 76vh EACH, pushing
       the thread/message pane below the fold. Compact the list so the conversation pane is in view.
       (Below 720px a separate rule shows one pane at a time, so this only affects the tablet band.) */
    .pt-chat-list { max-height: 34vh; }
}
@media (max-width: 880px) and (min-width: 721px) {
    .pt-nav a { padding: 8px 10px; }
}

/* ── Notification bell ─────────────────────────────────────────── */
.pt-bell {
    position: relative; cursor: pointer; color: var(--text-secondary);
    padding: 8px 10px; border-radius: var(--radius-sm);
    transition: var(--transition); display: inline-flex; align-items: center;
}
.pt-bell:hover { background: var(--surface-3); color: var(--brand-primary); }
/* Theme toggle button — matches the bell/lang controls. Icon is set by JS (☀/☾/🖥). */
.pt-theme-btn {
    cursor: pointer; color: var(--text-secondary); background: none; border: none;
    padding: 8px 9px; border-radius: var(--radius-sm); font-size: 1.05rem; line-height: 1;
    transition: var(--transition); display: inline-flex; align-items: center;
}
.pt-theme-btn:hover { background: var(--surface-3); color: var(--text-primary); }
.pt-bell-badge {
    position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px;
    padding: 0 4px; background: var(--brand-gold); color: var(--brand-primary);
    border-radius: 9px; font-size: .68rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
    line-height: 1;
}
.pt-user-email { display: inline; }

/* ── Notification drawer ───────────────────────────────────────── */
.pt-notif-drawer {
    position: fixed; top: 62px; right: 14px; width: 340px; max-width: 92vw;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); box-shadow: var(--shadow-card);
    max-height: 70vh; overflow: hidden;
    transform: translateY(-10px); opacity: 0; pointer-events: none;
    transition: transform .18s ease, opacity .18s ease;
    z-index: 40;
}
.pt-notif-drawer.show { transform: translateY(0); opacity: 1; pointer-events: auto; }
.pt-notif-head {
    display: flex; justify-content: space-between; align-items: center;
    padding: 12px 14px; border-bottom: 1px solid var(--border);
}
.pt-notif-list { overflow-y: auto; max-height: calc(70vh - 50px); }
.pt-notif-item {
    padding: 11px 14px; border-bottom: 1px solid var(--surface-3);
    cursor: pointer; transition: var(--transition);
}
.pt-notif-item:hover { background: var(--surface-2); }
.pt-notif-item.read { opacity: .65; }
.pt-notif-item.read .pt-notif-title { font-weight: 500; }
.pt-notif-title { font-weight: 600; font-size: .88rem; }
.pt-notif-msg   { font-size: .82rem; color: var(--text-secondary); margin-top: 3px; }
.pt-notif-time  { font-size: .72rem; color: var(--text-muted); margin-top: 4px; }

/* ── Toast ──────────────────────────────────────────────────────── */
.pt-toasts {
    /* Above .pt-modal-bg (1100) for the same reason it used to be above 50: a toast reporting the
       RESULT of a confirmed action must be readable while that dialog is still closing. */
    position: fixed; bottom: 18px; right: 18px; z-index: 1200;
    display: flex; flex-direction: column; gap: 9px;
}
.pt-toast {
    background: var(--brand-primary); color: #fff;
    padding: 12px 16px; border-radius: var(--radius-md);
    box-shadow: var(--shadow-card); min-width: 240px; max-width: 320px;
    transform: translateX(110%); transition: transform .25s ease;
    font-size: .87rem;
}
.pt-toast.show { transform: translateX(0); }
.pt-toast strong { display: block; font-size: .9rem; margin-bottom: 3px; }
.pt-toast div    { font-size: .82rem; color: #c3d4ea; }

/* ── Install banner ─────────────────────────────────────────────── */
.pt-install {
    position: fixed; bottom: 14px; left: 14px; right: 14px;
    background: var(--brand-primary); color: #fff;
    padding: 12px 16px; border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    font-size: .88rem; z-index: 55;
}
.pt-install span { flex: 1; }
@media (min-width: 880px) {
    .pt-install { left: auto; right: 18px; max-width: 460px; }
}

/* ── Nav badge (unseen indicator) ──────────────────────────────── */
.pt-nav a { position: relative; }
.pt-nav-badge {
    position: absolute; top: 3px; right: 5px;
    width: 8px; height: 8px; border-radius: 50%;
    background: #e8402b;
    border: 2px solid #ffffff;
    display: none;
}
.pt-nav-badge.show { display: block; }

/* ── Scrollbars ─────────────────────────────────────────────────────────────────────────────
   One treatment for every scrollable surface in the portal — the page, chat lists, modals, the
   notification drawer, tables, drawers, textareas. Left alone, each of those shows the OS default:
   on Windows a 17px light-grey trough with square buttons, which is wider than some of the panels
   it appears in and belongs to no design system at all.

   BOTH syntaxes are needed, and neither covers the other:
     · scrollbar-width / scrollbar-color is the STANDARD one (Firefox, and modern Chrome). It only
       accepts "thin" and a pair of flat colours — no radius, no inset.
     · ::-webkit-scrollbar is the older WebKit/Blink set, still what Chrome, Edge and Safari
       actually honour, and the only way to get a rounded, inset, floating thumb.

   The look: no track at all, and a pill thumb inset from the edge by a transparent border with
   background-clip:content-box — a 10px gutter carrying a 4px thumb. It darkens on hover so the
   affordance is there when reached for, without drawing the eye when it is not.

   Applied via the universal selector deliberately, so a new scroll container inherits it instead
   of needing to remember. Anything that wants something else just says so at higher specificity —
   as .pt-nav does, hiding its scrollbar entirely for the tab strip. */
*::-webkit-scrollbar        { width: 10px; height: 10px; }
*::-webkit-scrollbar-track  { background: transparent; }
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-thumb {
    background-color: var(--scroll-thumb);
    border-radius: 999px;
    /* The transparent border is what makes it read as a floating pill rather than a bar wedged
       against the edge; background-clip keeps the colour off the border box. */
    border: 3px solid transparent;
    background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--scroll-thumb-hover); }

/* Firefox has no ::-webkit-scrollbar, so it needs the standard properties instead — thin and
   tinted, which is as far as that syntax goes (no radius, no inset).
   ⚠ SCOPED, and the scoping is the point: Chrome supports BOTH, and the moment it sees
   scrollbar-color it switches to its own standard rendering and ignores every pseudo-element
   above — measured as a 12px square-edged bar instead of the 10px pill. @supports asks the
   engine whether it knows the pseudo-element at all, so Chrome/Edge/Safari skip this block and
   keep the pill, while Firefox (which fails the test) picks it up. A feature query, not a
   browser sniff, so it stays correct as engines change. */
@supports not selector(::-webkit-scrollbar) {
    * {
        scrollbar-width: thin;
        scrollbar-color: var(--scroll-thumb) transparent;
    }
}

/* ── Table wrapper (enables horizontal scroll on narrow viewports) ── */
.pt-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── Mobile responsiveness ─────────────────────────────────────── */
@media (max-width: 720px) {
    /* Two-row header on small screens:
         Row 1 — logo(s) on the left, the action cluster on the right.
         Row 2 — the nav, full-width with a top divider.
       The action cluster (.pt-actions) is ONE flex unit, so Change password
       and Sign out always stay together and never wrap one-button-per-line.
       If the row is too tight the whole cluster drops as a tidy block. */
    .pt-topbar {
        height: auto; min-height: 52px; padding: 8px 12px;
        display: flex; flex-wrap: wrap; align-items: center;
        column-gap: 8px; row-gap: 6px;
    }
    .pt-topbar .pt-brand  { display: none; }            /* the logo alone identifies the app */
    .pt-topbar .pt-tenant { display: none; }
    .pt-topbar .pt-spacer { display: none; }            /* alignment handled by .pt-actions margin */

    /* ── Row 1 stays ONE line: the logos shrink, the controls never move ─────────────
       A wide (banner-shaped) tenant logo used to push the action cluster onto its own row, so
       the header became THREE stacked rows — logos / actions / nav — instead of two.

       ⚠ THE NON-OBVIOUS PART, and the reason capping widths or adding `min-width: 0` to the
       images does NOT fix this: FLEXBOX WRAPS BEFORE IT SHRINKS. Line breaking is decided from
       each item's HYPOTHETICAL main size (its flex-basis clamped by min/max), and `flex-shrink`
       is only applied afterwards, to the items that ended up sharing a line. So as long as the
       logos and .pt-actions are separate children of a wrapping container, the browser's answer
       to "these don't fit" is always to move .pt-actions to a new line — it never considers
       making the images smaller. Measured on a 375px viewport: even a NORMAL square logo wrapped,
       because the action cluster alone is 208px of the 331px available.

       The fix is structural: the brand images live in .pt-brandbar, which has `flex-basis: 0`.
       A zero hypothetical size can always fit on the current line, so row 1 can never be split.
       flex-grow then hands the container whatever is left after the controls, and the images
       shrink INSIDE it — which is exactly the requested behaviour: smaller logos, one line,
       controls untouched. See .pt-brandbar in the base block above for the shared bits. */
    .pt-brandbar {
        flex: 1 1 0;                                    /* 0 basis ⇒ never triggers a wrap */
        min-width: 0;
        gap: 8px;
    }
    /* Sizing by max-height with auto dimensions (rather than a fixed height) also reclaims dead
       space: with `height: 22px` + object-fit, a letterboxed wide logo still RESERVED its whole
       96px box, so blank pixels counted against the row. With auto dimensions the box is only as
       wide as what is actually painted, and the browser derives the missing dimension from the
       intrinsic ratio, so nothing is distorted. */
    .pt-brandbar img {
        height: auto; width: auto;
        max-height: 24px;
        flex: 0 1 auto;                                 /* may shrink … */
        min-width: 0;                                   /* … and this is what permits it */
        object-fit: contain;
    }
    /* Cap the tenant logo's share so an unusually wide upload cannot starve the platform mark.
       Without it, both logos shrink in proportion to their natural width, so a banner-shaped
       tenant logo drags the Oxit wordmark down with it — measured at 19×5px for a 20:1 banner,
       i.e. both logos illegible instead of one. 45% keeps them comparable. */
    .pt-brandbar .pt-tenant-logo { max-height: 22px; max-width: 45%; }
    .pt-logo-div                 { flex: 0 0 auto; height: 20px; }

    .pt-actions {
        margin-left: auto;                              /* always right-aligned, even when wrapped */
        gap: 6px; flex-wrap: nowrap;
        /* The fixed point of the row: bell, avatar and switcher are tap targets and must keep
           their hit area. Pinning them is what directs all the pressure onto the logos. */
        flex: 0 0 auto;
    }
    .pt-bell              { padding: 6px 8px; }
    .pt-user-email        { display: none; }
    /* On phones the dropdown should hug the right edge of the screen, not the
       avatar — gives the menu its full width and avoids clipping. */
    .pt-user-menu { right: 4px; min-width: 220px; }
    [dir="rtl"] .pt-user-menu { left: 4px; right: auto; }

    .pt-nav {
        order: 99; flex-basis: 100%; width: calc(100% + 24px);
        /* flex-start (not space-around/center): a centred scroll container hides its LEADING overflow
           at a negative offset that scrollLeck can't reach, making the first tabs permanently
           unreachable. flex-start keeps the strip scrollable from tab #1. flex:1 on the tabs still
           fills the bar when they all fit, so the non-overflow case looks identical. */
        display: flex; flex-wrap: nowrap; justify-content: flex-start;
        border-top: 1px solid var(--border);
        margin: 4px -12px 0;
        position: relative;
    }
    /* Scroll-hint fade: a MASK on the strip's OWN box — NOT a positioned pseudo-element, which inside
       this overflow scroll container scrolls WITH the content and lands mid-strip after the active tab
       is auto-centred (the "AI Chatbot looks faded until you scroll" bug). A mask fades the visible edge
       only. JS toggles .pt-nav--fade-left / --fade-right based on whether tabs are actually clipped on
       that PHYSICAL edge (direction-agnostic), so a fully-visible tab — including the centred active tab
       — is never faded. Vars default to 0 => no fade (also the graceful no-JS state). */
    .pt-nav {
        --fade-l: 0px; --fade-r: 0px;
        -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent);
                mask-image: linear-gradient(to right, transparent, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent);
    }
    .pt-nav.pt-nav--fade-left  { --fade-l: 26px; }
    .pt-nav.pt-nav--fade-right { --fade-r: 26px; }
    .pt-nav a { flex: 1; text-align: center; padding: 9px 4px; font-size: .82rem; border-radius: 0; }
    /* A lone nav item (e.g. an Inbox-only agent): don't stretch it edge-to-edge — center it as a tab. */
    .pt-nav.pt-nav-solo { justify-content: center; }
    .pt-nav.pt-nav-solo a { flex: 0 1 auto; min-width: 150px; }
    /* Backstop: never let page content force horizontal scrolling on phones.
       (The sticky topbar is a sibling of .pt-main, so its stickiness is unaffected.) */
    .pt-main { padding: 12px; overflow-x: hidden; }
    .pt-chat-wrap, .pt-detail-grid { grid-template-columns: 1fr; gap: 12px; }

    /* Chats master-detail on phones: show EITHER the conversation list OR the open
       conversation — never both stacked (which squashed the open chat). */
    .pt-chat-wrap.viewing-conv > .pt-list-card { display: none; }
    .pt-chat-wrap:not(.viewing-conv) > .pt-conv { display: none; }
    .pt-chat-list { max-height: calc(100vh - 240px); }
    .pt-conv { height: calc(100vh - 180px); min-height: 380px; }
    .pt-conv-back { display: inline-flex; }

    .pt-toolbar { flex-wrap: wrap; }
    /* flex-basis 160px (not 0) so the flex line actually BREAKS: with basis 0 every control shrinks
       to a ~50px stub on one row; with 160px they wrap to ~2 per row on a 360px phone. */
    .pt-toolbar .pt-input, .pt-toolbar .pt-select { min-width: 0; flex: 1 1 160px; }
    .pt-table { font-size: .8rem; }
    .pt-table thead th, .pt-table tbody td { padding: 8px 10px; }
    /* Hide non-critical columns on mobile so table fits without squishing */
    .col-hide-sm { display: none; }
    /* Full-bleed on phones: also drop the 94vw/max-width cap, else the card floats with side gaps
       while claiming radius:0 + 100vh (looked half-broken). */
    .pt-login-card { border-radius: 0; min-height: 100vh; box-shadow: none; max-width: none; width: 100%; }
    .pt-login-page { padding: 0; align-items: flex-start; }
    /* Toolbar: stack filters vertically on small screens */
    .pt-toolbar { gap: 8px; }
    .pt-toolbar .pt-select { flex: 1 1 160px; min-width: 0; }
    /* Detail grid sidebar stacks below main content */
    #detailContent .pt-detail-grid { display: flex; flex-direction: column; gap: 12px; }
    /* The topbar becomes two rows on phones (title row + wrapped nav row), so the fixed
       top:62px drawer overlapped it. Drop it below the taller bar and let it span the width. */
    .pt-notif-drawer { top: 108px; right: 8px; left: 8px; width: auto; max-width: none; }
}

@media (max-width: 400px) {
    /* Tighten the action cluster so bell + avatar + lang switcher fit side by side. */
    .pt-actions { gap: 5px; }
    .pt-bell { padding: 6px 6px; }
}

/* ── Language switcher ─────────────────────────────────────────────── */
.pt-lang {
    appearance: auto; -webkit-appearance: auto;
    border: 1px solid var(--border); background: var(--surface);
    color: var(--text-secondary); border-radius: var(--radius-sm);
    padding: 5px 8px; font-size: .8rem; cursor: pointer; max-width: 140px;
}
@media (max-width: 720px) { .pt-lang { padding: 5px 6px; font-size: .76rem; max-width: 110px; } }

/* ── Feedback: long comments must wrap, not overflow (mobile-safe) ──── */
.pt-table-feedback .pt-fb-comment {
    white-space: normal; word-break: break-word; overflow-wrap: anywhere;
    min-width: 120px;
}
/* Any table can fall back to horizontal scroll instead of breaking the page. */
.pt-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── RTL (Arabic / right-to-left display languages) ────────────────────
   The portal is flexbox-based, so most layout mirrors automatically under
   [dir="rtl"]. These rules flip only the explicitly-directional properties so
   the design stays perfect in RTL without touching the LTR styles. */
[dir="rtl"] .pt-topbar .pt-tenant {
    padding-left: 0; margin-left: 0; border-left: none;
    padding-right: 14px; margin-right: 2px; border-right: 1px solid var(--border);
}
[dir="rtl"] .pt-table thead th { text-align: right; }
[dir="rtl"] .pt-dot { margin-right: 0; margin-left: 6px; }
[dir="rtl"] input[type="checkbox"]::after { left: auto; right: 2px; }
[dir="rtl"] input[type="checkbox"]:checked::after { transform: translateX(-16px); }
[dir="rtl"] .pt-bell-badge { right: auto; left: 2px; }
[dir="rtl"] .pt-notif-drawer { right: auto; left: 14px; }
[dir="rtl"] .pt-toasts { right: auto; left: 18px; }
[dir="rtl"] .pt-toast { transform: translateX(-110%); }
[dir="rtl"] .pt-toast.show { transform: translateX(0); }
[dir="rtl"] .pt-timeline li { padding-left: 0; padding-right: 18px; border-left: none; border-right: 2px solid var(--border); }
[dir="rtl"] .pt-install { left: 14px; right: 14px; }
@media (min-width: 720px) { [dir="rtl"] .pt-install { left: 18px; right: auto; } [dir="rtl"] .pt-actions { margin-left: 0; margin-right: auto; } }

/* ─────────────────────────────────────────────────────────────────────────────
   FE-01 — iOS focus zoom.
   iOS Safari zooms the viewport whenever a focused input/textarea/select has a COMPUTED
   font-size below 16px. Every text field in the portal was .84–.92rem (13.4–14.7px) against
   the default 16px root, so every single focus event zoomed — starting with the login tap,
   which is the first thing a user ever does. 16px exactly is the threshold: no zoom, no
   visible change on desktop (the rule is mobile-only).
   Applied to the ELEMENTS as well as the .pt-* classes, so a field that misses a class — or
   one added later — is covered by default rather than reintroducing the bug.
   ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
    .pt-input,
    .pt-select,
    input[type="text"],
    input[type="email"],
    input[type="password"],
    input[type="search"],
    input[type="tel"],
    input[type="url"],
    input[type="number"],
    input[type="date"],
    input[type="datetime-local"],
    input[type="time"],
    select,
    textarea {
        font-size: 16px;
    }
}

/* ─────────────────────────────────────────────────────────────────────────────
   FE-02 — iOS safe-area insets.
   _PortalLayout sets viewport-fit=cover AND apple-mobile-web-app-status-bar-style:
   black-translucent, which deliberately extend the page UNDER the status bar and the home
   indicator — but env(safe-area-inset-*) appeared nowhere in the repo, so on an installed
   iOS PWA the topbar sat beneath the clock and bottom-fixed controls beneath the home
   indicator. max() keeps the existing desktop/Android padding as the floor, so this is
   additive only: on hardware with no inset, env() resolves to 0 and nothing moves.
   ───────────────────────────────────────────────────────────────────────────── */
/* Floors MUST match the values these rules already had, or this stops being additive and becomes a
   restyle. .pt-topbar is `padding: 0 22px` — so top starts at 0 (env() alone; no inset, no change)
   and the sides keep 22px. Getting this wrong shrank the horizontal padding on every device. */
.pt-topbar {
    padding-top:   env(safe-area-inset-top);
    padding-left:  max(22px, env(safe-area-inset-left));
    padding-right: max(22px, env(safe-area-inset-right));
}
/* …but 22px is the DESKTOP floor. This rule sits after the responsive block, so on phones it was
   silently overriding `padding: 8px 12px` and putting 22px back — measured 22px on a 375px
   viewport, i.e. 20px of the narrowest layout's width spent on padding nobody asked for, on every
   phone. Restore the mobile floor while keeping the notch inset, which is all the rule above was
   ever meant to add. */
@media (max-width: 720px) {
    .pt-topbar {
        padding-left:  max(12px, env(safe-area-inset-left));
        padding-right: max(12px, env(safe-area-inset-right));
    }
}

/* The three bottom-fixed elements in the portal — each keeps its own existing offset as the
   floor, so nothing shifts on hardware without an inset. (Verified against the actual
   position:fixed rules: .pt-fab bottom 20px, .pt-toasts 18px, .pt-install 14px. The modal
   overlays use inset:0 and the notification panel is top-anchored, so neither needs this.) */
.pt-fab    { bottom: max(20px, calc(env(safe-area-inset-bottom) + 12px)); }
.pt-toasts { bottom: max(18px, calc(env(safe-area-inset-bottom) + 10px)); }
.pt-install {
    bottom: max(14px, calc(env(safe-area-inset-bottom) + 8px));
    left:   max(14px, env(safe-area-inset-left));
    right:  max(14px, env(safe-area-inset-right));
}

/* ── Customer card (drawer) ───────────────────────────────────────────────────
   Shared by Customers, Chats, Inbox, Emails and the VoC list. These rules lived in a <style> block
   in Pages/Portal/Customers.cshtml, which is why the drawer could only ever render on that page —
   the other surfaces had to navigate there to show a customer. Moved here with the markup and the
   logic (wwwroot/js/portal-customer-card.js) so all three travel together.
   The list-only rules (#cuTable, .cu-selcell, .cu-arrow) deliberately stayed behind on the
   Customers page: nothing else has that table. */
.cu-overlay { position:fixed; inset:0; background:rgba(0,0,0,.45); display:flex; justify-content:flex-end; z-index:1000; }
.cu-drawer  { width:100%; max-width:480px; height:100%; overflow:auto; background:var(--surface,#fff); box-shadow:-4px 0 24px rgba(0,0,0,.18); padding:20px; }
@media (max-width:560px){ .cu-drawer{ max-width:100%; } }
.cu-chip { display:inline-block; font-size:.7rem; padding:2px 7px; border-radius:10px; background:var(--surface-3,#eef2f8); border:1px solid var(--border); margin:2px 2px 0 0; }
/* A chip that is actually a link. It has to LOOK clickable — an agent will not discover a link that
   is styled identically to the four static chips beside it — without becoming a second visual weight
   in the timeline row. Inherited colour + underline-on-hover does both. */
.cu-chip-link { color:var(--brand-primary); text-decoration:none; cursor:pointer; }
.cu-chip-link:hover { text-decoration:underline; background:var(--surface-2); }
/* --brand-primary is #0d2b55 — near-black navy. As a TEXT colour on a dark chip it is unreadable,
   which is the same trap the block near the top of this file calls out for .pt-topbar et al. Same
   remedy: the (light-on-dark) primary text colour. */
:root[data-theme="dark"] .cu-chip-link { color: var(--text-primary); }
.cu-chip.ok { background:var(--success-bg); border-color:var(--border); color:var(--success); }
.cu-chip.no { background:var(--error-bg); border-color:var(--border); color:var(--error); }
:root[data-theme="dark"] .cu-chip.ok { background:#12271e; border-color:#2c6b4f; color:#34c48d; }
:root[data-theme="dark"] .cu-chip.no { background:#2b1512; border-color:#6e332e; color:#e5675c; }
.cu-sec { margin-top:18px; }
.cu-sec h4 { margin:0 0 8px; font-size:.82rem; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted,#667); }
.cu-idrow { display:flex; align-items:center; gap:8px; padding:6px 0; border-bottom:1px dashed var(--border); }
.cu-idrow .v { flex:1; font-size:.9rem; word-break:break-all; }
.cu-tl { padding:7px 0; border-bottom:1px dashed var(--border); font-size:.84rem; }

/* Row action menu — ONE "⋯" per table row in place of a strip of buttons.
   Rendered at BODY level with fixed coordinates so a table's overflow:auto wrapper cannot clip it.
   z-index 1050: above the drawer/overlay layer (1000), below .pt-modal-bg (1100) — a menu item that
   raises a dialog must be painted under the dialog it opened. */
.pt-rowmenu {
    position: fixed; z-index: 1050; min-width: 210px; padding: 6px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); box-shadow: var(--shadow-card);
}
.pt-rowmenu button {
    display: block; width: 100%; padding: 8px 10px; border: 0; border-radius: 7px;
    background: transparent; color: var(--text-primary); font: inherit;
    text-align: start; cursor: pointer; white-space: nowrap;
}
.pt-rowmenu button:hover,
.pt-rowmenu button:focus-visible { background: var(--surface-2); }
.pt-rowmenu button.danger { color: var(--error, #c0392b); }
.pt-rowmenu hr { border: 0; border-top: 1px solid var(--border); margin: 5px 2px; }

/* Document chosen in the chat composer. A PDF has nothing to render, so the confirmation is its
   NAME and SIZE — the name being exactly what the recipient will see on the message bubble. */
.pc-doc-preview {
    display: flex; align-items: center; gap: 10px;
    padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--surface-2); color: var(--text-primary); font-size: .9rem;
}
.pc-doc-preview span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-doc-preview small { margin-inline-start: auto; color: var(--text-secondary); white-space: nowrap; }

/* ── Human check (proof-of-work) ───────────────────────────────────────────────────
   Ported from the marketing site's contact-form captcha, restyled onto the portal's
   design tokens rather than its own slate palette — which is the whole reason this
   needs no dark-mode rules of its own: --surface-2 / --border / --success / --error
   are already redefined under [data-theme="dark"], so the widget follows the theme
   the login card is already using.
   States are driven by data-state on the wrapper (idle / verifying / verified /
   error), set by wwwroot/js/human-check.js. */
.pt-hc {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 10px 12px; margin-bottom: 14px;
    background: var(--surface-2);
    border: 1.5px solid var(--border);
    border-radius: var(--radius-sm);
    transition: border-color var(--transition), background var(--transition);
}

.pt-hc-check {
    display: inline-flex; align-items: center; gap: 10px;
    cursor: pointer; user-select: none;
    font-size: .9rem; font-weight: 600; color: var(--text-primary);
}

/* The real checkbox stays in the accessibility tree and keeps keyboard focus; .pt-hc-box
   is what gets painted. Hiding it with display:none would take it out of the tab order and
   leave the widget unreachable without a mouse. */
.pt-hc-input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }

.pt-hc-box {
    position: relative; flex-shrink: 0;
    width: 22px; height: 22px; border-radius: 6px;
    background: var(--surface);
    border: 2px solid var(--border);
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--transition), border-color var(--transition);
}

.pt-hc-input:focus-visible + .pt-hc-box {
    border-color: var(--brand-accent);
    box-shadow: 0 0 0 3px rgba(26, 111, 196, .25);
}

.pt-hc-tick {
    width: 14px; height: 14px;
    fill: none; stroke: #fff; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;
    opacity: 0; transform: scale(.5);
    transition: opacity var(--transition), transform var(--transition);
}

.pt-hc-spin {
    position: absolute; width: 15px; height: 15px;
    border: 2px solid var(--border);
    border-top-color: var(--brand-accent);
    border-radius: 50%;
    opacity: 0;
    animation: pt-hc-spin .7s linear infinite;
}
@keyframes pt-hc-spin { to { transform: rotate(360deg); } }

/* Respect a reduced-motion preference: keep the spinner visible as a static ring rather
   than animating it, so the "working" state still reads without the movement. */
@media (prefers-reduced-motion: reduce) {
    .pt-hc-spin { animation: none; }
}

.pt-hc-brand {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: .62rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--text-muted); white-space: nowrap;
}
.pt-hc-brand svg { width: 12px; height: 12px; fill: currentColor; opacity: .65; }

/* verifying — the ring shows */
.pt-hc[data-state="verifying"] .pt-hc-spin { opacity: 1; }

/* verified — filled box + tick. --success-bg is a light tint in both themes, so the tick's
   own colour comes from --success rather than borrowing the text colour. */
.pt-hc[data-state="verified"] { border-color: var(--success); background: var(--success-bg); }
.pt-hc[data-state="verified"] .pt-hc-box { background: var(--success); border-color: var(--success); }
.pt-hc[data-state="verified"] .pt-hc-tick { opacity: 1; transform: scale(1); }
.pt-hc[data-state="verified"] .pt-hc-check { color: var(--success); cursor: default; }

/* error — clickable to retry, so the cursor stays a pointer */
.pt-hc[data-state="error"] { border-color: var(--error); background: var(--error-bg); }
.pt-hc[data-state="error"] .pt-hc-box { border-color: var(--error); }
.pt-hc[data-state="error"] .pt-hc-check { color: var(--error); }
