/* ============================================================
   Better Pet Manager — Brand & App Styles
   Primary: #0ABAB5 (Tiffany Blue) · accent #8A3FA0 (purple)
   ============================================================ */

/* ────────────────────────────────────────────────────────────────────────────
   Inter — self-hosted brand UI typeface (SIL OFL). woff2 only, latin subset,
   weights 300/400/500/600/700 (every weight the app actually uses). Files live in
   this RCL's wwwroot/fonts, so they serve under _content/DogTiredApp.UI/fonts for
   BOTH the web host and the MAUI device app (same path app.css itself loads from).
   font-display: swap → text paints immediately in the fallback, then swaps to Inter
   (no invisible-text flash, and the app still works fully offline once cached).
   ──────────────────────────────────────────────────────────────────────────── */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 300; font-display: swap; src: url('fonts/inter-latin-300-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/inter-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('fonts/inter-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('fonts/inter-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url('fonts/inter-latin-700-normal.woff2') format('woff2'); }
/* ────────────────────────────────────────────────────────────────────────────
   THEME TOKENS — the single source of truth for color.
   :root = LIGHT defaults. The dark overrides live in :root[data-theme="dark"] below.
   Rules everywhere should reference var(--dt-*) — never a raw hex — so light/dark
   (and any future colorway) is "change the token, applies across the board."
   Brand hue tokens (--dt-primary*, --dt-secondary) are set PER-TENANT by
   BrandingService and intentionally persist across light/dark.
   ──────────────────────────────────────────────────────────────────────────── */
:root {
    /* ── Brand (per-tenant; same in light & dark) ── */
    --dt-primary:       #0ABAB5;   /* Tiffany Blue — the brand's primary/default. Bright, so text ON it
                                      must be dark (see --dt-on-primary), not white. */
    --dt-primary-dark:  #0F766E;   /* deeper teal — emphasis TEXT on light + button active fill */
    --dt-primary-hover: #2DC6C0;   /* brighter Tiffany for hover fills (dark on-primary stays legible) */
    --dt-primary-light: #E6FAF8;   /* soft teal tint — injected as a FIXED light hue per-tenant by
                                       BrandingService (onto .app-shell). Do NOT use as a fill behind
                                       theme-adaptive text: it stays light in dark → light-on-light
                                       washout. For a text-bearing brand tint, inline
                                         color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface))
                                       AT THE USE SITE (a descendant of .app-shell): --dt-primary then
                                       resolves to the tenant's injected brand and --dt-surface to the
                                       theme, so it's a pale tint in light and a dark tint in dark, with
                                       --dt-text legible on top either way. It CANNOT be a :root token —
                                       --dt-primary is injected below :root, so a :root-level color-mix
                                       would bake in the default teal for every tenant. */
    --dt-secondary:     #8A3FA0;   /* purple accent (brand heritage) */
    --dt-on-secondary:  #FFFFFF;   /* legible ink ON the secondary fill; auto-flipped per tenant by BrandCss */
    /* Legible text/icon color to sit ON the primary color (auto-flipped per tenant by BrandingService). */
    --dt-on-primary:    #1F2937;   /* dark ink — legible on bright Tiffany (#0ABAB5). White fails (~2.4:1). */

    /* Brand background gradient — radial aqua glow → Tiffany edge, matching
       assets/Gradient_Example.png. Single source of truth for the branded backdrop:
       the MAUI boot splash (index.html), the MAUI Login page, and any brand surface.
       Fixed brand hues (not the neutral tokens), so it stays on-brand in light & dark. */
    --dt-brand-gradient: radial-gradient(circle at 50% 42%, #9DFEFB 0%, #40D2CC 46%, #0ABAB5 100%);

    /* ── Sidebar (the brand's primary teal in BOTH themes; dark ink for legibility on the
          bright fill — same white-on-dark/black-on-light rule BrandCss uses per-tenant). ── */
    --dt-sidebar-bg:    #0ABAB5;
    --dt-sidebar-text:  rgba(0,0,0,0.82);
    --dt-sidebar-muted: rgba(0,0,0,0.48);
    --dt-sidebar-hover: rgba(0,0,0,0.06);
    --dt-sidebar-active:rgba(0,0,0,0.12);
    /* Mobile nav (bottom tab bar + slide-out drawer). Defaults to the sidebar tokens so the mobile
       nav matches the sidebar; a tenant can override --dt-mobilenav-* (Config → Branding, feature #13)
       to give mobile its own color independent of the desktop sidebar. */
    --dt-mobilenav-bg:    var(--dt-sidebar-bg);
    --dt-mobilenav-text:  var(--dt-sidebar-text);
    --dt-mobilenav-muted: var(--dt-sidebar-muted);
    --dt-mobilenav-hover: var(--dt-sidebar-hover);
    --dt-mobilenav-active:var(--dt-sidebar-active);
    --dt-header-bg:     #0ABAB5;

    /* ── Neutral surfaces & text (these are what flip in dark mode) ── */
    --dt-bg:            #F9FAFB;   /* app/page background (behind cards) */
    --dt-surface:       #FFFFFF;   /* cards, panels, list containers, "paper" */
    --dt-surface-2:     #F3F4F6;   /* subtle raised/hover fill, muted zebra */
    --dt-text:          #1F2937;   /* primary text */
    --dt-muted:         #6B7280;   /* secondary text / labels */
    --dt-faint:         #9CA3AF;   /* tertiary text, placeholder icons, chevrons */
    --dt-border:        #E5E7EB;   /* hairline borders */
    --dt-border-strong: #D1D5DB;   /* heavier borders (dashed dropzones, toggle cards) */

    /* ── Semantic (custom classes; Bootstrap's own success/danger follow data-bs-theme) ── */
    --dt-success:       #15803D;
    --dt-success-bg:    #F0FDF4;
    --dt-danger:        #B91C1C;
    --dt-danger-bg:     #FEF2F2;
    --dt-warning:       #C2410C;   /* orange — "in progress" / vet-recommend accent */
    --dt-warning-bg:    #FFF7ED;
    --dt-star:          #F0A500;   /* rating gold (filled stars) */
    --dt-neutral-bg:    #F3F4F6;   /* cancelled/neutral chip fill */
    --dt-neutral-fg:    #4B5563;

    /* THE RADIUS STANDARD (Apple-style: one machined corner everywhere).
       Every element that draws a border/background as a RECTANGLE — cards, list items,
       buttons, inputs, thumbnails, message bubbles, calendar chips, pickers, menus, nav
       items — MUST use `border-radius: var(--dt-radius)`. Never write a raw px/rem radius.
       ONLY exceptions (stay as-is): circles/avatars (50%), count/status pills (round by
       design), deliberate square corners (0), and concentric insets `calc(var(--dt-radius) - N)`. */
    --dt-radius:        8px;

    /* Calendar hover-tooltip colors. Pinned to a deep teal with light ink so tooltips stay a
       legible dark bubble — decoupled from the sidebar now that the sidebar is a bright fill
       (a bright-teal tooltip would wash out). A tenant sidebar override no longer bleeds in. */
    --dt-tooltip-bg:    #0C4A47;
    --dt-tooltip-text:  rgba(255,255,255,0.90);
    --dt-tooltip-muted: rgba(255,255,255,0.55);

    /* Bridge Bootstrap's primary to our brand purple so text-primary / bg-primary(-subtle) /
       border-primary and role pills follow the theme. BrandingService overrides these per tenant. */
    --bs-primary:              var(--dt-primary);
    --bs-primary-rgb:          10,186,181;
    --bs-primary-text-emphasis: var(--dt-primary-dark);
    --bs-primary-bg-subtle:    var(--dt-primary-light);
    --bs-primary-border-subtle: var(--dt-primary);
}

/* ────────────────────────────────────────────────────────────────────────────
   DARK THEME — override ONLY the neutral/surface/semantic tokens. Brand hue
   (--dt-primary*, --dt-secondary) and the already-dark sidebar are left alone,
   so a tenant's brand color still reads in dark mode. Applied when the theme
   script stamps data-theme="dark" on <html>; Bootstrap's own components come
   along via the matching data-bs-theme="dark". Colorway fine-tuning is a
   deliberate later pass — these are sensible, legible defaults.
   ──────────────────────────────────────────────────────────────────────────── */
:root[data-theme="dark"] {
    --dt-bg:            #0F1115;
    --dt-surface:       #1A1D24;
    --dt-surface-2:     #23272F;
    --dt-text:          #E6E8EC;
    --dt-muted:         #9AA3AF;
    --dt-faint:         #6B7280;
    --dt-border:        #2E333C;
    --dt-border-strong: #3A414C;

    /* Soft brand tint for selection/active fills — a dark, desaturated teal so text stays legible. */
    --dt-primary-light: #10312E;

    /* Semantic subtle backgrounds go deep-tinted; the foreground hues stay recognizable. */
    --dt-success:       #4ADE80;
    --dt-success-bg:    #0E2A19;
    --dt-danger:        #F87171;
    --dt-danger-bg:     #2C1416;
    --dt-warning:       #FB923C;
    --dt-warning-bg:    #2A1A0E;
    --dt-star:          #FBBF24;   /* rating gold — a touch brighter on the dark surface */
    --dt-neutral-bg:    #23272F;
    --dt-neutral-fg:    #9AA3AF;
}

/* Tenant surface guard (dark mode): a tenant's ThemeJson neutral colors (--dt-bg/--dt-text/--dt-muted/
   --dt-border) are applied as an INLINE style on .app-shell, whose specificity beats the :root[data-theme]
   dark block above — so a tenant's LIGHT surface customization would leak into dark mode and wreck contrast.
   Re-assert the dark neutral palette ON .app-shell (targets the same element, with !important, so it wins
   over the inline style). Brand HUE (--dt-primary*, --dt-secondary) and the tenant nav bars are intentionally
   left alone — a brand color is its color in both themes; only the neutral surfaces flip. */
:root[data-theme="dark"] .app-shell {
    --dt-bg:      #0F1115 !important;
    --dt-surface: #1A1D24 !important;
    --dt-surface-2: #23272F !important;
    --dt-text:    #E6E8EC !important;
    --dt-muted:   #9AA3AF !important;
    --dt-border:  #2E333C !important;
}
/* Client portal counterpart to the .app-shell guard above. .portal-shell ALSO carries the serving
   business's neutral branding vars as an INLINE style (PortalLayout: style="@_brandStyle"), so a tenant
   with LIGHT surface colors pins the portal background/text light even in dark mode — while Bootstrap's
   own components (.card, headings) still follow data-bs-theme="dark". The result is a light page with
   dark cards and washed-out headings (S149). Re-assert the dark neutral palette on .portal-shell, same
   !important-beats-inline reasoning; inert in light mode. */
:root[data-theme="dark"] .portal-shell {
    --dt-bg:      #0F1115 !important;
    --dt-surface: #1A1D24 !important;
    --dt-surface-2: #23272F !important;
    --dt-text:    #E6E8EC !important;
    --dt-muted:   #9AA3AF !important;
    --dt-border:  #2E333C !important;
}
/* Native device counterpart: there is no .app-shell — the tenant's neutral ThemeJson vars are
   stamped as an INLINE style on the document ROOT (window.dtBrand.apply → root.style.setProperty), which
   the plain :root[data-theme] block above cannot beat. Re-assert the dark neutrals on :root with
   !important so they win over the inline root vars too. Scoped to dark mode, so it's inert in light. */
:root[data-theme="dark"] {
    --dt-bg:      #0F1115 !important;
    --dt-surface: #1A1D24 !important;
    --dt-surface-2: #23272F !important;
    --dt-text:    #E6E8EC !important;
    --dt-muted:   #9AA3AF !important;
    --dt-border:  #2E333C !important;
}

/* ============================================================
   Dark-mode bridges — two systemic fixes that the token system alone
   can't reach. Both are keyed off the theme attribute stamped on <html>,
   so they are inert in light mode (the app's default pin).

   (1) BOOTSTRAP FIXED-CONTEXTUAL CLASSES. .table-light / .bg-light /
   .bg-white / .text-dark are Bootstrap "always this color" utilities —
   unlike the base .table / body they do NOT follow data-bs-theme, so in
   dark mode they render as white bands and light patches (thead headers,
   badge chips, subtle panels). We can't retint them per-element without
   touching 30+ razor files, so we re-point them at our dark surface tokens
   here, once. (.table-light drives Bootstrap's --bs-table-* cell vars.)

   (2) BRAND-TINT SURFACES. --dt-primary-light is injected inline per-tenant by
   BrandingService as a PALE tint of the brand that stays LIGHT in BOTH themes
   (brand persists across light/dark). Using it as a fill behind --dt-text looked
   fine in light (dark ink on pale) but washed out in dark (near-white on pale).
   Fixed at the source: text-bearing selection/active/chip fills now inline
   color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface)) at the use site,
   which flips dark in dark mode so --dt-text stays legible — no per-surface
   color-pin needed. Rings/accents use color-mix(--dt-primary N%, transparent).
   (Inline, not a :root token — see the --dt-primary-light note above for why.)
   ============================================================ */

/* (1) Bootstrap fixed-contextual classes → dark surface tokens. */
:root[data-bs-theme="dark"] .bg-light  { background-color: var(--dt-surface-2) !important; }
:root[data-bs-theme="dark"] .bg-white  { background-color: var(--dt-surface)   !important; }
:root[data-bs-theme="dark"] .text-dark { color: var(--dt-text) !important; }
/* …but a SOLID .bg-warning chip (allergy "Moderate", feeding "Not Fed") or .bg-info chip (the provider
   console's "Trial" status badge) stays bright in both themes, so the remap above would leave near-white
   text on yellow/cyan — unreadable. Pin dark ink on the badge specifically (higher specificity than
   .text-dark, so it wins). #1F2937 = the light-mode --dt-text ink; there is no always-dark token. */
:root[data-bs-theme="dark"] .badge.bg-warning.text-dark,
:root[data-bs-theme="dark"] .badge.bg-info.text-dark { color: #1F2937 !important; }
:root[data-bs-theme="dark"] .table-light {
    --bs-table-color:        var(--dt-text);
    --bs-table-bg:           var(--dt-surface-2);
    --bs-table-border-color: var(--dt-border);
    --bs-table-striped-color: var(--dt-text);
    --bs-table-striped-bg:    var(--dt-surface);
    --bs-table-active-color:  var(--dt-text);
    --bs-table-active-bg:     var(--dt-surface-2);
    --bs-table-hover-color:   var(--dt-text);
    --bs-table-hover-bg:      var(--dt-surface-2);
    color: var(--bs-table-color);
    border-color: var(--bs-table-border-color);
}

/* (2) Brand-tint surfaces — RESOLVED by inlining color-mix(var(--dt-primary) 12%, var(--dt-surface))
   at each use site, so these fills are already dark in dark mode with --dt-text legible on top.
   The old dark-mode color-pins (color: --dt-primary) and drop-zone bg swaps that this block used
   to carry are no longer needed and were removed; see the --dt-primary-light note up top. */

/* ============================================================
   Base
   ============================================================ */
html, body {
    font-family: 'Inter', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    height: 100%;
    margin: 0;
    background: var(--dt-bg);
    color: var(--dt-text);
}

/* ============================================================
   App shell — sidebar + main
   ============================================================ */
.app-shell {
    display: flex;
    height: 100vh;
    overflow: hidden;
}

/* ── Sidebar ── */
.app-sidebar {
    width: 240px;
    flex-shrink: 0;
    background: var(--dt-sidebar-bg);
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    transition: width 0.2s ease, transform 0.2s ease;
    z-index: 200;
}

.sidebar-brand {
    position: relative;   /* anchor for the collapse toggle pinned in the top-right corner */
    /* Horizontal padding matches the search box's 0.75rem margins so the logo box lines up edge-to-edge
       with the search field and nav items below it. */
    padding: 2.9rem 0.75rem 1.1rem;   /* top room for the chevron; brand (logo/name) sits below it */
    border-bottom: 1px solid var(--dt-sidebar-active);
    display: flex;
    align-items: center;
    /* Center the logo in the column. The collapse chevron is pulled out of the flex
       flow (absolute, right edge) so it doesn't shove the logo off-center. */
    justify-content: center;
    gap: 0.5rem;
    min-height: 64px;
    /* Never let the flex column compress the logo area — otherwise the tall logo
       overflows down onto the first nav item. (The footer does the same.) */
    flex-shrink: 0;
}

.sidebar-brand-id {
    display: flex;
    flex-direction: column;
    align-items: center;   /* center the logo + any org name/wordmark under it */
    gap: 0.3rem;
    min-width: 0;
    width: 100%;   /* fill the brand box so the logo's max-width:100% resolves to the search-box width */
}

.sidebar-logo {
    width: 120px;
    height: auto;
    /* Logo is purple on white — invert to white on dark sidebar */
    filter: brightness(0) invert(1);
}

/* Platform default mark (Better Pet Manager pitty) shown when a tenant has no uploaded logo.
   A full-color image, so NO invert; sized as a rounded app-style icon. Stays visible (smaller)
   when the sidebar is collapsed so the rail still carries the brand. */
.sidebar-logo-pitty {
    /* Same image box as an uploaded logo: spans the search-box width, caps at 112px tall, aspect
       preserved. A square/rectangular mark like the pitty maxes out at the 112px height. */
    max-width: 100%;
    max-height: 112px;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 14px;
}

.sidebar-collapsed .sidebar-logo-pitty {
    width: 44px;
    height: 44px;
}

/* Per-tenant org name shown under the logo (only when customized away from the default) */
.sidebar-brand-name {
    color: var(--dt-sidebar-text);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    /* Full column width now (chevron is in the top-right corner) — wrap instead of truncate. */
    overflow-wrap: anywhere;
}

.sidebar-collapsed .sidebar-brand-name {
    display: none;
}

/* Per-tenant uploaded logo in the sidebar (not inverted — keep its real colors) */
.sidebar-logo-custom {
    max-width: 100%;   /* span the search-box width (brand box now matches the search margins) */
    max-height: 112px;
    width: auto;
    height: auto;
    object-fit: contain;
}

.sidebar-collapsed .sidebar-logo-custom {
    display: none;
}

/* Text wordmark fallback for a customized org with no uploaded logo */
.sidebar-wordmark {
    color: #fff;
    font-weight: 700;
    font-size: 1.05rem;
    line-height: 1.2;
    text-align: center;
    /* The chevron now lives in the top-right corner, so the name has the full column width and
       wraps to a second line if it's long — no truncation. */
    overflow-wrap: anywhere;
}

.sidebar-collapsed .sidebar-wordmark {
    display: none;
}

/* ── Logo uploader (Configuration → Branding) ───────────────────────── */
.dt-logo-uploader {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.dt-logo-preview {
    /* Preview on the real sidebar color so light/white logos are visible as they'll appear */
    background: var(--dt-sidebar-bg, #2D1B69);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    min-height: 92px;
    padding: 0.75rem 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

.dt-logo-preview img {
    max-width: 240px;
    max-height: 72px;
    width: auto;
    height: auto;
    object-fit: contain;
}

.dt-logo-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    color: rgba(255,255,255,0.5);
    font-size: 0.85rem;
}

.dt-logo-placeholder i {
    font-size: 1.5rem;
}

.dt-logo-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

/* ── Messaging (/messages) ──────────────────────────────────────────── */
.msg-shell {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 1rem;
    height: calc(100vh - 230px);
    min-height: 420px;
}

.msg-list {
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    padding: 0.6rem;
    overflow-y: auto;
    background: var(--dt-surface);
}

.msg-list-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    padding: 0.5rem;
    border-radius: var(--dt-radius);
    cursor: pointer;
}

.msg-list-item:hover { background: var(--dt-bg); }
.msg-list-item.active { background: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface)); }

.msg-list-meta { display: flex; flex-direction: column; min-width: 0; }
.msg-list-name { font-weight: 600; font-size: 0.9rem; }
.msg-list-preview {
    font-size: 0.8rem; color: var(--dt-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 210px;
}

/* Unread conversation: bolder name + darker preview, with a dot on the right. */
.msg-list-item.unread .msg-list-name { font-weight: 700; color: var(--dt-text); }
.msg-list-item.unread .msg-list-preview { color: var(--dt-text); font-weight: 600; }
.msg-unread-dot {
    margin-left: auto;
    flex: none;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--dt-primary);
}

/* Unread count pill on a conversation row */
.msg-unread-count {
    margin-left: auto;
    flex: none;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--dt-primary);
    color: var(--dt-on-primary);
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 20px;
    text-align: center;
}

