/* ============================================================================
   Portal design system
   ----------------------------------------------------------------------------
   Shared primitives for the redesigned portal pages. Every class is prefixed
   `ds-` so this can coexist with Bootstrap 4 and the legacy Content/themes CSS
   while pages migrate ONE AT A TIME -- nothing here restyles an existing page.

   Brand colors come from --brand-* custom properties published per request by
   Views/Shared/_PartnerBrand.cshtml (resolved from web.PartnerPortalSettings).
   The fallbacks in var() below only apply if that partial didn't run.

   Neutrals and semantic colors are SYSTEM-owned and deliberately not partner-
   configurable: partners pick brand tokens, we guarantee legibility.

   Dark mode: tokens are redefined under [data-theme="dark"] only -- there is no
   prefers-color-scheme rule, so OS dark settings do NOT silently flip the portal.
   Wiring a toggle later is just setting that attribute on <html>.
   ========================================================================== */

:root {
    /* Neutrals -- biased very slightly cool so they sit well next to most brands. */
    --ds-ground: #f6f7f9;
    --ds-ground-2: #eef0f4;
    --ds-surface: #ffffff;
    --ds-surface-2: #fafbfc;
    --ds-ink: #19212e;
    --ds-ink-soft: #3b4655;
    --ds-muted: #6a7484;
    --ds-border: #e4e7ee;
    --ds-border-strong: #d3d8e2;

    /* Semantic -- status only. Never used as an accent, never partner-set. */
    --ds-good: #17935a;
    --ds-good-bg: #e7f5ee;
    --ds-warn: #b47708;
    --ds-warn-bg: #fdf3df;
    --ds-crit: #cf3f3f;
    --ds-crit-bg: #fbe9e9;

    --ds-radius: 14px;
    --ds-radius-sm: 9px;
    --ds-shadow: 0 1px 2px rgba(20,28,42,.04), 0 6px 20px rgba(20,28,42,.05);
    --ds-shadow-lg: 0 10px 40px rgba(20,28,42,.18);

    --ds-gap: 18px;
    /* Content rail. Much wider than the original Bootstrap .container (~1140px), but capped
       rather than full-bleed: edge-to-edge stretches card rows and transaction lines far
       enough apart on a wide monitor that the eye loses the row. This is the mockup's width.
       The header and the services island share this token, so chrome and content stay aligned. */
    --ds-maxw: 1560px;
    --ds-pad: clamp(16px, 2.5vw, 32px);
}

[data-theme="dark"] {
    --ds-ground: #10131a;
    --ds-ground-2: #161a22;
    --ds-surface: #1a1f29;
    --ds-surface-2: #1f2530;
    --ds-ink: #e9edf3;
    --ds-ink-soft: #c2c9d4;
    --ds-muted: #8b94a3;
    --ds-border: #2a303c;
    --ds-border-strong: #363d4b;
    --ds-good: #3fc07f;
    --ds-good-bg: #16281f;
    --ds-warn: #e0a13a;
    --ds-warn-bg: #2a2213;
    --ds-crit: #e56a6a;
    --ds-crit-bg: #2c1717;
    --ds-shadow: 0 1px 2px rgba(0,0,0,.3), 0 6px 20px rgba(0,0,0,.28);
    --ds-shadow-lg: 0 14px 46px rgba(0,0,0,.5);
}

/* Dark canvas. Deliberately applied to the legacy shell containers too, not just body:
   the page has a dark ground even before its components are migrated, so anything still
   rendering a light block on it is a page that hasn't been converted yet and looks like it.

   html is included because body does not necessarily paint the whole viewport — any
   horizontal overflow (or the scrollbar gutter) exposes the html background, which showed
   as a white strip down the right edge. */
/* NOTE the root selector is :root[data-theme] — the attribute lives ON <html>, so a
   descendant selector like `[data-theme="dark"] html` can never match anything. */
:root[data-theme="dark"],
[data-theme="dark"] body,
[data-theme="dark"] .container-wrapper,
[data-theme="dark"] .body-content {
    background-color: var(--ds-ground);
    /* Themes paint a white GRADIENT on .container-wrapper (see color.css:
       `background: linear-gradient(to left, white, #fafafa 50%, white)`). A gradient is a
       background-IMAGE, so it renders on top of background-color and hides it entirely —
       clearing the image is what actually makes the canvas dark. */
    background-image: none;
    color: var(--ds-ink);
}

[data-theme="dark"] .cookiefooter {
    background-color: var(--ds-surface-2);
    color: var(--ds-muted);
}

/* Sticky footer. Previously body was height:100% with .container-wrapper at min-height:80%,
   so a short page left a tall gap below the content where the theme's `body { background:
   #333 }` showed through — reading as a several-inch-tall footer. Column flex instead: the
   content region grows to fill the viewport, and a long page just pushes the footer down. */
body:not(.embedded) {
    display: flex;
    flex-direction: column;
    height: auto;
    min-height: 100vh;
    background-color: var(--ds-ground);
}

/* Flat canvas in BOTH modes. The themes paint a `linear-gradient(to left, white, #fafafa 50%,
   white)` on .container-wrapper; at the old fixed content width that was invisible, but
   full-width it stretches into a visible band with darker edges. Cards need a calm ground to
   sit on, so replace it with a flat one. */
.container-wrapper {
    background-image: none;
    background-color: var(--ds-ground);
    /* Overrides the theme's min-height:80%, which is what created the gap. */
    flex: 1 0 auto;
    min-height: 0;
}

.footer {
    flex-shrink: 0;
    /* Was relying on the dark body showing through. Give it its own surface, matched to the
       nav so the page is bookended by the same brand colour. */
    background-color: var(--brand-nav, #232833);
    color: #fff;
    padding: 14px 0;
    margin-top: 0;
}

.footer a { color: #fff; text-decoration: underline; }

/* ---------- Page shell ---------- */

/* Full-width content area with a sane reading ceiling. Replaces Bootstrap's
   fixed-width .container on redesigned pages. */
.ds-page {
    max-width: var(--ds-maxw);
    margin: 0 auto;
    padding: 22px var(--ds-pad) 60px;
    color: var(--ds-ink);
}

.ds-page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

.ds-page-head h1 {
    margin: 0;
    font-size: 22px;
    letter-spacing: -.01em;
    text-wrap: balance;
}

.ds-sub {
    color: var(--ds-muted);
    font-size: 13px;
    margin-top: 2px;
}

.ds-section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin: 30px 0 14px;
}

.ds-section-head h2 {
    margin: 0;
    font-size: 16px;
    letter-spacing: -.01em;
}

/* Digits that line up in columns (money, dates, counts). */
.ds-num { font-variant-numeric: tabular-nums; }

/* ---------- Grid ---------- */

.ds-grid {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: var(--ds-gap);
}

.ds-col-3 { grid-column: span 3; }
.ds-col-4 { grid-column: span 4; }
.ds-col-5 { grid-column: span 5; }
.ds-col-6 { grid-column: span 6; }
.ds-col-7 { grid-column: span 7; }
.ds-col-8 { grid-column: span 8; }
.ds-col-12 { grid-column: span 12; }

/* Cards collapse to halves, then to full width. Because the grid is auto-flow,
   a card that isn't rendered (feature off / no data) simply closes the gap. */
@media (max-width: 1160px) {
    .ds-col-3, .ds-col-4, .ds-col-5, .ds-col-7, .ds-col-8 { grid-column: span 6; }
}

@media (max-width: 720px) {
    .ds-grid > * { grid-column: span 12 !important; }
}

/* ---------- Card ---------- */

.ds-card {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    padding: 18px 20px;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ds-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 14px;
}

.ds-card h3,
.ds-card-title {
    margin: 0;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--ds-muted);
    font-weight: 700;
}

