/* *****************************************************

    ** Custom Stylesheet **

    Any custom styling you want to apply should be
    defined here.

***************************************************** */

/* Custom CSS for Vercaa 2026 Theme
 *
 * This file allows you to customize the theme colors and styles for the entire Vercaa 2026 template.
 * It is the SINGLE SOURCE OF TRUTH for color/design tokens across both this classic Smarty client
 * area AND the Vue/Shadow-DOM cart+domain-search app (templates/orderforms/vercaa-2026-cart), whose
 * own --vl-* variables reference these same tokens (--vl-primary: var(--primary) etc.) — CSS custom
 * properties inherit through the Shadow DOM boundary even though regular styles don't, so edit colors
 * ONLY here, never in the cart template's own custom.css.
 *
 * Loaded AFTER theme.min.css (see includes/head.tpl), so anything declared here safely overrides the
 * compiled Sass output via normal cascade — no Sass build step is required for changes here to take
 * effect. (There is no package.json/gulpfile/node_modules in this webroot, so sass/*.scss cannot
 * currently be rebuilt in this environment; theme.css/theme.min.css already reference many of these
 * same custom properties directly — e.g. var(--bg-inverted), var(--success), var(--text) — so editing
 * the token values below re-themes those compiled rules too. New component CSS that theme.css does NOT
 * already have hooks for — the app shell header/sidebar, modals, badges, etc. — is authored directly in
 * this file instead of hand-patching the 290KB compiled theme.css.)
 */

:root {
    --white: #fff;

    /* Neutral shades (light mode base) */
    --neutral-50: #fbf9fa;
    --neutral-100: #f4f5f7;
    --neutral-200: #e4e4e7;
    --neutral-300: #d0d5dd;
    --neutral-400: #9ca3af;
    --neutral-500: #6b7280;
    --neutral-600: #4b5563;
    --neutral-700: #374151;
    --neutral-800: #1f2937;
    --neutral-900: #111827;
    --neutral-950: #030712;

    /* Primary = the REAL Vercaa purple (2026-08-13 foundational retheme, superseding the 2026-08
       green rebalance above/below this comment — that rebalance was working from an assumption, not
       the client's actual live site; #a83fff was sampled directly via getComputedStyle from the real
       production vercaa.com on 2026-08-13, the exact same sampling already anchoring the 7 Market
       Connect pages' own --mc-purple token in market/_tokens.css, so this now matches that sibling
       surface exactly rather than competing with it). Purple IS the dominant/primary accent on the
       real site — main CTA buttons, links, the logo — not a minor accent. -lifted is the real site's
       own hover shade (--mc-purple-dark, #8b2fd9, 5.99:1 on white — used for hover/press, not just a
       mechanical darken). -accented is a hand-tuned deep ink (8.7:1 on white, 7.4:1 on -soft) for
       plain link/label text and soft-tint icon chips, since the old green family's derived shades
       were tuned for green's lightness/saturation and would misread on purple. -soft is a pale opaque
       tonal tint for light-mode chip/badge backgrounds (see --primary-soft's dark-mode translucent
       redefinition below for the dark-surface equivalent). */
    --primary: #a83fff;
    --primary-lifted: #8b2fd9;
    --primary-accented: #6b21a8;
    --primary-soft: #f3e9fb;
    /* -glow (2026-08-14 dark-mode contrast fix) — a real WCAG relative-luminance audit against the
       live dark theme found #a83fff itself sits at a lightness where NEITHER white nor near-black
       text clears 4.5:1 against it as a solid fill (white on #a83fff: 4.36:1; dark ink: 4.25:1), AND
       raw #a83fff used directly AS text-on-dark-surface (the "Available" domain-status label,
       "SAVE X%" badges, --primary-accented's old dark-mode alias) only manages 4.33-4.5:1 depending
       on which dark surface it sits on. No light-mode default is exercised (--primary-glow is inert
       until dark mode redefines it below) — kept defined here per this file's own convention of every
       token getting a base :root value. See the dark-mode block below for the real value + math. */
    --primary-glow: var(--primary);

    /* Secondary = Vercaa Orange, tightened to the real site's exact sampled value (--mc-orange in
       market/_tokens.css). Used for secondary CTAs, promo/discount badges, highlights, and (via
       --warning below) the pending/warning role. */
    --secondary: #f97316;
    --secondary-lifted: #db600b;
    --secondary-accented: #a54909;
    --secondary-soft: #fdeadd;

    /* Success = its OWN independently-defined green — deliberately NOT derived from --primary
       anymore (2026-08-13: --primary and --success used to share the exact hex #5ec269, a semantic
       conflation from the prior green-primary era; now that --primary is purple, every "paid" /
       "active" / checkmark / positive-state usage sitewide needs --success to keep reading as
       unambiguously green on its own). Nudged from the old #5ec269 toward the real site's supporting
       green (--mc-green, #22c55e) — a minor refinement (hue 127°→130°, richer/deeper), not a
       wholesale swap, per instruction: the point was decoupling, not reinventing the green. -accented
       was hand-picked (not mechanically derived) to clear 4.5:1 on both -soft (5.5:1) and white
       (6.1:1) — a real improvement over the pre-existing green family's own 3.9:1 on -soft. */
    --success: #4cc661;
    --success-lifted: #3ba44d;
    --success-accented: #297035;
    --success-soft: #e5f8e8;

    /* Accent Purple — RETIRED as a second, competing purple hue (2026-08-13). Now that --primary
       itself is Vercaa's real purple, keeping a separate --accent-purple family at a different
       lightness/saturation would just be two visually-similar-but-not-identical purples fighting for
       the same "this is the purple token" role sitewide (24+ real call sites: MarketConnect category
       icons/empty-states, SSL EV certificate styling, ticket "in progress" avatar + stat tile, promo
       tooltips, sidebar active-nav indicator, language-modal decoration). Aliased to the --primary
       family via live var() references (not new hex) so every one of those call sites inherits the
       real brand purple automatically — no per-call-site edits needed, and MarketConnect in
       particular now correctly shares its accent color with the already-built, purple-branded
       standalone market/*.php pages instead of a slightly-off second purple. Token names kept for
       backward compatibility with existing call sites. */
    --accent-purple: var(--primary);
    --accent-purple-lifted: var(--primary-lifted);
    --accent-purple-accented: var(--primary-accented);
    --accent-purple-soft: var(--primary-soft);

    /* Info = neutral informational blue — deliberately NOT one of the 3 brand colors (brand doc §12),
       so 2FA notices / card-authorization banners read as neutral info, not brand or warning. Aligned
       to the real site's exact sampled blue (--mc-blue in market/_tokens.css, "seen on the real
       site's info/tech icons" — a direct semantic match for this token's own role). */
    --info: #3b82f6;
    --info-lifted: #1d6ae7;
    --info-accented: #1456c2;
    --info-soft: #e8f0fc;

    /* Notice = a distinct indigo, deliberately hue-shifted (2026-08-13) away from the old #6d5df2
       violet, which sat too close to the new --primary purple (H246 vs H273 — read as "the same
       purple, slightly off" when the two appear near each other, e.g. affiliates.tpl's stat-tile row
       which places --info/--success/--notice tiles side by side). #6366f1 sits roughly equidistant
       between --info's blue (H217) and --primary's purple (H273), reading as its own clearly-distinct
       "indigo" family in either company, not a muddy near-purple. Still low real usage (one call
       site, .app-stat-tile-notice) so this is a hue correction, not a promotion to heavy use. */
    --notice: #6366f1;
    --notice-lifted: #5154dd;
    --notice-accented: #4144c3;
    --notice-soft: #e9e9fe;

    /* Warning reuses the Orange secondary hue per brand doc §12 ("Warning / pending" -> Vercaa Orange). */
    --warning: #f97316;
    --warning-lifted: #db600b;
    --warning-accented: #a54909;
    --warning-soft: var(--secondary-soft);

    /* Error = a distinct red, never reused for warning/promo, so orange doesn't do double duty. */
    --error: #e11d3c;
    --error-lifted: #c81733;
    --error-accented: #a01229;
    --error-soft: #fbe4e8;
    /* -glow (2026-08-14 dark-mode contrast sweep finding): raw --error as text-on-dark (e.g. the
       avatar dropdown's "Logout" link, .app-dropdown-item-danger) only clears 4.02-4.14:1 on the
       real dark surfaces -- same underlying issue as --primary's, just narrower. Inert in light
       mode, same convention as --primary-glow. */
    --error-glow: var(--error);

    /* Grayscale colors (light-mode default: near-black text/ink) */
    --grayscale: var(--neutral-900);
    --grayscale-lifted: var(--neutral-800);
    --grayscale-accented: var(--neutral-700);

    /* Neutral colors */
    --neutral: var(--neutral-500);
    --neutral-lifted: var(--neutral-600);
    --neutral-accented: var(--neutral-700);

    /* Text neutral colors */
    --text-inverted: var(--white);
    --text-muted: var(--neutral-400);
    --text-lifted: var(--neutral-500);
    --text-accented: var(--neutral-600);
    --text: var(--neutral-900);

    /* Border neutral colors */
    --border-muted: var(--neutral-200);
    --border: var(--neutral-300);
    --border-lifted: var(--neutral-400);
    --border-accented: var(--neutral-600);

    /* Background neutral colors */
    --bg: var(--white);
    --bg-muted: var(--neutral-50);
    --bg-lifted: var(--neutral-100);
    --bg-accented: var(--neutral-200);
    --bg-inverted: var(--neutral-900);

    /* Additional colors (unchanged, kept for any existing component that references them) */
    --yellow-200: #fff085;
    --yellow-300: #ffdf20;
    --teal-300: #46edd5;
    --teal-400: #00d5be;
    --emerald-300: #5ee9b5;
    --pink-400: #fb64b6;

    /* Font sizes */
    --text-xs: 0.625rem;
    --text-sm: 0.75rem;
    --text-md: 0.875rem;
    --text-lg: 1rem;

    /* Typography — heading/display font (2026-08-12 foundational pass, closing the "looks like
       default WHMCS + colors" gap: "Modern Professional" pairing — geometric/confident Poppins for
       headings+display text, paired with the existing Open Sans body copy, the standard SaaS/
       corporate-app pairing). --font-body made an explicit, referenceable token too (previously the
       Open Sans family was only ever applied via a hardcoded `body{font-family:"Open Sans",sans-serif}`
       rule baked into the compiled theme.min.css, not a real custom property). */
    --font-heading: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-body: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

    /* Spacing */
    --outline-sm: 1px;
    --outline-md: 2px;
    --outline-lg: 3px;

    /* Rounding — sm/md/lg/xl (4/8/12/16px) stay the small-to-medium control/card scale. Added
       --rounding-2xl (2026-08-13 foundational retheme) at 28px to match the Market Connect pages'
       own --mc-radius-xl exactly (market/_tokens.css) — used deliberately, not just defined, on the
       hero-scale surfaces that should read at that same generous "premium" radius (the gradient
       banner shell); everyday cards/controls stay on the existing sm/md/lg/xl steps so this doesn't
       get forced everywhere. */
    --rounding-sm: 0.25rem;
    --rounding-md: 0.5rem;
    --rounding-lg: 0.75rem;
    --rounding-xl: 1rem;
    --rounding-2xl: 1.75rem;

    /* Elevation — soft multi-layer shadow scale. Enriched 2026-08-13 to match the noticeably richer,
       softer "floating" shadow language already shipped on the Market Connect pages (market/*.php,
       e.g. --ssl-shadow: 0 24px 70px -36px rgba(15,17,21,.35)) instead of the older flatter single-
       layer values: each level is now a tight contact shadow + a soft, wide, negative-spread ambient
       shadow layered together (same dual-layer technique as those pages' --es-shadow-sm/--ox-shadow-
       2), using the same rgba(15,17,21,…) near-black "ink" tint as their --mc-ink rather than pure
       black, for a slightly warmer, less harsh shadow. 4 levels unchanged in meaning: 1 = resting
       card surface, 2 = hover/raised state, 3 = dropdowns/popovers/sticky elements, 4 = modals/
       dialogs (the domain-search modal's own literal `0 20px 60px rgba(0,0,0,.35)` shadow remains
       its own independent value, out of this pass's scope, per the existing note below). */
    --elevation-1: 0 1px 2px rgba(15, 17, 21, 0.04), 0 6px 16px -6px rgba(15, 17, 21, 0.08);
    --elevation-2: 0 3px 8px rgba(15, 17, 21, 0.06), 0 14px 32px -12px rgba(15, 17, 21, 0.16);
    --elevation-3: 0 6px 16px rgba(15, 17, 21, 0.08), 0 24px 48px -16px rgba(15, 17, 21, 0.2);
    --elevation-4: 0 12px 28px rgba(15, 17, 21, 0.12), 0 32px 70px -24px rgba(15, 17, 21, 0.3);

    /* Other */
    --letter-spacing: 0em;
    --disabled-opacity: 25%;

    /* -----------------------------------------------------------------
       Motion tokens — measured live from hosting.com (see
       docs/hosting-com-motion-interaction-reference.md), not guessed.
       ----------------------------------------------------------------- */
    --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);  /* buttons/fills (Tailwind "ease") */
    --ease-out: cubic-bezier(0, 0, 0.2, 1);          /* hover backgrounds / nav items */
    --duration-fast: 150ms;   /* dropdowns/popovers (avatar menu, notif panel entrance) */
    --duration-base: 200ms;   /* modals, notification panel */
    --duration-slow: 300ms;   /* button hover fills */
    --duration-drawer-open: 500ms;  /* mobile sidebar drawer opening (asymmetric, per spec) */
    --duration-drawer-close: 300ms; /* mobile sidebar drawer closing */

    /* Stacked focus-visible ring: 2px "gap" ring + accent ring, mirrors :active for press feedback. */
    --focus-ring-color: var(--primary);
    --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring-color);

    /* Always-on-brand gradient — intentionally declared OUTSIDE the [data-theme="dark"] block below so
       hero/promo banners never invert with the light/dark toggle (brand doc §12 "Dark-mode
       conventions"). Rebuilt purple-forward (2026-08-13) to match the real vercaa.com homepage's own
       hero treatment: a soft, layered, multi-color radial WASH rather than a hard 2-stop diagonal
       fill (the closest already-built local reference is monitoring.php's `.mp-hero-glow`, three
       radial color blooms over the page background) — reproduced here as radial blooms layered over
       an opaque purple linear base instead of a plain white page, since this token fills solid
       banner/card surfaces with white text on top and needs to stay dark/rich enough for that text to
       stay readable throughout, not just at one edge. Built entirely from existing tokens (var(--
       primary/-lifted/-accented), var(--info), var(--secondary/-accented)) plus color-mix() — no new
       hex literals — so it stays in sync automatically if those tokens are ever retuned. -warm swaps
       the cool blue corner bloom for a warm orange one and folds a touch of --secondary into the
       deepest stop, same "nods to orange without looking dull" relationship the old green version had
       with its gold midpoint. */
    --gradient-brand:
        radial-gradient(130% 160% at 0% 0%, rgba(255, 255, 255, 0.16) 0%, transparent 42%),
        radial-gradient(90% 130% at 100% 100%, color-mix(in srgb, var(--info) 42%, transparent) 0%, transparent 55%),
        linear-gradient(135deg, var(--primary) 0%, var(--primary-lifted) 55%, var(--primary-accented) 100%);
    --gradient-brand-warm:
        radial-gradient(130% 160% at 0% 0%, rgba(255, 255, 255, 0.18) 0%, transparent 42%),
        radial-gradient(90% 130% at 100% 100%, color-mix(in srgb, var(--secondary) 55%, transparent) 0%, transparent 55%),
        linear-gradient(135deg, var(--primary) 0%, var(--primary-lifted) 50%, color-mix(in srgb, var(--secondary-accented) 55%, var(--primary-accented) 45%) 100%);

    /* App-shell layout tokens */
    --header-height: 64px;
    --sidebar-width: 263px;
    --sidebar-width-collapsed: 78px;
}

/* -----------------------------------------------------------------------
   Dark mode — near-black bg with a slight cast, card surface one step
   lighter, off-white heading text, mid-gray secondary text (brand doc §12
   "Dark-mode conventions"). Toggled via data-theme="dark" on <html> by
   js/app-shell.js and persisted to localStorage.
   ----------------------------------------------------------------------- */
:root[data-theme="dark"] {
    --bg: #0c0b12;
    --bg-muted: #121019;
    --bg-lifted: #1b1926;
    --bg-accented: #262433;
    --bg-inverted: #f4f5f7;

    --text: #f5f3f8;
    --text-accented: #d9d6e1;
    --text-lifted: #ada9bc;
    /* Bumped 2026-08-14 (dark-mode contrast audit) from #78748a — that value cleared only 4.35:1 on
       --bg and 4.19:1 on --bg-muted (a real fail; e.g. the domain-search modal's "Was $X.XX"
       strikethrough price, .app-domain-row-strike, sits on --bg-muted). #9390a2 is the same hue/
       saturation lifted a few points, clearing all 4 dark surfaces in real use: 6.30:1 on --bg,
       6.06:1 on --bg-muted, 5.56:1 on --bg-lifted, 4.88:1 on --bg-accented. Still clearly a step below
       --text-lifted (8.5+:1) so the muted/secondary hierarchy is preserved, just no longer failing. */
    --text-muted: #9390a2;
    --text-inverted: #14121c;

    --border: #2c2a3a;
    --border-muted: #201e2a;
    --border-lifted: #3c3950;
    --border-accented: #504c68;

    --grayscale: #f5f3f8;
    --grayscale-lifted: #d9d6e1;
    --grayscale-accented: #ada9bc;

    --neutral: #ada9bc;
    --neutral-lifted: #928da3;
    --neutral-accented: #78748a;

    /* Soft brand tints become translucent overlays instead of pale flat fills on a dark base.
       Recomputed 2026-08-13 from the new purple/orange/green/blue/indigo bases above (same alpha per
       color this file already used, just fed the new hex). --accent-purple-soft is intentionally NOT
       listed here anymore: it's aliased to var(--primary-soft) up in :root now (see the "Accent
       Purple — RETIRED" comment), and that's a live var() reference, not a literal hex, so it already
       auto-resolves to the translucent purple overlay defined below without needing its own
       dark-mode line — one less place to keep in sync. */
    --primary-soft: rgba(168, 63, 255, 0.18);
    --secondary-soft: rgba(249, 115, 22, 0.18);
    --success-soft: rgba(76, 198, 97, 0.18);
    --warning-soft: var(--secondary-soft);
    --error-soft: rgba(225, 29, 60, 0.2);
    --info-soft: rgba(59, 130, 246, 0.22);
    --notice-soft: rgba(99, 102, 241, 0.2);

    /* The "-accented" shades (e.g. --success-accented) were tuned as dark ink sitting on PALE
       light-mode "-soft" pastel fills. On the translucent dark "-soft" overlays defined just above,
       that same dark ink reads as a muddy, low-contrast smudge — every .app-status-badge-*, .alert-*,
       .app-icon-circle-*, .app-text-success etc. pairing "-soft" bg with "-accented" text/icon color
       is affected sitewide (Home dashboard, Products & Services, Invoices, Domains empty state,
       Language/Currency modal). Fix: re-point each "-accented" token to its own already-defined
       bright base token for dark mode — same reuse-an-existing-token pattern this file's own
       domain-search modal already ships (.app-domain-modal's --domain-ink swaps var(--primary-
       accented) -> var(--primary) in dark mode for the identical reason). No new hex values
       introduced. --accent-purple-accented again needs no line of its own here: it's `var(--primary-
       accented)` in :root, a live reference, so it automatically picks up --primary-accented's
       dark-mode value (var(--primary)) the moment that's redefined below — the alias chain does the
       work that used to need its own explicit override. */
    /* --primary-accented used to alias straight to var(--primary) here (raw #a83fff) "for the
       identical reason" domain-ink's own dark override did (see that token's comment) — i.e. swap the
       muddy light-mode ink for something brighter. But raw --primary itself only clears 4.33-4.5:1 on
       the real dark surfaces (--bg-muted/--bg), which is where the ~60 sitewide consumers of
       --primary-accented-as-text (icon chips on --primary-soft, KB/landing-page links, welcome-stat
       icons, etc.) actually sit — a real, if less dramatic, AA fail. Repointed 2026-08-14 to the new
       --primary-glow (#b55cff, 5.3-5.5:1 on those same surfaces) instead. NOTE: a handful of
       *background-fill* consumers of --primary-accented (button :active states, the domain-upsell
       banner CTA, 2 SSL landing-page gradients) do NOT go through this alias anymore in dark mode —
       they're explicitly repointed to --primary-lifted via their own dark-mode overrides further down
       this file, since a brighter fill would only make their hardcoded white text less readable, not
       more. --primary-glow's own value only exists in dark mode (see --text-muted comment above for
       the sibling fix); it is NOT reused for light mode. */
    --primary-accented: var(--primary-glow);
    /* The new bright text-on-dark purple. Same H273/S100 hue+saturation as --primary itself, lightness
       lifted from L62 to L68 (i.e. a real "-glow" derivation of the brand color, not an unrelated
       hex) — clears 4.5:1+ on every real dark surface in use: 5.54:1 on --bg (#0c0b12), 5.33:1 on
       --bg-muted (#121019, the domain-search modal's own dialog background), 4.89:1 on --bg-lifted
       (#1b1926, card surfaces). Used for --primary-accented's dark alias above, the domain-search
       modal's --domain-ink (see domain-search-modal.css), and .text-primary/.border-primary/
       .btn-outline-primary's resting state below. */
    --primary-glow: #b55cff;
    --secondary-accented: var(--secondary);
    --success-accented: var(--success);
    --warning-accented: var(--warning);
    --info-accented: var(--info);
    --notice-accented: var(--notice);
    /* Real bright value (WCAG derivation: same H350/S77 hue+saturation as --error, lightness lifted
       L50->L61) -- clears 4.5:1+ on --bg (5.36), --bg-muted (5.16), --bg-lifted (4.73). All 5 real
       consumers of --error-accented are text-on-soft/generic-text roles (.alert-danger,
       .app-text-error, .app-icon-circle-danger, the KB downvote button) -- no background-fill
       consumers found, so (unlike --primary-accented) this one token can safely serve every consumer
       without a separate -lifted-style carve-out. */
    --error-glow: #e84f67;
    --error-accented: var(--error-glow);

    --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--focus-ring-color);

    /* Elevation shadows need noticeably more alpha on a dark base to read as depth at all — a
       light-mode-alpha shadow is nearly invisible against a page that's already near-black. Enriched
       2026-08-13 alongside the light-mode dual-layer rebuild (see that comment above): same tight-
       contact + wide-ambient two-layer shape, scaled up in blur/spread/offset to match, alpha raised
       further still per the existing "dark needs more alpha to read at all" principle. */
    --elevation-1: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 16px -6px rgba(0, 0, 0, 0.35);
    --elevation-2: 0 3px 8px rgba(0, 0, 0, 0.35), 0 14px 32px -12px rgba(0, 0, 0, 0.45);
    --elevation-3: 0 6px 16px rgba(0, 0, 0, 0.4), 0 24px 48px -16px rgba(0, 0, 0, 0.5);
    --elevation-4: 0 12px 28px rgba(0, 0, 0, 0.45), 0 32px 70px -24px rgba(0, 0, 0, 0.6);
}

/* =========================================================================
   Global component rebrand — Bootstrap utility classes used site-wide that
   don't already reference the token system in compiled theme.css.
   ========================================================================= */
/* .btn-primary / .btn-outline-primary — genuine hover/press refresh (2026-08-13 foundational
   retheme), not just a color swap: hover now moves to the real site's own hover shade
   (--primary-lifted, matching the exact `background:var(--mc-purple-dark)` pattern already shipped
   on the Market Connect pages' buttons) with a soft colored glow + a 1px lift, instead of the old
   same-color-plus-brightness-filter treatment. Press/active settles back down onto --primary-
   accented with a tighter, closer shadow — a believable "pressed" read. */
.btn-primary {
    /* 2026-08-25 light-mode contrast fix (DEFERRED.md §11.2): resting-state white text on raw
       --primary was 4.36:1, fails WCAG AA's 4.5:1 minimum -- the site's own primary CTA button,
       in its own default/light theme. Real WCAG relative-luminance math (both independently
       re-derived): white on --primary-lifted (#8b2fd9) = 5.99:1. Exactly mirrors the 2026-08-14
       dark-mode fix a few lines below (:root[data-theme="dark"] .btn-primary already uses this
       same --primary-lifted value for the identical reason) -- this is that same fix, applied to
       light mode's own base rule. :hover (--primary-lifted, 5.99:1) and :active (--primary-
       accented, 8.72:1) below were ALREADY safe -- only resting/idle state was ever broken. */
    background: var(--primary-lifted);
    border-color: var(--primary-lifted);
    color: var(--text-inverted, #fff);
    box-shadow: 0 2px 8px -2px color-mix(in srgb, var(--primary) 45%, transparent);
    transition: background-color var(--duration-slow) var(--ease-standard), border-color var(--duration-slow) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary.focus {
    background: var(--primary-lifted);
    border-color: var(--primary-lifted);
    color: #fff;
    box-shadow: 0 10px 22px -6px color-mix(in srgb, var(--primary) 55%, transparent);
    transform: translateY(-1px);
}
.btn-primary:not(:disabled):not(.disabled):active,
.btn-primary:not(:disabled):not(.disabled).active {
    background: var(--primary-accented);
    border-color: var(--primary-accented);
    color: #fff;
    box-shadow: 0 2px 6px -2px color-mix(in srgb, var(--primary) 45%, transparent);
    transform: translateY(0);
}
.btn-outline-primary {
    color: var(--primary);
    border-color: var(--primary);
    background: transparent;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard), border-color var(--duration-slow) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.btn-outline-primary:hover {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
    box-shadow: 0 10px 22px -6px color-mix(in srgb, var(--primary) 45%, transparent);
    transform: translateY(-1px);
}
.btn-outline-primary:not(:disabled):not(.disabled):active,
.btn-outline-primary:not(:disabled):not(.disabled).active {
    background: var(--primary-accented);
    border-color: var(--primary-accented);
    color: #fff;
    transform: translateY(0);
}
.badge-primary { background-color: var(--primary); }
.text-primary { color: var(--primary) !important; }
.bg-primary { background-color: var(--primary) !important; }
.border-primary { border-color: var(--primary) !important; }

/* ---------------------------------------------------------------------
   Dark-mode contrast fix (2026-08-14, real WCAG relative-luminance audit
   against the live dark theme — see the --primary-glow/--text-muted token
   comments above for the underlying numbers). Two distinct problems, two
   distinct fixes, neither touching --primary's own real-sampled-from-
   vercaa.com hex:
     1. Solid-fill buttons/badges/pills that use raw --primary as a
        BACKGROUND with white text (Continue/Search buttons, "Added to
        basket" success states, active pill-tabs) only clear 4.25-4.36:1 in
        dark mode -> swapped to the EXISTING --primary-lifted (white text on
        #8b2fd9 is 5.99:1, already comfortably AA in every mode -- no new
        token needed for this half).
     2. Text/border colored directly AS --primary on a dark surface
        (Bootstrap .text-primary/.border-primary, .btn-outline-primary's
        resting state) only clears 3.97-4.5:1 -> swapped to the new
        --primary-glow (5.3-5.5:1 on the real dark surfaces in use).
   --------------------------------------------------------------------- */
:root[data-theme="dark"] .btn-primary {
    background: var(--primary-lifted);
    border-color: var(--primary-lifted);
    color: #fff;
}
:root[data-theme="dark"] .btn-primary:not(:disabled):not(.disabled):active,
:root[data-theme="dark"] .btn-primary:not(:disabled):not(.disabled).active {
    background: var(--primary-lifted);
    border-color: var(--primary-lifted);
}
:root[data-theme="dark"] .btn-outline-primary {
    color: var(--primary-glow);
    border-color: var(--primary-glow);
}
:root[data-theme="dark"] .btn-outline-primary:hover,
:root[data-theme="dark"] .btn-outline-primary:not(:disabled):not(.disabled):active,
:root[data-theme="dark"] .btn-outline-primary:not(:disabled):not(.disabled).active {
    background: var(--primary-lifted);
    border-color: var(--primary-lifted);
}
:root[data-theme="dark"] .badge-primary { background-color: var(--primary-lifted); }
:root[data-theme="dark"] .bg-primary { background-color: var(--primary-lifted) !important; }
:root[data-theme="dark"] .text-primary { color: var(--primary-glow) !important; }
:root[data-theme="dark"] .border-primary { border-color: var(--primary-glow) !important; }

/* --secondary/orange notification/cart-count badge (.app-icon-badge, header) — white text on raw
   --secondary (#f97316) is only 2.8:1 (fails badly, not borderline, in ANY theme since --secondary
   itself never varies by theme). Scoped as a dark-mode-only fix per this task's scope (light mode
   "was NOT reported as broken", left untouched). Text swapped to var(--text-inverted) rather than
   darkening the fill: --secondary-accented is NOT usable here — like --primary-accented, it's
   already re-aliased to raw var(--secondary) inside this same dark block (for its own, separate,
   correct text-on-soft-tint-chip role a few dozen lines down) so referencing it from here would
   silently resolve to the unchanged, still-failing #f97316. --secondary-lifted (#db600b) was also
   checked and rejected: still only 3.69:1 with white text, not enough. --text-inverted's real
   dark-mode value (#14121c, defined in this same :root[data-theme="dark"] block) against #f97316 is
   6.61:1 — comfortably AA, and keeps the badge's fill the real, unaltered brand orange. */
:root[data-theme="dark"] .app-icon-badge { color: var(--text-inverted); }

/* The following are every OTHER real sitewide consumer that fills a solid background straight from
   var(--primary) (not --primary-lifted) and pairs it with hardcoded white text — same underlying
   4.25-4.36:1 fail as .btn-primary above, just each its own one-off selector instead of going through
   a shared class. Swapped to --primary-lifted for the identical reason. */
:root[data-theme="dark"] .app-domain-upsell-btn { background: var(--primary-lifted); }
:root[data-theme="dark"] .tld-filters a.app-pill-btn.active { background: var(--primary-lifted); border-color: var(--primary-lifted); }
:root[data-theme="dark"] .vhp-hero-search-card #btnDomainSearch,
:root[data-theme="dark"] .vhp-hero-search-card #btnDomainSearch2 { background: var(--primary-lifted) !important; border-color: var(--primary-lifted) !important; }
:root[data-theme="dark"] .landing-page.threesixtymonitoring .btn-primary { background-color: var(--primary-lifted) !important; border-color: var(--primary-lifted) !important; }
:root[data-theme="dark"] .vrg-rail-item.is-active .vrg-rail-marker { background: var(--primary-lifted); border-color: var(--primary-lifted); }
:root[data-theme="dark"] .landing-page.ssl .certificate-options { background: linear-gradient(135deg, var(--primary-lifted) 0%, var(--primary) 100%) !important; }
:root[data-theme="dark"] .landing-page.ssl .validation-levels { background: linear-gradient(135deg, var(--primary-lifted) 0%, var(--primary) 60%, var(--secondary-accented) 130%) !important; }
/* Second sweep pass (2026-08-14) — a handful more turned up on a full-file grep for
   "background: var(--primary)"/"var(--secondary)" beyond the ones already handled above:
   pill-select "selected" states, the ticket-open page's own copy of that same component, a client-
   area focus/hover fallback, the order form's ion-range-slider fill+value tooltip, and several
   third-party-product landing pages (SSL, CodeGuard, OX comparison table, NordVPN). Same two fixes as
   everywhere else: --primary-as-fill -> --primary-lifted; --secondary-as-fill kept as the real brand
   orange, text swapped to --text-inverted (6.61:1) instead, for the same reason documented on
   .app-icon-badge above. */
:root[data-theme="dark"] .app-nav-badge { color: var(--text-inverted); }
:root[data-theme="dark"] .app-notif-panel .app-empty-state-icon { color: var(--text-inverted); }
:root[data-theme="dark"] .app-pill-select-option.selected,
:root[data-theme="dark"] .app-pill-select-option:has(input:checked) { background: var(--primary-lifted); border-color: var(--primary-lifted); }
:root[data-theme="dark"] .tix-open-page .app-pill-select-option.selected,
:root[data-theme="dark"] .tix-open-page .app-pill-select-option:has(input:checked) { color: var(--text-inverted); }
:root[data-theme="dark"] .module-client-area .btn:focus,
:root[data-theme="dark"] .module-client-area a.autoLinked:hover { background: var(--primary-lifted); }
:root[data-theme="dark"] body #order-standard_cart .irs-bar,
:root[data-theme="dark"] body #order-standard_cart .irs-single { background: var(--primary-lifted) !important; }
:root[data-theme="dark"] .landing-page.ssl .validation-levels .item .btn { background: var(--primary-lifted) !important; }
:root[data-theme="dark"] .landing-page.codeguard .order-btn { background: var(--primary-lifted) !important; }
:root[data-theme="dark"] .landing-page.ox .pricing th { background: var(--primary-lifted) !important; }
:root[data-theme="dark"] .landing-page.ox .pricing th.feature { color: var(--text-inverted) !important; }
:root[data-theme="dark"] .nord-cycle-toggle button.active { background: var(--primary-lifted); }
/* Third sweep pass (2026-08-14) — found live, via an actual getComputedStyle contrast probe on a
   real logged-in dashboard page, not just static grep: the "Manage" row-action button
   (.app-row-action-btn, Products & Services / Domains lists), the active pill-tab's count bubble,
   the ticket thread's staff-avatar initials, the "How can we help" quick-link icon hover state, and
   the standalone hosting-page hero's .vhp-btn-primary — same two fixes as the rest of this file. */
:root[data-theme="dark"] .app-row-action-btn { color: var(--primary-glow); border-color: var(--primary-glow); }
:root[data-theme="dark"] .app-row-action-btn:hover { background: var(--primary-lifted); }
:root[data-theme="dark"] .app-pill-tab.active .app-pill-tab-count { background: var(--primary-lifted); }
:root[data-theme="dark"] .tix-thread-item.staff .tix-thread-avatar { background: var(--primary-lifted); }
:root[data-theme="dark"] .app-quick-link-item:hover .app-quick-link-icon { background: var(--primary-lifted); }
:root[data-theme="dark"] .vhp-btn-primary { background: var(--primary-lifted); }
/* Regression catch (2026-08-14, found live via a real getComputedStyle sweep of the actual logged-in
   dashboard — the "Welcome, Sara" gradient banner's "Manage support tickets" pill): this button's
   background is a literal, always-#fff white (matching --gradient-brand's own "never inverts with
   the theme" design, see that token's comment) — it does NOT sit on a dark surface. Its text color
   is var(--primary-accented), which this file's own dark-mode block now correctly repoints to the
   new --primary-glow for --primary-accented's ~55 OTHER, genuinely-on-dark consumers (icon chips,
   KB links, etc.) — but --primary-glow (#b55cff) on this button's permanent WHITE background is only
   3.54:1, an actual regression versus the pre-fix ink value (#6b21a8, 8.7:1). Pinned to
   --primary-lifted instead (#8b2fd9 vs white: 5.99:1 by the same symmetric-ratio math as every other
   --primary-lifted/white pairing above) — --primary-lifted is never redefined in dark mode, so this
   is a stable, always-readable purple ink specifically for this one theme-independent surface. */
:root[data-theme="dark"] .app-gradient-banner-btn:not(.app-gradient-banner-btn-outline),
:root[data-theme="dark"] .app-gradient-banner-btn:not(.app-gradient-banner-btn-outline):hover {
    color: var(--primary-lifted);
}

/* ---------------------------------------------------------------------
   Fourth sweep pass (2026-08-14, verification-pass catch) — a real, live
   getComputedStyle contrast probe of every text-on---X-soft-fill consumer
   (not just the plain-surface --X-accented/-glow math the earlier passes
   above verified) turned up a THIRD distinct problem, narrower than the
   two documented in this block's own header comment: a handful of real
   components put --X-accented TEXT directly on top of that SAME token's
   OWN --X-soft translucent FILL (not a plain --bg/--bg-muted/etc.
   surface). --primary-glow/--error-glow's own derivation (see their token
   comments above) was measured against the plain surfaces only -- against
   the darker, more-saturated composite a translucent soft-tint fill
   actually paints, the same bright text clears only 3.4-4.4:1, live-
   confirmed on 4 of these (language-switcher active row: 3.84:1, account
   contact avatar-initials chip: 4.07:1, invoice payment-method label:
   4.07:1, .alert-danger banner: 4.06:1) plus 2 more sharing the identical
   selector pattern (KB article vote buttons). Fix is deliberately narrow:
   retarget ONLY the `color` (never the background/border that carries the
   actual purple/red hue signal, and never any layout property) to the
   already-existing, always-safe var(--text-accented) neutral -- 10:1+
   against every one of these real composites, live-measured. Background/
   border stay on the real brand hue in every case, so the semantic color
   cue (still purple/still red) is untouched; only the harder-to-read
   colored-on-colored TEXT moves to a token guaranteed legible on any dark
   surface. .app-inv-pay-label specifically could NOT go the solid-lifted-
   fill route several other fixes in this file use for the identical
   underlying problem: its --primary-soft background is shared with 3
   sibling rows (.app-inv-pay-amount/-due/-method) already tuned against
   THAT soft tint specifically -- flipping the shared card to a solid fill
   would drag --text-lifted's contrast on it down to 2.62:1, a new
   regression. Retargeting only this one label's own color sidesteps that
   entirely. */
:root[data-theme="dark"] #modalChooseLanguage .app-lang-row.active,
:root[data-theme="dark"] .acct-avatar-chip,
:root[data-theme="dark"] .app-inv-pay-label,
:root[data-theme="dark"] .alert-danger,
:root[data-theme="dark"] .app-kb-vote-btn:hover,
:root[data-theme="dark"] .app-kb-vote-btn:focus,
:root[data-theme="dark"] .app-kb-vote-btn[value="no"]:hover,
:root[data-theme="dark"] .app-kb-vote-btn[value="no"]:focus {
    color: var(--text-accented);
}

.badge-success { background-color: var(--success); }
.badge-warning { background-color: var(--warning); color: #fff; }
.badge-danger { background-color: var(--error); }
.badge-info { background-color: var(--info); }

/* bootstrap-switch "ON" state -> var(--primary). NOTE (2026-08-13): --primary is Vercaa's real
   PURPLE again (see the token block's "Primary = the REAL Vercaa purple" comment) — the prior
   comment here claiming "--primary is Vercaa Green... not purple" was written for the intervening
   2026-08 green-primary era and is now stale. Left functionally as-is: none of the app's real
   toggles (mailing-list opt-in, SSO allow, card "don't store") actually hit this selector — they all
   use the separately-defined `.toggle-switch-success` class a few hundred lines down, which already
   maps independently to --success and is unaffected by this token change — so this generic
   `bootstrap-switch-primary` rule appears to be defensive styling for a WHMCS-core-rendered toggle
   this template doesn't otherwise exercise, not a live "on = green" semantic that needed preserving. */
.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-handle-on,
.bootstrap-switch-handle-on.bootstrap-switch-primary {
    background: var(--primary) !important;
    color: #fff !important;
    border-color: var(--primary) !important;
}

/* Global keyboard-focus ring (kept scoped to :focus-visible so it never fights existing :focus/:hover
   styling for mouse users — matches the motion doc's own measurement methodology). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* Same ring also fires on :active (press state) for clickable controls only — motion doc §4:
   "the same ring also fires on :active... a nice touch for perceived responsiveness" — so pressing
   a button/link gives the same visual feedback as keyboard focus. Deliberately excludes text
   inputs/selects/textareas: their :active state is just the mousedown instant before a normal text
   caret, not a meaningful "press", so ringing them there would read as a glitch flash, not feedback. */
a:active,
button:active,
[tabindex]:active {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* Modal backdrop darkened to match the measured 80% black used for true (non-panel) modals. */
.modal-backdrop.show { opacity: 0.8; }

/* =========================================================================
   App Shell — fixed header + persistent left sidebar (logged-in only).
   Guests keep the original horizontal nav untouched (see header.tpl).
   ========================================================================= */
body[data-app-shell] { padding-top: 0; }

.app-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--header-height);
    z-index: 1040;
    background: var(--bg);
    border-bottom: 1px solid var(--border-muted);
    box-shadow: 0 0 0 rgba(15, 17, 21, 0);
    transition: box-shadow var(--duration-slow) var(--ease-standard);
}
/* Scroll-elevation (2026-08-13, js/app-shell.js's initHeaderScroll()) — a floating-surface depth
   cue once the page has actually moved under the fixed header, instead of the header looking
   identical whether the client is at the very top of a page or three screens down. */
.app-header.app-header-scrolled {
    box-shadow: var(--elevation-2);
}
.app-topbar { height: 100%; }
.app-topbar-inner {
    height: 100%;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0 1.25rem;
}
.app-logo { display: flex; align-items: center; flex-shrink: 0; margin-right: 0.5rem; }
.app-logo .logo-img { max-height: 32px; }
.app-logo-text { font-weight: 700; font-size: 1.125rem; color: var(--text); }

.app-sidebar-mobile-toggle {
    display: none;
    border: 0;
    background: transparent;
    color: var(--text);
    font-size: 1.125rem;
    width: 40px;
    height: 40px;
    border-radius: var(--rounding-md);
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-sidebar-mobile-toggle:hover { background: var(--bg-muted); }
.app-sidebar-mobile-toggle svg { width: 20px; height: 20px; flex-shrink: 0; }

.app-pill-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border: 1px solid var(--border);
    background: var(--bg-muted);
    color: var(--text);
    border-radius: 999px;
    font-size: var(--text-md);
    font-weight: 600;
    white-space: nowrap;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
/* Subtle lift-on-hover (2026-08-13) — same interactive-affordance treatment as .btn-primary/
   .btn-outline-primary now get, scaled down since this is a secondary/tertiary control. */
.app-pill-btn:hover { background: var(--bg-lifted); border-color: var(--border-lifted); color: var(--text); box-shadow: var(--elevation-1); transform: translateY(-1px); }
.app-pill-btn:active { transform: translateY(0); box-shadow: none; }
.app-pill-btn svg { width: 18px; height: 18px; flex-shrink: 0; }

.app-topbar-spacer { flex: 1 1 auto; }

.app-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 999px;
    border: 0;
    background: transparent;
    color: var(--text-accented);
    font-size: 1.05rem;
    flex-shrink: 0;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard);
}
.app-icon-btn:hover { background: var(--bg-muted); color: var(--text); transform: translateY(-1px); }
.app-icon-btn:active { transform: translateY(0) scale(0.94); }
.app-icon-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--secondary);
    color: #fff;
    font-size: 10px;
    line-height: 16px;
    font-weight: 700;
    text-align: center;
    /* Cut-out ring (2026-08-13) — a thin --bg-colored halo so the badge reads as a distinct
       floating dot punched out of the icon behind it (the classic iOS/Android badge treatment)
       instead of visually fusing with the glyph underneath at small sizes. */
    box-shadow: 0 0 0 2px var(--bg), 0 2px 5px -1px color-mix(in srgb, var(--secondary) 55%, transparent);
}

.app-theme-icon-light { display: none; }
:root[data-theme="dark"] .app-theme-icon-dark { display: none; }
:root[data-theme="dark"] .app-theme-icon-light { display: inline; }

/* Vercaa logo — dark-text variant for light mode, white-text variant for dark mode
   (the source logo.png has an opaque white background; both variants here are
   pre-processed transparent PNGs, see templates/vercaa-2026/images/). */
.app-logo-img-dark { display: none; }
:root[data-theme="dark"] .app-logo-img-light { display: none; }
:root[data-theme="dark"] .app-logo-img-dark { display: inline-block; }
.app-logo .logo-img { height: 28px; width: auto; max-width: none; }

.app-avatar-btn { border: 0; background: transparent; padding: 0; border-radius: 999px; flex-shrink: 0; }
.app-avatar-circle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 999px;
    /* Gradient fill (2026-08-13, was a flat var(--primary)) — the one avatar every client sees on
       every single page gets the same rich brand wash as the sidebar CTA / hero banners instead of
       a plain solid disc, plus a soft colored ambient shadow so it reads as a small floating chip
       rather than a flat tile. */
    background: var(--gradient-brand);
    color: #fff;
    font-weight: 700;
    font-size: 0.875rem;
    text-transform: uppercase;
    box-shadow: 0 2px 10px -3px color-mix(in srgb, var(--primary) 55%, transparent);
    transition: box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.app-avatar-btn:hover .app-avatar-circle {
    box-shadow: 0 4px 16px -3px color-mix(in srgb, var(--primary) 65%, transparent);
    transform: translateY(-1px);
}
.app-avatar-btn:active .app-avatar-circle { transform: translateY(0); }

/* Dropdown menus (Bootstrap 4 .dropdown / data-toggle="dropdown" — reuses the framework already
   loaded via theme.yaml so outside-click/escape/positioning work for free). */
.app-dropdown {
    position: relative;
}
.app-dropdown .dropdown-menu {
    border-radius: var(--rounding-lg);
    border: 1px solid var(--border);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16);
    padding: 0;
    overflow: hidden;
    min-width: 280px;
    background: var(--bg);
}
/* The bundled Bootstrap dropdown JS in scripts.min.js computes Popper-less positioning offsets that
   assume the classic navbar <li>/<ul> dropdown structure it ships with elsewhere in this theme; inside
   our flex topbar it produces a bogus inline transform/right that pushes the menu off-canvas. Force our
   own simple, predictable CSS-only positioning instead — we only rely on the JS for the show/hide
   class toggle, outside-click and Escape handling, not for placement math. */
.app-dropdown .dropdown-menu.show {
    position: absolute !important;
    inset: auto !important;
    top: calc(100% + 8px) !important;
    right: 0 !important;
    left: auto !important;
    bottom: auto !important;
    margin: 0 !important;
    /* transform is intentionally left to the entrance animation below (with fill-mode: forwards) so
       it wins over the bogus inline transform the bundled dropdown JS also sets on this element. */
}
.app-dropdown .dropdown-menu.show {
    animation: appDropdownIn var(--duration-fast) var(--ease-standard) forwards;
}
@keyframes appDropdownIn {
    from { opacity: 0; transform: scale(0.95) translateY(-8px); }
    to { opacity: 1; transform: scale(1) translateY(0); }
}
.app-dropdown-header {
    padding: 0.9rem 1rem 0.6rem;
    font-weight: 700;
    color: var(--text);
}
.app-avatar-name { font-weight: 700; color: var(--text); }
.app-avatar-email { font-size: var(--text-sm); color: var(--text-lifted); }
.app-dropdown-divider { height: 1px; background: var(--border-muted); margin: 0.25rem 0; }
.app-dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 1rem;
    color: var(--text-accented);
    font-size: var(--text-md);
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-dropdown-item i { width: 16px; text-align: center; color: var(--text-lifted); }
.app-dropdown-item-svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-lifted); }
.app-dropdown-item:hover { background: var(--bg-muted); color: var(--text); }
.app-dropdown-item:hover .app-dropdown-item-svg { color: var(--text); }
.app-dropdown-item-danger { color: var(--error); }
.app-dropdown-item-danger i { color: var(--error); }
.app-dropdown-item-danger .app-dropdown-item-svg { color: var(--error); }
.app-dropdown-item-danger:hover { background: var(--error-soft); color: var(--error); }
.app-dropdown-item-danger:hover .app-dropdown-item-svg { color: var(--error); }
/* 2026-08-14 dark-mode contrast fix (sweep finding, avatar-menu "Logout" link): raw --error as text
   on dark is only 4.02-4.14:1 -- see --error-glow's token comment for the full derivation. */
:root[data-theme="dark"] .app-dropdown-item-danger,
:root[data-theme="dark"] .app-dropdown-item-danger i,
:root[data-theme="dark"] .app-dropdown-item-danger .app-dropdown-item-svg,
:root[data-theme="dark"] .app-dropdown-item-danger:hover,
:root[data-theme="dark"] .app-dropdown-item-danger:hover .app-dropdown-item-svg {
    color: var(--error-glow);
}

.app-notif-menu { width: 340px; max-width: 90vw; }
.app-notif-list { max-height: 320px; overflow-y: auto; }
.app-notif-item {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--border-muted);
    color: var(--text-accented);
}
.app-notif-item:hover { background: var(--bg-muted); }
.app-notif-icon { width: 18px; height: 18px; margin-top: 0.15rem; flex-shrink: 0; color: var(--text-lifted); }
.app-notif-icon-danger { color: var(--error); }
.app-notif-icon-warning { color: var(--warning); }
.app-notif-icon-info { color: var(--info); }
.app-notif-message { font-size: var(--text-md); }

/* -------------------------------------------------------------------------
   Persistent sidebar
   ------------------------------------------------------------------------- */
.app-sidebar {
    position: fixed;
    top: var(--header-height);
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    background: var(--bg);
    border-right: 1px solid var(--border-muted);
    z-index: 1030;
    display: flex;
    flex-direction: column;
    transition: width var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard);
}
.app-sidebar-inner {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
    padding: 1rem 0.85rem;
}
.app-sidebar-cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    background: var(--gradient-brand);
    color: #fff;
    font-weight: 700;
    border-radius: var(--rounding-md);
    padding: 0.7rem 1rem;
    margin-bottom: 1rem;
    flex-shrink: 0;
    position: relative;
    overflow: hidden;
    /* 2026-08-13: real lift-shadow + a hover-triggered light sweep, matching the "Apple-tier" bar
       set by the Market Connect pages' own .vpn-btn-primary (glow shadow + translateY lift). The
       CTA sits at the very top of the sidebar on every logged-in page, so it's the single highest-
       exposure button in the whole shell — worth the same interaction polish as .btn-primary. */
    box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--primary) 55%, transparent);
    transition: filter var(--duration-slow) var(--ease-standard), transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.app-sidebar-cta:hover {
    filter: brightness(0.97);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 14px 30px -10px color-mix(in srgb, var(--primary) 65%, transparent);
}
.app-sidebar-cta:active { transform: translateY(0); }
.app-sidebar-cta-svg { width: 16px; height: 16px; stroke-width: 2; flex-shrink: 0; }
/* Light-sweep on hover — a diagonal highlight travels across the button once, a small "alive"
   detail rather than a decorative loop (fires only on hover, never idle/looping, so it stays well
   inside the "1-2 key elements, meaningful motion" budget). */
.app-sidebar-cta::after {
    content: '';
    position: absolute;
    top: 0;
    left: -60%;
    width: 40%;
    height: 100%;
    background: linear-gradient(115deg, transparent, rgba(255, 255, 255, 0.35), transparent);
    transform: skewX(-18deg);
    transition: left 0.65s var(--ease-standard);
    pointer-events: none;
}
.app-sidebar-cta:hover::after { left: 130%; }
@media (prefers-reduced-motion: reduce) {
    .app-sidebar-cta::after { display: none; }
}

.app-nav-list { list-style: none; margin: 0; padding: 0; }
.app-nav-item { margin-bottom: 2px; }
.app-nav-link,
.app-nav-toggle {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.6rem 0.75rem;
    border-radius: var(--rounding-md);
    color: var(--text-accented);
    font-weight: 600;
    font-size: var(--text-md);
    border: 0;
    background: transparent;
    text-align: left;
    position: relative;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-nav-link:hover,
.app-nav-toggle:hover { background: var(--bg-muted); color: var(--text); }
/* Active-state redesign (2026-08-13 app-shell elevation pass) — superseding the old flat
   "purple in some places" tint (the comment used to explain this as the one deliberate purple
   accent left "now that --primary is green everywhere else"; that was true in the intervening
   green-primary era but is stale now that --primary IS the real Vercaa purple again sitewide —
   var(--accent-purple) is a live alias to var(--primary) today, so this rule already matched
   every other primary-colored control, it just didn't LOOK as considered as one). Now a genuine
   "current location" treatment instead of a flat tint swap: a soft gradient wash, an inset hairline
   ring for definition against the sidebar's own bg, and a flush-left accent rail (::before, offset
   by the exact negative of .app-sidebar-inner's own 0.85rem side padding so it lands flush against
   the sidebar's true edge, including in the collapsed icon-rail state). */
.app-nav-link.active,
.app-nav-toggle.active {
    background: linear-gradient(135deg, var(--primary-soft), color-mix(in srgb, var(--primary-soft) 45%, transparent));
    color: var(--primary);
    font-weight: 700;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 16%, transparent);
}
.app-nav-link.active::before,
.app-nav-toggle.active::before {
    content: '';
    position: absolute;
    left: -0.85rem;
    top: 22%;
    bottom: 22%;
    width: 3px;
    border-radius: 0 4px 4px 0;
    background: linear-gradient(180deg, var(--primary), var(--primary-lifted));
    box-shadow: 0 0 8px 0 color-mix(in srgb, var(--primary) 55%, transparent);
}
.app-nav-icon {
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    flex-shrink: 0;
    border-radius: var(--rounding-md);
    transition: background-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
/* Active item's icon gets its own mini "chip" halo — same soft-tint-circle language as
   .app-modal-icon-chip / .app-notif-icon-chip elsewhere in this pass, echoed here at nav scale so
   the "current section" reads unmistakably at a glance even in the collapsed icon-only rail. */
.app-nav-link.active .app-nav-icon,
.app-nav-toggle.active .app-nav-icon {
    background: var(--bg);
    box-shadow: 0 1px 4px -1px color-mix(in srgb, var(--primary) 35%, transparent);
}
/* Inline SVG nav icons (Heroicons outline, see includes/app-nav.tpl) — sized/weighted centrally
   here so color keeps inheriting from .app-nav-link/.app-nav-toggle (and their .active accent-purple
   rule above) via stroke="currentColor" on the SVG itself, exactly like the font-icon color did. */
.app-nav-svg {
    width: 20px;
    height: 20px;
    stroke-width: 1.75;
    display: block;
}
.app-nav-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-nav-badge {
    background: var(--secondary);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    border-radius: 999px;
    padding: 0 6px;
    min-width: 18px;
    text-align: center;
    line-height: 18px;
    flex-shrink: 0;
}
.app-nav-caret { width: 14px; height: 14px; color: var(--text-muted); transition: transform var(--duration-fast) var(--ease-standard); flex-shrink: 0; }
.app-nav-item-open > .app-nav-toggle .app-nav-caret { transform: rotate(180deg); }

.app-nav-submenu {
    list-style: none;
    margin: 0;
    padding-left: 2.35rem;
    max-height: 0;
    overflow: hidden;
    transition: max-height var(--duration-base) var(--ease-standard);
}
.app-nav-item-open > .app-nav-submenu { max-height: 640px; }
.app-nav-sublink {
    display: block;
    padding: 0.45rem 0.5rem;
    color: var(--text-lifted);
    font-size: var(--text-md);
    border-radius: var(--rounding-sm);
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-nav-sublink:hover { background: var(--bg-muted); color: var(--text); }
.app-nav-sublink.active { color: var(--accent-purple); font-weight: 700; }
.app-nav-subdivider { height: 1px; background: var(--border-muted); margin: 0.35rem 0.5rem; list-style: none; }

.app-sidebar-footer { margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--border-muted); flex-shrink: 0; }
.app-sidebar-status { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; color: var(--text-lifted); font-size: var(--text-sm); overflow: hidden; }
.app-status-dot { width: 8px; height: 8px; border-radius: 999px; flex-shrink: 0; background: var(--neutral); }
/* Breathing "operational" ping (2026-08-13) — a single subtle looping ambient shadow pulse on the
   ONE status this shell shows on every logged-in page's sidebar footer, matching the small
   "system is alive" indicator convention of Stripe's/Vercel's own status dots. Deliberately only
   the success state animates (danger/warning stay still — motion drawing extra attention to a
   real incident would be the wrong signal); respects prefers-reduced-motion. */
.app-status-dot-success {
    background: var(--success);
    animation: appStatusPulse 2.6s ease-in-out infinite;
}
@keyframes appStatusPulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 55%, transparent); }
    50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--success) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
    .app-status-dot-success { animation: none; }
}
.app-status-dot-warning { background: var(--warning); }
.app-status-dot-danger { background: var(--error); }
.app-status-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.app-sidebar-collapse-toggle {
    position: absolute;
    top: 50%;
    right: -13px;
    transform: translateY(-50%);
    width: 26px;
    height: 26px;
    border-radius: 999px;
    background: var(--bg);
    border: 1px solid var(--border);
    color: var(--text-lifted);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    z-index: 1031;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-sidebar-collapse-toggle:hover { background: var(--bg-muted); }
.app-sidebar-collapse-toggle svg { width: 14px; height: 14px; stroke-width: 2; transition: transform var(--duration-base) var(--ease-standard); }

.app-sidebar-backdrop { display: none; }

/* Collapsed rail state (persisted via localStorage, see js/app-shell.js) */
body.app-sidebar-collapsed .app-sidebar { width: var(--sidebar-width-collapsed); }
body.app-sidebar-collapsed .app-sidebar-cta-label,
body.app-sidebar-collapsed .app-nav-label,
body.app-sidebar-collapsed .app-nav-caret,
body.app-sidebar-collapsed .app-nav-badge,
body.app-sidebar-collapsed .app-nav-submenu,
body.app-sidebar-collapsed .app-status-label {
    display: none;
}
body.app-sidebar-collapsed .app-nav-link,
body.app-sidebar-collapsed .app-nav-toggle,
body.app-sidebar-collapsed .app-sidebar-cta { justify-content: center; }
body.app-sidebar-collapsed .app-sidebar-collapse-toggle svg { transform: rotate(180deg); }
body.app-sidebar-collapsed .app-shell-content { margin-left: var(--sidebar-width-collapsed); }

/* Content offset — wraps the UNTOUCHED existing #main-body/.container/.row/.primary-content markup
   and the footer, so neither of those structures need to change at all for this shell to fit beside
   the fixed sidebar (see header.tpl / footer.tpl). */
.app-shell-content {
    margin-left: var(--sidebar-width);
    padding-top: var(--header-height);
    transition: margin-left var(--duration-base) var(--ease-standard);
}
.app-shell-content #main-body .container { max-width: 100%; }

/* -------------------------------------------------------------------------
   Responsive: sidebar becomes an off-canvas drawer below the tablet breakpoint
   ------------------------------------------------------------------------- */
@media (max-width: 991.98px) {
    .app-pill-btn .app-pill-label { display: none; }
    .app-pill-btn { padding: 0.5rem; width: 40px; justify-content: center; }
    .app-sidebar-mobile-toggle { display: inline-flex; align-items: center; justify-content: center; }

    .app-sidebar {
        transform: translateX(-100%);
        width: min(320px, 85vw);
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
        transition: transform var(--duration-drawer-close) ease-in-out;
    }
    body.app-sidebar-mobile-open .app-sidebar {
        transform: translateX(0);
        transition: transform var(--duration-drawer-open) ease-in-out;
    }
    .app-sidebar-collapse-toggle { display: none; }

    .app-sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        top: var(--header-height);
        background: rgba(0, 0, 0, 0.8);
        z-index: 1029;
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--duration-base) ease-in-out;
    }
    body.app-sidebar-mobile-open .app-sidebar-backdrop { opacity: 1; pointer-events: auto; }

    .app-shell-content,
    body.app-sidebar-collapsed .app-shell-content { margin-left: 0; }
}

/* =========================================================================
   Support PIN modal
   ========================================================================= */
#modalSupportPin .modal-dialog { max-width: 420px; }
/* 2026-08-12: bottom-sheet entrance (was fade+scale-from-center) — same motion revision, and same
   reasoning, as .app-domain-modal-dialog/.app-ticket-sheet-dialog above and #modalChooseLanguage's
   .app-lang-modal-dialog below: dialog starts fully below its own resting position so it reads as
   "slides up from off-screen". Drag-to-dismiss wired via js/app-shell.js's initDismissibleSheet()
   (shared helper) through initSupportPinDismiss(), which also drives this same `transform`
   property live during a drag gesture — see that function's docblock for why onDismiss there just
   clicks the modal's own [data-dismiss="modal"] button rather than doing custom teardown. */
#modalSupportPin.fade .modal-dialog {
    transform: translateY(100%);
    opacity: 0;
    transition: transform var(--duration-base) var(--ease-standard), opacity var(--duration-base) var(--ease-standard);
}
#modalSupportPin.show .modal-dialog { transform: translateY(0); opacity: 1; }
#modalSupportPin .modal-content {
    /* Full theme consistency (per build reference §12 "Dark-mode conventions" — the source product's
       Support PIN modal stays light-card-always, which this rebuild deliberately does NOT replicate;
       background must follow --bg here since the title/body text already follows --text). */
    background: var(--bg);
    color: var(--text);
    border-radius: var(--rounding-lg);
    border: 0;
    padding: 2rem 1.75rem 1.75rem;
}
.app-modal-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 32px;
    height: 32px;
    border: 0;
    background: transparent;
    color: var(--text-lifted);
    border-radius: 999px;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-modal-close:hover { background: var(--bg-muted); color: var(--text); }
.app-modal-title { font-weight: 700; font-size: 1.25rem; color: var(--text); margin-bottom: 0.4rem; }
.app-modal-subtitle { color: var(--text-lifted); font-size: var(--text-md); margin-bottom: 0; }

/* .app-modal-icon-chip (2026-08-13) — shared centered icon-chip header for Bootstrap-driven
   bottom-sheet modals in this shell (currently: Support PIN). Deliberately the SAME soft-tint
   halo language as #modalChooseLanguage's own .app-lang-modal-icon-chip (primary-soft fill,
   primary-accented glyph) but centered/standalone rather than paired with a left-aligned heading,
   since the PIN modal's whole body is center-aligned. */
.app-modal-icon-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin: 0 auto 1rem;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary-accented);
    box-shadow: 0 0 0 8px color-mix(in srgb, var(--primary) 6%, transparent);
}
.app-modal-icon-chip svg { width: 26px; height: 26px; stroke-width: 1.6; }

/* Pin field widened 260px -> 300px (2026-08-13) to comfortably fit the new 6-segment box layout
   below without crowding the reveal button — verified live at both this modal's own max-width
   (420px) and at a 375px mobile viewport. */
.app-pin-field-wrapper { margin: 1.5rem auto; max-width: 300px; }
.app-pin-field {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    /* Richer surface (2026-08-13) — was a bare 1px outline; now a filled --bg-muted pill with an
       inset hairline, matching the "field" language used by the search input in the redesigned
       language modal (#modalChooseLanguage .app-lang-modal-search) instead of a plain stroke. */
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: 999px;
    padding: 0.5rem 0.5rem 0.5rem 1rem;
}
/* Digits redesigned from one run of letter-spaced text into 6 discrete "chip" boxes (2026-08-13) —
   an OTP/segmented-code treatment, matching bank/2FA code entry conventions rather than a loose
   bullet string. Costs nothing on the JS side: js/app-shell.js's initSupportPin() already renders
   the hidden state as 6 separate `<span class="app-pin-dot">` elements (one per bullet) — this
   only styles that pre-existing per-digit markup. The revealed state (plain digitsEl.textContent,
   a single text node, no per-char spans) is unaffected and keeps its own letter-spacing below. */
.app-pin-digits {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: 0.3em;
    color: var(--text);
}
.app-pin-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 36px;
    border-radius: var(--rounding-sm);
    background: var(--bg);
    box-shadow: inset 0 0 0 1px var(--border);
    font-size: 1.1rem;
    line-height: 1;
    color: var(--primary);
}
.app-pin-reveal {
    border: 0;
    background: transparent;
    color: var(--text-lifted);
    width: 32px;
    height: 32px;
    border-radius: 999px;
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-pin-reveal:hover { background: var(--bg); color: var(--text); }

.app-pin-actions { display: flex; gap: 0.75rem; justify-content: center; margin-bottom: 1.25rem; flex-wrap: wrap; }
.app-btn-dark {
    background: var(--grayscale);
    color: var(--text-inverted);
    border: 0;
    border-radius: var(--rounding-md);
    padding: 0.6rem 1.1rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    transition: filter var(--duration-slow) var(--ease-standard), transform var(--duration-fast) var(--ease-out);
}
.app-btn-dark:hover { filter: brightness(1.2); color: var(--text-inverted); transform: translateY(-1px); }
.app-btn-dark:active { transform: translateY(0); }
.app-btn-dark:disabled { opacity: var(--disabled-opacity); }
.app-btn-gradient {
    position: relative;
    background: var(--gradient-brand);
    color: #fff;
    border: 0;
    border-radius: var(--rounding-md);
    padding: 0.6rem 1.1rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    /* Real lift + glow on hover (2026-08-13), matching .btn-primary's own hover treatment —
       was filter-only, the one gradient CTA in this shell that hadn't gotten the same interaction
       polish as its solid-color sibling. */
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--primary) 50%, transparent);
    transition: filter var(--duration-slow) var(--ease-standard), transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.app-btn-gradient:hover {
    filter: brightness(0.97);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--primary) 60%, transparent);
}
.app-btn-gradient:active { transform: translateY(0); }

.app-copied-tooltip {
    position: absolute;
    bottom: calc(100% + 10px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    background: var(--grayscale);
    color: var(--text-inverted);
    padding: 0.35rem 0.75rem;
    border-radius: var(--rounding-md);
    font-size: var(--text-sm);
    font-weight: 600;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard);
}
.app-copied-tooltip::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent;
    border-top-color: var(--grayscale);
}
.app-copied-tooltip.show { opacity: 1; transform: translateX(-50%) translateY(0); }

.app-pin-footer-note { color: var(--text-lifted); font-size: var(--text-sm); margin-top: 0.5rem; margin-bottom: 0; }
.app-pin-footer-note i { margin-right: 0.3rem; }

/* =========================================================================
   Reusable partials (templates/vercaa-2026/includes/*.tpl) — cross-cutting
   empty/loading/pagination/status/hero patterns for the next phase's pages.
   ========================================================================= */

/* empty-state.tpl */
.app-empty-state { text-align: center; padding: 2.5rem 1.5rem; }
.app-empty-state-compact { padding: 1.5rem 1rem; }
.app-empty-state-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 1rem;
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--text-lifted);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
}
.app-empty-state-headline { font-weight: 700; color: var(--text); margin-bottom: 0.25rem; }
.app-empty-state-subtext { color: var(--text-lifted); font-size: var(--text-md); margin-bottom: 1rem; }
.app-empty-state-cta { display: inline-flex; }

/* pill-tabs.tpl */
.app-pill-tabs { display: inline-flex; background: var(--bg-muted); border-radius: 999px; padding: 4px; gap: 2px; flex-wrap: wrap; }
.app-pill-tab {
    padding: 0.45rem 0.9rem;
    border-radius: 999px;
    color: var(--text-lifted);
    font-weight: 600;
    font-size: var(--text-md);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-pill-tab:hover { color: var(--text); }
.app-pill-tab.active { background: var(--bg); color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); }
.app-pill-tab-count { background: var(--bg-accented); color: var(--text-lifted); font-size: 11px; font-weight: 700; border-radius: 999px; padding: 0 6px; }
.app-pill-tab.active .app-pill-tab-count { background: var(--primary); color: #fff; }

/* status-badge.tpl */
.app-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.65rem;
    border-radius: 999px;
    font-size: var(--text-sm);
    font-weight: 700;
    border: 1px solid transparent;
}
/* 2026-08-13: a hairline, color-matched ring per variant on top of the existing soft-tint fill — a
   small but genuine "premium chip" refinement consistent with the richer shadow/radius language
   elsewhere in this pass, not a hover/press affordance (these are status indicators, not buttons, so
   no lift/motion added — Bootstrap's global box-sizing:border-box means the added border costs zero
   outer size, it can't shift surrounding layout). */
.app-status-badge-success { background: var(--success-soft); color: var(--success-accented); border-color: color-mix(in srgb, var(--success) 24%, transparent); }
.app-status-badge-warning { background: var(--warning-soft); color: var(--warning-accented); border-color: color-mix(in srgb, var(--warning) 24%, transparent); }
.app-status-badge-error { background: var(--error-soft); color: var(--error); border-color: color-mix(in srgb, var(--error) 24%, transparent); }
.app-status-badge-info { background: var(--info-soft); color: var(--info); border-color: color-mix(in srgb, var(--info) 24%, transparent); }
.app-status-badge-neutral { background: var(--bg-accented); color: var(--text-lifted); }

/* pagination-footer.tpl */
.app-pagination-footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; padding: 1rem 0; color: var(--text-lifted); font-size: var(--text-md); }
.app-pagination-rows { display: flex; align-items: center; gap: 0.5rem; }
.app-pagination-select { border: 1px solid var(--border); border-radius: var(--rounding-sm); padding: 0.25rem 0.5rem; background: var(--bg); color: var(--text); }
.app-pagination-controls { display: flex; align-items: center; gap: 0.25rem; }
.app-pagination-btn {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: var(--rounding-sm);
    color: var(--text-accented);
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-pagination-btn:hover { background: var(--bg-muted); }
.app-pagination-btn.disabled { opacity: var(--disabled-opacity); pointer-events: none; }

/* gradient-banner.tpl — always Vercaa-branded, does NOT invert with theme (tokens referenced here are
   declared outside the [data-theme="dark"] block on purpose). Bumped to --rounding-2xl + given a real
   colored shadow (2026-08-13) — this is exactly the "hero-scale surface" the new 28px rounding token
   was added for, and a flat fill with zero shadow read as dated next to the Market Connect pages'
   floating-card treatment. */
.app-gradient-banner {
    background: var(--gradient-brand);
    border-radius: var(--rounding-2xl);
    padding: 2rem;
    color: #fff;
    margin-bottom: 1.5rem;
    box-shadow: 0 24px 60px -28px color-mix(in srgb, var(--primary) 45%, transparent), var(--elevation-2);
}
.app-gradient-banner-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1.25rem; }
.app-gradient-banner-heading { font-size: 1.75rem; font-weight: 700; color: #fff; margin-bottom: 0.35rem; }
.app-gradient-banner-subtext { color: rgba(255, 255, 255, 0.85); margin: 0; }
.app-gradient-banner-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.app-gradient-banner-btn {
    background: #fff;
    color: var(--primary-accented);
    font-weight: 700;
    border-radius: var(--rounding-md);
    padding: 0.6rem 1.1rem;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    transition: filter var(--duration-slow) var(--ease-standard), transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.app-gradient-banner-btn:hover { filter: brightness(0.95); color: var(--primary-accented); transform: translateY(-1px); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18); }
.app-gradient-banner-btn-svg { width: 16px; height: 16px; flex-shrink: 0; }
.app-gradient-banner-btn-outline { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.6); }
.app-gradient-banner-btn-outline:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }

/* =========================================================================
   Dark-mode contrast fixes — shared Bootstrap / page-content components
   =========================================================================
   theme.css / theme.min.css is compiled Sass output with no build pipeline
   available in this environment (see file header above), so the stock
   Bootstrap-derived component classes below still hardcode LIGHT-mode
   colors: literal #fff / white backgrounds, and text colors like #212529,
   #333, #495057 that never reference the token system. Meanwhile, plenty
   of the TEXT sitting on top of those hardcoded-light surfaces already
   *does* reference the dark-mode-aware tokens (global `a { color:
   var(--text) }`, `.card-header { color: var(--text) }`,
   `.sidebar .list-group-item { color: var(--text) }`, `.card-header .btn
   { color: var(--text) }`, etc.) — so in dark mode you get near-white
   text (var(--text) resolves to #f5f3f8) sitting on a background that
   never got dark-themed: invisible. The inverse also happens: a few
   spots hardcode dark text (#212529/#333) that sits directly on a page
   background that DID correctly switch to a dark token (e.g. DataTables'
   "Showing x to y of z entries" info text on the dark <section> page
   background) — dark-on-dark.

   Root-caused via live computed-style + matched-CSSOM-rule inspection
   (not guesswork) across the client area home/services/domains,
   billing, support/knowledgebase and account/affiliates page groups.
   Fixed here by re-pointing the shared component classes at the SAME
   --bg/--text/--border tokens already defined above, under the same
   :root[data-theme="dark"] convention — this covers every page that
   reuses these standard components, not just the pages that were
   audited (that's the point of targeting the generic classes).

   A handful of the original light-mode rules are extremely high
   specificity on purpose (e.g. `body .primary-content .client-home-cards
   .card .card-header .btn` — 6 chained classes — used to force an
   "always-white pill button" regardless of which .bg-color-* accent is
   also present on the same element; similarly `.btn-success` gets its
   background re-pointed to var(--bg-inverted) by 3-4 different
   ancestor-scoped rules of varying specificity, e.g. `body .sidebar
   .btn-success`, `body .primary-content .client-home-cards .card
   .btn-success`, `body .primary-content .btn-success.btn-lg[name=enable]`).
   Matching every such chain exactly here would be brittle and would
   silently stop working the moment any one of those ancestor wrappers
   changes. Those specific declarations are overridden with !important
   instead — safely scoped inside this dark-mode selector, so light mode
   is completely unaffected either way.
   ========================================================================= */

/* -------------------------------------------------------------------------
   App shell header — `header.header { background-color: #fff }` (element
   + class, specificity 0,1,1) outranks the plain `.app-header` class rule
   near the top of this file (0,1,0) regardless of cascade order, so the
   header stayed white in dark mode while .app-logo-text / .app-icon-btn /
   .app-sidebar-mobile-toggle already correctly used dark-mode text tokens
   — invisible text/icons on a still-white bar. Fixing the background here
   also restores the header icon buttons' contrast (.app-icon-btn already
   uses var(--text-accented), it just had no dark surface to sit on).
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] header.header.app-header {
    background-color: var(--bg);
}

/* -------------------------------------------------------------------------
   Classic footer (footer.tpl) — `footer.footer { background-color:
   var(--bg-inverted); color: #fff; }`. --bg-inverted is INTENTIONALLY a
   dark surface in light mode (giving a dark footer bar under the light
   page — by design) but flips to a LIGHT surface in dark mode (by design,
   for things that should invert), while this footer's child text
   (.nav-link white, .copyright using --text-lifted, the language-switcher
   button using --text-inverted-ish light values) was authored assuming a
   permanently-dark footer. Simplest correct fix: keep the footer on an
   always-dark surface in dark mode too, using --bg-muted instead of the
   token that intentionally inverts.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] footer.footer {
    background-color: var(--bg-muted);
}
/* .nav-link (#fff), .copyright (var(--text-lifted)) and .btn-outline-light
   (color/border: #f8f9fa, transparent bg) all already resolve to readable
   light-on-dark values once the footer surface above is actually dark —
   no further overrides needed for those three. */

/* -------------------------------------------------------------------------
   Cards / panels — the workhorse container reused everywhere: sidebar
   filter/info panels ("Your Info", "Contacts", "Shortcuts", "View",
   "Support"), ticket/domain/invoice detail cards, KB categories, the
   account section-switcher, etc. `.card { background-color: #fff }` and
   `.card-header { background-color: rgba(0,0,0,.03) }` (i.e. a faint tint
   OVER whatever #fff .card gives it) never picked up a dark surface,
   while `.card-header`/`.card-title`/`.sidebar .card-sidebar .panel-title`
   already use var(--text) — hence the pervasive "near-white heading on a
   white card" bug reported on nearly every page group.
   ------------------------------------------------------------------------- */
/* Card surface sits ONE STEP LIGHTER than the page background (--bg-lifted, not --bg) per
   hosting.com's own documented dark-mode convention ("card/surface background one step lighter" —
   build reference §12). Header/footer chrome bands step up again to --bg-accented so the
   header-over-body hierarchy stays visible now that body is no longer identical to the page. */
:root[data-theme="dark"] .card,
:root[data-theme="dark"] .mc-promo,
:root[data-theme="dark"] .mc-promo-manage,
:root[data-theme="dark"] .mc-promo-login,
:root[data-theme="dark"] .panel,
:root[data-theme="dark"] .well {
    background-color: var(--bg-lifted) !important;
    border-color: var(--border) !important;
    color: var(--text);
}
/* Design-review pass (2026-08-14): `.mc-promo` (bare) is the SAME "near-white heading on a
   white card" bug as above, in one more instance the original fix missed. `.mc-promo-manage`/
   `.mc-promo-login` are the client-area variants of this Marketplace-Connect promo widget; the
   cart/checkout order-form's own upsell cards (viewcart.tpl "Recommended for you",
   checkout.tpl "Last Chance" — templates/orderforms/standard_cart/css/all.css's stock
   `#order-standard_cart .mc-promo { background-color: #fff }`) use the bare `.mc-promo` class
   with no `-manage`/`-login` suffix, so they never matched the selector list above and stayed
   hardcoded white while their own `.headline`/text already inherit `var(--text)` (light in dark
   mode) — live-confirmed via getComputedStyle: card background rgb(255,255,255), heading color
   rgb(245,243,248), ~1:1 contrast. `.mc-promo .body` (the "Learn more" expandable detail panel)
   has the identical stock hardcoded `background-color: #f8f8f8`, so it gets the same treatment
   here rather than leaving a second, harder-to-notice instance of the same bug one click away. */
:root[data-theme="dark"] .mc-promo .body {
    background-color: var(--bg-lifted) !important;
    border-color: var(--border) !important;
}
:root[data-theme="dark"] .card-header,
:root[data-theme="dark"] .panel-heading {
    background-color: var(--bg-accented) !important;
    color: var(--text);
    border-color: var(--border-muted) !important;
}
:root[data-theme="dark"] .card-title,
:root[data-theme="dark"] .card-header h3,
:root[data-theme="dark"] .panel-title {
    color: var(--text) !important;
}
:root[data-theme="dark"] .card-body,
:root[data-theme="dark"] .panel-body {
    color: var(--text);
}
:root[data-theme="dark"] .card-footer,
:root[data-theme="dark"] .panel-footer {
    background-color: var(--bg-accented) !important;
    border-color: var(--border-muted) !important;
}

/* The stock "always-white pill button in a card-header corner" pattern
   (the "My Services" / "View All" quick-links, and any other card-header
   .btn) — theme.css deliberately forces `background: white` at very high
   specificity so it stays white regardless of which .bg-color-* accent
   class also happens to be on the same element, while `color: var(--text)`
   was correctly left dynamic. That combination is exactly what breaks in
   dark mode: near-white text on a background frozen to white. */
:root[data-theme="dark"] .card-header .btn {
    background-color: var(--bg-muted) !important;
    border-color: var(--border) !important;
    color: var(--text) !important;
}
:root[data-theme="dark"] .card-header .btn:hover {
    background-color: var(--bg-lifted) !important;
    border-color: var(--border-lifted) !important;
    color: var(--text) !important;
}
/* "View More..." / "View All" style links inside a now-dark .card-footer
   already resolve `color: var(--text)` correctly on their own — fixing
   the .card-footer background above is sufficient for those. */

/* -------------------------------------------------------------------------
   List groups — sidebar filter panels ("Active/Pending/Suspended/…" with
   count badges), ticket/service detail rows, KB category lists, the
   ticket sidebar's "Ticket Information" field rows, etc. `.list-group-item
   { background-color: #fff }` never picked up a dark surface, while
   `.sidebar .list-group-item { color: var(--text) }` (and the global
   `a { color: var(--text) }` for plain list-group links) already do.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] .list-group-item {
    background-color: var(--bg) !important;
    border-color: var(--border-muted) !important;
    color: var(--text);
}
:root[data-theme="dark"] .list-group-item-action {
    color: var(--text);
}
:root[data-theme="dark"] .list-group-item-action:hover,
:root[data-theme="dark"] .list-group-item-action:focus {
    background-color: var(--bg-muted) !important;
    color: var(--text);
}
:root[data-theme="dark"] .list-group-item-action:active {
    background-color: var(--bg-lifted) !important;
    color: var(--text);
}

/* -------------------------------------------------------------------------
   Tables — product/service, domain, invoice and ticket lists all render
   through the same `table.table-list` (custom) / `.table` (Bootstrap)
   markup inside a DataTable. `.table { color: #212529 }`,
   `table.table-list thead th { background-color: #fff; color: #333 }`
   and `.table-list > tbody > tr > td { background-color: #fff }` are all
   hardcoded, unthemed — same white-surface/dark-text-or-invisible-link
   pattern as the cards above.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] .table {
    color: var(--text);
}
:root[data-theme="dark"] .table > thead > tr > th,
:root[data-theme="dark"] table.table-list thead th {
    background-color: var(--bg-muted) !important;
    color: var(--text) !important;
    border-color: var(--border-muted) !important;
}
:root[data-theme="dark"] .table > tbody > tr > td,
:root[data-theme="dark"] .table-list > tbody > tr > td {
    background-color: var(--bg) !important;
    color: var(--text);
    border-color: var(--border-muted);
}
:root[data-theme="dark"] .table-list > tbody > tr:hover > td,
:root[data-theme="dark"] .table-hover > tbody > tr:hover > td {
    background-color: var(--bg-muted) !important;
}
:root[data-theme="dark"] .table > tbody > tr > td a,
:root[data-theme="dark"] .table > thead > tr > th a {
    color: var(--text);
}

/* DataTables' own "Showing x to y of z entries" info text and the "Show
   [10] entries" length-control label are unstyled by theme.css (no class
   hook at all — plain inherited Bootstrap body color #212529) and very
   often sit directly on the dark <section> page background rather than
   inside a card, producing dark-on-dark text. Generic fix covers every
   list page using the shared jQuery DataTables integration (services,
   domains, invoices, tickets, emails, affiliates, …). */
:root[data-theme="dark"] .dataTables_info,
:root[data-theme="dark"] .dataTables_length,
:root[data-theme="dark"] .dataTables_length label,
:root[data-theme="dark"] .dataTables_filter label {
    color: var(--text);
}

/* -------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------- */
/* .btn-success gets re-pointed to `background: var(--bg-inverted)` (a
   deliberately dark pill in LIGHT mode) by several different
   ancestor-scoped rules of differing specificity (body .sidebar
   .btn-success, .client-home-cards .btn-success, body .primary-content
   .btn-success.btn-lg[name=enable], …) — the "Update" button, the domain
   "Register" button, the invoice "Pay All" button and the ticket sidebar's
   duplicate "Reply" button are each hit by one of these. --bg-inverted
   flips to a LIGHT surface in dark mode by design, but the button's text
   stays hardcoded white — invisible. Force success buttons to the actual
   --success token in dark mode instead, regardless of which ancestor rule
   would otherwise win. */
:root[data-theme="dark"] .btn-success {
    background-color: var(--success) !important;
    border-color: var(--success) !important;
    color: #fff !important;
}
:root[data-theme="dark"] .btn-success:hover,
:root[data-theme="dark"] .btn-success:focus {
    background-color: var(--success-lifted) !important;
    border-color: var(--success-lifted) !important;
    color: #fff !important;
}

/* .btn-default is entirely hardcoded (#333 text / #fff bg / #ccc border)
   which is internally readable on its own, but its :hover state forces
   `color: white` onto `background-color: var(--grayscale-lifted)` — a
   token that resolves LIGHT in dark mode (by design, for things meant to
   invert), giving white-on-light on hover. */
:root[data-theme="dark"] .btn-default {
    background-color: var(--bg-muted);
    border-color: var(--border);
    color: var(--text);
}
:root[data-theme="dark"] .btn-default:hover {
    background-color: var(--bg-lifted);
    border-color: var(--border-lifted);
    color: var(--text);
}

/* -------------------------------------------------------------------------
   Dashboard stat tiles (Services / Domains / Tickets / Invoices).
   `.tiles .tile { background-color: #f8f8f8 }` never picked up a dark
   surface while the big number (`.stat`, no color rule of its own — just
   inherits the page's text color) and the muted "SERVICES" label
   (hardcoded #888, already tolerable on both light/dark) sit on top.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] .tiles .tile {
    background-color: var(--bg-muted) !important;
    border-color: var(--border-muted) !important;
}
:root[data-theme="dark"] .tiles .tile:hover {
    background-color: var(--bg-lifted) !important;
}
:root[data-theme="dark"] .tiles .tile .stat {
    color: var(--text);
}
:root[data-theme="dark"] .tiles .tile i {
    color: var(--text-muted);
}
:root[data-theme="dark"] .tiles .tile:hover i {
    color: var(--text-lifted);
}

/* -------------------------------------------------------------------------
   Misc page-content components
   ------------------------------------------------------------------------- */
/* Form inputs/selects (search boxes, CC-recipient email field, DataTables
   length select, etc.) are internally readable already (#495057 text on
   #fff, both hardcoded) but stay a jarring unthemed white box inside an
   otherwise dark card in dark mode — restyle for visual consistency. */
:root[data-theme="dark"] .form-control {
    background-color: var(--bg-muted);
    border-color: var(--border);
    color: var(--text);
}
:root[data-theme="dark"] .form-control:focus {
    background-color: var(--bg-muted);
    color: var(--text);
    border-color: var(--primary);
}
:root[data-theme="dark"] .form-control::placeholder {
    color: var(--text-muted);
}
:root[data-theme="dark"] .input-group-text {
    background-color: var(--bg-lifted);
    border-color: var(--border);
    color: var(--text-lifted);
}

/* `.alert:has(form) { background-color: white; color: var(--text) }` —
   same white-surface/dynamic-text mismatch as the cards above. */
:root[data-theme="dark"] .alert:has(form) {
    background-color: var(--bg) !important;
}

/* Bootstrap's `.bg-white`/`.bg-light` utility classes (both `!important`
   in theme.css) are used directly on a handful of one-off widgets outside
   the .card system — e.g. the product-details page's Domain/Username/SSL
   tab panel (`.tab-content.bg-white`) — with the SAME dynamic-text-on-
   frozen-white-surface mismatch as everything above. */
:root[data-theme="dark"] .bg-white {
    background-color: var(--bg) !important;
}
:root[data-theme="dark"] .bg-light {
    background-color: var(--bg-muted) !important;
}

/* Defensive: Bootstrap's default `.breadcrumb { background-color: #e9ecef }`
   isn't currently rendered anywhere in this theme's own templates, but
   fix it too in case a bundled module page uses the stock component. */
:root[data-theme="dark"] .breadcrumb {
    background-color: var(--bg-muted);
}
:root[data-theme="dark"] .breadcrumb-item.active {
    color: var(--text-lifted);
}

/* Safety net: Bootstrap's Reboot sets a hardcoded `body { color: #212529 }`
   that nothing else in theme.css overrides globally (only specific
   components like `.card-header`/`a` reference var(--text)). Any plain,
   otherwise-unstyled text run directly in the dark page background (e.g.
   the "Powered by WHMCompleteSolution" line under the primary content
   area) inherits that hardcoded near-black instead — dark-on-dark. This
   re-points the base text color for anything not already covered above,
   without touching the (already correct) html/body background-color
   rule higher up in theme.css.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] body {
    color: var(--text);
}

/* -------------------------------------------------------------------------
   Service status placeholder label — `.div-service-status .label-placeholder`
   is meant to always stay hidden (theme.css sets `visibility: hidden` on
   it, scoped to that exact ancestor) while JS swaps in the real
   `.label-{modifier}` status pill. A second rendering of the same
   service-item partial (e.g. the mobile off-canvas nav's services list)
   doesn't share that `.div-service-status` ancestor, so the placeholder's
   default white-text-on-no-background shows through. Hide it unscoped so
   it can never render visibly regardless of ancestor markup — this is a
   markup-context bug, not a light/dark color bug, so it applies in both
   themes.
   ------------------------------------------------------------------------- */
.label.label-placeholder {
    position: absolute !important;
    visibility: hidden !important;
}

/* =========================================================================
   Home dashboard (clientareahome.tpl) — hosting.com-inspired vertical stack
   of full-bleed cards (build reference §2). New component classes only —
   nothing above this block is touched. Every new card shell below reuses
   the existing .card / .card-header / .card-title / .card-body /
   .card-footer / .list-group(-item) primitives, which are already
   dark-mode-correct via the fixes earlier in this file, so only the
   page-specific INNER components below need new rules.
   ========================================================================= */

/* Welcome banner: gradient-banner.tpl + an attached darker "stat strip"
   directly beneath it, styled to read as one continuous banner (build
   reference §2 "Welcome banner"). Scoped to this wrapper class only — the
   base .app-gradient-banner rule itself is untouched, so every other page
   reusing the plain partial is unaffected. */
.app-welcome-banner-wrap { margin-bottom: 1.5rem; }
.app-welcome-banner-wrap .app-gradient-banner {
    margin-bottom: 0;
    /* Top corners match the base .app-gradient-banner's own --rounding-2xl (28px, 2026-08-13
       foundational retheme) instead of the smaller --rounding-lg this override used to hardcode —
       the floating stats card below now shares that same radius (see ".app-welcome-stats-card"
       further down this file), so the whole welcome unit reads as one consistent "premium hero"
       radius scale instead of two mismatched corner sizes stacked on top of each other. Bottom
       corners stay square: the overlapping stats card fully covers that edge visually. */
    border-radius: var(--rounding-2xl) var(--rounding-2xl) 0 0;
    padding-bottom: 1.75rem;
}
.app-welcome-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    background: rgba(0, 0, 0, 0.16);
    border-radius: 0 0 var(--rounding-lg) var(--rounding-lg);
    overflow: hidden;
}
.app-welcome-stat {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 1rem 1.25rem;
    color: #fff;
    border-right: 1px solid rgba(255, 255, 255, 0.14);
    transition: background-color var(--duration-fast) var(--ease-out);
    min-width: 0;
}
.app-welcome-stat:last-child { border-right: 0; }
.app-welcome-stat:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.app-welcome-stat i { font-size: 0.85rem; color: rgba(255, 255, 255, 0.75); margin-bottom: 0.15rem; display: block; }
.app-welcome-stat-value { font-size: 1.375rem; font-weight: 700; line-height: 1.1; }
.app-welcome-stat-label {
    font-size: var(--text-sm);
    color: rgba(255, 255, 255, 0.75);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
@media (max-width: 767.98px) {
    .app-welcome-stats { grid-template-columns: repeat(2, 1fr); }
    .app-welcome-stat:nth-child(2n) { border-right: 0; }
}

/* Shared full-bleed card rhythm + two-column row for the Home stack. */
.app-home-card { margin-bottom: 1.5rem; }
.app-home-row { display: flex; gap: 1.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; align-items: flex-start; }
.app-home-row > .app-home-card { flex: 1 1 360px; margin-bottom: 0; }

/* "Manage all" / "See all" understated link pattern (build reference §2 +
   §12) — a lighter-weight alternative to the boxy .btn-default header
   button the legacy generic panel renderer still uses further down. */
.app-manage-all-link {
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--text-accented);
    text-decoration: underline;
    text-underline-offset: 2px;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    transition: color var(--duration-fast) var(--ease-out);
}
.app-manage-all-link:hover { color: var(--primary); }
.app-manage-all-link i { font-size: 0.7rem; }

/* Products & Services card — active-products-services-item.tpl is a real,
   template-controlled partial (WHMCS core includes it once per service with
   a genuine $service model in scope — verified live, not opaque baked
   HTML), so its row is fully restructured rather than just restyled via
   hooks. Rules below are intentionally NOT scoped to #appServicesCard only:
   WHMCS core re-includes this exact same partial in at least one other
   context (see the `.label.label-placeholder` comment above), so the row
   needs to look correct wherever it's reused, not only inside this card. */
#appServicesCard .list-group-item { padding: 0; border: 0; border-bottom: 1px solid var(--border-muted); }
#appServicesCard .list-group-item:last-child { border-bottom: 0; }

.app-service-row {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1rem 1.5rem;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-service-row:hover { background: var(--bg-muted); }
.app-service-row-icon { flex: none; }
.app-service-row-main {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    flex: 1 1 auto;
    min-width: 0;
}
.app-service-row-main .app-cell-category { margin-bottom: 0; }
.app-service-row-main .app-cell-product {
    font-size: var(--text-lg);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-service-row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    font-size: var(--text-sm);
    color: var(--text-lifted);
}
.app-service-row-meta-item { display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.app-service-row-meta-item i { font-size: 0.7rem; color: var(--text-muted); }
.app-service-row-meta-dot { color: var(--border-lifted); }
.app-service-row-meta-domain { color: var(--text-lifted); font-weight: 600; white-space: nowrap; }
.app-service-row-meta-domain:hover { color: var(--primary); }
.app-service-row-status { flex: none; order: 2; }
.app-service-row-status .app-status-badge { white-space: nowrap; }
.app-service-row-action { flex: none; order: 3; display: flex; align-items: center; gap: 0.4rem; }
.app-service-row-action .app-row-action-btn {
    padding: 0.4rem 0.85rem;
    font-size: var(--text-sm);
    white-space: nowrap;
}
.app-service-row-action .app-row-action-btn i { font-size: 0.65rem; margin-left: 0.1rem; }

@media (max-width: 575.98px) {
    .app-service-row { flex-wrap: wrap; }
    .app-service-row-main { flex-basis: 100%; order: 1; }
    .app-service-row-icon { order: 0; }
    .app-service-row-status { order: 2; }
    .app-service-row-action { order: 3; margin-left: auto; }
}

/* "+N more services — view all" overflow row, shown when a client has more active
   services than the summary card displays (see clientareahome.tpl). Same understated
   link treatment as .app-manage-all-link, centered as its own list row. */
.app-service-row-overflow {
    display: block;
    text-align: center;
    padding: 0.85rem 1.5rem;
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--text-accented);
    transition: color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.app-service-row-overflow:hover { color: var(--primary); background: var(--bg-muted); }

/* Support Tickets card rows — same "opaque core HTML" situation: the
   ticket subject/status/timestamp line is MenuItem::getLabel() output,
   including a WHMCS-admin-configured inline status color deliberately left
   alone (so each ticket status keeps its own configured meaning) — only
   pill shape/spacing is restyled here, never its background color. */
.app-ticket-row {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.5rem;
    border: 0;
    border-bottom: 1px solid var(--border-muted);
    color: var(--text);
}
.app-ticket-row:last-child { border-bottom: 0; }
.app-ticket-row-icon {
    width: 36px;
    height: 36px;
    border-radius: 999px;
    /* --notice (indigo), not --primary — 2026-08-13 innovate pass: this page's own unified
       Products & Services card colors its "Tickets" stat tile and this exact same speech-bubble
       glyph with --notice too (see ".app-welcome-stat--notice" further down), so Support Tickets
       reads as one consistent color identity everywhere on Home rather than sharing --primary
       purple with half the other icons on the page (buttons, Services, Hosting rows, etc.). Only
       used in this one #appTicketsCard row on clientareahome.tpl, confirmed via grep — safe to
       change here without touching any other page. */
    background: var(--notice-soft);
    color: var(--notice-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 0.85rem;
}
.app-ticket-row-body { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.app-ticket-row-body .label { border-radius: 999px !important; padding: 0.2rem 0.6rem !important; font-size: var(--text-xs) !important; font-weight: 700 !important; margin-left: 0.35rem; }
.app-ticket-row-body small { color: var(--text-lifted); font-weight: 400; }
.app-ticket-row-arrow {
    width: 32px;
    height: 32px;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-lifted);
    flex-shrink: 0;
    font-size: 0.75rem;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-ticket-row:hover .app-ticket-row-arrow { background: var(--bg-muted); }

/* Help centre / quick-links card. */
.app-quicklink-row {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.5rem;
    border: 0;
    border-bottom: 1px solid var(--border-muted);
    color: var(--text);
}
.app-quicklink-row:last-child { border-bottom: 0; }
.app-quicklink-icon {
    width: 36px;
    height: 36px;
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--text-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-size: 0.9rem;
}
.app-quicklink-text { flex: 1 1 auto; min-width: 0; }
.app-quicklink-title { font-weight: 700; color: var(--text); display: block; }
.app-quicklink-desc {
    color: var(--text-lifted);
    font-size: var(--text-sm);
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.app-quicklink-arrow {
    width: 32px;
    height: 32px;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-lifted);
    flex-shrink: 0;
    font-size: 0.75rem;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-quicklink-row:hover .app-quicklink-arrow { background: var(--bg-muted); }

/* Domain-search upsell banner (reuses .app-gradient-banner for the
   "always on-brand" shell, build reference §12 "Dark-mode conventions") —
   a plain GET form to the SAME cart.php domain-add destination
   includes/domain-search.tpl already uses (that shared partial is
   untouched), per build reference §3's recommendation. */
.app-domain-upsell-banner { display: flex; flex-direction: column; gap: 1.5rem; }
.app-domain-upsell-search { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.app-domain-upsell-field {
    flex: 1 1 320px;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: rgba(255, 255, 255, 0.97);
    border-radius: 999px;
    padding: 0 0.5rem 0 1.25rem;
    transition: box-shadow var(--duration-fast) var(--ease-out);
}
.app-domain-upsell-field i { color: var(--text-lifted); }
.app-domain-upsell-input {
    flex: 1 1 auto;
    border: 0;
    background: transparent;
    padding: 0.85rem 0.5rem;
    font-size: var(--text-lg);
    color: var(--text);
    min-width: 0;
}
/* The global input:focus-visible box-shadow ring (see ~line 289) is square-cornered and looks
   broken sitting inside this fully rounded (border-radius:999px) pill field — give the PILL
   itself the ring instead, shaped to match, and suppress the mismatched one on the bare input. */
.app-domain-upsell-input:focus,
.app-domain-upsell-input:focus-visible { outline: none; box-shadow: none; }
.app-domain-upsell-field:focus-within {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.97), 0 0 0 4px var(--grayscale);
}
.app-domain-upsell-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    /* Was var(--grayscale)/var(--text-inverted) — a plain black-in-light/white-in-dark button that
       flips its OWN color scheme with the theme toggle while sitting on this banner's deliberately
       NEVER-themed purple gradient (build reference §12), which read as slightly disconnected from
       the "always-brand" banner around it. 2026-08-13 innovate pass: --primary-accented is itself
       one of the tokens NOT redefined in the dark-mode block (verified in this file's :root vs.
       :root[data-theme="dark"] blocks), so it's already theme-independent the same way the banner
       is — a deep, permanently-purple pill that pops against the lighter gradient wash and stays
       visually connected to the brand in both themes, instead of ever going flat black/white. */
    background: var(--primary-accented);
    color: var(--white);
    border: 0;
    border-radius: 999px;
    padding: 0.85rem 1.5rem;
    font-weight: 700;
    transition: background-color var(--duration-slow) var(--ease-standard),
        transform var(--duration-fast) var(--ease-out),
        box-shadow var(--duration-fast) var(--ease-out);
}
.app-domain-upsell-btn:hover {
    background: var(--primary-lifted);
    color: var(--white);
    transform: translateY(-1px);
    box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--primary) 55%, transparent);
}

/* Generic leftover-panels dump (Unpaid Invoices, Recent News/Announcements,
   any hook/addon-injected homepage panel not given a bespoke design above)
   — same accent-card shell the stock template already used, just stacked
   full-bleed instead of the old 3-column split, so no hook's data or
   functionality is ever silently dropped by this redesign. */
.app-home-extra-panels .card { margin-bottom: 1.5rem; }
.app-home-extra-panels .card:last-child { margin-bottom: 0; }

/* =========================================================================
   Account / Profile pages — clientareadetails.tpl, clientareasecurity.tpl,
   user-profile.tpl, user-security.tpl, user-password.tpl,
   account-contacts-manage.tpl, account-contacts-new.tpl,
   two-factor-challenge.tpl, two-factor-new-backup-code.tpl (profile agent).

   ELEVATION PASS (2026-08-13, ui-ux-pro-max "innovative, not just recolored"
   retheme): every page in this surface now wraps its content in a single
   `.acct-shell` div (added directly in each .tpl right after the gradient
   banner) — one predictable scoping root instead of hand-adding a class to
   every card/input, so the existing .acct-card-header-row/.acct-field-grid/
   .acct-form-actions component classes below keep their original markup
   contract (zero churn on existing call sites) while new cross-cutting
   rules (input focus ring, card entrance stagger, sticky save bar) key off
   `.acct-shell` alone. New category-color icon-chip modifiers (-blue/
   -orange/-green/-indigo, default unsuffixed = purple/primary) let each
   settings section carry its own accent, mirroring the colored-icon-chip
   language already established on the Market Connect marketing pages
   (market/_tokens.css's --mc-purple/-orange/-green/-blue) and this app's
   own empty-state.tpl tone system — never a new hex, only the existing
   --primary/--info/--secondary/--success/--notice tokens.
   ========================================================================= */
.acct-shell { position: relative; }

/* Card/section header row (icon + title/subtext + optional action), per
   build reference §8 "Profile > Personal" record-manager heading pattern. */
.acct-card-header-row {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    margin-bottom: 1.35rem;
    flex-wrap: wrap;
}
.acct-card-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--rounding-lg);
    background: linear-gradient(155deg, var(--primary-soft), color-mix(in srgb, var(--primary-soft) 55%, transparent));
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    flex-shrink: 0;
    box-shadow: inset 0 1px 0 color-mix(in srgb, white 35%, transparent);
}
:root[data-theme="dark"] .acct-card-icon { box-shadow: inset 0 1px 0 color-mix(in srgb, white 8%, transparent); }
.acct-card-icon-blue { background: linear-gradient(155deg, var(--info-soft), color-mix(in srgb, var(--info-soft) 55%, transparent)); color: var(--info); }
.acct-card-icon-orange { background: linear-gradient(155deg, var(--secondary-soft), color-mix(in srgb, var(--secondary-soft) 55%, transparent)); color: var(--secondary); }
.acct-card-icon-green { background: linear-gradient(155deg, var(--success-soft), color-mix(in srgb, var(--success-soft) 55%, transparent)); color: var(--success-accented); }
.acct-card-icon-indigo { background: linear-gradient(155deg, var(--notice-soft), color-mix(in srgb, var(--notice-soft) 55%, transparent)); color: var(--notice); }
.acct-card-heading { flex: 1 1 auto; min-width: 0; }
.acct-card-title { font-weight: 700; font-size: 1.05rem; color: var(--text); margin: 0 0 0.2rem; letter-spacing: -0.01em; }
.acct-card-subtext { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }
.acct-card-header-action { margin-left: auto; flex-shrink: 0; }

/* Two-column responsive field grid, replaces the old row/col-md-6/float hack. */
.acct-field-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0 1.25rem;
}
.acct-field-grid > .form-group { margin-bottom: 1.1rem; }
.acct-field-full { grid-column: 1 / -1; }
.acct-field-grid label {
    display: block;
    font-weight: 600;
    font-size: var(--text-md);
    color: var(--text-accented);
    margin-bottom: 0.4rem;
}
@media (max-width: 640px) {
    .acct-field-grid { grid-template-columns: 1fr; }
}

/* Default-Bootstrap-blue focus-ring fix — every text input/select on this
   surface (inside OR outside .acct-field-grid — the security-question and
   password forms below use plain .form-group) previously fell through to
   Bootstrap4's stock blue glow with no on-brand treatment. Scoped to
   `.acct-shell` so it only ever touches these 7 pages, never any other
   page's inputs. */
.acct-shell .form-control,
.acct-shell .custom-select {
    border-radius: var(--rounding-lg);
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.acct-shell .form-control:focus,
.acct-shell .custom-select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 4px var(--primary-soft);
}

.acct-form-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding-top: 0.25rem;
}
.acct-form-actions-danger { margin-left: auto; }

/* Floating "save bar" for long multi-card forms (clientareadetails.tpl,
   account-contacts-manage.tpl, account-contacts-new.tpl) — genuinely stays
   pinned to the viewport while scrolling through ANY part of the form, not
   just its final screenful. NOTE: this deliberately uses `position: fixed`,
   not `sticky` — this element is the LAST child of a form that's several
   thousand px tall, so a `sticky` version has zero float range (a sticky
   element only ever "catches" once its own normal static position is
   already within `bottom` distance of the viewport, which for a last-child
   element is basically the same screenful it would occupy unstyled anyway;
   confirmed empirically live before switching approaches). Anchored
   bottom-right (not full-width/centered) specifically so it never needs to
   track the two-column app-shell's variable content-column bounds (sidebar
   present/collapsed/off-canvas) — a `:has()` rule below adds matching
   bottom padding to `.acct-shell` so the floating bar never covers the
   last card/footer. Matches the "never lose your Save button on a long
   settings form" pattern from Stripe/Linear-style SaaS settings screens. */
.acct-form-actions-sticky {
    position: fixed;
    right: 1.5rem;
    bottom: 1.5rem;
    left: auto;
    z-index: 60;
    max-width: calc(100vw - 3rem);
    margin: 0;
    padding: 0.85rem 1.1rem;
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-2xl);
    box-shadow: var(--elevation-4);
}
.acct-shell:has(.acct-form-actions-sticky) { padding-bottom: 5.5rem; }
@media (max-width: 640px) {
    .acct-form-actions-sticky {
        left: 0;
        right: 0;
        bottom: 0;
        max-width: none;
        border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
        border-left-width: 0;
        border-right-width: 0;
        border-bottom-width: 0;
        padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
        justify-content: center;
    }
    .acct-shell:has(.acct-form-actions-sticky) { padding-bottom: 4.5rem; }
}

.acct-badge-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.85rem; flex-wrap: wrap; }

/* Labelled toggle "settings row" (switch on the right, label + live status
   description on the left) — clientareasecurity.tpl's SSO switch. Wraps the
   EXISTING #inputAllowSso / #ssoStatusTextEnabled / #ssoStatusTextDisabled
   elements verbatim (same ids/names/classes, so any WHMCS-core JS bound to
   them by id keeps working) — purely a layout upgrade from "checkbox +
   inline sentence" to a proper settings row. */
.acct-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    background: var(--bg-muted);
    flex-wrap: wrap;
}
.acct-toggle-row-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.acct-toggle-row-label { font-weight: 600; color: var(--text); font-size: var(--text-md); }
.acct-toggle-row-desc { color: var(--text-lifted); font-size: var(--text-sm); }
.acct-toggle-row input[type="checkbox"] { flex-shrink: 0; }

/* "Choose contact" switcher toolbar — legacy select-based version, kept in
   case any call site isn't upgraded to the .acct-contact-grid picker below. */
.acct-contact-switcher { display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; }
.acct-contact-switcher .form-group { flex: 1 1 260px; margin-bottom: 0; }
.acct-contact-switcher label {
    display: block;
    font-weight: 600;
    font-size: var(--text-md);
    color: var(--text-accented);
    margin-bottom: 0.4rem;
}

/* Contact picker — replaces the plain <select> "choose a contact" dropdown
   with a grid of tappable identity cards (account-contacts-manage.tpl,
   account-contacts-new.tpl). Each card is its own tiny same-origin POST
   <form> (contactid hidden input + submit button styled as the whole card)
   — zero JS, fully keyboard accessible, identical backend contract to the
   original onchange="submit()" <select>. */
.acct-contact-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0.75rem;
}
.acct-contact-card-form { margin: 0; min-width: 0; }
.acct-contact-card {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.85rem 0.9rem;
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    background: var(--bg);
    text-align: left;
    cursor: pointer;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
    text-decoration: none;
    color: inherit;
}
.acct-contact-card:hover {
    border-color: var(--primary);
    box-shadow: var(--elevation-2);
    transform: translateY(-1px);
    text-decoration: none;
    color: inherit;
}
.acct-contact-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.acct-contact-card.is-active {
    border-color: var(--primary);
    background: var(--primary-soft);
    box-shadow: 0 0 0 1px var(--primary) inset;
}
.acct-contact-card-body { min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.acct-contact-card-name { font-weight: 600; color: var(--text); font-size: var(--text-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acct-contact-card-email { font-size: var(--text-sm); color: var(--text-lifted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acct-contact-card-add { border-style: dashed; color: var(--text-lifted); }
.acct-contact-card-add:hover { color: var(--primary); }

/* Reusable initials avatar chip — shares the same primary-soft/-accented
   visual language as the sub-accounts table's .acct-um-avatar, used here on
   contact cards and the live name-preview on user-profile.tpl. */
.acct-avatar-chip {
    width: 40px;
    height: 40px;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--text-md);
    flex-shrink: 0;
    text-transform: uppercase;
}
.acct-avatar-chip-add { background: var(--bg-muted); color: var(--text-muted); }
.acct-avatar-chip-add svg { width: 18px; height: 18px; }

/* Password field visibility toggle (user-password.tpl). */
.acct-pw-field { position: relative; }
.acct-pw-field .form-control { padding-right: 2.75rem; }
.acct-pw-toggle {
    position: absolute;
    top: 50%;
    right: 0.4rem;
    transform: translateY(-50%);
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: var(--rounding-md);
    color: var(--text-muted);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.acct-pw-toggle:hover { background: var(--bg-muted); color: var(--text); }
.acct-pw-toggle:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.acct-pw-toggle svg { width: 18px; height: 18px; }

/* Live password-requirements checklist (user-password.tpl) — purely
   additive, second/independent listener on #inputNewPassword1; does not
   touch pwstrength.tpl's own meter markup or JS. */
.acct-pw-checklist { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.15rem 1rem; }
@media (max-width: 460px) { .acct-pw-checklist { grid-template-columns: 1fr; } }
.acct-pw-check-item { display: flex; align-items: center; gap: 0.5rem; font-size: var(--text-sm); color: var(--text-lifted); padding: 0.2rem 0; transition: color var(--duration-fast) var(--ease-out); }
.acct-pw-check-item.is-met { color: var(--text); }
.acct-pw-check-icon {
    width: 18px; height: 18px; border-radius: 999px;
    border: 1.5px solid var(--border-lifted);
    display: flex; align-items: center; justify-content: center;
    color: transparent;
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.acct-pw-check-icon svg { width: 11px; height: 11px; }
.acct-pw-check-item.is-met .acct-pw-check-icon { background: var(--success); border-color: var(--success); color: #fff; }

/* Security score overview widget (user-security.tpl) — computed purely from
   real booleans already in template scope: $twoFactorAuthAvailable /
   $twoFactorAuthEnabled and $securityQuestions->count() / $user->
   hasSecurityQuestion(). Linked-accounts availability renders as a
   separate, visually distinct informational row and never contributes to
   the score, since no "is currently linked" boolean is exposed here — see
   the .tpl's own comment for the exact math. */
.acct-secscore-card .card-body { padding: 1.5rem; }
.acct-secscore-row { display: flex; align-items: center; gap: 1.75rem; flex-wrap: wrap; }
.acct-secscore-ring-wrap { position: relative; width: 108px; height: 108px; flex-shrink: 0; }
.acct-secscore-ring { width: 100%; height: 100%; transform: rotate(-90deg); }
.acct-secscore-ring-track { fill: none; stroke: var(--border-muted); stroke-width: 9; }
.acct-secscore-ring-fill {
    fill: none;
    stroke: var(--warning);
    stroke-width: 9;
    stroke-linecap: round;
    stroke-dasharray: 263.9;
    transition: stroke-dashoffset 900ms var(--ease-standard), stroke var(--duration-slow) var(--ease-standard);
}
.acct-secscore-ring-fill.is-complete { stroke: var(--success); }
.acct-secscore-ring-label {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.acct-secscore-ring-num { font-size: 1.35rem; font-weight: 800; color: var(--text); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.acct-secscore-ring-sep { color: var(--text-muted); font-weight: 600; margin: 0 0.05em; }
.acct-secscore-ring-sub { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-top: 0.1rem; }
.acct-secscore-text { flex: 1 1 260px; min-width: 0; }
.acct-secscore-title { font-size: 1.05rem; font-weight: 700; color: var(--text); margin: 0 0 0.3rem; letter-spacing: -0.01em; }
.acct-secscore-checklist { margin-top: 0.9rem; display: flex; flex-direction: column; gap: 0.55rem; }
.acct-secscore-check-item { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
.acct-secscore-check-label { display: flex; align-items: center; gap: 0.55rem; font-weight: 600; font-size: var(--text-md); color: var(--text); }
.acct-secscore-check-label svg { width: 18px; height: 18px; color: var(--text-muted); flex-shrink: 0; }
.acct-secscore-check-item-info { opacity: 0.85; }
.acct-secscore-note { margin: 0.6rem 0 0; font-size: var(--text-sm); color: var(--text-muted); font-style: italic; }

/* Card entrance — subtle staggered fade+lift on first paint for every
   top-level .card inside .acct-shell (motion doc's own "restrained, not
   heavy scroll choreography" finding — same duration/ease language as the
   Market Connect pages' .vpn-reveal, fired on load instead of on scroll
   since these are short, mostly-above-the-fold forms). */
.acct-shell > form > .card,
.acct-shell > .card {
    animation: acctCardIn 0.5s var(--ease-standard) both;
}
.acct-shell > form > .card:nth-of-type(1), .acct-shell > .card:nth-of-type(1) { animation-delay: 0ms; }
.acct-shell > form > .card:nth-of-type(2), .acct-shell > .card:nth-of-type(2) { animation-delay: 60ms; }
.acct-shell > form > .card:nth-of-type(3), .acct-shell > .card:nth-of-type(3) { animation-delay: 120ms; }
.acct-shell > form > .card:nth-of-type(4), .acct-shell > .card:nth-of-type(4) { animation-delay: 180ms; }
.acct-shell > form > .card:nth-of-type(5), .acct-shell > .card:nth-of-type(5) { animation-delay: 240ms; }
.acct-shell > form > .card:nth-of-type(n+6), .acct-shell > .card:nth-of-type(n+6) { animation-delay: 300ms; }
@keyframes acctCardIn {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .acct-shell > form > .card,
    .acct-shell > .card,
    .acct-secscore-ring-fill {
        animation: none !important;
        transition: none !important;
    }
}

/* Centered auth-card shell (two-factor-challenge.tpl, two-factor-new-backup-code.tpl). */
.acct-auth-shell { text-align: center; }
.acct-auth-icon {
    width: 64px;
    height: 64px;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    margin: 0 auto 1rem;
    position: relative;
}
.acct-auth-icon::before {
    content: "";
    position: absolute;
    inset: -8px;
    border-radius: 999px;
    border: 1.5px solid var(--primary-soft);
    animation: acctAuthPulse 2.4s var(--ease-standard) infinite;
}
.acct-auth-icon-success { background: var(--success-soft); color: var(--success-accented); }
.acct-auth-icon-success::before { border-color: var(--success-soft); }
@keyframes acctAuthPulse {
    0% { transform: scale(0.9); opacity: 0.9; }
    100% { transform: scale(1.35); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .acct-auth-icon::before { animation: none; display: none; }
}

/* Identifier-style display for the one-time backup code (build reference §12
   typography: "Invoice codes / ticket IDs rendered in a bold, slightly
   condensed/monospace-leaning treatment to read as identifiers"). */
.acct-code-display {
    display: inline-block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-weight: 700;
    font-size: 1.5rem;
    letter-spacing: 0.12em;
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    padding: 0.9rem 1.5rem;
    color: var(--text);
}
.acct-code-row { display: inline-flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; justify-content: center; }

/* =========================================================================
   Cross-cutting sweep fix (out-of-scope-this-round pages: Knowledgebase,
   Downloads, Quotes, Announcements, Contact, Server Status, and Support's
   supportticketsubmit-kbsuggestions.tpl "Suggested articles" list — all
   reuse this same stock Bootstrap "list-group-item article row" markup).
   ========================================================================= */
/* Bootstrap's `.text-black-50 { color: rgba(0,0,0,.5) !important }` is a
   hardcoded-black utility class used (unthemed) on the small file-icon in
   every KB/downloads "article row" (`<i class="fal fa-file-alt fa-fw
   text-black-50">`). It's already low-contrast by design in light mode,
   but in dark mode it sits on the same-fixed dark .list-group-item
   background as everything else in this file's "Dark-mode contrast fixes"
   section above — rgba(0,0,0,.5) over a near-black (#0c0b12) surface
   renders as an essentially invisible icon. Re-point it to the muted-text
   token in dark mode only (light mode is untouched/still exactly the
   hosting.com-reference-matching 50%-black tone). */
:root[data-theme="dark"] .text-black-50 {
    color: var(--text-muted) !important;
}

/* =========================================================================
   Support Tickets — supportticketslist.tpl, viewticket.tpl,
   supportticketsubmit-*.tpl, ticketfeedback.tpl (support agent).
   New component classes only — nothing above this block was touched.
   ========================================================================= */

/* ---- Ticket list: stat cards ---- */
.tix-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.tix-stat-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    padding: 1.1rem 1.25rem;
}
.tix-stat-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.15rem;
    flex-shrink: 0;
}
.tix-stat-icon-warning { background: var(--warning-soft); color: var(--warning-accented); }
.tix-stat-icon-neutral { background: var(--bg-accented); color: var(--text-lifted); }
.tix-stat-value { font-size: 1.5rem; font-weight: 700; color: var(--text); line-height: 1.2; }
.tix-stat-label { font-size: var(--text-sm); color: var(--text-lifted); font-weight: 600; }
@media (max-width: 640px) {
    .tix-stats { grid-template-columns: 1fr; }
}

/* ---- Toolbar: search / filter / sort ---- */
.tix-toolbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.tix-search {
    position: relative;
    flex: 1 1 240px;
    min-width: 200px;
}
.tix-search i {
    position: absolute;
    left: 0.9rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    font-size: 0.85rem;
    pointer-events: none;
}
.tix-search input {
    width: 100%;
    padding: 0.6rem 0.9rem 0.6rem 2.25rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    background: var(--bg);
    color: var(--text);
    font-size: var(--text-md);
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.tix-search input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}
.tix-toolbar-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-accented);
    border-radius: var(--rounding-md);
    font-weight: 600;
    font-size: var(--text-md);
    white-space: nowrap;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.tix-toolbar-btn:hover,
.tix-toolbar-btn[aria-expanded="true"] { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); }
.tix-sort select {
    padding: 0.6rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    background: var(--bg);
    color: var(--text);
    font-size: var(--text-md);
    font-weight: 600;
}
.tix-filter-menu {
    padding: 0.85rem 1rem 1rem;
    min-width: 220px;
}
.tix-filter-menu-title { font-weight: 700; color: var(--text); font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.6rem; }
.tix-filter-check { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0; color: var(--text-accented); font-size: var(--text-md); font-weight: 500; cursor: pointer; }
.tix-filter-check input { accent-color: var(--primary); width: 15px; height: 15px; }

/* ---- Ticket row-cards. #tableTicketsList stays a real <table> so the
   existing DataTables wiring in includes/tablelist.tpl keeps working
   unmodified — each <tr> is CSS-blockified into a flex "card" instead
   (build reference §7 "ticket row cards"); flex items ignore an inner
   display:table-cell per the CSS Display spec's blockification rules,
   so plain <td>s work as flex children with no extra wrapper markup. ---- */
.tix-table { border-collapse: separate; border-spacing: 0 0.65rem; width: 100%; margin-bottom: 0; }
.tix-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.tix-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.tix-row:hover { background: var(--bg-muted); border-color: var(--border-lifted); }
.tix-row td { border: 0; padding: 0.9rem 0; display: block; }
.tix-col-avatar { flex: 0 0 auto; position: relative; padding-left: 1.1rem !important; }
.tix-avatar {
    width: 40px;
    height: 40px;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--text-md);
}
.tix-unread-dot {
    position: absolute;
    top: 8px;
    left: 1.1rem;
    width: 9px;
    height: 9px;
    border-radius: 999px;
    background: var(--error);
    border: 2px solid var(--bg);
}
.tix-col-main { flex: 1 1 auto; min-width: 0; }
.tix-main-link { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; color: inherit; }
.tix-main-link:hover { color: inherit; }
.tix-main-link:hover .tix-subject { color: var(--primary); }
.tix-dept-label { display: block; width: 100%; font-size: var(--text-sm); color: var(--text-lifted); font-weight: 600; margin-bottom: 0.15rem; }
.tix-subject { font-weight: 700; color: var(--text); font-size: var(--text-lg); transition: color var(--duration-fast) var(--ease-out); }
.tix-id-badge {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--text-lifted);
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-sm);
    padding: 0.1rem 0.4rem;
}
.tix-col-status { flex: 0 0 auto; padding-right: 0.5rem !important; }
.tix-col-date { flex: 0 0 auto; padding-right: 1.1rem !important; color: var(--text-lifted); font-size: var(--text-sm); white-space: nowrap; }
.tix-lock-icon { color: var(--text-muted); margin-right: 0.35rem; font-size: 0.8rem; }
.tix-tab-empty { border: 1px dashed var(--border); border-radius: var(--rounding-lg); margin-top: 0.5rem; }

@media (max-width: 767.98px) {
    .tix-row { flex-wrap: wrap; padding: 0.25rem 0; }
    .tix-col-date { margin-left: calc(40px + 1.1rem + 1rem); }
}

/* ---- pill-select.tpl (new partial: department single-select chips) ---- */
.app-pill-select { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.app-pill-select-option {
    position: relative;
    display: inline-flex;
    flex-direction: column;
    padding: 0.6rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg);
    color: var(--text-accented);
    font-weight: 600;
    font-size: var(--text-md);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-pill-select-option input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.app-pill-select-option:hover { border-color: var(--border-lifted); background: var(--bg-muted); }
.app-pill-select-option.selected,
.app-pill-select-option:has(input:checked) {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}
.app-pill-select-option input:focus-visible ~ .app-pill-select-label { box-shadow: var(--focus-ring); border-radius: var(--rounding-sm); }
.app-pill-select-desc { font-size: var(--text-sm); font-weight: 400; opacity: 0.85; }
.app-pill-select-link { text-decoration: none; }
.app-pill-select-link:hover { text-decoration: none; }

/* ---- View ticket page ---- */
.tix-view-actions { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: -0.5rem 0 1.5rem; }
.tix-view-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 1.5rem; align-items: start; }
@media (max-width: 991.98px) {
    .tix-view-grid { grid-template-columns: 1fr; }
}

.tix-composer { margin-bottom: 1.5rem; }
.tix-composer .card-title { margin-bottom: 1rem; }

.tix-thread { display: flex; flex-direction: column; }
.tix-thread-item { display: flex; gap: 0.9rem; position: relative; padding-bottom: 1.75rem; }
.tix-thread-item:last-child { padding-bottom: 0; }
.tix-thread-item::before {
    content: '';
    position: absolute;
    left: 19px;
    top: 44px;
    bottom: 0;
    width: 2px;
    background: var(--border-muted);
}
.tix-thread-item:last-child::before { display: none; }
.tix-thread-item.attachments-hidden { display: none; }
.tix-thread-avatar-col { flex: 0 0 auto; position: relative; z-index: 1; }
.tix-thread-avatar {
    width: 40px;
    height: 40px;
    border-radius: 999px;
    background: var(--bg-accented);
    color: var(--text-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--text-md);
    border: 2px solid var(--bg);
}
.tix-thread-item.staff .tix-thread-avatar { background: var(--primary); color: #fff; }
.tix-thread-staff-badge {
    position: absolute;
    bottom: -2px;
    right: -2px;
    width: 16px;
    height: 16px;
    border-radius: 999px;
    background: var(--success);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 9px;
    border: 2px solid var(--bg);
}
.tix-thread-body { flex: 1 1 auto; min-width: 0; }
.tix-thread-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.tix-thread-name { font-weight: 700; color: var(--text); }
.tix-thread-role { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; padding: 0.05rem 0.45rem; border-radius: 999px; background: var(--bg-accented); color: var(--text-lifted); }
.tix-thread-item.staff .tix-thread-role { background: var(--primary-soft); color: var(--primary); }
.tix-thread-time { margin-left: auto; font-size: var(--text-sm); color: var(--text-muted); }
.tix-thread-bubble { background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: var(--rounding-lg); padding: 1rem 1.1rem; color: var(--text); }
.tix-thread-item.staff .tix-thread-bubble { background: var(--primary-soft); border-color: transparent; }
.tix-thread-ip { color: var(--text-muted); font-size: var(--text-sm); margin-top: 0.5rem; }
.tix-thread-attachments { margin-top: 0.75rem; }
.tix-thread-attachments-title { font-size: var(--text-sm); font-weight: 700; color: var(--text-lifted); margin-bottom: 0.4rem; }
.tix-attachment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.tix-attachment-list li a,
.tix-attachment-list li span {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    background: var(--bg);
    color: var(--text-accented);
    font-size: var(--text-sm);
    transition: background-color var(--duration-fast) var(--ease-out);
}
.tix-attachment-list li a:hover { background: var(--bg-muted); color: var(--text); }
.tix-thread-filter { margin-bottom: 1.25rem; }

/* Rating stars (existing WHMCS ticket-feedback widget) restyled to brand tokens. */
.rating .star,
.rating-done .star { color: var(--border-lifted); }
.rating-done .star.active { color: var(--warning); }

/* Right-column collapsible cards ("Ticket Details" / "Product Details"), default collapsed. */
.tix-side-card { margin-bottom: 1.25rem; overflow: hidden; }
.tix-side-card-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 1.1rem 1.25rem;
    background: transparent;
    border: 0;
    color: var(--text);
    font-weight: 700;
    font-size: var(--text-lg);
    text-align: left;
}
.tix-side-card-toggle i { color: var(--text-lifted); transition: transform var(--duration-base) var(--ease-standard); }
.tix-side-card-toggle.collapsed i { transform: rotate(-90deg); }
.tix-side-card-body { padding: 0 1.25rem 1.25rem; }
.tix-detail-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.55rem 0; border-top: 1px solid var(--border-muted); }
.tix-detail-row:first-child { border-top: 0; }
.tix-detail-label { color: var(--text-lifted); font-size: var(--text-sm); font-weight: 600; }
.tix-detail-value { color: var(--text); font-size: var(--text-md); font-weight: 600; text-align: right; }
.tix-detail-value.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: var(--text-sm); }

/* ---- New-ticket flow (supportticketsubmit-*.tpl) ---- */
.tix-kb-suggestions { list-style: none; margin: 0 0 1.5rem; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.tix-kb-suggestions li a {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    color: var(--text);
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.tix-kb-suggestions li a:hover { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); }
.tix-kb-suggestions li a i { color: var(--primary); flex-shrink: 0; margin-top: 0.2rem; }
.tix-kb-suggestions li a small { display: block; color: var(--text-lifted); font-weight: 400; }

.tix-confirm-icon {
    width: 64px;
    height: 64px;
    border-radius: 999px;
    background: var(--success-soft);
    color: var(--success-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    margin: 0 auto 1.25rem;
}

.tix-product-picker-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    margin-bottom: 0.6rem;
}

/* =========================================================================
   Billing agent — clientareainvoices.tpl, viewinvoice.tpl,
   account-paymentmethods.tpl, account-paymentmethods-manage.tpl,
   account-paymentmethods-billing-contacts.tpl, clientareaaddfunds.tpl,
   invoice-payment.tpl. New component classes only, prefixed app-bill- /
   app-inv- / app-pm- / app-funds- to avoid colliding with any other
   concurrently-running agent's own new classes in this shared file —
   nothing above this block was touched. All colors/spacing/motion
   reference the existing token system (custom.css top-of-file :root and
   --ease-* / --duration-* vars), so nothing here needs a separate
   :root[data-theme="dark"] override unless explicitly noted.
   ========================================================================= */

/* Small "ghost" (text-only, no border) button — Reset/Cancel-weight actions
   next to a primary app-btn-gradient action, reused across the Billing
   pages' filter panels and forms. */
.app-btn-ghost-sm {
    background: transparent;
    border: 0;
    color: var(--text-lifted);
    font-weight: 600;
    font-size: var(--text-md);
    padding: 0.5rem 0.75rem;
    border-radius: var(--rounding-md);
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-btn-ghost-sm:hover { background: var(--bg-muted); color: var(--text); }

/* -------------------------------------------------------------------------
   clientareainvoices.tpl — toolbar, pill-tab-filtered table, pagination.
   ------------------------------------------------------------------------- */
.app-bill-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    padding: 1.5rem;
}
.app-bill-toolbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.app-bill-search { flex: 1 1 260px; max-width: 360px; }
.app-bill-toolbar-actions { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; flex-wrap: wrap; }
.app-pill-label { white-space: nowrap; }

.app-bill-filter-menu { width: 300px; max-width: 90vw; }
.app-bill-filter-body { padding: 0.25rem 1rem 0.5rem; }
.app-bill-filter-row { margin-bottom: 0.9rem; }
.app-bill-filter-row:last-child { margin-bottom: 0; }
.app-bill-filter-row label { display: block; font-weight: 600; font-size: var(--text-sm); color: var(--text-accented); margin-bottom: 0.35rem; }
.app-bill-filter-inputs { display: flex; align-items: center; gap: 0.5rem; color: var(--text-muted); }
.app-bill-filter-inputs input { min-width: 0; flex: 1 1 auto; }
.app-bill-filter-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--border-muted);
    margin-top: 0.5rem;
}

.app-bill-table-wrap { margin-top: 1.25rem; overflow-x: auto; }
.app-bill-table { width: 100%; border-collapse: collapse; }
.app-bill-table thead th {
    text-align: left;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-lifted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
.app-bill-table tbody tr {
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
    border-bottom: 1px solid var(--border-muted);
}
.app-bill-table tbody tr:last-child { border-bottom: 0; }
.app-bill-table tbody tr:hover { background: var(--bg-muted); }
.app-bill-table tbody td { padding: 0.85rem 0.75rem; color: var(--text-accented); vertical-align: middle; }
.app-bill-invoice-num { font-weight: 700; color: var(--text); letter-spacing: 0.01em; }
.app-bill-invoice-sub { font-size: var(--text-sm); color: var(--text-muted); margin-top: 0.15rem; }
.app-bill-invoice-sub i { margin-right: 0.3rem; }
.app-bill-balance-zero { color: var(--success); font-weight: 700; }
.app-bill-col-actions { text-align: right; white-space: nowrap; }
@media (max-width: 700px) {
    .app-bill-table thead { display: none; }
    .app-bill-table, .app-bill-table tbody, .app-bill-table tr, .app-bill-table td { display: block; width: 100%; }
    .app-bill-table tbody tr { padding: 0.75rem; border: 1px solid var(--border-muted); border-radius: var(--rounding-md); margin-bottom: 0.6rem; }
    .app-bill-table tbody td { padding: 0.2rem 0; }
    .app-bill-col-actions { text-align: left; margin-top: 0.5rem; }
}

/* -------------------------------------------------------------------------
   viewinvoice.tpl — standalone document (no header.tpl/footer.tpl shell,
   see the file's own comment on why). It loads its own separate, fully
   self-contained Bootstrap bundle (css/invoice.min.css, hardcoded
   `body{background:#fff;color:#212529}` and its own unthemed `--primary`
   etc.) rather than theme.min.css's compiled tokens, which is why `.card`
   /`.table`/`.btn-default`/`.btn-primary` all already look reasonable in
   LIGHT mode without any help (that stylesheet's own defaults) — but need
   one explicit dark-mode fix here for the `body` background itself, since
   nothing else in this file touches a bare `body` selector for dark mode
   (only `body[data-app-shell]`, which this standalone page never has).
   Scoped to .app-inv-body so it can only ever match this one page. *}
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] body.app-inv-body {
    background-color: var(--bg) !important;
    color: var(--text) !important;
}

.app-inv-page { max-width: 1180px; margin: 0 auto; padding: 1.75rem 1.5rem 4rem; }

.app-inv-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding-bottom: 1.25rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--border-muted);
}
.app-inv-back { color: var(--text-lifted); font-weight: 600; font-size: var(--text-md); transition: color var(--duration-fast) var(--ease-out); }
.app-inv-back:hover { color: var(--primary); text-decoration: none; }
.app-inv-back i { margin-right: 0.4rem; }
.app-inv-logo .app-inv-logo { max-height: 32px; }
.app-inv-brand .app-inv-logo { max-height: 32px; display: block; }
.app-inv-logo-text { font-weight: 700; font-size: 1.125rem; color: var(--text); }

.app-inv-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
@media (max-width: 900px) {
    .app-inv-grid { grid-template-columns: 1fr; }
}

.app-inv-card { margin-bottom: 1.5rem; }
.app-inv-accent-success { border-top: 3px solid var(--success); }
.app-inv-accent-warning { border-top: 3px solid var(--warning); }
.app-inv-accent-error { border-top: 3px solid var(--error); }
.app-inv-accent-info { border-top: 3px solid var(--info); }
.app-inv-accent-neutral { border-top: 3px solid var(--border-lifted); }

.app-inv-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.app-inv-title { font-size: 1.5rem; font-weight: 700; color: var(--text); }

.app-inv-meta-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 1rem 0;
    margin-bottom: 1.25rem;
    border-top: 1px solid var(--border-muted);
    border-bottom: 1px solid var(--border-muted);
}
.app-inv-meta-issued { text-align: right; }
.app-inv-meta-label { font-size: var(--text-sm); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin-bottom: 0.3rem; }
.app-inv-meta-value { font-size: 1.5rem; font-weight: 700; color: var(--text); }
.app-inv-meta-value-zero { color: var(--success); }
.app-inv-meta-value-sm { font-weight: 600; color: var(--text-accented); }
.app-inv-meta-due { font-size: var(--text-md); color: var(--text-lifted); margin-top: 0.25rem; }

.app-inv-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 560px) { .app-inv-addresses { grid-template-columns: 1fr; } }
.app-inv-address-label { font-size: var(--text-sm); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin-bottom: 0.4rem; }
.app-inv-address { color: var(--text-accented); font-style: normal; line-height: 1.6; }

.app-inv-credit-inputs { display: flex; gap: 0.6rem; margin-top: 0.6rem; max-width: 420px; }
.app-inv-credit-inputs .form-control { flex: 1 1 auto; }

.app-inv-tax-note { color: var(--text-muted); font-size: var(--text-sm); }

.app-inv-qr-card img { max-width: 220px; }

/* -- Side (right) column -- */
.app-inv-pay-card { background: var(--primary-soft); border-color: var(--primary); }
.app-inv-pay-label { font-size: var(--text-sm); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--primary-accented); }
.app-inv-pay-amount { font-size: 2rem; font-weight: 700; color: var(--text); margin: 0.25rem 0; }
.app-inv-pay-due { color: var(--text-lifted); font-size: var(--text-md); margin-bottom: 1rem; }
.app-inv-pay-method { color: var(--text-accented); font-size: var(--text-md); margin-bottom: 1rem; }
.app-inv-gateway-form { margin-bottom: 1rem; }
.app-inv-gateway-form label { display: block; font-weight: 600; font-size: var(--text-sm); color: var(--text-accented); margin-bottom: 0.35rem; }
.app-inv-pay-btn-container { text-align: center; }
.app-inv-pay-btn-container:empty { display: none; }

.app-inv-side-title { font-weight: 700; color: var(--text); margin-bottom: 0.9rem; }
.app-inv-side-actions { display: flex; gap: 0.6rem; }
.app-inv-side-btn { flex: 1 1 0; }

.app-inv-accordion { padding: 0; overflow: hidden; }
.app-inv-accordion-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    background: transparent;
    border: 0;
    padding: 1.1rem 1.25rem;
    font-weight: 700;
    color: var(--text);
    font-size: var(--text-lg);
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-inv-accordion-trigger:hover { background: var(--bg-muted); }
.app-inv-accordion-trigger i:first-child { margin-right: 0.55rem; color: var(--text-lifted); }
.app-inv-accordion-caret { color: var(--text-muted); transition: transform var(--duration-base) var(--ease-standard); font-size: 0.85rem; }
.app-inv-accordion-open .app-inv-accordion-caret { transform: rotate(180deg); }
.app-inv-accordion-panel { max-height: 0; overflow: hidden; transition: max-height var(--duration-base) var(--ease-standard); }
.app-inv-accordion-panel-open { max-height: 480px; }
.app-inv-accordion-panel-inner { padding: 0 1.25rem 1.1rem; }
.app-inv-accordion-panel-inner-flush { padding: 0 0 0.5rem; }
.app-inv-kv-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border-muted); font-size: var(--text-md); }
.app-inv-kv-row:last-child { border-bottom: 0; }
.app-inv-kv-row span:first-child { color: var(--text-muted); }
.app-inv-kv-row span:last-child { color: var(--text); font-weight: 600; text-align: right; }

/* =========================================================================
   My Emails / Refer-a-Friend — clientareaemails.tpl, affiliates.tpl,
   affiliatessignup.tpl (emails-referrals agent). New component classes only
   — nothing above this block is touched. Card shells reuse the existing
   .card/.card-body primitives (already dark-mode-correct via the fixes
   earlier in this file); only page-specific inner components are added here.
   ========================================================================= */

/* Upsell-flavored empty state (build reference §13 pattern #3) — a
   brand-gradient icon badge instead of empty-state.tpl's neutral default,
   reserved for emptiness that maps to a purchasable product/feature (e.g.
   My Emails' zero-mailbox state). Scoped to a wrapper class so the shared
   partial's own default styling is untouched for every other empty state
   (e.g. affiliates.tpl's "No referrals yet", which is a plain pattern #1
   "no results" case and intentionally keeps the neutral badge). */
.app-empty-state-upsell .app-empty-state-icon {
    background: var(--gradient-brand);
    color: #fff;
}

/* My Emails — message list */
.app-emails-row { cursor: pointer; }
.app-emails-date-cell { white-space: nowrap; color: var(--text-lifted); font-size: var(--text-md); }
.app-emails-subject-cell { display: flex; align-items: center; gap: 0.65rem; flex-wrap: wrap; }
.app-emails-row-icon {
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--text-lifted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    flex-shrink: 0;
}
.app-emails-subject { font-weight: 600; color: var(--text); }
.app-emails-attachment { color: var(--text-muted); font-size: 0.85em; }

/* Outline button for row-level secondary actions ("View Message", etc.) —
   a lighter-weight alternative to .app-btn-dark/.app-btn-gradient for
   actions that shouldn't compete visually with a page's primary CTA. */
.app-btn-outline {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: var(--rounding-md);
    padding: 0.4rem 0.85rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    transition: background-color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.app-btn-outline:hover { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); }

/* Refer-a-Friend — stat tile row (Pending/Available/Withdrawn balance, and
   the secondary Clicks/Signups/Conversion row), build reference §8
   "Overview" 3-tile pattern. */
.app-stat-tile-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
@media (max-width: 767.98px) {
    .app-stat-tile-row { grid-template-columns: 1fr; }
}
.app-stat-tile {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    padding: 1.1rem 1.25rem;
}
.app-stat-tile-icon {
    width: 44px;
    height: 44px;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    flex-shrink: 0;
}
.app-stat-tile-info .app-stat-tile-icon { background: var(--info-soft); color: var(--info); }
.app-stat-tile-success .app-stat-tile-icon { background: var(--success-soft); color: var(--success); }
.app-stat-tile-notice .app-stat-tile-icon { background: var(--notice-soft); color: var(--notice); }
.app-stat-tile-label {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-lifted);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.2rem;
}
.app-stat-tile-value { display: block; font-size: 1.4rem; font-weight: 700; color: var(--text); line-height: 1.2; }

.app-section-heading { margin: 2rem 0 1rem; }
.app-section-heading h3 { font-size: 1.1rem; font-weight: 700; color: var(--text); margin: 0; }

/* Referral-link promo card + copy button. .app-referral-copy-btn reuses
   .app-btn-gradient (already position:relative) so the shared
   .app-copied-tooltip bubble (defined earlier, Support PIN modal) anchors
   correctly with no extra positioning rules needed here. */
.app-referral-card-label { font-weight: 700; color: var(--text); margin-bottom: 0.6rem; }
.app-referral-link-row { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.app-referral-link-input { flex: 1 1 260px; font-weight: 600; }
.app-referral-copy-btn { flex-shrink: 0; }

.app-withdraw-cta { margin: 1.5rem 0; }
.app-withdraw-note { color: var(--text-lifted); font-size: var(--text-md); margin-top: 0.6rem; margin-bottom: 0; }

.app-linktous-card img { max-width: 100%; }

/* Refer-a-Friend — signup / opt-in page (affiliatessignup.tpl) */
.app-affiliate-signup-card { text-align: center; padding: 1rem; }
.app-affiliate-signup-icon {
    width: 64px;
    height: 64px;
    margin: 0 auto 1.25rem;
    border-radius: 999px;
    background: var(--gradient-brand);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.75rem;
}
.app-affiliate-signup-title { font-size: 1.4rem; font-weight: 700; color: var(--text); margin-bottom: 0.5rem; }
.app-affiliate-signup-intro { color: var(--text-lifted); font-size: var(--text-lg); max-width: 520px; margin: 0 auto 1.5rem; }
.app-affiliate-signup-benefits { list-style: none; margin: 0 auto 1.75rem; padding: 0; max-width: 560px; text-align: left; display: flex; flex-direction: column; gap: 0.9rem; }
.app-affiliate-signup-benefits li { display: flex; align-items: flex-start; gap: 0.75rem; color: var(--text-accented); }
.app-affiliate-signup-benefits i { color: var(--success); margin-top: 0.2rem; flex-shrink: 0; }
.app-affiliate-activate-btn { font-size: 1rem; padding: 0.85rem 2rem; }

/* =========================================================================
   Products & Services / Domains lists + Product Detail
   (clientareaproducts.tpl, clientareadomains.tpl, clientareaproductdetails.tpl)
   hosting.com-pattern list toolbar + data table + gradient hero + summary
   card (build reference §5, §12, §13). New component classes only — every
   selector below is new, nothing above this block is touched.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Page header (H1 + optional right-aligned actions)
   ------------------------------------------------------------------------- */
.app-page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.app-page-title { font-size: 1.75rem; font-weight: 700; color: var(--text); margin: 0 0 0.2rem; }
.app-page-subtext { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }
.app-page-header-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.app-page-header-btn-outline {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    color: var(--text-accented);
    font-weight: 600;
    background: var(--bg);
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.app-page-header-btn-outline:hover { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); }

/* -------------------------------------------------------------------------
   List toolbar: search input + Filter dropdown + Sort select
   ------------------------------------------------------------------------- */
.app-list-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1rem;
}
.app-list-search {
    position: relative;
    flex: 1 1 260px;
    min-width: 200px;
    max-width: 420px;
}
.app-list-search i {
    position: absolute;
    left: 0.9rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    font-size: 0.85rem;
    pointer-events: none;
}
.app-list-search-input {
    width: 100%;
    padding: 0.6rem 0.9rem 0.6rem 2.35rem;
    border-radius: var(--rounding-md);
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text);
    font-size: var(--text-md);
    transition: border-color var(--duration-fast) var(--ease-out);
}
.app-list-search-input::placeholder { color: var(--text-muted); }
.app-list-search-input:focus { outline: none; border-color: var(--primary); box-shadow: var(--focus-ring); }
.app-list-toolbar-right { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.app-list-filter-menu { padding: 0.9rem 1rem; min-width: 220px; }
.app-list-filter-label { display: block; font-size: var(--text-sm); font-weight: 700; color: var(--text-lifted); margin-bottom: 0.4rem; text-transform: uppercase; letter-spacing: 0.03em; }
.app-list-filter-select,
.app-list-sort-select {
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    background: var(--bg);
    color: var(--text);
    padding: 0.55rem 0.75rem;
    font-size: var(--text-md);
    width: 100%;
}
.app-list-sort-select { width: auto; min-width: 200px; }
.app-list-filter-select:focus,
.app-list-sort-select:focus { outline: none; border-color: var(--primary); box-shadow: var(--focus-ring); }

.app-pill-tabs-row { margin-bottom: 1.25rem; }

/* -------------------------------------------------------------------------
   Data table card — flat bordered surface, border-b row dividers (build
   reference §12 "Shadows/surface treatment", §4 motion doc row pattern)
   ------------------------------------------------------------------------- */
.app-table-card {
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    overflow: hidden;
    background: var(--bg);
}
.app-dtable-wrap { position: relative; }
table.app-dtable { margin-bottom: 0; width: 100%; }
table.app-dtable > thead > tr > th {
    background: var(--bg-muted) !important;
    color: var(--text-lifted) !important;
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    border-bottom: 1px solid var(--border-muted) !important;
    border-top: none !important;
    padding: 0.85rem 1rem;
    white-space: nowrap;
}
table.app-dtable > tbody > tr > td {
    background: var(--bg) !important;
    border-top: none !important;
    border-bottom: 1px solid var(--border-muted) !important;
    padding: 0.85rem 1rem;
    vertical-align: middle;
    color: var(--text);
}
table.app-dtable > tbody > tr:last-child > td { border-bottom: none !important; }
table.app-dtable > tbody > tr {
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
}
table.app-dtable > tbody > tr:hover > td { background: var(--bg-muted) !important; }

.app-cell-icon { width: 44px; padding-right: 0 !important; }
.app-row-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary);
    font-size: 0.9rem;
}
.app-row-icon img { border-radius: var(--rounding-sm); }

.app-cell-name { min-width: 220px; }
.app-cell-category {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    margin-bottom: 0.1rem;
}
.app-cell-product {
    display: block;
    font-weight: 700;
    color: var(--text);
    font-size: var(--text-lg);
}
a.app-cell-product.app-cell-domain-name:hover { color: var(--primary); }
.app-cell-domain-link {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-lifted);
    margin-top: 0.15rem;
}
a.app-cell-domain-link:hover { color: var(--primary); }

.app-cell-price { white-space: nowrap; }
.app-cell-price-amount { display: block; font-weight: 700; color: var(--text); }
.app-cell-price-cycle { display: block; font-size: var(--text-sm); color: var(--text-lifted); }

.app-cell-date { color: var(--text-accented); white-space: nowrap; }

.app-cell-action { text-align: right; white-space: nowrap; }
.app-row-action-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.95rem;
    border-radius: var(--rounding-md);
    border: 1px solid var(--primary);
    color: var(--primary);
    font-weight: 700;
    font-size: var(--text-md);
    background: transparent;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard);
}
.app-row-action-btn:hover { background: var(--primary); color: #fff; }

.app-table-empty-filtered { padding: 1rem 0; }

/* -------------------------------------------------------------------------
   Bulk domain-action bar (clientareadomains.tpl) — same functional buttons
   as before (.setBulkAction ids preserved), restyled to the app-pill-btn look.
   ------------------------------------------------------------------------- */
.app-bulk-actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1rem; }
.app-bulk-more .dropdown-menu { min-width: 200px; }

/* -------------------------------------------------------------------------
   Product Detail — gradient hero (custom markup, reuses --gradient-brand
   directly rather than the gradient-banner.tpl partial so it can host a
   category badge + status badge inline per build reference §5).
   ------------------------------------------------------------------------- */
.app-pd-hero {
    background: var(--gradient-brand);
    border-radius: var(--rounding-lg);
    padding: 1.75rem 2rem;
    margin-bottom: 1.25rem;
    color: #fff;
}
.app-pd-hero-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; }
.app-pd-hero-badge {
    display: inline-block;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.35);
    color: #fff;
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.2rem 0.7rem;
    border-radius: 999px;
    margin-bottom: 0.5rem;
}
.app-pd-hero-heading { font-size: 1.6rem; font-weight: 700; color: #fff; margin: 0; }
.app-pd-hero-status .app-status-badge { background: rgba(255, 255, 255, 0.92); }

/* Alert bar — conditional colored bar gating the page when the product
   isn't fully active (build reference §5 "Conditional colored alert bar").
   Solid tinted fill per §12 "alert/status bars use a solid tinted fill". */
.app-alert-bar {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1rem 1.25rem;
    border-radius: var(--rounding-lg);
    margin-bottom: 1.25rem;
}
.app-alert-bar-icon { font-size: 1.25rem; flex-shrink: 0; }
.app-alert-bar-body { flex: 1 1 auto; }
.app-alert-bar-title { font-weight: 700; margin: 0; }
.app-alert-bar-desc { margin: 0.15rem 0 0; font-size: var(--text-md); opacity: 0.9; }
.app-alert-bar-action {
    flex-shrink: 0;
    background: #fff;
    color: var(--text-inverted, #14121c);
    font-weight: 700;
    padding: 0.5rem 1rem;
    border-radius: var(--rounding-md);
    transition: filter var(--duration-slow) var(--ease-standard);
}
.app-alert-bar-action:hover { filter: brightness(0.95); color: var(--text-inverted, #14121c); }
.app-alert-bar-warning { background: var(--warning-soft); color: var(--warning-accented); }
.app-alert-bar-error { background: var(--error-soft); color: var(--error); }

.app-pd-layout { display: flex; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap; }
.app-pd-main { flex: 1 1 480px; min-width: 0; }
.app-pd-side { flex: 0 0 300px; width: 300px; }
@media (max-width: 767.98px) {
    .app-pd-side { flex: 1 1 100%; width: 100%; }
}

.app-pd-subnav { border-bottom: none; margin-bottom: 0; }
.app-pd-section-card {
    border: 1px solid var(--border-muted);
    border-radius: 0 var(--rounding-lg) var(--rounding-lg) var(--rounding-lg);
    background: var(--bg);
    padding: 1.5rem;
}
.app-pd-section-title { font-size: 1.05rem; font-weight: 700; color: var(--text); margin: 0 0 1rem; }

.app-detail-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.65rem 0;
    border-bottom: 1px solid var(--border-muted);
}
.app-detail-row:last-child { border-bottom: none; }
.app-detail-label { color: var(--text-lifted); font-weight: 600; font-size: var(--text-md); flex: 0 0 40%; }
.app-detail-value { color: var(--text); font-size: var(--text-md); text-align: right; flex: 1 1 auto; word-break: break-word; }
.app-copy-btn {
    border: 0;
    background: transparent;
    color: var(--text-muted);
    margin-left: 0.4rem;
    width: 24px;
    height: 24px;
    border-radius: var(--rounding-sm);
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-copy-btn:hover { background: var(--bg-muted); color: var(--primary); }

.app-pd-links { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-top: 1rem; }

.app-summary-card {
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    background: var(--bg);
    padding: 1.5rem;
    position: sticky;
    top: calc(var(--header-height) + 1rem);
}
.app-summary-title { font-size: 1.05rem; font-weight: 700; color: var(--text); margin: 0 0 1rem; }
.app-summary-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--border-muted);
}
.app-summary-row:last-of-type { border-bottom: none; }
.app-summary-label { color: var(--text-lifted); font-size: var(--text-md); }
.app-summary-value { color: var(--text); font-weight: 700; font-size: var(--text-md); text-align: right; }
.app-summary-row-warning .app-summary-value { color: var(--warning-accented); }
.app-summary-cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    margin-top: 1.1rem;
}
.app-summary-actions { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.75rem; }
.app-summary-action-btn { justify-content: center; width: 100%; }
.app-summary-action-btn-danger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.45rem 0.95rem;
    border-radius: var(--rounding-md);
    border: 1px solid var(--error);
    color: var(--error);
    font-weight: 700;
    font-size: var(--text-md);
    background: transparent;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard);
}
.app-summary-action-btn-danger:hover { background: var(--error); color: #fff; }
.app-summary-action-btn-danger.disabled { opacity: var(--disabled-opacity); pointer-events: none; }

/* -------------------------------------------------------------------------
   Dark-mode: this block's classes above are already token-based/theme-aware
   by construction (no hardcoded hex outside --gradient-brand/hero, which is
   deliberately theme-invariant per build reference §12), so only genuinely
   dark-specific *overrides* belong here. DataTables' bundled default
   pagination/length/filter widgets remain hidden (we render our own via
   pagination-footer.tpl) so no dark-mode fix is needed for them here.
   ------------------------------------------------------------------------- */
:root[data-theme="dark"] .app-alert-bar-action {
    color: var(--bg-inverted);
}
:root[data-theme="dark"] .app-alert-bar-action:hover {
    color: var(--bg-inverted);
}

/* Hide DataTables' own bundled length/filter/info/pagination controls on
   these two list pages — the custom toolbar + pagination-footer.tpl above
   fully replace them (still driven by the same DataTable instance/API). */
#tableServicesList_wrapper .dataTables_length,
#tableServicesList_wrapper .dataTables_filter,
#tableServicesList_wrapper .dataTables_info,
#tableServicesList_wrapper .dataTables_paginate,
#tableDomainsList_wrapper .dataTables_length,
#tableDomainsList_wrapper .dataTables_filter,
#tableDomainsList_wrapper .dataTables_info,
#tableDomainsList_wrapper .dataTables_paginate {
    display: none !important;
}

/* Same treatment for the Support Tickets list (support agent) — its own
   toolbar/pill-tabs/pagination-footer.tpl fully replace DataTables' bundled
   controls, still driven by the same #tableTicketsList DataTable instance. */
#tableTicketsList_wrapper .dataTables_length,
#tableTicketsList_wrapper .dataTables_filter,
#tableTicketsList_wrapper .dataTables_info,
#tableTicketsList_wrapper .dataTables_paginate {
    display: none !important;
}

/* =========================================================================
   Billing agent, part 2 — account-paymentmethods.tpl,
   account-paymentmethods-manage.tpl, account-paymentmethods-billing-
   contacts.tpl, clientareaaddfunds.tpl, invoice-payment.tpl. Prefixed
   app-pm- / app-funds- (clientareainvoices.tpl/viewinvoice.tpl's app-bill-
   / app-inv- rules are earlier in this file — nothing there was touched).
   ========================================================================= */

/* Generic stock-Bootstrap modal dark-mode fix (`.modal-content` etc. ship
   unthemed, hardcoded-white in theme.css/invoice.css) — needed for this
   page's delete-payment-method confirmation modal and the "Add new
   billing address" modal in account-paymentmethods-manage.tpl. Purely
   additive/corrective (same pattern as the existing .card/.table dark-mode
   fixes earlier in this file), safe to reuse for any other agent's stock
   Bootstrap modal too since it only targets un-namespaced Bootstrap
   classes, not anything page-specific. */
:root[data-theme="dark"] .modal-content {
    background-color: var(--bg) !important;
    color: var(--text);
    border-color: var(--border);
}
:root[data-theme="dark"] .modal-header,
:root[data-theme="dark"] .modal-footer {
    border-color: var(--border-muted) !important;
}
:root[data-theme="dark"] .modal-title { color: var(--text); }
:root[data-theme="dark"] .close { color: var(--text); text-shadow: none; opacity: 0.7; }
:root[data-theme="dark"] .close:hover { opacity: 1; color: var(--text); }

/* -------------------------------------------------------------------------
   account-paymentmethods.tpl — "Saved Cards" list.
   ------------------------------------------------------------------------- */
.app-pm-card { background: var(--bg); border: 1px solid var(--border); border-radius: var(--rounding-lg); padding: 1.5rem; }
.app-pm-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.app-pm-card-title { font-weight: 700; color: var(--text); margin: 0 0 0.25rem; }
.app-pm-card-subtext { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }
.app-pm-card-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }

.app-pm-list { border: 1px solid var(--border-muted); border-radius: var(--rounding-md); overflow: hidden; }
.app-pm-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.1rem;
    border-bottom: 1px solid var(--border-muted);
    flex-wrap: wrap;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-pm-row:last-child { border-bottom: 0; }
.app-pm-row:hover { background: var(--bg-muted); }
.app-pm-row-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--rounding-md);
    background: var(--bg-muted);
    color: var(--text-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    flex-shrink: 0;
}
.app-pm-row-main { flex: 1 1 220px; min-width: 0; }
.app-pm-row-name { font-weight: 700; color: var(--text); display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.app-pm-row-desc { color: var(--text-lifted); font-size: var(--text-md); margin-top: 0.2rem; }
.app-pm-row-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-left: auto; }
.app-pm-btn-delete:hover { background: var(--error-soft); border-color: var(--error); color: var(--error); }

/* -------------------------------------------------------------------------
   account-paymentmethods-manage.tpl — add/edit card or bank account.
   Markup/ids/classes the payment-gateway JS depends on are untouched;
   this only adds spacing/visual chrome around them.
   ------------------------------------------------------------------------- */
.app-pm-manage-card .card-body { padding: 1.75rem; }
.app-pm-manage-title { font-weight: 700; color: var(--text); margin-bottom: 1.5rem; }
/* "Credit or Debit Card" / "Bank Account" type selector — restyle the
   existing iCheck form-check-inline rows into tiles without touching
   their markup. */
.frm-credit-card-input > .form-group.row:first-of-type .form-check-inline {
    display: inline-flex;
    align-items: center;
    padding: 0.6rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    margin-right: 0.6rem;
    margin-bottom: 0.4rem;
    color: var(--text-accented);
    font-weight: 600;
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.frm-credit-card-input > .form-group.row:first-of-type .form-check-inline:hover { background: var(--bg-muted); }
.field-error-msg { color: var(--error); font-size: var(--text-sm); }
.fieldgroup-bankaccount, .fieldgroup-creditcard, .fieldgroup-auxfields { margin-top: 0.5rem; }

.app-pm-contacts { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.5rem; }
.app-pm-contact-tile {
    display: flex !important;
    align-items: flex-start;
    gap: 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    padding: 0.75rem 0.9rem;
    margin: 0 !important;
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.app-pm-contact-tile:hover { background: var(--bg-muted); }
.app-pm-contact-details { display: flex; flex-direction: column; gap: 0.2rem; }
.app-pm-contact-details .name { color: var(--text); }
.app-pm-contact-address { color: var(--text-lifted); font-size: var(--text-sm); }

/* -------------------------------------------------------------------------
   clientareaaddfunds.tpl
   ------------------------------------------------------------------------- */
.app-funds-wrap { display: flex; flex-direction: column; gap: 1.25rem; max-width: 640px; margin: 0 auto; }
.app-funds-limits { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.9rem; }
@media (max-width: 560px) { .app-funds-limits { grid-template-columns: 1fr; } }
.app-funds-limit-tile { background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: var(--rounding-md); padding: 0.9rem 1rem; text-align: center; }
.app-funds-limit-label { font-size: var(--text-sm); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin-bottom: 0.3rem; }
.app-funds-limit-value { font-weight: 700; color: var(--text); font-size: 1.05rem; }
.app-funds-card .form-group label { font-weight: 600; color: var(--text-accented); font-size: var(--text-md); }

/* -------------------------------------------------------------------------
   MarketConnect "Recommended for you" promo carousel (.promo-container)
   Server-injected by WHMCS core ahead of clientareahome.tpl's own content —
   not present in any of our .tpl files, so it can't be reordered via markup.
   js/app-shell.js relocates the DOM node to the end of .primary-content on
   load; this block re-skins WHMCS's stock light-mode-only styling into the
   Vercaa system (dark by default, glowing gradient badge, gradient CTA).
   ------------------------------------------------------------------------- */
.promo-container {
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    overflow: hidden;
    margin-bottom: 1.5rem;
    box-shadow: 0 0 0 1px rgba(94, 194, 105, 0.06), 0 12px 32px -16px rgba(94, 194, 105, 0.35);
    position: relative;
}
.promo-container::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    /* One of the deliberate "purple in some places" touches — a thin 3-color brand bar. */
    background: linear-gradient(90deg, var(--accent-purple), var(--secondary), var(--success));
}
.promo-container .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.1rem 1.4rem;
    border-bottom: 1px solid var(--border-muted);
}
.promo-container .header h3,
.promo-container .header h4 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.promo-container .header h3::before,
.promo-container .header h4::before {
    content: "\2726";
    color: var(--primary);
    font-size: 0.95em;
}
.promo-container .promotions-slider-control { display: flex; gap: 0.4rem; float: none; }
.promo-container .promotions-slider-control a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--text-lifted);
    border: 1px solid var(--border-muted);
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.promo-container .promotions-slider-control a:hover { background: var(--primary-soft); color: var(--primary); }
.promo-container .carousel-item { background: transparent; }

/* .promo-banner is the FULL slide (icon + text + CTA), not just an icon — despite the name.
   .card-body lays out .icon-left (the real icon, a fixed circle) beside .content (the text/CTA). */
.promo-container .promo-banner { background: transparent; }
.promo-container .card-body {
    display: flex;
    align-items: flex-start;
    gap: 1.5rem;
    padding: 1.6rem 1.4rem;
    flex-wrap: wrap;
}
.promo-container .icon-left {
    flex-shrink: 0;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 999px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--white);
    box-shadow: 0 0 0 5px rgba(94, 194, 105, 0.14), 0 8px 24px -10px rgba(94, 194, 105, 0.55);
}
.promo-container .icon-left a { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.promo-container .icon-left img { max-width: 62%; max-height: 62%; object-fit: contain; }
.promo-container .content { flex: 1; min-width: 220px; color: var(--text-lifted); }
.promo-container .content h3 {
    color: var(--text);
    font-weight: 700;
    font-size: 1.15rem;
    margin: 0 0 0.15rem;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.6rem;
}
.promo-container .content h4 {
    color: var(--text-lifted);
    font-weight: 400;
    font-size: 0.95rem;
    margin: 0 0 0.9rem;
}
.promo-container .content h3 small {
    font-size: 0.8rem;
    font-weight: 600;
}
.promo-container .content h3 small a { color: var(--info); text-decoration: none; }
.promo-container .content h3 small a:hover { text-decoration: underline; }
.promo-container .content ul {
    list-style: none;
    margin: 0 0 1.1rem;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.45rem 1.5rem;
}
.promo-container .content ul li { color: var(--text-lifted); font-size: var(--text-sm); display: flex; align-items: center; gap: 0.5rem; }
.promo-container .content ul li i { color: var(--success); flex-shrink: 0; }
.promo-container .content form { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin: 0; }
.promo-container .content .btn,
.promo-container .content .btn.btn-success {
    background: linear-gradient(135deg, var(--primary), var(--primary-lifted)) !important;
    border: none !important;
    color: var(--white) !important;
    font-weight: 700;
    border-radius: var(--rounding-md);
    padding: 0.7rem 1.4rem;
    transition: filter var(--duration-slow) var(--ease-standard), transform var(--duration-fast) var(--ease-out);
    box-shadow: 0 8px 20px -10px rgba(94, 194, 105, 0.7);
}
.promo-container .content .btn:hover,
.promo-container .content .btn.btn-success:hover { filter: brightness(1.08); transform: translateY(-1px); }
@media (max-width: 640px) {
    .promo-container .card-body { flex-direction: column; align-items: flex-start; text-align: left; }
    .promo-container .content ul { grid-template-columns: 1fr; }
}

/* =========================================================================
   QA fixes — 17-issue pass (home/services/domains, billing/support,
   profile/emails/home groups). Each block below is scoped to one finding;
   see the inline comment on each for the finding it addresses.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Fix: "Update" button (and the domain Register / invoice Pay All / ticket
   sidebar Reply buttons it shares a class with) goes bright green in dark
   mode while every other CTA on the page keeps its color across themes.
   theme.css's own light-mode rules already repoint THESE SAME three
   ancestor-scoped contexts (body .sidebar .btn-success, the home-card
   .client-home-cards .btn-success, and body .primary-content
   .btn-success.btn-lg[name=enable]) to a neutral dark pill via
   var(--bg-inverted) instead of leaving them Bootstrap-green — i.e. none of
   these are meant to read as a "success" action in light mode either. The
   existing blanket dark-mode .btn-success rule further up (re-pointing ALL
   .btn-success to var(--success) to fix invisible white-on-light-gray text)
   inadvertently turns these specific neutral buttons green too. Re-assert
   the same neutral treatment for just these three contexts, at matching
   specificity to the generic dark .btn-success rule plus one extra
   ancestor class, so it wins in the cascade without touching that rule. */
:root[data-theme="dark"] .sidebar .btn-success,
:root[data-theme="dark"] .primary-content .client-home-cards .btn-success,
:root[data-theme="dark"] .primary-content .btn-success.btn-lg[name="enable"] {
    background-color: var(--bg-muted) !important;
    border-color: var(--border) !important;
    color: var(--text) !important;
}
:root[data-theme="dark"] .sidebar .btn-success:hover,
:root[data-theme="dark"] .primary-content .client-home-cards .btn-success:hover,
:root[data-theme="dark"] .primary-content .btn-success.btn-lg[name="enable"]:hover {
    background-color: var(--bg-lifted) !important;
    border-color: var(--border-lifted) !important;
    color: var(--text) !important;
}

/* -------------------------------------------------------------------------
   Fix: the "Domain" tab pill above Domain Details (Product Details page)
   keeps a light/white pill background in dark mode. `.app-pill-tab.active`
   sets `background: var(--bg)` (correctly theme-aware) but loses to
   theme.css's `.nav-tabs .nav-link.active` (3 chained classes, background:
   #fff hardcoded) on specificity alone (0,3,0 beats 0,2,0) regardless of
   cascade order, since this tab markup carries BOTH .nav-link and
   .app-pill-tab. Match that same specificity inside the dark scope. */
:root[data-theme="dark"] .nav-tabs .nav-link.app-pill-tab.active {
    background-color: var(--bg) !important;
    color: var(--text) !important;
    border-color: var(--border) var(--border) var(--bg) !important;
}

/* -------------------------------------------------------------------------
   Fix: on Services / Product Details, the sidebar's long upsell-card stack
   runs 1000-1300px taller than the short main content column, leaving a
   large blank gap in the content column once it ends while the sidebar
   keeps scrolling. Pin the sidebar column (already used as a summary/
   actions rail, not primary reading content) in the viewport the same way
   .app-summary-card already does elsewhere in this file, with its own
   internal scroll once it exceeds the viewport — the mismatch in content
   LENGTH no longer reads as a layout defect once the shorter column simply
   ends and the sidebar stays conveniently in view instead of scrolling
   away out of sync with it. Desktop-only (matches the .col-lg-4 breakpoint
   this two-column layout itself only applies at). */
@media (min-width: 992px) {
    .row > .col-lg-4.col-xl-3 {
        position: sticky;
        top: calc(var(--header-height) + 1rem);
        max-height: calc(100vh - var(--header-height) - 2rem);
        overflow-y: auto;
        align-self: flex-start;
    }
}

/* -------------------------------------------------------------------------
   Fix: with 0 domains (or a client-side search/filter reduced to 0 rows),
   the shared DataTables config (includes/tablelist.tpl's sZeroRecords/
   sEmptyTable) injects its own bare "No Records Found" row into the table
   body at the same time as the bespoke illustrated empty-state panel
   already rendered underneath it (clientareadomains.tpl / clientareaproducts.tpl,
   both opted into this pattern via the .app-dtable class) — two empty
   states, back to back. Every .app-dtable table already has its own
   designed empty state, so suppress DataTables' native message row instead
   of hiding/restructuring the table (which tablelist.tpl's shared,
   multi-page init script still needs present in the DOM to initialize
   against). */
.app-dtable tr:has(> td.dataTables_empty) { display: none; }
.app-dtable td.dataTables_empty { display: none; }

/* -------------------------------------------------------------------------
   Fix: the rich-text "Message" editor (bootstrap-markdown's vendored
   .md-editor block in all.css) is entirely hardcoded to light colors
   (#fff/#eee/#f5f5f5/#ddd) with no dark-mode override anywhere, so it sits
   as a solid white box in the middle of an otherwise dark page on Submit
   Ticket and the ticket Reply box (both share the same .markdown-editor
   init in js/whmcs.js, so fixing the shared .md-editor classes covers
   both). Toolbar buttons already inherit the .btn-default dark override
   further up this file; only the editor's own container/textarea/preview
   surfaces need a dark surface here. */
:root[data-theme="dark"] .md-editor {
    border-color: var(--border);
    background: var(--bg);
}
:root[data-theme="dark"] .md-editor .md-footer,
:root[data-theme="dark"] .md-editor > .md-header,
:root[data-theme="dark"] .md-editor > .btn-toolbar {
    background: var(--bg-muted) !important;
}
:root[data-theme="dark"] .md-editor > textarea {
    background: var(--bg) !important;
    color: var(--text);
    border-top-color: var(--border-muted);
    border-bottom-color: var(--border-muted);
}
:root[data-theme="dark"] .md-editor > textarea:focus {
    background: var(--bg) !important;
    color: var(--text);
}
/* theme.min.css's `.md-editor>.md-preview,...{background-color:#fff!important;
   border-top:1px solid #ddd!important;border-bottom:1px solid #ddd!important}`
   beats the plain (non-!important) declarations this block used to have on
   specificity/!important alone, regardless of source order — confirmed live via
   getComputedStyle on the ticket Reply box's "Preview" toggle (viewticket.php,
   not just the excluded open-ticket-submit flow): the panel rendered as a solid
   white box with near-invisible light text. Every property being fought here
   needs its own !important to actually win. */
:root[data-theme="dark"] .md-editor > .md-preview {
    background: var(--bg) !important;
    color: var(--text);
    border-top-color: var(--border-muted) !important;
    border-bottom-color: var(--border-muted) !important;
}
:root[data-theme="dark"] .md-editor .md-controls .md-control { color: var(--text-lifted); }
:root[data-theme="dark"] .md-editor .md-controls .md-control:hover { color: var(--text); }

/* -------------------------------------------------------------------------
   Fix: the classic footer (footer.tpl) doesn't follow the light/dark
   toggle. theme.css hardcodes `footer.footer { background-color:
   var(--bg-inverted); color: #fff; }` unconditionally — --bg-inverted is
   correctly dark in light mode and light in dark mode for the handful of
   components that are DELIBERATELY always-inverted, but the footer isn't
   one of those; it should just track the page like every other panel. The
   existing dark-mode override further up this file already keeps the
   footer on a proper dark surface in dark mode (so it doesn't flip light);
   this adds the missing light-mode side so the footer actually inverts
   WITH the toggle in both directions instead of only in one. */
footer.footer {
    background-color: var(--bg-muted);
    color: var(--text);
}
footer.footer .nav-link { color: var(--text-lifted); }
footer.footer .nav-link:hover { color: var(--text); }
footer.footer .btn-outline-light {
    color: var(--text);
    border-color: var(--border);
}
footer.footer .btn-outline-light:hover {
    background: var(--bg-lifted);
    color: var(--text);
    border-color: var(--border-lifted);
}

/* Fix (same finding): theme.css's `html, body { background-color:
   var(--bg-inverted) }` uses that same deliberately-inverted token for the
   page's own base canvas — backwards for a base canvas (it should just
   match the page, not invert against it). With nothing else painted over
   it, that's what leaves a stray light sliver of raw body background
   visible just below the dark footer in dark mode (and would leave a dark
   sliver below it in light mode). Point the actual canvas at the normal,
   theme-following --bg token instead. */
html, body {
    background-color: var(--bg);
}

/* -------------------------------------------------------------------------
   Fix: the Home dashboard's 4-tile stat strip (Services/Domains/Tickets/
   Invoices, inside the welcome banner) is illegible in light mode. Per the
   build reference, this strip is meant to be "a slightly darker strip
   within the same [always-brand-purple] banner" — but .app-welcome-stats
   is a sibling <div> below .app-gradient-banner, not nested inside it, and
   its own background is JUST a 16%-black tint with no opaque base of its
   own. That tint sits on the PAGE background: dark enough over dark mode's
   near-black page to keep the hardcoded white text/icons legible by
   accident, but only light-gray over light mode's page background, with
   the same hardcoded white text now nearly invisible. Give the strip its
   own always-purple base (matching --gradient-brand, which is deliberately
   never themed either) so it reads the same regardless of page theme. */
.app-welcome-stats {
    background: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), var(--gradient-brand);
}

/* -------------------------------------------------------------------------
   Fix: the "Join our mailing list" toggle (bootstrap-switch, .toggle-
   switch-success — also reused for the SSO "allow" toggle) has almost no
   visual affordance in EITHER theme. all.css only defines color variants
   for bootstrap-switch-{primary,secondary,info,success,warning,danger};
   the OFF state (bootstrapSwitch's default offColor option, "default") and
   the sliding .bootstrap-switch-label thumb have no matching class at all,
   so both render with no background whatsoever — just a 10%-opacity
   border — and inherit whatever's behind them. Token-based colors here
   (not scoped to a dark selector — the vars already resolve correctly per
   theme) give it a real neutral track + visible thumb + green ON fill,
   matching the reference doc's "green ON/OFF toggle switch" pattern. */
.bootstrap-switch {
    border-color: var(--border);
    background: var(--bg-accented);
}
.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success {
    background-color: var(--success) !important;
    border-color: var(--success) !important;
    color: #fff !important;
}
.bootstrap-switch .bootstrap-switch-handle-off {
    background-color: var(--bg-accented) !important;
    border-color: var(--border) !important;
    color: var(--text-lifted) !important;
}
.bootstrap-switch .bootstrap-switch-label {
    background-color: var(--bg) !important;
    border-color: var(--border) !important;
}

/* -------------------------------------------------------------------------
   Fix: clientareasecurity.tpl's dead-end "Head to Login and Security under
   your profile" guidance was plain, unclickable gray text. Now rendered as
   a real link (see clientareasecurity.tpl) — give empty-state subtext
   links real link styling so it reads as clickable. */
.app-empty-state-subtext a { color: var(--primary); font-weight: 600; text-decoration: underline; }
.app-empty-state-subtext a:hover { color: var(--primary-lifted); }

/* -------------------------------------------------------------------------
   Fix (supporting clientareadetails.tpl / viewinvoice.tpl QA fixes): small
   helper-text style for the new Payment Method field note and other
   inline field-level notes, matching the existing .acct-card-subtext
   look/token usage. */
.acct-field-note { color: var(--text-lifted); font-size: var(--text-sm); margin: 0.4rem 0 0; }
.acct-field-note a { color: var(--primary); font-weight: 600; }
.acct-field-note a:hover { color: var(--primary-lifted); }

/* -------------------------------------------------------------------------
   Fix (supporting viewinvoice.tpl QA fixes): the un-configured "Pay To"
   placeholder message, and the added "Contact support for payment
   instructions" link shown alongside it when Pay To isn't configured.
   ------------------------------------------------------------------------- */
.app-inv-payto-missing { color: var(--text-lifted); font-style: italic; }
.app-inv-payto-contact-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.6rem;
    color: var(--primary);
    font-weight: 600;
    font-size: var(--text-md);
}
.app-inv-payto-contact-link:hover { color: var(--primary-lifted); text-decoration: underline; }

/* -------------------------------------------------------------------------
   Fix (supporting viewinvoice.tpl QA fix): sizing for the new self-
   contained theme-toggle button on the standalone invoice page — reuses
   .app-icon-btn for color/hover, just needs to not shrink in the topbar's
   flex row alongside the back-link and brand. */
.app-inv-theme-toggle { flex-shrink: 0; }

/* =========================================================================
   DEFERRED.md §2d sweep — Account/security/misc pages:
   user-switch-account(-forced).tpl, user-invite-accept.tpl,
   user-verify-email.tpl, account-user-management.tpl,
   account-user-permissions.tpl, password-reset-*.tpl, clientregister.tpl,
   clientareacancelrequest.tpl, clientareaquotes.tpl, viewquote.tpl,
   subscription-manage.tpl, clientareaproductusagebilling.tpl,
   usagebillingpricing.tpl. New component classes only, prefixed
   acct-switch- / acct-perm- / acct-um- / acct-cancel- / acct-usage- /
   app-quote- to avoid colliding with any other concurrently-running
   agent's own classes in this shared file — nothing above this block was
   touched. All colors/spacing/motion reuse the existing token system.
   ========================================================================= */

/* -------------------------------------------------------------------------
   user-switch-account.tpl / user-switch-account-forced.tpl — replaces the
   stock plain "<a>" list with tappable tiles, matching the existing
   .app-pm-contact-tile radio-tile visual language (account-paymentmethods
   pass) without duplicating its CSS wholesale, since these are buttons
   (click-to-switch) not radio inputs.
   ------------------------------------------------------------------------- */
.acct-switch-list { display: flex; flex-direction: column; gap: 0.6rem; margin: 1.25rem 0; text-align: left; }
.acct-switch-tile {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.9rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    color: var(--text);
    font-weight: 600;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.acct-switch-tile:hover { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); text-decoration: none; }
.acct-switch-tile.disabled { opacity: var(--disabled-opacity); pointer-events: none; }
.acct-switch-tile-name { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.acct-switch-tile i.acct-switch-chevron { color: var(--text-muted); }
.acct-switch-empty-icon {
    width: 56px; height: 56px; border-radius: 999px;
    background: var(--primary-soft); color: var(--primary);
    display: flex; align-items: center; justify-content: center;
    font-size: 1.4rem; margin: 0 auto 1rem;
}

/* -------------------------------------------------------------------------
   Shared permissions checklist — used both by account-user-management.tpl's
   "Invite New User" form and account-user-permissions.tpl's per-user edit
   form (same $permissions source array in both controllers).
   ------------------------------------------------------------------------- */
.acct-perm-list { display: flex; flex-direction: column; gap: 0; margin: 1rem 0; border: 1px solid var(--border-muted); border-radius: var(--rounding-md); overflow: hidden; }
/* Bug fix: .w-hidden (WHMCS core utility, display:none) must win over this component's own
   display:flex when both classes are present (account-user-management.tpl's "Choose Permissions"
   toggle) — same specificity, so without this the later-declared .acct-perm-list rule above wins
   and the panel never actually hides before the radio is switched to "choose". */
.acct-perm-list.w-hidden { display: none; }
.acct-perm-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--border-muted);
    cursor: pointer;
}
.acct-perm-item:last-child { border-bottom: 0; }
.acct-perm-item:hover { background: var(--bg-muted); }
.acct-perm-item input[type="checkbox"] { margin-top: 0.2rem; flex-shrink: 0; }
.acct-perm-item input[type="radio"] { margin-top: 0.2rem; flex-shrink: 0; }
.acct-perm-body { flex: 1 1 auto; min-width: 0; }
.acct-perm-title { font-weight: 600; color: var(--text); }
.acct-perm-desc { color: var(--text-lifted); font-size: var(--text-sm); margin-top: 0.1rem; }
.acct-perm-panel { padding: 0.25rem 0.25rem 0.5rem; }

/* -------------------------------------------------------------------------
   account-user-management.tpl — users table + pending invites, reusing the
   generic .app-bill-table row/hover/responsive-stack shape (billing agent
   block above) since it's the same "list of records with actions" shape.
   ------------------------------------------------------------------------- */
.acct-um-avatar {
    width: 34px; height: 34px; border-radius: 999px;
    background: var(--primary-soft); color: var(--primary-accented);
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: var(--text-md); flex-shrink: 0;
}
.acct-um-user-cell { display: flex; align-items: center; gap: 0.75rem; }
.acct-um-user-email { font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.acct-um-user-meta { color: var(--text-lifted); font-size: var(--text-sm); margin-top: 0.15rem; }
.acct-um-shield-on { color: var(--success); }
.acct-um-shield-off { color: var(--text-muted); }
.acct-um-invites-heading { display: flex; align-items: center; gap: 0.5rem; padding: 0.85rem 0.75rem 0.5rem; color: var(--text-lifted); font-weight: 700; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.04em; }
.acct-um-col-actions { text-align: right; white-space: nowrap; }
@media (max-width: 700px) { .acct-um-col-actions { text-align: left; margin-top: 0.5rem; } }

/* -------------------------------------------------------------------------
   clientareacancelrequest.tpl — cancellation form card.
   ------------------------------------------------------------------------- */
.acct-cancel-domain-note { margin-top: 1rem; }
.acct-cancel-type-row { max-width: 360px; }

/* -------------------------------------------------------------------------
   clientareaquotes.tpl — reuses .app-bill-card / .app-bill-table shape
   (billing agent block) directly since quotes are structurally the same
   "list of billing documents" pattern as invoices; only the quote-number
   cell gets its own thin wrapper class to avoid implying it's an invoice.
   ------------------------------------------------------------------------- */
.app-quote-num { font-weight: 700; color: var(--text); letter-spacing: 0.01em; }
.app-quote-sub { font-size: var(--text-sm); color: var(--text-muted); margin-top: 0.15rem; }
.app-quote-sub i { margin-right: 0.3rem; }

/* -------------------------------------------------------------------------
   subscription-manage.tpl, clientareaproductusagebilling.tpl,
   usagebillingpricing.tpl — small utility card polish.
   ------------------------------------------------------------------------- */
.acct-usage-table-wrap { overflow-x: auto; }
.acct-usage-table { width: 100%; }
.acct-usage-table th {
    text-align: left; font-size: var(--text-sm); font-weight: 700; color: var(--text-lifted);
    text-transform: uppercase; letter-spacing: 0.04em; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border);
}
.acct-usage-table td { padding: 0.85rem 0.75rem; color: var(--text-accented); vertical-align: middle; border-bottom: 1px solid var(--border-muted); }
.acct-usage-table tr:last-child td { border-bottom: 0; }
.acct-usage-metric-name { font-weight: 600; color: var(--text); }
.acct-usage-price-link { margin-top: 0.3rem; }

/* -------------------------------------------------------------------------
   clientareaproductdetails.tpl — Market Connect vendor "Manage" panel
   (DEFERRED.md §2a). {$moduleclientarea} is raw HTML echoed straight out of
   WHMCS's ionCube-encoded modules/servers/marketconnect module — we can't
   touch its markup, only brand the surrounding chrome (.app-pd-section-card /
   .app-pd-section-title, added in the .tpl) and re-skin the generic Bootstrap
   classes the module happens to emit (.row/.col-*, .btn.btn-default,
   a.autoLinked, bare <img>/<form>). Confirmed live against the 2 already-
   purchased vendors:
     - RapidSSL (SSL Certificates, product id 2): plain label/value pairs —
       <div class="row py-2"><div class="col-md-4">Label</div>
       <div class="col-md-8">Value <a class="btn btn-default autoLinked">…</a></div></div>
     - 360 Monitoring "Lite" (product id 13): a vendor logo <img> followed by
       two SSO/upgrade <form>s, each ending in a <button class="btn btn-default">
       (one also carries .btn-service-sso + a hidden .loading spinner span).
   Selectors are written off these generic Bootstrap hooks (not vendor-specific
   classes) so the same rules should also cover the 5 not-yet-purchased Market
   Connect vendors (NordVPN, SpamExperts, Open-Xchange, CodeGuard, SiteLock)
   once purchased, since WHMCS's own module-authored panels share this pattern
   — re-verify visually once each is bought, this is not a guarantee.
   ------------------------------------------------------------------------- */
.module-client-area { padding: 0; }

/* RapidSSL-style label/value rows */
.module-client-area .row.py-2 {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin: 0;
    padding: 0.65rem 0;
    border-bottom: 1px solid var(--border-muted);
}
.module-client-area .row.py-2:last-child { border-bottom: none; }
.module-client-area .row.py-2 > [class*="col-"] {
    padding: 0;
    max-width: none;
    flex: initial;
    width: auto;
}
.module-client-area .row.py-2 > [class*="col-"]:first-child {
    color: var(--text-lifted);
    font-weight: 600;
    font-size: var(--text-md);
}
.module-client-area .row.py-2 > [class*="col-"]:last-child {
    color: var(--text);
    font-size: var(--text-md);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* SSO panel — vendor logo + stacked <form> action buttons (360 Monitoring et al.) */
.module-client-area > img { display: block; max-width: 200px; height: auto; margin: 0 0 1rem; }
.module-client-area > br { display: none; }
.module-client-area form {
    display: inline-flex;
    align-items: center;
    margin: 0 0.6rem 0.6rem 0;
}
.module-client-area .login-feedback { flex-basis: 100%; margin-bottom: 0.75rem; }

/* Stock Bootstrap .btn-default emitted by the module — re-skin to the same
   outline-brand pattern as .app-row-action-btn used elsewhere on this page. */
.module-client-area .btn,
.module-client-area a.autoLinked {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.95rem;
    border-radius: var(--rounding-md);
    border: 1px solid var(--primary);
    background: transparent;
    color: var(--primary);
    font-weight: 700;
    font-size: var(--text-md);
    text-decoration: none;
    box-shadow: none;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard);
}
.module-client-area .btn:hover,
.module-client-area .btn:focus,
.module-client-area a.autoLinked:hover {
    background: var(--primary);
    color: #fff;
}

/* 360 Monitoring's vendor logo bakes near-black wordmark text into the PNG
   itself — verified live, it goes almost unreadable on our dark-mode
   background. Give any bare vendor <img> in this panel a white chip behind
   it in dark mode so the wordmark stays legible regardless of what colors
   the vendor's own asset uses; harmless no-op in light mode since the page
   background is already white there. */
:root[data-theme="dark"] .module-client-area > img {
    background: #fff;
    padding: 0.75rem 1rem;
    border-radius: var(--rounding-md);
}

/* =========================================================================
   Cart / checkout / order-form flow (DEFERRED.md §2c) — branding sweep for
   the classic Smarty pages of the order-form funnel: products, configure-
   product, configure-domains, addons, checkout, ordersummary (AJAX
   fragment), complete. These live at templates/orderforms/vercaa-2026-cart/
   and inherit any file they don't override from templates/orderforms/
   standard_cart/ (theme.yaml parent) — so several shared partials that
   render on every one of these pages (sidebar-categories.tpl, sidebar-
   categories-collapsed.tpl, sidebar-categories-selector.tpl, recommend-
   ations-modal.tpl) are still the STOCK, un-forked files. Rather than fork
   those too (out of scope — see DEFERRED.md's exact file list), their
   stock class names are themed here via plain CSS, same as everything
   below: no markup was touched in those particular partials, only in the
   7 files actually listed in the punch list (plus viewcart.tpl, which is
   Vue/Shadow-DOM-mounted and out of scope for hand-edits, see its own
   comment in domainregister.tpl / HANDOFF.md §4.2).

   These pages render inside the normal vercaa-2026 client-area shell
   (header.tpl/footer.tpl — Template=vercaa-2026 in tblconfiguration), so
   all the tokens below are already in scope; no --vl-* Shadow DOM mapping
   is involved here (that trick is only for the Vue-mounted pages).
   ========================================================================= */

/* -------------------------------------------------------------------------
   Global Bootstrap component rebrand additions — extends the "Global
   component rebrand" block near the top of this file (.btn-primary etc.)
   with the variants the cart pages lean on heavily that theme.css ships
   as hardcoded Bootstrap defaults (#28a745 green, #f8d7da pink, ...)
   rather than token-driven. Site-wide selectors, not cart-scoped, so any
   other still-stock page using these classes benefits too. */
.btn-success {
    background: var(--success);
    border-color: var(--success);
    color: #fff;
}
.btn-success:hover,
.btn-success:focus,
.btn-success.focus {
    background: var(--success-lifted);
    border-color: var(--success-lifted);
    color: #fff;
    box-shadow: none;
}
.btn-success:not(:disabled):not(.disabled):active {
    background: var(--success-accented);
    border-color: var(--success-accented);
}
.btn-info {
    background: var(--info);
    border-color: var(--info);
    color: #fff;
}
.btn-info:hover, .btn-info:focus { background: var(--info-lifted); border-color: var(--info-lifted); color: #fff; }
.btn-warning {
    background: var(--warning);
    border-color: var(--warning);
    color: #fff;
}
.btn-warning:hover, .btn-warning:focus { background: var(--warning-lifted); border-color: var(--warning-lifted); color: #fff; }
.btn-default {
    background: var(--bg);
    border-color: var(--border);
    color: var(--text);
}
.btn-default:hover, .btn-default:focus { background: var(--bg-muted); border-color: var(--border-accented); color: var(--text); }
.alert-success { background: var(--success-soft); border-color: var(--success); color: var(--success-accented); }
.alert-danger { background: var(--error-soft); border-color: var(--error); color: var(--error-accented); }
.alert-warning { background: var(--warning-soft); border-color: var(--warning); color: var(--warning-accented); }
.alert-info { background: var(--info-soft); border-color: var(--info); color: var(--info-accented); }
.alert-danger .alert-link, .alert-warning .alert-link, .alert-info .alert-link, .alert-success .alert-link { color: inherit; text-decoration: underline; font-weight: 700; }

/* Small color utility classes used by configuredomains.tpl in place of the
   stock template's hardcoded style="color:#009900" / "color:#cc0000". */
.app-text-success { color: var(--success-accented); font-weight: 600; }
.app-text-error { color: var(--error-accented); font-weight: 600; }

/* -------------------------------------------------------------------------
   Page structure. The stock body #order-standard_cart / .cart-sidebar /
   .cart-body float layout is left alone (it works); only spacing/visual
   treatment of what sits inside it changes. */
body #order-standard_cart { color: var(--text); }
body #order-standard_cart a { color: var(--primary); }
body #order-standard_cart .cart-body .app-gradient-banner { margin-bottom: 1.75rem; }

/* Category sidebar card (sidebar-categories.tpl, stock/un-forked — see note
   above) + its mobile "collapsed" counterpart (sidebar-categories-
   collapsed.tpl / sidebar-categories-selector.tpl, also stock). */
body #order-standard_cart .cart-sidebar .panel,
body #order-standard_cart .sidebar-collapsed .panel {
    background: var(--bg);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    box-shadow: none;
}
body #order-standard_cart .cart-sidebar .panel-heading,
body #order-standard_cart .sidebar-collapsed .panel-heading {
    background: var(--bg-muted);
    border-bottom: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
    padding: 0.9rem 1.1rem;
}
body #order-standard_cart .cart-sidebar .panel-title,
body #order-standard_cart .sidebar-collapsed .panel-title {
    color: var(--text);
    font-weight: 700;
    font-size: var(--text-md);
}
body #order-standard_cart .cart-sidebar .list-group-item {
    background: transparent;
    border-bottom: 1px solid var(--border-muted);
    color: var(--text-lifted);
    font-weight: 500;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
body #order-standard_cart .cart-sidebar .list-group-item:hover {
    background: var(--bg-muted);
    color: var(--text);
}
body #order-standard_cart .cart-sidebar .list-group-item.active {
    background: var(--primary-soft);
    color: var(--primary-accented);
    font-weight: 700;
    border-color: var(--border-muted);
}
body #order-standard_cart .cart-sidebar .panel-footer,
body #order-standard_cart .sidebar-collapsed .panel-footer {
    background: var(--bg-muted);
    border-top: 1px solid var(--border-muted);
    border-radius: 0 0 var(--rounding-lg) var(--rounding-lg);
}
body #order-standard_cart .sidebar-collapsed select.form-control,
body #order-standard_cart .sidebar-collapsed .custom-select {
    background: var(--bg);
    border-color: var(--border);
    color: var(--text);
}

/* -------------------------------------------------------------------------
   ".sub-heading" section dividers — used heavily on configureproduct /
   configuredomains / checkout ("Personal Information", "Payment Details",
   etc). Stock is a plain gray <hr>-style line with a centered label
   floated on top; keep that shape (it reads fine) but move it onto
   tokens, and give the label an explicit background matching the page so
   it still visually "cuts" the line in dark mode (stock relies on the
   page always being white, which dark mode breaks). */
body #order-standard_cart .sub-heading,
body #order-standard_cart .sub-heading-borderless {
    border-top-color: var(--border);
}
body #order-standard_cart .sub-heading span,
body #order-standard_cart .sub-heading-borderless span {
    background: var(--bg);
    color: var(--primary-accented);
    font-weight: 700;
    letter-spacing: 0.01em;
}

/* -------------------------------------------------------------------------
   Product / addon pricing cards (products.tpl, addons.tpl). */
body #order-standard_cart .products .product {
    background: var(--bg);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    box-shadow: none;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
body #order-standard_cart .products .product:hover {
    border-color: var(--primary);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
}
body #order-standard_cart .products .product header {
    background: var(--bg-muted);
    border-bottom: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
}
body #order-standard_cart .products .product header span { color: var(--text); font-weight: 700; }
body #order-standard_cart .products .product header .qty { color: var(--text-muted); font-size: var(--text-sm); }
body #order-standard_cart .products .product .product-desc { color: var(--text-lifted); }
body #order-standard_cart .products .product .product-desc ul { list-style: none; padding-left: 0; margin: 0.75rem 0; }
body #order-standard_cart .products .product .product-desc li {
    padding: 0.35rem 0;
    border-bottom: 1px dashed var(--border-muted);
    color: var(--text-accented);
}
body #order-standard_cart .products .product .product-desc li:last-child { border-bottom: 0; }
body #order-standard_cart .products .product .product-desc .feature-value { color: var(--primary-accented); font-weight: 700; margin-right: 0.35rem; }
body #order-standard_cart .products .product footer { border-top: 1px solid var(--border-muted); }
body #order-standard_cart .products .product .product-pricing .price { color: var(--text); font-weight: 700; }
body #order-standard_cart .products .product .btn-order-now { border-radius: var(--rounding-md); font-weight: 700; }

/* -------------------------------------------------------------------------
   Selectable "addon" panels — used for domain add-ons (DNS management, ID
   protection, email forwarding on configuredomains.tpl), config-option
   add-ons on configureproduct.tpl, and the reusable panel-add "+" footer. */
body #order-standard_cart .panel-addon {
    background: var(--bg);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-md);
    overflow: hidden;
    transition: border-color var(--duration-fast) var(--ease-out);
}
body #order-standard_cart .panel-addon:hover { border-color: var(--primary); }
body #order-standard_cart .panel-addon.panel-addon-selected { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
body #order-standard_cart .panel-addon .panel-body {
    background: transparent;
    color: var(--text);
}
body #order-standard_cart .panel-addon .panel-body label { color: var(--text); }
body #order-standard_cart .panel-addon .panel-price {
    background: var(--bg-muted);
    color: var(--text-lifted);
    border-top: 1px solid var(--border-muted);
}
body #order-standard_cart .panel-addon .panel-add {
    background: var(--success);
    color: #fff;
    font-weight: 600;
    transition: background-color var(--duration-fast) var(--ease-out);
}
body #order-standard_cart .panel-addon.panel-addon-selected .panel-add { background: var(--success-accented); }

/* -------------------------------------------------------------------------
   Form fields — icon-prepended inputs (.field / .field-icon), select
   controls, config-option radios/checkboxes. .form-control itself already
   has a dark-mode fix elsewhere in this file; these extend the same
   treatment to the cart's own ".field" alias class and the icon glyphs. */
body #order-standard_cart .field,
body #order-standard_cart .form-control {
    background: var(--bg);
    border-color: var(--border);
    color: var(--text);
}
body #order-standard_cart .field:focus,
body #order-standard_cart .form-control:focus {
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}
body #order-standard_cart .field-icon i { color: var(--text-muted); }
body #order-standard_cart .field-help-text { color: var(--text-muted); font-size: var(--text-sm); }
body #order-standard_cart .form-control-static-inline { color: var(--text-lifted); }

/* -------------------------------------------------------------------------
   Order summary sidebar (checkout.tpl + configureproduct.tpl's live-
   updating panel) — reuses the same visual language as .app-summary-card
   elsewhere in the client area (sticky card, bordered rows, bold total)
   instead of the stock dark-gray #666 box. ordersummary.tpl (the AJAX
   fragment swapped into #producttotal / #orderSummary on billing-cycle /
   config-option change) renders its markup straight into these same
   selectors, so it's covered by the same rules — no separate CSS needed
   for that file. */
body #order-standard_cart .secondary-cart-sidebar { position: relative; }
/* Sticky is applied to #orderSummary (the ID), not .order-summary (the class), on purpose: on
   checkout.tpl the same element carries both id="orderSummary" and class="order-summary" so this
   is a no-op difference there, but on configureproduct.tpl they're TWO DIFFERENT elements —
   id="orderSummary" is the outer wrapper around BOTH .order-summary (the totals card) AND the
   sibling "Continue" button. Making only .order-summary sticky left the button in normal flow
   below it, so once scrolled the sticky card visually overlapped/hid the button underneath it —
   found live. Stickying the shared outer wrapper keeps card+button pinned together as one unit. */
body #order-standard_cart #orderSummary {
    position: sticky;
    top: calc(var(--header-height) + 1rem);
}
body #order-standard_cart .order-summary {
    background: var(--bg);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
}
body #order-standard_cart .order-summary h2 {
    background: var(--bg-muted);
    color: var(--text);
    border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
    font-weight: 700;
    border-bottom: 1px solid var(--border-muted);
}
body #order-standard_cart .order-summary .loader { color: var(--primary); }
body #order-standard_cart .summary-container {
    background: transparent;
    color: var(--text-lifted);
}
body #order-standard_cart .summary-container .product-name { color: var(--text); font-weight: 700; }
body #order-standard_cart .summary-container .product-group { color: var(--text-muted); font-size: var(--text-sm); display: block; margin-bottom: 0.5rem; }
/* Vercaa (2026-08-14, pass 2): the old `.summary-container .clearfix` / `.summary-totals`
   dashed-border rules that used to live here are gone on purpose, not lost — ordersummary.tpl
   (the real AJAX fragment swapped into this element) no longer renders those stock float-row
   classes at all; it now renders `.app-order-summary-row`/`-subrows`/`-divider` instead (see
   that file's own comment), which templates/orderforms/vercaa-2026-cart/css/custom.css already
   styles for the shared order-summary partial. Removed rather than left dead per this project's
   own "no dead/duplicate code" standard. */
body #order-standard_cart .total-due-today {
    background: var(--primary-soft);
    color: var(--primary-accented);
    border-radius: var(--rounding-md);
    padding: 0.85rem 1rem;
    margin: 0.75rem 0;
}
body #order-standard_cart .total-due-today .amt { color: var(--primary-accented); font-weight: 800; }
body #order-standard_cart .express-checkout-buttons .separator { color: var(--text-muted); }
body #order-standard_cart .btn-checkout { border-radius: var(--rounding-md); font-weight: 700; }

/* -------------------------------------------------------------------------
   Checkout-specific: "already registered" toggle row, existing-account
   selector cards, payment method radios, security notice. */
body #order-standard_cart .already-registered p { color: var(--text-lifted); }
body #order-standard_cart .account-select-container { border-right-color: var(--border-muted); }
body #order-standard_cart .account {
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-md);
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
body #order-standard_cart .account.active {
    background: var(--primary-soft);
    border-color: var(--primary);
}
body #order-standard_cart .account .address strong { color: var(--text); }
body #order-standard_cart .account .address .small { color: var(--text-muted); }
body #order-standard_cart label.radio-inline,
body #order-standard_cart label.checkbox-inline { color: var(--text); }
body #order-standard_cart #paymentGatewaysContainer .radio-inline {
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    padding: 0.5rem 1rem;
    margin: 0 0.4rem 0.6rem;
    display: inline-flex;
    align-items: center;
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
body #order-standard_cart #paymentGatewaysContainer .radio-inline:has(input:checked) {
    border-color: var(--primary);
    background: var(--primary-soft);
    color: var(--primary-accented);
    font-weight: 600;
}
body #order-standard_cart #totalDueToday.alert-success {
    background: var(--success-soft);
    border-color: var(--success);
    color: var(--success-accented);
    font-weight: 700;
}
body #order-standard_cart .checkout-security-msg {
    background: var(--info-soft);
    border-color: var(--info);
    color: var(--info-accented);
    border-radius: var(--rounding-md);
}
body #order-standard_cart .checkout-security-msg strong { color: var(--info-accented); }
body #order-standard_cart .apply-credit-container {
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-md);
    padding: 1rem;
    margin-bottom: 1rem;
}
body #order-standard_cart .marketing-email-optin {
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-md);
    padding: 1rem;
}
body #order-standard_cart .existing-cc-grid .paymethod-card,
body #order-standard_cart #existingCardsContainer {
    color: var(--text);
}

/* -------------------------------------------------------------------------
   complete.tpl order-confirmation banner. */
body #order-standard_cart .order-confirmation {
    background: var(--info-soft);
    border-color: var(--info);
    color: var(--info-accented);
    font-weight: 600;
}
body #order-standard_cart .order-confirmation span { color: var(--info-accented); font-weight: 800; }

/* -------------------------------------------------------------------------
   Dark-mode: everything above is token-based/theme-aware by construction,
   so only genuinely dark-specific overrides belong here — the plan-picker
   slider widget (sidebar-categories-selector.tpl's mobile <select>, and
   any noUiSlider-style track/handle it renders as on the desktop panel)
   ships with light-gray-on-white assumptions that need an explicit dark
   pass, and .product/.panel-addon box-shadows read wrong on a dark bg. */
:root[data-theme="dark"] body #order-standard_cart .products .product:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
}
:root[data-theme="dark"] body #order-standard_cart input[type="text"].form-control,
:root[data-theme="dark"] body #order-standard_cart input[type="email"].form-control,
:root[data-theme="dark"] body #order-standard_cart input[type="tel"].form-control,
:root[data-theme="dark"] body #order-standard_cart input[type="password"].form-control,
:root[data-theme="dark"] body #order-standard_cart select.form-control {
    background: var(--bg-muted);
}

/* Plan-picker slider (rendered by JS onto the mobile "select" element in
   sidebar-categories-selector.tpl — a stock un-forked partial, see the big
   comment at the top of this section) — theme the generic noUiSlider /
   ion.rangeSlider class names defensively since the exact library wasn't
   confirmed from the minified bundle; harmless no-op selectors if unused. */
body #order-standard_cart .noUi-connect,
body #order-standard_cart .irs-bar,
body #order-standard_cart .irs-single {
    background: var(--primary) !important;
}
body #order-standard_cart .noUi-handle,
body #order-standard_cart .irs-slider {
    border-color: var(--primary) !important;
    background: var(--bg) !important;
}
body #order-standard_cart .noUi-target,
body #order-standard_cart .irs-line {
    background: var(--border) !important;
}

/* -------------------------------------------------------------------------
   Specificity fix: WHMCS's OWN compiled theme.min.css (Nexus theme, not
   authored by us — see this file's top-of-file comment re: "theme.css
   already reference many of these same custom properties directly")
   ships an extensive pre-built body #order-standard_cart styling pass already
   (confirmed live via getComputedStyle + matching CSSOM rules: 119 rules
   under `body #order-standard_cart ...`), mostly already token-driven.
   BUT it uses a `body` element-selector prefix that gives it higher
   specificity than the plain `body #order-standard_cart ...` rules earlier in
   this section, so several of ITS rules win over ours despite ours
   loading later in the cascade — verified live (product cards + sub-
   heading dividers were staying stock-white in dark mode despite the
   rules above). A handful of theme.min.css's own rules also still
   hardcode #fff/#f8f9fa instead of a token. Re-declare just those, at
   matching specificity, to finish the token conversion — do NOT hand-
   patch the compiled theme.min.css itself per this file's own convention. */
body #order-standard_cart .products .product { background: var(--bg); }
body #order-standard_cart .cart-body .products .product header { background: var(--bg-muted); }
body #order-standard_cart .sub-heading span,
body #order-standard_cart .sub-heading-borderless span { background: var(--bg); color: var(--primary-accented); }
body #order-standard_cart .panel .panel-heading { background: var(--bg-muted); }
body #order-standard_cart .panel .panel-footer { background: var(--bg-muted); }

/* The generic `:root[data-theme="dark"] .btn-default` rule (~line 1182) outranks
   the un-prefixed `.module-client-area .btn` rule above on specificity (3 classes
   vs 2), so in dark mode it was winning and repainting our outline-brand button
   back to the neutral grey default — verified live (RapidSSL's "Configure Now").
   Re-declare with matching specificity, scoped to this panel only. */
:root[data-theme="dark"] .module-client-area .btn.btn-default,
:root[data-theme="dark"] .module-client-area a.autoLinked {
    background: transparent;
    border-color: var(--primary);
    color: var(--primary);
}
:root[data-theme="dark"] .module-client-area .btn.btn-default:hover,
:root[data-theme="dark"] .module-client-area .btn.btn-default:focus,
:root[data-theme="dark"] .module-client-area a.autoLinked:hover {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}

/* =========================================================================
   Support / content pages branding sweep (DEFERRED.md Task #10 §2e, 2026-08-12).
   Covers: knowledgebase*.tpl, contact.tpl, announcements.tpl/viewannouncement.tpl,
   serverstatus.tpl, downloads.tpl/downloadscat.tpl/downloaddenied.tpl,
   markdown-guide.tpl, ticketfeedback.tpl (already done in an earlier pass — no
   CSS needed here), viewbillingnote.tpl, viewemail.tpl, forwardpage.tpl (no CSS
   needed — already token-driven via .bg-color-blue{background:var(--info)}),
   access-denied.tpl, banned.tpl, includes/network-issues-notifications.tpl.
   New classes only, prefixed app- (generic) or targeting existing stock
   nexus classes (kb-category / kb-article-item / kb-search / .announcement)
   that had no prior styling — nothing above this block was touched.
   ========================================================================= */

/* ---- Generic centered icon-in-circle badge for notice/confirmation pages
        (access-denied, banned, downloaddenied) — same shape/size as the
        ticket-flow's .tix-confirm-icon, generalized with color modifiers so
        it isn't semantically tied to tickets. ---- */
.app-icon-circle {
    width: 64px;
    height: 64px;
    border-radius: 999px;
    background: var(--success-soft);
    color: var(--success-accented);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    margin: 0 auto 1.25rem;
}
.app-icon-circle-warning { background: var(--warning-soft); color: var(--warning-accented); }
.app-icon-circle-danger { background: var(--error-soft); color: var(--error-accented); }
.app-icon-circle-info { background: var(--info-soft); color: var(--info-accented); }

/* ---- Knowledge Base / Downloads shared markup (both reuse the exact same
        stock nexus classes: .kb-category, .kb-article-item, .kb-search — a
        single styling pass here brands knowledgebase.tpl, knowledgebasecat.tpl,
        knowledgebasearticle.tpl (related-articles list), downloads.tpl and
        downloadscat.tpl all at once, with zero extra markup changes needed
        for the icon/card treatment itself. ---- */
.kb-category {
    border-radius: var(--rounding-lg);
    border: 1px solid var(--border);
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.kb-category:hover {
    border-color: var(--border-lifted);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
    transform: translateY(-1px);
}
.kb-category .card-body { padding: 1.25rem 1.5rem; }
.kb-category .fa-folder {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary-accented);
    margin-right: 0.6rem;
    font-size: 0.95rem;
    vertical-align: middle;
}
.kb-article-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.kb-article-item .fa-file-alt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 999px;
    background: var(--bg-accented);
    color: var(--text-lifted) !important;
    font-size: 0.85rem;
    flex-shrink: 0;
}
.kb-article-item small { display: block; width: 100%; color: var(--text-lifted); margin-top: 0.15rem; }
.kb-search .form-control {
    border-radius: var(--rounding-lg) 0 0 var(--rounding-lg);
    border-color: var(--border);
}
.kb-search .form-control:focus { border-color: var(--primary); box-shadow: none; }
.kb-search .input-group-append .btn {
    border-radius: 0 var(--rounding-lg) var(--rounding-lg) 0;
}

/* ---- Announcements list (announcements.tpl reuses the stock .announcement
        class per-item; no markup change needed for this polish pass). ---- */
.announcements .announcement {
    padding: 1.25rem 0;
    border-bottom: 1px solid var(--border-muted);
}
.announcements .announcement:first-child { padding-top: 0.25rem; }
.announcements .announcement:last-child { border-bottom: 0; }
.announcements .announcement h1 { font-size: 1.25rem; margin-bottom: 0.4rem; }
.announcements .announcement h1 a { color: var(--text); }
.announcements .announcement h1 a:hover { color: var(--primary); }

/* ---- markdown-guide.tpl — <pre> blocks otherwise inherit the browser's
        default black-on-white styling, which breaks badly in dark mode
        (a real bug caught during this pass, fixed here). ---- */
.markdown-guide-content h4 { margin-top: 1.75rem; color: var(--text); font-weight: 700; }
.markdown-guide-content h4:first-child { margin-top: 0; }
.markdown-guide-content pre {
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
    padding: 1rem 1.25rem;
    color: var(--text);
    font-size: var(--text-sm);
    overflow-x: auto;
    white-space: pre-wrap;
}

/* ---- viewbillingnote.tpl — a standalone document (own <html>, not wrapped
        by header.tpl) that now carries the same theme-detection snippet as
        header.tpl so it can honor a saved dark-mode preference; .card already
        goes dark via the existing `:root[data-theme="dark"] .card` rule, but
        the surrounding <body>/page background does not without this explicit
        pairing (scoped to .app-print-doc only — does not touch the global
        `body` selector used site-wide). ---- */
:root[data-theme="dark"] body.app-print-doc {
    background: var(--bg);
    color: var(--text);
}

/* ---- includes/network-issues-notifications.tpl — replaces the raw Bootstrap
        .alert-warning/.alert-info bar (which never picked up the token system)
        with a slim brand-consistent banner, full width above the breadcrumb. ---- */
.app-network-alert {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.75rem 1.25rem;
    font-size: var(--text-md);
    font-weight: 500;
    text-align: center;
}
.app-network-alert-warning { background: var(--warning-soft); color: var(--warning-accented); }
.app-network-alert-info { background: var(--info-soft); color: var(--info-accented); }
.app-network-alert-link {
    font-weight: 700;
    text-decoration: underline;
    color: inherit;
}
.app-network-alert-link:hover { color: inherit; opacity: 0.85; }

/* ============================================================================
   Domain sub-pages branding sweep (DEFERRED.md §2b) — clientareadomaindetails,
   clientareadomaindns, clientareadomainaddons, clientareadomaincontactinfo,
   clientareadomainemailforwarding, clientareadomaingetepp,
   clientareadomainregisterns, bulkdomainmanagement, domain-pricing.
   Reuses existing tokens/components (.app-pd-hero, .app-pd-section-card,
   .app-detail-row, .app-alert-bar, .acct-field-grid, .acct-form-actions,
   status-badge.tpl, gradient-banner.tpl) — the additions below are only the
   handful of genuinely new shapes these pages introduce.
   ============================================================================ */

/* "What would you like to do today?" quick-links list (domaindetails Overview tab) — replaces a
   bare <ul> of plain text links with a tappable row list matching the app-summary-card language. */
.app-quick-links-title { margin-top: 1.75rem; }
.app-quick-links { display: flex; flex-direction: column; gap: 0.5rem; }
.app-quick-link-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-md);
    color: var(--text);
    background: var(--bg-muted);
    transition: border-color 0.15s ease, background 0.15s ease;
}
.app-quick-link-item:hover { border-color: var(--primary); background: var(--primary-soft); color: var(--text); }
.app-quick-link-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 999px;
    background: var(--bg-accented);
    color: var(--text-lifted);
    flex-shrink: 0;
    font-size: 0.85rem;
}
.app-quick-link-item:hover .app-quick-link-icon { background: var(--primary); color: #fff; }
.app-quick-link-label { flex: 1 1 auto; font-weight: 600; font-size: var(--text-md); }
.app-quick-link-arrow { color: var(--text-muted); font-size: 0.75rem; flex-shrink: 0; }

/* Centered single-status action panels (Auto Renew / Registrar Lock tabs) — a status-badge row
   plus one large call-to-action button, reused for both enable/disable directions. */
.app-status-panel { text-align: center; }
.app-status-panel-desc { color: var(--text-lifted); max-width: 44rem; margin: 0 auto 1.25rem; }
.app-status-panel-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    margin: 0 auto 1.5rem;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
}
.app-status-panel-btn { min-width: 220px; justify-content: center; }

/* Segmented radio-pill choice (nameserver default/custom, WHOIS existing/custom contact) — visual
   twin of .app-pill-tabs but built on real radio inputs instead of anchor tabs. */
.app-radio-pill-group { display: inline-flex; background: var(--bg-muted); border-radius: 999px; padding: 4px; gap: 2px; margin-bottom: 1.25rem; flex-wrap: wrap; }
.app-radio-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 1rem;
    border-radius: 999px;
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--text-lifted);
    cursor: pointer;
    margin: 0;
}
.app-radio-pill input[type="radio"] { accent-color: var(--primary); margin: 0; }
.app-radio-pill:has(input:checked) { background: var(--bg); color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); }

/* Domain addon feature-card grid (Addons tab in domaindetails + the standalone buy/disable page's
   large centered icon). Inline SVG icons per project convention (no new FontAwesome icon-font use). */
.app-addon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1rem; margin-top: 1.25rem; }
.app-addon-card {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1.25rem;
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    background: var(--bg-muted);
}
.app-addon-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary-accented);
    flex-shrink: 0;
}
.app-addon-card-icon svg { width: 1.4rem; height: 1.4rem; }
.app-addon-card-icon-lg { width: 4rem; height: 4rem; margin: 0 auto 1rem; }
.app-addon-card-icon-lg svg { width: 2rem; height: 2rem; }
.app-addon-card-body { flex: 1 1 auto; }
.app-addon-card-title { font-size: 1rem; font-weight: 700; color: var(--text); margin: 0 0 0.35rem; }
.app-addon-card-desc { font-size: var(--text-md); color: var(--text-lifted); margin: 0; }
.app-addon-card-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0; }

/* DNS Management / Email Forwarding record tables — a light wrapper over .app-table-card so the
   editable inline <input>/<select> rows still read as a clean data grid. */
.app-dns-table-wrap { padding: 0; }
.app-dns-table { margin: 0; }
.app-dns-table thead th {
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-lifted);
    font-weight: 700;
    border-bottom: 1px solid var(--border);
    padding: 0.85rem 1rem;
}
.app-dns-table tbody td { padding: 0.6rem 1rem; vertical-align: middle; border-top: 1px solid var(--border-muted); }
.app-dns-table tbody tr:first-child td { border-top: none; }
.app-dns-table-new-row td { background: var(--bg-muted); }
.app-dns-table-at { text-align: center; color: var(--text-lifted); font-weight: 600; white-space: nowrap; }
.app-dns-table-note { text-align: right; color: var(--text-lifted); margin: 0.75rem 0 1.25rem; }

/* Form action row shared by every domain sub-page save/cancel bar (registerns, dns, email
   forwarding, contact info, bulk management) — .acct-form-actions already exists (left-aligned by
   default); these pages read better centered under a single card. */
.acct-form-actions-center { justify-content: center; }

/* Contact-info pill-tab content pane — the pill-tab nav itself reuses .app-pill-tabs/.app-pd-subnav
   (established by clientareaproductdetails.tpl); this just gives the panel below it the same
   section-card padding used elsewhere on these pages. */
.app-contact-tab-content { margin-top: 0; }

/* Bulk Domain Management — "the following domains are affected" chip list, replacing the stock
   <ul class="list-group"> with a wrapping row of pill chips (build reference §12 iconography). */
.app-bulk-domain-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.5rem; }
.app-bulk-domain-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.85rem;
    border-radius: 999px;
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    color: var(--text);
    font-size: var(--text-md);
    font-weight: 600;
}
.app-bulk-domain-chip i { color: var(--text-lifted); font-size: 0.8rem; }

/* Domain Pricing (public page) — category filter pills reuse .app-pill-btn's shape but need an
   explicit "active" state since this page drives its own filter state via jQuery instead of a
   server-rendered `.active` class. */
.tld-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.tld-filters a.app-pill-btn { text-decoration: none; }
.tld-filters a.app-pill-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.tld-filters a.app-pill-btn.active .app-pill-tab-count { background: rgba(255, 255, 255, 0.25); color: #fff; }
.app-pricing-currency-form { margin-left: auto; }
.app-pricing-currency-form .currency-selector { min-width: 220px; }
.app-pricing-tld-cell { font-weight: 700; color: var(--text); }
#tableDomainPricing.app-dtable { margin: 0; }
@media (max-width: 640px) {
    .app-pricing-currency-form { margin-left: 0; width: 100%; }
}

/* -------------------------------------------------------------------------
   clientareaquotes.tpl (§2d misc sweep) — same treatment as the
   #tableServicesList/#tableDomainsList/#tableTicketsList rule above: hide
   DataTables' own bundled length/filter/info/pagination controls since
   this page's own .app-bill-table-wrap + pagination-footer.tpl (driven by
   the same #tableQuotesList DataTable instance via includes/tablelist.tpl)
   fully replace them. */
#tableQuotesList_wrapper .dataTables_length,
#tableQuotesList_wrapper .dataTables_filter,
#tableQuotesList_wrapper .dataTables_info,
#tableQuotesList_wrapper .dataTables_paginate {
    display: none !important;
}

/* Vercaa QA fix (2026-08-12): header.tpl's #main-body > .row (line ~227) only gets a
   .container parent when WHMCS core's $skipMainBodyContainer is falsy — several core
   pages (confirmed live: the 404 "page not found" error page; likely others that render
   their own full-width container, e.g. access-denied/banned) set it true, leaving the
   Bootstrap .row's -15px negative margins with no compensating padding and causing a
   constant 15px horizontal page overflow (document.documentElement.scrollWidth >
   window.innerWidth at every width tested). Fixed at the #main-body level so it's caught
   regardless of which specific pages skip the container, rather than chasing each one. */
#main-body {
    overflow-x: hidden;
}

/* =========================================================================
   ?embed=1 chrome-hiding mode (history: originally built for an early
   iframe-of-the-Vue-widget version of the "Domain Name Search" modal —
   long since replaced by a hand-built results renderer that calls WHMCS's
   domain APIs directly and never uses an iframe at all, see css/
   domain-search-modal.css. Currently used by the support-ticket bottom sheet
   only, includes/ticket-sheet.tpl iframing submitticket.php). Kept here
   (not moved) since it's genuinely still needed by that Smarty-only feature.
   ========================================================================= */

/* ---- ?embed=1 chrome-hiding mode — applies INSIDE the iframe, on cart.php
   itself, via the body.app-cart-embed class header.tpl adds when the query
   param is present. No template logic is skipped, only chrome is hidden, so
   the exact same page still renders normally when visited directly without
   the flag. */
body.app-cart-embed .app-header,
body.app-cart-embed .app-sidebar,
body.app-cart-embed .app-sidebar-backdrop,
body.app-cart-embed .master-breadcrumb,
body.app-cart-embed #footer,
body.app-cart-embed #order-standard_cart .cart-sidebar {
    display: none !important;
}
/* 2026-08-13 (domains page build): this rule predates the guest header/footer unification pass
   and only ever hid the LOGGED-IN shell's `.app-header`. header.tpl's `{if $loggedin}` guest
   branch renders `<header id="header" class="header guest-nav-header">` instead — a different
   class, but the SAME `id="header"` (confirmed: header.tpl's logged-in branch is also `<header
   id="header" class="header app-header">`) — so targeting `#header` here hides both variants with
   one rule, closing a real gap: an anonymous visitor hitting this iframe (e.g. market/domains.php's
   embedded search, the first guest-context caller of ?embed=1) previously got a full duplicate
   guest nav rendered inside the iframe, stacked under the host page's own real header — the exact
   "doubled chrome" defect this project's own verification process explicitly watches for. The
   guest nav's sibling off-canvas drawer + backdrop (`.guest-nav-drawer-root`, rendered as a sibling
   of <header>, not nested inside it — see shared-guest-chrome.php's own docblock on why) needs the
   same treatment; it's invisible until opened but still occupies markup that has no reason to exist
   twice on one visible page. */
body.app-cart-embed #header,
body.app-cart-embed .guest-nav-drawer-root {
    display: none !important;
}
body.app-cart-embed .app-shell-content { margin-left: 0 !important; }
body.app-cart-embed #main-body { padding-top: 0.5rem; }
body.app-cart-embed #order-standard_cart .cart-body { width: 100% !important; float: none !important; }
body.app-cart-embed .container { max-width: 100%; }
/* The yellow "Dev License" banner (WHMCS core-injected, present on every page per HANDOFF.md) is
   deliberately left visible here rather than special-cased away on just this one embedded page. */

/* 2026-08-12: extended for submitticket.php reuse (the new ticket bottom sheet, see
   includes/ticket-sheet.tpl) — this rule was written generically enough (keys off body.app-cart-
   embed, not anything cart-specific) to already apply to ANY page loaded with ?embed=1, but the
   two rules below were still missing because cart.php pages never render this markup in the
   first place (they set $inShoppingCart, which suppresses header.tpl's own generic contextual-
   sidebar column entirely — see header.tpl's "$primarySidebar->hasChildren()" condition), so
   nothing existed to hide there. submitticket.php DOES render a real "Your Recent Tickets" /
   "Support" contextual sidebar (confirmed live), which needs the same treatment, and its own
   "<- Cancel" back-link (supportticketsubmit-stepone.tpl / -steptwo.tpl) is redundant with — and
   would conflict with — the sheet's own close (X) affordance once presented as a popup. */
body.app-cart-embed .col-lg-4.col-xl-3,
body.app-cart-embed .sidebar-secondary,
body.app-cart-embed .tix-open-back {
    display: none !important;
}
body.app-cart-embed .primary-content {
    flex: 0 0 100% !important;
    max-width: 100% !important;
}

/* Domain Name Search modal shell (backdrop, dialog, drag handle, close button, title, search
   bar, loading state, --domain-ink token) — MOVED (2026-08-13 domain-search unification pass,
   not duplicated) to css/domain-search-modal.css, which market/*.php pages can also link (this
   file, custom.css, never loads there — see that file's own header comment for the full "why"
   and exactly what moved). The old .app-domain-modal-iframe rules that used to sit here were
   dropped entirely in the same move: dead CSS from this modal's original iframe-of-the-Vue-widget
   era, confirmed unreferenced by any markup/JS for a long time before this pass. */

/* ==========================================================================
   Home dashboard — Unified Products & Services card (Hosting / Domains /
   Market Connect), clientareahome.tpl section 3. Redesign of the flat
   single-list "Products & Services" summary: groups all three product
   families into their own clearly labeled sections inside one card, with a
   pill-tab filter on top. Row markup/classes (.app-service-row, .app-row-
   icon, .app-cell-category/.app-cell-product, .app-service-row-status/
   -action) are unchanged from the existing Products & Services row language
   (see the #appServicesCard rules above) — everything below is the new
   section/group scaffolding + entrance motion around those same rows, not a
   fourth visual style. Uses js/app-shell.js's initUnifiedServiceGroups()
   for the Hosting/Market Connect row relocation + pill-tab filtering (see
   that function's own comments for why relocation is necessary at all). */

.app-card-subtext {
    font-size: var(--text-md);
    color: var(--text-lifted);
    margin: 0.15rem 0 0;
    font-weight: 500;
}

.app-unified-tabs-row {
    padding: 0 1.5rem 1rem;
    border-bottom: 1px solid var(--border-muted);
}
#appServicesCard .card-header + .app-unified-tabs-row { padding-top: 0.25rem; }

.app-unified-groups { display: flex; flex-direction: column; }

.app-unified-group { border-top: 1px solid var(--border-muted); }
.app-unified-group:first-child { border-top: 0; }
.app-unified-group[hidden] { display: none; }

.app-unified-group-header {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 1rem 1.5rem 0.6rem;
}
.app-unified-group-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--rounding-md);
    font-size: 0.8rem;
    flex: none;
}
.app-unified-group-icon-hosting { background: var(--primary-soft); color: var(--primary-accented); }
.app-unified-group-icon-domain { background: var(--info-soft); color: var(--info-accented); }
/* Was --accent-purple-soft/-accented — the Foundation retheme pass (2026-08-13) aliased that whole
   family to var(--primary...) sitewide (see custom.css's :root token block), which means this chip
   silently became IDENTICAL to .app-unified-group-icon-hosting right above it: two of the three
   category chips in this exact grouped card reading as the exact same purple, defeating the "quick
   glance tells you which family" point this per-category tinting exists for at all (that report
   flagged this specific pairing as worth a visual pass). --notice (indigo) is already this page's
   third established category hue (Support Tickets, below), reused here as-is rather than inventing
   a fourth — gives Hosting/Domains/Market Connect three genuinely distinct colors again. */
.app-unified-group-icon-marketconnect { background: var(--notice-soft); color: var(--notice-accented); }
.app-unified-group-icon svg { width: 16px; height: 16px; }
.app-unified-group-label {
    font-weight: 700;
    font-size: var(--text-sm);
    color: var(--text-lifted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.app-unified-group-count {
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-lifted);
    background: var(--bg-muted);
    border-radius: 999px;
    min-width: 22px;
    text-align: center;
    padding: 0 7px;
    line-height: 1.5;
}

/* Group empty-states read one step more compact than the card-level empty state so three of
   them stacked (an account with e.g. hosting only) doesn't feel padding-heavy. */
.app-unified-group .app-empty-state-compact { padding: 1.25rem 1.5rem 1.75rem; }

/* Per-category icon tint on the actual rows too (not just the section header), so a quick
   glance at a single row is enough to tell which family it belongs to even out of context
   (e.g. the "+N more — view all" overflow link still reads correctly, and the filtered single-
   category view doesn't lose the color cue the grouped view provided). */
.app-unified-groups .app-service-row[data-category="hosting"] .app-row-icon,
.app-unified-group[data-group="hosting"] .app-row-icon {
    background: var(--primary-soft);
    color: var(--primary-accented);
}
.app-unified-groups .app-service-row[data-category="domain"] .app-row-icon,
.app-unified-group[data-group="domain"] .app-row-icon {
    background: var(--info-soft);
    color: var(--info-accented);
}
.app-unified-groups .app-service-row[data-category="marketconnect"] .app-row-icon,
.app-unified-group[data-group="marketconnect"] .app-row-icon {
    /* --notice, matching .app-unified-group-icon-marketconnect's own 2026-08-13 fix above (same
       reasoning: --accent-purple is now aliased to --primary sitewide, which made these rows'
       icons identical to Hosting's). */
    background: var(--notice-soft);
    color: var(--notice-accented);
}

/* This group's own EMPTY state (rendered via includes/empty-state.tpl when $appMarketConnectCount
   is 0) auto-infers the sitewide tone="marketconnect" -> .app-empty-state-icon-marketconnect,
   which is DELIBERATELY left at the sitewide --accent-purple/--primary color everywhere else on
   the site (see that class's own definition/comment, ~line 6954 below — a broad, cross-page system
   out of this page's scope to change). Only override it here, nested specifically inside THIS
   group, so the empty-state icon matches the notice-indigo header/row icons immediately around it
   in the exact same card section instead of clashing with them — every other page's marketconnect
   empty state is completely unaffected by this rule. */
.app-unified-group[data-group="marketconnect"] .app-empty-state-icon-marketconnect {
    background: var(--notice-soft);
    color: var(--notice-accented);
}

/* Real hover treatment (not just a color swap): a subtle lift + soft shadow, per this
   project's established hover convention (filter/shadow over flat background swaps). Scoped to
   the unified card specifically so the plain flat list used elsewhere that still reuses
   .app-service-row (e.g. a future/alternate context) is unaffected. */
.app-unified-groups .app-service-row {
    transition: background-color var(--duration-fast) var(--ease-out),
        transform var(--duration-fast) var(--ease-out),
        box-shadow var(--duration-fast) var(--ease-out);
}
.app-unified-groups .app-service-row:hover {
    transform: translateY(-1px);
    box-shadow: 0 3px 10px rgba(17, 24, 39, 0.08);
    position: relative;
    z-index: 1;
}
:root[data-theme="dark"] .app-unified-groups .app-service-row:hover {
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
}

/* Entrance: brief fade+rise stagger applied by initUnifiedServiceGroups() to every row once
   it's in its final section (relocated Hosting/Market Connect rows and server-rendered Domains
   rows alike), reusing the project's own timing tokens rather than one-off values. Respects
   reduced-motion.

   Fill mode is deliberately `backwards`, NOT `both`/`forwards`: a `forwards` fill would keep the
   animation's own `transform` value applied to the element forever after it finishes, which — per
   the CSS cascade's animation-always-wins-over-normal-declarations rule — permanently shadows the
   plain `.app-unified-groups .app-service-row:hover { transform: translateY(-1px); ... }` rule
   below, silently killing the hover lift on every row (confirmed live: getComputedStyle still
   reported the identity matrix while :hover was active). `backwards` still holds the FROM state
   during the staggered delay (preventing a flash of fully-visible content before each row's turn),
   but releases the `transform` property back to the normal cascade once the entrance finishes, so
   :hover can apply normally afterwards — the end visual state (opacity 1, no translate) is
   identical either way, only what happens to the property post-animation differs. */
@keyframes appUnifiedRowIn {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
}
.app-unified-row-anim {
    animation: appUnifiedRowIn var(--duration-slow) var(--ease-standard) backwards;
    animation-delay: calc(var(--stagger-index, 0) * 45ms);
}
@media (prefers-reduced-motion: reduce) {
    .app-unified-row-anim { animation: none; }
}

@media (max-width: 575.98px) {
    .app-unified-tabs-row { padding: 0 1.25rem 0.85rem; }
    .app-unified-group-header { padding: 0.85rem 1.25rem 0.5rem; }
}

/* Domain Name Search modal — hand-built results renderer (.app-domain-row and friends) — MOVED
   (2026-08-13 domain-search unification pass, not duplicated) to css/domain-search-modal.css
   alongside the modal shell it paints into. See that file's own header comment. */

/* =========================================================================
   Cart/checkout + support-content sitewide polish pass (DEFERRED.md Task #12
   successor to §2f, 2026-08-12). Most of §2c (cart/checkout) and §2e
   (support/content) were already brought onto the token system in an
   earlier pass this session (verified: every existing transition in this
   file already uses var(--duration-*)/var(--ease-*), and the global
   `a/button/input/select/textarea/[tabindex]:focus-visible` + `:active`
   ring block near the top already covers every native interactive element
   here for free). The two genuine gaps found on live/code audit: ---- */

/* 1) Table-overflow: ticketfeedback.tpl's 12-column satisfaction-rating
   table (0-10 + worst/best) had no .table-responsive wrapper anywhere in
   the stock nexus markup — every other table page in this template
   (viewinvoice/viewbillingnote/viewquote/serverstatus) already uses that
   wrapper, this one was simply missed. Fixed with a matching wrapper div
   in ticketfeedback.tpl itself (markup-only change, no CSS needed for the
   scroll behavior — .table-responsive is Bootstrap's own utility). */

/* 2) Native checkbox/radio accent-color: nothing in this file ever set
   accent-color on a plain (non-toggle-switch, non-.app-radio-pill) native
   checkbox/radio, so every one of them — the cart's "existing account"
   selector, config-option radios/checkboxes (configureproduct.tpl optiontype
   2/3), domain addon checkboxes (DNS management/ID protection/email
   forwarding on configuredomains.tpl), the checkout TOS checkbox, AND (found
   during the support-pages sweep) the ticketfeedback.tpl rating radios +
   viewquote.tpl's "agree to TOS" checkbox (both class="form-check-input") —
   rendered in the browser's default system accent color instead of the
   brand green, especially glaring in dark mode. `.tix-filter-check input`
   and `.app-radio-pill input[type="radio"]` already set this correctly
   elsewhere in this file; extending the same treatment here rather than
   duplicating per-component. */
body #order-standard_cart input[type="checkbox"],
body #order-standard_cart input[type="radio"],
.form-check-input[type="checkbox"],
.form-check-input[type="radio"] {
    accent-color: var(--primary);
}

/* Selectable addon/config-option panels (panel-addon) already get a border+
   shadow change on hover/selected (~line 3787), but the actual focusable
   element is the checkbox/radio inside — tabbing to it with a keyboard gave
   no panel-level feedback, only the generic global ring on the tiny native
   input itself. :has() is already an established pattern in this file
   (.app-radio-pill:has(input:checked), #paymentGatewaysContainer
   .radio-inline:has(input:checked)) so this reuses the same technique to
   extend the same primary-color ring to the whole card. */
body #order-standard_cart .panel-addon:has(input:focus-visible) {
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

/* =========================================================================
   Sitewide polish pass (2026-08-12, DEFERRED §2f successor / Task #12) —
   core shell + home + product/domain pages. Most of this file already
   follows the --ease-*/--duration-* vocabulary established earlier this
   session very consistently; the fixes below are the genuine gaps found on
   audit: a couple of hover states with NO transition at all (instant snap),
   one component using the wrong token pair for its interaction type (see
   note), and one hardcoded non-token duration. Every selector below already
   exists elsewhere in this file — these are targeted, later-cascade
   corrections appended per the append-only convention, not edits to the
   original declarations (safer under concurrent editing this session).
   ========================================================================= */

/* .app-icon-btn (topbar help/notif-bell/theme-toggle icon buttons) was using
   --duration-slow/--ease-standard ("button hover fills" pair, per the Motion
   tokens comment ~line 166) even though its own hover is a plain background
   TINT, not a solid-fill button — the same interaction as its immediate
   topbar sibling .app-pill-btn (Support PIN) and .app-sidebar-mobile-toggle,
   both of which correctly use --duration-fast/--ease-out ("hover
   backgrounds / nav items"). Corrected so all three topbar icon-style
   controls feel identical, not one visibly slower than its neighbors. */
.app-icon-btn {
    /* 2026-08-13: transform appended (not a new rule) so the hover/press lift added to
       .app-icon-btn:hover/:active above actually animates instead of snapping — this is the
       declaration that wins in cascade (later source order, identical specificity to the
       original ~line 534 rule). */
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}

/* .app-notif-item (notification dropdown rows) had a :hover background
   change with no transition property anywhere in its rule — an instant snap
   next to .app-dropdown-item/.app-avatar-menu rows in the very same
   dropdown, which do fade. */
.app-notif-item {
    transition: background-color var(--duration-fast) var(--ease-out);
}

/* .app-gradient-banner-btn-outline (secondary CTA on gradient hero banners —
   welcome banner + product/domain hero) had no transition; its filled
   sibling .app-gradient-banner-btn right next to it already fades. */
.app-gradient-banner-btn-outline {
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}

/* Home dashboard — domain name inside a Products & Services row (e.g.
   "sarayoussef-demo.com") had a color-only hover with no transition. */
.app-service-row-meta-domain {
    transition: color var(--duration-fast) var(--ease-out);
}

/* Products/Domains list DataTable — the row-hover transition token was
   declared on <tr> (table.app-dtable > tbody > tr), but the property that
   actually changes on hover is the <td> background (tr:hover > td), which
   never inherits a transition from its parent — so this was silently dead
   code and every row hover snapped instantly. Declaring it on the cell
   itself, matching the same fast/ease-out pattern used by every other
   hover-tinted row in this file. */
table.app-dtable > tbody > tr > td {
    transition: background-color var(--duration-fast) var(--ease-out);
}

/* Products/Domains list + product-detail cells — domain-name links inside
   table rows had a color-only hover with no transition. */
a.app-cell-product.app-cell-domain-name,
.app-cell-domain-link {
    transition: color var(--duration-fast) var(--ease-out);
}

/* Shell-level network-issue notification bar link (includes/network-issues-
   notifications.tpl, rendered just under the header on every page) — color/
   opacity hover with no transition. */
.app-network-alert-link {
    transition: color var(--duration-fast) var(--ease-out), opacity var(--duration-fast) var(--ease-out);
}

/* Footer nav links — Bootstrap's stock .nav-link ships with no transition of
   its own (confirmed against the compiled theme.min.css), so this color
   hover was snapping while every other footer control (.btn-outline-light,
   which extends Bootstrap's .btn and inherits ITS transition) already fades. */
footer.footer .nav-link {
    transition: color var(--duration-fast) var(--ease-out);
}

/* Domain sub-pages "What would you like to do today?" quick-links list
   (clientareadomaindetails.tpl Overview tab) — the row itself was hardcoded
   to a non-token "0.15s ease" instead of the project's tokens, and its
   circular icon changed background+color on hover with no transition at
   all. Corrected both to the standard fast/ease-out hover-background pair. */
.app-quick-link-item {
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.app-quick-link-icon {
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}

/* Vercaa QA fix (2026-08-12, cart/checkout polish pass): configuredomains.tpl's
   "[No Hosting! Click to Add]" warning link (class="app-text-error") was
   rendering in the brand primary green instead of the intended error red —
   confirmed live. Root cause: `body #order-standard_cart a { color:
   var(--primary); }` (~line 3674) carries an ID in its selector, which
   outranks the plain `.app-text-error` class rule (~line 3667) on
   specificity regardless of source order, so every domain missing hosting
   showed its "needs attention" link in the same color as ordinary links —
   easy to miss the one thing on the page that actually needs a click.
   Re-declared at matching specificity so the semantic error color wins. */
body #order-standard_cart a.app-text-error { color: var(--error-accented); }
body #order-standard_cart a.app-text-error:hover { color: var(--error); }

/* Domain Name Search modal — Checkout CTA bar + row-entrance motion + add-to-basket "is-added"
   opacity fix — MOVED (2026-08-13 domain-search unification pass, not duplicated) to
   css/domain-search-modal.css. See that file's own header comment. */

/* =========================================================================
   FOUNDATIONAL TYPOGRAPHY + ELEVATION PASS (2026-08-12)

   Direct client feedback after seeing the live site: "the design in general
   of the client area looks old... it looks like the whmcs default design
   just a colors applied to it." Everything up to this point in the file was
   real, careful, page-by-page work — but the client's complaint is a
   HOLISTIC one, so this section makes token-level changes that touch every
   page at once: a real heading/body font pairing (previously the whole site
   ran on one font with zero display-type contrast — a big part of why it
   read as "default + colors") and a real elevation/shadow scale (previously
   cards were flat Bootstrap boxes with a hardcoded 1px rgba(0,0,0,.125)
   border and zero shadow — the single most recognizable "stock Bootstrap"
   tell there is). Both new token families (--font-heading/--font-body,
   --elevation-1..4) are declared in the :root token block near the top of
   this file (search "Typography —" and "Elevation —"); this section is
   where they actually get APPLIED to real selectors, appended here per this
   file's own append-only convention rather than hunting down and editing
   every one of the ~30+ scattered card/heading rule blocks individually
   (safe because CSS's cascade means a later rule at equal specificity wins
   for the specific properties it sets, without disturbing anything else
   those earlier rules already declared).
   ========================================================================= */

/* -------------------------------------------------------------------------
   A) Typography — Poppins for headings/display text, Open Sans for body.
   ------------------------------------------------------------------------- */
body { font-family: var(--font-body); }

h1, h2, h3, h4, h5, h6,
.card-title, .panel-title, .panel-heading,
.app-quicklink-title, .app-quick-links-title,
.acct-card-heading, .acct-card-title, .acct-perm-title, .acct-um-invites-heading,
.tix-filter-menu-title, .tix-thread-attachments-title,
.app-inv-title, .app-inv-side-title,
.app-section-heading, .app-affiliate-signup-title, .app-page-title,
.app-pd-hero-heading, .app-alert-bar-title, .app-pd-section-title,
.app-summary-title, .modal-title, .app-modal-title, .app-modal-subtitle,
.app-pm-card-title, .app-pm-manage-title,
.sub-heading, .sub-heading-borderless,
.app-addon-card-title, .app-gradient-banner-heading,
.app-logo-text,
/* Large numeric "display" values read as headings too (Home welcome stats,
   ticket-summary stat cards, the stat-tile row) — same treatment, per the
   "Modern Professional" pairing's own guidance that display/emphasis text,
   not just literal <h*> tags, belongs on the heading face. */
.app-welcome-stat-value, .tix-stat-value, .app-stat-tile-value,
.app-inv-pay-amount {
    font-family: var(--font-heading);
}

/* Bootstrap's own default heading weight (700 on h1-h6) already suits
   Poppins fine, but a couple of the classes above were set at 700 against
   Open Sans specifically because Open Sans's own 700 cut is what read as
   "confident" for it — Poppins' 600 (SemiBold) is the geometric-font
   equivalent read (700/Bold in a geometric face like this starts to look
   heavy/display-poster at small sizes). Only re-pointing the ones actually
   built with an explicit 700 weight small enough for this to matter
   (section/card titles, not the big hero/page-title sizes where 700 still
   reads correctly on Poppins too). */
.card-title, .panel-title, .app-summary-title, .app-pd-section-title,
.acct-card-title, .app-pm-card-title, .app-inv-side-title {
    font-weight: 600;
}

/* -------------------------------------------------------------------------
   B) Elevation — real soft-shadow depth on shared card/panel surfaces,
   replacing the flat/hard-border Bootstrap default. Resting shadow is
   applied broadly (purely additive — none of these had a box-shadow
   before, so there's no earlier declaration to fight); hover-lift is
   scoped more conservatively to genuinely tile/unit-like cards (addon
   cards, saved payment-method cards) rather than every static content
   panel, since a whole-panel lift on hover reads as "clickable" and a
   large data-table-in-a-card panel lifting under an unrelated table-row
   hover would feel like a bug, not polish — narrower scope here on
   purpose, verified live across Home/Products/Domains/Invoices/Tickets/
   Knowledgebase/Cart/Account before landing.
   ------------------------------------------------------------------------- */
.card,
.app-summary-card,
.app-table-card,
.app-bill-card,
.app-pd-section-card,
.app-pm-card,
.app-addon-card,
.tix-side-card,
.acct-perm-panel {
    box-shadow: var(--elevation-1);
    transition: box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
/* Softer, less "hard cardboard box" border to go with the new shadow
   (Bootstrap's own .card default is a hardcoded rgba(0,0,0,.125) — not
   token-driven, and visibly harsher than the shadow now doing most of the
   depth work). Left every OTHER card class alone here since they already
   declare their own on-brand var(--border)/var(--border-muted) border. */
.card { border-color: var(--border-muted); }

.app-addon-card:hover,
.app-pm-card:hover {
    box-shadow: var(--elevation-2);
    transform: translateY(-2px);
}

/* -------------------------------------------------------------------------
   C) Border-radius consistency — .card's own radius (var(--rounding-md),
   8px, set by WHMCS's compiled theme.min.css) bumped to match the rest of
   this file's already-established card family (.app-bill-card/.app-pm-card/
   .app-addon-card/.app-pd-section-card all already use var(--rounding-lg),
   12px) so every card on the site now shares one consistent radius instead
   of the generic Bootstrap .card sitting at a visibly smaller radius than
   the purpose-built ones next to it.

   Selector deliberately repeats `.primary-content .card` (not plain `.card`)
   — confirmed live via getComputedStyle that a plain `.card { border-radius:
   ... }` here was silently LOSING the cascade to theme.min.css's own
   `.primary-content .card, .primary-content .mc-promo-login, ... {
   border-radius: var(--rounding-md) }` rule, because that selector has
   higher specificity (two class-level selectors, 0-2-0) than a bare `.card`
   (0-1-0) — CSS specificity beats source order regardless of which
   stylesheet loads later. Matching the same `.primary-content .card`
   specificity here restores this file's normal "later in custom.css wins"
   convention. */
.primary-content .card { border-radius: var(--rounding-lg); }

/* -------------------------------------------------------------------------
   D) Remaining stock-Bootstrap leftovers with zero token-driven styling —
   .btn-success/.btn-info/.btn-warning/.btn-primary and the badge-/alert-
   color variants were already covered by earlier passes (see ~line 240 and
   ~3652 onward); .btn-secondary/.btn-danger/.btn-light/.btn-dark and the
   -secondary/-light/-dark badge/alert variants were not.
   ------------------------------------------------------------------------- */
.btn-secondary {
    background: var(--neutral-lifted);
    border-color: var(--neutral-lifted);
    color: #fff;
}
.btn-secondary:hover, .btn-secondary:focus, .btn-secondary.focus {
    background: var(--neutral-accented);
    border-color: var(--neutral-accented);
    color: #fff;
    filter: brightness(0.97);
    box-shadow: none;
}
.btn-danger {
    background: var(--error);
    border-color: var(--error);
    color: #fff;
}
.btn-danger:hover, .btn-danger:focus, .btn-danger.focus {
    background: var(--error-lifted);
    border-color: var(--error-lifted);
    color: #fff;
}
.btn-light {
    background: var(--bg-muted);
    border-color: var(--border);
    color: var(--text);
}
.btn-light:hover, .btn-light:focus { background: var(--bg-lifted); border-color: var(--border-lifted); }
.btn-dark {
    background: var(--bg-inverted);
    border-color: var(--bg-inverted);
    color: var(--text-inverted);
}
.btn-dark:hover, .btn-dark:focus { filter: brightness(1.15); }

.badge-secondary { background-color: var(--neutral-lifted); color: #fff; }
.badge-light { background-color: var(--bg-muted); color: var(--text-accented); }
.badge-dark { background-color: var(--bg-inverted); color: var(--text-inverted); }

.alert-secondary { background: var(--bg-muted); border-color: var(--border); color: var(--text-accented); }
.alert-light { background: var(--bg-muted); border-color: var(--border-muted); color: var(--text-accented); }
.alert-dark { background: var(--bg-inverted); border-color: var(--bg-inverted); color: var(--text-inverted); }

/* Baseline "premium" polish for every alert/badge, not just the -secondary/
   -light/-dark ones just added above — rounder corners + a hairline-only
   border read as a soft callout instead of a stock Bootstrap warning box,
   matching the card radius scale from (C) above. */
.alert { border-radius: var(--rounding-md); }
.badge { border-radius: var(--rounding-sm); font-family: var(--font-body); }

/* Invoices list (clientareainvoices.tpl) — hosting.com screenshot parity pass, 2026-08-12.
   New "..." more-actions trigger sitting next to the existing View button in
   .app-bill-col-actions (View Invoice / Download PDF / Pay Now-when-unpaid), matching the
   reference screenshot's row-end kebab icon. Sized down from the 40px topbar .app-icon-btn
   (too large for a table row) but reuses the exact same transparent-bg/hover/radius/color
   treatment for visual consistency with every other icon-only button in the app. */
.app-bill-col-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; }
.app-bill-row-more { position: relative; }
.app-bill-row-more-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 0;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-bill-row-more-btn:hover,
.app-bill-row-more-btn:focus-visible { background: var(--bg-muted); color: var(--text); }
.app-bill-row-more .dropdown-menu { min-width: 190px; }

/* Table header label + Issued-column date reformat: this page previously used the
   `invoicesdatecreated` global lang string ("Invoice Date") and the raw client-date-format
   ("11/08/2026"). Changed in the .tpl (not here) to a page-local "Issued" label and a
   `%B %e, %Y`-formatted long date ("August 11, 2026") — real data (WHMCS's own
   normalisedDateCreated, just reformatted, no fabricated time-of-day since tblinvoices.date
   has no time component to show) styled closer to the hosting.com reference's readable date
   line. Mobile card-stack view below needs the actions row to right-align its two buttons
   instead of the desktop's default (table-cell text-align), since .app-bill-col-actions is
   now a flex row rather than a single link. */
@media (max-width: 640px) {
    .app-bill-col-actions { justify-content: flex-start; }
}

/* =========================================================================
   Home dashboard — stat-tile strip, "live overview" innovate pass, 2026-08-13.
   Supersedes the 2026-08-12 hosting.com-screenshot-parity rebuild (rationale
   preserved in git history / the two even-older dead ".app-welcome-stats"
   rules further up this file, left in place per this file's append-only
   discipline — this block still wins via source-order cascade). This pass
   is a genuine step up, not a recolor: a new ".app-welcome-stats-card"
   wrapper now owns the floating-card chrome (was directly on ".app-welcome-
   stats" itself) so a labeled "Account overview / Updated just now" head
   strip can sit above the tile grid — echoing the "Live Status Preview /
   Checked Xs ago" head bar on market/monitoring.php's own dashboard mockup,
   the explicit creative reference this task named for this exact area,
   except every number below it is real WHMCS data, not illustrative. Each
   tile also now gets its OWN category color (via a --primary/--info/
   --notice/--secondary/--success modifier class set server-side in the
   .tpl, not decorative nth-child guessing) instead of one flat orange
   applied to all six, and its value count-up-animates from 0 to the real
   server-rendered number on load (initWelcomeStatsCountUp() in app-
   shell.js) — "real data feels alive," per the brief, not just listed.

   IMPORTANT — explicit property resets below (background/border-radius/
   box-shadow/margin/overflow all set to neutral values on ".app-welcome-
   stats" itself, not simply omitted): the two OLDER dead ".app-welcome-
   stats" rule blocks earlier in this file each also declare some of these
   same properties (a translucent-black background, a smaller border-
   radius). Since those properties now moved to ".app-welcome-stats-card"
   instead, omitting them here entirely would let the dead blocks' own
   values win the cascade by default (same selector, no later contest) and
   silently reappear on the plain grid element — explicit neutral resets
   prevent that regression. Same reasoning covers the removed ".app-
   welcome-stat" border-right the oldest dead block still declares (see
   "border-right: 0" below) — a real, previously-invisible-in-light-mode/
   faint-in-dark-mode leftover, cleaned up as part of this pass. */
.app-welcome-stats-card {
    position: relative;
    z-index: 1;
    margin: -1.75rem 1.5rem 0;
    background: var(--bg-lifted);
    border-radius: var(--rounding-2xl);
    box-shadow: var(--elevation-3), 0 16px 36px -18px color-mix(in srgb, var(--primary) 30%, transparent);
    overflow: hidden;
}
:root[data-theme="dark"] .app-welcome-stats-card {
    box-shadow: var(--elevation-3), 0 16px 36px -18px color-mix(in srgb, var(--primary) 45%, transparent);
}
.app-welcome-stats-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    padding: 0.8rem 1.25rem;
    border-bottom: 1px solid var(--border-muted);
}
.app-welcome-stats-live {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-accented);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.app-welcome-stats-live-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--success);
    box-shadow: 0 0 0 3px var(--success-soft);
    flex-shrink: 0;
    animation: appWelcomeLivePulse 2.2s ease-in-out infinite;
}
@keyframes appWelcomeLivePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.app-welcome-stats-updated {
    font-size: var(--text-sm);
    color: var(--text-lifted);
}
.app-welcome-stats {
    position: relative;
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    margin: 0;
}
.app-welcome-stat {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.95rem 1.1rem;
    color: var(--text);
    border-left: 1px solid var(--border);
    border-right: 0; /* cancels the oldest dead ".app-welcome-stat" block's leftover border-right */
    box-shadow: inset 0 -2px 0 0 transparent;
    min-width: 0;
    transition: background-color var(--duration-fast) var(--ease-out),
        box-shadow var(--duration-fast) var(--ease-out);
}
.app-welcome-stat:first-child { border-left: 0; }
.app-welcome-stat:hover { background: var(--bg-muted); color: var(--text); box-shadow: inset 0 -2px 0 0 var(--stat-accent, transparent); }
.app-welcome-stat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--rounding-md);
    background: var(--bg-accented);
    color: var(--text-accented);
    font-size: 0.9rem;
    flex: none;
    transition: transform var(--duration-fast) var(--ease-out);
}
.app-welcome-stat:hover .app-welcome-stat-icon { transform: scale(1.08); }
.app-welcome-stat-label {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--text-lifted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.app-welcome-stat-value {
    flex: none;
    font-size: 1.2rem;
    font-weight: 800;
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* Per-category identity (2026-08-13) — six tiles, five real semantic hues (Domains/Affiliates/
   Quotes share one "info" slot since only one of the three ever renders per install). "Emails" is
   deliberately left the plain neutral default (no modifier rule needed below) rather than reusing
   any of the five, since it's a Market Connect upsell metric that reads correctly a little quieter
   — usually 0 until that vendor is purchased. */
.app-welcome-stat--primary { --stat-accent: var(--primary); }
.app-welcome-stat--primary .app-welcome-stat-icon { background: var(--primary-soft); color: var(--primary-accented); }
.app-welcome-stat--info { --stat-accent: var(--info); }
.app-welcome-stat--info .app-welcome-stat-icon { background: var(--info-soft); color: var(--info-accented); }
.app-welcome-stat--notice { --stat-accent: var(--notice); }
.app-welcome-stat--notice .app-welcome-stat-icon { background: var(--notice-soft); color: var(--notice-accented); }
.app-welcome-stat--warning { --stat-accent: var(--secondary); }
.app-welcome-stat--warning .app-welcome-stat-icon { background: var(--secondary-soft); color: var(--secondary-accented); }
.app-welcome-stat--success { --stat-accent: var(--success); }
.app-welcome-stat--success .app-welcome-stat-icon { background: var(--success-soft); color: var(--success-accented); }

@media (max-width: 991.98px) {
    .app-welcome-stats-card { margin: -1.5rem 1rem 0; }
    .app-welcome-stats { grid-template-columns: repeat(3, 1fr); }
    .app-welcome-stat:nth-child(3n+1) { border-left: 0; }
    .app-welcome-stat:nth-child(n+4) { border-top: 1px solid var(--border); }
}
@media (max-width: 575.98px) {
    .app-welcome-stats-card { margin: -1.25rem 0.75rem 0; }
    .app-welcome-stats-head { padding: 0.65rem 1rem; }
    .app-welcome-stats { grid-template-columns: repeat(2, 1fr); }
    .app-welcome-stat { padding: 0.75rem 0.85rem; gap: 0.5rem; }
    .app-welcome-stat-icon { width: 30px; height: 30px; font-size: 0.8rem; }
    .app-welcome-stat-value { font-size: 1.05rem; }
    /* Reset the tablet breakpoint's 3-column divider math, then reapply for 2 columns
       (later rule in the same cascade layer/specificity wins at this narrower width). */
    .app-welcome-stat:nth-child(3n+1) { border-left: 1px solid var(--border); }
    .app-welcome-stat:nth-child(2n+1) { border-left: 0; }
    .app-welcome-stat:nth-child(n+4) { border-top: 0; }
    .app-welcome-stat:nth-child(n+3) { border-top: 1px solid var(--border); }
}
@media (prefers-reduced-motion: reduce) {
    .app-welcome-stat.app-unified-row-anim { animation: none; }
    .app-welcome-stats-live-dot { animation: none; }
}

/* -------------------------------------------------------------------------
   Home dashboard "Products & Services" pill-tabs (#appUnifiedTabsRow) —
   centering + modernization pass, 2026-08-12. Scoped to THIS instance only
   (not the shared .app-pill-tabs/.app-pill-tab base rules near the top of
   this file, still used as-is on Invoices/Products&Services/Support Tickets
   list-page toolbars, which are deliberately left-aligned above their own
   filter toolbars — this is the one spot that reads better centered under a
   card header). Studied against Screenshot5.png's segmented-track pattern
   (light-gray track, raised solid pill for the active item) — kept this
   project's existing fully-rounded pill shape (not that screenshot's
   softer-radius full-width segments) since the brief explicitly asked for
   "modern rounded-pill shape", just gave it more visual weight: larger
   track/pill padding, a stronger active-vs-inactive contrast (bolder active
   text weight, dedicated hover treatment on inactive pills that was missing
   before), and an explicit transform+shadow entrance on the active state
   using this project's own --duration-slow/--ease-standard "fill" pair
   (per the token comments) instead of the base rule's --duration-fast/
   --ease-out "hover background" pair, since switching the ACTIVE pill is a
   more deliberate action than a plain hover. */
.app-unified-tabs-row {
    display: flex;
    justify-content: center;
}
.app-unified-tabs-row .app-pill-tabs {
    padding: 5px;
    gap: 3px;
    background: var(--bg-muted);
}
.app-unified-tabs-row .app-pill-tab {
    padding: 0.55rem 1.15rem;
    font-weight: 600;
    color: var(--text-lifted);
    transition: background-color var(--duration-slow) var(--ease-standard),
        color var(--duration-slow) var(--ease-standard),
        box-shadow var(--duration-slow) var(--ease-standard);
}
.app-unified-tabs-row .app-pill-tab:hover { background: var(--bg-accented); color: var(--text); }
.app-unified-tabs-row .app-pill-tab.active {
    font-weight: 700;
    color: var(--text);
    background: var(--bg);
    box-shadow: 0 2px 6px rgba(17, 24, 39, 0.1);
}
:root[data-theme="dark"] .app-unified-tabs-row .app-pill-tab.active {
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}
@media (max-width: 575.98px) {
    .app-unified-tabs-row .app-pill-tabs { width: 100%; justify-content: center; }
}

/* =========================================================================
   Open Support Ticket — single-screen rebuild (supportticketsubmit-steptwo.tpl)
   =========================================================================
   Matches the hosting.com "Submit a support ticket" reference structurally
   (centered title -> centered department question+pills -> Subject/Message
   -> Related product pill -> bottom bar with Add files / Submit) while
   staying a normal page inside this app's persistent header+sidebar shell
   rather than a full-viewport overlay — see the long docblock at the top
   of supportticketsubmit-steptwo.tpl for the full reasoning. Everything
   below is scoped under .tix-open-page so none of it leaks onto any other
   page that happens to reuse .app-pill-select, .custom-file, etc. */

.tix-open-page { max-width: 900px; margin: 0 auto; }

.tix-open-back {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-lifted);
    font-weight: 600;
    font-size: var(--text-md);
    margin-bottom: 1rem;
    padding: 0.35rem 0.6rem 0.35rem 0.25rem;
    border-radius: var(--rounding-md);
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.tix-open-back svg { width: 18px; height: 18px; stroke-width: 2.25; }
.tix-open-back:hover { background: var(--bg-muted); color: var(--text); text-decoration: none; }

.tix-open-card { overflow: hidden; }
.tix-open-card .card-body.extra-padding { padding: 2.5rem 2.5rem 2rem; }
.tix-open-inner { max-width: 640px; margin: 0 auto; }

.tix-open-header { text-align: center; margin-bottom: 1.75rem; }
.tix-open-title { font-size: 1.65rem; font-weight: 800; color: var(--text); margin: 0; }

.tix-open-question { text-align: center; margin-bottom: 1.25rem; }
.tix-open-question-title { font-size: 1.15rem; font-weight: 700; color: var(--text); margin: 0 0 0.4rem; }
.tix-open-question-help { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }

.tix-open-dept { display: flex; justify-content: center; margin-bottom: 1.25rem; }
.tix-open-dept .app-pill-select { justify-content: center; }
/* Orange (--secondary) selected-pill fill for THIS page only — the shared
   .app-pill-select-option.selected default (custom.css ~line 1946) uses
   --primary (green) for every other consumer of the partial; overridden
   here per the explicit instruction to replace hosting.com's green
   accents with Vercaa orange throughout this rebuild. */
.tix-open-page .app-pill-select-option.selected,
.tix-open-page .app-pill-select-option:has(input:checked) {
    background: var(--secondary);
    border-color: var(--secondary);
    color: #fff;
}

.tix-open-priority { display: flex; align-items: center; justify-content: center; gap: 0.65rem; flex-wrap: wrap; margin-bottom: 1.75rem; }
.tix-open-priority-label { font-size: var(--text-sm); font-weight: 700; color: var(--text-lifted); text-transform: uppercase; letter-spacing: 0.03em; }
.tix-open-priority-pills .app-pill-select-option { padding: 0.3rem 0.75rem; font-size: var(--text-sm); }

.tix-open-guest-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 0.25rem; }
.tix-open-guest-fields.tix-open-hidden { display: none; }
@media (max-width: 575.98px) { .tix-open-guest-fields { grid-template-columns: 1fr; } }

.tix-open-field { margin-bottom: 1.25rem; }
.tix-open-field label { display: block; font-weight: 700; color: var(--text); font-size: var(--text-md); margin-bottom: 0.4rem; }
.tix-open-required { color: var(--secondary); font-weight: 700; }
.tix-open-optional { color: var(--text-muted); font-weight: 500; font-size: var(--text-sm); }

.tix-open-input,
.tix-open-textarea {
    border-radius: var(--rounding-md);
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
/* Orange focus ring on the composer's own fields (replaces hosting.com's
   reference dark ring / this project's sitewide green --focus-ring-color,
   same orange-over-green swap as the department pills above). */
.tix-open-page .tix-open-input:focus,
.tix-open-page .tix-open-input:focus-visible,
.tix-open-page textarea.tix-open-textarea:focus,
.tix-open-page textarea.tix-open-textarea:focus-visible {
    outline: none;
    border-color: var(--secondary);
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--secondary);
}

.tix-open-relproduct { position: relative; display: flex; align-items: center; }
.tix-open-relproduct-icon {
    position: absolute;
    left: 1.1rem;
    width: 16px;
    height: 16px;
    color: var(--secondary-accented);
    pointer-events: none;
    stroke-width: 2.25;
}
/* 2026-08-12: chevron enlarged (14px→16px) and moved off the same
   secondary/secondary-soft family the fill used to share — see the select
   rule below for why. Neutral by default (var(--text-muted), the exact
   token this project's other real dropdown carets already use — see
   .app-nav-caret and .acct-switch-chevron) so it reads via the universal
   "select" convention instead of blending into an orange-on-orange fill;
   it still picks up the page's orange accent on hover/focus so it doesn't
   go fully generic. */
.tix-open-relproduct-chevron {
    position: absolute;
    right: 1.1rem;
    width: 16px;
    height: 16px;
    color: var(--text-muted);
    pointer-events: none;
    stroke-width: 2.5;
    transition: color var(--duration-fast) var(--ease-out);
}
.tix-open-relproduct:hover .tix-open-relproduct-chevron,
.tix-open-relproduct-select:focus ~ .tix-open-relproduct-chevron,
.tix-open-relproduct-select:focus-visible ~ .tix-open-relproduct-chevron {
    color: var(--secondary);
}
/* 2026-08-12: was a solid bold-centered var(--secondary-soft) pill — live
   feedback was that, chevron notwithstanding, it read as a CTA button, not
   a picker. Root cause: this page reserves exactly that visual — solid
   secondary-soft fill + secondary-accented bold text — for its own real
   action button (.tix-open-submit-btn's pre-ready state, a few rules
   down), so reusing it on a plain native <select> borrowed a "press me"
   signal that a picker shouldn't send. Rebuilt on the same bordered/
   left-aligned surface language this project already uses for its other
   real dropdown affordances (.app-pill-btn: border var(--border) +
   background var(--bg-muted); the account-avatar dropdown trigger is the
   same .app-icon-btn/.app-pill-btn family) — left-aligned text, neutral
   surface, orange reserved for the left "+" icon (an "add" hint, not a
   fill) and for hover/focus. The :has() rule below adds the "current
   selection" state this control never had: muted/regular while the
   "+ Add product" placeholder (value="") is still selected, bold brand-
   orange once a real related service is chosen — pure CSS, no JS, so it
   degrades safely (permanently muted, never wrong) on any engine without
   :has() support. */
.tix-open-relproduct-select {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-accented);
    font-weight: 600;
    font-size: var(--text-md);
    padding: 0.75rem 2.75rem;
    text-align: left;
    text-align-last: left;
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.tix-open-relproduct-select:hover { background: var(--bg-lifted); border-color: var(--border-lifted); }
.tix-open-relproduct-select:focus,
.tix-open-relproduct-select:focus-visible {
    outline: none;
    border-color: var(--secondary);
    background: var(--bg-lifted);
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--secondary);
}
@supports selector(:has(*)) {
    .tix-open-relproduct-select:has(option:checked:not([value=""])) {
        color: var(--secondary-accented);
        font-weight: 700;
    }
}

.tix-open-bottombar {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 1.25rem 2.5rem;
    border-top: 1px solid var(--border-muted);
    background: var(--bg-muted);
}
.tix-open-attach { flex: 1 1 auto; min-width: 0; }
.tix-open-attach .attachment-group { display: flex; align-items: stretch; gap: 0; margin-bottom: 0.35rem; }
/* 2026-08-12: real root cause of the "badly proportioned" Add-files control (Screenshot4.png parity
   fix) — NOT primarily a color/weight styling gap, the visible pill was already a reasonably close
   match. The actual bug: Bootstrap's stock .custom-file-input is `position:relative` (in normal flow)
   by design, with opacity:0 and its OWN native full-size file-input box — it's kept invisible-but-
   present specifically so a `.custom-file-label` at `position:absolute` can overlay/cover it. This
   component's label was (correctly, for the pill look) switched to `position:relative` so it renders
   as a normal inline pill instead of an absolute overlay — but the invisible native input was never
   taken out of flow to match, so it kept contributing its own ~300-400px native rendered width to
   `.custom-file`'s layout box (confirmed live via getComputedStyle: input width 326.95px, opacity 0,
   position relative, sitting directly after the 93px-wide visible pill) — creating a large invisible
   dead-zone between the pill and the "add more" (+) button, which is what actually read as "badly
   proportioned"/disconnected on screen, not the pill's own border/radius/padding. Fixed by restoring
   the standard Bootstrap custom-file pattern: input absolutely positioned/stretched exactly over the
   label (inset:0, so a click anywhere on the visible pill still opens the real file picker — pure
   positioning change, zero effect on the real <input type="file" name="attachments[]"> functionality,
   multiple-file/add-more flow, or js/scripts.js's existing delegated handlers), label promoted back to
   the sizing/stacking context via z-index. Applies to every dynamically-cloned attachment row too
   (same .tix-open-attach ancestor scope), not just the first. */
.tix-open-attach .custom-file { position: relative; width: auto; flex: 0 0 auto; height: auto; display: inline-block; }
.tix-open-attach .custom-file-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    z-index: 2;
}
/* 2026-08-12: visual-quality redesign pass, on top of (not replacing) the dead-zone
   positioning fix above, which stays intact. The earlier fix made the control correctly
   *sized*; this pass addresses it still reading as low-effort once sized right: a plain
   bordered pill sitting next to a same-looking-but-separately-bordered circular "+" a
   few px away, with no icon and no sense of "how many files so far." Three changes, all
   CSS/markup-only — the real <input type="file" name="attachments[]">, the #btnTicket-
   AttachmentsAdd add-more click handler, and the .custom-file-input change handler in
   js/scripts.js (~line 22295-22298, re-read before this pass) are untouched:
   1. Paperclip glyph (Heroicons outline "paper-clip" — same self-hosted MIT set used
      everywhere else, see app-nav.tpl's icon comment) via ::before + mask-image, NOT a
      real DOM child. Reason: scripts.js's change handler does
      `.siblings('.custom-file-label').text(fileName)` — a real .text() call replaces
      every child node of the label, which would delete any icon element placed inside
      it the moment a file is chosen. A masked pseudo-element isn't part of the DOM that
      jQuery mutates, so it survives every state (empty "+ Add files", filename shown,
      and every dynamically-cloned row) without touching that handler.
   2. `.attachment-group` (the existing Bootstrap input-group wrapper — no new element)
      becomes ONE bordered/rounded shell around both the label and the add-more button,
      with a hairline divider between them, instead of two independently-pilled controls
      floating near each other. Scoped to `.tix-open-attach > .attachment-group` (direct
      child only) so it only wraps the real first row with a real add-more button next to
      it; rows later cloned into #fileUploadsContainer sit deeper (`.tix-open-attach >
      .tix-open-filelist > .attachment-group`, see js/scripts.js's append target) and
      correctly fall through to the plain single-pill treatment further down instead,
      since they never come with their own add-more button in the cloned markup.
   3. `.tix-open-filelist:not(:empty)` (already an existing selector) gets a small "+N
      more attached" counter badge, pure CSS via counter-increment on each cloned
      `.attachment-group` read back with counter() on the list's own ::after — so it
      totals whatever's actually in the DOM at render time, no JS. Worded as "attached"
      rather than "N files" since CSS can't see whether a given cloned file input
      actually has a file chosen yet (no such thing as a `:has-file` selector), only that
      the row exists — same honesty the pre-existing filename-swap-on-choose already
      gives you for the first slot. */
.tix-open-attach > .attachment-group {
    display: inline-flex;
    align-items: stretch;
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-radius: 999px;
    overflow: hidden;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.tix-open-attach > .attachment-group:hover,
.tix-open-attach > .attachment-group:focus-within {
    border-color: var(--secondary);
}
.tix-open-attach > .attachment-group:focus-within {
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--secondary);
}
/* Cancel Bootstrap's stock `.input-group-append{margin-left:-1px}` (a border-
   collapsing trick for its own two-separately-bordered-siblings layout, which
   no longer applies now the divider is a single 1px border owned by the
   add-more button itself) — without this the append wrapper pulls 1px into
   the label side of the shared shell above. */
.tix-open-attach > .attachment-group > .input-group-append { margin-left: 0; }
.tix-open-attach .custom-file-label {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    height: auto;
    background: transparent;
    border: 0;
    border-radius: 999px;
    padding: 0.6rem 1.15rem 0.6rem 2.35rem;
    font-weight: 600;
    font-size: var(--text-md);
    color: var(--text-accented);
    max-width: 240px;
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out);
}
.tix-open-attach .custom-file-label::before {
    content: "";
    position: absolute;
    left: 0.95rem;
    top: 50%;
    width: 15px;
    height: 15px;
    transform: translateY(-50%);
    background-color: var(--secondary-accented);
    mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271.75%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%3E%3Cpath%20d%3D%27M18.375%2012.739l-7.693%207.693a4.5%204.5%200%200%201-6.364-6.364l10.94-10.94A3%203%200%201%201%2019.5%207.372L8.552%2018.32m5.699-9.941-7.81%207.81a1.5%201.5%200%200%200%202.122%202.122l7.81-7.81%27/%3E%3C/svg%3E");
    mask-repeat: no-repeat;
    mask-position: center;
    mask-size: contain;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%20fill%3D%27none%27%20stroke%3D%27white%27%20stroke-width%3D%271.75%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%3E%3Cpath%20d%3D%27M18.375%2012.739l-7.693%207.693a4.5%204.5%200%200%201-6.364-6.364l10.94-10.94A3%203%200%201%201%2019.5%207.372L8.552%2018.32m5.699-9.941-7.81%207.81a1.5%201.5%200%200%200%202.122%202.122l7.81-7.81%27/%3E%3C/svg%3E");
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    -webkit-mask-size: contain;
    pointer-events: none;
}
.tix-open-attach .custom-file-label::after { display: none; content: none; }
.tix-open-attach > .attachment-group:hover .custom-file-label,
.tix-open-attach .custom-file-input:focus ~ .custom-file-label { color: var(--text); }
.tix-open-addmore-btn {
    width: 40px;
    height: auto;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border: 0;
    border-left: 1px solid var(--border);
    border-radius: 0;
    background: transparent;
    color: var(--text-lifted);
    font-size: 0.8rem;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.tix-open-addmore-btn:hover,
.tix-open-addmore-btn:focus-visible {
    background: var(--secondary-soft);
    color: var(--secondary-accented);
    outline: none;
}
.tix-open-filelist:not(:empty) { margin-bottom: 0.35rem; counter-reset: tix-attach-count; }
.tix-open-filelist .attachment-group {
    margin-top: 0.35rem;
    counter-increment: tix-attach-count;
}
.tix-open-filelist .attachment-group .custom-file-label {
    background: var(--bg-muted);
    border: 1px solid var(--border);
}
.tix-open-filelist .attachment-group:hover .custom-file-label,
.tix-open-filelist .attachment-group .custom-file-input:focus ~ .custom-file-label {
    border-color: var(--secondary);
    background: var(--bg-lifted);
}
.tix-open-filelist:not(:empty)::after {
    content: "+" counter(tix-attach-count) " more attached";
    display: inline-block;
    margin-top: 0.15rem;
    padding: 0.2rem 0.65rem;
    border-radius: 999px;
    background: var(--secondary-soft);
    color: var(--secondary-accented);
    font-size: var(--text-xs);
    font-weight: 700;
}
.tix-open-attach-note { margin-top: 0.15rem; }
.tix-open-attach-note small { font-size: var(--text-xs); }

.tix-open-submit-btn {
    flex-shrink: 0;
    border: 0;
    border-radius: 999px;
    padding: 0.8rem 1.75rem;
    font-weight: 700;
    font-size: var(--text-md);
    color: var(--secondary-accented);
    background: var(--secondary-soft);
    box-shadow: none;
    cursor: not-allowed;
    transition: background var(--duration-slow) var(--ease-standard),
        color var(--duration-slow) var(--ease-standard),
        box-shadow var(--duration-slow) var(--ease-standard),
        filter var(--duration-slow) var(--ease-standard);
}
/* Real disabled attribute stays in sync with JS validity, so this rule (not
   the sitewide [disabled]{opacity:25%} rule) is what governs the soft
   pre-valid look — an explicit opacity:1 keeps it from also picking up that
   generic dimming on top of its own already-soft background/text pairing. */
.tix-open-submit-btn:disabled { opacity: 1; }
.tix-open-submit-btn.is-ready {
    color: #fff;
    cursor: pointer;
    background: linear-gradient(135deg, var(--secondary) 0%, var(--secondary-lifted) 100%);
    box-shadow: 0 4px 14px rgba(232, 123, 53, 0.35);
}
.tix-open-submit-btn.is-ready:hover,
.tix-open-submit-btn.is-ready:focus-visible { filter: brightness(0.97); color: #fff; }

@media (max-width: 575.98px) {
    .tix-open-card .card-body.extra-padding { padding: 1.75rem 1.25rem 1.5rem; }
    .tix-open-bottombar { padding: 1.1rem 1.25rem; flex-direction: column; align-items: stretch; }
    .tix-open-submit-btn { width: 100%; text-align: center; }
    .tix-open-attach .custom-file-label { max-width: 100%; }
}

/* =========================================================================
   Header icon-row polish + language-switcher relocation + footer rebuild +
   notifications slide-in panel (2026-08-12). See header.tpl/footer.tpl for
   the matching markup changes and their inline docblocks for full context.
   ========================================================================= */

/* Part 1 — shared sizing for the topbar's inline-SVG action icons (Help, Notifications-bell,
   Theme-toggle moon/sun, Cart, Language). Same stroke-width as the sidebar's .app-nav-svg
   (Heroicons-outline convention) so the two icon systems read as one consistent visual language
   instead of the sidebar looking crisper than the topbar (which is exactly what the old FontAwesome-
   icon-font topbar icons looked like next to these). Sized a touch larger than the sidebar's 20px
   (22px) since these sit alone in a 40px circular button with more surrounding whitespace, matching
   the icon-to-button proportions measured off screenshot.png/Screenshot2.png/Screenshot5.png. */
.app-icon-svg {
    width: 22px;
    height: 22px;
    stroke-width: 1.75;
}
/* Deliberately NOT declaring `display` here. First attempt at this rule included `display: block`,
   which reproduced the exact same bug it was meant to fix: .app-icon-svg and .app-theme-icon-dark/
   -light (custom.css ~line 408: `.app-theme-icon-light { display: none; }`) both sit on the SAME
   <svg> element with equal specificity (single class each) — since .app-icon-svg was declared LATER
   in this file (appended at the very end), its own `display: block` cascaded over the theme toggle's
   `display: none`, so BOTH the moon and sun SVGs stayed visible at once, same symptom as the original
   FontAwesome bug this whole pass exists to fix (caught live via getComputedStyle before shipping this
   file — see the header.tpl docblock at the theme-toggle button for the original bug writeup). Since
   these icons are direct children of an `.app-icon-btn`/`.app-nav-link` flex container either way,
   they get auto-"blockified" as flex items regardless of their own specified display value (CSS
   Flexbox §2), so `display: block` was never actually necessary here — omitting it removes the
   specificity collision entirely rather than working around it with !important or a more specific
   selector. */

/* A touch more breathing room between the topbar's trailing icon cluster (Help through Avatar) than
   the default 0.6rem gap every direct child of .app-topbar-inner uses — closer to the generous spacing
   in the reference screenshots. Scoped to .app-icon-btn/.app-avatar-dropdown specifically (not the
   logo/mobile-toggle/Support-PIN-pill on the leading edge) via margin-left, which stacks with the flex
   gap rather than fighting it. */
.app-topbar-inner > .app-icon-btn,
.app-topbar-inner > .app-avatar-dropdown {
    margin-left: 0.15rem;
}

/* Part 2 — language/currency switcher, relocated from the footer to the header (see header.tpl,
   directly before .app-avatar-dropdown). It's a plain .app-icon-btn, so it already inherits that
   class's sizing/hover/focus treatment identically to its neighbors — no new button styling needed,
   just confirming here (no CSS block required) that no footer-specific override survives to affect
   it. The old footer button's own btn-outline-light/iti-flag styling is simply unused now (that
   markup no longer exists in footer.tpl); left alone in place per the append-only convention rather
   than hunting down and deleting those now-dead rules. */

/* Part 3b — Footer modernization. MOVED (2026-08-13 header/footer unification pass) to
   templates/vercaa-2026/css/guest-chrome.css, the one shared stylesheet the footer markup now
   comes from in every context (Smarty AND the 9 market/*.php pages) via templates/vercaa-2026/
   includes/shared-guest-chrome.php's vercaa_render_guest_footer(). Same padding/hairline-divider/
   copyright-color values as before, just no longer duplicated here — the two earlier defensive
   footer.footer background-color fixes above/below this comment (the stock-theme dark-inverted-band
   corrections) are left in place unchanged; they're harmless, redundant-but-matching now that
   guest-chrome.css also sets background-color, not conflicting. */

/* Part 4 — Notifications: right-anchored, full-viewport-height slide-in panel (header.tpl's
   #appNotifPanel/#appNotifBackdrop, opened/closed by initNotifPanel() in app-shell.js), replacing the
   old small Bootstrap .dropdown-menu. z-index sits directly above the sidebar/sidebar-backdrop pair
   (1030/1029) and the fixed header (1040), same numbering convention as the mobile sidebar drawer's
   own backdrop-below/panel-above pairing. */
.app-notif-backdrop {
    position: fixed;
    inset: 0;
    top: var(--header-height);
    /* 2026-08-13: was fully transparent (click-catcher only) — a faint scrim now separates the
       panel from the page behind it, giving it a genuine "floating surface" read instead of the
       rest of the app looking fully interactive underneath. Deliberately much lighter than the
       true modals' 80% black (.app-domain-modal-backdrop / .modal-backdrop.show) since this is a
       drawer over an otherwise-still-usable page, not a blocking dialog. */
    background: rgba(8, 8, 12, 0.22);
    transition: opacity var(--duration-base) var(--ease-standard);
    z-index: 1049;
    opacity: 0;
    pointer-events: none;
}
body.app-notif-open .app-notif-backdrop { opacity: 1; pointer-events: auto; }

.app-notif-panel {
    position: fixed;
    top: var(--header-height);
    right: 0;
    bottom: 0;
    width: 400px;
    max-width: 92vw;
    background: var(--bg);
    border-left: 1px solid var(--border-muted);
    box-shadow: -12px 0 30px rgba(0, 0, 0, 0.14);
    z-index: 1050;
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform var(--duration-base) var(--ease-standard);
}
body.app-notif-open .app-notif-panel { transform: translateX(0); }

.app-notif-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--border-muted);
    flex-shrink: 0;
}
/* Title + icon chip pairing (2026-08-13) — see header.tpl's Part 4 markup update. Same chip
   language as .app-lang-modal-icon-chip/.app-modal-icon-chip, sized down for an inline header
   context rather than a standalone hero position. */
.app-notif-panel-heading { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.app-notif-panel-icon-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary-accented);
}
.app-notif-panel-icon-chip svg { width: 18px; height: 18px; stroke-width: 1.75; }
.app-notif-panel-title { margin: 0; font-size: 1.125rem; font-weight: 700; color: var(--text); }
.app-notif-panel-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    border: 0;
    background: transparent;
    color: var(--text-lifted);
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-notif-panel-close:hover { background: var(--bg-muted); color: var(--text); }
.app-notif-panel-close svg { width: 18px; height: 18px; display: block; }

/* Tabs reuse the shared .app-pill-tabs/.app-pill-tab/.app-pill-tab-count system (pill-tabs.tpl,
   already used by Products & Services / Invoices / Support Tickets etc.) for visual consistency,
   authored directly here rather than through that partial because pill-tabs.tpl's `{if $tab.count}`
   guard hides a zero count entirely — wrong for this panel, where "Read (0)" showing its real zero
   is the whole point (matches Screenshot6.png's "All (0) / Unread (0) / Read (0)" exactly). Buttons
   instead of pill-tabs.tpl's anchors since this is pure client-side filtering of already-rendered
   markup (same pattern as initUnifiedServiceGroups()'s tab click-handler on the home dashboard), not
   page navigation. */
.app-notif-panel-tabs.app-pill-tabs { display: flex; width: calc(100% - 3rem); margin: 1rem 1.5rem 0; }
.app-notif-panel-tabs button.app-pill-tab {
    flex: 1 1 0;
    justify-content: center;
    border: 0;
    background: transparent;
    font-family: inherit;
    cursor: pointer;
}
.app-notif-panel-tabs button.app-pill-tab.active { background: var(--bg); }

.app-notif-panel-body {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0;
}
.app-notif-list-panel { display: flex; flex-direction: column; min-height: 100%; }
.app-notif-list-panel[hidden] { display: none; }
.app-notif-list-panel .app-notif-item { padding: 0.9rem 1.5rem; }
/* Reused as-is from the old dropdown's row styling (.app-notif-item/.app-notif-icon/.app-notif-message,
   ~line 497) per the instruction to adapt the existing row component rather than rebuild it — only the
   padding above changes, to fit this wider panel instead of the old 340px dropdown. */

/* Row redesign (2026-08-13 app-shell elevation pass) — the bare stroke icon is now wrapped in
   .app-notif-icon-chip (see includes/notif-item.tpl) so severity reads as a real colored badge,
   an unread "ping" dot sits on that chip, and a hover rail echoes the same left-accent motif used
   on the persistent sidebar's active nav item (.app-nav-link.active::before) — a small deliberate
   thread tying two different overlay surfaces in this shell to one visual language. */
.app-notif-item {
    position: relative;
    overflow: hidden; /* clips the hover rail's rounded corner cleanly at the row edge */
}
.app-notif-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.65rem;
    bottom: 0.65rem;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--primary);
    opacity: 0;
    transform: scaleY(0.5);
    transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.app-notif-item:hover::before { opacity: 1; transform: scaleY(1); }
.app-notif-icon-chip {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: var(--rounding-md);
    background: var(--bg-muted);
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-notif-icon-chip .app-notif-icon { width: 18px; height: 18px; margin-top: 0; }
.app-notif-icon-chip-danger { background: var(--error-soft); }
.app-notif-icon-chip-warning { background: var(--warning-soft); }
.app-notif-icon-chip-info { background: var(--info-soft); }
.app-notif-icon-chip-success { background: var(--success-soft); }
/* .app-notif-icon-success — the row severity-color map (~line 645) only ever defined danger/
   warning/info; added here defensively so a literal "success" severity (WHMCS\User\Alert's 4th
   standard value) isn't left uncolored the way it silently would have been before. */
.app-notif-icon-success { color: var(--success); }
/* Unread "ping" — pure CSS, keyed off the ANCESTOR row's absence of .is-read rather than a
   template-rendered span, so js/app-shell.js's moveRowToRead() (which clones this whole row, then
   only adds .is-read afterwards) removes it for free with zero JS changes. */
.app-notif-item:not(.is-read) .app-notif-icon-chip::after {
    content: '';
    position: absolute;
    top: -2px;
    right: -2px;
    width: 9px;
    height: 9px;
    border-radius: 999px;
    background: var(--primary);
    box-shadow: 0 0 0 2px var(--bg);
}
.app-notif-item:not(.is-read) .app-notif-message { font-weight: 600; color: var(--text); }

/* When a group has no real rows (foreachelse's empty-state.tpl output is the group's only child),
   center it in the available panel-body height/width instead of pinning it to the top — matches
   Screenshot6.png's vertically-centered "You're all caught up!" state. Has no effect on a populated
   group (.app-notif-item rows stack normally; margin:auto only touches .app-empty-state itself, which
   isn't present in that variant). */
.app-notif-list-panel .app-empty-state { margin: auto; }

/* Empty-state icon chip recolored to Vercaa orange (--secondary) for this panel specifically — the
   shared empty-state.tpl partial's DEFAULT icon chip (~line 883) is a neutral gray circle used
   site-wide, intentionally left unchanged everywhere else; this scoped override (two classes,
   .app-notif-panel .app-empty-state-icon, higher specificity than the single-class default) only
   applies inside the notifications panel, and also swaps the shape from a full circle to a rounded
   square, matching Screenshot6.png's reference chip exactly (the screenshot's own chip happens to be
   hosting.com's green — recolored to Vercaa's brand orange per the user's explicit instruction). */
.app-notif-panel .app-empty-state-icon {
    border-radius: var(--rounding-lg);
    background: var(--secondary);
    color: #fff;
}

@media (max-width: 480px) {
    .app-notif-panel { width: 100%; max-width: 100%; }
}

/* -------------------------------------------------------------------------------------------
   REAL BUG FIX (2026-08-12, live-verified at 480px/514px viewport): adding the language-switcher
   icon (Part 2) pushed the topbar's trailing icon cluster past the point where it fits in a
   narrow-phone viewport. Confirmed live via getBoundingClientRect: at 514px window width,
   .app-topbar-inner's own content measured 613px wide against a 514px client width — the language
   icon and the avatar were being silently clipped off the right edge (not a page-wide horizontal
   scrollbar, since the fixed-position header doesn't create document overflow — the icons were
   just invisible/unreachable, worse than an ugly scrollbar since there was no visual cue anything
   was missing). This was already borderline before Part 2 added a 10th flex child (the ~175px-wide
   full wordmark logo alone was already most of the budget on a ~480-520px phone); tightening the
   layout at this breakpoint instead of just accepting the extra icon fixes both the pre-existing
   marginal case and the one this pass introduced. Three changes, in order of preference (each
   reduces the deficit; only the icons after point 2 fit on the very narrowest phones tested):
     1) Shrink the logo and tighten gaps/padding — recovers most of the width without removing
        anything.
     2) Remove the extra 0.15rem inter-icon margin (custom.css, "Part 1" above) at this width —
        that breathing room was a nice-to-have on desktop, not worth fighting for space over here.
     3) Hide the Help icon specifically (app-icon-btn-help, header.tpl) below 400px only, as a last
        resort — Help Centre/Knowledgebase remains one tap away via the sidebar's own "Support"
        section, so nothing becomes actually unreachable, it just stops competing for the same 40px
        on the very smallest phones. */
@media (max-width: 575.98px) {
    .app-topbar-inner { padding: 0 0.85rem; gap: 0.35rem; }
    .app-logo { margin-right: 0.35rem; }
    .app-logo .logo-img { height: 22px; }
    .app-topbar-inner > .app-icon-btn,
    .app-topbar-inner > .app-avatar-dropdown { margin-left: 0; }
}
@media (max-width: 400px) {
    .app-icon-btn-help { display: none; }
}

/* ==========================================================================
   Support-ticket "Open Ticket" bottom sheet (2026-08-12) — see
   includes/ticket-sheet.tpl for the full rationale (iframe of the real,
   already-orange-styled supportticketsubmit-steptwo.tpl, reusing the ?embed=1
   chrome-hiding mechanism) and js/app-shell.js's initTicketSheet()/
   initDismissibleSheet() for the behavior. Shell chrome (backdrop, dialog
   shape, drag handle, close button, loading state) intentionally mirrors the
   domain-search modal's own structure 1:1 — same z-index scheme (offset by
   +10 so the two can never visually collide even though they're never open
   at once), same bottom-sheet slide-up transform/transition values, same
   [hidden]-removal + forced-reflow + class-toggle open pattern — but kept as
   its own component-scoped class names (.app-ticket-sheet-*) rather than
   literally sharing .app-domain-modal-* selectors, consistent with how every
   other component on this site is styled (e.g. the domain modal's own
   --domain-ink tokens) rather than forcing two visually/structurally
   distinct popups to fight over one shared class. */
.app-ticket-sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1070;
    background: rgba(0, 0, 0, 0.8);
    opacity: 0;
    transition: opacity var(--duration-base) ease-in-out;
}
.app-ticket-sheet-backdrop.is-open { opacity: 1; }

.app-ticket-sheet {
    position: fixed;
    inset: 0;
    z-index: 1071;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    pointer-events: none;
}

.app-ticket-sheet-dialog {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: min(94vw, 720px);
    height: min(88vh, 880px);
    background: var(--bg-muted);
    color: var(--text);
    border-radius: var(--rounding-lg);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
    position: relative;
    overflow: hidden;
    opacity: 0;
    /* Same bottom-sheet motion as .app-domain-modal-dialog (custom.css, ~line 4503) — slides up
       from below its own resting position rather than fading/scaling in from center. Both this
       and the domain modal use the SAME js/app-shell.js function (initDismissibleSheet()) to
       drive this exact `transform` property live during a drag gesture. */
    transform: translateY(100%);
    transition: opacity var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard);
    pointer-events: auto;
}
.app-ticket-sheet.is-open .app-ticket-sheet-dialog { opacity: 1; transform: translateY(0); }

.app-ticket-sheet-drag-handle {
    width: 40px;
    height: 4px;
    border-radius: 999px;
    background: var(--border);
    margin: 0.85rem auto 0;
    flex-shrink: 0;
    touch-action: none;
    cursor: grab;
}
.app-ticket-sheet-drag-handle:active { cursor: grabbing; }

.app-ticket-sheet-close {
    position: absolute;
    top: 1.1rem;
    right: 1.1rem;
    width: 32px;
    height: 32px;
    border: 0;
    background: transparent;
    color: var(--text);
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.app-ticket-sheet-close svg { width: 20px; height: 20px; stroke-width: 2.25; }
.app-ticket-sheet-close:hover { background: var(--bg-accented); }

.app-ticket-sheet-body {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
}
.app-ticket-sheet-iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
    opacity: 0;
    transition: opacity var(--duration-slow) var(--ease-standard);
}
.app-ticket-sheet-iframe.is-loaded { opacity: 1; }

.app-ticket-sheet-loading {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.85rem;
    color: var(--text-lifted);
    font-weight: 600;
    background: var(--bg-muted);
    z-index: 1;
}
.app-ticket-sheet-loading[hidden] { display: none; }
/* Orange, not the domain modal's brand green — matches this task's explicit instruction to keep
   the --secondary treatment already applied to the ticket form itself (tix-open-* rules), so the
   sheet's own loading state reads as "this is the ticket flow" before the iframe content even
   paints. Reuses the domain modal's spin keyframe (appDomainModalSpin, ~line 4680) rather than
   redefining an identical @keyframes block under a new name. */
.app-ticket-sheet-spinner {
    width: 32px;
    height: 32px;
    color: var(--secondary);
    animation: appDomainModalSpin 1s linear infinite;
}

body.app-ticket-sheet-open { overflow: hidden; }

@media (max-width: 640px) {
    .app-ticket-sheet { padding: 0; }
    .app-ticket-sheet-dialog { max-width: 100%; height: 100%; border-radius: 0; }
}

/* Reduced motion (ticket sheet): keeps the popup fully functional — it still opens/closes
   instantly via the same [hidden] + class toggles — just skips the slide/fade tween itself. The
   domain-search modal's own equivalent rule moved to css/domain-search-modal.css alongside the
   rest of its shell CSS (2026-08-13 domain-search unification pass) — see that file's header
   comment. The drag-to-dismiss gesture already has its own separate reduced-motion branch in
   initDismissibleSheet() (now js/domain-search-modal.js, exported as window.VercaaDismissibleSheet
   for this ticket sheet to keep using) that skips the "finish the slide, then close" step and
   closes immediately instead. */
@media (prefers-reduced-motion: reduce) {
    .app-ticket-sheet-dialog,
    .app-ticket-sheet-backdrop {
        transition-duration: 0.01ms !important;
    }
}

/* =========================================================================
   Sitewide icon consistency + color-coding pass (2026-08-12).

   PART A — empty-state.tpl converted from FontAwesome <i> glyphs to self-
   hosted inline SVG (see that file for the full icon lookup table). The old
   rule only sized a font-based glyph via font-size: 1.5rem; SVG needs
   explicit pixel sizing since it doesn't inherit font-size the same way.
   =========================================================================
*/
.app-empty-state-icon svg { width: 26px; height: 26px; }
.app-empty-state-compact .app-empty-state-icon svg { width: 22px; height: 22px; }

/* PART B — the color-mapping system itself. Extends the SAME "icon-in-soft-
   colored-chip" pattern already established this session for the home
   dashboard's .app-unified-group-icon-{hosting,domain,marketconnect} chips
   (~line 4783 above) to every other "landmark" icon chip sitewide (empty
   states, card/section headers) via a shared, restrained 4-color system —
   deliberately capped at 4, not a rainbow, mapped by SEMANTIC PRODUCT
   CATEGORY, not decoratively:

     - hosting     -> --primary / --primary-soft / --primary-accented (green).
                      Server/hosting-plan-related. Matches .app-unified-
                      group-icon-hosting exactly (same tokens, same idea).
     - domain      -> --info / --info-soft / --info-accented (blue). Matches
                      .app-unified-group-icon-domain exactly — deliberately
                      NOT put in the green "hosting" family even though
                      domains are a hosting-adjacent concept, because that
                      distinction was already made by an earlier pass and
                      changing it now would un-do an existing, working
                      convention rather than extend it.
     - billing     -> --secondary / --secondary-soft / --secondary-accented
                      (orange). New category, but not a new HUE — --secondary
                      is Vercaa's own established second brand color
                      (invoices, payment methods, credits/wallet).
     - marketconnect -> --accent-purple / --accent-purple-soft /
                      --accent-purple-accented (purple) EVERYWHERE this tone
                      is used sitewide. Note (2026-08-13): no longer matches
                      .app-unified-group-icon-marketconnect on the Home
                      dashboard specifically — that one chip was repointed to
                      --notice/indigo instead (see its own comment), because
                      the 2026-08-13 foundational retheme aliased
                      --accent-purple to the new purple --primary, which made
                      it collide with Hosting's own primary-purple chip
                      sitting right next to it in that one 3-way category
                      grouping. This sitewide default is untouched and still
                      the documented "minor accent, used sparingly" color
                      from the brand-rebalance notes at the top of this file,
                      a good fit for the "extras/marketplace" category
                      everywhere else it appears.
     - neutral     -> the ORIGINAL default (--bg-muted / --text-lifted,
                      gray). This is the deliberate "everything else"
                      bucket: support/tickets, security, quotes, referrals,
                      generic "no results found" / "all caught up" states.
                      Support and security icons are NOT given their own
                      brand hue on purpose — forcing a 5th category color
                      onto them would blow past the "3-4 colors max"
                      restraint brief, and semantically they're not a
                      product family the way hosting/domains/billing/
                      market-connect are. Where an icon represents a STATUS
                      rather than a category (notification severity, ticket
                      priority, alert bars), this project already uses the
                      real semantic tokens (--error/--warning/--info/
                      --success) directly at the call site — those are left
                      untouched by this pass, on purpose, since forcing
                      THOSE onto the brand-category system would be wrong
                      in the other direction (a "danger" icon should never
                      become purple just because it happens to sit in a
                      Market Connect card). */
.app-empty-state-icon-hosting { background: var(--primary-soft); color: var(--primary-accented); }
.app-empty-state-icon-domain { background: var(--info-soft); color: var(--info-accented); }
.app-empty-state-icon-billing { background: var(--secondary-soft); color: var(--secondary-accented); }
/* 2026-08-13 app-shell elevation pass: re-pointed marketconnect off --accent-purple, which is now
   a live alias to var(--primary) (see the token block's "Accent Purple — RETIRED" comment) and so
   had become IDENTICAL to .app-empty-state-icon-hosting above — two different product categories
   rendering the exact same purple chip, the "4-color category system... needs reconsidering now
   that primary IS purple" gap the foundational retheme's own handoff flagged. Matched to
   --notice (indigo) rather than picking independently: .app-unified-group-icon-marketconnect
   (Home dashboard's equivalent chip, same category system) already made this exact call, so this
   keeps the mapping consistent app-wide instead of introducing a second, different "fix". */
.app-empty-state-icon-marketconnect { background: var(--notice-soft); color: var(--notice-accented); }
.app-empty-state-icon-neutral { background: var(--bg-muted); color: var(--text-lifted); }

/* PART C — the same 4+1 tone system, generalized into standalone reusable
   chip classes for use OUTSIDE empty-state.tpl (card/section headers, list
   sub-headers) — same sizing/shape idea as .app-unified-group-icon but a
   slightly larger 32px size tuned for a card-header context (vs. that
   component's 28px group-header context) and available independent of it,
   since .app-unified-group-icon lives under the Home dashboard's unified-
   services-card rules specifically. Usage: <span class="app-icon-chip
   app-icon-chip-hosting"><svg>...</svg></span>. */
.app-icon-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--rounding-md);
    flex: none;
}
.app-icon-chip svg { width: 18px; height: 18px; }
.app-icon-chip-hosting { background: var(--primary-soft); color: var(--primary-accented); }
.app-icon-chip-domain { background: var(--info-soft); color: var(--info-accented); }
.app-icon-chip-billing { background: var(--secondary-soft); color: var(--secondary-accented); }
/* Re-pointed off --accent-purple to --notice (indigo) — see the matching comment on
   .app-empty-state-icon-marketconnect above for the full reasoning (accent-purple/primary
   collision) and why --notice specifically (consistency with .app-unified-group-icon-marketconnect,
   the Home dashboard's own equivalent chip). */
.app-icon-chip-marketconnect { background: var(--notice-soft); color: var(--notice-accented); }
.app-icon-chip-neutral { background: var(--bg-muted); color: var(--text-lifted); }

/* =========================================================================
   Language/Currency chooser modal (#modalChooseLanguage, footer.tpl) — full
   redesign (2026-08-12), per explicit user instruction: "the language
   changer popup must be very innovative and matching our design." Was
   completely stock until this pass — WHMCS's own theme.min.css
   `.modal-localisation` rule painted a dark #3e3e3e panel with a literal
   `background:url(worldmap.png)` behind a plain 3-col grid of rectangular
   buttons on a generic gray "Apply". theme.min.css itself is NOT edited
   (compiled WHMCS asset, same rule this project applies to main.min.js/
   theme.min.css everywhere else) — this block's ID-scoped selectors
   (#modalChooseLanguage ...) simply out-specificity the old class-level
   `.modal-localisation` rules from theme.min.css without touching it.

   Trigger stayed put by design (header.tpl's topbar `.app-icon-btn`, moved
   there from the footer by a concurrent pass this same session) — this is a
   pure restyle of the modal itself, not its trigger. The underlying
   `item-selector`/`.item`/`data-value` contract that js/whmcs.js's existing
   delegated click handler depends on is unchanged (see footer.tpl's own
   docblock right above the markup for the full reasoning, including why
   flags were deliberately skipped in favor of a repeated globe/language
   glyph — several of the 26 real locales enabled on this install don't map
   to one country flag, and slicing native-script names to a single
   "lettermark" character in Smarty risked corrupting multi-byte UTF-8).
   ========================================================================= */

/* Backdrop is already the project-standard rgba(0,0,0,.8) sitewide via the
   `.modal-backdrop.show { opacity: 0.8; }` rule near the top of this file —
   nothing extra needed here. Entrance: `.fade` was added to this modal's own
   root class list in footer.tpl (the stock markup never had it, so the old
   modal used to just snap open/closed instantly). 2026-08-12: revised from
   fade+scale-from-center to a bottom-sheet slide (same motion, and same
   reasoning, as #modalSupportPin's .modal-dialog above and .app-domain-modal-
   dialog/.app-ticket-sheet-dialog elsewhere in this file) — dialog starts
   fully below its own resting position, reading as "slides up from off-
   screen". Drag-to-dismiss wired via js/app-shell.js's initDismissibleSheet()
   (shared helper) through initLangModalDismiss(), which also drives this same
   `transform` property live during a drag gesture — see that function's
   docblock for why onDismiss there just clicks the modal's own
   [data-dismiss="modal"] button rather than doing custom teardown. */
#modalChooseLanguage.fade .app-lang-modal-dialog {
    transform: translateY(100%);
    opacity: 0;
    transition: transform var(--duration-base) var(--ease-standard), opacity var(--duration-base) var(--ease-standard);
}
#modalChooseLanguage.show .app-lang-modal-dialog { transform: translateY(0); opacity: 1; }

#modalChooseLanguage .app-lang-modal-dialog { max-width: 480px; width: 100%; }
/* Every rule below is deliberately ID-scoped (`#modalChooseLanguage .app-lang-*`, specificity
   (1,1,0)+) rather than plain `.app-lang-*` classes — theme.min.css's own compiled
   `.modal-localisation .item-selector .item` / `.modal-content` / `.modal-footer` rules are
   TWO-AND-THREE-class combinators (specificity (0,2,0)/(0,3,0)), which would silently beat a
   single bare class selector regardless of this file's later source order (found live: the first
   version of this block used bare classes and every row rendered as the old dark
   block/border/center-text stock button, chip floating top-left, because the stock 3-class rule
   was winning on every shared property). An ID always outranks any number of classes, so prefixing
   with the modal's own `#modalChooseLanguage` guarantees this new design wins without having to
   touch theme.min.css itself. */
#modalChooseLanguage .app-lang-modal-content {
    position: relative;
    background: var(--bg);
    color: var(--text);
    border: 0;
    border-radius: var(--rounding-xl);
    box-shadow: var(--elevation-4);
    overflow: hidden;
    padding: 0;
}
#modalChooseLanguage .app-lang-modal-body { background: none; padding: 2rem 2rem 0.5rem; }

#modalChooseLanguage .app-lang-modal-close { top: 1.1rem; right: 1.1rem; z-index: 2; }

#modalChooseLanguage .app-lang-modal-header {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding-right: 2.5rem;
    margin-bottom: 1.5rem;
}
#modalChooseLanguage .app-lang-modal-header-secondary {
    border-top: 1px solid var(--border-muted);
    margin-top: 1.75rem;
    padding-top: 1.75rem;
}
#modalChooseLanguage .app-lang-modal-icon-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex: none;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary-accented);
    /* Soft outer halo (2026-08-13) — same ring-of-color-mix language as the new shared
       .app-modal-icon-chip (Support PIN modal), so both bottom-sheet modals' icon chips share one
       "glowing badge" treatment instead of the language modal's own chip being flatter. */
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--primary) 6%, transparent);
}
#modalChooseLanguage .app-lang-modal-icon-chip svg { width: 22px; height: 22px; stroke-width: 1.75; }
#modalChooseLanguage .app-lang-modal-icon-chip-secondary { background: var(--secondary-soft); color: var(--secondary-accented); }
#modalChooseLanguage .app-lang-modal-heading { padding-top: 0.15rem; }
#modalChooseLanguage .app-lang-modal-title {
    font-family: var(--font-heading);
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text);
    margin: 0 0 0.2rem;
    line-height: 1.25;
}
#modalChooseLanguage .app-lang-modal-heading .app-lang-modal-title:only-child { margin-bottom: 0; }
#modalChooseLanguage .app-lang-modal-subtitle { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }

/* Search — only rendered alongside the language list (26 real locales on this install, confirmed
   live, not assumed — genuinely needs search+scroll rather than the old flat 3-col grid). */
#modalChooseLanguage .app-lang-modal-search-wrap { position: relative; margin-bottom: 1.25rem; }
#modalChooseLanguage .app-lang-modal-search-icon {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    width: 18px;
    height: 18px;
    color: var(--text-muted);
    pointer-events: none;
}
#modalChooseLanguage .app-lang-modal-search {
    width: 100%;
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: 999px;
    color: var(--text);
    font-size: var(--text-lg);
    padding: 0.65rem 1rem 0.65rem 2.75rem;
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
#modalChooseLanguage .app-lang-modal-search::placeholder { color: var(--text-muted); }
#modalChooseLanguage .app-lang-modal-search:focus {
    outline: none;
    background: var(--bg);
    border-color: var(--primary);
    box-shadow: var(--focus-ring);
}

#modalChooseLanguage .app-lang-modal-divider { height: 1px; background: var(--border-muted); margin: 0 -2rem 0.5rem; }

/* Scrollable single-column list, replacing the stock 3-col grid of rectangular buttons. */
#modalChooseLanguage .app-lang-modal-list {
    max-height: 340px;
    overflow-y: auto;
    margin: 0 -0.5rem;
    padding: 0.25rem 0.5rem;
    scrollbar-width: thin;
    scrollbar-color: var(--border-lifted) transparent;
}
#modalChooseLanguage .app-lang-modal-list::-webkit-scrollbar { width: 8px; }
#modalChooseLanguage .app-lang-modal-list::-webkit-scrollbar-track { background: transparent; }
#modalChooseLanguage .app-lang-modal-list::-webkit-scrollbar-thumb { background: var(--border-lifted); border-radius: 999px; }
#modalChooseLanguage .app-lang-modal-list-currency { max-height: none; overflow: visible; }

#modalChooseLanguage .app-lang-row {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.6rem 0.75rem;
    margin: 2px 0;
    border: 0;
    border-radius: var(--rounding-lg);
    color: var(--text);
    text-align: left;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
#modalChooseLanguage .app-lang-row:hover, #modalChooseLanguage .app-lang-row:focus { background: var(--bg-muted); color: var(--text); text-decoration: none; }
#modalChooseLanguage .app-lang-row.active { background: var(--primary-soft); border-color: transparent; color: var(--primary-accented); }
#modalChooseLanguage .app-lang-row[hidden] { display: none; }

#modalChooseLanguage .app-lang-row-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    flex: none;
    border-radius: 999px;
    background: var(--primary-soft);
    color: var(--primary-accented);
}
#modalChooseLanguage .app-lang-row-chip svg { width: 15px; height: 15px; stroke-width: 1.75; }
#modalChooseLanguage .app-lang-row-chip-currency { font-size: var(--text-md); font-weight: 700; }
/* Color-cycle the row icon chip across 4 established brand-safe soft tokens (purple/orange/green/
   info-blue) purely as a visual anchor per row — deliberately not meaningful-per-language, just
   variety, since a flat 26-row list of identical gray circles would read monotonous. nth-of-type
   counts only same-tag (`<a>`) siblings within each list, so the leading hidden `<input>` (a
   different tag) doesn't throw off the count.
   2026-08-13 app-shell elevation pass: position 3 (4n+3) re-pointed off --accent-purple, which
   used to be a genuinely distinct 4th hue back when --primary was Vercaa's green-era primary, but
   is now a live alias to var(--primary) (see the token block's "Accent Purple — RETIRED" comment)
   — meaning it had quietly become IDENTICAL to this same rule's own DEFAULT chip color a few lines
   up (also --primary-soft/--primary-accented), so 2 of every 4 rows rendered the exact same purple
   chip and the promised "4 established... tokens" was actually only 3 in practice. Re-pointed to
   --success (green) instead, restoring 4 genuinely distinct hues — and landing on exactly the same
   4-hue family (purple/orange/green/blue) as the real vercaa.com brand tokens in market/_tokens.css,
   which is more on-brand than the 3-hue set this had silently degraded to. */
#modalChooseLanguage .app-lang-row:nth-of-type(4n+2) .app-lang-row-chip { background: var(--secondary-soft); color: var(--secondary-accented); }
#modalChooseLanguage .app-lang-row:nth-of-type(4n+3) .app-lang-row-chip { background: var(--success-soft); color: var(--success-accented); }
#modalChooseLanguage .app-lang-row:nth-of-type(4n) .app-lang-row-chip { background: var(--info-soft); color: var(--info-accented); }

#modalChooseLanguage .app-lang-row-name { flex: 1 1 auto; font-weight: 600; font-size: var(--text-lg); }
#modalChooseLanguage .app-lang-row-check {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    flex: none;
    color: var(--primary-accented);
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-standard);
}
#modalChooseLanguage .app-lang-row.active .app-lang-row-check { opacity: 1; }

#modalChooseLanguage .app-lang-modal-empty { text-align: center; color: var(--text-lifted); padding: 1.5rem 0.5rem; margin: 0; }

#modalChooseLanguage .app-lang-modal-footer {
    border: 0;
    border-top: 1px solid var(--border-muted);
    padding: 1.25rem 2rem 1.75rem;
    display: flex;
    justify-content: flex-end;
}
#modalChooseLanguage .app-lang-modal-apply { padding: 0.65rem 1.5rem; }

@media (max-width: 480px) {
    #modalChooseLanguage .app-lang-modal-body { padding: 1.75rem 1.25rem 0.5rem; }
    #modalChooseLanguage .app-lang-modal-divider { margin: 0 -1.25rem 0.5rem; }
    #modalChooseLanguage .app-lang-modal-footer { padding: 1.1rem 1.25rem 1.4rem; }
    #modalChooseLanguage .app-lang-modal-list { max-height: 300px; }
}

@media (prefers-reduced-motion: reduce) {
    #modalChooseLanguage.fade .app-lang-modal-dialog { transition-duration: 0.01ms !important; }
}

/* =========================================================================
   Icon consistency + color pass (2026-08-12), continued — Home dashboard
   (clientareahome.tpl) icon-font -> inline SVG sizing. Every rule below is a
   NEW sibling selector for an SVG replacing what used to be a bare <i>
   FontAwesome glyph (sized implicitly via font-size/line-height); SVG needs
   explicit pixel dimensions since it doesn't inherit type sizing the same
   way, so each existing icon-holder class gets a matching `svg` sizing rule
   here rather than in place, to keep this a clean append.
   ========================================================================= */
.app-manage-all-link svg { width: 12px; height: 12px; flex-shrink: 0; }
.app-ticket-row-icon svg { width: 16px; height: 16px; }
.app-ticket-row-arrow svg { width: 14px; height: 14px; }
.app-quicklink-icon svg { width: 17px; height: 17px; }
.app-quicklink-arrow svg { width: 14px; height: 14px; }
.app-domain-upsell-field svg { width: 18px; height: 18px; color: var(--text-lifted); flex-shrink: 0; }
.app-domain-upsell-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.app-welcome-stat-icon svg { width: 17px; height: 17px; }

/* .app-card-title-chip: the same .app-icon-chip "colored chip" component (defined earlier in this
   file, Part C of the empty-state color pass) used INLINE inside a <h3 class="card-title"> next to
   plain text, e.g. clientareahome.tpl's "Products & Services" / "Support Tickets" / "Help Centre"
   card headers. .app-icon-chip's default 32px/18px sizing was tuned for a standalone block context;
   this modifier trims it slightly and adds the vertical-align + spacing needed to sit correctly on
   a text baseline next to an <h3>'s own font-size. */
.app-card-title-chip {
    width: 28px;
    height: 28px;
    vertical-align: middle;
    margin-right: 0.5rem;
    margin-top: -0.2rem;
}
.app-card-title-chip svg { width: 16px; height: 16px; }

/* =========================================================================
   Dark-mode fix: intl-tel-input's phone-number country dropdown
   =========================================================================
   Found during a sitewide dark-mode "white patch" audit: Account Details'
   phone-number field (and any other tel input using the same plugin) opens
   a country picker whose vendored CSS (`.country-list` in all.min.css) is
   entirely hardcoded light: `background-color:#fff` + `border:1px solid
   #ccc`, no !important and no dark-mode override anywhere. Because this
   theme's own global text-color rules already paint `.country`/list text
   near-white (matching the rest of the dark UI), the un-fixed result was
   WORSE than a plain white box — it was low-contrast near-white-on-white,
   verified live via getComputedStyle (`.country-list` bg `rgb(255,255,255)`,
   `.country` text `rgb(245,243,248)`) before this fix. No !important needed
   on the source rule, so plain specificity/order is enough here — same
   `var(--bg)` token already used for `.app-dropdown .dropdown-menu` above,
   for a consistent floating-menu surface across the site. */
:root[data-theme="dark"] .country-list {
    background: var(--bg);
    border-color: var(--border);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
:root[data-theme="dark"] .country-list .country { color: var(--text); }
:root[data-theme="dark"] .country-list .country .dial-code { color: var(--text-lifted); }
:root[data-theme="dark"] .country-list .country.highlight,
:root[data-theme="dark"] .country-list .country:hover {
    background-color: var(--bg-muted);
}
:root[data-theme="dark"] .country-list .divider { border-bottom-color: var(--border); }

/* Domain search modal — pill-shaped focus fix — MOVED (2026-08-13 domain-search unification
   pass, not duplicated) to css/domain-search-modal.css (.app-domain-modal-search-field:focus-
   within), alongside the rest of the search bar's own CSS. See that file's own header comment. */

/* =========================================================================
   Dark-mode fix: MarketConnect "Recommended for you" promo carousel icon chip
   =========================================================================
   Found during a sitewide dark-mode "white patch" audit (clientareahome.tpl,
   scrolled to the bottom "Recommended for you" widget). `.promo-container
   .icon-left` (declared ~line 3091, own build pass this session) sets
   `background: var(--white)` UNCONDITIONALLY — same value in both themes —
   to give WHMCS's vendor-supplied icon PNGs (e.g. assets/img/marketconnect/
   symantec/ssl-subs.png, transparent-background icon art, not full logos)
   a light circular chip to sit on. In light mode this reads as a subtle
   chip; in dark mode it's a stark solid-white 4.5rem circle floating in an
   otherwise near-black card — confirmed live via getComputedStyle
   (backgroundColor: rgb(255,255,255)) and visually, a textbook instance of
   this project's known "white patch" bug class. Rather than force every
   vendor's icon art dark (risking illegible icons for vendors whose PNGs
   assume a light backdrop), swap the chip itself to this project's own
   "one step up from the page" dark surface token — same convention already
   used for cards sitting on the dark page background sitewide (HANDOFF.md
   §6, "dark mode fidelity fix") — so the chip still reads as a deliberate
   badge, not an unthemed leftover. Light mode is completely untouched. */
:root[data-theme="dark"] .promo-container .icon-left {
    background: var(--bg-lifted);
    box-shadow: 0 0 0 5px rgba(94, 194, 105, 0.14), 0 8px 24px -10px rgba(94, 194, 105, 0.55);
}

/* =========================================================================
   Dark-mode fix: DataTables search/filter input (e.g. Email History,
   ticket-adjacent lists that still use the stock DataTables widget)
   =========================================================================
   Found during a sitewide dark-mode "white patch" audit: the generic
   `:root[data-theme="dark"] .form-control` rule (~line 1278) already covers
   the DataTables "Show N entries" <select> correctly (confirmed live), but
   NOT the adjacent "Search:" <input> — both share the `.form-control` class,
   but theme.min.css's own selector for the search box specifically is
   `body .dataTables_wrapper .dataTables_filter label .form-control` (also
   carrying a `background:url(../../../assets/img/search.png) #fff …`
   declaration, no !important) — its specificity (0,3,2) beats the generic
   dark rule's (0,3,0) on the tied class/attribute count once the extra
   `body`/`label` element selectors are counted, so the input stayed a solid
   white box with a barely-visible magnifying-glass icon baked into it.
   Matching the exact stock selector chain (with the dark-theme root
   prepended) restores the correct win. The background image is dropped
   (shorthand reset) rather than reproduced — theme.min.css already draws
   its own separate search icon via `.dataTables_filter::after` (a Font
   Awesome glyph, already themed), so the baked-in PNG was redundant. */
:root[data-theme="dark"] body .dataTables_wrapper .dataTables_filter label .form-control {
    background: var(--bg-muted);
    color: var(--text);
    border-color: var(--border);
}
:root[data-theme="dark"] body .dataTables_wrapper .dataTables_filter label .form-control::placeholder {
    color: var(--text-muted);
}

/* =========================================================================
   Dark-mode fix: bare Bootstrap `.custom-select` (no `.form-control` class)
   =========================================================================
   Found during a sitewide dark-mode "white patch" audit: vendor/product
   config pages that are separate top-level scripts outside the main
   templates/vercaa-2026/*.tpl set — e.g. configuressl.php's "Server Type"
   dropdown (Configure Now flow off a Market Connect SSL product's detail
   page) — still render Bootstrap's stock `.custom-select`, which is a
   DIFFERENT class from plain `.form-control` and was never covered by the
   existing `:root[data-theme="dark"] .form-control` rule (~line 1278):
   that rule only matches elements actually carrying the `.form-control`
   class, and this element carries `.custom-select` alone. Confirmed live
   via getComputedStyle — background stayed `rgb(255,255,255)` with
   `data-theme="dark"` correctly set on :root, i.e. a real gap, not a
   specificity fight like the DataTables filter fix above. Only
   `background-color`/`color`/`border-color` are touched (not the `background`
   shorthand) so `.custom-select`'s own baked-in chevron-arrow SVG
   (`background-image`, a separate sub-property) survives untouched — same
   technique already used for `.form-control` so the two rules compose
   correctly wherever an element carries both classes (e.g. the DataTables
   "Show N entries" select). */
:root[data-theme="dark"] .custom-select {
    background-color: var(--bg-muted);
    color: var(--text);
    border-color: var(--border);
}
:root[data-theme="dark"] .custom-select:focus {
    background-color: var(--bg-muted);
    color: var(--text);
}
:root[data-theme="dark"] .custom-select:disabled {
    background-color: var(--bg-muted);
    color: var(--text-muted);
}

/* -------------------------------------------------------------------------
   Notifications panel — read-state muting (2026-08-12, notif read/unread
   persistence feature — see includes/hooks/NotificationReadState.php +
   notification-mark-read.php). Reuses the already-established .app-notif-item
   (line ~538) / .app-notif-icon-* (lines ~547-550) classes verbatim; this is
   purely an opacity tweak, no new colors introduced. */
.app-notif-item.is-read {
    opacity: 0.6;
}

/* =========================================================================
   Shared drag-to-dismiss handle for Bootstrap-driven modals (2026-08-12) —
   #modalSupportPin (footer.tpl) and #modalChooseLanguage (footer.tpl). Extends
   the same drag-to-dismiss "bottom sheet" mechanic already proven on the
   domain-search modal/ticket sheet (js/app-shell.js's initDismissibleSheet(),
   see .app-domain-modal-drag-handle ~line 4600 / .app-ticket-sheet-drag-handle
   ~line 6330) to these two popups, wired up by initSupportPinDismiss() /
   initLangModalDismiss() respectively. Those two popups are fully custom
   (own backdrop + `.is-open` markup, one handle class per popup); these two
   are real Bootstrap `.modal.fade` markup instead, and both share the exact
   same visual chrome (a `.modal-content` card with an absolutely-positioned
   `.app-modal-close` in the corner — see ~line 800), so ONE shared class
   here (rather than a modal-by-modal one-off) mirrors the same "generic
   `.app-modal-*` base class reused across both modals" convention already
   established by `.app-modal-close`/`.app-modal-title`/`.app-modal-subtitle`
   above, instead of inventing a new visual style. Declarations copied
   verbatim from .app-domain-modal-drag-handle so the handle looks and
   behaves identically everywhere it appears. touch-action:none stops the
   browser's own scroll/refresh gesture from fighting the JS-driven drag on
   touch devices; cursor:grab/:active:grabbing signal the affordance on
   desktop too. Both modals' `.modal-content` is flex-column (Bootstrap's own
   stock `.modal-content` rule), so this first child centers itself via
   margin:auto on the cross axis with no extra positioning needed — same as
   the reference implementation. */
.app-modal-drag-handle {
    width: 40px;
    height: 4px;
    border-radius: 999px;
    background: var(--border);
    margin: 0.85rem auto 0;
    flex-shrink: 0;
    touch-action: none;
    cursor: grab;
}

/* =========================================================================
   Ticket status "pill" legibility fix (2026-08-12, dark-mode color audit,
   root cause #2) — two surfaces render a ticket's status as WHMCS core's own
   raw inline-styled markup, unlike supportticketslist.tpl (which already
   strips that same markup via {$ticket.status|strip_tags} and re-renders it
   through the app's own token-driven .app-status-badge-* system, hence
   correct there): the Home dashboard's "Recent Support Tickets" quick-list
   (clientareahome.tpl, `<label class="label" style="background-color:...">`
   inside .app-ticket-row-body) and viewticket.php's "Ticket Information"
   sidebar (`<span class="label" style="background-color:...">` inside
   .list-group-item.ticket-details-children). Confirmed live: the "Answered"
   status's core-supplied color is literally #000000 — an invisible black
   pill on the near-black dark-mode card, only the white text survives.
   "Open" resolves to #779500 (visually fine as a shape, just not brand-
   themed). Since this is raw core HTML (not a Smarty field with a usable
   statusClass in these two contexts), it can't be re-mapped to the correct
   per-status warning/info/error badge color the list page uses without
   editing a core-rendered string, which is off-limits — instead every
   status pill in both contexts is forced onto ONE consistent, always-
   legible neutral chip built from real Vercaa tokens (never any of the raw
   inline colors WHMCS supplies), which is automatically correct in both
   themes and can't regress to invisible again no matter what color a status
   is configured with in the future. !important is required to beat the
   inline style="" WHMCS emits. */
.app-ticket-row-body .label,
.ticket-details-children .label {
    background: var(--bg-accented) !important;
    color: var(--text) !important;
    border: 1px solid var(--border-lifted);
}

/* =========================================================================
   Password-strength meter (2026-08-12, dark-mode color audit, root cause #3)
   — Account > Your Profile > Change Password (index.php?rp=/user/password),
   templates/vercaa-2026/includes/pwstrength.tpl + WHMCS core's
   assets/js/PasswordStrength.js. The meter is stock unthemed Bootstrap: the
   outer #passwordStrengthBar.progress track is hardcoded rgb(233,236,239)
   (near-white) with no dark-mode override anywhere, so it renders as a stark
   light-gray bar across the near-black page regardless of password state;
   the fill itself cycles stock Bootstrap utility colors (.bg-danger
   rgb(220,53,69) / .bg-warning rgb(255,193,7) / .bg-success rgb(40,167,69))
   that never traced to a Vercaa token at all, confirmed live across all 3
   strength states (weak/medium/strong), not just the "strong" case that
   happened to render green in the original test. Scoped to
   #passwordStrengthBar specifically (not a global `.progress`/`.bg-success`
   override) since those are generic Bootstrap utility classes reused
   elsewhere in stock WHMCS markup this pass didn't audit. */
#passwordStrengthBar.progress {
    background: var(--bg-accented);
}
#passwordStrengthBar .progress-bar.bg-danger { background-color: var(--error) !important; }
#passwordStrengthBar .progress-bar.bg-warning { background-color: var(--warning) !important; }
#passwordStrengthBar .progress-bar.bg-success { background-color: var(--success) !important; }
.app-modal-drag-handle:active { cursor: grabbing; }

/* =========================================================================
   MARKET CONNECT STOREFRONT REDESIGN (2026-08-12)
   Scope: templates/vercaa-2026/store/{ssl,threesixtymonitoring,nordvpn,
   spamexperts,ox,codeguard,sitelock}/index.tpl (+ shared/*.tpl partials) —
   the 8 `/store/*` category pages. These ship 100%-stock WHMCS "Storefront"
   markup, styled entirely by the un-forked core css/store.css (WHMCS
   Twenty-One Theme stylesheet, loaded via {assetPath file='store.css'} in
   each index.tpl). This block reskins them purely via cascade (loaded AFTER
   store.css per includes/head.tpl) — store.css itself is never edited, same
   "override via custom.css, don't fork the compiled/stock asset" approach
   already used for theme.min.css elsewhere in this file. Every color below
   resolves to an existing Vercaa token (--primary/--secondary/--accent-
   purple/neutrals) — hosting.com's live product/pricing pages were
   researched for STRUCTURE, LAYOUT and MOTION patterns only (hero
   composition, tier-card layout, pricing-toggle mechanics, collapsible
   comparison tables, ribbon/badge treatment) per HARD RULE #3 — no color
   value from that research appears anywhere in this block.

   Root cause of the pre-existing dark-mode bug fixed here: store.css sets
   `.landing-page .hero { color: #333 }` with no background of its own, so
   it inherits `.landing-page.bg-white`'s Bootstrap utility — which this
   file's dark-mode pass correctly flips to var(--bg) (near-black) for every
   OTHER page using .bg-white, stranding the hero's hardcoded #333 text on a
   now-black surface. Fix strategy used throughout this whole block, not
   just the hero: (1) the hero/header band on every vendor page gets a
   fixed, always-dark, on-brand gradient with always-light text — same
   "doesn't invert with the toggle" convention as includes/gradient-
   banner.tpl (brand doc §12 "Dark-mode conventions") — legible in both
   themes by construction; (2) every card/panel/pricing surface below the
   hero (tier cards, plan panels, accordion bodies, FAQ blocks) is retinted
   from store.css's hardcoded light hex values onto var(--bg-lifted)/
   var(--bg-muted)/var(--border)/var(--text) tokens so it auto-adapts with
   the theme toggle exactly like the rest of the app's card system, instead
   of staying a frozen light rectangle. Brand glows use color-mix() against
   the real --primary/--secondary/--accent-purple custom properties, so
   there is no new hex literal introduced anywhere in this block.
   ========================================================================= */

/* -----------------------------------------------------------------------
   0. Base rhythm — Poppins headings (brand doc §12), consistent link color,
   remove store.css's assumption that the page background is always white.
   ----------------------------------------------------------------------- */
.landing-page {
    background: var(--bg) !important;
    color: var(--text);
}
.landing-page h1,
.landing-page h2,
.landing-page h3,
.landing-page h4,
.landing-page h5,
.landing-page h6 {
    font-family: var(--font-heading);
    letter-spacing: -0.01em;
}
.landing-page a { color: var(--primary-accented); }
.landing-page a:hover { color: var(--primary); }
.landing-page .container { position: relative; }

/* -----------------------------------------------------------------------
   1. Hero / header band — ssl, codeguard, sitelock, mail-services (spam-
   experts), nordvpn share the literal `.hero` class; ox uses `.header`;
   threesixtymonitoring uses `.section-header` with its own inline
   background-image (overridden with !important below, the only place in
   this block that needs it, since inline style="" otherwise wins). Fixed
   dark gradient + light text, permanently, in both themes — see file-level
   note above. Layered radial glows built from color-mix() against our own
   tokens give the "layered, breaking-the-frame" liveliness hosting.com's
   hero composition has, without introducing any new color or copying a
   literal hosting.com value.
   ----------------------------------------------------------------------- */
.landing-page .hero,
.landing-page.ox .header,
.landing-page.threesixtymonitoring .section-header {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: 4.5rem 0 5rem !important;
    margin: 0;
    min-height: 0;
    text-align: center;
    color: #fbfaff;
    background:
        radial-gradient(52% 80% at 10% 4%, color-mix(in srgb, var(--primary) 40%, transparent) 0%, transparent 72%),
        radial-gradient(48% 70% at 96% 102%, color-mix(in srgb, var(--secondary) 34%, transparent) 0%, transparent 70%),
        radial-gradient(38% 55% at 80% -8%, color-mix(in srgb, var(--accent-purple) 32%, transparent) 0%, transparent 68%),
        linear-gradient(160deg, var(--neutral-950) 0%, #171522 55%, var(--neutral-900) 100%) !important;
    background-image:
        radial-gradient(52% 80% at 10% 4%, color-mix(in srgb, var(--primary) 40%, transparent) 0%, transparent 72%),
        radial-gradient(48% 70% at 96% 102%, color-mix(in srgb, var(--secondary) 34%, transparent) 0%, transparent 70%),
        radial-gradient(38% 55% at 80% -8%, color-mix(in srgb, var(--accent-purple) 32%, transparent) 0%, transparent 68%),
        linear-gradient(160deg, var(--neutral-950) 0%, #171522 55%, var(--neutral-900) 100%) !important;
}
.landing-page .hero::before,
.landing-page.ox .header::before,
.landing-page.threesixtymonitoring .section-header::before {
    content: "";
    position: absolute;
    inset: -20% -10% auto -10%;
    height: 60%;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--primary) 55%, transparent), transparent);
    filter: blur(60px);
    opacity: 0.55;
    z-index: 0;
    pointer-events: none;
}
.landing-page .hero::after,
.landing-page.ox .header::after,
.landing-page.threesixtymonitoring .section-header::after {
    content: "";
    position: absolute;
    inset: auto -15% -30% auto;
    width: 45%;
    height: 70%;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--secondary) 45%, transparent), transparent);
    filter: blur(70px);
    opacity: 0.45;
    z-index: 0;
    pointer-events: none;
}
.landing-page .hero > *,
.landing-page.ox .header > *,
.landing-page.threesixtymonitoring .section-header > * { position: relative; z-index: 1; }
.landing-page .hero h1,
.landing-page .hero h2,
.landing-page .hero h3,
.landing-page.ox .header h1,
.landing-page.threesixtymonitoring .section-header h1,
.landing-page.threesixtymonitoring .section-header h2 {
    color: #fff !important;
    animation: storeHeroIn 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.landing-page .hero h2 { font-size: clamp(2rem, 4vw, 2.9em); font-weight: 700; }
.landing-page .hero h3 { font-weight: 400; color: rgba(255,255,255,0.82) !important; animation-delay: 0.08s; }
.landing-page .hero p,
.landing-page .hero span,
.landing-page.ox .header p,
.landing-page.threesixtymonitoring .section-header p { color: rgba(255,255,255,0.82); }
.landing-page .hero .guarantee { color: rgba(255,255,255,0.65) !important; }
@keyframes storeHeroIn {
    from { opacity: 0; transform: translateY(14px); }
    to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .landing-page .hero h1, .landing-page .hero h2, .landing-page .hero h3,
    .landing-page.ox .header h1, .landing-page.threesixtymonitoring .section-header h1,
    .landing-page.threesixtymonitoring .section-header h2 { animation: none; }
}

/* Hero primary CTA — every vendor's stock hero/pricing "get started" button
   (mixed btn-default/btn-success/btn-primary/vendor classes) unified onto
   the app's own gradient-CTA language (brand doc §12/§13). */
.landing-page .hero .btn,
.landing-page .hero button.btn,
.landing-page.ox .header .btn,
.landing-page.threesixtymonitoring .section-header .btn-search,
.landing-page.threesixtymonitoring .tab-website .btn-primary,
.landing-page.threesixtymonitoring .tab-server .btn-primary {
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-lifted) 100%) !important;
    border: 0 !important;
    color: #fff !important;
    font-weight: 700;
    border-radius: var(--rounding-md);
    box-shadow: 0 8px 20px color-mix(in srgb, var(--primary) 35%, transparent);
    transition: transform 200ms cubic-bezier(0.4,0,0.2,1), box-shadow 200ms cubic-bezier(0.4,0,0.2,1), filter 200ms;
}
.landing-page .hero .btn:hover,
.landing-page.ox .header .btn:hover,
.landing-page.threesixtymonitoring .section-header .btn-search:hover {
    transform: translateY(-2px);
    filter: brightness(1.04);
    box-shadow: 0 12px 28px color-mix(in srgb, var(--primary) 45%, transparent);
    color: #fff !important;
}
.landing-page .hero .logo-container img { filter: drop-shadow(0 8px 24px rgba(0,0,0,0.35)); }

/* threesixtymonitoring's site-check tool + probe select — the one hero on
   these 8 pages with a genuine embedded lead-gen widget; give it a
   frosted-glass "floating card" treatment consistent with the hosting.com-
   style layered-card hero motif (structural pattern only, no copied color). */
.landing-page.threesixtymonitoring .tab-website {
    background: rgba(255,255,255,0.07);
    border: 1px solid rgba(255,255,255,0.14);
    border-radius: var(--rounding-xl);
    padding: 1.75rem 1.75rem 1.5rem;
    backdrop-filter: blur(16px);
    box-shadow: var(--elevation-3);
}
.landing-page.threesixtymonitoring .tab-website .form-control {
    background: rgba(255,255,255,0.92);
    border: 0;
    border-radius: var(--rounding-md);
}

/* NordVPN hero keeps its own left-aligned two-column layout (image column),
   just inherits the new dark-gradient surface + always-light text above. */
.landing-page.nordvpn .hero .available .divider .line { background: rgba(255,255,255,0.25); }
.landing-page.nordvpn .hero .available img { filter: none; opacity: 0.9; }


/* -----------------------------------------------------------------------
   2. Sub-navigation → pill-tab bar (build reference §13 pattern #7, same
   visual language as includes/pill-tabs.tpl). Covers ssl/codeguard/
   sitelock/mail-services's identical bare-Bootstrap `nav.navbar.navbar-
   light.bg-light` markup, plus threesixtymonitoring's own `.nav-pills`.
   Sticky under the header for quick jump-to-section navigation — a modern,
   low-risk touch (position:sticky, no JS required).
   ----------------------------------------------------------------------- */
.landing-page nav.navbar.navbar-light {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg) !important;
    border-bottom: 1px solid var(--border-muted);
    padding: 0.6rem 0 !important;
}
.landing-page nav.navbar.navbar-light .navbar-nav { gap: 0.35rem; }
.landing-page nav.navbar.navbar-light .navbar-nav > li > a,
.landing-page nav.navbar.navbar-light .nav-link {
    border: 0 !important;
    border-radius: 999px !important;
    color: var(--text-lifted) !important;
    font-weight: 600;
    padding: 0.5rem 1.1rem !important;
    transition: background 200ms cubic-bezier(0,0,0.2,1), color 200ms;
}
.landing-page nav.navbar.navbar-light .navbar-nav > li > a:hover,
.landing-page nav.navbar.navbar-light .nav-link:hover {
    background: var(--bg-muted);
    color: var(--text) !important;
}
.landing-page nav.navbar.navbar-light .navbar-nav > li.active > a {
    background: var(--primary-soft) !important;
    color: var(--primary-accented) !important;
}
.landing-page .navbar-toggler { border-color: var(--border); color: var(--text); }

.landing-page.threesixtymonitoring .nav-primary,
.landing-page.threesixtymonitoring .nav-secondary {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg);
    border-bottom: 1px solid var(--border-muted);
}
.landing-page.threesixtymonitoring .nav-primary .nav-link,
.landing-page.threesixtymonitoring .nav-secondary .nav-link {
    color: var(--text-lifted) !important;
    font-weight: 600;
    border-radius: 999px;
    transition: background 200ms, color 200ms;
}
.landing-page.threesixtymonitoring .nav-primary .nav-link.active,
.landing-page.threesixtymonitoring .nav-secondary .nav-link.active {
    background: var(--primary-soft) !important;
    color: var(--primary-accented) !important;
}

/* -----------------------------------------------------------------------
   3. Generic content sections — .content-block (universal wrapper on ssl/
   sitelock/codeguard/mail-services), .standout / .light-grey-bg (tinted
   panel variants), .image-standout (ssl "What is SSL?"). Re-tinted onto
   theme tokens instead of store.css's hardcoded #f6f6f6/#f8f8f8 so they
   auto-adapt with the toggle, and given real vertical rhythm + Poppins
   headings so the page reads as one design system instead of a stock
   marketing dump.
   ----------------------------------------------------------------------- */
.landing-page .content-block { padding: 3.25rem 0; }
.landing-page .content-block h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 700; margin-bottom: 0.75rem; }
.landing-page .content-block h3 { font-weight: 500; color: var(--text-lifted); }
.landing-page .content-block p { color: var(--text-lifted); line-height: 1.7; }
.landing-page .standout,
.landing-page .light-grey-bg,
.landing-page .image-standout,
.landing-page.ssl .standout {
    background: var(--bg-muted) !important;
    color: var(--text) !important;
}
.landing-page .standout h2, .landing-page .light-grey-bg h2, .landing-page.ssl .standout h2 { color: var(--text); }
.landing-page ul li { color: var(--text-lifted); }
.landing-page .content-block ul li i.fa-check-circle,
.landing-page .content-block ul li .fas.fa-check-circle {
    color: var(--primary);
    margin-right: 0.4rem;
}

/* -----------------------------------------------------------------------
   4. Buttons / CTAs — every vendor's mixed btn-default / btn-success /
   vendor-specific class (.order-btn, .btn-buy, .btn-search, .btn-pricing-
   scroll, .btn-learn-more, .btn-get-*) unified onto one consistent
   Vercaa CTA language: solid primary-green fill for the main action,
   outline for the secondary action, both with the app's own hover-lift.
   ----------------------------------------------------------------------- */
.landing-page .btn-default,
.landing-page .btn-success,
.landing-page .order-btn,
.landing-page .btn-buy,
.landing-page .btn-pricing-scroll,
.landing-page .row-pricing-table .btn,
.landing-page .plan .btn,
.landing-page .validation-levels .btn,
.landing-page .certificate-options .btn {
    background: var(--primary) !important;
    border: 0 !important;
    color: #fff !important;
    font-weight: 700;
    border-radius: var(--rounding-md) !important;
    transition: transform 200ms cubic-bezier(0.4,0,0.2,1), filter 200ms, box-shadow 200ms;
}
.landing-page .btn-default:hover,
.landing-page .btn-success:hover,
.landing-page .order-btn:hover,
.landing-page .btn-buy:hover,
.landing-page .btn-pricing-scroll:hover,
.landing-page .row-pricing-table .btn:hover,
.landing-page .plan .btn:hover {
    filter: brightness(1.05);
    transform: translateY(-1px);
    color: #fff !important;
}
.landing-page .btn-learn-more {
    background: transparent !important;
    border: 1.5px solid var(--border-lifted) !important;
    color: var(--text) !important;
    border-radius: var(--rounding-md) !important;
    font-weight: 600;
}
.landing-page .btn-learn-more:hover { border-color: var(--primary) !important; color: var(--primary-accented) !important; }


/* -----------------------------------------------------------------------
   5. Tier / pricing cards — SSL's 3-card "Choose your level of security"
   (.validation-levels .item), SiteLock's .plan cards, mail-services'
   .product-options .item, SSL's certificate-pricing .row-pricing-table
   columns. Rebuilt as the app's own elevated-card system (rounded-xl,
   --elevation shadow, hover lift, accent top rule) — the hosting.com-
   researched "tiered pricing card row" structural pattern, translated to
   Vercaa tokens. Cards use var(--bg-lifted) instead of store.css's
   hardcoded #fff so they read correctly in dark mode automatically.
   ----------------------------------------------------------------------- */
.landing-page.ssl .validation-levels { padding: 3rem 0 1rem; }
.landing-page.ssl .validation-levels .item,
.landing-page.sitelock .plan,
.landing-page.mail-services .product-options .item,
.landing-page .product-options .item {
    background: var(--bg-lifted) !important;
    color: var(--text) !important;
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-xl) !important;
    box-shadow: var(--elevation-2);
    transition: transform 220ms cubic-bezier(0.4,0,0.2,1), box-shadow 220ms cubic-bezier(0.4,0,0.2,1);
}
.landing-page.ssl .validation-levels .item:hover,
.landing-page.sitelock .plan:hover,
.landing-page .product-options .item:hover {
    transform: translateY(-4px);
    box-shadow: var(--elevation-4);
}
.landing-page.ssl .validation-levels .item h4,
.landing-page.sitelock .plan .header h4,
.landing-page .product-options .item h4 { color: var(--text); }
.landing-page.ssl .validation-levels .item p.first,
.landing-page.ssl .validation-levels .item p.second,
.landing-page.sitelock .plan p,
.landing-page .product-options .item p,
.landing-page .product-options .item span { color: var(--text-lifted) !important; }
.landing-page.ssl .validation-levels .item .btn {
    background: var(--primary) !important;
    color: #fff !important;
    width: auto;
    padding: 0.5rem 1.5rem;
}
.landing-page.sitelock .plan .header { border-bottom: 1px solid var(--border-muted); padding-bottom: 0.85rem; margin-bottom: 0.5rem; }
.landing-page.sitelock .plan ul li { border-color: var(--border-muted) !important; color: var(--text-lifted); }
.landing-page.sitelock .plan .footer { border-top: 1px solid var(--border-muted); }

/* "Recommended" ribbon (SSL's .popular-plan, isFeatured items) → the app's
   accent-purple promo language (brand doc §12 "purple = promo/discount
   accent") replacing store.css's hardcoded orange gradient ribbon. */
.landing-page.ssl .popular-plan .plan-container .txt-container {
    background: linear-gradient(135deg, var(--accent-purple) 0%, var(--accent-purple-lifted) 100%) !important;
    box-shadow: 0 6px 16px color-mix(in srgb, var(--accent-purple) 40%, transparent);
    border-radius: var(--rounding-sm) var(--rounding-sm) 0 0;
    letter-spacing: 0.06em;
}
.landing-page.ssl .popular-plan .plan-container:before,
.landing-page.ssl .popular-plan .plan-container:after {
    border-color: var(--accent-purple-accented) transparent transparent transparent !important;
}
.landing-page.ssl ul.ssl-certs-all li.featured {
    background: var(--accent-purple-soft) !important;
    border: 1px solid color-mix(in srgb, var(--accent-purple) 35%, transparent);
}
.landing-page.ssl ul.ssl-certs-all li { border-bottom-color: var(--border-muted) !important; }
.landing-page.ssl ul.ssl-certs-all .price span { color: var(--text-muted) !important; }
.landing-page.ssl ul.ssl-certs-all h4, .landing-page.ssl ul.ssl-certs-all p { color: var(--text); }

/* SSL detailed pricing table (dv/ov/ev/wildcard sub-pages, certificate-
   pricing.tpl) — column headers + featured column highlight. */
.landing-page.ssl .certificate-options {
    background: linear-gradient(135deg, var(--primary-accented) 0%, var(--primary) 100%) !important;
    color: #fff !important;
    border-radius: var(--rounding-xl);
    padding: 2.5rem 0;
}
.landing-page.ssl .certificate-options.ov { background: linear-gradient(135deg, var(--secondary-accented) 0%, var(--secondary) 100%) !important; }
.landing-page.ssl .certificate-options.ev { background: linear-gradient(135deg, var(--accent-purple-accented) 0%, var(--accent-purple) 100%) !important; }
.landing-page.ssl .row-pricing-table > div:not(.sidebar) {
    background: rgba(255,255,255,0.97);
    border-radius: var(--rounding-lg);
    padding: 1rem 0.75rem 1.25rem;
    color: #1f2937;
}
.landing-page.ssl .row-pricing-table .sidebar ul li { color: rgba(255,255,255,0.85); }
.landing-page.ssl .row-pricing-table .header h4 { color: #1f2937; }

/* CodeGuard's ion-rangeSlider "pick your backup plan" price readout. */
.landing-page.codeguard .price-calc-container {
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-xl);
    padding: 2rem 1.75rem;
    box-shadow: var(--elevation-2);
}
.landing-page.codeguard .pricing-container .price { color: var(--primary-accented); font-weight: 800; font-size: 2.2rem; }
.landing-page.codeguard #pricingCycle { color: var(--text-lifted); }
.landing-page.codeguard .strong-green,
.landing-page.codeguard .strong-green span,
.landing-page.codeguard h3 { color: var(--primary-accented) !important; }
.landing-page.codeguard .order-btn { background: var(--primary) !important; }

/* OX pricing comparison table + header/stand-out band color debleed. */
.landing-page.ox .stand-out { background: var(--bg-muted) !important; color: var(--text) !important; }
.landing-page.ox .stand-out h2 { color: var(--text) !important; }
.landing-page.ox h3 { color: var(--primary-accented) !important; }
.landing-page.ox .pricing table { color: var(--text) !important; }
.landing-page.ox .pricing th { background: var(--primary) !important; color: #fff !important; }
.landing-page.ox .pricing th.feature { background: var(--secondary) !important; }
.landing-page.ox .pricing td:not(.no-border) { border-color: var(--border-muted) !important; }
.landing-page.ox .pricing table tr:nth-child(even) td:first-child { background: var(--bg-muted) !important; }
.landing-page.ox .faqs { background: var(--bg-muted) !important; }


/* -----------------------------------------------------------------------
   6. Accordion / FAQ — SSL's "Help Me Choose / View All / Multi-Year /
   FAQ" accordion (.detailed-info .card), NordVPN's FAQ accordion,
   SiteLock's FAQ accordion, CodeGuard's static FAQ grid. Chevron-rotate +
   smooth-open motion (build reference §13's recommended 150-200ms recipe),
   active-item accent rule — replacing store.css's flat blue #327aa7 header
   bar with the token system, and finally making the collapse feel like an
   intentional interaction instead of a bare Bootstrap default.
   ----------------------------------------------------------------------- */
.landing-page .detailed-info,
.landing-page.sitelock .content-block.faq,
.landing-page.nordvpn .feature.faq {
    background: var(--bg) !important;
}
.landing-page .detailed-info .card,
.landing-page.sitelock .content-block.faq .card,
.landing-page.nordvpn .accordion .card {
    background: var(--bg-lifted) !important;
    border: 1px solid var(--border-muted) !important;
    border-radius: var(--rounding-lg) !important;
    margin-bottom: 0.85rem;
    overflow: hidden;
    box-shadow: var(--elevation-1);
}
.landing-page .detailed-info .card-header,
.landing-page.sitelock .content-block.faq .card-header,
.landing-page.nordvpn .accordion .card-header {
    background: transparent !important;
    color: var(--text) !important;
    border-radius: 0 !important;
    padding: 1rem 1.35rem;
    cursor: pointer;
    transition: background 200ms;
}
.landing-page .detailed-info .card-header:hover,
.landing-page.sitelock .content-block.faq .card-header:hover,
.landing-page.nordvpn .accordion .card-header:hover { background: var(--bg-muted) !important; }
.landing-page .detailed-info .card-header h4,
.landing-page.sitelock .content-block.faq .card-header h4,
.landing-page.nordvpn .accordion .card-header h2 { color: var(--text) !important; font-size: 1.05rem; font-weight: 700; margin: 0; }
.landing-page .detailed-info .card-header .arrow i,
.landing-page .detailed-info .card-header i.fas,
.landing-page.nordvpn .accordion .card-header i {
    color: var(--primary);
    transition: transform 220ms cubic-bezier(0.4,0,0.2,1);
}
.landing-page .detailed-info .card-header[aria-expanded="true"] .arrow i,
.landing-page.nordvpn .accordion .card-header .btn[aria-expanded="true"] i { transform: rotate(180deg); }
.landing-page .detailed-info .card-body,
.landing-page.sitelock .content-block.faq .card-body,
.landing-page.nordvpn .accordion .card-body {
    background: var(--bg-muted) !important;
    color: var(--text-lifted) !important;
    border: 0 !important;
    border-top: 1px solid var(--border-muted) !important;
}
.landing-page .detailed-info .card-body h4 { color: var(--text) !important; }
.landing-page .collapse { transition: height 220ms cubic-bezier(0.4,0,0.2,1); }

/* CodeGuard's static (non-accordion) FAQ list, restyled as a light card
   grid to match the rest of the app's card language. */
.landing-page.codeguard .content-block.faqs h3,
.landing-page.sitelock .content-block.features h2 { color: var(--text); }
.landing-page.codeguard .content-block.faqs .col-lg-4 h4 {
    color: var(--text);
    font-weight: 700;
}
.landing-page.codeguard .content-block.faqs .col-lg-4 {
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    padding: 1.25rem 1.4rem;
    margin-bottom: 1rem;
    box-shadow: var(--elevation-1);
}
.landing-page.codeguard .content-block.faqs .col-lg-4 p { color: var(--text-lifted); }

/* -----------------------------------------------------------------------
   7. Feature / icon grids — CodeGuard's overview-features + features,
   OX's feature-block, SiteLock's feature-wrapper, SSL's benefits row,
   NordVPN's icon sections. Icons get the app's own .app-icon-chip color-
   rotation treatment (soft token bg + accented token fg) instead of raw
   image sprites/flat icon-font glyphs sitting directly on the page.
   ----------------------------------------------------------------------- */
.landing-page .content-block.features .feature-wrapper,
.landing-page.ox .feature-block,
.landing-page.codeguard .content-block.features .feature {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    padding: 1rem 0;
}
.landing-page .feature-wrapper > i.fas,
.landing-page .feature-wrapper > i.far {
    flex: none;
    width: 46px;
    height: 46px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--rounding-md);
    font-size: 1.15rem;
}
.landing-page.sitelock .feature-wrapper:nth-child(6n+1) i,
.landing-page.sitelock .row:nth-of-type(6n+1) .feature-wrapper:first-child i { background: var(--primary-soft); color: var(--primary-accented); }
.landing-page.sitelock .feature-wrapper i { background: var(--accent-purple-soft); color: var(--accent-purple-accented); }
.landing-page.sitelock .feature-wrapper .content h4 { color: var(--text); }
.landing-page.sitelock .feature-wrapper .content p { color: var(--text-lifted); }
.landing-page.codeguard .content-block.features .icon img,
.landing-page.codeguard .overview-features img { filter: drop-shadow(0 2px 6px rgba(0,0,0,0.15)); }
.landing-page.codeguard .content-block.features .feature h4 { color: var(--text); }
.landing-page.codeguard .content-block.features .feature p { color: var(--text-lifted); }
.landing-page .overview-features {
    background: var(--bg-muted);
    border-radius: var(--rounding-xl);
    padding: 1.5rem 1rem;
}
.landing-page .overview-features li span { color: var(--text); font-weight: 600; }
.landing-page.ssl .content-block.ssl-benefits .row > div {
    color: var(--text-lifted);
    padding: 1rem 0.5rem;
}
.landing-page.ssl .content-block.ssl-benefits .row > div i {
    display: block;
    margin: 0 auto 0.6rem;
    width: 48px;
    height: 48px;
    line-height: 48px;
    border-radius: 50%;
    background: var(--primary-soft);
    color: var(--primary-accented);
}
.landing-page.ssl .content-block.ssl-benefits .row > div:nth-child(2n) i { background: var(--secondary-soft); color: var(--secondary-accented); }
.landing-page.ssl .content-block.ssl-benefits .row > div:nth-child(3n) i { background: var(--accent-purple-soft); color: var(--accent-purple-accented); }
.landing-page.ox .features .title { color: var(--text); }
.landing-page.ox .features p { color: var(--text-lifted); }
.landing-page.ox .features img { filter: drop-shadow(0 2px 6px rgba(0,0,0,0.12)); }


/* -----------------------------------------------------------------------
   8. NordVPN — vendor-red CTA debleed onto Vercaa green, plus a genuine
   hosting.com-style instant monthly/annual pricing toggle. The pricing
   section already renders EVERY real billing-cycle option WHMCS has priced
   (real $product/$pricing data, store/nordvpn/index.tpl's own `{foreach
   $pricings[$plan->id] as $pricing}` loop) as separate cards side by side;
   store/nordvpn/index.tpl adds one `data-cycle="{$pricing->cycle()}"`
   passthrough attribute (js/store-enhancements.js reads it, pure
   presentation, no new data) so this toggle can show/hide the real
   pre-rendered cards instantly client-side — matching hosting.com's
   observed "swap a CSS class over pre-rendered markup, no AJAX round-trip"
   mechanic exactly, translated to Vercaa's own tokens/markup.
   ----------------------------------------------------------------------- */
.landing-page.nordvpn .btn-get-nord,
.landing-page.nordvpn .btn-get-monthly,
.landing-page.nordvpn .btn-get-annually,
.landing-page.nordvpn .btn-get-deal,
.landing-page.nordvpn [class*="btn-get-"] {
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-lifted) 100%) !important;
    color: #fff !important;
    box-shadow: 0 6px 16px color-mix(in srgb, var(--primary) 35%, transparent);
}
.landing-page.nordvpn .blue { color: var(--primary-accented); }
.landing-page.nordvpn h1 { color: var(--text); }
.landing-page.nordvpn .feature.threat h1,
.landing-page.nordvpn .feature.world h1,
.landing-page.nordvpn .feature.icon h1,
.landing-page.nordvpn .feature.use h1,
.landing-page.nordvpn .feature.pricing h1,
.landing-page.nordvpn .feature.faq h1 { color: var(--text); }
.landing-page.nordvpn .feature p { color: var(--text-lifted); }
.landing-page.nordvpn .keep-data-safe-card {
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    padding: 1.5rem;
    box-shadow: var(--elevation-1);
}
.landing-page.nordvpn .keep-data-safe-card h3 { color: var(--text); }
.landing-page.nordvpn .keep-data-safe-card p { color: var(--text-lifted); }

.landing-page.nordvpn .feature.pricing .row > div {
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-xl);
    padding: 1.75rem 1.25rem;
    margin-bottom: 1rem;
    box-shadow: var(--elevation-2);
    transition: transform 220ms cubic-bezier(0.4,0,0.2,1), box-shadow 220ms;
}
.landing-page.nordvpn .feature.pricing .row > div:hover { transform: translateY(-3px); box-shadow: var(--elevation-4); }
.landing-page.nordvpn .feature.pricing .billing-cycle { color: var(--text-lifted); text-transform: uppercase; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em; }
.landing-page.nordvpn .feature.pricing .billing-price { color: var(--text); font-size: 1.9rem; font-weight: 800; }
.landing-page.nordvpn .feature.pricing .billing-save { color: var(--primary-accented); font-weight: 700; min-height: 1.2em; }
.landing-page.nordvpn .feature.pricing .row > div[data-cycle].nord-cycle-hidden { display: none; }

.nord-cycle-toggle {
    display: inline-flex;
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: 999px;
    padding: 0.25rem;
    margin: 0 auto 2rem;
    gap: 0.25rem;
}
.nord-cycle-toggle button {
    border: 0;
    background: transparent;
    color: var(--text-lifted);
    font-weight: 700;
    font-size: 0.9rem;
    padding: 0.5rem 1.25rem;
    border-radius: 999px;
    cursor: pointer;
    transition: background 200ms cubic-bezier(0.4,0,0.2,1), color 200ms;
}
.nord-cycle-toggle button.active {
    background: var(--primary);
    color: #fff;
    box-shadow: var(--elevation-1);
}

/* -----------------------------------------------------------------------
   9. SSL misc sections still using hardcoded light-only surfaces —
   browser-warning "standout-1" band, the orange competitive-upgrade
   promo strip, the dark ".features" icon band, help-me-choose text.
   ----------------------------------------------------------------------- */
.landing-page.ssl .standout-1 { background: var(--bg-muted) !important; }
.landing-page.ssl .standout-1 h3 { color: var(--text) !important; }
.landing-page.ssl .competitive-upgrade-promo {
    background: linear-gradient(135deg, var(--secondary) 0%, var(--secondary-lifted) 100%) !important;
}
.landing-page.ssl .competitive-upgrade-banner { background: var(--bg-lifted) !important; color: var(--text) !important; border-top: 1px solid var(--border-muted); }
.landing-page.ssl .features { background: var(--neutral-950) !important; }
.landing-page.ssl .features .feature {
    background: rgba(255,255,255,0.06) !important;
    border-radius: var(--rounding-lg);
    transition: background 200ms;
}
.landing-page.ssl .features .feature:hover { background: color-mix(in srgb, var(--primary) 28%, transparent) !important; }
.landing-page.ssl p.help-me-choose { color: var(--text); }
.landing-page.ssl p.help-me-choose a { color: var(--primary-accented); font-weight: 700; }
.landing-page.ssl .secure-wildcard .quote-section { color: var(--text-lifted); }
.landing-page.ssl .secure-wildcard .google-quote { color: var(--text); }
.landing-page.ssl .currency-selector,
.landing-page .currency-selector {
    background: var(--bg-lifted);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--rounding-md);
}


/* -----------------------------------------------------------------------
   10. Scroll-reveal utility — a light, restrained fade-up used by
   js/store-enhancements.js (IntersectionObserver) on each page's tier/
   feature/FAQ blocks. Deliberately subtle per docs/hosting-com-motion-
   interaction-reference.md's own finding that hosting.com's "liveliness"
   comes mostly from static composition (layered cards, ribbons), not
   heavy scroll choreography — same 200-300ms / standard-easing budget
   used everywhere else in this app, not a new animation language.
   ----------------------------------------------------------------------- */
.store-reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 420ms cubic-bezier(0.4,0,0.2,1), transform 420ms cubic-bezier(0.4,0,0.2,1);
}
.store-reveal.is-visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
    .store-reveal { opacity: 1; transform: none; transition: none; }
}

/* -----------------------------------------------------------------------
   11. "Explore Market Connect" hub — a new, additive section appended to
   templates/orderforms/vercaa-2026-cart/addons.tpl, below the real
   product-addons cross-sell list (that page's own genuine, unchanged
   purpose). Links out to all 7 real, purchased Market Connect vendor
   category pages as one visual "front door", per this task's own design
   direction — a real, useful new page section, not a repurposing of
   addons.tpl's existing behavior. Card grid uses the same elevated-card
   + icon-chip language as the rest of this redesign so it reads as one
   family with the pages it links to.
   ----------------------------------------------------------------------- */
.app-mc-hub { margin-top: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--border-muted); }
.app-mc-hub-head { margin-bottom: 1.25rem; }
.app-mc-hub-head h3 { font-family: var(--font-heading); font-weight: 700; margin: 0 0 0.25rem; color: var(--text); }
.app-mc-hub-head p { color: var(--text-lifted); margin: 0; }
.app-mc-hub-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 1rem;
}
.app-mc-hub-card {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    padding: 1.25rem;
    text-decoration: none !important;
    box-shadow: var(--elevation-1);
    transition: transform 200ms cubic-bezier(0.4,0,0.2,1), box-shadow 200ms cubic-bezier(0.4,0,0.2,1), border-color 200ms;
}
.app-mc-hub-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--elevation-3);
    border-color: var(--primary);
}
.app-mc-hub-card-icon {
    width: 42px;
    height: 42px;
    border-radius: var(--rounding-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-purple-soft);
    color: var(--accent-purple-accented);
}
.app-mc-hub-card:nth-child(3n+2) .app-mc-hub-card-icon { background: var(--primary-soft); color: var(--primary-accented); }
.app-mc-hub-card:nth-child(3n+3) .app-mc-hub-card-icon { background: var(--secondary-soft); color: var(--secondary-accented); }
.app-mc-hub-card-icon svg { width: 22px; height: 22px; }
.app-mc-hub-card-name { font-weight: 700; color: var(--text); font-size: 0.98rem; }
.app-mc-hub-card-pitch { color: var(--text-lifted); font-size: 0.85rem; line-height: 1.5; flex: 1; }
.app-mc-hub-card-cta { color: var(--primary-accented); font-weight: 700; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.3rem; }
.app-mc-hub-card:hover .app-mc-hub-card-cta { color: var(--primary); }

/* =========================================================================
   Support Tickets — LIST page "innovative pass" (2026-08-12). The page was
   already functionally rebuilt earlier (see the original "Support Tickets"
   block near line 1815 — .tix-stat-card/.tix-stat-icon/.tix-filter-menu
   etc., ALL left untouched below, per this file's append-only convention:
   the home dashboard's .app-welcome-stats block used the exact same
   "leave the old rule, add a new one later that wins by source order, or
   use new class names" approach). This block only concerns
   supportticketslist.tpl (NOT viewticket.php/supportticketsubmit-*.tpl/
   ticketfeedback.tpl, which keep using the original block's classes
   unchanged). Research: hosting.com's real /support-tickets page was
   inspected live for structure/motion only — no colors or copy reused
   (hard rule #3) — every color below is one of this project's own
   --primary/--secondary/--accent-purple/--success/--warning tokens.
   ========================================================================= */

/* ---- 1) Stat strip fused into one embedded card overlapping the gradient
   banner's bottom edge — mirrors this project's OWN home-dashboard
   .app-welcome-stats overlap technique (same negative-margin + --bg-lifted
   + elevation recipe), which is also, independently, hosting.com's real
   pattern for this exact page. New class names (.tix-stat-tile, not the
   old .tix-stat-card) so nothing here can collide with the original block. */
.tix-banner-wrap { margin-bottom: 1.5rem; }
.tix-banner-wrap .app-gradient-banner {
    margin-bottom: 0;
    border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
    padding-bottom: 1.75rem;
}
.tix-stats {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
    background: var(--bg-lifted);
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-3);
    margin: -1.75rem 1.5rem 0;
}
:root[data-theme="dark"] .tix-stats { box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3); }
.tix-stat-tile {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1.1rem 1.25rem;
    border-left: 1px solid var(--border);
    min-width: 0;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.tix-stat-tile:first-child { border-left: 0; }
.tix-stat-tile:hover { background: var(--bg-muted); }
.tix-stat-tile-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.15rem;
    flex-shrink: 0;
}
.tix-stat-tile-warning .tix-stat-tile-icon { background: var(--warning-soft); color: var(--warning-accented); }
.tix-stat-tile-purple .tix-stat-tile-icon { background: var(--accent-purple-soft); color: var(--accent-purple-accented); }
.tix-stat-tile-text { display: flex; flex-direction: column; min-width: 0; }
.tix-stat-tile-value { font-size: 1.5rem; font-weight: 700; color: var(--text); line-height: 1.2; }
.tix-stat-tile-label { font-size: var(--text-sm); color: var(--text-lifted); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 991.98px) {
    .tix-stats { margin: -1.5rem 1rem 0; }
}
@media (max-width: 575.98px) {
    .tix-stats { grid-template-columns: 1fr; margin: -1.25rem 0.75rem 0; }
    .tix-stat-tile { border-left: 0; border-top: 1px solid var(--border); padding: 0.85rem 1rem; }
    .tix-stat-tile:first-child { border-top: 0; }
}

/* ---- 2) Row-cards: "needs attention" visual language built ONLY from real
   fields (unread flag + statusClass — no fabricated priority), status-
   derived avatar coloring (this install is single-department, so every
   avatar used to render as an identical green "G" circle — coloring by
   status instead gives real visual variety across today's actual tickets),
   and a capped fade+rise entrance stagger reusing appUnifiedRowIn — the
   SAME keyframes already defined earlier in this file for the home
   dashboard's Products & Services rows (one entrance recipe site-wide,
   not a new one invented per page). nth-of-type targets DataTables' own
   rendered row order, so paging/sorting/filtering re-triggers the same
   settle-in look on every redraw, not just first paint. */
.tix-row-attn {
    border-left-width: 3px;
    border-left-color: var(--warning);
    box-shadow: var(--elevation-2);
}
.tix-row-attn:hover { border-left-color: var(--warning); box-shadow: var(--elevation-2); }

.tix-avatar-open, .tix-avatar-customer-reply { background: var(--warning-soft); color: var(--warning-accented); }
.tix-avatar-answered { background: var(--success-soft); color: var(--success-accented); }
.tix-avatar-onhold { background: var(--bg-accented); color: var(--text-lifted); }
.tix-avatar-inprogress { background: var(--accent-purple-soft); color: var(--accent-purple-accented); }
.tix-avatar-closed { background: var(--bg-accented); color: var(--text-muted); }

.tix-row-anim { animation: appUnifiedRowIn var(--duration-slow) var(--ease-standard) backwards; }
.tix-table tbody tr:nth-of-type(1) { animation-delay: 0ms; }
.tix-table tbody tr:nth-of-type(2) { animation-delay: 45ms; }
.tix-table tbody tr:nth-of-type(3) { animation-delay: 90ms; }
.tix-table tbody tr:nth-of-type(4) { animation-delay: 135ms; }
.tix-table tbody tr:nth-of-type(5) { animation-delay: 180ms; }
.tix-table tbody tr:nth-of-type(6) { animation-delay: 225ms; }
.tix-table tbody tr:nth-of-type(7) { animation-delay: 270ms; }
.tix-table tbody tr:nth-of-type(8) { animation-delay: 315ms; }
.tix-table tbody tr:nth-of-type(n+9) { animation-delay: 360ms; }
@media (prefers-reduced-motion: reduce) {
    .tix-row-anim { animation: none; }
}

/* ---- 3) Filter: right-anchored slide-in panel replacing the old small
   Bootstrap dropdown-menu — modeled directly on this project's own
   established .app-notif-panel/.app-notif-backdrop pattern (header.tpl +
   js/app-shell.js's initNotifPanel(); same right-anchored full-height
   slide-in + transparent click-to-dismiss backdrop + Escape-to-close +
   focus-return recipe, same z-index neighborhood, offset by +5 so the two
   panels can never tie if a future page somehow has both mounted at once).
   hosting.com's own real Filter control is also a substantial slide-in
   panel, not a tiny anchored dropdown — structure/motion reference only,
   no hosting.com colors or copy (hard rule #3). The old anchored
   .tix-filter-menu/.tix-filter-menu-title rules above are left in place,
   unused by this page now but harmless — .tix-filter-menu-title and
   .tix-filter-check are still reused inside the new panel body below. */
.tix-filter-backdrop {
    position: fixed;
    inset: 0;
    top: var(--header-height);
    background: transparent;
    z-index: 1055;
    opacity: 0;
    pointer-events: none;
}
body.tix-filter-open .tix-filter-backdrop { opacity: 1; pointer-events: auto; }

.tix-filter-panel {
    position: fixed;
    top: var(--header-height);
    right: 0;
    bottom: 0;
    width: 380px;
    max-width: 92vw;
    background: var(--bg);
    border-left: 1px solid var(--border-muted);
    box-shadow: -12px 0 30px rgba(0, 0, 0, 0.14);
    z-index: 1056;
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform var(--duration-base) var(--ease-standard);
}
:root[data-theme="dark"] .tix-filter-panel { box-shadow: -12px 0 30px rgba(0, 0, 0, 0.45); }
body.tix-filter-open .tix-filter-panel { transform: translateX(0); }

.tix-filter-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid var(--border-muted);
    flex-shrink: 0;
}
.tix-filter-panel-title { margin: 0; font-size: 1.125rem; font-weight: 700; color: var(--text); }
.tix-filter-panel-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    border: 0;
    background: transparent;
    color: var(--text-lifted);
    flex-shrink: 0;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.tix-filter-panel-close:hover { background: var(--bg-muted); color: var(--text); }

.tix-filter-panel-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.85rem 1.5rem;
    border-bottom: 1px solid var(--border-muted);
    font-size: var(--text-sm);
    color: var(--text-lifted);
    flex-shrink: 0;
}
.tix-filter-clear {
    color: var(--primary);
    font-weight: 700;
    background: none;
    border: 0;
    padding: 0;
    font-size: var(--text-sm);
    transition: color var(--duration-fast) var(--ease-out);
}
.tix-filter-clear:hover { color: var(--primary-accented); text-decoration: underline; }

.tix-filter-panel-body { flex: 1 1 auto; overflow-y: auto; padding: 1.25rem 1.5rem; }
.tix-filter-panel-body .tix-filter-menu-title { margin-bottom: 0.75rem; }
.tix-filter-panel-body .tix-filter-check { font-size: var(--text-md); padding: 0.6rem 0; }

.tix-filter-panel-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem;
    padding: 1rem 1.5rem;
    border-top: 1px solid var(--border-muted);
    flex-shrink: 0;
}

.tix-filter-badge {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    background: var(--warning);
    margin-left: 0.35rem;
    vertical-align: middle;
}

@media (max-width: 480px) {
    .tix-filter-panel { width: 100%; max-width: 100%; }
}

/* ---- 4) Pill-tabs: full-width equal-width segments on this page only
   (hosting.com's real page uses one continuous 3-segment track rather than
   3 pills with visible gaps between them) — scoped to #tixPillTabs so the
   shared pill-tabs.tpl base rules (.app-pill-tabs/.app-pill-tab, reused as-
   is by Products & Services/Invoices/the notif panel/etc.) are untouched.
   Same override-by-container-id convention already used for
   .app-unified-tabs-row .app-pill-tab earlier in this file. */
#tixPillTabs.app-pill-tabs { display: flex; width: 100%; }
#tixPillTabs .app-pill-tab { flex: 1 1 0; justify-content: center; }

/* =========================================================================
   Help Center redesign (knowledgebase.tpl / knowledgebasecat.tpl /
   knowledgebasearticle.tpl), 2026-08-12 — structural/layout clone of
   hosting.com's real "Help Center" hub (kb.hosting.com — its own on-page
   identity literally reads "HELP CENTER" as a kicker above its H1, even
   though hosting.com's own nav link text says "Knowledge Base"; confirmed
   live before treating it as the design reference, per HANDOFF.md §33e).
   All new classes are additive, prefixed app-kb-, and layered ON TOP OF the
   existing stock .kb-category/.kb-article-item/.kb-search classes (kept in
   the markup unchanged, both for the js/whmcs.js click-delegation contract
   on .kb-article-item/a.card-body, and because downloads.tpl/downloadscat.
   tpl — NOT part of this pass, out of scope — still render those same stock
   classes with their old FontAwesome markup untouched). Every color below
   is one of this project's own existing tokens (--primary/--secondary/
   --info/--accent-purple + their -soft/-accented variants, --text/--border/
   --bg neutrals) — nothing sampled from hosting.com's own screenshots,
   matching the standing rule documented in HANDOFF.md §19's --domain-ink
   fix. Icons are this project's self-hosted inline-SVG Heroicons-outline
   set (stroke-width 1.75, 24px viewBox), inlined directly in the 3 .tpl
   files + the 2 new shared partials (includes/kb-category-card.tpl,
   includes/kb-article-row.tpl) — no FontAwesome fal/fas classes remain in
   any of the markup this pass touched.
   ========================================================================= */

/* ---- Search hero: large centered pill instead of the old soft-square
        input-group corners (hosting.com's own hero search treatment).
        Additive modifier alongside the existing .kb-search base class —
        those older rules (~L4152) are left untouched since downloads.tpl/
        downloadscat.tpl still rely on their plain soft-square version. ---- */
.app-kb-search-pill { box-shadow: var(--elevation-1); }
.app-kb-search-pill .input-group-prepend .app-kb-search-icon-text {
    background: var(--bg);
    border: 1px solid var(--border);
    border-right: 0;
    border-radius: 999px 0 0 999px;
    padding-left: 1.35rem;
    padding-right: 0.6rem;
    color: var(--text-muted);
}
.app-kb-search-pill .app-kb-search-icon-text svg { width: 19px; height: 19px; }
.app-kb-search-pill.kb-search .form-control {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    padding-left: 0.5rem;
}
.app-kb-search-pill.kb-search .form-control:focus { box-shadow: none; }
.app-kb-search-pill:focus-within { box-shadow: var(--elevation-1), 0 0 0 3px var(--primary-soft); border-radius: 999px; }
.app-kb-search-pill .input-group-append .btn {
    border-radius: 0 999px 999px 0;
    padding-left: 1.75rem;
    padding-right: 1.75rem;
}

/* ---- Category grid tiles (includes/kb-category-card.tpl). Base shape/
        border/transition still comes from the existing .kb-category rule
        (~L4152); this only ADDS the icon-led hover treatment (border+soft
        glow ring using --primary-soft instead of the old hardcoded
        rgba(0,0,0,.06) shadow — hosting.com's own hover cue, cloned as
        layout/motion only, entirely on Vercaa's own green) and the new
        icon/title/desc/meta layout. Two-class selectors (.app-kb-cat-card,
        not bare .kb-category) so this can't leak onto downloads.tpl's
        still-stock category cards, which never receive this class. ---- */
.app-kb-cat-card:hover {
    border-color: var(--primary);
    box-shadow: var(--elevation-2), 0 0 0 4px var(--primary-soft);
    transform: translateY(-1px);
}
.app-kb-cat-link { display: block; position: relative; }
.app-kb-cat-edit { position: absolute; top: 1rem; right: 1rem; z-index: 1; }
.app-kb-cat-icon.app-icon-chip { width: 44px; height: 44px; border-radius: var(--rounding-lg); margin-bottom: 0.9rem; }
.app-kb-cat-icon svg { width: 22px; height: 22px; }
.app-kb-cat-title { display: block; font-family: var(--font-heading); font-weight: 700; font-size: 1.05rem; color: var(--text); margin-bottom: 0.4rem; }
.app-kb-cat-desc { color: var(--text-lifted); font-size: var(--text-md); line-height: 1.55; margin: 0 0 0.75rem; }
.app-kb-cat-meta { display: block; color: var(--text-muted); font-size: var(--text-sm); font-weight: 600; }

/* ---- Article-row lists (includes/kb-article-row.tpl) — reuses the
        existing .app-quicklink-row component (Home dashboard's Help Centre
        card) instead of a bare .list-group-item, for Popular Articles,
        category article lists AND the article page's Related Articles.
        Two-class overrides here win over the older .kb-article-item rule's
        conflicting gap/flex-wrap (~L4182) regardless of source order, and
        again can't leak onto downloads.tpl since .app-kb-row is never
        applied there. ---- */
.app-kb-row.kb-article-item { gap: 0.85rem; flex-wrap: nowrap; }
.app-kb-row-edit { margin-right: 0.5rem; flex-shrink: 0; }

/* ---- Article page: quieter meta row (tags/useful count/print) above the
        article body. ---- */
.app-kb-meta-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; }
.app-kb-meta-left { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
.app-kb-tag { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.25rem 0.7rem; border-radius: 999px; background: var(--info-soft); color: var(--info-accented); font-size: var(--text-sm); font-weight: 600; }
.app-kb-tag svg { width: 13px; height: 13px; }
.app-kb-useful { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--text-lifted); font-size: var(--text-sm); }
.app-kb-useful svg { width: 14px; height: 14px; }
.app-kb-print-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-accented);
    border-radius: var(--rounding-md);
    padding: 0.4rem 0.9rem;
    font-size: var(--text-sm);
    font-weight: 600;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.app-kb-print-btn:hover, .app-kb-print-btn:focus { background: var(--bg-muted); border-color: var(--border-lifted); color: var(--text); text-decoration: none; }
.app-kb-print-btn svg { width: 15px; height: 15px; }
.app-kb-divider { border-color: var(--border-muted); margin: 1.25rem 0; }

/* ---- Article body: constrained reading column + real heading hierarchy
        for whatever h1-h6 the raw $kbarticle.text HTML happens to contain
        — pure CSS, the HTML itself is rendered completely as-is. ---- */
.app-kb-article-body { max-width: 46rem; margin: 0 auto; }
.app-kb-article-body > :first-child { margin-top: 0; }
.app-kb-article-body h1, .app-kb-article-body h2, .app-kb-article-body h3,
.app-kb-article-body h4, .app-kb-article-body h5, .app-kb-article-body h6 {
    font-family: var(--font-heading);
    color: var(--text);
    font-weight: 700;
}
.app-kb-article-body h2 { font-size: 1.4rem; margin-top: 2.25rem; margin-bottom: 0.9rem; padding-bottom: 0.6rem; border-bottom: 1px solid var(--border-muted); }
.app-kb-article-body h3 { font-size: 1.15rem; margin-top: 1.75rem; margin-bottom: 0.65rem; }
.app-kb-article-body h4, .app-kb-article-body h5, .app-kb-article-body h6 { font-size: 1rem; margin-top: 1.4rem; margin-bottom: 0.5rem; }
.app-kb-article-body p { color: var(--text-accented); line-height: 1.75; margin-bottom: 1.1rem; }
.app-kb-article-body ul, .app-kb-article-body ol { color: var(--text-accented); line-height: 1.75; margin-bottom: 1.1rem; padding-left: 1.5rem; }
.app-kb-article-body li { margin-bottom: 0.4rem; }
.app-kb-article-body a { color: var(--primary-accented); text-decoration: underline; text-decoration-color: var(--border-lifted); text-underline-offset: 2px; }
.app-kb-article-body a:hover { color: var(--primary-lifted); }
.app-kb-article-body img { max-width: 100%; height: auto; border-radius: var(--rounding-md); }
.app-kb-article-body code { background: var(--bg-muted); color: var(--text-accented); padding: 0.15rem 0.4rem; border-radius: 0.3rem; font-size: 0.9em; }
.app-kb-article-body pre { background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: var(--rounding-md); padding: 1rem; overflow-x: auto; }
.app-kb-article-body pre code { background: none; padding: 0; }
.app-kb-article-body blockquote { border-left: 3px solid var(--primary); padding-left: 1rem; color: var(--text-lifted); margin: 1.25rem 0; }
.app-kb-article-body table { width: 100%; max-width: 100%; display: block; overflow-x: auto; }

/* ---- "Was this helpful?" — plain inline prompt + small outline/ghost pill
        buttons (hosting.com's own lightweight treatment) instead of the old
        solid btn-secondary pair under a <h4>. Yes hovers into the brand
        green family, No hovers into the existing --error family — both
        already-established semantic tokens, nothing new invented. The
        "thumbs down" icon is the exact same thumbs-up path, rotated 180°
        via CSS rather than a hand-authored mirror path. ---- */
.app-kb-vote { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.75rem; }
.app-kb-vote-label { color: var(--text-lifted); font-size: var(--text-md); }
.app-kb-vote-buttons { display: flex; gap: 0.6rem; margin: 0; }
.app-kb-vote-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 1rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-accented);
    font-size: var(--text-sm);
    font-weight: 600;
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.app-kb-vote-btn:hover, .app-kb-vote-btn:focus { background: var(--primary-soft); border-color: var(--primary); color: var(--primary-accented); }
.app-kb-vote-btn[value="no"]:hover, .app-kb-vote-btn[value="no"]:focus { background: var(--error-soft); border-color: var(--error); color: var(--error-accented); }
.app-kb-vote-icon { width: 15px; height: 15px; }
.app-kb-vote-icon-down { transform: rotate(180deg); }

/* ---- Optional sticky "On this page" mini-TOC rail (knowledgebasearticle.
        tpl's own inline <script> populates/unhides this only when the real
        article body has 2+ real h2/h3 elements — otherwise it stays
        [hidden]). Force-hidden below xl width regardless of JS state so it
        never crowds the existing sticky secondary-sidebar column (see
        `.row > .col-lg-4.col-xl-3` above) on laptop-width viewports. ---- */
.app-kb-article-layout { display: flex; align-items: flex-start; gap: 2rem; }
.app-kb-article-main { flex: 1 1 0; min-width: 0; }
.app-kb-article-toc { flex: 0 0 200px; position: sticky; top: calc(var(--header-height) + 1rem); }
.app-kb-article-toc[hidden] { display: none; }
.app-kb-toc-label { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin: 0 0 0.75rem; padding-left: 1rem; }
#appKbTocList { border-left: 2px solid var(--border-muted); }
.app-kb-toc-link { display: block; font-size: var(--text-sm); color: var(--text-lifted); padding: 0.35rem 0 0.35rem 1rem; text-decoration: none; border-left: 2px solid transparent; margin-left: -2px; transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); }
.app-kb-toc-link-sub { padding-left: 1.75rem; font-size: var(--text-xs); }
.app-kb-toc-link:hover, .app-kb-toc-link:focus { color: var(--text); text-decoration: none; }
.app-kb-toc-link.active { color: var(--primary-accented); border-left-color: var(--primary); font-weight: 600; }
@media (max-width: 1199.98px) {
    .app-kb-article-toc { display: none !important; }
}

/* =========================================================================
   Help Center — hosting.com-fidelity follow-up pass (2026-08-14). The
   2026-08-12 pass above already replaced the stock category badge-list and
   bare list-group with icon-led cards/rows and retinted everything onto
   Vercaa's own tokens, but re-verified LIVE against the real kb.hosting.com
   today turned up two remaining genuine structural gaps (not just color):
     1. hosting.com's search bar is docked INSIDE the dark hero itself —
        the page's single most prominent element — not a separate plain box
        underneath it. Fixed via gradient-banner.tpl's new $kicker/
        $extraContent params (see that file's docblock) + the two rules
        below, reusing clientareahome.tpl's own proven "form docked inside
        .app-gradient-banner" pattern (.app-domain-upsell-*) verbatim.
     2. hosting.com's own "Popular articles" section is a 2-column grid of
        individually-bordered tiles, not a single-column flush list — the
        one remaining spot that still read as "generic settings list" even
        after the quicklink-row treatment. .app-kb-article-grid below
        replaces .list-group/.list-group-flush at all 3 call sites
        (knowledgebase.tpl "Most Popular", knowledgebasecat.tpl's article
        list, knowledgebasearticle.tpl's "Related Articles").
   Every color is still one of this project's own existing tokens (or the
   already-established rgba(255,255,255,.85) text-on-permanently-branded-
   surface value .app-gradient-banner-subtext set in the 2026-08-13
   foundational retheme) — nothing new invented, nothing sampled from
   hosting.com's own screenshots.
   ========================================================================= */

/* ---- Hero kicker + docked search (gradient-banner.tpl's $kicker/
        $extraContent). .app-kb-hero is the shared baseline (kicker sizing,
        a touch more heading punch than the banner's normal 1.75rem default
        since this is a real page-title hub, not a promo strip); the
        -search modifier additionally centers the whole hero and docks the
        search form, and is only applied to the 2 call sites that actually
        have one (knowledgebase.tpl, knowledgebasecat.tpl) — the article
        page's hero stays left-aligned/kicker-only, matching hosting.com's
        own article title treatment there too. ---- */
.app-gradient-banner-kicker {
    color: rgba(255, 255, 255, 0.85);
    font-weight: 700;
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin: 0 0 0.6rem;
}
.app-gradient-banner-extra { margin-top: 1.5rem; }
.app-kb-hero .app-gradient-banner-heading { font-size: clamp(1.85rem, 3.2vw, 2.5rem); }
.app-kb-hero-search .app-gradient-banner-inner { flex-direction: column; text-align: center; }
.app-kb-hero-search .app-gradient-banner-text { max-width: 42rem; margin: 0 auto; }
.app-kb-hero-search .app-domain-upsell-search { width: 100%; max-width: 34rem; margin: 0 auto; }
@media (max-width: 575.98px) {
    .app-kb-hero-search .app-domain-upsell-btn { width: 100%; justify-content: center; }
}

/* ---- Article-row grid: 2-column grid of bordered tiles (kb.hosting.com's
        real "Popular articles" layout, confirmed live) instead of a flush
        vertical list. includes/kb-article-row.tpl's own markup/classes are
        unchanged (still .app-quicklink-row/.app-kb-row) — only the WRAPPER
        + the tile's own border/radius/padding are new, and only apply
        inside .app-kb-article-grid so nothing else using .app-quicklink-row
        (Home dashboard's Help Centre card) is affected. Hover reuses the
        exact same border+lift language as .app-kb-cat-card:hover above for
        one consistent "tile" feel across category cards AND article tiles.

        2026-08-14 fix pass, confirmed bug (detail-polish review, measured via
        getBoundingClientRect/getComputedStyle, not just visual): a bare `1fr`
        track means `minmax(auto, 1fr)` — the "auto" minimum is a min-CONTENT
        floor, not 0. `.app-quicklink-desc` (kb-article-row.tpl) is
        `white-space: nowrap`, so its unwrapped min-content width is the full
        line width; with no `min-width: 0` on the grid item itself
        (.kb-article-item.app-kb-row, the flex row this sits inside), that
        huge min-content floor propagates straight up into the grid track's
        own automatic minimum — measured at 717px live, on both the desktop
        2-col grid (splitting unevenly, ~380px dead gap) and the mobile 1-col
        grid (tile overflowing its column by ~250px, silently cropped by an
        ancestor's overflow:hidden — invisible to the scrollWidth===
        innerWidth check, but real content, including the truncate|"..." mid-
        word). Standard CSS Grid fix: swap `1fr` for `minmax(0, 1fr)` at both
        breakpoints below, so the track's minimum is explicitly 0 instead of
        auto and the item can shrink to fit — `.app-quicklink-text`'s existing
        `min-width: 0` (~L1896) then does the rest, letting `.app-quicklink-
        desc`'s own `overflow:hidden;text-overflow:ellipsis` (~L1898) render
        the actual, un-clipped "…" instead of a hard crop.

        Also fixes a second confirmed-live defect (layout/structure review):
        a lone or last-odd tile left a same-height dead blank cell next to it
        in the 2-col grid (today's real 1-article dataset, and any future
        odd-count category). `:nth-child(odd):last-child` makes the last tile
        span both columns whenever the total count is odd (covers today's
        single-tile case too, since child 1 is both odd and last) — full
        columns still just stack normally under the mobile 1-col query where
        this is a no-op. ---- */
.app-kb-article-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
}
.app-kb-article-grid .kb-article-item.app-kb-row {
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    padding: 1rem 1.1rem;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.app-kb-article-grid .kb-article-item.app-kb-row:hover {
    border-color: var(--primary);
    box-shadow: var(--elevation-1);
    transform: translateY(-1px);
}
.app-kb-article-grid > *:nth-child(odd):last-child { grid-column: 1 / -1; }
.app-kb-empty { grid-column: 1 / -1; text-align: center; color: var(--text-muted); padding: 2rem 0; }
@media (max-width: 767.98px) {
    .app-kb-article-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Article-list section heading (knowledgebase.tpl "Most Popular
        Articles", knowledgebasecat.tpl's article list, knowledgebasearticle.
        tpl's "Related Articles"). 2026-08-14 fix pass (2 independent harsh
        reviews, both re-verified live): all 3 sections were wrapped in the
        sitewide .card/.card-header component — a visibly bordered white box
        with a faint gray-tinted header strip, i.e. the exact "generic
        settings panel" smell this whole redesign pass exists to eliminate,
        and glaringly inconsistent with the category grid one section above,
        which already floats on the bare page background with no wrapper.
        The real kb.hosting.com reference has zero card on its own "Popular
        articles" heading either. Card/card-header shell dropped at all 3
        call sites; .app-kb-section only supplies the vertical rhythm the
        removed .card's own margin-bottom/card-body padding used to provide.
        The heading itself keeps the exact same .card-title text styling
        (font/weight unchanged, zero visual regression) via
        .app-kb-section-title, which only adds the margin the old
        .card-header's padding used to provide. ---- */
.app-kb-section { margin: 2rem 0; }
.app-kb-section-title { margin: 0 0 1.25rem; }

/* Market Connect storefront redesign — follow-up patch (2026-08-12, live QA pass). SSL's
   "Choose your level of security" band (.validation-levels) still shipped store.css's own
   hardcoded vendor blue (#327ba7) even after the rest of the page was retinted onto Vercaa
   tokens, live-verified as a jarring off-brand seam directly under the new hero. Same treatment
   as the hero directly above it (fixed brand gradient, always-light text, doesn't invert with
   the theme toggle) so the two bands read as one continuous, intentional brand surface. */
.landing-page.ssl .validation-levels {
    background: linear-gradient(135deg, var(--primary-accented) 0%, var(--primary) 60%, var(--secondary-accented) 130%) !important;
}
.landing-page.ssl .validation-levels h3 { color: #fff !important; }

/* Follow-up patch #2 (2026-08-12, live QA pass) — store.css's generic `.landing-page .tab-content
   { background-color: #fff }` (used by threesixtymonitoring's Bootstrap tab-pane sections, e.g.
   "Monitor load times..." under the Website/Server pills) sits OUTSIDE the .bg-white utility this
   file already neutralises for dark mode, so it stayed a frozen white panel with dark text after
   the rest of the page went dark — confirmed live via computed-style inspection (traced the exact
   winning rule, not guessed). Same var(--bg-lifted) card treatment as the rest of this redesign. */
.landing-page .tab-content {
    background-color: var(--bg-lifted) !important;
    color: var(--text);
    border-radius: var(--rounding-lg);
}
.landing-page .tab-content h1,
.landing-page .tab-content h2,
.landing-page .tab-content h3,
.landing-page .tab-content h4,
.landing-page .tab-content h5,
.landing-page .tab-content h6 { color: var(--text); }
.landing-page .tab-content p { color: var(--text-lifted); }
.landing-page.threesixtymonitoring .section-features { background: linear-gradient(var(--bg-muted), var(--bg)) !important; }
/* NOTE: .section-header (the hero) is intentionally NOT touched here — it already gets its fixed
   always-dark brand gradient from the shared hero rule above (section 1 of this whole block); this
   only cleans up the OTHER two selectors store.css groups into that same light-blue rule. */
.landing-page.threesixtymonitoring .modal-results .modal-body,
.landing-page.threesixtymonitoring .tab-website .section-monitoring {
    background-color: transparent !important;
    background-image: none !important;
}
.landing-page.threesixtymonitoring .nav-secondary .nav-link.active { background-color: var(--bg-lifted) !important; }
.landing-page.threesixtymonitoring h1,
.landing-page.threesixtymonitoring h2,
.landing-page.threesixtymonitoring h3 { color: var(--text) !important; }
.landing-page.threesixtymonitoring { color: var(--text-lifted); }
.landing-page.threesixtymonitoring .row-features span { color: var(--text); }
.landing-page.threesixtymonitoring .btn-primary { background-color: var(--primary) !important; border-color: var(--primary) !important; }
.landing-page.threesixtymonitoring .btn-search { background-color: var(--neutral-950) !important; }

/* =========================================================================
   MARKET CONNECT STOREFRONT REDESIGN — completion pass (2026-08-13)
   The block above (search "MARKET CONNECT STOREFRONT REDESIGN (2026-08-12)")
   already shipped the full hero/nav/card/accordion/hub reskin for all 8
   vendor pages and is left completely untouched here — this patch only
   finishes the two pieces that block referenced but that weren't actually
   wired up yet: (1) js/store-enhancements.js genuinely didn't exist on disk
   (confirmed via grep before writing it — templates/vercaa-2026/js/
   store-enhancements.js is a new file, now created) so NordVPN's
   .nord-cycle-toggle component had no buttons and no click behaviour; (2)
   .store-reveal was declared but referenced by zero real elements (grepped,
   confirmed) so the whole scroll-reveal utility was dead code — now applied
   to one natural section per vendor page (SSL's tier cards, SiteLock's
   plans, CodeGuard's price calculator, mail-services' product options, OX's
   pricing table, NordVPN's pricing row) via js/store-enhancements.js's
   IntersectionObserver, respecting prefers-reduced-motion exactly like the
   rest of this app. No new color literal anywhere below — everything
   resolves to existing --primary/--bg-*/--border/--text tokens. ========= */

/* NordVPN cycle-toggle pill row — box-styling for the buttons JS appends;
   the container itself was already `.nord-cycle-toggle` in the block above,
   this only adds the missing default-hidden → visible reveal + a focus
   ring, and re-flows the pricing row into a centered, evenly-sized set once
   JS activates filtering (the original grid was column-sized via Smarty's
   `$pricing@total` for the FULL unfiltered card count, so a filtered subset
   needs its own layout rather than inheriting those Bootstrap column
   widths). */
.landing-page.nordvpn .nord-cycle-toggle[hidden] { display: none; }
.landing-page.nordvpn .nord-cycle-toggle button:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}
.landing-page.nordvpn .feature.pricing .row.nord-cycle-active {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.25rem;
}
.landing-page.nordvpn .feature.pricing .row.nord-cycle-active > [data-cycle] {
    flex: 0 1 260px;
    max-width: 260px;
}
.landing-page.nordvpn .feature.pricing .row > [data-cycle].nord-cycle-hidden {
    display: none !important;
}

/* =========================================================================
   PRODUCTS & SERVICES / DOMAINS / PRODUCT DETAIL — innovative retheme pass
   (2026-08-13). Brief: not a recolor, a genuine visual/interaction leap to
   match the purple-dominant brand + the Apple-tier energy already shipped
   on the 7 standalone Market Connect pages (market/vpn.php, monitoring.php
   etc — reveal-on-load motion, layered radial-bloom "aurora" washes,
   eyebrow pills with a living pulse-dot, floating glow blobs, spring-eased
   segmented controls).

   SCOPING DISCIPLINE — read before editing anything below: .app-page-
   header / .app-list-toolbar / .app-table-card / .app-dtable / .app-pd-* /
   .app-detail-row / .app-alert-bar / .app-summary-card are NOT unique to
   these 3 pages — grepped and confirmed also rendered by
   clientareadomaindetails.tpl (Domain Details, a sibling surface owned by a
   different pass this run) plus clientareadomaindns/-contactinfo/-addons/
   -emailforwarding/-registerns/-getepp/bulkdomainmanagement/domain-
   pricing.tpl. NONE of the base rules for those shared classes are edited
   here — every rule in this block is prefixed with one of 3 new wrapper
   classes (.app-products-page / .app-domains-page / .app-pd-page) added
   ONLY around clientareaproducts.tpl / clientareadomains.tpl /
   clientareaproductdetails.tpl's own markup, so this entire pass is
   additive and cannot visually change any other page. Every color below
   traces to an existing token (var(--primary) etc.) via either a direct
   var() reference or a color-mix() derivation of one, matching the exact
   local-scoped-token pattern the Market Connect pages themselves already
   use (e.g. market/vpn.php's --vpn-purple-16). rgba(255,255,255,X) is used
   ONLY inside .app-pd-hero (already an existing, deliberately theme-
   invariant always-on-brand-gradient surface per the --gradient-brand
   comment in :root) — the exact same idiom .app-pd-hero-badge already used
   pre-existing, never on a themed surface.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Shared micro-interactions (scoped, reused by more than one of the 3
   pages below).
   ------------------------------------------------------------------------- */
@keyframes app-pulse-dot {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 35%, transparent); }
    50% { box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 0%, transparent); }
}
@keyframes app-row-fade-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
}
@keyframes app-pd-blob-float {
    0%, 100% { transform: translate(0, 0); }
    50% { transform: translate(-14px, 10px); }
}

/* Sliding segmented-control indicator (js/app-pill-slider.js). Only ever
   added to the 2 list pages' status-filter pill row (NOT the Product
   Detail sub-nav, which is a Bootstrap .nav-tabs component with its own
   already-shipped, specificity-tuned dark-mode fix — see this file's
   ":root[data-theme='dark'] .nav-tabs .nav-link.app-pill-tab.active" rule;
   deliberately left alone rather than fought). */
.app-pill-tabs.app-pill-tabs-sliding { position: relative; }
.app-pill-tabs-sliding .app-pill-tab { position: relative; z-index: 1; }
.app-pill-tabs-sliding .app-pill-tab.active { background: transparent; box-shadow: none; }
.app-pill-indicator {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 0;
    opacity: 0;
    border-radius: 999px;
    background: var(--bg);
    box-shadow: var(--elevation-1);
    transition: transform 0.38s cubic-bezier(0.22, 1, 0.36, 1), width 0.38s cubic-bezier(0.22, 1, 0.36, 1), height 0.38s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.2s var(--ease-out);
}

/* Live-status stat pills (page-header quick glance chips — Products &
   Services / Domains headers both use these). */
.app-stat-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.85rem;
    border-radius: 999px;
    font-size: var(--text-sm);
    font-weight: 700;
    background: var(--bg);
    border: 1px solid var(--border-muted);
    color: var(--text-accented);
    box-shadow: var(--elevation-1);
}
.app-stat-pill-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 22%, transparent);
    flex: none;
}
.app-stat-pill-live .app-stat-pill-dot { animation: app-pulse-dot 2.4s ease-in-out infinite; }
.app-stat-pill-success { color: var(--success-accented); }
.app-stat-pill-warning { color: var(--warning-accented); }
.app-stat-pill-neutral { color: var(--text-accented); }

/* -------------------------------------------------------------------------
   1) PRODUCTS & SERVICES (clientareaproducts.tpl) + DOMAINS
      (clientareadomains.tpl) — shared "hero header" + "row card" language.
   ------------------------------------------------------------------------- */
.app-products-page .app-page-hero,
.app-domains-page .app-page-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--rounding-2xl);
    padding: 1.75rem 2rem;
    margin-bottom: 1.5rem;
    background: var(--bg);
    border: 1px solid var(--border-muted);
    box-shadow: var(--elevation-1);
}
.app-products-page .app-page-hero-glow,
.app-domains-page .app-page-hero-glow {
    position: absolute;
    inset: -40% -10% auto -10%;
    height: 220px;
    z-index: 0;
    pointer-events: none;
    filter: blur(6px);
}
.app-products-page .app-page-hero-glow {
    background:
        radial-gradient(420px 180px at 15% 30%, color-mix(in srgb, var(--primary) 16%, transparent), transparent 70%),
        radial-gradient(360px 160px at 85% 10%, color-mix(in srgb, var(--info) 12%, transparent), transparent 70%);
}
.app-domains-page .app-page-hero-glow {
    background:
        radial-gradient(420px 180px at 15% 30%, color-mix(in srgb, var(--info) 16%, transparent), transparent 70%),
        radial-gradient(360px 160px at 85% 10%, color-mix(in srgb, var(--primary) 12%, transparent), transparent 70%);
}
.app-products-page .app-page-header,
.app-domains-page .app-page-header {
    position: relative;
    z-index: 1;
    margin-bottom: 0;
}
.app-products-page .app-page-header-heading,
.app-domains-page .app-page-header-heading {
    display: flex;
    align-items: center;
    gap: 1rem;
}
.app-page-icon-chip {
    width: 52px;
    height: 52px;
    border-radius: var(--rounding-lg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 6%, transparent);
}
.app-page-icon-chip svg { width: 26px; height: 26px; }
.app-products-page .app-page-icon-chip {
    background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 18%, var(--bg)), color-mix(in srgb, var(--primary) 6%, var(--bg)));
    border: 1px solid color-mix(in srgb, var(--primary) 24%, transparent);
    color: var(--primary);
}
.app-domains-page .app-page-icon-chip {
    background: linear-gradient(135deg, color-mix(in srgb, var(--info) 18%, var(--bg)), color-mix(in srgb, var(--info) 6%, var(--bg)));
    border: 1px solid color-mix(in srgb, var(--info) 24%, transparent);
    color: var(--info);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--info) 6%, transparent);
}
.app-page-stat-pills { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }

/* Toolbar: pill-shaped search with a focus glow instead of the flat
   bordered rectangle. */
.app-products-page .app-list-search-input,
.app-domains-page .app-list-search-input {
    border-radius: 999px;
    background: var(--bg-muted);
    border-color: transparent;
    transition: background-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.app-products-page .app-list-search-input:focus,
.app-domains-page .app-list-search-input:focus {
    background: var(--bg);
    border-color: var(--primary);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 14%, transparent);
}
.app-products-page .app-list-search:focus-within i,
.app-domains-page .app-list-search:focus-within i { color: var(--primary); }

/* Row cards: colored left accent (per real row status) + icon chip glow +
   staggered reveal-on-load, applied to the same <tr>/<td> structure
   (nothing in the DataTables markup shape changes, only paint). */
.app-products-page table.app-dtable > tbody > tr,
.app-domains-page table.app-dtable > tbody > tr {
    animation: app-row-fade-in 0.45s var(--ease-out) both;
}
.app-products-page table.app-dtable > tbody > tr:nth-child(1), .app-domains-page table.app-dtable > tbody > tr:nth-child(1) { animation-delay: 0ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(2), .app-domains-page table.app-dtable > tbody > tr:nth-child(2) { animation-delay: 30ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(3), .app-domains-page table.app-dtable > tbody > tr:nth-child(3) { animation-delay: 60ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(4), .app-domains-page table.app-dtable > tbody > tr:nth-child(4) { animation-delay: 90ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(5), .app-domains-page table.app-dtable > tbody > tr:nth-child(5) { animation-delay: 120ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(6), .app-domains-page table.app-dtable > tbody > tr:nth-child(6) { animation-delay: 150ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(7), .app-domains-page table.app-dtable > tbody > tr:nth-child(7) { animation-delay: 180ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(8), .app-domains-page table.app-dtable > tbody > tr:nth-child(8) { animation-delay: 210ms; }
.app-products-page table.app-dtable > tbody > tr:nth-child(n+9), .app-domains-page table.app-dtable > tbody > tr:nth-child(n+9) { animation-delay: 240ms; }

.app-products-page .app-cell-icon,
.app-domains-page .app-cell-icon { position: relative; }
.app-products-page .app-cell-icon::before,
.app-domains-page .app-cell-icon::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--border-muted);
    transition: background-color var(--duration-fast) var(--ease-out), width var(--duration-fast) var(--ease-out);
}
.app-products-page table.app-dtable > tbody > tr:hover .app-cell-icon::before,
.app-domains-page table.app-dtable > tbody > tr:hover .app-cell-icon::before { width: 4px; }
.app-products-page tr.app-row-success .app-cell-icon::before, .app-domains-page tr.app-row-success .app-cell-icon::before { background: var(--success); }
.app-products-page tr.app-row-warning .app-cell-icon::before, .app-domains-page tr.app-row-warning .app-cell-icon::before { background: var(--warning); }
.app-products-page tr.app-row-error .app-cell-icon::before, .app-domains-page tr.app-row-error .app-cell-icon::before { background: var(--error); }
.app-products-page tr.app-row-info .app-cell-icon::before, .app-domains-page tr.app-row-info .app-cell-icon::before { background: var(--info); }

.app-products-page .app-row-icon,
.app-domains-page .app-row-icon { transition: transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out); }
.app-products-page table.app-dtable > tbody > tr:hover .app-row-icon,
.app-domains-page table.app-dtable > tbody > tr:hover .app-row-icon {
    transform: scale(1.08);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 10%, transparent);
}

/* Expiring-soon domains: a real-data-driven amber pulse on the due-date
   cell only (build off $domain.expiringSoon, already computed server-side
   — see the tr class + .app-cell-date-due class added in
   clientareadomains.tpl; .app-cell-date-due specifically targets the
   NEXT-DUE-DATE cell, not the neighboring registration-date cell, which
   also carries the generic .app-cell-date class). */
.app-domains-page tr.app-row-expiring .app-cell-date-due { color: var(--warning-accented); font-weight: 700; }
.app-domains-page tr.app-row-expiring .app-cell-date-due::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--warning);
    margin-right: 6px;
    vertical-align: middle;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 25%, transparent);
    animation: app-pulse-dot 2.4s ease-in-out infinite;
}

.app-products-page .app-row-action-btn,
.app-domains-page .app-row-action-btn {
    border-radius: 999px;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.app-products-page .app-row-action-btn:hover,
.app-domains-page .app-row-action-btn:hover {
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--primary) 55%, transparent);
    transform: translateY(-1px);
}

/* -------------------------------------------------------------------------
   2) PRODUCT DETAIL (clientareaproductdetails.tpl) — the flagship surface:
      richer gradient hero (grid texture + floating glow blob + eyebrow
      pulse-dot + glanceable meta chips, matching the Market Connect hero
      language), icon-labeled summary rows, refined section card.
   ------------------------------------------------------------------------- */
.app-pd-page .app-pd-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--rounding-2xl);
    padding: 2.25rem 2.25rem 2rem;
    box-shadow: var(--elevation-3);
}
.app-pd-page .app-pd-hero-inner { position: relative; z-index: 1; }
.app-pd-hero-grid {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        linear-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.08) 1px, transparent 1px);
    background-size: 40px 40px;
    -webkit-mask-image: radial-gradient(120% 100% at 100% 0%, black, transparent 75%);
    mask-image: radial-gradient(120% 100% at 100% 0%, black, transparent 75%);
    opacity: 0.6;
}
.app-pd-hero-blob {
    position: absolute;
    width: 280px;
    height: 280px;
    right: -60px;
    top: -90px;
    z-index: 0;
    pointer-events: none;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 255, 255, 0.22), transparent 70%);
    filter: blur(20px);
    animation: app-pd-blob-float 12s ease-in-out infinite;
}
.app-pd-page .app-pd-hero-top { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.65rem; }
.app-pd-hero-icon {
    width: 48px;
    height: 48px;
    border-radius: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    background: rgba(255, 255, 255, 0.16);
    border: 1px solid rgba(255, 255, 255, 0.3);
    color: #fff;
}
.app-pd-hero-icon svg { width: 24px; height: 24px; }
.app-pd-page .app-pd-hero-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0;
}
.app-pd-hero-badge-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.28);
    animation: app-pulse-dot 2.4s ease-in-out infinite;
    flex: none;
}
.app-pd-page .app-pd-hero-heading {
    font-size: clamp(1.55rem, 2.4vw, 2rem);
    letter-spacing: -0.01em;
    line-height: 1.15;
}
.app-pd-hero-domain { color: rgba(255, 255, 255, 0.78); margin: 0.25rem 0 0; font-size: var(--text-md); }
.app-pd-hero-meta { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.1rem; }
.app-pd-hero-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.24);
    color: #fff;
    font-size: var(--text-sm);
    font-weight: 600;
}
.app-pd-hero-chip svg { width: 14px; height: 14px; opacity: 0.85; flex: none; }
.app-pd-page .app-pd-hero-status .app-status-badge { background: rgba(255, 255, 255, 0.92); }

/* Section card: slightly richer elevation + a hairline top accent to tie
   back to the hero above it. */
.app-pd-page .app-pd-section-card {
    border-radius: 0 var(--rounding-xl) var(--rounding-xl) var(--rounding-xl);
    box-shadow: var(--elevation-1);
}
.app-pd-page .app-pd-subnav .app-pill-tab { border-radius: var(--rounding-md) var(--rounding-md) 0 0; }

/* Icon-labeled detail rows — icons added via new <i class="app-detail-icon fas ..."> markup in
   the .tpl (purely decorative prefix, no data/markup restructuring). */
.app-pd-page .app-detail-label { display: flex; align-items: center; gap: 0.5rem; }
.app-detail-icon { color: var(--text-muted); font-size: 0.85rem; width: 1rem; text-align: center; flex: none; }
.app-pd-page .app-detail-row { transition: background-color var(--duration-fast) var(--ease-out); border-radius: var(--rounding-sm); }
.app-pd-page .app-detail-row:hover { background: var(--bg-muted); }
.app-pd-page .app-detail-value { font-variant-numeric: tabular-nums; }

/* Summary card: "hero price" emphasis on the payment-amount row(s) (class
   added in the .tpl, same row/markup, no reorder). */
.app-pd-page .app-summary-card {
    border-radius: var(--rounding-xl);
    box-shadow: var(--elevation-2);
}
.app-pd-page .app-summary-row-hero {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
    padding: 0.75rem 0.9rem;
    margin: 0 -0.9rem 0.4rem;
    border-radius: var(--rounding-md);
    border-bottom: none;
    background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 8%, var(--bg)), color-mix(in srgb, var(--primary) 2%, var(--bg)));
}
.app-pd-page .app-summary-row-hero .app-summary-label { font-size: var(--text-sm); }
.app-pd-page .app-summary-row-hero .app-summary-value {
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--primary-accented);
    font-variant-numeric: tabular-nums;
}
.app-pd-page .app-summary-cta,
.app-pd-page .app-summary-action-btn { border-radius: 999px; }
.app-pd-page .app-summary-cta:hover { box-shadow: 0 10px 26px -10px color-mix(in srgb, var(--primary) 55%, transparent); }

/* Alert bars on this page: a touch more lift + a softly pulsing icon so a
   pending-cancellation / unpaid-invoice notice reads as "needs attention"
   without shouting. */
.app-pd-page .app-alert-bar { box-shadow: var(--elevation-1); border: 1px solid transparent; }
.app-pd-page .app-alert-bar-warning { border-color: color-mix(in srgb, var(--warning) 24%, transparent); }
.app-pd-page .app-alert-bar-error { border-color: color-mix(in srgb, var(--error) 24%, transparent); }
.app-pd-page .app-alert-bar-icon { animation: app-pulse-dot 2.6s ease-in-out infinite; }

@media (max-width: 640px) {
    .app-page-icon-chip { width: 44px; height: 44px; }
    .app-page-icon-chip svg { width: 22px; height: 22px; }
    .app-pd-hero-icon { width: 40px; height: 40px; }
    .app-pd-hero-icon svg { width: 20px; height: 20px; }
}

@media (prefers-reduced-motion: reduce) {
    .app-products-page table.app-dtable > tbody > tr,
    .app-domains-page table.app-dtable > tbody > tr {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
    }
    .app-stat-pill-dot,
    .app-pd-hero-badge-dot,
    .app-domains-page tr.app-row-expiring .app-cell-date-due::before,
    .app-pd-hero-blob,
    .app-pd-page .app-alert-bar-icon,
    .app-pill-indicator {
        animation: none !important;
    }
    .app-pd-hero-blob { display: none; }
}

/* =========================================================================
   BILLING / INVOICES — visual + interaction elevation pass (2026-08-13)
   Surface: clientareainvoices.tpl (list), viewinvoice.tpl (detail,
   standalone document), invoice-payment.tpl + payment/invoice-summary.tpl
   (checkout). Sits on top of the "Billing agent" base classes defined
   earlier in this file (search "Billing agent — clientareainvoices.tpl")
   from the original build pass — those rules are left in place; this block
   only ADDS new component classes (app-bill-snap-*, app-inv-hero-*,
   app-pay-*) plus a small number of clearly-marked IN-PLACE corrections to
   existing rules (typography scale + card radius/shadow on
   .app-inv-*/.app-bill-invoice-num, called out individually below) — never
   a silent removal. Reuses the sitewide .app-unified-row-anim fade+rise
   entrance (defined earlier in this file, keyframe appUnifiedRowIn) as-is
   on the new blocks below instead of inventing a parallel animation system
   — it's already reduced-motion-guarded and already the app's established
   "content just appeared" motion language. Every color below traces to an
   existing --primary/--success/--warning/--info/--border/--text/--bg
   token; no new hex literals. --primary is now the real Vercaa purple
   (Foundation phase, 2026-08-13) and --success is fully decoupled from it,
   which is exactly why the Billing Snapshot ring below, the row status
   accent bars, and the payment-summary "Balance Due" callout are each
   deliberately colored per their OWN semantic token (success=paid,
   warning=unpaid, info=refunded, primary=call-to-action/"still owed") —
   never reusing --success for anything that isn't an actually-completed
   payment state. */

/* -------------------------------------------------------------------------
   clientareainvoices.tpl — "Billing Snapshot" strip: 2 real KPI cells
   (outstanding balance, paid-to-date) + a small CSS conic-gradient status
   ring (no chart library) summarizing the exact same Paid/Unpaid/Refunded
   counts the pill-tabs above already compute, colored to match their own
   badge semantics (success/warning/info) so the two components never
   disagree. Renders as a lightly-overlapping card tucked just under the
   gradient banner (small -0.85rem negative top margin — a much more
   conservative overlap than the Home dashboard's own -1.75rem
   .app-welcome-stats, deliberately, since this banner is title-only and
   shorter). Server-computed real data via includes/hooks/
   InvoiceBillingSnapshot.php — see that file's header comment for exactly
   which $invoices fields feed each number; nothing here is fabricated. */
.app-bill-snapshot {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    align-items: stretch;
    background: var(--bg-lifted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-2);
    margin: -0.85rem 0 1.5rem;
    overflow: hidden;
}
.app-bill-snap-cell {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1.1rem 1.35rem;
    border-left: 1px solid var(--border-muted);
    min-width: 0;
}
.app-bill-snap-cell:first-child { border-left: 0; }
.app-bill-snap-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: var(--rounding-md);
    flex: none;
}
.app-bill-snap-icon svg { width: 21px; height: 21px; }
.app-bill-snap-icon-warning { background: var(--warning-soft); color: var(--warning-accented); }
.app-bill-snap-icon-success { background: var(--success-soft); color: var(--success-accented); }
.app-bill-snap-text { min-width: 0; }
.app-bill-snap-label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-bottom: 0.2rem;
}
.app-bill-snap-value {
    display: block;
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--text);
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}
.app-bill-snap-sub {
    display: block;
    font-size: var(--text-sm);
    color: var(--text-lifted);
    margin-top: 0.15rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.app-bill-snap-sub-ok { color: var(--success-accented); font-weight: 600; }

.app-bill-snap-ring-cell { gap: 1rem; }
.app-bill-ring {
    position: relative;
    width: 58px;
    height: 58px;
    border-radius: 999px;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    background: conic-gradient(
        var(--success) 0% var(--ring-paid-end, 0%),
        var(--warning) var(--ring-paid-end, 0%) var(--ring-unpaid-end, 0%),
        var(--info) var(--ring-unpaid-end, 0%) var(--ring-refunded-end, 0%),
        var(--border-muted) var(--ring-refunded-end, 0%) 100%
    );
}
.app-bill-ring::before {
    content: "";
    position: absolute;
    inset: 7px;
    border-radius: 999px;
    background: var(--bg-lifted);
}
.app-bill-ring-count {
    position: relative;
    z-index: 1;
    font-size: 1rem;
    font-weight: 800;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.app-bill-ring-legend { display: flex; flex-direction: column; gap: 0.3rem; font-size: var(--text-sm); }
.app-bill-ring-legend span { display: flex; align-items: center; gap: 0.4rem; color: var(--text-lifted); white-space: nowrap; }
.app-bill-ring-legend b { color: var(--text); font-weight: 700; }
.app-bill-ring-dot { width: 8px; height: 8px; border-radius: 999px; flex: none; display: inline-block; }
.app-bill-ring-dot-paid { background: var(--success); }
.app-bill-ring-dot-unpaid { background: var(--warning); }
.app-bill-ring-dot-refunded { background: var(--info); }

@media (max-width: 991.98px) and (min-width: 761px) {
    .app-bill-snapshot { grid-template-columns: 1fr 1fr; }
    .app-bill-snap-ring-cell { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--border-muted); }
}
@media (max-width: 760px) {
    .app-bill-snapshot { grid-template-columns: 1fr; margin: -0.6rem 0 1.25rem; }
    .app-bill-snap-cell { border-left: 0; border-top: 1px solid var(--border-muted); }
    .app-bill-snap-cell:first-child { border-top: 0; }
}

/* Invoice list rows — a hairline, color-matched left accent (via inset
   box-shadow, so it costs zero layout width against the table's own
   column math) reusing the SAME success/warning/error/info/neutral
   vocabulary as the row's own status-badge.tpl call (see the
   $invBadgeType assignment already computed per-row in
   clientareainvoices.tpl — these classes are applied from that exact
   variable, not a new parallel status system) — the amount/status text
   still carries the same information on its own, so this is a color+text
   pairing, not color-only.

   Live-verified correction: the shadow is applied to the row's FIRST <td>
   (":first-child"), not the <tr> itself. Confirmed live via
   getComputedStyle that a box-shadow declared directly on the <tr> DOES
   compute correctly (inset 3px 0 0 0 <color>) but never actually PAINTS —
   the first <td>'s own opaque background (var(--bg)/var(--bg-lifted),
   painted after the row per normal table stacking) fully covers it, a
   known table-row rendering quirk. Putting the shadow on the <td> itself
   means the same element owns both its background and its shadow, so
   there's no cross-element paint-order conflict. */
.app-bill-row-success > td:first-child { box-shadow: inset 3px 0 0 0 var(--success); }
.app-bill-row-warning > td:first-child { box-shadow: inset 3px 0 0 0 var(--warning); }
.app-bill-row-error > td:first-child { box-shadow: inset 3px 0 0 0 var(--error); }
.app-bill-row-info > td:first-child { box-shadow: inset 3px 0 0 0 var(--info); }
.app-bill-row-neutral > td:first-child { box-shadow: inset 3px 0 0 0 var(--border-lifted); }
.app-bill-table tbody tr[class*="app-bill-row-"] > td:first-child { transition: box-shadow var(--duration-fast) var(--ease-out); }

/* IN-PLACE correction: .app-bill-invoice-num already existed (original
   Billing agent pass, "clientareainvoices.tpl — toolbar, pill-tab-filtered
   table" block above) with only font-weight/color/letter-spacing — this
   adds the tabular-figures + slightly larger size a "this is money" number
   deserves, matching the same font-variant-numeric treatment used
   everywhere else in this pass. Kept as a targeted addition to the
   existing selector rather than a duplicate rule further down. */
.app-bill-invoice-num { font-size: 1.05rem; font-variant-numeric: tabular-nums; }

/* Search field — pill shape + branded focus glow, replacing the plain
   square input-group box. Scoped to .app-bill-search-pill so it only
   affects this one toolbar, not every .input-group sitewide. */
.app-bill-search-pill .input-group-prepend .input-group-text {
    background: var(--bg-muted);
    border: 1px solid var(--border);
    border-right: 0;
    border-radius: 999px 0 0 999px;
    color: var(--text-lifted);
    padding-left: 1.1rem;
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.app-bill-search-pill .form-control {
    border: 1px solid var(--border);
    border-left: 0;
    border-radius: 0 999px 999px 0;
    background: var(--bg-muted);
    transition: border-color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.app-bill-search-pill .form-control:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
    border-color: var(--primary);
    background: var(--bg);
}
.app-bill-search-pill:focus-within .input-group-prepend .input-group-text {
    border-color: var(--primary);
    background: var(--bg);
    color: var(--primary);
}

/* -------------------------------------------------------------------------
   viewinvoice.tpl — hero card glow + typography scale + shared card
   elevation/radius for every .app-inv-card on the page (hero, credit,
   pay, side actions, both accordions, QR) in one rule, since they all
   already share that one class.
   ------------------------------------------------------------------------- */

/* IN-PLACE correction: .app-inv-card previously only set margin-bottom.
   Adding real radius/shadow here (not a new selector) is exactly the
   "hero/feature-card surface" upgrade the Foundation phase's own
   handoff note called out — these cards had zero shadow and the stock
   4px Bootstrap radius before this pass. overflow:hidden is required so
   the hero glow (below) and the pay-card's top accent bar stay clipped
   to the card's own rounded corners. */
.app-inv-card {
    margin-bottom: 1.5rem;
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-1);
    overflow: hidden;
}

/* IN-PLACE correction: the 5 .app-inv-accent-* rules already existed
   (status color-coded top border) — each gets a same-color --inv-accent
   custom property alongside its existing border-top declaration, so the
   NEW .app-inv-hero-card glow below can read "whatever this invoice's
   real status color is" without a sixth near-duplicate rule per status. */
.app-inv-accent-success { border-top: 3px solid var(--success); --inv-accent: var(--success); }
.app-inv-accent-warning { border-top: 3px solid var(--warning); --inv-accent: var(--warning); }
.app-inv-accent-error { border-top: 3px solid var(--error); --inv-accent: var(--error); }
.app-inv-accent-info { border-top: 3px solid var(--info); --inv-accent: var(--info); }
.app-inv-accent-neutral { border-top: 3px solid var(--border-lifted); --inv-accent: var(--border-lifted); }

/* New: soft radial status-color glow in the hero card's top-right corner,
   deliberately faint (20% color-mix) so it reads as ambient light, not a
   decoration competing with the real balance/status content — same
   "glow behind real content" technique already established on the
   Market Connect marketing pages (market/monitoring.php's .mp-hero-glow),
   scaled down to fit inside a normal card instead of a full-bleed hero
   section. Scoped to the one hero card only (new .app-inv-hero-card class
   added in viewinvoice.tpl) — the pay/side/accordion cards sharing
   .app-inv-card do NOT get this glow. */
.app-inv-hero-card { position: relative; }
.app-inv-hero-card::after {
    content: "";
    position: absolute;
    top: -35%;
    right: -12%;
    width: 300px;
    height: 300px;
    background: radial-gradient(closest-side, color-mix(in srgb, var(--inv-accent, var(--primary)) 20%, transparent), transparent 72%);
    pointer-events: none;
    z-index: 0;
}
.app-inv-hero-card .card-body { position: relative; z-index: 1; }

/* IN-PLACE correction: bump the hero title / balance / pay-amount numbers
   up a step so the invoice's own total reads as the clear focal point of
   the page (previously all three sat at a fairly modest 1.5rem/2rem,
   under-selling the single most important number on a billing document).
   Kept the -0.01em tightening already used sitewide on large display
   numbers (e.g. the Home dashboard's own stat values). */
.app-inv-title { font-size: 1.85rem; font-weight: 800; letter-spacing: -0.01em; }
.app-inv-meta-value { font-size: 2.1rem; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.app-inv-pay-amount { font-size: 2.5rem; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

/* Pay card: thin top accent bar in the real brand gradient (matches the
   gradient-banner's own --gradient-brand token, not a new value) plus a
   tabular-nums pass on every money figure in the line-items / payment-
   history tables (new .app-inv-amount-cell class added at each amount
   <td> in viewinvoice.tpl — additive class only, no loop/logic touched). */
.app-inv-pay-card { position: relative; }
.app-inv-pay-card::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 4px;
    background: var(--gradient-brand);
}
.app-inv-amount-cell { font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------------------
   invoice-payment.tpl + payment/invoice-summary.tpl — checkout elevation.
   ------------------------------------------------------------------------- */
.app-pay-method-card { position: relative; overflow: hidden; border-radius: var(--rounding-lg); box-shadow: var(--elevation-1); }
.app-pay-method-card::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 4px;
    background: var(--gradient-brand);
}
.app-pay-method-head { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.25rem; }
.app-pay-submit-btn { padding: 0.85rem 2.25rem; font-size: 1.05rem; gap: 0.6rem; }
.app-pay-lock-icon { width: 16px; height: 16px; flex: none; }

.app-pay-summary-card { border-radius: var(--rounding-lg); box-shadow: var(--elevation-1); overflow: hidden; }
.app-pay-summary-head { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.25rem; }
.app-pay-summary-head h2 { margin: 0; font-size: 1.15rem; font-weight: 800; color: var(--text); text-align: left; }
.app-pay-summary-table td { font-variant-numeric: tabular-nums; }
.app-pay-summary-table .total-row td { font-weight: 700; color: var(--text); }
.app-pay-paid-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 0.1rem;
    color: var(--text-lifted);
    font-size: var(--text-md);
    border-top: 1px dashed var(--border-muted);
    margin-top: 0.5rem;
}
.app-pay-paid-line strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* "Balance Due" callout — deliberately NOT --success (the original markup
   used Bootstrap's plain .alert-success, i.e. bright green, for an amount
   the client still OWES — a real semantic mismatch this pass corrects:
   green now unambiguously means "paid/complete" everywhere else on this
   surface, so a not-yet-paid balance instead gets the brand --primary
   "action" treatment, matching the same purple the actual Pay button
   uses). */
.app-pay-balance-callout {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    background: var(--primary-soft);
    border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
    border-radius: var(--rounding-md);
    padding: 0.9rem 1.1rem;
    margin-top: 0.5rem;
}
.app-pay-balance-callout-label {
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--primary-accented);
}
.app-pay-balance-callout-value { font-size: 1.4rem; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }

@media (max-width: 575.98px) {
    .app-bill-snap-value { font-size: 1.25rem; }
    .app-inv-pay-amount { font-size: 2.1rem; }
    .app-inv-meta-value { font-size: 1.75rem; }
    .app-pay-balance-callout { flex-direction: column; align-items: flex-start; gap: 0.35rem; }
}

/* =========================================================================
   View Ticket — detail page "innovative pass" (2026-08-13, retheme
   creative-elevation task, build reference §7 follow-up). Companion to the
   supportticketslist.tpl block above (search "innovative pass" 2026-08-12)
   — same overlap-card hero recipe, same reused .app-service-row-meta
   (Home dashboard Products & Services rows) and .acct-card-icon/-header-row
   (Account pages) components, so all the tickets surfaces read as one
   coherent, purple-primary system rather than three unrelated pages.
   Nothing above this block was touched; the original "Support Tickets"
   block's .tix-view-actions rule (~L2126) is now unused by viewticket.tpl
   (replaced by .tix-view-hero/.tix-view-meta below) but left in place,
   orphaned but harmless — same append-only convention already used
   throughout this file (e.g. .tix-stat-card next to .tix-stat-tile).
   ========================================================================= */

/* ---- 1) Hero: status/meta/actions fused into one card overlapping the
   gradient banner's bottom edge — identical recipe to supportticketslist.
   tpl's own .tix-banner-wrap/.tix-stats above. ---- */
.tix-view-hero { margin-bottom: 1.75rem; }
.tix-view-hero .app-gradient-banner {
    margin-bottom: 0;
    border-radius: var(--rounding-lg) var(--rounding-lg) 0 0;
    padding-bottom: 1.9rem;
}
.tix-view-meta {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.85rem;
    background: var(--bg-lifted);
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-3);
    margin: -1.75rem 1.5rem 0;
    padding: 1.1rem 1.35rem;
}
:root[data-theme="dark"] .tix-view-meta { box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3); }
.tix-view-meta-info { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; min-width: 0; }
.tix-view-meta-status { flex-shrink: 0; }
.tix-view-meta-line { font-size: var(--text-sm); }
.tix-view-meta-idtag { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.tix-view-meta-actions { display: flex; align-items: center; gap: 0.6rem; flex-shrink: 0; }

/* Soft pulse ring behind the status dot while a ticket is genuinely awaiting
   a reply from us (Open/Customer-Reply — the same "needs attention" state
   supportticketslist.tpl already flags via .tix-row-attn) — a brief,
   tasteful cue (capped at 3 iterations, not infinite) rather than a
   constant distraction, and fully respects reduced-motion below. */
.tix-view-meta-status-live .fa-circle { position: relative; }
.tix-view-meta-status-live .fa-circle::before {
    content: '';
    position: absolute;
    inset: -4px;
    border-radius: 999px;
    background: var(--info);
    opacity: 0.4;
    animation: tixStatusPulse 2.2s var(--ease-standard) 3;
}
@keyframes tixStatusPulse {
    0% { transform: scale(0.7); opacity: 0.5; }
    70% { transform: scale(2); opacity: 0; }
    100% { transform: scale(2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .tix-view-meta-status-live .fa-circle::before { animation: none; display: none; }
}

@media (max-width: 767.98px) {
    .tix-view-meta { margin: -1.5rem 1rem 0; align-items: flex-start; }
    .tix-view-meta-actions { width: 100%; }
    .tix-view-meta-actions .btn { flex: 1 1 auto; }
}

/* ---- 2) Composer: icon header (reuses .acct-card-header-row/-icon/
   -heading/-title/-subtext verbatim, the Account pages' own component) +
   an on-brand focus glow, scoped so it can't affect any other
   .form-control/.md-editor elsewhere in the app. ---- */
.tix-composer-header { margin-bottom: 1.1rem; }
.tix-composer.card {
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-2);
    overflow: hidden;
}
.tix-composer.card::before {
    content: '';
    display: block;
    height: 3px;
    background: var(--gradient-brand);
}
.tix-composer .form-control:focus,
.tix-composer .md-editor:focus-within {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}
.tix-composer .md-editor:focus-within { border-radius: var(--rounding-md); }

/* ---- 3) Thread: scroll-in reveal, progressive enhancement only — the
   matching JS lives in viewticket.tpl's own <script> block (search
   "Thread entrance reveal"); no gating class is ever applied unless that
   whole block succeeds, so a JS failure can never hide a real reply. ---- */
.tix-thread.tix-thread-anim-ready .tix-thread-item {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.55s var(--ease-standard), transform 0.55s var(--ease-standard);
}
.tix-thread.tix-thread-anim-ready .tix-thread-item.tix-thread-in {
    opacity: 1;
    transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
    .tix-thread.tix-thread-anim-ready .tix-thread-item { opacity: 1; transform: none; transition: none; }
}

/* ---- 4) Side cards: icon-led toggle headers (reuses .tix-side-card-toggle
   as-is, just gives it a leading icon chip) + a slightly richer resting
   elevation than the plain card default, so the column reads as a matched
   set with the composer/hero above it. ---- */
.tix-side-card {
    border-radius: var(--rounding-lg);
    box-shadow: var(--elevation-1);
    transition: box-shadow var(--duration-base) var(--ease-standard);
}
.tix-side-card:hover { box-shadow: var(--elevation-2); }
.tix-side-card-toggle-label { display: inline-flex; align-items: center; gap: 0.65rem; min-width: 0; }
.tix-side-card-icon {
    width: 30px;
    height: 30px;
    border-radius: var(--rounding-sm);
    background: var(--primary-soft);
    color: var(--primary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    flex-shrink: 0;
}
.tix-side-card-icon-info { background: var(--info-soft); color: var(--info-accented); }

/* Domains page only: stat pills render as their own secondary row below
   the title+actions header row (Domains' header already has 2 right-aligned
   CTA buttons, so pills share a row with the icon+title instead — see
   .app-page-header-heading above — rather than crowding one line further). */
.app-domains-page .app-page-stat-pills-row {
    position: relative;
    z-index: 1;
    margin-top: 1rem;
}

/* =========================================================================
   Home dashboard — unified Products & Services pill-tab icons, 2026-08-13
   innovate pass. New optional `.app-pill-tab-icon` slot (includes/pill-
   tabs.tpl now renders it only `{if $tab.icon}` — every other page reusing
   that shared partial doesn't pass one, so this is purely additive there).
   Sized to sit comfortably inside the existing pill padding/line-height
   without needing to touch .app-unified-tabs-row .app-pill-tab itself. */
.app-pill-tab-icon {
    display: inline-flex;
    width: 14px;
    height: 14px;
    margin-right: 0.35rem;
    vertical-align: -2px;
    flex-shrink: 0;
}
.app-pill-tab-icon svg { width: 100%; height: 100%; }

/* =========================================================================
   Account/Profile elevation pass — one small follow-on rule (2026-08-13):
   user-profile.tpl's live initials-avatar chip reuses .acct-card-icon as
   its shell (same size/gradient/radius as every other section icon on this
   surface) but holds two letters of text instead of an <i> glyph, which
   needs its own type treatment rather than the icon-font sizing above. */
.acct-card-icon span {
    font-weight: 800;
    font-size: 0.9rem;
    letter-spacing: 0.01em;
}

/* account-contacts-new.tpl's "Add contact" tile in the contact-picker grid is a non-interactive
   <span> (this IS the page the visitor is already on), not a <button>/<a> like the other tiles —
   suppress the pointer cursor/hover-lift so it doesn't read as a clickable-but-dead control. */
span.acct-contact-card { cursor: default; }
span.acct-contact-card:hover { transform: none; box-shadow: none; }

/* viewinvoice.tpl's theme-aware logo pair (see the .app-inv-brand fix comment in that file) —
   sizing only. Deliberately NO `display` property on this rule (a first draft here that added
   `display:block` was itself a bug: same 0,1,0 specificity as the sitewide
   ".app-logo-img-dark { display: none; }" rule, and being LATER in this file's source order
   would have won that tie, undoing the toggle and showing both logos stacked in light mode —
   caught before shipping by re-deriving the specificity math, not caught live). The sitewide
   .app-logo-img-light/-dark rules already fully own `display` for any element carrying those
   classes (base: dark hidden; :root[data-theme="dark"] qualifier: light hidden/dark shown — that
   attribute-qualified selector is genuinely more specific, 0,2,0 vs 0,1,0, so it wins correctly
   regardless of source order) — this rule must never touch `display` or it re-breaks that. */
.app-inv-brand-logo { max-height: 32px; }

/* =========================================================================
   Fix (2026-08-13, live-verified while QA-ing the new password show/hide
   toggle): Bootstrap4's `.form-control.is-valid`/`.is-invalid` bakes in a
   right-aligned checkmark/✕ background-image (this is what pwstrength.tpl's
   JS toggles on #inputNewPassword1/#inputNewPassword2 once a password is
   typed) — confirmed live it was rendering directly on top of the new
   `.acct-pw-toggle` eye button added in this same pass, both fighting for
   the same top-right corner. The live checklist below the field already
   gives equal-or-better feedback, so the redundant baked-in icon is simply
   suppressed inside `.acct-pw-field` (scoped to this one new wrapper class,
   introduced in this same elevation pass — cannot affect any other field
   sitewide) rather than trying to reposition two icons into the same slot. */
.acct-pw-field .form-control.is-valid,
.acct-pw-field .form-control.is-invalid {
    background-image: none;
}

/* =========================================================================
   Guest nav + mobile drawer — MOVED (2026-08-13 header/footer unification pass) to
   templates/vercaa-2026/css/guest-chrome.css, the one real stylesheet now shared between this
   Smarty client area AND the 9 standalone market/*.php pages (which cannot load this 11000+ line
   WHMCS-specific file). Markup is now emitted by templates/vercaa-2026/includes/
   shared-guest-chrome.php via header.tpl's guest branch (through the includes/hooks/
   shared-guest-chrome-loader.php bridge). guest-chrome.css is linked from includes/head.tpl,
   right after this file, so nothing else about load order changes. See that file for the full,
   unabridged ruleset (unchanged from what used to live here, plus the new Hosting dropdown +
   "Coming soon" pill + active-link state the new 5-item nav needed).
   ========================================================================= */

/* =========================================================================================
   12. Homepage — FULL BODY REBUILD (Phase 3 punch-list item #1, 2026-08-13, rebuild pass 3)

   Guest-facing marketing homepage: templates/vercaa-2026/homepage.tpl (body content) +
   templates/vercaa-2026/includes/homepage-hero.tpl (the hero). Per direct, detailed user
   instruction, the entire body (hero through the section just above the footer) has been rebuilt
   from scratch this pass, modelled closely on vercaa.com's OWN real homepage section shape
   (gradient-blob hero -> feature cards -> interactive hosting configurator -> testimonials ->
   closing CTA) — NOT a pixel clone, and using only OUR real brand tokens (var(--primary)=purple,
   var(--secondary)=orange, var(--success)=green, var(--info)=blue) and OUR real WHMCS product
   data, never vercaa.com's own literal colors or unverifiable marketing claims. Every color below
   is a var(--token) reference against the real :root block at the top of this file — no new hex
   literal is introduced anywhere in this section.

   WHAT CHANGED THIS PASS vs. the prior rebuild (kept for the record, not because anyone needs to
   diff it, but so the next person touching this file understands why some rules below look
   familiar and some are wholly new):
     - Hero: kept the white-canvas/near-black-headline/single-purple-button bar from the previous
       pass (still correct, still user-verified), but the single-blob glow is now TWO blurred,
       low-saturation, multi-color blobs (purple+orange top-right, green bottom-left — all
       color-mix() off the 3 brand tokens) per the brief's "soft multi-color gradient-blob
       background" instruction, plus one new `.vhp-hero-accent` rule for the headline's accent word.
     - NEW: `.vhp-features*` — 3 feature cards (icon-chip + category tag + title/stat-badge +
       description + accent-line pattern), replacing nothing (this section didn't exist before).
     - REPLACED: the old CSS-only 4-tab "hosting-tier" description panel (`.vhp-hosting-panel` /
       `.vhp-htab-*` / `.vhp-hpane*`) is retired in favor of `.vhp-configurator*` — a genuinely
       richer, still-CSS-only (same hidden-radio + :has() technique, zero JS, so it can never get
       stuck in two visible states at once) plan-tile + live detail-pane + "Your Plan" summary-card
       configurator, carrying forward the exact same real descriptive copy the old tab panes used
       (cPanel/Imunify360/Python+Node.js facts) rather than discarding that work, now paired with
       REAL pricing for the one real product (Vercaa Starter, $9.99/mo — confirmed directly against
       tblpricing) and honest "Coming soon" framing (never a fabricated price) for the other three.
     - REMOVED (content absorbed elsewhere, not deleted — see homepage.tpl's own docblock for where
       each real link ended up): `.vhp-infra-*` (the old productGroups/register/transfer standalone
       cards — that functionality is still fully live via the hero's own untouched domain-search
       form), `.vhp-midcta-*` (folded into the closing CTA band), `.vhp-trust-*` + `.vhp-marquee*`
       (each real fact/link moved into the feature cards, the configurator, or was already present
       verbatim in the untouched domain-search.tpl). `.vhp-btn-light` / `.vhp-btn-outline-light`
       also dropped — confirmed via sitewide grep to be genuinely unused (a leftover from an even
       earlier draft that had a permanent dark gradient surface, which no longer exists anywhere on
       this page).
     - NEW, then FIXED (2026-08-13, same day, independent verification pass): `.vhp-testimonials`
       originally shipped 6 named-person/company "testimonials" with invented "Key Results" stats,
       claimed to be real verbatim Trustpilot reviews. They were not — independent web research
       found no connection between any of those names/companies and Vercaa, and confirmed the real
       vercaa.com Trustpilot reviews are short, generic hosting feedback, nothing like what was
       shown. Fabricating testimonials/stats is a non-negotiable rule violation for this project, so
       the per-card rules were removed along with the markup and replaced with `.vhp-testi-panel`, an
       honest aggregate-rating strip. See homepage.tpl's own docblock for the full explanation.
     - KEPT, unchanged: `.vhp-section*` shared rhythm, `.vhp-service-*` (Market Connect's 7 real
       services), `.vhp-btn-primary/-ghost`, `.vhp-quick-*` (How can we help / Your account), and
       `.vhp-closing-*` (closing CTA band) — all already correct, already user-verified, and not
       touched by this pass's brief.

   HARD CONSTRAINT HONORED: no scroll-triggered opacity/translateY section-reveal gating anywhere
   in this section, full stop (this exact bug shipped broken twice on this page already). Every
   section below is visible by default in normal document flow. The only motion here is: the
   configurator's radio-driven tile-select + pane-crossfade (contained, :has()-only, never two
   panes visible at once by construction), simple hover states, and the hero's live-availability
   pulse dot — none of these gate a section's *base* visibility on scroll position or a JS-added
   class, which is the one thing the constraint rules out.
   ========================================================================================= */

/* ---- Shared section rhythm (unchanged) ---- */
.vhp-section { max-width: 1180px; margin: 0 auto; padding: clamp(3.5rem, 6vw, 6rem) 1.5rem; }
.vhp-section-head { max-width: 640px; margin: 0 auto 2.75rem; text-align: center; }
.vhp-section-eyebrow {
    font-size: var(--text-sm); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--primary-accented); margin: 0 0 0.6rem;
}
.vhp-section-title {
    font-family: var(--font-heading); font-size: clamp(1.75rem, 3vw, 2.5rem); font-weight: 700;
    color: var(--text); margin: 0 0 0.85rem; line-height: 1.15; letter-spacing: -0.01em;
}
.vhp-section-sub { font-size: 1.0625rem; color: var(--text-lifted); line-height: 1.6; margin: 0; }

/* -----------------------------------------------------------------------
   Hero (includes/homepage-hero.tpl).
   ----------------------------------------------------------------------- */
.vhp-hero {
    position: relative;
    background: var(--bg);
    overflow: hidden;
    padding: clamp(3rem, 7vw, 5.5rem) 1.5rem clamp(4rem, 8vw, 6.5rem);
}
/* 2026-08-25 hero redesign (direct user request: "the hero section of homepage is not really
   nice", specifically compared unfavorably to market/web-hosting.php's own hero — see
   hosting-landing.css's .hero-glow, the shared component all 7 hosting pages use). That version
   blends its 3 brand-color tints into ONE layer (one background property, one blur, one
   contained band) instead of two separately-shaped, separately-blurred blobs — reads as a single
   cohesive ambient glow rather than two distinct floating shapes. Replaced the old two-pseudo-
   element approach with the same one-layer technique, same 3 real tokens (--primary/--secondary/
   --success), same conservative tint percentages (still a depth cue on a white/near-white canvas,
   never a wash -- the white-canvas rule still governs). Still 100% contained by the parent's own
   `overflow:hidden`, so it cannot affect scrollWidth. `.vhp-hero::after` (the old second blob) is
   retired -- no longer needed now that one layer carries all 3 tints. */
.vhp-hero::before {
    content: "";
    position: absolute;
    inset: -18% -8% auto -8%;
    height: 640px;
    background:
        radial-gradient(46% 55% at 20% 15%, color-mix(in srgb, var(--primary) 30%, transparent), transparent 68%),
        radial-gradient(42% 50% at 82% 8%, color-mix(in srgb, var(--secondary) 24%, transparent), transparent 68%),
        radial-gradient(38% 48% at 50% 62%, color-mix(in srgb, var(--success) 20%, transparent), transparent 70%);
    filter: blur(20px);
    pointer-events: none;
    z-index: 0;
}
.vhp-hero-inner {
    position: relative; z-index: 1;
    max-width: 1280px; margin: 0 auto;
    display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(2rem, 5vw, 4rem);
    align-items: center;
}
.vhp-hero-eyebrow {
    display: inline-flex; align-items: center; gap: 0.5rem;
    color: var(--primary-accented);
    font-weight: 700; font-size: var(--text-md); letter-spacing: 0.03em; margin: 0 0 1.1rem;
}
.vhp-hero-eyebrow-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex-shrink: 0; }
.vhp-hero-title {
    font-family: var(--font-heading); color: var(--text);
    font-size: clamp(2.25rem, 4.6vw, 3.75rem); font-weight: 700; line-height: 1.08;
    letter-spacing: -0.01em; margin: 0 0 1.25rem;
}
/* The headline's one accent clause (design brief: "bold 2-line headline with one accent clause") —
   a single purple word carrying the emphasis, everything else staying the near-black heading color
   so the hierarchy still reads as typography-led, not color-led. */
.vhp-hero-accent { color: var(--primary); }
.vhp-hero-sub {
    color: var(--text-lifted);
    font-size: clamp(1rem, 1.4vw, 1.1875rem); line-height: 1.6; max-width: 34rem; margin: 0 0 1.75rem;
}
.vhp-hero-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 0; padding: 0; }
.vhp-hero-chip {
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    color: var(--text-accented); font-size: var(--text-sm); font-weight: 600; padding: 0.5rem 0.9rem 0.5rem 0.7rem;
    border-radius: 999px;
}
.vhp-hero-chip svg { width: 14px; height: 14px; color: var(--primary); flex-shrink: 0; }

/* 2026-08-25 hero redesign, continued: the domain-search presentation itself was the other half of
   the "not really nice" feedback — user gave explicit creative license to replace it as long as
   real search stays reachable. The underlying mechanism (includes/domain-search.tpl, its form
   action/ids/captcha/$featuredTlds loop/window.VercaaDomainSearch.open() wiring) is completely
   untouched below, same hard rule as always — only the CONTAINER around it changes: from a flat
   white bordered box to a glass panel that sits IN the ambient glow instead of blocking it, plus a
   few small decorative touches (orbiting dots echoing vpn.php's own established device-visual
   language elsewhere on this site, a corner "Live" badge instead of a full label row, and a
   cycling placeholder handled by homepage-hero.tpl's own small inline script). */
.vhp-hero-search-wrap { position: relative; z-index: 1; }

/* Decorative floating dots behind/around the panel -- purely ambient, aria-hidden, echoes the
   orbit-dot visual language market/vpn.php's hero already established (same technique: small
   currentColor-glow dots on a slow float, never gating any real content's visibility). */
.vhp-hero-search-orbit { position: absolute; inset: -10% -8%; z-index: 0; pointer-events: none; }
.vhp-hero-search-orbit span {
    position: absolute; width: 8px; height: 8px; border-radius: 50%;
    box-shadow: 0 0 16px 3px currentColor; animation: vhpOrbitFloat 7s ease-in-out infinite;
}
.vhp-hero-search-orbit span:nth-child(1) { top: 6%; right: 12%; color: var(--primary); background: var(--primary); animation-delay: 0s; }
.vhp-hero-search-orbit span:nth-child(2) { bottom: 14%; left: 4%; color: var(--success); background: var(--success); animation-delay: 2.3s; }
.vhp-hero-search-orbit span:nth-child(3) { top: 44%; right: -2%; color: var(--secondary); background: var(--secondary); animation-delay: 4.6s; width: 6px; height: 6px; }
@keyframes vhpOrbitFloat {
    0%, 100% { transform: translateY(0) scale(1); opacity: 0.7; }
    50% { transform: translateY(-14px) scale(1.15); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .vhp-hero-search-orbit span { animation: none; } }
@media (max-width: 640px) { .vhp-hero-search-orbit { display: none; } }

.vhp-hero-search-card {
    position: relative; z-index: 1;
    background: color-mix(in srgb, var(--bg) 78%, transparent);
    backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    border: 1px solid color-mix(in srgb, var(--primary) 16%, var(--border-muted));
    border-radius: var(--rounding-2xl);
    box-shadow: var(--elevation-4), 0 0 0 1px color-mix(in srgb, var(--primary) 6%, transparent);
    padding: 2.1rem 1.75rem 1.6rem;
    transition: box-shadow var(--duration-slow) var(--ease-standard), transform var(--duration-slow) var(--ease-standard), border-color var(--duration-slow) var(--ease-standard);
}
.vhp-hero-search-card:hover {
    box-shadow: var(--elevation-4), 0 0 0 1px color-mix(in srgb, var(--primary) 14%, transparent);
    border-color: color-mix(in srgb, var(--primary) 28%, var(--border-muted));
    transform: translateY(-2px);
}
/* Corner badge (was a full label row above the form) — smaller footprint, reads as a genuine
   product-UI status chip rather than a caption, matching the "SAMPLE DASHBOARD"/live-pill pattern
   already established on monitoring.php's own hero. */
.vhp-hero-search-live {
    position: absolute; top: -0.85rem; right: 1.5rem; z-index: 2;
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: var(--bg); border: 1px solid var(--border-muted); border-radius: 999px;
    padding: 0.4rem 0.8rem 0.4rem 0.65rem; box-shadow: var(--elevation-2);
    font-size: var(--text-xs); font-weight: 700; color: var(--text-lifted); letter-spacing: 0.02em;
}
.vhp-live-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--success); flex-shrink: 0;
    animation: vhpLivePulse 2.4s ease-out infinite;
}
@keyframes vhpLivePulse {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 55%, transparent); }
    70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--success) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .vhp-live-dot { animation: none; } }

/* Restyle the REAL, unedited-in-structure includes/domain-search.tpl markup to sit inside the card
   above — see that file's own docblock. Unchanged from the prior verified pass. */
.vhp-hero-search-card .home-domain-search { background: transparent; }
.vhp-hero-search-card .home-domain-search .container { padding: 0 !important; max-width: none !important; }
.vhp-hero-search-card .home-domain-search .p-5 { padding: 0 !important; }
.vhp-hero-search-card .home-domain-search h2 {
    font-size: var(--text-lg); font-weight: 700; color: var(--text); text-align: left; margin: 0 0 0.9rem;
}
.vhp-hero-search-card .input-group-wrapper {
    background: var(--bg-muted); border: 1px solid var(--border); border-radius: var(--rounding-lg) !important;
    box-shadow: none; padding: 0.45rem;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.vhp-hero-search-card .input-group-wrapper:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
/* 2026-08-25: bumped from 1rem/0.65rem 0.75rem for a bolder, more premium input weight, matching
   the panel's own more premium glass treatment. */
.vhp-hero-search-card .input-group-wrapper .form-control { background: transparent; color: var(--text); font-size: 1.05rem; padding: 0.8rem 0.85rem; }
.vhp-hero-search-card .input-group-wrapper .form-control::placeholder { color: var(--text-muted); }
.vhp-hero-search-card #btnDomainSearch,
.vhp-hero-search-card #btnDomainSearch2 { background: var(--primary) !important; border-color: var(--primary) !important; color: var(--white) !important; font-weight: 600; }
.vhp-hero-search-card #btnDomainSearch:hover, .vhp-hero-search-card #btnDomainSearch2:hover,
.vhp-hero-search-card #btnDomainSearch:focus, .vhp-hero-search-card #btnDomainSearch2:focus { background: var(--primary-lifted) !important; border-color: var(--primary-lifted) !important; }
.vhp-hero-search-card #btnTransfer,
.vhp-hero-search-card #btnTransfer2 { background: transparent !important; border: 1px solid var(--border) !important; color: var(--text-accented) !important; font-weight: 600; }
.vhp-hero-search-card #btnTransfer:hover, .vhp-hero-search-card #btnTransfer2:hover,
.vhp-hero-search-card #btnTransfer:focus, .vhp-hero-search-card #btnTransfer2:focus { background: var(--bg-lifted) !important; border-color: var(--border-lifted) !important; }
.vhp-hero-search-card .btn-link { color: var(--primary-accented); font-size: var(--text-sm); }
.vhp-hero-search-card .tld-logos {
    display: flex !important; flex-wrap: wrap; gap: 0.6rem; margin: 1rem 0 0 !important; padding: 0 !important;
    list-style: none; font-size: var(--text-sm) !important; color: var(--text-lifted) !important; font-weight: 600 !important;
}
.vhp-hero-search-card .tld-logos li {
    float: none !important; padding: 0.4rem 0.7rem !important; display: flex; align-items: center; gap: 0.4rem;
    background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: 999px;
}
.vhp-hero-search-card .tld-logos img { width: 20px !important; height: auto; }
.vhp-hero-search-card .domainchecker-homepage-captcha p { color: var(--text-lifted); font-size: var(--text-sm); }
.vhp-hero-search-card #inputCaptcha {
    background: var(--bg-muted); border: 1px solid var(--border); color: var(--text);
    border-radius: var(--rounding-md);
}

@media (max-width: 860px) {
    .vhp-hero-inner { grid-template-columns: 1fr; }
    .vhp-hero-copy { text-align: center; }
    .vhp-hero-chips { justify-content: center; }
    .vhp-hero-sub { margin-left: auto; margin-right: auto; }
}

/* -----------------------------------------------------------------------
   Shared button set for this page (vhp-btn-*).
   ----------------------------------------------------------------------- */
.vhp-btn {
    display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.85rem 1.6rem; border-radius: 999px;
    font-weight: 600; font-size: 1rem; text-decoration: none; cursor: pointer; border: 0;
    transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard),
        transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), filter var(--duration-fast) var(--ease-out);
}
.vhp-btn-primary { background: var(--primary); color: var(--white); box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--primary) 55%, transparent); }
.vhp-btn-primary:hover { background: var(--primary-lifted); color: var(--white); transform: translateY(-1px); }
.vhp-btn-ghost { background: transparent; color: var(--primary-accented); border: 1px solid var(--border); }
.vhp-btn-ghost:hover { background: var(--bg-muted); border-color: var(--primary); color: var(--primary-accented); }

/* =========================================================================================
   Feature cards — icon-chip + category tag + title/stat-badge + description + accent-line
   pattern, per the design brief's exact structural reference to vercaa.com's own 3 feature
   cards. Content is 100% honest/real about THIS install (cPanel, Imunify360, human support) —
   never vercaa.com's own unverifiable marketing numbers. Icons reuse the exact same path data
   already shipping elsewhere in this template (archive-box icon = clientareaproducts.tpl /
   productGroup card; shield = market/ssl.php's own SSL icon; ticket = the submitticket icon used
   in the Quick Access section below) — one consistent icon language sitewide.
   ========================================================================================= */
.vhp-features-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
@media (min-width: 860px) { .vhp-features-grid { grid-template-columns: repeat(3, 1fr); } }

.vhp-feature-card {
    display: flex; flex-direction: column;
    background: var(--bg); border: 1px solid var(--border-muted); border-radius: var(--rounding-xl);
    padding: 1.75rem; box-shadow: var(--elevation-1);
    transition: transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.vhp-feature-card:hover { transform: translateY(-4px); box-shadow: var(--elevation-3); border-color: var(--border); }

.vhp-feature-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.1rem; }
.vhp-feature-icon {
    width: 46px; height: 46px; border-radius: var(--rounding-lg); display: inline-flex; align-items: center;
    justify-content: center; background: var(--primary-soft); color: var(--primary-accented); flex-shrink: 0;
}
.vhp-feature-icon svg { width: 22px; height: 22px; }
.vhp-feature-tag {
    font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--text-muted); background: var(--bg-muted); border: 1px solid var(--border-muted);
    border-radius: 999px; padding: 0.3rem 0.65rem;
}
.vhp-feature-title {
    font-family: var(--font-heading); font-size: 1.1875rem; font-weight: 700; color: var(--text);
    margin: 0 0 0.75rem; display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; line-height: 1.35;
}
.vhp-feature-stat {
    font-family: var(--font-body); font-size: var(--text-xs); font-weight: 700;
    background: var(--primary-soft); color: var(--primary-accented); border-radius: 999px; padding: 0.25rem 0.6rem;
    white-space: nowrap;
}
.vhp-feature-desc { color: var(--text-lifted); font-size: 0.9375rem; line-height: 1.6; margin: 0; flex-grow: 1; }
.vhp-feature-foot { display: flex; align-items: center; gap: 0.85rem; margin-top: 1.35rem; }
.vhp-feature-line { flex: 1 1 auto; height: 2px; border-radius: 2px; background: linear-gradient(90deg, var(--primary), transparent); }
.vhp-feature-wave { width: 46px; height: 16px; color: var(--primary-accented); opacity: 0.55; flex-shrink: 0; }
.vhp-feature-cta {
    display: inline-flex; align-items: center; gap: 0.35rem; color: var(--primary-accented); font-weight: 600;
    font-size: 0.875rem; white-space: nowrap; text-decoration: none; flex-shrink: 0;
}
.vhp-feature-cta svg { width: 14px; height: 14px; transition: transform var(--duration-fast) var(--ease-out); }
.vhp-feature-cta:hover { color: var(--primary); text-decoration: none; }
.vhp-feature-cta:hover svg { transform: translate(2px, -2px); }

/* Per-card accent variants — success (green) and secondary (orange), matching the brief's own
   3-card color rotation (purple/orange/green icon chips) while staying entirely token-driven. */
.vhp-feature-card-success .vhp-feature-icon { background: var(--success-soft); color: var(--success-accented); }
.vhp-feature-card-success .vhp-feature-stat { background: var(--success-soft); color: var(--success-accented); }
.vhp-feature-card-success .vhp-feature-line { background: linear-gradient(90deg, var(--success), transparent); }
.vhp-feature-card-success .vhp-feature-wave { color: var(--success-accented); }

.vhp-feature-card-secondary .vhp-feature-icon { background: var(--secondary-soft); color: var(--secondary-accented); }
.vhp-feature-card-secondary .vhp-feature-stat { background: var(--secondary-soft); color: var(--secondary-accented); }
.vhp-feature-card-secondary .vhp-feature-line { background: linear-gradient(90deg, var(--secondary), transparent); }
.vhp-feature-card-secondary .vhp-feature-cta { color: var(--secondary-accented); }
.vhp-feature-card-secondary .vhp-feature-cta:hover { color: var(--secondary); }

/* Hosting Configurator (plan-tile switcher + "Your Plan" card + plan/term sliders) --
   EXTRACTED 2026-08-13 into its own shared stylesheet, css/hosting-configurator.css, linked from
   head.tpl below AND from each of the 7 market/*.php hosting pages -- same reasoning, and the
   same move already made for guest-chrome.css/domain-search-modal.css. See that file for the
   full component (unchanged rules, unchanged classes, zero visual difference here). */
/* =========================================================================================
   Market Connect service grid — REBUILT 2026-08-13 (rebuild pass 4), per direct user feedback:
   "the Market Connect section in the homepage looks veeery bad and outdated". The 7 real
   services/hrefs/copy are UNCHANGED (same SSL/360 Monitoring/NordVPN/Email Spam Filtering/
   OpenXchange/CodeGuard/SiteLock, same {$WEB_ROOT}/... links) — only the card's visual treatment
   changes, brought up to the same bar as `.vhp-feature-card` above (icon-chip + colored top
   accent + accent-line/arrow footer) instead of the flatter icon+text+plain-arrow-link pattern
   this replaces. Real "starting from" prices that already existed (Backup $2.09/mo, Monitoring
   $1.99/mo) now render as a `.vhp-service-price-chip` badge, matching `.vhp-feature-stat`'s
   pattern one section up — still no fabricated price on the other 5 cards (VPN/SSL/Website
   Security/Email/Email Security), same honesty rule as everywhere else on this page.
   4-color token rotation (primary/success/info/secondary), same set of 4 the feature cards +
   testimonials + hero blobs already establish — never a 5th invented hue.
   ========================================================================================= */
.vhp-service-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }
@media (min-width: 700px) { .vhp-service-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1100px) { .vhp-service-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 520px) { .vhp-service-grid { grid-template-columns: 1fr; } }

.vhp-service-card {
    position: relative; display: flex; flex-direction: column; align-items: flex-start; overflow: hidden;
    background: var(--bg); border: 1px solid var(--border-muted); border-radius: var(--rounding-xl);
    padding: 1.75rem; box-shadow: var(--elevation-1); text-decoration: none;
    transition: transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.vhp-service-card::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
    background: linear-gradient(90deg, var(--primary), transparent); opacity: 0.85;
}
.vhp-service-card:hover { transform: translateY(-6px); box-shadow: var(--elevation-3); border-color: var(--border); text-decoration: none; }

.vhp-service-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; width: 100%; margin-bottom: 1.1rem; }
.vhp-service-icon {
    width: 48px; height: 48px; border-radius: var(--rounding-lg); display: inline-flex; align-items: center;
    justify-content: center; background: var(--primary-soft); color: var(--primary-accented); flex-shrink: 0;
}
.vhp-service-icon svg { width: 23px; height: 23px; }
.vhp-service-price-chip {
    font-size: var(--text-xs); font-weight: 700; color: var(--text-muted); background: var(--bg-muted);
    border: 1px solid var(--border-muted); border-radius: 999px; padding: 0.3rem 0.6rem; white-space: nowrap; margin-top: 0.15rem;
}

.vhp-service-title { font-family: var(--font-heading); font-size: 1.125rem; font-weight: 700; color: var(--text); margin: 0 0 0.5rem; line-height: 1.35; }
.vhp-service-vendor { font-weight: 500; color: var(--text-muted); font-size: 0.9em; }
.vhp-service-desc { color: var(--text-lifted); font-size: 0.9375rem; line-height: 1.55; margin: 0; flex-grow: 1; }

.vhp-service-foot { display: flex; align-items: center; gap: 0.75rem; margin-top: 1.35rem; width: 100%; }
.vhp-service-line { flex: 1 1 auto; height: 2px; border-radius: 2px; background: linear-gradient(90deg, var(--primary), transparent); }
.vhp-service-cta { display: inline-flex; align-items: center; gap: 0.35rem; color: var(--primary-accented); font-weight: 600; font-size: 0.875rem; white-space: nowrap; }
.vhp-service-cta svg { width: 14px; height: 14px; transition: transform var(--duration-fast) var(--ease-out); }
.vhp-service-card:hover .vhp-service-cta svg { transform: translate(2px, -2px); }

/* Token rotation — success (green) */
.vhp-service-card-success::before { background: linear-gradient(90deg, var(--success), transparent); }
.vhp-service-card-success .vhp-service-icon { background: var(--success-soft); color: var(--success-accented); }
.vhp-service-card-success .vhp-service-line { background: linear-gradient(90deg, var(--success), transparent); }
.vhp-service-card-success .vhp-service-cta { color: var(--success-accented); }
/* Token rotation — info (blue) */
.vhp-service-card-info::before { background: linear-gradient(90deg, var(--info), transparent); }
.vhp-service-card-info .vhp-service-icon { background: var(--info-soft); color: var(--info-accented); }
.vhp-service-card-info .vhp-service-line { background: linear-gradient(90deg, var(--info), transparent); }
.vhp-service-card-info .vhp-service-cta { color: var(--info-accented); }
/* Token rotation — secondary (orange) */
.vhp-service-card-secondary::before { background: linear-gradient(90deg, var(--secondary), transparent); }
.vhp-service-card-secondary .vhp-service-icon { background: var(--secondary-soft); color: var(--secondary-accented); }
.vhp-service-card-secondary .vhp-service-line { background: linear-gradient(90deg, var(--secondary), transparent); }
.vhp-service-card-secondary .vhp-service-cta { color: var(--secondary-accented); }

/* =========================================================================================
   Testimonials — VERIFICATION-PASS FIX (2026-08-13): this used to style 6 "REAL, verbatim
   Trustpilot reviews" that turned out to be fabricated (named people/companies + invented "Key
   Results" stats — see homepage.tpl's own docblock for the full explanation of how that was
   caught and why it's a hard rule violation). The per-card rules (.vhp-testi-grid/-card/-top/
   -date/-tag/-quote*/-results*/-person/-name/-role/-company*/-foot/-tp-badge/-read) are gone with
   the markup that used them. `.vhp-testi-rating`/`-stars`/`-rating-text` stay (still used by the
   honest replacement panel below); `.vhp-testi-panel` is new. "4.6/5 on Trustpilot" is kept only
   because it's independently corroborated by two unrelated sources, not invented for this page.
   ========================================================================================= */
.vhp-testi-rating { display: flex; align-items: center; justify-content: center; gap: 0.6rem; margin: 0 0 1.5rem; flex-wrap: wrap; }
.vhp-testi-stars { display: inline-flex; gap: 0.15rem; color: var(--yellow-300); font-size: 1.15rem; letter-spacing: 0.05em; }
.vhp-testi-rating-text { color: var(--text-accented); font-weight: 600; font-size: 0.9375rem; }
.vhp-testi-rating-text strong { color: var(--text); font-weight: 700; }

.vhp-testi-panel {
    max-width: 28rem; margin: 0 auto; text-align: center;
    background: var(--bg); border: 1px solid var(--border-muted); border-radius: var(--rounding-2xl);
    padding: clamp(2.25rem, 4vw, 3rem) 2rem; box-shadow: var(--elevation-1);
}
.vhp-testi-panel .vhp-btn { margin: 0 auto; }
.vhp-testi-panel .vhp-btn svg { width: 15px; height: 15px; }

/* -----------------------------------------------------------------------
   Quick access ("How can we help" / "Your account") — every real href/lang
   key from the stock file this replaces is preserved (see homepage.tpl's
   own docblock). Unchanged.
   ----------------------------------------------------------------------- */
.vhp-quick-grid {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 2.5rem;
    background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: var(--rounding-2xl);
    padding: 2.5rem;
}
@media (max-width: 760px) { .vhp-quick-grid { grid-template-columns: 1fr; gap: 2rem; padding: 1.75rem; } }
.vhp-quick-col-title { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 700; color: var(--text); margin: 0 0 1.1rem; }
.vhp-quick-links { display: flex; flex-direction: column; gap: 0.3rem; }
.vhp-quick-link {
    display: flex; align-items: center; gap: 0.85rem; padding: 0.65rem 0.6rem; border-radius: var(--rounding-md);
    color: var(--text-accented); font-weight: 600; font-size: 0.95rem; text-decoration: none; min-height: 44px;
    transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.vhp-quick-link:hover { background: var(--bg-lifted); color: var(--primary-accented); text-decoration: none; }
.vhp-quick-link-icon {
    width: 34px; height: 34px; border-radius: var(--rounding-md); display: inline-flex; align-items: center;
    justify-content: center; background: var(--bg); border: 1px solid var(--border-muted); color: var(--primary-accented); flex-shrink: 0;
}
.vhp-quick-link-icon svg { width: 17px; height: 17px; }

/* -----------------------------------------------------------------------
   Closing CTA band. Unchanged.
   ----------------------------------------------------------------------- */
.vhp-closing-inner {
    position: relative; overflow: hidden;
    background: var(--bg-muted); border: 1px solid var(--border-muted); border-radius: var(--rounding-2xl);
    padding: clamp(3rem, 6vw, 5rem) 2rem; text-align: center;
    box-shadow: var(--elevation-2);
}
.vhp-closing-bar {
    position: absolute; top: 0; left: 0; right: 0; height: 4px;
    background: linear-gradient(90deg, var(--primary), var(--primary-lifted));
}
.vhp-closing-eyebrow {
    display: inline-flex; align-items: center; gap: 0.5rem;
    color: var(--primary-accented); font-weight: 700; font-size: var(--text-md);
    letter-spacing: 0.03em; margin: 0 0 0.75rem;
}
.vhp-closing-title { font-family: var(--font-heading); color: var(--text); font-size: clamp(1.75rem, 3.4vw, 2.75rem); font-weight: 700; margin: 0 0 0.85rem; }
.vhp-closing-sub { color: var(--text-lifted); font-size: 1.0625rem; max-width: 36rem; margin: 0 auto 2rem; line-height: 1.6; }
.vhp-closing-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
/* =============================================================================================
   18. Login — REBUILT 2026-08-13 (Phase 3 punch-list item #2 of 3, login.tpl)

   Centered "Minimal Single Column" composition (ui-ux-pro-max --design-system recommendation
   for this product/page type, palette ignored per brief): eyebrow -> headline -> subhead -> a
   real "System status" live chip -> an elevated form card -> a footer create-account line. White
   canvas (var(--bg)) throughout, same "master color = white" bar as market/ssl.php and this
   session's homepage hero — purple only as the eyebrow dot, the status-chip dot, focus glows, and
   the one solid submit button (which already gets its hover/press treatment for free from the
   sitewide .btn-primary rule ~line 359, not duplicated here). Deliberately NOT a split dark-panel
   layout — see login.tpl's own top docblock for why that would still read as a vercaa.com clone
   even in different colors. Visual language (eyebrow dot, live-pulse dot, focus-glow inputs)
   deliberately mirrors .vhp-hero-* (includes/homepage-hero.tpl) so this reads as the same product.
   ---------------------------------------------------------------------------------------------- */
/* REAL layout bug fix, direct user instruction (2026-08-13): "behind the form of login and
   registeration the frame must be full screen not just around the form." Root cause, confirmed by
   reading the rendered DOM: header.tpl (shared, out of scope) puts this page's whole markup inside
   Bootstrap's `<div class="container">` (capped ~1140-1320px, centered with gutters) — so `.vlg`
   used to ALSO cap itself at `max-width: 30rem` on top of that, meaning the "frame" (this element's
   own background + its `::before` radial blob) only ever occupied a narrow boxed-in column hugging
   the form, never reading as full screen, and had no explicit height so it was only ever as tall as
   its own padding+content (a hard cutoff to plain page background well above the footer on any
   viewport taller than the form). Fix: `.vlg` itself becomes the full-bleed "frame" — the standard
   CSS full-bleed breakout (`width: 100vw` + negative `calc(50% - 50vw)` margins escapes the
   ancestor `.container` without touching header.tpl) plus `min-height: 100vh` so the background
   reaches the bottom of the viewport instead of stopping short. The readable single-column content
   (`.vlg-intro`, `.vlg-panel`) keeps its original `max-width: 30rem`, just moved onto those two
   children (each `margin: 0 auto`) so the FORM stays exactly as narrow/centered as before — only the
   frame behind it is now genuinely full-screen. No new color: still `var(--bg)`, unchanged. */
.vlg {
    position: relative;
    background: var(--bg);
    padding: clamp(3rem, 8vw, 5.5rem) 1.25rem clamp(3.5rem, 8vw, 6rem);
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    min-height: 100vh;
}
.vlg::before {
    content: "";
    position: absolute;
    top: -6%; left: 50%;
    width: min(560px, 140%); height: min(560px, 70%);
    transform: translateX(-50%);
    background: radial-gradient(circle, var(--primary-soft) 0%, transparent 72%);
    pointer-events: none;
    z-index: 0;
}

/* width:100% + max-width (not a bare max-width alone) so this centers correctly via margin:auto
   now that its parent .vlg is 100vw wide, not 30rem — see the full-bleed fix note on .vlg above. */
.vlg-intro { position: relative; z-index: 1; text-align: center; width: 100%; max-width: 30rem; margin: 0 auto 2.25rem; }
.vlg-eyebrow {
    display: inline-flex; align-items: center; gap: 0.5rem;
    color: var(--primary-accented);
    font-weight: 700; font-size: var(--text-md); letter-spacing: 0.03em; margin: 0 0 1rem;
}
.vlg-eyebrow-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex-shrink: 0; }
.vlg-title {
    font-family: var(--font-heading); color: var(--text);
    font-size: clamp(2rem, 5vw, 2.75rem); font-weight: 700; line-height: 1.12;
    letter-spacing: -0.01em; margin: 0 0 0.85rem;
}
.vlg-sub { color: var(--text-lifted); font-size: 1rem; line-height: 1.6; margin: 0 0 1.5rem; }

/* The one honest "dynamic branded element": a live-feeling pulse, deliberately worded as a
   neutral observation ("System status") rather than an asserted verdict ("All systems
   operational") — nothing on this page has actually checked live status, so it doesn't claim to.
   Dot uses a NEW keyframe (not a reuse of .vhp-live-dot's `vhpLivePulse`) specifically because that
   one's box-shadow color is baked in as var(--success) — reusing it verbatim here would visually
   read as an unverified "all good" claim. This dot is brand-purple instead: a "this is live," not
   a verdict. Rendered as a plain `<span role="status">`, NOT a link — live-verified (fresh curl,
   no cookies) that serverstatus.php requires login on this install and 302s a guest straight back
   to this exact login page, so no hover/focus/cursor affordance is applied here: an element that
   visually invites a click but leads nowhere is worse than one that plainly doesn't. */
.vlg-status-chip {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center; gap: 0.5rem;
    background: var(--bg-muted); border: 1px solid var(--border-muted);
    color: var(--text-accented); font-size: var(--text-sm); font-weight: 600;
    padding: 0.55rem 0.95rem; border-radius: 999px;
}
.vlg-status-dot {
    width: 7px; height: 7px; border-radius: 50%; background: var(--primary); flex-shrink: 0;
    animation: vlgStatusPulse 2.4s ease-out infinite;
}
@keyframes vlgStatusPulse {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 55%, transparent); }
    70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--primary) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .vlg-status-dot { animation: none; } }

/* Same width:100%/max-width/margin:auto centering as .vlg-intro above, same reason. */
.vlg-panel { position: relative; z-index: 1; width: 100%; max-width: 30rem; margin: 0 auto; }
.vlg-form { display: block; }
.vlg-card {
    background: var(--bg);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-2xl);
    box-shadow: var(--elevation-3);
    padding: clamp(1.75rem, 5vw, 2.5rem);
}
.vlg-card-head { margin: 0 0 1.5rem; }
.vlg-card-heading { font-family: var(--font-heading); font-size: 1.05rem; font-weight: 700; color: var(--text); margin: 0 0 0.3rem; }
.vlg-card-subtext { color: var(--text-lifted); font-size: var(--text-sm); margin: 0; }

/* .vlg-flash wrapper is intentionally unstyled (no margin of its own) — flashmessage.tpl's own
   .alert already carries Bootstrap's default margin-bottom, and when there's no message the
   include renders nothing, so this wrapper naturally collapses instead of leaving a styled empty
   gap. aria-live="polite" (on the wrapper, in login.tpl — not by editing the shared partial)
   means a real wrong-password error is announced to screen readers the moment it's injected. */
.vlg-flash:empty { display: none; }

.vlg-field { margin: 0 0 1.25rem; }
.vlg-label { display: block; font-size: var(--text-sm); font-weight: 700; color: var(--text-accented); margin: 0 0 0.5rem; }
.vlg-field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin: 0 0 0.5rem; }
.vlg-field-head .vlg-label { margin: 0; }
.vlg-forgot { color: var(--text-muted); font-size: var(--text-sm); font-weight: 600; text-decoration: none; transition: color var(--duration-fast) var(--ease-out); }
.vlg-forgot:hover, .vlg-forgot:focus-visible { color: var(--primary-accented); text-decoration: underline; }

/* .input-group (Bootstrap's own class) is KEPT, not replaced — js/whmcs.js's .btn-reveal-pw click
   handler does `.closest('.input-group').find('.pw-input')`, so removing it would silently break
   the show/hide toggle. .vlg-input-wrap is an ADDITIONAL class carrying the real visual rebuild. */
.vlg-input-wrap {
    display: flex; align-items: stretch;
    background: var(--bg-muted); border: 1px solid var(--border);
    border-radius: var(--rounding-lg); overflow: hidden;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.vlg-input-wrap:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.vlg-input-icon { display: flex; }
.vlg-input-icon .input-group-text {
    background: transparent; border: none; color: var(--text-muted);
    padding: 0 0 0 0.9rem; font-size: 0.9rem;
    transition: color var(--duration-fast) var(--ease-out);
}
.vlg-input-wrap:focus-within .vlg-input-icon .input-group-text { color: var(--primary); }
.vlg-input {
    background: transparent !important; border: none !important; box-shadow: none !important;
    color: var(--text); font-size: 1rem; padding: 0.75rem 0.85rem;
}
.vlg-input::placeholder { color: var(--text-muted); }
.vlg-input-wrap .input-group-append { display: flex; }
.vlg-reveal-btn {
    background: transparent; border: none; color: var(--text-muted);
    padding: 0 0.9rem; display: flex; align-items: center;
    transition: color var(--duration-fast) var(--ease-out);
}
.vlg-reveal-btn:hover, .vlg-reveal-btn:focus-visible { color: var(--primary-accented); }
/* REAL bug fix, caught live: `.btn-default` (Bootstrap core, theme.css) sets `:focus{box-shadow:
   0 0 0 .2rem rgba(0,123,255,.25)}` — an unrelated leftover Bootstrap blue — on plain `:focus`,
   not gated to `:focus-visible`, so clicking the reveal button painted a stray blue ring clashing
   with this page's purple system. `.btn-primary` already has its own `:focus` box-shadow override
   sitewide (~line 366) for the exact same reason; `.vlg-reveal-btn` didn't, since it's a new class.
   Scoped to this one button, not a sitewide `.btn-default` change. */
.vlg-reveal-btn:focus { box-shadow: var(--focus-ring); border-radius: var(--rounding-md); }

/* Reserved-but-hidden by default (display:none, not an animated height/opacity) so there is no
   dead reserved gap on the vast majority of visits where Caps Lock never comes up, and no
   layout-shift-causing ANIMATION either (an instant display swap driven by real keyboard state
   isn't the "excessive/decorative motion" this project's brief warns against — see
   initLoginCapsLockHint() in app-shell.js). */
.vlg-capslock-hint {
    display: none; align-items: center; gap: 0.4rem;
    margin: 0.55rem 0 0; font-size: var(--text-sm); font-weight: 600; color: var(--warning);
}
.vlg-capslock-hint.is-visible { display: flex; }
.vlg-capslock-hint svg { width: 14px; height: 14px; flex-shrink: 0; }

/* In-place fix, same session: `{if $captcha->isEnabled()}` (kept verbatim, per the requirement
   to preserve that exact real gate) can be true globally while captcha.tpl's OWN inner condition
   (`isEnabledForForm($captchaForm)`) is false for this specific form — verified live on this dev
   install, where that's exactly the case. The original stock markup had no wrapping div at all, so
   that empty-inner-content state was invisible; this wrapper made it a real, visible ~1.25rem dead
   gap between the password field and the remember-me row. Fix: margin lives on the actual rendered
   child (the `.text-center...row` div captcha.tpl itself outputs) instead of the wrapper — a
   wrapper with no real child collapses to 0 height on its own, no `:empty` needed (Smarty's own
   whitespace around the {include} tag means the wrapper is never literally childless in the DOM,
   so `:empty` would never have matched anyway). */
.vlg-captcha > div { margin: 0 0 1.25rem; }

.vlg-remember { margin: 0 0 1.25rem; }
.vlg-checkbox { display: inline-flex; align-items: center; gap: 0.55rem; font-size: var(--text-sm); font-weight: 600; color: var(--text-accented); cursor: pointer; }
/* REAL BUG fix, caught live: theme.css's own core `.form-check-input` rule (Bootstrap's stock
   ".form-check" pattern) is `position: absolute; margin-left: -1.25rem;` — written assuming its
   usual parent is a `.form-check` with `position:relative; padding-left:1.25rem`, reserving a
   gutter for the absolutely-positioned box to sit in without touching the label text. `.vlg-
   checkbox` doesn't use that pattern (it's a plain inline-flex label with a `gap`), so the
   checkbox rendered absolutely positioned OVER the first two characters of "Remember Me",
   visually reading as "member Me" — confirmed live via getComputedStyle (position: absolute,
   left: 41px, overlapping the label text) before this fix. `position: static` here returns it to
   normal flex-item flow, where the existing `gap: 0.55rem` on `.vlg-checkbox` already provides
   correct spacing — scoped to this one component only, not a sitewide change to `.form-check-input`. */
.vlg-checkbox input[type="checkbox"] { position: static; width: 1rem; height: 1rem; margin: 0; cursor: pointer; }

.vlg-submit { display: block; width: 100%; padding: 0.85rem 1rem; font-size: 1rem; font-weight: 700; border-radius: var(--rounding-lg); }

.vlg-divider { display: flex; align-items: center; gap: 0.75rem; margin: 1.5rem 0; color: var(--text-muted); font-size: var(--text-sm); font-weight: 600; }
.vlg-divider::before, .vlg-divider::after { content: ""; flex: 1; height: 1px; background: var(--border-muted); }

.vlg-footer-link { text-align: center; margin: 1.5rem 0 0; color: var(--text-lifted); font-size: var(--text-sm); }
.vlg-create-link { color: var(--primary-accented); font-weight: 700; text-decoration: none; margin-left: 0.35rem; }
.vlg-create-link:hover, .vlg-create-link:focus-visible { text-decoration: underline; }

/* SSO buttons (linkedaccounts.tpl's .social-signin-btns), when a provider is actually configured
   — none are in this dev environment, but this keeps the spacing correct the moment one is. */
.vlg-panel .sub-heading, .vlg-panel .sub-heading-borderless { text-align: center; margin: 0 0 1rem; }
.vlg-panel .social-signin-btns { display: flex; flex-direction: column; gap: 0.65rem; margin: 0 0 0.5rem; }

@media (max-width: 420px) {
    .vlg { padding-left: 1rem; padding-right: 1rem; }
    .vlg-card { padding: 1.5rem 1.25rem; }
}

/* REAL bug found during independent verification (2026-08-13), same specificity-collision class
   already diagnosed and fixed for a different component at ~line 4708 ("The generic
   `:root[data-theme="dark"] .btn-default` rule ... outranks the un-prefixed rule above on
   specificity"): `.vlg-reveal-btn` (the password show/hide button) carries `btn btn-default
   btn-reveal-pw vlg-reveal-btn` — the sitewide `:root[data-theme="dark"] .btn-default` rule (3
   simple selectors: :root pseudo-class + attribute + class) outranks the plain single-class
   `.vlg-reveal-btn`/`.vlg-reveal-btn:hover`/`.vlg-reveal-btn:focus-visible` rules above, so in dark
   mode ONLY, the reveal icon was silently repainted to `var(--text)` (near-white) at rest AND on
   hover/focus-visible alike — confirmed live via getComputedStyle (`color: rgb(245, 243, 248)` in
   every state, matching --text exactly, not the intended --text-muted/--primary-accented pair).
   Net effect: the icon never dims to muted at rest and never shifts color on hover/focus in dark
   mode — the hover/focus affordance this button relies on to signal interactivity was completely
   dead specifically in dark theme, while working correctly in light theme (not touched by this
   dark-scoped override). Fix: re-declare at matching specificity, scoped to this one button only —
   same pattern as the existing ~line 4713 fix, not a change to the shared `.btn-default` rule. */
:root[data-theme="dark"] .vlg-reveal-btn {
    background: transparent;
    border-color: transparent;
    color: var(--text-muted);
}
:root[data-theme="dark"] .vlg-reveal-btn:hover,
:root[data-theme="dark"] .vlg-reveal-btn:focus-visible {
    background: transparent;
    border-color: transparent;
    color: var(--primary-accented);
}

/* =============================================================================================
   19. Registration — REBUILT 2026-08-13 (Phase 3 punch-list item #3 of 3, clientregister.tpl)

   White canvas throughout (var(--bg)), same "master color = white" bar as market/ssl.php, the
   homepage hero, and .vlg-* (login) — purple only as the eyebrow dot, rail active/complete
   markers, focus glows, and the one solid submit button. See clientregister.tpl's own docblock
   for the full layout-choice reasoning (why this is a sticky scroll-spy RAIL, not a hard
   display:none multi-panel wizard) — the short version: StatesDropdown.js, the phone field's
   intl-tel-input widget, and native HTML5 required-field validation on submit all have real,
   confirmed dependencies that a hidden-step wizard would put at risk for no functional gain,
   since nothing here is ever hidden either way.

   FIELD-RESTYLE STRATEGY, the one thing worth explaining before the rules below: the stock
   `#registration .prepend-icon / .field-icon / .field` rules (sass/_registration.scss, compiled
   into theme.min.css, loaded BEFORE this file) are left completely structurally alone — same
   absolute-positioned icon, same `.form-group` ancestor, same `.field`/`.form-control` classes —
   because `StatesDropdown.js`'s `#stateinput`-parent `.append()` and `scripts.js`'s intl-tel-input
   phone-field init (`.parents('div.form-group').find('.field-icon').hide()`) both depend on that
   exact structure, not just its appearance. Every rule below only overrides COSMETIC properties
   (color/background/border/radius/padding/shadow/transition) on the same selectors, scoped with
   `#registration` + enough classes to reliably outrank the stock 2-class rule on specificity
   alone (`#registration .prepend-icon .field-icon` = id+2class; mine below add a 3rd class,
   `#registration .form-group.prepend-icon .field-icon` = id+3class — wins by class count,
   regardless of source order, so none of this needs `!important`). Confirmed live in both themes
   and against the real phone/state fields (see this session's verification notes).
   ---------------------------------------------------------------------------------------------- */
/* REAL layout bug fix, direct user instruction (2026-08-13): same "frame must be full screen, not
   just around the form" fix as .vlg (login.tpl) above — see that rule's full comment for the root
   cause (Bootstrap's `.container`, from shared header.tpl, capping this page's whole markup width).
   `.vrg` itself never had its own `max-width` (unlike `.vlg`) — it was already only as wide as its
   `.container`-capped ancestor allowed, and had no explicit height — so the same two-part fix
   applies: full-bleed breakout (`width: 100vw` + negative `calc(50% - 50vw)` margins) so the frame's
   background/blob reaches both viewport edges, plus `min-height: 100vh` so it reaches the bottom of
   the viewport. Nothing needs to change on `.vrg-intro`/`.vrg-shell` — they already self-constrain
   to their own readable `max-width` + `margin: 0 auto`, which still centers correctly inside the
   now-wider `.vrg`. No new color: still `var(--bg)`, unchanged.

   REAL BUG caught live during verification: clientregister.tpl's outer element is
   `<div id="registration" class="vrg">` — TWO selectors on one element — and theme.css's stock
   `#registration { margin: 0; padding: 0 0 40px 0; }` (line ~12549) is an ID selector, specificity
   1-0-0, which unconditionally outranks a plain `.vrg` class rule's 0-1-0 REGARDLESS of source
   order. Confirmed via getBoundingClientRect(): `.vrg`'s own `margin-left`/`margin-right` were
   being silently discarded (computed margin stayed `0px`), so the full-bleed breakout never
   applied — `.vrg` kept `width:100vw` but with no compensating negative margin, its left edge sat
   at its parent's 15px padding inset instead of the viewport edge, and its right edge overshot the
   viewport by 15px (only invisible as a scrollbar because `#main-body{overflow-x:hidden}`, a
   pre-existing unrelated safety net, clipped it) — the same latent specificity gap was already
   silently zeroing this rule's own `padding` too. Fix: qualify the selector as `#registration.vrg`
   (specificity 1-1-0) so it reliably outranks the bare `#registration` rule for every property
   declared here, exactly the same "outrank the stock id selector with class count/id+class, not
   `!important`" pattern this section's own docblock above already uses for the `.prepend-icon`
   field rules. */
#registration.vrg {
    position: relative;
    background: var(--bg);
    padding: clamp(3rem, 8vw, 5rem) 1.25rem clamp(3.5rem, 8vw, 6rem);
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    min-height: 100vh;
}
.vrg::before {
    content: "";
    position: absolute;
    top: -4%; left: 50%;
    width: min(620px, 150%); height: min(460px, 60%);
    transform: translateX(-50%);
    background: radial-gradient(circle, var(--primary-soft) 0%, transparent 72%);
    pointer-events: none;
    z-index: 0;
}

.vrg-intro { position: relative; z-index: 1; max-width: 34rem; margin: 0 auto 3rem; text-align: center; }
.vrg-eyebrow {
    display: inline-flex; align-items: center; gap: 0.5rem;
    color: var(--primary-accented);
    font-weight: 700; font-size: var(--text-md); letter-spacing: 0.03em; margin: 0 0 1rem;
}
.vrg-eyebrow-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex-shrink: 0; }
.vrg-title {
    font-family: var(--font-heading); color: var(--text);
    font-size: clamp(2rem, 5vw, 2.75rem); font-weight: 700; line-height: 1.12;
    letter-spacing: -0.01em; margin: 0 0 0.85rem;
}
.vrg-sub { color: var(--text-lifted); font-size: 1rem; line-height: 1.6; margin: 0; }

/* ---- Shell: sticky rail + form panel ---- */
.vrg-shell {
    position: relative; z-index: 1;
    max-width: 58rem; margin: 0 auto;
    display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 2.5rem; align-items: start;
}
@media (max-width: 991.98px) {
    .vrg-shell { grid-template-columns: 1fr; gap: 0; }
}

.vrg-rail { position: sticky; top: calc(var(--header-height) + 1.75rem); }
@media (max-width: 991.98px) {
    .vrg-rail {
        position: sticky; top: var(--header-height); z-index: 5;
        background: var(--bg); padding: 0.85rem 0 1rem; margin: 0 0 1.5rem;
        border-bottom: 1px solid var(--border-muted);
    }
}

.vrg-rail-track { height: 3px; background: var(--border-muted); border-radius: 999px; margin: 0 0 1.1rem; overflow: hidden; }
.vrg-rail-fill { height: 100%; width: 0%; background: var(--primary); border-radius: 999px; transition: width 0.4s var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .vrg-rail-fill { transition: none; } }

.vrg-rail-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
@media (max-width: 991.98px) {
    .vrg-rail-list { flex-direction: row; gap: 0.4rem; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
    .vrg-rail-list::-webkit-scrollbar { display: none; }
    .vrg-rail-item { flex-shrink: 0; }
}

.vrg-rail-btn {
    display: flex; align-items: center; gap: 0.7rem; width: 100%;
    padding: 0.6rem 0.7rem; border: 0; background: transparent;
    border-radius: var(--rounding-md); text-align: left; cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-out);
}
.vrg-rail-btn:hover { background: var(--bg-muted); }
.vrg-rail-btn:focus-visible { box-shadow: var(--focus-ring); }
.vrg-rail-item.is-active .vrg-rail-btn { background: var(--primary-soft); }
@media (max-width: 991.98px) { .vrg-rail-btn { width: auto; padding: 0.5rem 0.65rem; } }

.vrg-rail-marker {
    position: relative; flex-shrink: 0;
    width: 1.85rem; height: 1.85rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--bg-muted); border: 1px solid var(--border);
    color: var(--text-muted); font-weight: 700; font-size: var(--text-sm);
    transition: background-color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.vrg-rail-item.is-active .vrg-rail-marker { background: var(--primary); border-color: var(--primary); color: var(--white); }
.vrg-rail-item.is-complete .vrg-rail-marker { background: var(--success); border-color: var(--success); color: var(--white); }
.vrg-rail-check { display: none; width: 14px; height: 14px; }
.vrg-rail-item.is-complete .vrg-rail-num { display: none; }
.vrg-rail-item.is-complete .vrg-rail-check { display: inline-block; }

.vrg-rail-copy { display: flex; flex-direction: column; min-width: 0; }
.vrg-rail-copy strong { font-family: var(--font-heading); font-weight: 700; font-size: var(--text-md); color: var(--text); }
.vrg-rail-copy small { font-size: var(--text-sm); color: var(--text-muted); }
@media (max-width: 991.98px) { .vrg-rail-copy small { display: none; } }

.vrg-panel { min-width: 0; }

/* ---- Steps ---- */
.vrg-step { scroll-margin-top: calc(var(--header-height) + 5.5rem); padding-bottom: 3rem; }
.vrg-step-head { margin: 0 0 1.4rem; }
.vrg-step-tag { display: block; color: var(--primary-accented); font-weight: 700; font-size: var(--text-sm); letter-spacing: 0.04em; text-transform: uppercase; margin: 0 0 0.5rem; }
.vrg-step-title { font-family: var(--font-heading); font-weight: 700; font-size: 1.55rem; color: var(--text); margin: 0 0 0.35rem; letter-spacing: -0.01em; }
.vrg-step-subtext { color: var(--text-lifted); font-size: var(--text-md); margin: 0; }

.vrg-card {
    background: var(--bg); border: 1px solid var(--border-muted);
    border-radius: var(--rounding-xl); box-shadow: var(--elevation-1);
    padding: clamp(1.5rem, 4vw, 2rem);
    margin: 0 0 1.25rem;
}
.vrg-card:last-child { margin-bottom: 0; }
.vrg-card-compact { padding: 1.25rem clamp(1.5rem, 4vw, 2rem); }

.vrg-card-subhead { margin: 0 0 1.1rem; }
.vrg-card-subhead-title { font-family: var(--font-heading); font-weight: 700; font-size: 1rem; color: var(--text); margin: 0 0 0.2rem; }
.vrg-card-subhead-text { color: var(--text-lifted); font-size: var(--text-sm); margin: 0; }

.vrg-optin-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.25rem; }
.vrg-optin-copy { flex: 1 1 auto; min-width: 0; }
@media (max-width: 480px) { .vrg-optin-row { flex-direction: column; gap: 0.85rem; } }

/* ---- Grids (used only for multi-field rows; single fields are plain block .vrg-field) ---- */
.vrg-grid { display: grid; gap: 1.1rem 1rem; margin: 0 0 1.1rem; }
.vrg-grid:last-child { margin-bottom: 0; }
.vrg-grid-2 { grid-template-columns: 1fr 1fr; }
.vrg-grid-3 { grid-template-columns: 1.1fr 1.1fr 0.85fr; }
@media (max-width: 640px) { .vrg-grid-2, .vrg-grid-3 { grid-template-columns: 1fr; } }
.vrg-grid > .vrg-field { margin: 0; }

.vrg-field { margin: 0 0 1.1rem; }
.vrg-card > .vrg-field:last-child { margin-bottom: 0; }
.vrg-label { display: block; font-size: var(--text-sm); font-weight: 700; color: var(--text-accented); margin: 0 0 0.45rem; }
.vrg-required { color: var(--text-muted); }
.vrg-label-optional { color: var(--text-muted); font-weight: 500; }

/* ---- Field visual system (cosmetic-only overrides — see docblock above) ---- */
#registration .form-group.prepend-icon { margin: 0; }
#registration .form-group.prepend-icon .field-icon {
    top: 0; bottom: 0; height: auto; width: 2.75rem;
    display: flex; align-items: center; justify-content: center;
    color: var(--text-muted); font-size: 0.95rem;
    transition: color var(--duration-fast) var(--ease-out);
}
#registration .form-group.prepend-icon .field-icon i { color: inherit; font-size: 0.95rem; }
#registration .form-group.prepend-icon:focus-within .field-icon { color: var(--primary); }

#registration input.field,
#registration input.form-control,
#registration select.field,
#registration select.form-control,
#registration textarea.field,
#registration textarea.form-control {
    height: auto; border-radius: var(--rounding-lg); border: 1px solid var(--border);
    background: var(--bg-muted); color: var(--text); font-size: 1rem;
    padding: 0.72rem 0.85rem;
    transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
#registration .prepend-icon .field,
#registration .prepend-icon .form-control { padding-left: 2.75rem; }
#registration input.field:focus,
#registration input.form-control:focus,
#registration select.field:focus,
#registration select.form-control:focus,
#registration textarea.field:focus,
#registration textarea.form-control:focus {
    border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); background: var(--bg); outline: none;
}
#registration .field::placeholder,
#registration .form-control::placeholder { color: var(--text-muted); }
#registration select.field,
#registration select.form-control { cursor: pointer; }

/* Generate-password ghost button — restyled to sit quietly beside the strength meter rather than
   compete with the real submit CTA (secondary action, outline treatment, not filled). */
#registration .generate-password.vrg-genpw-btn {
    height: auto; width: 100%; padding: 0.7rem 1rem;
    background: var(--bg); border: 1px solid var(--border); color: var(--text-accented);
    border-radius: var(--rounding-lg); font-weight: 600; font-size: var(--text-sm);
    transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
#registration .generate-password.vrg-genpw-btn:hover,
#registration .generate-password.vrg-genpw-btn:focus-visible {
    border-color: var(--primary); color: var(--primary-accented); background: var(--primary-soft);
}

/* Password-strength meter — id-scoped like the pre-existing #passwordStrengthBar rule (~line
   8013), but targeting THIS page's own different id (#passwordStrengthMeterBar, set by
   PasswordStrength.js's registerFormPasswordStrengthFeedback(), confirmed by reading that file —
   distinct from the account-security page's #passwordStrengthBar/pwstrength.tpl). JS sets `width`
   via inline style on this exact element (`jQuery("#passwordStrengthMeterBar").css('width', ...)`)
   so nothing here declares `width` — only track chrome + the color map + a transition, so the
   live-updating width still animates smoothly instead of snapping. */
#registration .password-strength-meter { margin: 0.35rem 0 0; }
#registration .password-strength-meter .progress {
    height: 8px; border-radius: 999px; background: var(--bg-accented); overflow: hidden; box-shadow: none;
}
#registration .password-strength-meter .progress-bar { transition: width 0.3s var(--ease-out), background-color 0.3s var(--ease-out); }
#registration .password-strength-meter .progress-bar.bg-danger { background-color: var(--error) !important; }
#registration .password-strength-meter .progress-bar.bg-warning { background-color: var(--warning) !important; }
#registration .password-strength-meter .progress-bar.bg-success { background-color: var(--success) !important; }
#registration #passwordStrengthTextLabel { margin: 0.55rem 0 0; font-size: var(--text-sm); color: var(--text-lifted); text-align: left; }

/* Captcha (classic image+code — confirmed live enabled for THIS form on this install) */
.vrg-captcha { margin: 0 0 1.25rem; }
.vrg-captcha #default-captcha-domainchecker { margin: 0; padding: 0; }
.vrg-captcha .captchaimage { display: flex; align-items: center; }
.vrg-captcha .captchaimage img {
    border-radius: var(--rounding-md); border: 1px solid var(--border-muted);
    max-width: 100%; height: auto;
}
.vrg-captcha-refresh-row { margin: -0.6rem 0 1.25rem; text-align: center; }
.vrg-captcha-refresh {
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: transparent; border: 0; padding: 0.3rem 0.5rem;
    color: var(--text-muted); font-size: var(--text-sm); font-weight: 600;
    border-radius: var(--rounding-sm); cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out);
}
.vrg-captcha-refresh svg { width: 14px; height: 14px; }
.vrg-captcha-refresh:hover, .vrg-captcha-refresh:focus-visible { color: var(--primary-accented); }
.vrg-captcha-refresh:focus-visible { box-shadow: var(--focus-ring); }

/* TOS checkbox — `.form-check` (Bootstrap's real gutter pattern: position:relative + padding-left)
   already reserves the correct space for the sitewide absolutely-positioned `.form-check-input`,
   unlike .vlg-checkbox on the login page (which didn't use `.form-check` and needed a position
   fix) — confirmed by reading theme.css's `.form-check` rule, so no override needed here, just
   spacing/typography polish. */
.vrg-tos { margin: 0 0 1.5rem; color: var(--text-accented); font-size: var(--text-sm); }
.vrg-tos a { color: var(--primary-accented); font-weight: 600; }

.vrg-submit-row { margin: 0; }
#registration .vrg-submit {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 14rem; padding: 0.9rem 1.75rem; font-size: 1rem; font-weight: 700;
    border-radius: var(--rounding-lg);
}
@media (max-width: 480px) { #registration .vrg-submit { width: 100%; } }

.vrg-footer-link { text-align: center; margin: 2rem 0 0; color: var(--text-lifted); font-size: var(--text-sm); }
.vrg-signin-link { color: var(--primary-accented); font-weight: 700; text-decoration: none; margin-left: 0.35rem; }
.vrg-signin-link:hover, .vrg-signin-link:focus-visible { text-decoration: underline; }

/* SSO buttons (linkedaccounts.tpl), inert on this dev install ($linkableProviders is falsy) but
   kept correctly spaced the moment a provider is enabled — same defensive pattern as .vlg-panel. */
.vrg-panel .sub-heading, .vrg-panel .sub-heading-borderless { text-align: center; margin: 0 0 1.5rem; }
.vrg-panel .social-signin-btns { display: flex; flex-direction: column; gap: 0.65rem; margin: 0 0 1rem; }

@media (max-width: 420px) {
    .vrg { padding-left: 1rem; padding-right: 1rem; }
    .vrg-card { padding: 1.25rem; }
}

/* REAL bug found during live verification (2026-08-13): the phone field's intl-tel-input widget
   (scripts.js ~line 44814, `.intl-tel-input` wrapper injected around #inputPhone once initialized)
   renders its own flag + dial-code box (`.selected-flag`, confirmed live via getBoundingClientRect
   at 70px wide) absolutely positioned over the LEFT of the input — but the generic icon padding
   above (`.prepend-icon .field { padding-left: 2.75rem }`, i.e. 44px, sized for MY OWN decorative
   phone icon, which the plugin correctly hides) is 26px too narrow for the plugin's own 70px-wide
   flag box, so "+1" visually overlapped the "Phone Number" placeholder's leading letter. Fixed by
   widening padding-left specifically inside `.intl-tel-input` (3 real classes + a type selector,
   confirmed via computed specificity to outrank the 2-class rule above regardless of source order)
   — scoped to exactly this widget, not a change to the generic icon-padding rule every other field
   relies on. Verified live: flag/dial-code box and placeholder no longer overlap. */
#registration .prepend-icon .intl-tel-input input.field,
#registration .prepend-icon .intl-tel-input input.form-control {
    padding-left: 4.75rem;
}

/* domainregister.tpl (2026-08-14, DEFERRED.md §11 fix) — the standalone "Register a New Domain"
   page's own search panel, replacing the broken #nexus-root Vue widget mount (see that template's
   own comment for the full root-cause writeup). A lifted card matching the sitewide
   card-sits-one-step-lighter-than-page convention (--bg-lifted, same family as .app-gradient-banner's
   sibling cards elsewhere in this order form), not a new visual language. */
.app-domainregister-search-panel {
    background: var(--bg-lifted);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    padding: 2rem;
    margin-top: 1.5rem;
    text-align: center;
}
.app-domainregister-search-heading {
    margin: 0 0 0.5rem;
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--text);
}
.app-domainregister-search-subtext {
    margin: 0 0 1.5rem;
    color: var(--text-lifted);
    font-size: var(--text-md);
}
.app-domainregister-search-form .input-group-wrapper {
    max-width: 640px;
    margin: 0 auto;
}
.app-domainregister-search-form .input-group {
    display: flex;
    flex-wrap: nowrap;
}
.app-domainregister-search-form .form-control {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    height: auto;
    padding: 0.75rem 1rem;
}
.app-domainregister-search-form .input-group-append {
    display: flex;
}
.app-domainregister-search-form .input-group-append .btn {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    white-space: nowrap;
}
.app-domainregister-search-transfer-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 1.25rem;
    color: var(--text-lifted);
    font-weight: 600;
    font-size: var(--text-sm);
    text-decoration: none;
    transition: color var(--duration-fast) var(--ease-out);
}
.app-domainregister-search-transfer-link:hover,
.app-domainregister-search-transfer-link:focus-visible {
    color: var(--primary);
    text-decoration: underline;
}
@media (max-width: 576px) {
    .app-domainregister-search-panel { padding: 1.5rem 1.25rem; }
    .app-domainregister-search-form .input-group { flex-wrap: wrap; }
    .app-domainregister-search-form .form-control {
        border-top-right-radius: var(--rounding-md);
        border-bottom-right-radius: var(--rounding-md);
        width: 100%;
    }
    .app-domainregister-search-form .input-group-append {
        width: 100%;
        margin-top: 0.75rem;
    }
    .app-domainregister-search-form .input-group-append .btn {
        border-radius: var(--rounding-md);
        width: 100%;
    }
}

/* domaintransfer.tpl (2026-08-25) — real Vercaa-branded redesign of the domain-transfer order form,
   overriding the previously-unstyled stock templates/orderforms/standard_cart/domaintransfer.tpl.
   Same "card sits one step lighter than page" convention as .app-domainregister-search-panel just
   above (--bg-lifted for the primary form card), with a secondary --bg-muted treatment for the
   aside's two informational cards to keep the form the clear primary action on the page (primary-
   action guideline: one primary CTA per screen, everything else visually subordinate). All ids/
   classes referenced by the real stock form fields (inputTransferDomain, inputAuthCode,
   transferUnavailable, captchaContainer, btnTransferDomain, addTransferLoader, addToCart) are
   untouched by this block — it only adds new wrapper/layout classes around them. */
.app-domaintransfer-grid { margin-top: 1.5rem; }

.app-domaintransfer-panel {
    background: var(--bg-lifted);
    border: 1px solid var(--border);
    border-radius: var(--rounding-lg);
    padding: 2rem;
}
.app-domaintransfer-panel-head {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    margin-bottom: 1.5rem;
}
.app-domaintransfer-panel-icon {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: var(--rounding-md);
    background: var(--primary-soft);
    color: var(--primary);
}
.app-domaintransfer-panel-heading {
    margin: 0 0 0.15rem;
    font-size: 1.375rem;
    font-weight: 800;
    color: var(--text);
}
.app-domaintransfer-panel-subtext {
    margin: 0;
    color: var(--text-lifted);
    font-size: var(--text-md);
}
.app-domaintransfer-field { margin-bottom: 1.25rem; }
.app-domaintransfer-field label {
    display: block;
    margin-bottom: 0.4rem;
    font-weight: 600;
    font-size: var(--text-md);
    color: var(--text-accented);
}
.app-domaintransfer-form .form-control {
    height: auto;
    padding: 0.75rem 1rem;
    border-radius: var(--rounding-md);
}
.app-domaintransfer-help-trigger {
    float: right;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--text-lifted);
    font-weight: 500;
    font-size: var(--text-sm);
    text-decoration: none;
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out);
}
.app-domaintransfer-help-trigger:hover,
.app-domaintransfer-help-trigger:focus-visible { color: var(--primary); }
.app-domaintransfer-help-text {
    margin: 0.5rem 0 0;
    color: var(--text-lifted);
    font-size: var(--text-sm);
    line-height: 1.5;
}
.app-domaintransfer-submit {
    width: 100%;
    padding: 0.85rem 1.5rem;
    font-weight: 700;
    border-radius: var(--rounding-md);
}
.app-domaintransfer-footnote {
    margin: 1.25rem 0 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.app-domaintransfer-aside {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    margin-top: 1.5rem;
}
.app-domaintransfer-card {
    background: var(--bg-muted);
    border: 1px solid var(--border-muted);
    border-radius: var(--rounding-lg);
    padding: 1.5rem;
}
.app-domaintransfer-card-heading {
    margin: 0 0 1rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
}
.app-domaintransfer-price-list {
    list-style: none;
    margin: 0 0 0.9rem;
    padding: 0;
}
.app-domaintransfer-price-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0.65rem;
    border-radius: var(--rounding-sm);
    border-bottom: 1px solid var(--border-muted);
    transition: background var(--duration-fast) var(--ease-out);
}
.app-domaintransfer-price-row:last-child { border-bottom: none; }
.app-domaintransfer-price-row.is-matched {
    background: var(--primary-soft);
    border-bottom-color: transparent;
}
.app-domaintransfer-price-tld {
    font-weight: 700;
    color: var(--text);
}
.app-domaintransfer-price-value {
    font-weight: 800;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.app-domaintransfer-price-suffix {
    margin-left: 0.2rem;
    font-weight: 500;
    font-size: var(--text-sm);
    color: var(--text-muted);
}
.app-domaintransfer-card-note {
    margin: 0;
    color: var(--text-lifted);
    font-size: var(--text-sm);
    line-height: 1.5;
}
.app-domaintransfer-fact-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.app-domaintransfer-fact-list li {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    color: var(--text-accented);
    font-size: var(--text-sm);
    line-height: 1.5;
}
.app-domaintransfer-fact-list li svg {
    flex: none;
    margin-top: 0.15rem;
    color: var(--success);
}
.app-domaintransfer-trust {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    padding-top: 1rem;
    border-top: 1px solid var(--border-muted);
    color: var(--text-lifted);
    font-size: var(--text-sm);
}
.app-domaintransfer-trust svg {
    flex: none;
    color: var(--success);
}

@media (max-width: 576px) {
    .app-domaintransfer-panel { padding: 1.5rem 1.25rem; }
    .app-domaintransfer-help-trigger { float: none; display: flex; margin-top: 0.35rem; }
}