/* "Log a client reply" inline input */
.msg-log-reply {
    padding: 0.5rem 0.75rem;
    background: var(--dt-bg);
    border-bottom: 1px solid var(--dt-border);
}

.msg-main {
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    background: var(--dt-surface);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.msg-empty {
    margin: auto;
    text-align: center;
    color: var(--dt-muted);
}
.msg-empty i { font-size: 2.5rem; display: block; margin-bottom: 0.5rem; opacity: 0.5; }

.msg-thread-header {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--dt-border);
    display: flex;
    align-items: center;
}

.msg-thread {
    flex: 1;
    overflow-y: auto;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.msg-bubble-row { display: flex; }
.msg-bubble-row.mine { justify-content: flex-end; }

.msg-bubble {
    max-width: 70%;
    background: var(--dt-bg);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    padding: 0.5rem 0.75rem;
}
.msg-bubble-row.mine .msg-bubble {
    background: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface));
    border-color: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface));
}

.msg-sender { font-size: 0.75rem; font-weight: 600; color: var(--dt-primary); margin-bottom: 0.15rem; }
.msg-body { white-space: pre-wrap; word-break: break-word; }
.msg-time { font-size: 0.7rem; color: var(--dt-muted); margin-top: 0.2rem; text-align: right; }

.msg-attachments { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.4rem; }
.msg-attachment {
    max-width: 180px; max-height: 180px; border-radius: var(--dt-radius); object-fit: cover;
}

.msg-composer {
    display: flex;
    gap: 0.5rem;
    padding: 0.6rem;
    border-top: 1px solid var(--dt-border);
    align-items: flex-end;
}
.msg-composer textarea { resize: none; }

.msg-new { padding: 1rem; overflow-y: auto; }
.msg-picker-list {
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    max-height: 320px;
    overflow-y: auto;
}
.msg-picker-item {
    display: flex; align-items: center; gap: 0.6rem;
    width: 100%; text-align: left; border: none; background: transparent;
    padding: 0.5rem 0.6rem; cursor: pointer; border-bottom: 1px solid var(--dt-border);
}
.msg-picker-item:last-child { border-bottom: none; }
.msg-picker-item:hover { background: var(--dt-bg); }

.msg-attach-picker { border-top: 1px solid var(--dt-border); padding: 0.6rem; background: var(--dt-bg); }
.msg-attach-grid { display: flex; flex-wrap: wrap; gap: 0.4rem; max-height: 180px; overflow-y: auto; }
.msg-attach-thumb {
    position: relative; width: 72px; height: 72px; border-radius: var(--dt-radius); overflow: hidden;
    border: 2px solid transparent; padding: 0; cursor: pointer; background: #000;
}
.msg-attach-thumb.sel { border-color: var(--dt-primary); }
.msg-attach-thumb img { width: 100%; height: 100%; object-fit: cover; }
.msg-attach-video { color: #fff; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.msg-attach-check {
    position: absolute; top: 2px; right: 2px; background: var(--dt-primary); color: var(--dt-on-primary);
    border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 0.7rem;
}

/* Clickable message attachment thumbnails */
.msg-attachment-btn {
    position: relative; display: inline-block; padding: 0; border: none; background: none;
    cursor: pointer; line-height: 0; border-radius: var(--dt-radius); overflow: hidden;
}
.msg-attachment-btn .msg-attachment { transition: transform 0.12s; }
.msg-attachment-btn:hover .msg-attachment { transform: scale(1.02); }
.msg-attachment-play {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: 2rem; text-shadow: 0 1px 6px rgba(0,0,0,0.6); pointer-events: none;
}

/* Full-size media lightbox */
.msg-lightbox {
    position: fixed; inset: 0; z-index: 1100; background: rgba(0,0,0,0.82);
    display: flex; align-items: center; justify-content: center; padding: 2rem;
}
.msg-lightbox-inner {
    display: flex; flex-direction: column; align-items: center; gap: 0.75rem; max-width: 100%; max-height: 100%;
}
.msg-lightbox-media {
    max-width: 90vw; max-height: 80vh; border-radius: var(--dt-radius);
    box-shadow: 0 10px 40px rgba(0,0,0,0.5); background: #000;
}
.msg-lightbox-actions { display: flex; gap: 0.5rem; }

/* Pending device-upload previews (above the composer) */
.msg-pending-uploads { display: flex; flex-wrap: wrap; gap: 0.4rem; padding: 0.4rem 0.2rem 0; }
.msg-pending-thumb {
    position: relative; width: 64px; height: 64px; border-radius: var(--dt-radius); overflow: hidden;
    background: #000; border: 1px solid var(--bs-border-color, #dee2e6);
}
.msg-pending-thumb img { width: 100%; height: 100%; object-fit: cover; }
.msg-pending-remove {
    position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; padding: 0;
    border: none; border-radius: 50%; background: rgba(0,0,0,0.65); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 0.75rem; cursor: pointer;
}
.msg-pending-remove:hover { background: var(--dt-danger); }

@media (max-width: 991.98px) {
    .msg-shell { grid-template-columns: 1fr; height: auto; }
    .msg-list { max-height: 240px; }
    .msg-main { min-height: 420px; }
}

/* Unread count badge on a sidebar nav item (iPhone-style red pill) */
.sidebar-nav .nav-link .nav-badge {
    margin-left: auto;
    background: var(--dt-danger);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
/* When the sidebar is collapsed to a rail, float the badge over the icon. */
.sidebar-collapsed .sidebar-nav .nav-link .nav-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    margin-left: 0;
}

/* Collapse / expand toggle (desktop) */
.sidebar-toggle {
    position: absolute;
    top: 0.85rem;
    right: 1rem;
    background: var(--dt-sidebar-hover);
    border: none;
    color: var(--dt-sidebar-text);
    width: 30px;
    height: 30px;
    border-radius: var(--dt-radius);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 0.15s, transform 0.2s ease;
}

.sidebar-toggle:hover {
    background: var(--dt-sidebar-active);
}

.sidebar-toggle i {
    transition: transform 0.2s ease;
    font-size: 1.05rem;
    line-height: 1;
}

.sidebar-nav {
    flex: 1;
    padding: 0.5rem 0;
}

/* Section headers */
.nav-section-label {
    padding: 1rem 1rem 0.25rem;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--dt-sidebar-muted);
}

/* Nav links */
.sidebar-nav .nav-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.45rem 1rem;
    margin: 1px 0.5rem;
    border-radius: var(--dt-radius);
    font-size: 0.875rem;
    color: var(--dt-sidebar-text);
    text-decoration: none;
    transition: background 0.15s;
}

.sidebar-nav .nav-link:hover {
    background: var(--dt-sidebar-hover);
    color: var(--dt-sidebar-text);
}

.sidebar-nav .nav-link.active {
    background: var(--dt-sidebar-active);
    color: var(--dt-sidebar-text);
    font-weight: 600;
}

.sidebar-nav .nav-link i {
    width: 1.1rem;
    font-size: 1rem;
    text-align: center;
    flex-shrink: 0;
}

/* ── Main area ── */
.app-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
}

/* ── Sidebar footer: account + sign out, pinned to bottom ── */
.sidebar-footer {
    border-top: 1px solid var(--dt-sidebar-active);
    padding: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex-shrink: 0;
}

.sidebar-account {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem;
    border-radius: var(--dt-radius);
    color: var(--dt-sidebar-text);
    text-decoration: none;
    transition: background 0.15s;
}

.sidebar-account:hover {
    background: var(--dt-sidebar-hover);
}

/* Static identity block (S159) — Account settings now lives in the Config takeover, so the footer no
   longer links out. Not interactive: no hover affordance, default cursor. */
.sidebar-account-static {
    cursor: default;
}
.sidebar-account-static:hover {
    background: transparent;
}

.account-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--dt-primary-hover);
    color: var(--dt-on-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.9rem;
    flex-shrink: 0;
}