/* Pushes a block to the bottom of a card so cards in a row align. */
.ds-card-foot { margin-top: auto; padding-top: 12px; }

/* ---------- Buttons ---------- */

.ds-btn {
    border: 1px solid var(--ds-border-strong);
    background: var(--ds-surface);
    color: var(--ds-ink);
    padding: 9px 15px;
    border-radius: var(--ds-radius-sm);
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    text-decoration: none;
    transition: background .12s, border-color .12s, transform .05s;
}

.ds-btn:hover {
    background: var(--ds-surface-2);
    border-color: var(--ds-muted);
    color: var(--ds-ink);
    text-decoration: none;
}

.ds-btn:active { transform: translateY(1px); }

.ds-btn-primary {
    background: var(--brand-primary, #2563eb);
    border-color: var(--brand-primary, #2563eb);
    color: var(--brand-primary-fg, #fff);
}

.ds-btn-primary:hover {
    background: var(--brand-primary-hover, #1d4ed8);
    border-color: var(--brand-primary-hover, #1d4ed8);
    color: var(--brand-primary-fg, #fff);
}

.ds-btn-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--brand-link, #2f6fb0);
    padding: 6px 8px;
}

.ds-btn-ghost:hover {
    background: var(--ds-ground-2);
    color: var(--brand-link-hover, #245a91);
}

.ds-btn-sm { padding: 6px 11px; font-size: 12.5px; }

.ds-btn-danger { color: var(--ds-crit); border-color: var(--ds-border-strong); }
.ds-btn-danger:hover { background: var(--ds-crit-bg); border-color: var(--ds-crit); color: var(--ds-crit); }

/* ---------- User cards ---------- */

.ds-user-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 16px;
}

.ds-user {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    padding: 16px 18px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.ds-user__head { display: flex; align-items: center; gap: 12px; min-width: 0; }

.ds-user__avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    flex: none;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 15px;
    background: var(--brand-primary-soft, #e8eefc);
    color: var(--brand-primary, #2563eb);
}

.ds-user__ident { display: flex; flex-direction: column; min-width: 0; }

.ds-user__name {
    font-size: 15px;
    font-weight: 650;
    color: var(--ds-ink);
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.ds-user__username { font-size: 12px; color: var(--ds-muted); }

/* Addresses are long and unbreakable; let them wrap rather than widen the card. */
.ds-user__contact { overflow-wrap: anywhere; text-align: right; }

.ds-user__actions {
    display: flex;
    gap: 8px;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--ds-border);
}

/* ---------- Usage ---------- */

.ds-usage-list { display: flex; flex-direction: column; gap: 16px; }

/* Expanded detail sits inside the card's row list, so it needs to break out of the row
   rhythm without inheriting its borders. */
.ds-usage-detail {
    padding: 10px 0 4px;
    border-top: 1px solid var(--ds-border);
    overflow-x: auto;
}

.ds-usage-detail .table { margin-bottom: 0; font-size: 13px; }

/* Leaflet stacks its own panes and controls up to z-index ~1000, which is far above the fixed
   header (z-index 1) — so an open map painted straight over the site chrome. `position:
   relative; z-index: 0` makes the map container its own stacking context, so all of Leaflet's
   internal z-indexes are contained inside it and the whole map participates in the page at
   z-index 0. Fixes it without raising the header above jQuery UI's dialogs. */
[id^="MapDiv-"] {
    position: relative;
    z-index: 0;
    border-radius: var(--ds-radius-sm);
    overflow: hidden;
    margin-bottom: 12px;
}

/* ---------- Allowance meters ----------
   For anything shaped "used X of Y": the bar answers "am I close?" at a glance, which four
   numeric columns never do — the customer had to subtract Used from Total themselves. */

.ds-meters { display: flex; flex-direction: column; gap: 16px; }

.ds-meter[hidden] { display: none; }

.ds-meter__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
}

.ds-meter__name {
    font-size: 13.5px;
    font-weight: 650;
    color: var(--ds-ink);
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    min-width: 0;
}

/* The remaining figure is the one people are looking for, so it gets the weight. */
.ds-meter__figure { font-size: 13.5px; font-weight: 650; color: var(--ds-ink); white-space: nowrap; }

.ds-meter__track {
    height: 8px;
    background: var(--ds-ground-2);
    border-radius: 999px;
    overflow: hidden;
}

.ds-meter__track > i {
    display: block;
    height: 100%;
    background: var(--brand-primary, #2563eb);
    border-radius: 999px;
    min-width: 2px;
}

.ds-meter__sub { font-size: 11.5px; color: var(--ds-muted); margin-top: 5px; }

/* State is carried by the bar AND the caption, not colour alone — an allowance at 86% has to
   read as "pay attention" to someone who can't distinguish the fill colour. */
.ds-meter--warn .ds-meter__track > i { background: var(--ds-warn); }
.ds-meter--warn .ds-meter__sub { color: var(--ds-warn); font-weight: 650; }
.ds-meter--crit .ds-meter__track > i { background: var(--ds-crit); }
.ds-meter--crit .ds-meter__sub { color: var(--ds-crit); font-weight: 650; }

.ds-disc-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 12px 18px 16px;
    border-top: 1px solid var(--ds-border);
}

.ds-disc-pager[hidden] { display: none; }

/* ---------- Settings cards ---------- */

/* Settings are a SINGLE narrow column, not a card grid. Seven short cards tiled across a
   1560px page have no hierarchy, stair-step to different heights, and leave most of the
   screen empty. One column of full-width sections keeps every label on the same left edge
   and every action on the same right edge, so the page scans top-to-bottom. Capped at a
   readable width — settings are read, not scanned like a dashboard. */
.ds-settings-grid {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Centres a narrow reading column on a wide page. Applied to the page rather than just the
   sections so the heading sits on the same left edge as the cards below it — capping only
   the sections would leave the title floating off to one side. */
.ds-page--narrow > * {
    max-width: 880px;
    margin-left: auto;
    margin-right: auto;
}

.ds-set {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    padding: 16px 18px;
    min-width: 0;
}

/* Spans the grid — for lists that would be cramped in one column. */
.ds-set--wide { grid-column: 1 / -1; }

.ds-set__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.ds-set__address { font-size: 13.5px; line-height: 1.6; color: var(--ds-ink-soft); }

/* A settings row is three aligned cells: what it is, what it's set to, how to change it.
   Every label starts on the same left edge and every action ends on the same right edge, all
   the way down the page. */
.ds-set .ds-row {
    display: grid;
    grid-template-columns: 190px 1fr auto;
    align-items: center;
    gap: 16px;
    padding: 12px 0;
}

/* Row labels are normalised here rather than at each call site: some come from .resx already
   uppercased ("PRIMARY EMAIL") and others are written sentence-case, so styling them as one
   thing is what makes the column read consistently. */
.ds-set__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--ds-muted);
}

/* Values sit against the labels, left-aligned — a right-aligned value column leaves a river
   of whitespace down the middle of a wide row. */
.ds-set__value {
    text-align: left;
    overflow-wrap: anywhere;
    color: var(--ds-ink);
}

.ds-set .ds-row > span:nth-child(2) { text-align: left; }

@media (max-width: 620px) {
    .ds-set .ds-row {
        grid-template-columns: 1fr auto;
        row-gap: 2px;
    }
    .ds-set__label { grid-column: 1 / -1; }
}

/* Emails and security answers are long and unbreakable; wrap rather than widen the card. */
.ds-set__value { overflow-wrap: anywhere; text-align: right; }

/* ---------- Contact cards ---------- */

.ds-contact-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 12px;
}

.ds-contact {
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-surface-2);
    padding: 12px 14px;
    min-width: 0;
}

.ds-contact__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.ds-contact__name {
    font-weight: 650;
    font-size: 14px;
    color: var(--ds-ink);
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    min-width: 0;
}

.ds-contact__actions { display: flex; gap: 2px; flex: none; }

.ds-contact__lines {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 12.5px;
    color: var(--ds-ink-soft);
    overflow-wrap: anywhere;
}

.ds-contact__lines .fa { color: var(--ds-muted); width: 14px; text-align: center; }

/* ---------- Permission picker ---------- */

/* Add/edit user dialog: identity on the left, permissions on the right, so the whole user is
   visible at once instead of the permissions living below a scroll. */
.ds-user-form { display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; }
.ds-user-form__col { min-width: 0; }

/* The permissions column's heading rule is a divider between the two halves on wide layouts,
   so it shouldn't also draw a line across the top of the right column. */
.ds-user-form__col + .ds-user-form__col .ds-perm-head {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

@media (max-width: 820px) {
    .ds-user-form { grid-template-columns: 1fr; }
    .ds-user-form__col + .ds-user-form__col .ds-perm-head {
        margin-top: 22px;
        padding-top: 16px;
        border-top: 1px solid var(--ds-border);
    }
}

.ds-perm-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 22px 0 10px;
    padding-top: 16px;
    border-top: 1px solid var(--ds-border);
}

.ds-perm__all {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ds-ink-soft);
    cursor: pointer;
}

.ds-perm-group { margin-bottom: 14px; }

.ds-perm-group__title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 700;
    color: var(--ds-muted);
    margin-bottom: 6px;
}

.ds-perm-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2px 14px; }