.account-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.account-name {
    font-size: 0.8rem;
    color: var(--dt-sidebar-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.account-link-label {
    font-size: 0.7rem;
    color: var(--dt-sidebar-muted);
}

.sidebar-signout {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem;
    border-radius: var(--dt-radius);
    color: var(--dt-sidebar-text);
    text-decoration: none;
    font-size: 0.85rem;
    transition: background 0.15s;
}

.sidebar-signout:hover {
    background: var(--dt-sidebar-hover);
    color: var(--dt-sidebar-text);
}

.sidebar-signout i {
    width: 34px;
    text-align: center;
    font-size: 1rem;
    flex-shrink: 0;
}

/* Theme toggle reuses the sign-out row styling but is a <button>, so reset its chrome. */
.dt-theme-toggle {
    background: none;
    border: none;
    width: 100%;
    font: inherit;
    cursor: pointer;
}

/* ── Floating menu button (mobile only) ── */
.mobile-menu-btn {
    display: none;
    position: fixed;
    top: 0.75rem;
    left: 0.75rem;
    z-index: 160;
    width: 44px;
    height: 44px;
    border-radius: var(--dt-radius);
    border: none;
    background: var(--dt-primary);
    color: var(--dt-on-primary);
    font-size: 1.4rem;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}

/* ── Mobile brand bar + bottom tab bar (shown only ≤767px, below) ── */
.mobile-topbar { display: none; }
.mobile-tabbar { display: none; }

/* ── Collapsed rail (desktop) ── */
.app-shell.sidebar-collapsed .app-sidebar {
    width: 76px;
}

.sidebar-collapsed .sidebar-brand {
    justify-content: center;
    padding: 0.85rem 0;
}

/* In the narrow rail the logo is hidden, so the toggle is the only thing left —
   put it back in the centered flow instead of pinned to the right edge. */
.sidebar-collapsed .sidebar-toggle {
    position: static;
    transform: none;
}

.sidebar-collapsed .sidebar-logo {
    display: none;
}

.sidebar-collapsed .sidebar-toggle i {
    transform: rotate(180deg);
}

.sidebar-collapsed .nav-label,
.sidebar-collapsed .nav-section-label,
.sidebar-collapsed .account-meta {
    display: none;
}

.sidebar-collapsed .sidebar-nav .nav-link {
    justify-content: center;
    gap: 0;
    padding: 0.5rem 0;
    margin: 1px 0.5rem;
}

.sidebar-collapsed .sidebar-account,
.sidebar-collapsed .sidebar-signout {
    justify-content: center;
    padding: 0.5rem 0;
}

.sidebar-collapsed .sidebar-signout i {
    width: auto;
}

/* ── Content ── */
.app-content {
    flex: 1;
    overflow-y: auto;
    padding: 1.5rem;
}

/* ── Desktop content-width system (S98 desktop audit §1) ──────────────────────
   `.app-content` is intentionally edge-to-edge (data-dense tables/calendar want
   the room). These OPT-IN wrappers cap a page's readable / form / single-panel
   width so mobile-first layouts stop sprawling across a 1600px desktop. They use
   `max-width`, which is a NO-OP whenever the viewport is already narrower than the
   cap — so on phones and tablet-portrait they do nothing and can't hurt mobile.
   Model: the client portal's `.portal-content` (960px) has always done this.
   Apply by wrapping a page's body: <div class="dt-readable">…</div>. */
.dt-readable { max-width: 1160px; margin-inline: auto; }   /* detail / hub / report reading width */
.dt-form     { max-width: 820px;  margin-inline: auto; }   /* editors, wizards, the report card    */
.dt-panel    { max-width: 480px;  margin-inline: auto; }   /* single-panel screens (My Reviews, Walk) */

/* `.setup-wizard` is used by 4 wizards (AnimalWizard/ClientWizard/SetupWizard/
   LeadEdit) but was defined in NO css — so those wizards rendered full-bleed.
   Give it the capped, centered column its name implies (also self-gating). */
.setup-wizard { max-width: 760px; margin-inline: auto; }

/* ── CRM-style card grid (S98 desktop rework) ─────────────────────────────────
   Lay a page's section cards TWO PER ROW at full width, reflowing to one column
   on narrow screens (phones/tablet-portrait). Robert's call: use the width with
   columns, not a narrow cap. Wrap a run of `.card`s in <div class="dt-cardgrid">.
   `align-items:start` so a short card (e.g. Photo) doesn't stretch to match a
   tall neighbor; `minmax(0,1fr)` stops long content from blowing the track. */
.dt-cardgrid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
    align-items: start;
    margin-bottom: 1.5rem;
}
.dt-cardgrid > .card,
.dt-cardgrid .card { margin-bottom: 0 !important; }   /* the grid gap owns vertical spacing now */
@media (max-width: 767.98px) {
    .dt-cardgrid { grid-template-columns: 1fr; gap: 1rem; }   /* stack on mobile — unchanged phone layout */
}

/* Editor rail layout (S98): a narrow identity/summary rail (left) + a wider detail column (right),
   reflowing to a single column on narrow screens. Columns are flex stacks so their cards space evenly. */
.dt-editgrid { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: 1.25rem; align-items: start; margin-bottom: 1.5rem; }
.dt-editcol { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }
.dt-editcol > .card { margin-bottom: 0 !important; }
/* Balanced variant: two equal columns (for editors whose "identity" card is field-heavy and
   doesn't suit a narrow rail — e.g. Edit Animal). Still one column on mobile. */
.dt-editgrid--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 767.98px) { .dt-editgrid, .dt-editgrid--even { grid-template-columns: 1fr; } }

/* ── Mobile sidebar overlay ── */
.sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.4);
    z-index: 150;
}

/* Explicit drawer-close controls are mobile-only (desktop uses the collapse chevron). Shown in the
   mobile media query below. */
.sidebar-close,
.sidebar-close-bottom {
    display: none;
}

@media (max-width: 991.98px) {
    /* The nav drawer slides in from the RIGHT (opened by the bottom-bar Menu button). */
    .app-sidebar {
        position: fixed;
        top: 0;
        left: auto;
        right: 0;
        /* Full-height drawer. While it's open, the bottom tab bar is slid out of the way (see
           `.sidebar-open .mobile-tabbar` below), so the drawer owns the whole viewport with nothing to
           collide with — far more robust than coordinating a fixed drawer height against the tab bar,
           which clipped the last items in the iOS in-app browser (unreliable dvh). dvh tracks the
           visible viewport; the bottom padding keeps the last row (Sign out) clear of the home indicator. */
        height: 100vh;            /* fallback: older browsers */
        height: 100dvh;
        padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.75rem);
        transform: translateX(100%);
        /* Feature #13: on mobile the drawer adopts the mobile-nav color (defaults to the sidebar's own
           color, so no change unless the tenant set a separate mobile color) by re-pointing the
           sidebar tokens the drawer's own styles consume at the --dt-mobilenav-* set. */
        --dt-sidebar-bg:     var(--dt-mobilenav-bg);
        --dt-sidebar-text:   var(--dt-mobilenav-text);
        --dt-sidebar-muted:  var(--dt-mobilenav-muted);
        --dt-sidebar-hover:  var(--dt-mobilenav-hover);
        --dt-sidebar-active: var(--dt-mobilenav-active);
    }

    .app-shell.sidebar-open .app-sidebar {
        transform: translateX(0);
        box-shadow: -4px 0 24px rgba(0,0,0,0.35);
    }

    .app-shell.sidebar-open .sidebar-overlay {
        display: block;
    }

    /* While the drawer is open, slide the (now-redundant — it contains the same destinations) bottom
       tab bar out of view so the full-height drawer has nothing overlapping its last items. It slides
       back in when the drawer closes. */
    .app-shell.sidebar-open .mobile-tabbar {
        transform: translateY(110%);
    }

    /* Give the logo/wordmark extra top room so it sits clearly BELOW the top-corner close (X) button
       (the X spans ~0.6rem–3.35rem; 2.9rem padding let the centered wordmark ride up beside it). */
    .sidebar-brand {
        padding-top: 4rem;
    }

    /* Top-corner close (X): always visible at the top of the open drawer — a large, obvious tap target
       so users never have to discover that tapping outside closes the menu. */
    .sidebar-close {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        top: 0.6rem;
        right: 0.6rem;
        width: 44px;
        height: 44px;
        flex: 0 0 auto;
        border: none;
        border-radius: var(--dt-radius);
        background: rgba(255, 255, 255, 0.14);
        color: var(--dt-sidebar-text);
        font-size: 1.35rem;
        line-height: 1;
        cursor: pointer;
    }
    .sidebar-close:active {
        background: rgba(255, 255, 255, 0.26);
    }

    /* A second, clearly-labelled close at the BOTTOM of the drawer for users who scroll down. */
    .sidebar-close-bottom {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        flex: 0 0 auto;
        margin: 0.4rem 1rem 0.25rem;
        padding: 0.75rem 1rem;
        min-height: 44px;
        border: 1px solid rgba(255, 255, 255, 0.22);
        border-radius: var(--dt-radius);
        background: rgba(255, 255, 255, 0.08);
        color: var(--dt-sidebar-text);
        font-size: 0.95rem;
        font-weight: 600;
        cursor: pointer;
    }
    .sidebar-close-bottom:active {
        background: rgba(255, 255, 255, 0.18);
    }

    /* On mobile the sidebar is a full off-canvas drawer — never the icon rail. */
    .app-shell.sidebar-collapsed .app-sidebar {
        width: 240px;
    }

    .sidebar-collapsed .nav-label {
        display: inline;
    }

    .sidebar-collapsed .nav-section-label {
        display: block;
    }

    .sidebar-collapsed .account-meta {
        display: flex;
    }

    .sidebar-collapsed .sidebar-nav .nav-link,
    .sidebar-collapsed .sidebar-account,
    .sidebar-collapsed .sidebar-signout {
        justify-content: flex-start;
        gap: 0.625rem;
    }

    .sidebar-collapsed .sidebar-logo {
        display: block;
    }

    /* Hide the desktop collapse chevron; show the floating open button. */
    .sidebar-toggle {
        display: none;
    }

    /* The floating hamburger is replaced by the bottom tab bar. */
    .mobile-menu-btn {
        display: none !important;
    }

    /* Thin, NON-sticky brand bar at the top of the scroll area — favicon + business name.
       Scrolls away with the page and doubles as a bit of breathing room before the content. */
    .mobile-topbar {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0.55rem 0.15rem 0.85rem;
        border-bottom: 1px solid var(--dt-border, #e5e7eb);
        margin-bottom: 1rem;
    }
    .mobile-topbar-icon {
        width: 28px;
        height: 28px;
        object-fit: contain;
        border-radius: var(--dt-radius);
        flex: 0 0 auto;
    }
    .mobile-topbar-name {
        font-weight: 700;
        color: var(--dt-primary);
        font-size: 1.05rem;
        line-height: 1.1;
    }
    /* Messages shortcut pinned to the right of the brand bar, with an unread badge. */
    .mobile-topbar-msg {
        margin-left: auto;
        position: relative;
        flex: 0 0 auto;
        color: var(--dt-primary);
        font-size: 1.35rem;
        line-height: 1;
        text-decoration: none;
        padding: 0.15rem 0.25rem;
    }

    .app-content {
        padding-top: 1rem;
        /* Clear the fixed bottom tab bar (icon + label + top border, taller than 64px) AND the
           iOS home indicator, with breathing room so action buttons never hug the bar. */
        padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px) + 1.25rem);
    }

    /* Bottom sticky tab bar: four equal-width destinations (Home / Calendar / Visits / Menu). */
    .mobile-tabbar {
        display: flex;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 160;
        transition: transform 0.25s ease;
        /* Feature #13: the bottom tab bar shares the mobile-nav color (--dt-mobilenav-*, which defaults
           to the sidebar color but can be set independently in Config → Branding) so the tray and the
           slide-out drawer read as ONE surface. Text/active tints are auto-contrast derivatives. */
        background: var(--dt-mobilenav-bg);
        border-top: 1px solid rgba(255, 255, 255, 0.12);
        box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }
    .mobile-tab {
        flex: 1 1 0;          /* equal widths — 25% each */
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        min-height: 60px;
        padding: 0.4rem 0;
        border: none;
        background: none;
        color: var(--dt-mobilenav-text);
        opacity: 0.85;
        font-size: 0.7rem;
        font-weight: 600;
        text-decoration: none;
        cursor: pointer;
        transition: opacity 0.15s ease, background-color 0.15s ease;
    }
    .mobile-tab i { font-size: 1.35rem; line-height: 1; }
    .mobile-tab.active { opacity: 1; color: var(--dt-mobilenav-text); background: var(--dt-mobilenav-active); }

    .btn-mobile-full {
        width: 100%;
        padding: 14px;
        font-size: 1.1rem;
    }

    /* Two buttons sit 50/50; a third (or a long-label button that can't fit) wraps to its own
       full-width line. Applies to any row marked `dt-actions`. */
    .dt-actions > .btn {
        flex: 1 1 140px;
        justify-content: center;
    }

    /* Stack section headers: title on its own line, the Add button full-width below it. */
    .dt-section-head {
        flex-direction: column;
        align-items: stretch;
    }
    .dt-section-head > .btn { justify-content: center; }
}


/* ============================================================
   Page content helpers
   ============================================================ */
.page-header {
    margin-bottom: 1.5rem;
}

.page-header h1 {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--dt-primary);
    margin: 0;
}

.page-header .page-subtitle {
    font-size: 0.875rem;
    color: var(--dt-muted);
    margin-top: 0.25rem;
}

/* Action button rows — inline/natural on desktop; on mobile (rule below in the media query)
   buttons wrap so two sit 50/50 side-by-side and anything that doesn't fit (a 3rd button or a
   long label) drops to its own full-width line. Swap `d-flex gap-2` → `dt-actions` on action rows. */
.dt-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

/* Section header (title + action button). A space-between row on desktop; on mobile (rule in the
   media query) it stacks so the "Add …" button drops to its own full-width line below the title
   instead of overlapping it. */
.dt-section-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
}

/* ============================================================
   Status chips
   ============================================================ */
.status-badge {
    display: inline-block;
    padding: 0.2em 0.6em;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

/* Wide tables → stacked cards on phones. Add `dt-table-cards` to a <table> and a `data-label` to
   each <td>; on narrow screens each row becomes a card (first cell = title, rest = label/value rows,
   last cell = actions). Avoids the cramped horizontal-scroll clipping of many-column config tables. */
@media (max-width: 767.98px) {
    .dt-table-cards thead { display: none; }
    .dt-table-cards, .dt-table-cards tbody, .dt-table-cards tr, .dt-table-cards td { display: block; width: 100%; }
    .dt-table-cards tr {
        border: 1px solid var(--dt-border, #e5e7eb);
        border-radius: var(--dt-radius, 8px);
        margin-bottom: 0.75rem;
        padding: 0.6rem 0.85rem;
        background: var(--dt-surface, #fff);
    }
    .dt-table-cards td {
        border: none;
        padding: 0.25rem 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 1rem;
        text-align: right;
    }
    .dt-table-cards td::before {
        content: attr(data-label);
        font-weight: 600;
        color: var(--dt-muted, #6b7280);
        text-align: left;
    }
    /* First cell is the card title (row name); last cell holds the action buttons. */
    .dt-table-cards td:first-child {
        display: block;
        text-align: left;
        font-weight: 700;
        border-bottom: 1px solid var(--dt-border, #e5e7eb);
        padding-bottom: 0.4rem;
        margin-bottom: 0.4rem;
    }
    .dt-table-cards td:first-child::before,
    .dt-table-cards td:last-child::before { content: ""; }
    .dt-table-cards td:last-child { justify-content: flex-end; padding-top: 0.5rem; }
    .dt-table-cards td:last-child .input-group { width: 100%; }   /* editable last cell (e.g. rate input) fills the card */

    /* Tab rows scroll horizontally instead of wrapping/clipping — no tab gets cut off at the edge. */
    .nav-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;   /* Firefox */
    }
    .nav-tabs::-webkit-scrollbar { display: none; }   /* WebKit */
    .nav-tabs .nav-link { white-space: nowrap; }

    /* Form controls (incl. inline filters like "Employee [▾]") are full-width on phones.
       !important overrides inline width:auto used to keep them compact on desktop. */
    .dt-filter > .form-select,
    .dt-filter > .form-control { width: 100% !important; }
}

.status-scheduled  { background: var(--dt-primary-light); color: var(--dt-primary); }
.status-inprogress { background: var(--dt-warning-bg); color: var(--dt-warning); }
.status-completed  { background: var(--dt-success-bg); color: var(--dt-success); }
.status-cancelled  { background: var(--dt-neutral-bg); color: var(--dt-neutral-fg); }
.status-missed     { background: var(--dt-danger-bg); color: var(--dt-danger); }

/* ============================================================
   Bootstrap override — primary color → brand purple
   ============================================================ */
.btn-primary {
    --bs-btn-bg:            var(--dt-primary);
    --bs-btn-border-color:  var(--dt-primary);
    --bs-btn-hover-bg:      var(--dt-primary-hover);
    --bs-btn-hover-border:  var(--dt-primary-hover);
    --bs-btn-active-bg:     var(--dt-primary-dark);
    --bs-btn-active-border: var(--dt-primary-dark);
    --bs-btn-color:         var(--dt-on-primary);
    --bs-btn-hover-color:   var(--dt-on-primary);
    --bs-btn-active-color:  var(--dt-on-primary);
    /* Bootstrap leaves --bs-btn-disabled-bg at its literal blue, so a disabled primary button rendered
       BLUE (e.g. a "Save" gated until dirty). Point the disabled state at the brand color too — the
       default 0.65 opacity still reads as inactive. */
    --bs-btn-disabled-bg:           var(--dt-primary);
    --bs-btn-disabled-border-color: var(--dt-primary);
    --bs-btn-disabled-color:        var(--dt-on-primary);
}

.btn-outline-primary {
    --bs-btn-color:         var(--dt-primary);
    --bs-btn-border-color:  var(--dt-primary);
    --bs-btn-hover-bg:      var(--dt-primary);
    --bs-btn-hover-border:  var(--dt-primary);
    --bs-btn-hover-color:   var(--dt-on-primary);
}

/* Bootstrap's .text-bg-primary hardcodes a WHITE foreground (body text + a white close glyph). On the
   bright Tiffany primary that fails contrast, so re-point the whole primary context at the dark
   on-primary ink. One place covers everything painted on the brand primary: the primary/Info toasts
   (body + dismiss X) and the customer portal's outgoing chat bubble (text + timestamp). Scoped to
   .text-bg-primary so the dark danger/success toasts keep their correct white glyph. */
.text-bg-primary { color: var(--dt-on-primary) !important; }
.text-bg-primary .btn-close-white { filter: none; }   /* dark X on the bright primary toast */
.text-bg-primary .text-white-50 { color: color-mix(in srgb, var(--dt-on-primary) 60%, transparent) !important; }

/* ── Dark-mode: outline buttons render SOLID at rest (Robert S137) ─────────────
   In dark mode an outline button is brand/neutral-colored text + border on a near-black
   surface — low contrast, worst on a dark brand hue. Fill them at rest so they read, and
   BRIGHTEN on hover (the reverse of light mode's outline→fill). Keyed off the dark theme
   attribute so light mode keeps the classic outline look. All contextual variants. */
:root[data-theme="dark"] .btn-outline-primary {
    --bs-btn-bg: var(--dt-primary); --bs-btn-border-color: var(--dt-primary); --bs-btn-color: var(--dt-on-primary);
    --bs-btn-hover-bg: var(--dt-primary-hover); --bs-btn-hover-border: var(--dt-primary-hover); --bs-btn-hover-color: var(--dt-on-primary);
    --bs-btn-active-bg: var(--dt-primary-dark); --bs-btn-active-border: var(--dt-primary-dark); --bs-btn-active-color: var(--dt-on-primary);
}
:root[data-theme="dark"] .btn-outline-secondary {
    --bs-btn-bg: #6C757D; --bs-btn-border-color: #6C757D; --bs-btn-color: #fff;
    --bs-btn-hover-bg: #7C858E; --bs-btn-hover-border: #7C858E; --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: #5C646C; --bs-btn-active-border: #5C646C; --bs-btn-active-color: #fff;
}
:root[data-theme="dark"] .btn-outline-danger {
    --bs-btn-bg: var(--bs-danger); --bs-btn-border-color: var(--bs-danger); --bs-btn-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-danger) 85%, #fff); --bs-btn-hover-border: color-mix(in srgb, var(--bs-danger) 85%, #fff); --bs-btn-hover-color: #fff;
}
:root[data-theme="dark"] .btn-outline-success {
    --bs-btn-bg: var(--bs-success); --bs-btn-border-color: var(--bs-success); --bs-btn-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-success) 85%, #fff); --bs-btn-hover-border: color-mix(in srgb, var(--bs-success) 85%, #fff); --bs-btn-hover-color: #fff;
}
:root[data-theme="dark"] .btn-outline-warning {
    --bs-btn-bg: var(--bs-warning); --bs-btn-border-color: var(--bs-warning); --bs-btn-color: #1F2937;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-warning) 85%, #fff); --bs-btn-hover-border: color-mix(in srgb, var(--bs-warning) 85%, #fff); --bs-btn-hover-color: #1F2937;
}
:root[data-theme="dark"] .btn-outline-info {
    --bs-btn-bg: var(--bs-info); --bs-btn-border-color: var(--bs-info); --bs-btn-color: #1F2937;
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-info) 85%, #fff); --bs-btn-hover-border: color-mix(in srgb, var(--bs-info) 85%, #fff); --bs-btn-hover-color: #1F2937;
}
/* Exception: inside a segmented .btn-group an outline button denotes the INACTIVE segment, so keep it
   see-through — the solid/active sibling must stand out. Restore the outline resting look there. */
:root[data-theme="dark"] .btn-group > .btn-outline-primary   { --bs-btn-bg: transparent; --bs-btn-color: var(--dt-primary); }
:root[data-theme="dark"] .btn-group > .btn-outline-secondary { --bs-btn-bg: transparent; --bs-btn-color: #ADB5BD; }
:root[data-theme="dark"] .btn-group > .btn-outline-danger    { --bs-btn-bg: transparent; --bs-btn-color: var(--bs-danger); }
:root[data-theme="dark"] .btn-group > .btn-outline-success   { --bs-btn-bg: transparent; --bs-btn-color: var(--bs-success); }
:root[data-theme="dark"] .btn-group > .btn-outline-warning   { --bs-btn-bg: transparent; --bs-btn-color: var(--bs-warning); }
:root[data-theme="dark"] .btn-group > .btn-outline-info      { --bs-btn-bg: transparent; --bs-btn-color: var(--bs-info); }

/* ============================================================
   Cards
   ============================================================ */
.dt-card {
    background: var(--dt-surface);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    padding: 1.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}

/* ── Clickable navigational cards (S132) ──────────────────────────────────────
   A card whose whole surface opens a detail/hub view — the web equivalent of the
   native chevron-nav rows (Rule 18). On a `.card`/`.dt-card` add `dt-clickcard`,
   then drop TWO children inside it: an empty focusable
     <a class="dt-card-link" href="…" aria-label="…"></a>
   and a trailing chevron
     <i class="bi bi-chevron-right dt-card-chevron"></i>.
   Behaviour:
     • the whole card is the hit target — the link's ::after stretches over the card
     • a trailing chevron in the tenant brand primary marks the card as tappable
     • hover/focus: border → brand primary + a soft bottom drop shadow + a tiny lift
   Inner buttons/links (Resume, Start, a client-name link…) stay independently
   clickable because they're raised above the whole-card overlay. */
.dt-clickcard {
    position: relative;
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.dt-clickcard:hover,
.dt-clickcard:focus-within,
/* Tiles whose whole card is wrapped in an <a> (e.g. portal quick-nav): the anchor,
   not the card, takes keyboard focus, so mirror the hover look when it's focused. */
a:focus-visible > .dt-clickcard {
    border-color: var(--dt-primary);
    /* !important overrides Bootstrap's .shadow-sm utility (which is itself !important
       and sits on these cards) so the lift actually deepens the drop shadow on hover. */
    box-shadow: 0 10px 22px -12px rgba(0,0,0,.35), 0 2px 6px -3px rgba(0,0,0,.12) !important;
    transform: translateY(-2px);
}
/* Reserve a right-hand gutter for the chevron ONLY on cards that actually have one,
   so centered stat/nav tiles (no chevron) keep their centering. */
.dt-clickcard:has(.dt-card-chevron) > .card-body { padding-right: 2.9rem; }
.dt-clickcard.dt-card:has(.dt-card-chevron) { padding-right: 2.9rem; }

/* Trailing chevron affordance — decorative; the whole card is the actual link,
   so it ignores pointer events and clicks fall through to .dt-card-link. */
.dt-card-chevron {
    position: absolute;
    top: 50%;
    right: 1rem;
    transform: translateY(-50%);
    color: var(--dt-primary);
    font-size: 1.15rem;
    line-height: 1;
    pointer-events: none;
    transition: right .15s ease;
}
.dt-clickcard:hover .dt-card-chevron,
.dt-clickcard:focus-within .dt-card-chevron { right: .8rem; }

/* Brand-tinted chevron for IN-FLOW nav rows (e.g. list-group rows that are already
   whole-row <a> links) — the row's own layout places it; this just colours it. */
.dt-chevron-brand { color: var(--dt-primary); }

/* Whole-card / whole-row link: an empty, keyboard-focusable anchor whose ::after
   covers the nearest positioned ancestor (the .dt-clickcard, or a .dt-rowlink <tr>).
   Kept position:static so the ::after resolves against that ancestor, not the anchor. */
.dt-card-link,
.dt-row-link { position: static; }
.dt-card-link::after,
.dt-row-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
}
/* Real interactive controls sit ABOVE the whole-card overlay so they keep their
   own click target instead of triggering the card navigation. */
.dt-clickcard .card-body a:not(.dt-card-link),
.dt-clickcard .card-body button,
.dt-clickcard .card-body .btn,
.dt-clickcard .dt-actions { position: relative; z-index: 2; }

/* ── Clickable table rows (S133) ──────────────────────────────────────────────
   The whole-row equivalent of .dt-clickcard for desktop list tables. Add `dt-rowlink`
   to a <tr>, drop an empty <a class="dt-row-link" href=… aria-label=…> in its first
   cell, replace the row's view/eye button with a trailing <i class="bi bi-chevron-right
   dt-chevron-brand">, and keep any edit pencil (it stays clickable, raised above the
   overlay). On hover the row gets a brand left-accent bar + the brand chevron. */
tr.dt-rowlink { position: relative; cursor: pointer; }
tr.dt-rowlink:hover > td:first-child { box-shadow: inset 3px 0 0 0 var(--dt-primary); }
/* Keep the edit pencil / any real control above the row overlay. */
tr.dt-rowlink td a:not(.dt-row-link),
tr.dt-rowlink td button,
tr.dt-rowlink td .btn { position: relative; z-index: 2; }

.dt-stat-card {
    background: var(--dt-surface);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    padding: 1.25rem;
    border-left: 4px solid var(--dt-primary);
}

.dt-stat-value {
    font-size: 2rem;
    font-weight: 700;
    color: var(--dt-primary);
    line-height: 1;
}

.dt-stat-label {
    font-size: 0.8rem;
    color: var(--dt-muted);
    margin-top: 0.25rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

.dt-stat-sub {
    font-size: 0.8rem;
    color: var(--dt-muted);
    margin-top: 0.5rem;
}

/* DtStatTile semantic tones — colour the value AND the left accent rail together.
   Default (no tone / tone-primary) stays the Tiffany primary from the rules above. */
.dt-stat-card.tone-neutral { border-left-color: var(--dt-border-strong); }
.dt-stat-card.tone-neutral .dt-stat-value { color: var(--dt-text); }
.dt-stat-card.tone-success { border-left-color: var(--dt-success); }
.dt-stat-card.tone-success .dt-stat-value { color: var(--dt-success); }
.dt-stat-card.tone-danger  { border-left-color: var(--dt-danger); }
.dt-stat-card.tone-danger  .dt-stat-value { color: var(--dt-danger); }
.dt-stat-card.tone-warning { border-left-color: var(--dt-warning); }
.dt-stat-card.tone-warning .dt-stat-value { color: var(--dt-warning); }
/* small muted suffix (%, mi, "/ 30", ★) that trails the value */
.dt-stat-value small { font-size: 0.9rem; font-weight: 600; color: var(--dt-muted); margin-left: 0.1rem; }
/* optional tertiary context line under the label */
.dt-stat-detail { font-size: 0.75rem; color: var(--dt-muted); margin-top: 0.15rem; }

/* ============================================================
   Placeholder / coming soon pages
   ============================================================ */
.placeholder-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 60vh;
    text-align: center;
    color: var(--dt-muted);
}

.placeholder-page i {
    font-size: 3rem;
    /* Was --dt-primary-light (a fixed light hue → invisible on the default tenant's dark surface).
       A brand-tinted faint that stays visible in both themes for this large decorative glyph. */
    color: color-mix(in srgb, var(--dt-primary) 45%, var(--dt-faint));
    margin-bottom: 1rem;
}

.placeholder-page h2 {
    color: var(--dt-primary);
    margin-bottom: 0.5rem;
}

/* ============================================================
   Identity / auth pages
   ============================================================ */
.content { padding-top: 1.1rem; }
h1:focus { outline: none; }

.valid.modified:not([type=checkbox]) { outline: 1px solid var(--dt-success); }
.invalid { outline: 1px solid var(--dt-danger); }
.validation-message { color: var(--dt-danger); }

.blazor-error-boundary {
    background: #B32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}
.blazor-error-boundary::after { content: "An error has occurred."; }

.darker-border-checkbox.form-check-input { border-color: #929292; }

/* ── Tabs & pill nav use the brand primary (tenant-configurable via --dt-primary),
      never the Bootstrap/Syncfusion default blue. Applies app-wide: Configuration,
      account settings, and anywhere else .nav-tabs / .nav-pills are used. ── */
.nav-tabs {
    --bs-nav-tabs-border-color:             var(--dt-border);
    --bs-nav-link-color:                    var(--dt-muted);
    --bs-nav-link-hover-color:              var(--dt-primary);
    --bs-nav-tabs-link-active-color:        var(--dt-primary);
    --bs-nav-tabs-link-active-bg:           transparent;
    --bs-nav-tabs-link-active-border-color: transparent transparent var(--dt-primary);
    --bs-nav-tabs-link-hover-border-color:  transparent transparent color-mix(in srgb, var(--dt-primary) 40%, transparent);
}
.nav-tabs .nav-link {
    border-width: 0 0 2px 0;
    font-weight: 600;
}
.nav-pills {
    --bs-nav-link-color:             var(--dt-muted);
    --bs-nav-link-hover-color:       var(--dt-primary);
    --bs-nav-pills-link-active-color: var(--dt-on-primary);
    --bs-nav-pills-link-active-bg:   var(--dt-primary);
}

.form-floating > .form-control-plaintext::placeholder,
.form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder,
.form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Rounded event bars so calendar items match the app's buttons/cards radius (--dt-radius).
   Covers week/day/month timeline events + all-day and agenda events. overflow clips the inline
   background to the rounded corners. */
.e-schedule .e-appointment,
.e-schedule .e-agenda-view .e-appointment,
.e-schedule .e-month-view .e-appointment,
.e-schedule .e-all-day-appointment {
    border-radius: var(--dt-radius, 8px) !important;
    overflow: hidden;
}

/* ── Calendar appointment colors ────────────────────────────────────────── */
/* Event background/border/text are set inline per appointment in OnEventRendered (from the
   service's own ColorHex). Event text inherits that inline foreground color
   (auto white/dark by service-color luminance). Covers timeline + agenda views so
   text stays readable whatever color the tenant picks. */
.e-schedule .e-appointment .e-appointment-details,
.e-schedule .e-appointment .e-subject,
.e-schedule .e-appointment .e-time,
.e-schedule .e-appointment .e-date-time,
.e-schedule .e-appointment .e-location,
.e-schedule .e-agenda-view .e-appointment .e-subject,
.e-schedule .e-agenda-view .e-appointment .e-date-time { color: inherit !important; }

/* ── Agenda view: roomier events, tighter line spacing ───────────────────────
   Robert's request: more breathing room at the top/bottom of each event bar,
   but the two text lines (subject + time) sit closer together instead of being
   pushed to the top and bottom of a tall block. Stack + center the lines and
   let padding do the spacing. */
.e-schedule .e-agenda-view .e-appointment,
.e-schedule .e-agenda-view .e-appointment .e-appointment-details {
    display: flex !important;
    flex-direction: column !important;
    justify-content: center !important;
    gap: 2px !important;
    min-height: 0 !important;
}
.e-schedule .e-agenda-view .e-appointment {
    padding: 12px 14px !important;
}
.e-schedule .e-agenda-view .e-appointment .e-subject,
.e-schedule .e-agenda-view .e-appointment .e-date-time {
    line-height: 1.3 !important;
    margin: 0 !important;
    padding: 0 !important;
}
.e-schedule .e-agenda-view .e-appointment .e-subject { font-weight: 600; }
.e-schedule .e-agenda-view .e-appointment .e-date-time { opacity: 0.9; }


/* ── Blazor reconnect modal ──────────────────────────────────────────────
   blazor.web.js toggles one class at a time on #components-reconnect-modal:
     .components-reconnect-show     — trying to reconnect
     .components-reconnect-failed   — gave up
     .components-reconnect-rejected — session ended by server
   ─────────────────────────────────────────────────────────────────────── */

#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1050;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

/* Semi-transparent backdrop */
.reconnect-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    backdrop-filter: blur(2px);
}

/* Centered card */
.reconnect-card {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: var(--dt-surface);
    border-radius: var(--dt-radius);
    padding: 2rem 2.5rem;
    width: min(380px, 90vw);
    text-align: center;
}

/* All state sections hidden by default */
.reconnect-state {
    display: none;
}

/* Show only the active state's section */
#components-reconnect-modal.components-reconnect-show     .reconnect-state-show     { display: block; }
#components-reconnect-modal.components-reconnect-failed   .reconnect-state-failed   { display: block; }
#components-reconnect-modal.components-reconnect-rejected .reconnect-state-rejected { display: block; }

/* ── Animal toggle cards (booking form) ─────────────────────────────────
   Replaces checkbox inputs with fully-clickable cards.
   ─────────────────────────────────────────────────────────────────────── */
/* ── Active Visit — orange button for "Vet Contact Recommended" health flag ──
   Bootstrap 5 has no built-in orange; this fills the gap between warning and danger.
   ─────────────────────────────────────────────────────────────────────────── */
.btn-vet {
    --bs-btn-color:         #fff;
    --bs-btn-bg:            #EA580C;
    --bs-btn-border-color:  #C2410C;
    --bs-btn-hover-bg:      #C2410C;
    --bs-btn-hover-border:  #9A3412;
    --bs-btn-active-bg:     #9A3412;
    --bs-btn-active-border: #7C2D12;
}
.btn-outline-vet {
    --bs-btn-color:         #EA580C;
    --bs-btn-border-color:  #EA580C;
    --bs-btn-hover-color:   #fff;
    --bs-btn-hover-bg:      #EA580C;
    --bs-btn-hover-border:  #C2410C;
}

/* ── Active Visit status card borders ─────────────────────────────────────── */
.visit-event-card-inprogress { border-left: 4px solid var(--dt-warning); }
.visit-event-card-scheduled  { border-left: 4px solid var(--dt-primary); }
.visit-event-card-completed  { border-left: 4px solid var(--dt-success); }

.animal-toggle-card {
    border: 2px solid var(--dt-border-strong);
    border-radius: var(--dt-radius);
    padding: 0.75rem 1rem;
    cursor: pointer;
    transition: border-color 0.15s, background-color 0.15s;
    user-select: none;
}

.animal-toggle-card:hover {
    border-color: var(--dt-faint);
    background-color: var(--dt-surface-2);
}

.animal-toggle-card.selected {
    border-color: var(--dt-primary);
    background-color: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface));
}

.animal-toggle-icon {
    font-size: 1.25rem;
    color: var(--dt-faint);
    flex-shrink: 0;
}

/* ── Blazor error UI (global — scoped CSS cannot target this element reliably) ── */
#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.animal-toggle-card.selected .animal-toggle-icon {
    color: var(--dt-primary, #6B21A8);
}

/* ── Pack Walk Builder — drag-and-drop panels ─────────────────────────────── */
.pw-dropzone {
    /* Both columns are the same fixed height so the "On This Walk" drop target stays
       visible/reachable no matter how long the available list gets; the lists scroll
       internally instead of growing the page. min-height wins on very short screens. */
    height: 60vh;
    min-height: 260px;
    overflow-y: auto;
    border: 2px dashed var(--dt-border-strong);
    border-radius: var(--dt-radius);
    padding: 0.5rem;
    transition: border-color 0.15s, background-color 0.15s;
}

.pw-dropzone-active {
    border-color: var(--dt-primary);
    background-color: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface));
}