/* The whole row is the label, so the text is clickable — the old markup put the caption and
   the checkbox in separate elements, so only the box itself was a target. */
.ds-perm {
    display: flex;
    align-items: center;
    gap: 9px;
    margin: 0;
    padding: 7px 8px;
    border-radius: var(--ds-radius-sm);
    font-size: 13.5px;
    font-weight: 400;
    color: var(--ds-ink-soft);
    cursor: pointer;
}

.ds-perm:hover { background: var(--ds-ground-2); }
.ds-perm input[type="checkbox"] { width: 16px; height: 16px; flex: none; margin: 0; }

/* ---------- Links ---------- */

.ds-link { color: var(--brand-link, #2f6fb0); text-decoration: none; }
.ds-link:hover { color: var(--brand-link-hover, #245a91); text-decoration: underline; }

/* ---------- Pills / status ---------- */

.ds-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11.5px;
    font-weight: 700;
    padding: 3px 9px;
    border-radius: 999px;
    letter-spacing: .02em;
    white-space: nowrap;
}

.ds-pill-good { background: var(--ds-good-bg); color: var(--ds-good); }
.ds-pill-warn { background: var(--ds-warn-bg); color: var(--ds-warn); }
.ds-pill-crit { background: var(--ds-crit-bg); color: var(--ds-crit); }
.ds-pill-brand { background: var(--brand-primary-soft, #e8eefc); color: var(--brand-primary, #2563eb); }

.ds-pill-dot::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    display: inline-block;
}

/* ---------- Figures ---------- */

.ds-figure {
    font-size: 40px;
    font-weight: 750;
    letter-spacing: -.02em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.ds-figure-sm {
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
}

.ds-figure-unit { font-size: 14px; color: var(--ds-muted); font-weight: 600; }

/* Amounts: credits reduce the balance, charges increase it. */
.ds-amt-credit { color: var(--ds-good); font-weight: 650; }
.ds-amt-debit { color: var(--ds-crit); font-weight: 650; }

/* ---------- Definition rows (label/value lists inside cards) ---------- */

.ds-rows { display: flex; flex-direction: column; }

.ds-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 0;
    border-top: 1px solid var(--ds-border);
    font-size: 13.5px;
    color: var(--ds-ink-soft);
}

.ds-row:first-child { border-top: none; }

.ds-row-total {
    font-weight: 700;
    color: var(--ds-ink);
    border-top: 1px dashed var(--ds-border-strong);
    margin-top: 1px;
    padding-top: 10px;
}

.ds-row-sub { display: block; color: var(--ds-muted); font-size: 12px; }

/* ---------- Table ---------- */

.ds-table-wrap { overflow-x: auto; }

.ds-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
}

.ds-table th {
    text-align: left;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ds-muted);
    font-weight: 700;
    padding: 8px 10px;
    border-bottom: 1px solid var(--ds-border-strong);
    white-space: nowrap;
}

.ds-table td {
    padding: 10px;
    border-bottom: 1px solid var(--ds-border);
    color: var(--ds-ink-soft);
    vertical-align: top;
}

.ds-table tr:last-child td { border-bottom: none; }
.ds-table .ds-right, .ds-table th.ds-right { text-align: right; }

/* ---------- Promotions carousel ---------- */

.ds-promo {
    position: relative;
    margin-bottom: 18px;
    border-radius: var(--ds-radius);
    overflow: hidden;
    border: 1px solid var(--ds-border);
    box-shadow: var(--ds-shadow);
    background: var(--ds-surface);
}

.ds-promo[hidden] { display: none; }

.ds-promo-viewport { overflow: hidden; }
.ds-promo-track { display: flex; flex-wrap: nowrap; transition: transform .42s cubic-bezier(.4,0,.2,1); }

/* Height is content-driven but FENCED: a floor so a one-line slide isn't cramped, and the
   body clamped so a wordy one can't balloon the band and push the dashboard down. All
   slides share the track height, so advancing never shifts the page.
   The art column is intentionally a single custom property + its own class, so adding
   image-right / full-bleed layouts later is a CSS change, not a markup rewrite. */
.ds-promo-slide {
    flex: 0 0 100%;
    min-width: 0;
    display: grid;
    grid-template-columns: var(--ds-promo-art-w, 190px) 1fr;
    align-items: stretch;
    min-height: 150px;
}

/* Derived art for slides with no image of their own. A flat two-stop gradient reads as a
   coloured rectangle, so the panel also carries two soft radial highlights (one light, one
   shadow) for depth and a faint diagonal weave for texture. All of it is built from the
   partner's own brand tokens — no asset to host, and it restyles itself when they rebrand. */
.ds-promo-art {
    background:
        radial-gradient(circle at 78% 18%, rgba(255, 255, 255, .30), transparent 48%),
        radial-gradient(circle at 18% 88%, rgba(0, 0, 0, .22), transparent 54%),
        linear-gradient(135deg, var(--brand-primary, #2563eb), var(--brand-accent, #1d4ed8));
    position: relative;
    overflow: hidden;
}

/* Diagonal weave — deliberately very low contrast; it should read as texture, not stripes. */
.ds-promo-art::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: repeating-linear-gradient(
        135deg,
        rgba(255, 255, 255, .07) 0 2px,
        transparent 2px 14px
    );
    pointer-events: none;
}

/* Motif (szArtStyle). Oversized and bled off the corner so it reads as a watermark behind the
   panel rather than a centred icon — it's decoration, and it must never look like a control. */
.ds-promo-motif {
    position: absolute;
    right: -18%;
    bottom: -22%;
    width: 118%;
    height: auto;
    color: #fff;
    opacity: .22;
    pointer-events: none;
}

/* A supplied image replaces the derived art entirely, so the two never fight. */
.ds-promo-art img { width: 100%; height: 100%; object-fit: cover; display: block; position: relative; z-index: 1; }
.ds-promo-art:has(img)::after { display: none; }

@media (max-width: 720px) {
    /* The art strip is only ~74px tall stacked, so a bled watermark just shows a fragment. */
    .ds-promo-motif { right: 12px; bottom: -30%; width: 92px; }
}

.ds-promo-copy {
    padding: 20px 22px;
    display: flex;
    flex-direction: column;
    gap: 5px;
    justify-content: center;
    min-width: 0;
}

.ds-promo-eyebrow {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .08em;
    font-weight: 700;
    color: var(--brand-primary, #2563eb);
}

.ds-promo-title { margin: 0; font-size: 20px; letter-spacing: -.01em; text-wrap: balance; color: var(--ds-ink); }

.ds-promo-body {
    margin: 0;
    color: var(--ds-muted);
    font-size: 13.5px;
    max-width: 62ch;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.ds-promo-cta { margin-top: 10px; align-self: flex-start; }

/* One control cluster in the bottom corner: prev, dots, next. The arrows used to be
   vertically centred and absolutely placed over the slide, which sat the prev arrow on top of
   the body copy. Keeping them together also means one predictable place to look. */
.ds-promo-controls {
    position: absolute;
    bottom: 10px;
    right: 14px;
    display: flex;
    align-items: center;
    gap: 10px;
    /* Recessed until the pane is hovered or focused, so they never compete with the message
       itself. Always fully visible on touch, where there is no hover to reveal them. */
    opacity: .55;
    transition: opacity .15s;
}

.ds-promo:hover .ds-promo-controls,
.ds-promo:focus-within .ds-promo-controls { opacity: 1; }

@media (hover: none) {
    .ds-promo-controls { opacity: 1; }
}

.ds-promo-nav {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--ds-border);
    background: var(--ds-surface);
    color: var(--ds-ink);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    flex: none;
}

.ds-promo-nav:hover { background: var(--ds-ground-2); }

.ds-promo-dots { display: flex; gap: 6px; }

.ds-promo-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    border: none;
    padding: 0;
    background: var(--ds-border-strong);
    cursor: pointer;
}

.ds-promo-dot.is-active { background: var(--brand-primary, #2563eb); width: 18px; border-radius: 999px; }

.ds-promo-dismiss {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--ds-muted);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}

.ds-promo-dismiss:hover { background: var(--ds-ground-2); color: var(--ds-ink); border-color: var(--ds-border); }

.ds-promo-restore { margin-bottom: 16px; }
.ds-promo-restore[hidden] { display: none; }

/* Moved into the page header by script (see _Promotions), where it costs no vertical space. */
.ds-page-head .ds-promo-restore { margin-bottom: 0; align-self: center; }

.ds-promo-restore button {
    border: 1px dashed var(--ds-border-strong);
    background: transparent;
    color: var(--ds-muted);
    font-size: 12.5px;
    font-weight: 600;
    padding: 7px 13px;
    border-radius: 999px;
    cursor: pointer;
}

.ds-promo-restore button:hover { color: var(--ds-ink); border-color: var(--ds-muted); background: var(--ds-surface); }

@media (max-width: 720px) {
    .ds-promo-slide { grid-template-columns: 1fr; }
    .ds-promo-art { height: 74px; }

    /* Narrow slides put the CTA near the bottom, so the controls move to their own row
       underneath rather than sitting over it. */
    .ds-promo-controls {
        position: static;
        justify-content: center;
        padding: 4px 0 12px;
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ds-promo-track { transition: none; }
}

/* ---------- Package cards ---------- */

.ds-pkg-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
    gap: 16px;
}

.ds-pkg {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Brand-colored band so a package reads as a distinct object at a glance. */
.ds-pkg-band { height: 4px; background: var(--brand-primary, #2563eb); }

.ds-pkg-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 4px; flex: 1; }

.ds-pkg-name { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }

.ds-pkg-price {
    font-size: 22px;
    font-weight: 750;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    margin-top: 6px;
}

.ds-pkg-price span { font-size: 12px; color: var(--ds-muted); font-weight: 600; }

.ds-pkg-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 12px 18px;
    border-top: 1px solid var(--ds-border);
    background: var(--ds-surface-2);
}

.ds-chips { display: flex; gap: 6px; flex-wrap: wrap; }

.ds-chip {
    font-size: 11px;
    background: var(--ds-ground-2);
    color: var(--ds-ink-soft);
    padding: 2px 8px;
    border-radius: 999px;
    font-weight: 600;
}

.ds-chip--good { background: var(--ds-good-bg); color: var(--ds-good); }
.ds-chip--warn { background: var(--ds-warn-bg); color: var(--ds-warn); }
.ds-chip--crit { background: var(--ds-crit-bg); color: var(--ds-crit); }

/* ---------- Slide-over sheet ---------- */

.ds-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15,20,30,.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s;
    z-index: 1040;
}

.ds-overlay.is-open { opacity: 1; pointer-events: auto; }

.ds-sheet {
    position: fixed;
    top: 0;
    right: 0;
    height: 100%;
    width: min(460px, 100%);
    background: var(--ds-surface);
    z-index: 1050;
    transform: translateX(100%);
    transition: transform .26s cubic-bezier(.4,0,.2,1);
    display: flex;
    flex-direction: column;

    /* NO shadow while closed. A closed sheet is parked just off the right edge by the
       transform, but a 40px blur reaches back OVER that edge and paints a grey band down the
       side of the page. Pages render one sheet per row — nine lines on a usage page, one per
       package on Overview — so the bands stacked and read as a striped smear that had nothing
       to do with any sheet being open. */
    box-shadow: none;
}

.ds-sheet.is-open {
    transform: translateX(0);
    box-shadow: var(--ds-shadow-lg);
}

.ds-sheet-head {
    padding: 18px 26px 14px;
    border-bottom: 1px solid var(--ds-border);
    position: relative;
}

.ds-sheet-head h3 { margin: 0; font-size: 19px; letter-spacing: -.01em; color: var(--ds-ink); }

.ds-sheet-close {
    position: absolute;
    top: 14px;
    right: 22px;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    border: 1px solid var(--ds-border);
    background: var(--ds-surface);
    color: var(--ds-muted);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}

.ds-sheet-close:hover { background: var(--ds-ground-2); color: var(--ds-ink); }

.ds-tabs { display: flex; gap: 18px; padding: 0 20px; border-bottom: 1px solid var(--ds-border); }

.ds-tab {
    padding: 11px 2px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ds-muted);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
}

.ds-tab.is-active { color: var(--ds-ink); border-bottom-color: var(--brand-primary, #2563eb); }

/* Roomier gutters than the narrow sheet needed. The wide usage sheet carries a facts grid
   and a records list with a right-aligned figure column, and 20px left the amounts almost
   touching the edge. scrollbar-gutter keeps the padding honest when the list overflows,
   instead of the scrollbar eating into it. */
.ds-sheet-body {
    padding: 18px 26px 28px;
    overflow-y: auto;
    scrollbar-gutter: stable;
    flex: 1;
}

.ds-tabpane { display: none; }
.ds-tabpane.is-active { display: block; }

@media (prefers-reduced-motion: reduce) {
    .ds-sheet, .ds-overlay { transition: none; }
}

/* ---------- Form controls ---------- */

/* Browsers default inputs to a white field with dark text regardless of page theme, so the
   surface and text colour have to be stated. color-scheme tells the browser which palette to
   use for the parts it draws itself (select popups, scrollbars, autofill). */
.ds-input,
.ds-select {
    min-height: 40px;
    padding: 8px 12px;
    border: 1px solid var(--ds-border-strong);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-surface);
    color: var(--ds-ink);
    font-size: 14px;
    font-family: inherit;
    color-scheme: light dark;
}

.ds-input::placeholder { color: var(--ds-muted); opacity: 1; }

.ds-input:focus,
.ds-select:focus {
    outline: 2px solid var(--brand-primary, #2563eb);
    outline-offset: 1px;
    border-color: var(--brand-primary, #2563eb);
}

/* ---------- Transaction list ---------- */

.ds-txn-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 14px 18px;
    border-bottom: 1px solid var(--ds-border);
}

.ds-txn-toolbar .ds-input { flex: 1 1 260px; max-width: 420px; }

/* Controls in a toolbar's right-hand group stay on ONE row — a search box that wraps its
   own sort and page-size selects onto a second line reads as two unrelated toolbars. */
.ds-txn-toolbar > .ds-inline { flex-wrap: nowrap; }
.ds-txn-toolbar > .ds-inline > .ds-input { flex: 1 1 200px; min-width: 150px; }
.ds-txn-toolbar > .ds-inline > .ds-select { flex: 0 0 auto; }

@media (max-width: 720px) {
    .ds-txn-toolbar > .ds-inline { flex-wrap: wrap; }
}

.ds-txn-list { display: flex; flex-direction: column; }

/* A statement line, not a table row: date rail, then what happened, then the amount. */
.ds-txn {
    display: grid;
    grid-template-columns: 76px 1fr auto;
    gap: 14px;
    align-items: baseline;
    padding: 13px 18px;
    border-top: 1px solid var(--ds-border);
}

.ds-txn:first-child { border-top: none; }
.ds-txn[hidden] { display: none; }
.ds-txn:hover { background: var(--ds-surface-2); }

.ds-txn__date { display: flex; flex-direction: column; line-height: 1.25; }
.ds-txn__day { font-size: 13px; font-weight: 600; color: var(--ds-ink-soft); white-space: nowrap; }
.ds-txn__year { font-size: 11px; color: var(--ds-muted); }

.ds-txn__body { min-width: 0; }

.ds-txn__type {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ds-ink);
    flex-wrap: wrap;
}

/* Direction encoded in form as well as colour, so it survives a colour-blind reader. */
.ds-txn__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.ds-txn__dot.is-credit { background: var(--ds-good); }
.ds-txn__dot.is-debit { background: var(--ds-crit); }

.ds-txn__note {
    font-size: 12.5px;
    color: var(--ds-muted);
    margin-top: 3px;
    line-height: 1.45;
    overflow-wrap: anywhere;
}

.ds-txn__amt { font-size: 14.5px; white-space: nowrap; }

/* Tighter variant for the Overview card, where the list is a summary rather than the page. */
.ds-txn--compact { padding: 9px 0; gap: 12px; grid-template-columns: 54px 1fr auto; }
.ds-txn--compact:hover { background: transparent; }
.ds-txn--compact .ds-txn__note { margin-top: 1px; }

/* ---------- Categorical dots ----------
   For distinguishing items in a list (packages, series) where the colour carries no
   good/bad meaning — deliberately separate from the semantic good/warn/crit tokens, which
   must keep meaning what they say. Always paired with a label, never the only signal. */
.ds-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; flex: none; }
.ds-dot-1 { background: #7c5cff; }
.ds-dot-2 { background: #2f6fb0; }
.ds-dot-3 { background: #17935a; }
.ds-dot-4 { background: #d98324; }
.ds-dot-5 { background: #b4508f; }

@media (max-width: 560px) {
    .ds-txn {
        grid-template-columns: 1fr auto;
        row-gap: 4px;
    }
    /* Date moves under the description rather than eating a third of a narrow screen. */
    .ds-txn__date { grid-row: 2; grid-column: 1; flex-direction: row; gap: 5px; }
    .ds-txn__body { grid-row: 1; grid-column: 1; }
    .ds-txn__amt { grid-row: 1; grid-column: 2; }
}

@media print {
    .ds-txn-toolbar, .ds-page-head, .header, .footer { display: none !important; }
    .ds-txn { break-inside: avoid; }
}

/* ---------- Utilities ---------- */

.ds-muted { color: var(--ds-muted); }

/* Keep partner/system-authored text (payment references, descriptions) from blowing out a
   card's height. Pair with min-width:0 on the flex child so it can actually shrink. */
.ds-clamp-1,
.ds-clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.ds-clamp-1 { -webkit-line-clamp: 1; }
.ds-clamp-2 { -webkit-line-clamp: 2; }
.ds-note { color: var(--ds-muted); font-size: 11.5px; line-height: 1.45; }
.ds-stack { display: flex; flex-direction: column; gap: 8px; }
.ds-inline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ds-empty { color: var(--ds-muted); font-size: 13px; padding: 6px 0; }

/* ---------- Ticket cards ---------- */

.ds-tkt-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 16px;
}

.ds-tkt {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    padding: 16px 18px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

.ds-tkt[hidden] { display: none; }

.ds-tkt__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.ds-tkt__id { font-size: 12.5px; font-weight: 700; color: var(--ds-muted); }

/* Status keeps the tenant-configured colour, but as an outlined chip so it reads as a state
   rather than as arbitrarily coloured text. */
.ds-tkt__status {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .02em;
    padding: 2px 9px;
    border: 1px solid currentColor;
    border-radius: 999px;
    white-space: nowrap;
}

.ds-tkt__priority { font-size: 11.5px; font-weight: 600; display: inline-flex; align-items: center; gap: 5px; }
.ds-tkt__dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.ds-tkt__escalated { color: var(--ds-crit); font-size: 13px; }

.ds-tkt__title {
    margin: 0;
    font-size: 15.5px;
    font-weight: 650;
    line-height: 1.35;
    color: var(--ds-ink);
    overflow-wrap: anywhere;
}

/* Descriptions are free text and can be long; cap them so one verbose ticket doesn't set the
   height of every card in the row. */
.ds-tkt__desc {
    font-size: 13px;
    color: var(--ds-muted);
    line-height: 1.5;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

.ds-tkt__meta { display: flex; gap: 6px; flex-wrap: wrap; }

.ds-tkt__foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 10px;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--ds-border);
}

.ds-tkt__when { font-size: 11.5px; color: var(--ds-ink-soft); line-height: 1.5; }
.ds-tkt__actions { display: flex; gap: 2px; flex: none; }

/* Whole card opens the detail sheet, so it needs to look interactive. */
.ds-tkt { cursor: pointer; transition: border-color .12s, box-shadow .12s; }
.ds-tkt:hover { border-color: var(--brand-primary, #2563eb); box-shadow: var(--ds-shadow-lg); }
.ds-tkt:focus-visible { outline: 2px solid var(--brand-primary, #2563eb); outline-offset: 2px; }

.ds-tkt__open { display: flex; align-items: center; gap: 12px; font-size: 12px; white-space: nowrap; }
.ds-tkt__attachcount { color: var(--ds-muted); }

/* ---------- Ticket detail sheet ---------- */

/* The usage detail sheet carries a six-cell facts grid and day-grouped records with a figure
   column, which 620px squeezed to the point the right-hand cell was unreadable. */
.ds-sheet--wide { width: min(820px, 100%); }

/* While a sheet is open the page behind it must not scroll — the wheel belongs to the panel
   the reader is looking at. */
body.ds-sheet-open { overflow: hidden; }

/* Third-party chat widgets set a z-index in the billions from their own stylesheet, so the
   bubble (and, mid-open, its full-height panel) paints OVER the slide-over — which is the
   grey band down the right of the sheet. Demote it while a sheet is open so the overlay dims
   it like everything else; it comes straight back on close.

   !important is required and sufficient: the widget sets z-index inline WITHOUT !important,
   and an important declaration in a stylesheet outranks a normal inline one. */
body.ds-sheet-open > div[class*="livechat"],
body.ds-sheet-open > div[class*="LiveChat"],
body.ds-sheet-open > div[id*="livechat"],
body.ds-sheet-open > iframe[class*="livechat"],
body.ds-sheet-open > iframe[id*="livechat"] {
    z-index: 1000 !important;
}

.ds-sheet-head h3 { margin-top: 6px; padding-right: 40px; }

.ds-tkt-detail__desc {
    font-size: 14px;
    line-height: 1.6;
    color: var(--ds-ink-soft);
    margin-bottom: 18px;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* ---------- Comment thread ---------- */

.ds-thread { display: flex; flex-direction: column; gap: 14px; margin-bottom: 18px; }

.ds-thread__item {
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    padding: 10px 12px;
}

.ds-thread__meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    font-size: 11.5px;
    color: var(--ds-muted);
    margin-bottom: 5px;
}

.ds-thread__author { font-weight: 650; color: var(--ds-ink-soft); }

.ds-thread__body {
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--ds-ink);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.ds-thread__form { border-top: 1px solid var(--ds-border); padding-top: 14px; }

/* ---------- Unbilled work ---------- */

.ds-unbilled__project { border-top: 1px solid var(--ds-border); }

.ds-unbilled__projecthead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 18px;
    background: var(--ds-surface-2);
    font-size: 13px;
}

.ds-unbilled__projectname { font-weight: 650; color: var(--ds-ink); }

/* ---------- FCC broadband labels ----------
   The label is a REGULATED DOCUMENT with a mandated layout, rendered as pre-built HTML we
   don't control (bill.BroadbandLabel.szLabel) with its own hard-coded dark text. Recolouring
   it for dark mode would mean rewriting a disclosure the FCC specifies the appearance of — so
   it stays on a light ground in both themes, the same call as the CPP capture iframe.
   A document on a page is allowed to look like a document. */
.bbl {
    background: #ffffff;
    color: #111111;
    border-radius: var(--ds-radius-sm);
}

[data-theme="dark"] .bbl {
    background: #ffffff;
    color: #111111;
    /* Softens the edge so a white document doesn't glare against the dark sheet. */
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .14);
}

[data-theme="dark"] .svc-label-scroll,
[data-theme="dark"] .ds-tabpane .bbl { color: #111111; }

/* ---------- Dark-mode fallbacks for un-migrated (Bootstrap/legacy) markup ----------
   Deliberately minimal: enough that a page which hasn't been converted is still READABLE in
   dark mode — no white-on-white inputs, no black text on a white table inside a dark page —
   without making it look finished. Converted pages should use the .ds-* components instead. */

[data-theme="dark"] .form-control,
[data-theme="dark"] .custom-select,
[data-theme="dark"] .input-group-text {
    background-color: var(--ds-surface);
    color: var(--ds-ink);
    border-color: var(--ds-border-strong);
    color-scheme: light dark;
}

[data-theme="dark"] .form-control::placeholder { color: var(--ds-muted); opacity: 1; }

[data-theme="dark"] .form-control:disabled,
[data-theme="dark"] .form-control[readonly] { background-color: var(--ds-ground-2); }

[data-theme="dark"] .table { color: var(--ds-ink-soft); }
[data-theme="dark"] .table th { color: var(--ds-ink); border-color: var(--ds-border-strong); }
[data-theme="dark"] .table td { border-color: var(--ds-border); }
[data-theme="dark"] .table thead th { border-color: var(--ds-border-strong); }
[data-theme="dark"] .table-striped tbody tr:nth-of-type(odd) { background-color: var(--ds-surface-2); }
[data-theme="dark"] .thead-light th { background-color: var(--ds-surface-2); color: var(--ds-ink); }

[data-theme="dark"] .card,
[data-theme="dark"] .modal-content,
[data-theme="dark"] .list-group-item,
[data-theme="dark"] .dropdown-menu {
    background-color: var(--ds-surface);
    color: var(--ds-ink);
    border-color: var(--ds-border);
}

[data-theme="dark"] .dropdown-item { color: var(--ds-ink-soft); }
[data-theme="dark"] .dropdown-item:hover,
[data-theme="dark"] .dropdown-item:focus { background-color: var(--ds-ground-2); color: var(--ds-ink); }

[data-theme="dark"] .dropdown-divider { border-color: var(--ds-border); }

/* The header's own menu classes, which are NOT Bootstrap's .dropdown-item — the nav and
   "Other links"/"Analytics" sub-menus use .SubMenuColor / .SubMenuItemColor, and the theme
   sets those to `background: white` / `color: black`. On a dark drop-down that's black text
   on a dark ground, i.e. invisible. The theme marks the hover state !important, so the
   override has to as well. */
[data-theme="dark"] .SubMenuColor {
    background-color: var(--ds-surface) !important;
    border-color: var(--ds-border);
}

[data-theme="dark"] .SubMenuItemColor { color: var(--ds-ink-soft) !important; }

[data-theme="dark"] .SubMenuItemColor:hover,
[data-theme="dark"] .SubMenuItemColor.active {
    background-color: var(--brand-primary, #2563eb) !important;
    color: var(--brand-primary-fg, #fff) !important;
}

[data-theme="dark"] .MainMenuColor { background-color: transparent; }

[data-theme="dark"] .ModuleSection { border-color: var(--ds-border); }

/* The portal's custom checkbox/radio (otherstyles.css .control/.control-indicator) hides the
   real input and draws its own box, with the tick as `border: solid #333`. On a dark card that
   tick is black-on-black: the control toggles, but nothing visibly changes, so the form reads
   as disabled. Repaint the box and the tick from the theme. */
[data-theme="dark"] .control .control-indicator {
    border-color: var(--ds-border-strong);
    background-color: var(--ds-surface);
}

[data-theme="dark"] .control-checkbox:hover .control-indicator { border-color: var(--ds-muted); }

[data-theme="dark"] .control-checkbox input[type=checkbox] ~ .control-indicator:after {
    border-color: var(--brand-primary, #2563eb);
}

[data-theme="dark"] .control input[type=radio] ~ .control-indicator:after {
    background: var(--brand-primary, #2563eb);
}

/* Give the hidden input a keyboard-visible focus ring via its indicator. */
[data-theme="dark"] .control input:focus-visible ~ .control-indicator,
.control input:focus-visible ~ .control-indicator {
    outline: 2px solid var(--brand-primary, #2563eb);
    outline-offset: 2px;
}

/* Native select popups and scrollbars are drawn by the browser, so it needs telling which
   palette the page is using — otherwise an open drop-down is light-on-light. */
[data-theme="dark"] select.form-control,
[data-theme="dark"] input.form-control,
[data-theme="dark"] textarea.form-control { color-scheme: light dark; }

/* jQuery UI dialogs are used for most of the portal's modals. */
[data-theme="dark"] .ui-dialog,
[data-theme="dark"] .ui-dialog .ui-dialog-content,
[data-theme="dark"] .ui-widget-content {
    background: var(--ds-surface);
    color: var(--ds-ink);
    border-color: var(--ds-border);
}

/* ---------- Third-party chat widget ----------
   Tenant chat widgets (customerportal.Settings.szChatScript) inject their container as the
   first child of <body> and position it from their OWN stylesheet, which loads
   asynchronously. Until that arrives the container is position:static, so it paints in normal
   flow as a full-width bar across the top of the page — the blue flash on every navigation.

   The widget sets its own inline right/bottom/z-index, so pinning only `position` here — from
   a stylesheet the browser has already parsed — lands it in the right place on the first
   paint. Nothing else about the widget is touched. */
body > div[class*="livechat"],
body > div[class*="LiveChat"],
body > div[id*="livechat"] {
    position: fixed !important;
}

/* Keep it out of print output. */
@media print {
    body > div[class*="livechat"],
    body > div[class*="LiveChat"],
    body > div[id*="livechat"] { display: none !important; }
}

/* ---------- Accessibility ---------- */

.ds-page :focus-visible,
.ds-btn:focus-visible {
    outline: 2px solid var(--brand-primary, #2563eb);
    outline-offset: 2px;
    border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
    .ds-btn { transition: none; }
}

/* ---------- Usage: status banner ----------
   Answers "is anything wrong?" before the numbers start. Only rendered when the account has
   an allowance that could run out -- an account with none gets no banner rather than a hollow
   reassurance. The dot carries the state redundantly with the tint. */

.ds-alert {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    padding: 14px 18px;
    margin-bottom: 18px;
    background: var(--ds-surface);
}

.ds-alert__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex: none;
    margin-top: 7px;
    background: var(--ds-muted);
}

.ds-alert__title { font-weight: 650; color: var(--ds-ink); }
.ds-alert__text { font-size: 13.5px; color: var(--ds-ink-soft); margin-top: 1px; }

.ds-alert--good { background: var(--ds-good-bg); border-color: var(--ds-good); }
.ds-alert--good .ds-alert__dot { background: var(--ds-good); }
.ds-alert--warn { background: var(--ds-warn-bg); border-color: var(--ds-warn); }
.ds-alert--warn .ds-alert__dot { background: var(--ds-warn); }
.ds-alert--crit { background: var(--ds-crit-bg); border-color: var(--ds-crit); }
.ds-alert--crit .ds-alert__dot { background: var(--ds-crit); }

/* ---------- Usage: period totals ----------
   One joined strip rather than four floating cards: these are four readings of the same
   period, and separate cards made them look like four unrelated things. */

.ds-statstrip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1px;
    background: var(--ds-border);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    overflow: hidden;
    margin-bottom: 18px;
}

.ds-stat { background: var(--ds-surface); padding: 15px 18px 16px; }

.ds-stat__label {
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ds-muted);
    font-weight: 650;
}

.ds-stat__value {
    font-size: 25px;
    font-weight: 650;
    letter-spacing: -.02em;
    margin-top: 4px;
    color: var(--ds-ink);
}

.ds-stat__note { font-size: 12.5px; color: var(--ds-muted); margin-top: 2px; }

/* The only figure here with money on it. As the fourth of four identical tiles it read as
   an afterthought; the brand tint makes it the one the eye lands on. */
.ds-stat--money { background: var(--brand-primary-soft, #eaf0fe); }
.ds-stat--money .ds-stat__value { color: var(--brand-primary, #2563eb); }
.ds-stat--money .ds-stat__label { color: var(--brand-primary, #2563eb); }

/* ---------- Usage: line cards ----------
   These replace the "Data by service" bar chart. With a handful of lines a chart restated
   two numbers, mixed MB and GB on one axis, and silently omitted any line with no data.
   The cards carry the same comparison plus talk and messages. */

.ds-linegrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: var(--ds-gap);
}

.ds-linecard {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius);
    box-shadow: var(--ds-shadow);
    padding: 18px 20px 16px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.ds-linecard__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.ds-linecard__id { min-width: 0; }

.ds-linecard__name {
    font-weight: 650;
    font-size: 15.5px;
    color: var(--ds-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ds-linecard__num { font-size: 13px; color: var(--ds-muted); margin-top: 1px; }

.ds-linecard__cost { text-align: right; flex: none; }
.ds-linecard__cost b { font-size: 19px; font-weight: 650; letter-spacing: -.01em; color: var(--ds-ink); }

.ds-linecard__cost span {
    display: block;
    font-size: 11px;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ds-muted);
    font-weight: 650;
}

.ds-linecard__body { display: flex; flex-direction: column; gap: 12px; }

/* Usage figures are stated, not metered. Drawing them as bars inside the allowance would
   assert that usage consumes the allowance, which is not true -- traffic that does not
   qualify for a discount is billed without touching it. */
.ds-plainstat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    font-size: 13.5px;
}

.ds-plainstat__name { color: var(--ds-ink-soft); font-weight: 600; }
.ds-plainstat__figure { font-weight: 650; color: var(--ds-ink); }

/* Allowances are a different KIND of fact from the usage figures above, so they are fenced
   off and labelled rather than continuing the same list. */
.ds-allowances {
    border-top: 1px dashed var(--ds-border-strong);
    padding-top: 13px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.ds-allowances__label {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ds-muted);
    font-weight: 700;
}

.ds-allowances__none { font-size: 13px; color: var(--ds-muted); }

.ds-linecard__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    border-top: 1px solid var(--ds-border);
    padding-top: 12px;
    margin-top: auto;
}

.ds-linecard__more {
    font: inherit;
    font-size: 13.5px;
    font-weight: 650;
    color: var(--brand-primary, #2563eb);
    background: none;
    border: 0;
    padding: 4px 2px;
    cursor: pointer;
    border-radius: 6px;
}

.ds-linecard__more:hover { text-decoration: underline; }

.ds-linecard__more:focus-visible {
    outline: 2px solid var(--brand-primary, #2563eb);
    outline-offset: 2px;
}

/* ---------- Usage: line detail sheet ----------
   The drawer answers the question before producing the evidence. The panel it replaces
   opened straight into a 300-row paged table in which four of seven columns were blank on
   every row and every cost read $0.00. */

.ds-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 1px;
    background: var(--ds-border);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    overflow: hidden;
    margin-bottom: 18px;
}

.ds-fact { background: var(--ds-surface-2); padding: 12px 14px; }

.ds-fact__label {
    font-size: 10.5px;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ds-muted);
    font-weight: 650;
}

.ds-fact__value { font-size: 17px; font-weight: 650; margin-top: 3px; color: var(--ds-ink); }

.ds-usage-note {
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    padding: 10px 13px;
    margin-bottom: 18px;
}

.ds-daygroup { margin-bottom: 18px; }

.ds-daygroup__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 0 2px 7px;
    border-bottom: 1px solid var(--ds-border);
    margin-bottom: 2px;
}

.ds-daygroup__date { font-weight: 650; font-size: 14px; color: var(--ds-ink); }
.ds-daygroup__sum { font-size: 12.5px; color: var(--ds-muted); }

/* Three columns: when, what, how much. A field that is empty simply does not join the meta
   line, which is what retires the permanently blank Number and Location columns. */
.ds-rec {
    display: grid;
    grid-template-columns: 74px 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 9px 2px;
    border-bottom: 1px solid var(--ds-border);
    font-size: 13.5px;
}

.ds-rec:last-child { border-bottom: 0; }
.ds-rec__time { color: var(--ds-muted); }
.ds-rec__what { min-width: 0; }
.ds-rec__kind { font-weight: 600; color: var(--ds-ink); display: block; }
.ds-rec__meta { font-size: 12.5px; color: var(--ds-muted); }
.ds-rec__amt { text-align: right; }
.ds-rec__amt b { color: var(--ds-ink); font-weight: 650; }
.ds-rec__included { font-size: 12px; color: var(--ds-good); font-weight: 600; }

/* "No usage" in a cell that otherwise carries figures. Muted so a column of real numbers
   still reads as the signal and the blanks recede, rather than "0 MB" repeated down the
   page implying a measurement that was never taken. */
.ds-muted-cell { color: var(--ds-muted); font-weight: 400; }

/* A button that opens a panel, sitting where a link would. It behaves like a button — no
   navigation, no URL to copy — so it is one, and only borrows the link's appearance. */
.ds-linkbtn {
    font: inherit;
    color: var(--brand-link, #2563eb);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-align: left;
}

.ds-linkbtn:hover { text-decoration: underline; }

.ds-linkbtn:focus-visible {
    outline: 2px solid var(--brand-primary, #2563eb);
    outline-offset: 2px;
    border-radius: 3px;
}

@media (max-width: 640px) {
    .ds-rec { grid-template-columns: 62px 1fr auto; gap: 8px; }
}