.pw-dropzone-walk {
    border-color: var(--dt-primary);
    background-color: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface));
}

.pw-dog-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: var(--dt-surface);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    padding: 0.5rem 0.75rem;
    margin-bottom: 0.4rem;
    cursor: grab;
    user-select: none;
    transition: box-shadow 0.12s, border-color 0.12s;
}

.pw-dog-card:hover {
    box-shadow: 0 2px 6px rgba(0,0,0,0.10);
    border-color: var(--dt-faint);
}

.pw-dog-card:active {
    cursor: grabbing;
}

.pw-dog-card-on-walk {
    border-left: 3px solid var(--dt-primary, #6B21A8);
}

.pw-dog-conflict {
    border-left: 3px solid var(--dt-warning);
    background-color: var(--dt-warning-bg);
}

.pw-dog-icon {
    font-size: 1.25rem;
    flex-shrink: 0;
}

.pw-dog-info {
    flex: 1;
    min-width: 0;
}

.pw-dog-info .fw-semibold {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Profile photos / avatars ─────────────────────────────────────────────── */
.dt-avatar {
    border-radius: 50%;
    object-fit: cover;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--dt-primary-hover, #5a4b8a);
    color: var(--dt-on-primary);
    font-weight: 600;
    flex-shrink: 0;
    overflow: hidden;
    line-height: 1;
    text-transform: uppercase;
}

.dt-avatar-fallback {
    /* initials sit centered on the soft brand background */
}

.dt-avatar-xs { width: 24px;  height: 24px;  font-size: 0.65rem; }

/* Overlapping row of dog avatars for list rows */
.dt-avatar-stack { display: inline-flex; align-items: center; }
/* Overlap applies to the direct child — a bare avatar, or the flag-badge wrapper when flagged. */
.dt-avatar-stack > .dt-avatar,
.dt-avatar-stack > .dt-avatar-wrap { margin-left: -8px; }
.dt-avatar-stack > .dt-avatar:first-child,
.dt-avatar-stack > .dt-avatar-wrap:first-child { margin-left: 0; }
/* White ring goes on the avatar circle itself (whether wrapped or not) to separate overlaps. */
.dt-avatar-stack > .dt-avatar,
.dt-avatar-stack > .dt-avatar-wrap > .dt-avatar { border: 2px solid var(--dt-surface); }

/* Calendar hover tooltip (Syncfusion TooltipTemplate) — does not affect event bars.
   Colors come from --dt-tooltip-* so they're easy to re-theme; sized up for readability. */
.e-tooltip-wrap.e-popup,
.e-schedule .e-tooltip-wrap {
    background: var(--dt-tooltip-bg) !important;
    color: var(--dt-tooltip-text) !important;
    border: none !important;
    box-shadow: 0 6px 20px rgba(0,0,0,0.25) !important;
    max-width: 340px !important;
}
.e-tooltip-wrap .e-tip-content { background: var(--dt-tooltip-bg) !important; color: var(--dt-tooltip-text) !important; }
/* Hide Syncfusion's little arrow tip so the rounded box reads cleanly. */
.e-schedule .e-tooltip-wrap .e-arrow-tip { display: none !important; }

.dt-cal-tip { padding: 0.5rem 0.65rem; min-width: 200px; color: var(--dt-tooltip-text); }
.dt-cal-tip-title { font-weight: 700; font-size: 1.05rem; margin-bottom: 3px; }
.dt-cal-tip-time { font-size: 0.9rem; color: var(--dt-tooltip-muted); margin-bottom: 4px; }
.dt-cal-tip-staff { font-size: 0.9rem; color: var(--dt-tooltip-muted); margin-bottom: 8px; }
.dt-cal-tip-staff i { margin-right: 4px; }
.dt-cal-tip-dogs { display: flex; flex-direction: column; gap: 6px; }
.dt-cal-tip-dog { display: flex; align-items: center; gap: 8px; font-size: 0.95rem; }

.dt-avatar-sm { width: 32px;  height: 32px;  font-size: 0.8rem; }
.dt-avatar-md { width: 48px;  height: 48px;  font-size: 1rem; }
.dt-avatar-lg { width: 72px;  height: 72px;  font-size: 1.4rem; }
.dt-avatar-xl { width: 112px; height: 112px; font-size: 2.2rem; }

/* ── Temperament-flag corner badge on an avatar ──────────────────────────────
   The top-priority flag shows as a small colored circle in the upper-left corner. */
.dt-avatar-wrap { position: relative; display: inline-block; line-height: 0; }
.dt-flag-badge {
    position: absolute;
    top: -3px;
    left: -3px;
    border-radius: 50%;
    border: 2px solid #fff;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    box-sizing: border-box;
    pointer-events: none;
}
/* UIUX-1 (S80): the old badges were unreadable — 7–12px glyphs filling barely half the dot. Bigger
   dots + the glyph filling ~65% of the dot. These carry temperament flags (bite/escape risk), so
   legibility wins over a smaller footprint on the small avatars. */
.dt-flag-badge-xs { width: 15px; height: 15px; font-size: 10px; border-width: 1.5px; top: -2px; left: -2px; }
.dt-flag-badge-sm { width: 20px; height: 20px; font-size: 13px; }
.dt-flag-badge-md { width: 26px; height: 26px; font-size: 17px; }
.dt-flag-badge-lg { width: 32px; height: 32px; font-size: 20px; }
.dt-flag-badge-xl { width: 44px; height: 44px; font-size: 28px; }

/* Inline flag chips shown AFTER a pet's name (Robert S136 — preferred over a badge overlapping the
   photo, which never sat right). A pet can carry several, so they sit in a row; each is the flag's
   own colour with a white glyph. */
.dt-flag-inline {
    display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; border-radius: 50%;
    color: #fff; font-size: 14px; line-height: 1;
    vertical-align: middle; flex-shrink: 0;
}

/* ── Single-photo uploader ────────────────────────────────────────────────── */
.dt-uploader {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

.dt-uploader-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* ── Multi-photo gallery ──────────────────────────────────────────────────── */
.dt-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 1rem;
}

.dt-gallery-item {
    margin: 0;
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
    overflow: hidden;
    background: var(--dt-surface);
    display: flex;
    flex-direction: column;
}

.dt-gallery-item img,
.dt-gallery-item video {
    width: 100%;
    height: 160px;
    object-fit: cover;
    display: block;
    background: #000;
}

/* Standard: a gallery tile is a button that opens the full-screen MediaLightbox (view/download/
   prev/next), never a raw link to blob storage. Used by PhotoGallery + the pet Media tab. */
.dt-gallery-open {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: none;
    background: #000;
    cursor: pointer;
    line-height: 0;
}
.dt-gallery-open:focus-visible {
    outline: 2px solid var(--dt-primary);
    outline-offset: -2px;
}
.dt-media-play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.2rem;
    color: #fff;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
    pointer-events: none;
}

.dt-gallery-item figcaption {
    padding: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

/* Selection affordances for any gallery's Edit mode (pet Media tab + the staff
   booking/visit/pack-walk galleries). The base .dt-gallery-item styles above apply to
   these tiles too; these rules only add the selectable/selected + check indicator. */
.dt-gallery-item.selectable {
    position: relative;
    cursor: pointer;
    user-select: none;
    transition: outline-color .12s ease, box-shadow .12s ease;
    outline: 2px solid transparent;
    outline-offset: -2px;
}

.dt-gallery-item.selectable:hover {
    outline-color: var(--dt-border-strong);
}

.dt-gallery-item.selected {
    outline-color: var(--dt-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--dt-primary) 40%, transparent);
}

/* The check indicator sits over the top-left of the thumbnail. */
.dt-media-check {
    position: absolute;
    top: .375rem;
    left: .375rem;
    z-index: 2;
    line-height: 1;
    background: var(--dt-surface);
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}

.dt-media-check i {
    display: block;
    font-size: 1.4rem;
}

.dt-media-check i.bi-circle {
    color: var(--dt-muted);
}

.dt-media-check i.bi-check-circle-fill {
    color: var(--dt-primary);
}

/* ============================================================
   Fit & finish — one consistent radius + list padding everywhere
   ============================================================ */

/* Drive Bootstrap's own radii from our token (app.css loads after Bootstrap), so cards,
   inputs, selects, dropdowns, alerts, modals, buttons and list-groups all round the same
   way — with correctly-computed inner corners — matching the navigation. */
:root {
    --bs-border-radius: var(--dt-radius);
}

/* Custom (non-Bootstrap) surfaces → same radius as the nav. */
.sidebar-nav .nav-link,
.sidebar-account,
.dt-card,
.dt-stat-card,
.pw-dropzone,
.pw-dog-card {
    border-radius: var(--dt-radius);
}

/* Standalone table lists (e.g. Dashboard, Bookings): rounded container. Bootstrap already
   sets overflow-x:auto here, which both preserves horizontal scroll AND clips the rounded
   corners — so we only add the border + radius. */
.table-responsive {
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
}

/* Bootstrap gives .table a bottom margin — inside the rounded .table-responsive that reads as an ugly
   gap between the last row and the container's bottom border. Kill it so the list ends flush. */
.table-responsive > .table {
    margin-bottom: 0;
}

/* Consistent, comfortable padding for every table-based list. */
.table > :not(caption) > * > * {
    padding: 0.7rem 1rem;
}

/* Syncfusion calendar hover tooltip — same rounded corners as everything else. */
.e-tooltip-wrap.e-popup,
.e-schedule .e-tooltip-wrap {
    border-radius: var(--dt-radius) !important;
    overflow: hidden;
}
.e-tooltip-wrap .e-tip-content {
    border-radius: var(--dt-radius) !important;
}

/* ── Billing documents (statement / receipt / invoice) ───────────────────────
   A clean, branded printable document. On screen it's a centered "paper" card;
   in print we hide the app chrome and show only the document. */
.dt-doc {
    max-width: 800px;
    margin: 0 auto;
    background: var(--dt-surface);
    border: 1px solid var(--dt-border, #e5e7eb);
    border-radius: var(--dt-radius, 8px);
    padding: 2rem 2.25rem;
}
.dt-doc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; margin-bottom: 1.5rem; }
.dt-doc-brand { display: flex; align-items: center; gap: 0.75rem; }
.dt-doc-brand img { max-height: 56px; max-width: 200px; object-fit: contain; }
.dt-doc-title { text-align: right; }
.dt-doc-title h2 { margin: 0; font-size: 1.4rem; }
.dt-doc table { width: 100%; border-collapse: collapse; }
.dt-doc table th, .dt-doc table td { padding: 0.5rem 0.4rem; border-bottom: 1px solid var(--dt-border, #e5e7eb); }
.dt-doc table th { text-align: left; font-size: 0.8rem; text-transform: uppercase; color: var(--dt-muted); }
.dt-doc-totals { margin-top: 1rem; margin-left: auto; max-width: 320px; }
.dt-doc-totals .row-line { display: flex; justify-content: space-between; padding: 0.25rem 0; }
.dt-doc-totals .grand { border-top: 2px solid var(--dt-border, #e5e7eb); margin-top: 0.25rem; padding-top: 0.5rem; font-weight: 700; font-size: 1.1rem; }

/* Phone width: stack the branded-document header (logo/business over the doc title) so the
   "INVOICE/STATEMENT" block no longer runs off the right edge, and trim the paper padding
   so more of the content fits. Wide line-item tables get .table-responsive in markup. */
@media (max-width: 991.98px) {
    .dt-doc { padding: 1.25rem 1.1rem; }
    .dt-doc-head { flex-direction: column; }
    .dt-doc-title { text-align: left; }
    .dt-doc-totals { max-width: none; }

    /* Segmented choice controls (Feeding / Behavior / Health on the visit report) can't fit
       four long labels on one phone row — a Bootstrap .btn-group refuses to wrap, so the last
       option runs off-screen. Stack them into full-width rows: big, unambiguous tap targets. */
    .dt-choice-group { flex-direction: column; width: 100%; }
    .dt-choice-group > .btn {
        width: 100%;
        margin-left: 0 !important;
        border-radius: var(--bs-border-radius, 0.375rem) !important;
    }
    .dt-choice-group > .btn + .btn { margin-top: 0.4rem; }
}
/* A .table-responsive wrapper must never hide content when the document is printed. */
@media print { .table-responsive { overflow: visible !important; } }

/* Equal-width action rows on a phone: buttons in a .dt-actions row snap to a tidy grid — a
   50% flex-basis means 1 button fills the width, 2 go half-and-half, an odd last button grows
   to full width, and longer bars wrap into uniform pairs. No more ragged auto-width buttons. */
@media (max-width: 991.98px) {
    /* Target 50/50, but min-width: max-content means a button can NEVER shrink below its single-line
       label — so equal-width is overridden the moment it would wrap the text: a too-long button keeps
       its natural width (its partner takes the rest), and if the pair can't fit on one line it wraps to
       full-width lines instead. Text never wraps inside a button (S82, Robert). align-items: stretch
       keeps a same-line pair equal-height. */
    .dt-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; width: 100%; align-items: stretch; }
    .dt-actions > .btn { flex: 1 1 calc(50% - 0.25rem); min-width: max-content; }
    .dt-actions + .dt-actions { margin-top: 0.5rem; }

    /* Every Bootstrap modal footer follows the same rule app-wide, without touching each modal's
       markup (S82, Robert): on a phone the action buttons FILL the row — one spans full width, two go
       50/50, and equal-width yields (see above) to a natural width whenever 50% would wrap the label.
       This is why individual confirm/gate/compose dialogs don't each need a dt-actions class. */
    /* gap does the spacing since we zero Bootstrap's per-child margins below. Two values: the ROW gap
       (stacked buttons) is 0.75rem to match the footer's own top padding (Bootstrap's
       modal-padding − footer-gap/2 = 1rem − 0.25rem), so the space above Cancel and between the
       stacked buttons read the same; the COLUMN gap (side-by-side pair) stays 0.5rem to match the
       calc() basis below. */
    .modal-footer { flex-wrap: wrap; align-items: stretch; gap: 0.75rem 0.5rem; }
    .modal-footer > .btn { flex: 1 1 calc(50% - 0.25rem); min-width: max-content; margin: 0; }
}

/* ── Leave-without-saving guard modal (DtUnsavedGuard + DtEditScaffold) ──────────────
   Robert (S97): Bootstrap's default backdrop (50% black, no blur) let this prompt blend
   into the dark app behind it. Darken + blur the backdrop so it reads as a separate
   layer, and lift the card onto the raised surface with a strong border and deeper
   shadow so its edges are unmistakable in both light and dark.
   NOTE: .modal-backdrop is rendered ONLY by these two guard modals (every other dialog
   uses DtDialogScaffold's own overlay), so this global rule is effectively scoped. */
.modal-backdrop.show {
    opacity: 1;                                  /* darkness comes from the rgba below, not element opacity */
    background-color: rgba(0, 0, 0, 0.72);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
}
.dt-leave-modal .modal-content {
    background-color: var(--dt-surface-2);
    border: 1px solid var(--dt-border-strong);
    box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.55);
}

/* Mobile "collection" list — a tappable row of avatar + name (used where a desktop table is
   too wide for a phone, e.g. Clients). The whole row is a link to the detail page. Reusable. */
.dt-mobile-list { display: flex; flex-direction: column; }
.dt-list-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.7rem 0.25rem;
    border-bottom: 1px solid var(--dt-border, #e5e7eb);
    color: inherit;
    text-decoration: none;
}
.dt-list-item:active { background: rgba(0, 0, 0, 0.04); }
.dt-list-item-body { flex: 1 1 auto; min-width: 0; }
.dt-list-item-title {
    font-weight: 600;
    color: var(--dt-text, #1f2937);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dt-list-item-sub {
    font-size: 0.85rem;
    color: var(--dt-muted, #6b7280);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dt-list-item-sub .dt-dot { opacity: 0.5; margin: 0 0.15rem; }
.dt-list-chev { color: var(--dt-muted, #9ca3af); font-size: 1.1rem; flex: 0 0 auto; }

/* Standard physical print margins on every printed page, so document content is never
   crammed against the paper edge. Applies to the whole printout. */
@page { margin: 0.75in; }

@media print {
    .app-sidebar, .sidebar-overlay, .mobile-menu-btn, .mobile-topbar, .mobile-tabbar, .toast-container,
    #blazor-error-ui, .d-print-none, .page-header .btn { display: none !important; }
    .app-main, .app-content { margin: 0 !important; padding: 0 !important; }
    .app-shell { display: block !important; }
    body, .app-shell { background: #fff !important; }
    /* @page supplies the outer margin; keep a little inner breathing room and drop the on-screen frame. */
    .dt-doc { border: none !important; border-radius: 0 !important; box-shadow: none !important; padding: 0 !important; max-width: 100% !important; }
}

/* ── Client portal ────────────────────────────────────────────────────────────
   Deliberately simple + mobile-first: a top bar (no staff sidebar) and a centered
   content column. Pet owners get a clean, uncluttered surface. */
/* Transient toast surface (device operator shell → ToastHost). Fixed, top-centered, and it MUST
   clear the top safe area. Do NOT rebuild this out of Bootstrap spacing utilities (.p-3 / .pt-*):
   those compile to `padding:…!important`, which beats an inline safe-area padding-top and drops the
   toast under the notch / status bar on every device. Styling it here (no !important) lets the
   safe-area value win — env() answers on iOS, the native bridge (--dt-safe-top) on Android, same
   pattern as .dt-hybrid-content / .dt-statusbar-cap. */
.dt-toast-host {
    position: fixed; top: 0; left: 50%; transform: translateX(-50%);
    z-index: 1090;
    width: max-content; max-width: 100vw;
    padding: 1rem;
    padding-top: max(1rem, env(safe-area-inset-top), var(--dt-safe-top, 0px));
    pointer-events: none; /* the gutter never blocks taps on content beneath */
}
.dt-toast-host > .toast { pointer-events: auto; }

/* Walk route map (Leaflet). Height is set inline per use; a light placeholder shows before tiles load. */
.dt-walkmap { width: 100%; background: var(--dt-surface-2, #eef1f4); }
.dt-walkmap-rounded-bottom { border-bottom-left-radius: var(--dt-radius, 8px); border-bottom-right-radius: var(--dt-radius, 8px); overflow: hidden; }
.leaflet-container { font: inherit; }

/* Legend dot for the visit check-in/check-out stamps (#32) — matches the map's vector markers. */
.dt-stamp-dot {
    display: inline-block; width: .7rem; height: .7rem; border-radius: 50%;
    border: 2px solid var(--dt-surface, #fff); flex: 0 0 auto;
}

.portal-shell { min-height: 100vh; background: var(--dt-bg); }
.portal-topbar {
    background: var(--dt-portal-header-bg, #0C4A47); /* on-brand default teal; #2D1B69 was a stale pre-teal purple that flashed on full reload before tenant branding applied (S149) */
    color: var(--dt-portal-header-text, rgba(255,255,255,.9));
    box-shadow: 0 1px 4px rgba(0,0,0,.12);
    position: sticky; top: 0; z-index: 1030;
    /* Installed to the home screen, the portal runs full-screen with a translucent status bar, so the
       page paints under the clock/wifi/battery. Reserve that strip. Zero everywhere else. */
    padding-top: env(safe-area-inset-top);
}
.portal-topbar-inner {
    /* Wider than the 960px content column on purpose: the header carries brand + 10 nav links, which
       don't fit one row at 960 and used to WRAP to an ugly second row. 1100 fits them in a single row
       down to the 992px drawer breakpoint; the slight brand/content offset is an accepted trade. */
    max-width: 1100px; margin: 0 auto; padding: .6rem 1rem;
    display: flex; align-items: center; gap: 1rem; flex-wrap: nowrap;
}
.portal-brand { display: inline-flex; align-items: center; gap: .5rem; color: var(--dt-portal-header-text, #fff); text-decoration: none; font-weight: 600; }
.portal-logo { height: 34px; width: auto; }
.portal-brand-text { font-size: 1.05rem; }
.portal-menu-btn {
    margin-left: auto; background: transparent; border: 0; color: var(--dt-portal-header-text, #fff); font-size: 1.5rem; line-height: 1; display: none;
}
.portal-nav { display: flex; gap: .1rem; margin-left: auto; flex-wrap: nowrap; }
.portal-nav-link {
    color: var(--dt-portal-header-text, rgba(255,255,255,.85)); text-decoration: none; padding: .4rem .55rem; border-radius: var(--dt-radius, 8px);
    font-size: .9rem;
    white-space: nowrap;
    /* Flex + gap at every width, not just in the drawer: the icon spacing used to come from a `me-1`
       utility on each <i>, so styling it only inside the mobile media query left the desktop row with
       its icons welded to the labels. */
    display: inline-flex; align-items: center; gap: .3rem;
    /* Sign out is a <button> sharing this class. Without the reset it picks up the UA border/background
       on desktop and reads as a stray boxed control next to the links. */
    border: none; background: none; font-family: inherit; cursor: pointer;
}
.portal-nav-link:hover { color: var(--dt-portal-header-text, #fff); background: var(--dt-portal-header-hover, rgba(255,255,255,.12)); }
.portal-nav-link.active { color: var(--dt-portal-header-text, #fff); background: var(--dt-portal-header-active, rgba(255,255,255,.2)); }
.portal-content { max-width: 960px; margin: 0 auto; padding: 1.25rem 1rem 4rem; }
.portal-nav-overlay { display: none; }

/* Drawer-only chrome — hidden on desktop, where .portal-nav is just an inline link row. */
.portal-nav-close,
.portal-nav-close-bottom,
.portal-nav-head,
.portal-nav-sep { display: none; }

@media (max-width: 991.98px) {
    /* The header nav becomes a right-side drawer, opened by the bottom Menu tab (below). */
    .portal-nav {
        position: fixed;
        top: 0;
        right: 0;
        /* 100vh is the pre-dvh fallback. It measures the LARGEST viewport, so with the iOS browser
           chrome showing it overflows and clips the last rows (Sign out / Close) — the same failure the
           staff drawer hit. dvh tracks what's actually visible; the safe-area padding then keeps the
           final row clear of the home indicator. */
        height: 100vh;
        height: 100dvh;
        width: min(82vw, 320px);
        margin: 0;
        flex-direction: column;
        flex-wrap: nowrap;
        gap: 0.15rem;
        /* Top inset matches the header: installed full-screen, the drawer also starts under the clock. */
        padding: calc(0.75rem + env(safe-area-inset-top, 0px)) 0.75rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
        background: var(--dt-portal-header-bg, #0C4A47); /* on-brand default teal; #2D1B69 was a stale pre-teal purple that flashed on full reload before tenant branding applied (S149) */
        transform: translateX(100%);
        transition: transform 0.2s ease;
        z-index: 1035;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }

    /* While the drawer is open, slide the bottom tab bar away. It holds the same destinations, so it's
       redundant here — and leaving it up meant the 64px bar sat over the drawer's last rows. Sliding it
       out is what lets the drawer own the full height instead of negotiating with a fixed bar. */
    .portal-shell.nav-open .mobile-tabbar { transform: translateY(110%); }

    /* Top-corner close. Tapping the scrim already worked, but nothing on screen said so — this is the
       obvious, 44px target. */
    .portal-nav-close {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        top: calc(0.6rem + env(safe-area-inset-top, 0px));
        right: 0.6rem;
        width: 44px;
        height: 44px;
        flex: 0 0 auto;
        border: none;
        border-radius: var(--dt-radius);
        background: rgba(255, 255, 255, 0.14);
        color: var(--dt-portal-header-text, #fff);
        font-size: 1.1rem;
        line-height: 1;
        cursor: pointer;
    }
    .portal-nav-close:active { background: rgba(255, 255, 255, 0.26); }

    /* Account header. Padded right so the brand name never runs under the X above it. */
    .portal-nav-head {
        display: flex;
        flex-direction: column;
        gap: 0.1rem;
        flex: 0 0 auto;
        padding: 0.35rem 3.25rem 0.85rem 0.7rem;
        margin-bottom: 0.35rem;
        border-bottom: 1px solid rgba(255, 255, 255, 0.16);
        min-width: 0;
    }
    .portal-nav-head-brand {
        color: var(--dt-portal-header-text, #fff);
        font-size: 1rem;
        font-weight: 700;
        line-height: 1.25;
        overflow-wrap: anywhere;
    }
    .portal-nav-head-user {
        color: var(--dt-portal-header-text, rgba(255, 255, 255, 0.75));
        opacity: 0.75;
        font-size: 0.78rem;
        overflow-wrap: anywhere;
    }

    /* Roomier rows with the icon on its own column, so labels align instead of stepping in and out
       with each icon's width. */
    .portal-nav-link {
        width: 100%;
        display: flex;
        align-items: center;
        gap: 0.7rem;
        min-height: 46px;
        padding: 0.55rem 0.7rem;
        font-size: 1rem;
        border: none;
        background: none;
        text-align: left;
    }
    .portal-nav-link i {
        flex: 0 0 1.35rem;
        font-size: 1.1rem;
        line-height: 1;
        text-align: center;
        opacity: 0.9;
    }
    .portal-nav-link > span { flex: 1 1 auto; min-width: 0; }

    /* Unread pill, sized to match the tab-bar badge rather than Bootstrap's default. */
    .portal-nav-count {
        flex: 0 0 auto;
        min-width: 20px;
        padding: 0 6px;
        border-radius: 10px;
        background: var(--dt-danger);
        color: #fff;
        font-size: 0.7rem;
        font-weight: 700;
        line-height: 20px;
        text-align: center;
    }

    /* Divider between "what I came here to do" and "my account". */
    .portal-nav-sep {
        display: block;
        flex: 0 0 auto;
        height: 1px;
        margin: 0.5rem 0.7rem;
        background: rgba(255, 255, 255, 0.16);
    }

    .portal-nav-signout { color: var(--dt-portal-header-text, rgba(255, 255, 255, 0.85)); }

    /* mt-auto pins this to the bottom of the drawer when the list is short, and it simply follows the
       last row when the list scrolls. */
    .portal-nav-close-bottom {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        flex: 0 0 auto;
        margin: auto 0.2rem 0;
        padding: 0.7rem 1rem;
        min-height: 44px;
        border: 1px solid rgba(255, 255, 255, 0.22);
        border-radius: var(--dt-radius);
        background: rgba(255, 255, 255, 0.08);
        color: var(--dt-portal-header-text, #fff);
        font-size: 0.95rem;
        font-weight: 600;
        cursor: pointer;
    }
    .portal-nav-close-bottom:active { background: rgba(255, 255, 255, 0.2); }
    .portal-shell.nav-open .portal-nav {
        transform: translateX(0);
        box-shadow: -4px 0 24px rgba(0, 0, 0, 0.35);
    }
    /* The drawer lives inside .portal-topbar, whose z-index:1030 forms a stacking context — so the
       drawer's z-index is capped under it and the overlay (a header sibling, z:1034) would paint on
       top and swallow every tap on a drawer link. When the drawer is open, lift the header's context
       above the overlay so the links receive taps; the overlay still covers the rest (tap to close). */
    .portal-shell.nav-open .portal-topbar { z-index: 1040; }
    .portal-nav-link { width: 100%; }

    .portal-nav-overlay {
        display: block;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 1034;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
    }
    .portal-shell.nav-open .portal-nav-overlay { opacity: 1; pointer-events: auto; }

    /* Leave room for the fixed bottom tab bar. */
    .portal-content { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px) + 1rem); }

    /* Unread badge on the Messages tab icon. */
    .mobile-tab-iconwrap { position: relative; display: inline-flex; }
    .mobile-tab-badge {
        position: absolute;
        top: -5px;
        right: -9px;
        min-width: 15px;
        height: 15px;
        padding: 0 3px;
        border-radius: 8px;
        background: var(--dt-danger);
        color: #fff;
        font-size: 0.6rem;
        font-weight: 700;
        line-height: 15px;
        text-align: center;
    }
}

/* Public document pages (shareable invoice) — minimal, responsive, operator-agnostic */
.public-doc-bg {
    min-height: 100vh;
    background: var(--dt-bg);
    padding: 1.5rem 0.75rem 3rem;
}

/* ── Calendar / Availability control rows ─────────────────────────────────────
   Natural width on desktop; on phones the team-member picker and the month-nav
   controls go full width so they don't end at ragged widths. */
.dt-ctl-select { width: auto; min-width: 220px; }
.dt-monthlabel { min-width: 11rem; text-align: center; }

@media (max-width: 575.98px) {
    .dt-ctl-select { width: 100%; min-width: 0; }
    .dt-monthnav .dt-navgroup { flex: 1 1 100%; justify-content: space-between; }
    .dt-monthlabel { min-width: 0; flex: 1 1 auto; }
    .dt-today-btn { flex: 1 1 100%; }
}

/* ── Global search overlay (feature #11) — tokenized so it follows light/dark ── */
.dt-search-backdrop { position: fixed; inset: 0; z-index: 1080; background: rgba(0,0,0,0.4); }
.dt-search-panel {
    position: fixed; z-index: 1081; top: 4.5rem; left: 50%; transform: translateX(-50%);
    width: min(640px, calc(100vw - 1.5rem)); max-height: min(70vh, 640px);
    display: flex; flex-direction: column;
    background: var(--dt-surface); border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius); box-shadow: 0 20px 60px rgba(0,0,0,0.35); overflow: hidden;
}
.dt-search-inputwrap { display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.85rem; border-bottom: 1px solid var(--dt-border); }
.dt-search-icon { color: var(--dt-muted); font-size: 1.1rem; }
.dt-search-input { flex: 1; border: none; outline: none; background: transparent; font-size: 1.05rem; color: var(--dt-text); }
.dt-search-close { border: none; background: none; color: var(--dt-muted); cursor: pointer; padding: 0.25rem 0.4rem; border-radius: var(--dt-radius); }
.dt-search-close:hover { color: var(--dt-text); background: var(--dt-surface-2); }
.dt-search-results { overflow-y: auto; padding: 0.4rem 0; }
.dt-search-status { padding: 1.25rem 1rem; color: var(--dt-muted); text-align: center; }
.dt-search-seeall { display: block; width: 100%; text-align: left; border: none; background: none; cursor: pointer; padding: 0.55rem 1rem; font-size: 0.85rem; font-weight: 600; color: var(--dt-primary); border-bottom: 1px solid var(--dt-border); }
.dt-search-seeall:hover { background: var(--dt-surface-2); }
.dt-search-group { padding: 0.25rem 0 0.5rem; }
.dt-search-group-label { padding: 0.35rem 1rem; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--dt-muted); }
.dt-search-hit { display: flex; align-items: center; gap: 0.5rem; padding-right: 0.5rem; }
.dt-search-hit:hover { background: var(--dt-surface-2); }
.dt-search-hit-main { flex: 1; min-width: 0; text-align: left; border: none; background: none; cursor: pointer; padding: 0.5rem 1rem; display: flex; flex-direction: column; gap: 0.1rem; }
.dt-search-hit-title { font-weight: 600; color: var(--dt-text); }
.dt-search-hit-sub { font-size: 0.82rem; color: var(--dt-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dt-search-hit-actions { display: flex; gap: 0.15rem; flex: 0 0 auto; opacity: 0; transition: opacity 0.12s; }
.dt-search-hit:hover .dt-search-hit-actions, .dt-search-hit:focus-within .dt-search-hit-actions { opacity: 1; }
.dt-search-action { width: 34px; height: 34px; border-radius: var(--dt-radius); display: inline-flex; align-items: center; justify-content: center; color: var(--dt-primary); text-decoration: none; }
.dt-search-action:hover { background: color-mix(in srgb, var(--dt-primary) 12%, var(--dt-surface)); }
@media (hover: none) { .dt-search-hit-actions { opacity: 1; } }
@media (max-width: 575.98px) { .dt-search-panel { top: 0.75rem; max-height: calc(100vh - 1.5rem); } }
.sidebar-search {
    display: flex; align-items: center; gap: 0.625rem; margin: 0.5rem 0.75rem 0.25rem; padding: 0.45rem 0.7rem;
    border-radius: var(--dt-radius); border: 1px solid var(--dt-sidebar-active); background: var(--dt-sidebar-hover);
    color: var(--dt-sidebar-muted); font-size: 0.85rem; cursor: pointer; text-align: left; width: calc(100% - 1.5rem);
}
.sidebar-search:hover { background: var(--dt-sidebar-active); color: var(--dt-sidebar-text); }
.sidebar-collapsed .sidebar-search { justify-content: center; }
.sidebar-collapsed .sidebar-search .nav-label { display: none; }

@media (max-width: 575.98px) { .dt-search-hit-actions { opacity: 1; } }

/* ============================================================
   Appearance segmented control (ThemePicker: System / Light / Dark)
   Track uses neutral surface tokens; the active segment is the solid brand
   (--dt-primary + --dt-on-primary) so it "pops" and stays legible in BOTH
   themes for any tenant. Active state is chosen by the mode stamped on <html>
   (data-theme-mode) — no JS button-sync, so it survives SSR / enhanced-nav.
   ============================================================ */
.dt-theme-picker {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    background: var(--dt-surface-2);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius);
}
.dt-theme-opt {
    display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
    padding: 0.4rem 0.75rem;
    border: 0; background: transparent;
    color: var(--dt-muted);
    font-size: 0.85rem; font-weight: 600; line-height: 1; white-space: nowrap;
    border-radius: calc(var(--dt-radius) - 3px);
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease;
}
.dt-theme-opt i { font-size: 1rem; }
.dt-theme-opt:hover { color: var(--dt-text); }
/* Active segment — selected by the mode on <html>, so it needs no runtime sync. */
:root[data-theme-mode="system"] .dt-theme-opt[data-dt-mode="system"],
:root[data-theme-mode="light"]  .dt-theme-opt[data-dt-mode="light"],
:root[data-theme-mode="dark"]   .dt-theme-opt[data-dt-mode="dark"] {
    background: var(--dt-primary);
    color: var(--dt-on-primary);
    box-shadow: 0 1px 2px rgba(0,0,0,0.12);
}

/* Appearance picker lives in its own "Theme" nav SECTION (Robert S136), styled to READ AS NAV LINKS
   (like Marketing, Integrations, …) — no track/bounding box, matching padding/spacing, and the SELECTED
   mode uses the nav's own subtle active highlight (--dt-sidebar-active), NOT the brand/primary fill (that
   looked foreign in the nav). Collapses to icons with the rest of the nav when the rail is narrow. */
.sidebar-nav .dt-theme-picker {
    display: flex; flex-direction: column; gap: 1px;
    margin: 0; padding: 0; width: 100%;
    background: none; border: 0; border-radius: 0;
}
.sidebar-nav .dt-theme-opt {
    justify-content: flex-start; gap: 0.625rem;
    margin: 1px 0.5rem; padding: 0.45rem 1rem;
    font-size: 0.875rem; font-weight: 400;
    border-radius: var(--dt-radius);
    color: var(--dt-sidebar-text);
}
.sidebar-nav .dt-theme-opt i { width: 1.1rem; text-align: center; }
.sidebar-nav .dt-theme-opt:hover { background: var(--dt-sidebar-hover); color: var(--dt-sidebar-text); }
/* Selected mode = the nav's active look, overriding the base picker's primary-color active rule. */
:root[data-theme-mode="system"] .sidebar-nav .dt-theme-opt[data-dt-mode="system"],
:root[data-theme-mode="light"]  .sidebar-nav .dt-theme-opt[data-dt-mode="light"],
:root[data-theme-mode="dark"]   .sidebar-nav .dt-theme-opt[data-dt-mode="dark"] {
    background: var(--dt-sidebar-active); color: var(--dt-sidebar-text); font-weight: 600; box-shadow: none;
}
/* Collapsed rail: hide the labels (icons remain), matching how nav-links collapse. */
.sidebar-collapsed .sidebar-nav .dt-theme-opt { justify-content: center; }
.sidebar-collapsed .sidebar-nav .dt-theme-opt .nav-label { display: none; }

/* On the device drawer, stretch to the drawer width for big, thumb-friendly targets. */
.dt-drawer .dt-theme-picker { display: flex; margin: 0.35rem 1.1rem; }
.dt-drawer .dt-theme-opt { flex: 1 1 0; }

/* ============================================================================
   S80 — MOBILE-FIRST UX LANGUAGE (shared primitives)
   Desktop keeps its familiar patterns; mobile gets combos/sheets/sticky-save.
   Components: PetLabel, DtViewPicker (segments >=768px / bottom sheet <768px),
   DtEditBar (sticky Save/Cancel), DtCardActions. All theme-tokenised (--dt-*).
   ============================================================================ */

/* --- PetLabel: "Bella (Johnson)" ------------------------------------------- */
.dt-pet-owner { color: var(--dt-muted); font-weight: 400; margin-left: .25rem; }

/* --- DtViewPicker: desktop segmented control ------------------------------- */
.dt-picker-seg {
    gap: .25rem; padding: .25rem; background: var(--dt-surface-2);
    border-radius: var(--dt-radius); flex-wrap: wrap; vertical-align: middle;
}
.dt-picker-seg-btn {
    display: inline-flex; align-items: center; gap: .4rem;
    border: 0; background: transparent; color: var(--dt-muted);
    padding: .4rem .8rem; border-radius: var(--dt-radius);
    font-size: .9rem; font-weight: 600; line-height: 1; cursor: pointer; white-space: nowrap;
}
.dt-picker-seg-btn:hover { color: var(--dt-text); }
.dt-picker-seg-btn.active {
    background: var(--dt-surface); color: var(--dt-primary); box-shadow: 0 1px 2px rgba(0,0,0,.08);
}

/* --- DtViewPicker: mobile trigger (icon + current label + caret) ----------- */
.dt-picker-trigger {
    display: inline-flex; align-items: center; gap: .5rem; width: 100%;
    border: 1px solid var(--dt-border); background: var(--dt-surface); color: var(--dt-text);
    padding: .6rem .85rem; border-radius: var(--dt-radius); font-size: 1rem; font-weight: 600; line-height: 1.2; cursor: pointer;
}
.dt-picker-trigger .dt-picker-trigger-label { flex: 1 1 auto; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-picker-trigger .dt-picker-caret { color: var(--dt-muted); font-size: .85rem; }
.dt-picker-trigger > i.bi:first-child { color: var(--dt-primary); }

/* Compact filter trigger — a funnel icon that sits next to a search box; lights up when filtered. */
.dt-picker-trigger-icon {
    flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    width: 2.9rem; align-self: stretch;
    border: 1px solid var(--dt-border); background: var(--dt-surface); color: var(--dt-muted);
    border-radius: var(--dt-radius); font-size: 1.1rem; cursor: pointer;
}
.dt-picker-trigger-icon.filtered { color: var(--dt-primary); border-color: var(--dt-primary); background: var(--dt-primary-subtle); }
:root[data-theme="dark"] .dt-picker-trigger-icon.filtered { background: var(--dt-surface-2); }

/* Search + Compact-filter row: the picker wrapper is a flex child that stretches to the row height,
   but the funnel button inside it didn't fill the wrapper (came up shorter than the search box). Make
   the wrapper a flex box so the button stretches to match the search-box height exactly. */
.dt-searchfilter { align-items: stretch; }
.dt-searchfilter .dt-picker { display: flex; align-items: stretch; }
.dt-searchfilter .dt-picker-trigger-icon { height: auto; }

/* Count/badge pill (Medications 2, Upcoming 1, ...) */
.dt-picker-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.15rem; height: 1.15rem; padding: 0 .35rem;
    background: var(--dt-primary-subtle); color: var(--dt-primary);
    border-radius: 999px; font-size: .7rem; font-weight: 700; line-height: 1;
}
:root[data-theme="dark"] .dt-picker-badge { background: var(--dt-surface-2); }
/* Semantic tones — keep the meaning that colored tab badges carried (severe allergies, overdue vax). */
.dt-picker-badge.danger    { background: #DC2626; color: #fff; }
.dt-picker-badge.warning   { background: #F59E0B; color: #1F2937; }
.dt-picker-badge.success   { background: #16A34A; color: #fff; }
.dt-picker-badge.secondary { background: var(--dt-surface-2); color: var(--dt-muted); }
:root[data-theme="dark"] .dt-picker-badge.danger  { background: #B91C1C; }
:root[data-theme="dark"] .dt-picker-badge.success { background: #15803D; }

/* --- DtViewPicker: mobile bottom sheet ------------------------------------- */
.dt-sheet-scrim { position: fixed; inset: 0; z-index: 1090; background: rgba(0,0,0,.45); animation: dt-sheet-fade .15s ease; }
.dt-sheet {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1091;
    background: var(--dt-surface);
    border-top-left-radius: 1.1rem; border-top-right-radius: 1.1rem;
    box-shadow: 0 -8px 32px rgba(0,0,0,.28);
    padding: .5rem .5rem calc(.5rem + max(env(safe-area-inset-bottom), var(--dt-safe-bottom, 0px)));
    max-height: 72vh; overflow-y: auto;
    animation: dt-sheet-up .22s cubic-bezier(.32,.72,0,1);
}
@keyframes dt-sheet-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dt-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
.dt-sheet-grip { width: 2.25rem; height: .3rem; border-radius: 999px; background: var(--dt-border-strong); margin: .35rem auto .5rem; }
.dt-sheet-title { padding: .25rem .85rem .5rem; font-size: .8rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--dt-muted); }
.dt-sheet-option {
    display: flex; align-items: center; gap: .75rem; width: 100%;
    border: 0; background: transparent; color: var(--dt-text);
    padding: .85rem .85rem; border-radius: var(--dt-radius); text-align: left; font-size: 1rem; font-weight: 500; cursor: pointer;
}
.dt-sheet-option:active { background: var(--dt-surface-2); }
.dt-sheet-option.active { color: var(--dt-primary); font-weight: 700; }
.dt-sheet-option-icon { width: 1.4rem; text-align: center; color: var(--dt-muted); font-size: 1.1rem; }
.dt-sheet-option.active .dt-sheet-option-icon { color: var(--dt-primary); }
.dt-sheet-option-label { flex: 1 1 auto; }
.dt-sheet-check { color: var(--dt-primary); font-size: 1.1rem; }

/* --- DtEditBar: sticky Save/Cancel bar for edit mode ----------------------- */
/* When an object is in edit mode the page renders <DtEditBar>; it pins to the bottom, and the host
   hides the app's bottom tab bar (body.dt-editing) so the only bottom control is Cancel/Save. */
.dt-editbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1045;
    display: flex; align-items: center; gap: .6rem;
    padding: .6rem .9rem calc(.6rem + max(env(safe-area-inset-bottom), var(--dt-safe-bottom, 0px)));
    background: var(--dt-surface); border-top: 1px solid var(--dt-border);
    box-shadow: 0 -4px 16px rgba(0,0,0,.10);
}
.dt-editbar .dt-editbar-state { flex: 1 1 auto; font-size: .85rem; color: var(--dt-muted); min-width: 0; }
.dt-editbar .dt-editbar-state.dirty { color: var(--dt-warning, #B45309); font-weight: 600; }
/* Direct-child only: the default bar's own Cancel/Save stay natural width. A nested wizard row
   (.dt-editbar > .dt-actions > .btn) must NOT match this, so it can follow the .dt-actions sizing
   rules (natural on desktop, equal 50/50 halves on a phone). */
.dt-editbar > .btn { flex: 0 0 auto; }
/* Wizard/custom action row hosted in the bar (ChildContent mode): fill the bar's width and spread
   Back to the left, the primary to the right. On a phone .dt-actions already makes the buttons
   equal-width halves (the rule near the bottom of this file), so this just widens the row. */
.dt-editbar > .dt-actions { flex: 1 1 auto; width: 100%; justify-content: space-between; margin: 0; }
/* Host hides the device tab bar while editing so the sticky bar owns the bottom. */
body.dt-editing .dt-tabbar { display: none !important; }
/* Reserve room so page content isn't hidden behind the fixed edit bar. */
.dt-editbar-pad { height: calc(4.5rem + max(env(safe-area-inset-bottom), var(--dt-safe-bottom, 0px))); }

/* Desktop: start the sticky footer AT the content, not under the sidebar (S98). Gated to the shell
   breakpoint (≥992) so the mobile full-width bar over the hidden tab bar is untouched. Tracks the
   sidebar's collapsed width and animates with it. */
@media (min-width: 992px) {
    .dt-editbar { left: 240px; transition: left 0.2s ease; }
    .app-shell.sidebar-collapsed .dt-editbar { left: 76px; }
}

/* --- DtDialogScaffold + DtActionBar: uniform popups (S85) ------------------- */
/* The popup counterpart of the edit pages: CENTERED CARD on desktop, FULL-SCREEN page-style on a
   phone. Overlay scrim + a flex-column dialog (header / scrollable body / sticky action footer). */
.dt-dialog-overlay {
    position: fixed; inset: 0; z-index: 1055;
    display: flex; align-items: center; justify-content: center;
    padding: 1rem;
    background: rgba(0, 0, 0, .5);   /* scrim — intentional raw per THEMING.md */
}
.dt-dialog {
    display: flex; flex-direction: column; overflow: hidden;
    width: 100%; max-width: 560px; max-height: 90vh;
    background: var(--dt-surface); color: var(--dt-text);
    border: 1px solid var(--dt-border); border-radius: var(--dt-radius);
    box-shadow: 0 10px 40px rgba(0, 0, 0, .30);
}
.dt-dialog-header {
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: space-between; gap: .75rem;
    padding: .85rem 1rem; border-bottom: 1px solid var(--dt-border);
}
.dt-dialog-title { margin: 0; font-size: 1.05rem; font-weight: 600; color: var(--dt-text); }
.dt-dialog-body { flex: 1 1 auto; overflow-y: auto; padding: 1rem; }

/* The sticky action footer (DtActionBar) — same look as .dt-editbar but not fixed, so it rides the
   bottom of the dialog in both the full-screen and centered-card layouts. */
.dt-actionbar {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: .6rem;
    padding: .6rem .9rem calc(.6rem + max(env(safe-area-inset-bottom), var(--dt-safe-bottom, 0px)));
    background: var(--dt-surface); border-top: 1px solid var(--dt-border);
}
.dt-actionbar .dt-actionbar-status { flex: 1 1 auto; font-size: .85rem; color: var(--dt-muted); min-width: 0; }
.dt-actionbar > .btn { flex: 0 0 auto; }

/* ── Auth pages: keep the ROOT off the dark theme ───────────────────────────
   The web sign-in surface (AccountLayout's .account-bg) is a fixed light-branded page: a white card on
   the brand gradient, deliberately NOT following light/dark/system. .account-bg already pins its own
   subtree (data-theme + data-bs-theme = light), but the html/body ROOT still carries the app's dark
   theme, so a dark strip can show beyond the gradient on overscroll or when the viewport is taller than
   the document. Pin the root to the gradient's outer teal so nothing dark ever peeks on the login.
   Scoped by :has() to pages that actually render the account layout — mobile (MAUI) has no .account-bg,
   so this is inert there. */
html:has(.account-bg),
html:has(.account-bg) body {
    background: #0ABAB5;
}
/* Custom action row (ChildContent, e.g. a wizard's Back/Continue): fill + spread like DtEditBar. */
.dt-actionbar > .dt-actions { flex: 1 1 auto; width: 100%; justify-content: space-between; margin: 0; }

/* Compact = a small centered dialog on EVERY screen (tiny yes/no confirms) — never full-screen. */
.dt-dialog--compact { max-width: 420px; }
.dt-dialog--compact .dt-dialog-body { flex: 0 1 auto; }

/* Phone: a (non-compact) dialog becomes a full-screen page — same feel as a dedicated edit screen. */
@media (max-width: 575.98px) {
    .dt-dialog-overlay:not(.dt-dialog-overlay--compact) { padding: 0; }
    .dt-dialog:not(.dt-dialog--compact) {
        width: 100%; height: 100%; max-width: none; max-height: none;
        border: 0; border-radius: 0;
    }
    .dt-dialog:not(.dt-dialog--compact) .dt-dialog-header {
        padding-top: calc(.85rem + max(env(safe-area-inset-top), var(--dt-safe-top, 0px)));
    }
    /* Compact stays a centered card even on a phone; keep the overlay padding so it floats. */
}

/* --- Pencil edit affordance (top-right of an object panel) ------------------ */
.dt-edit-pencil {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem; border-radius: var(--dt-radius);
    border: 1px solid var(--dt-border); background: var(--dt-surface); color: var(--dt-muted);
    cursor: pointer; flex: 0 0 auto;
}
.dt-edit-pencil:hover { color: var(--dt-primary); border-color: var(--dt-primary); }
/* Filled state for a pencil that toggles an inline edit mode (vs. navigating to an edit page). */
.dt-edit-pencil.active { background: var(--dt-primary); border-color: var(--dt-primary); color: var(--dt-on-primary, #fff); }
/* When the pencil sits inline in an action row beside a full-height button, match that button's height
   (its default 2rem square is shorter than a .btn). Width stays 2rem; only the height stretches. */
.dt-edit-pencil.dt-edit-pencil-match { height: auto; align-self: stretch; }

/* --- Standard Back affordance (S83): pops the route stack; top-left of detail/edit screens. --------
   Pairs with the native iOS edge-swipe / Android system back — a visible, discoverable equivalent. */
.dt-back-link {
    display: inline-flex; align-items: center; gap: .1rem;
    background: none; border: none; padding: 0; margin: 0 0 .35rem;
    color: var(--dt-muted); font-size: .95rem; font-weight: 500; cursor: pointer;
}
.dt-back-link:hover { color: var(--dt-primary); }
.dt-back-link i { font-size: 1.1em; margin-right: .1rem; }

/* --- Danger zone: destructive record actions, shown only inside edit (S80 rule 7) ---------- */
.dt-dangerzone { border-color: color-mix(in srgb, var(--dt-danger) 40%, var(--dt-border)); }
.dt-dangerzone .card-header { background: var(--dt-danger-bg); border-bottom-color: color-mix(in srgb, var(--dt-danger) 30%, var(--dt-border)); }

/* --- DtCardActions: one primary + an overflow menu -------------------------- */
.dt-cardactions { display: flex; align-items: center; gap: .5rem; }
.dt-cardactions .dt-cardactions-primary { flex: 1 1 auto; }
.dt-cardactions-more {
    position: relative; flex: 0 0 auto;
    width: 2.5rem; height: 2.5rem; border-radius: var(--dt-radius);
    border: 1px solid var(--dt-border); background: var(--dt-surface); color: var(--dt-muted); cursor: pointer;
}
.dt-cardactions-menu {
    position: absolute; right: 0; bottom: calc(100% + .35rem); z-index: 20; min-width: 12rem;
    background: var(--dt-surface); border: 1px solid var(--dt-border); border-radius: var(--dt-radius);
    box-shadow: 0 8px 28px rgba(0,0,0,.22); padding: .35rem; display: flex; flex-direction: column;
}
.dt-cardactions-item {
    display: flex; align-items: center; gap: .6rem; width: 100%;
    border: 0; background: transparent; color: var(--dt-text);
    padding: .6rem .65rem; border-radius: var(--dt-radius); text-align: left; font-size: .95rem; cursor: pointer;
}
.dt-cardactions-item:active { background: var(--dt-surface-2); }
.dt-cardactions-item.danger { color: var(--dt-danger, #DC2626); }
.dt-cardactions-item i { width: 1.2rem; text-align: center; }


/* ============================================================================
   Configuration takeover shell (S158) — moved from DtConfigShell.razor.css to
   global so the static Account/Manage chrome (ManageLayout) can reuse the same
   rail. Interactive config pages (DtConfigShell) use these same classes.
   ============================================================================ */
/* Configuration takeover shell (S157, theme-aware). Full-screen overlay (rail + content) styled off the
   app's --dt-* tokens so DARK MODE is preserved (fixed-light is onboarding-only). Mirrors DtWizardShell's
   position:fixed takeover so it covers the app chrome in both the web and Blazor-Hybrid hosts. */

.dtc-root {
    position: fixed;
    inset: 0;
    z-index: 1045; /* above the app chrome + mobile tab bar, below Bootstrap modals (1055) */
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    background: var(--dt-bg);
    color: var(--dt-text);
    padding-bottom: var(--dt-safe-bottom, 0);
}

/* ── Top bar: title left, Exit right. Solid black bar with the tenant's PRIMARY as the only accent
   (Robert, S158) — a calm two-tone header instead of the black/white/teal/purple mishmash. ── */
.dtc-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    padding: 0.7rem clamp(1rem, 3vw, 2rem);
    padding-top: calc(0.7rem + var(--dt-safe-top, 0px));
    background: #000;
    border-bottom: 1px solid var(--dt-border);
    flex: 0 0 auto;
}
.dtc-topbar-left {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
}
.dtc-title-icon { color: var(--dt-primary); }
.dtc-title {
    font-weight: 800;
    font-size: 1.1rem;
    letter-spacing: -0.01em;
    color: var(--dt-primary);
}
.dtc-exit {
    border: 0;
    background: transparent;
    color: var(--dt-primary);
    font-weight: 700;
    padding: 0.35rem 0.5rem;
    border-radius: 8px;
    white-space: nowrap;
}
.dtc-exit:hover { text-decoration: underline; }

/* ── Body: rail + content fill the rest, each scrolls independently ── */
.dtc-body {
    flex: 1 1 auto;
    display: flex;
    min-height: 0;
}

/* ── Desktop rail ── */
.dtc-rail {
    flex: 0 0 250px;
    width: 250px;
    flex-direction: column;
    border-right: 1px solid var(--dt-border);
    background: var(--dt-surface-2, var(--dt-surface));
    min-height: 0;
}
.dtc-rail-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.9rem 0.75rem;
}
.dtc-rail-footer {
    flex: 0 0 auto;
    border-top: 1px solid var(--dt-border);
    padding: 0.6rem 0.75rem calc(0.6rem + var(--dt-safe-bottom, 0px));
}

.dtc-group + .dtc-group { margin-top: 1.1rem; }
.dtc-group-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--dt-muted);
    padding: 0 0.55rem 0.4rem;
}

.dtc-item {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    color: var(--dt-text);
    text-decoration: none;   /* the static (Account) rail renders items as <a> — no underline */
    font-size: 0.95rem;
    font-weight: 500;
    padding: 0.55rem 0.6rem;
    border-radius: 10px;
    line-height: 1.2;
}
.dtc-item:hover { text-decoration: none; }
.dtc-item + .dtc-item { margin-top: 0.15rem; }
.dtc-item i {
    font-size: 1.05rem;
    width: 1.35rem;
    text-align: center;
    color: var(--dt-muted);
    flex: 0 0 auto;
}
.dtc-item:hover {
    background: var(--dt-surface);
    color: var(--dt-text);
}
.dtc-item:hover i { color: var(--dt-text); }
.dtc-item.active {
    background: color-mix(in srgb, var(--dt-primary) 15%, transparent);
    color: var(--dt-primary);
    font-weight: 700;
}
.dtc-item.active i { color: var(--dt-primary); }

/* ── Content ── */
.dtc-content {
    flex: 1 1 auto;
    overflow-y: auto;
    min-width: 0;
    padding: 1.5rem clamp(1rem, 3vw, 2.25rem) 2.5rem;
    max-width: 1100px;
}
.dtc-mobile-picker { margin-bottom: 1.25rem; }

@media (max-width: 767.98px) {
    .dtc-content { padding: 1.1rem clamp(0.85rem, 4vw, 1.5rem) 2.5rem; }
}
