/* Togglebox design tokens (loaded globally by header.shared.tpl).
   Layers: primitives (--blue-*, --neutral-*, --brand-*, --cat-*), semantic tokens
   (--color-*, --shadow-*, --space-*), then component tokens (--sidebar-*,
   --meter-*, --toggle-*). Components consume semantic tokens; a semantic token
   swaps per theme in the html[data-theme="dark"] block below, so a component
   needs no dark override of its own. Module-owned tokens (OpenNebula sliders,
   billing bar, usage charts, operation log, terminal) live in the module. */
:root {
  /* Blue primitive ramp. Fixed: the same value in both themes, so a blue
     means one colour everywhere; semantic tokens below pick a different step
     per theme. Every theme blue is one of these steps (Bootstrap #0056b3,
     #0b7dba and #075985 retired onto the nearest step). Contrast:
       step  on #fff  on #0f172a
       100   1.7      10.4
       200   1.9       9.5
       300   2.3       7.9
       400   2.7       6.7
       500   2.7       6.6   (the brand blue)
       600   5.2       3.4
       700   5.9       3.0
       800   7.4       2.4
     Components never read a step directly; they read a role token. */
  --blue-100: #7CD0F5;
  --blue-200: #6BC8EF;
  --blue-300: #3FB7F2;
  --blue-400: #38A9E0;
  --blue-500: #29A7E6;
  --blue-600: #0473A9;
  --blue-700: #0369A1;
  --blue-800: #035B85;

  /* Brand palette */
  /* The one primary hue. Fill, text and icon roles derive from it below
     (--color-primary-fill, --color-link, --color-icon-accent); the brand blue
     itself is 2.7:1 on #fff, so it is never text or a meaning-bearing icon in
     light mode. */
  --brand-primary: var(--blue-500);
  --brand-primary-light: var(--blue-200);
  /* Deprecated text alias (kept for module CSS): light --blue-600 (5.2:1 on
     #fff); dark follows --color-link (--blue-200, 9.5:1 on the page). It is
     never a fill: under white text it measures 1.9:1 in dark. Text uses
     --color-link, fills use --color-primary-fill. */
  --brand-primary-dark: var(--blue-600);
  /* Brand tint, gradient and the commerce and highlight hues */
  --brand-primary-bg: rgba(41, 167, 230, 0.08);
  --brand-primary-gradient: linear-gradient(135deg, var(--blue-600) 0%, var(--blue-800) 100%);
  --brand-primary-deep: var(--blue-800);
  --brand-cta: #B45309;       /* #fff on it: 5.0:1 */
  --brand-cta-hover: #92400E; /* #fff on it: 7.1:1 */
  --brand-highlight: #FAAB4D;
  --brand-highlight-bg: rgba(250, 171, 77, 0.30);

  /* Status colors */
  --brand-success: #2DC76B;
  --brand-success-bg: rgba(45, 199, 107, 0.08);
  --brand-info: #0EA5E9;
  --brand-info-bg: rgba(14, 165, 233, 0.08);
  --brand-danger: #D9252E;
  --brand-danger-bg: rgba(217, 37, 46, 0.08);
  --brand-warning: #F59E0B;
  --brand-warning-bg: rgba(245, 158, 11, 0.08);

  /* Neutral primitives: fixed values, never redefined per theme. Components
     do not consume them directly; they use the semantic --color-surface-*,
     --color-fg-* and --color-border-* tokens, which point at different values
     per theme. */
  --neutral-50: #F8F9FA;
  --neutral-100: #F1F3F5;
  --neutral-200: #E9ECEF;
  --neutral-300: #DEE2E6;
  --neutral-400: #CED4DA;
  --neutral-500: #ADB5BD;
  --neutral-600: #6C757D;
  --neutral-700: #495057;
  --neutral-800: #343A40;
  --neutral-900: #212529;

  /* Deprecated: --gray-* is the old neutral ramp, now a fixed alias of
     --neutral-* (the same value in both themes). New code uses the semantic
     tokens; a fixed grey uses --neutral-*. The dark-flipped ramp survives
     only inside the module scopes listed in the compat block at the end of
     this file, for module CSS that still reads --gray-* as semantic. */
  --gray-50: var(--neutral-50);
  --gray-100: var(--neutral-100);
  --gray-200: var(--neutral-200);
  --gray-300: var(--neutral-300);
  --gray-400: var(--neutral-400);
  --gray-500: var(--neutral-500);
  --gray-600: var(--neutral-600);
  --gray-700: var(--neutral-700);
  --gray-800: var(--neutral-800);
  --gray-900: var(--neutral-900);

  /* Elevation: the one shadow scale (DESIGN.md: resting, lift, overlay,
     modal). Dark mode keeps the geometry and raises the density. */
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.05);   /* resting surface */
  --shadow-md: 0 4px 12px rgba(0,0,0,0.08);  /* hover lift, dropdowns */
  --shadow-lg: 0 8px 24px rgba(0,0,0,0.12);  /* popovers, floating panels */
  --shadow-xl: 0 20px 60px rgba(0,0,0,0.15); /* modals */
  --shadow-sticky: 0 -4px 20px rgba(0,0,0,0.15); /* bars pinned to the viewport bottom */
  --shadow-glow: 0 0 20px rgba(41, 167, 230, 0.15);
  /* Deprecated aliases of the scale above (module CSS still reads them). */
  --shadow-sm-soft: var(--shadow-sm);
  --elevation-soft-1: var(--shadow-sm);
  --elevation-soft-2: var(--shadow-sm);
  --elevation-popover: var(--shadow-lg);
  --elevation-sticky: var(--shadow-sticky);

  /* Icon sizes */
  --icon-size-sm: 16px;
  --icon-size-md: 18px;
  --icon-size-lg: 22px;

  /* Surface gradient for header cards */
  --surface-gradient: linear-gradient(135deg, #fff 0%, var(--neutral-50) 100%);

  /* Module Token Bridge (aliases to HostBill 2019 theme vars where available) */
  /* Semantic colors: resolve to theme variables with safe fallbacks */
  /* Interactive text and icons. --color-link is the text colour of links,
     text buttons and current-state labels; it clears 4.5:1 on the page, card,
     surface, striped and hovered rows in both themes (light #0369A1 5.0:1 on
     #e9ecef; dark #6BC8EF 5.5:1 on #334155). --color-icon-accent is for
     meaning-bearing icons (info, settings, tooltip triggers): 3:1 or better on
     the same surfaces (light #0473A9 4.4:1 on #e9ecef; dark #38A9E0 3.9:1 on
     #334155). */
  --color-link: var(--blue-700);
  --color-link-hover: var(--blue-800);
  --color-icon-accent: var(--blue-600);

  /* One primary. --color-primary and --color-accent are deprecated aliases
     kept because module CSS reads them as text, icon, border, fill and tint
     colours. They follow --color-icon-accent, which holds 3:1 or better on
     every surface in both themes (light 5.2:1 on #fff; dark 6.7:1 on the
     page); --brand-primary would drop light icons to 2.7:1, under the 3.2:1
     the compiled --primary (#3891F2) gave them. New code names the role:
     --color-link, --color-icon-accent, --color-primary-fill or --brand-primary
     for decoration. The numbered variants alias the role tokens. */
  --color-primary: var(--color-icon-accent);
  --color-primary-600: var(--color-primary-fill-hover);
  --color-accent: var(--color-icon-accent);
  --color-accent-600: var(--color-link);
  /* Deprecated alias of --color-link (light 5.9:1 on #fff; dark --blue-200,
     9.5:1 on the page, where the old fixed #0369A1 was 3.0:1). No theme rule
     reads it; it is not a fill (use --color-primary-fill). */
  --color-accent-700: var(--color-link);

  --color-success: var(--success, #28a745);
  /* Deprecated numbered tones: aliases of the role tokens that replaced them. */
  --color-success-700: var(--color-success-strong);
  --color-warning: var(--warning, #ffc107);
  /* Amber as text or a small fill: 5.0:1 on #fff (dark: --brand-warning). */
  --color-warning-700: #B45309;
  --color-danger: var(--danger, #dc3545);
  /* Danger on the page (error borders, error text): the fill red in light,
     a lighter red in dark where the fill red is 3.5:1. */
  --color-danger-soft: var(--color-danger-fill);
  --color-danger-700: var(--color-danger-fill);

  /* Fills under white text (filled buttons, current page chip). Each stays at
     least 4.5:1 with #fff in both themes; do not alias them to --color-primary
     or --color-danger, which lighten in dark mode. */
  --color-primary-fill: var(--blue-600);
  --color-primary-fill-hover: var(--blue-800);
  --color-danger-fill: #dc2626;
  --color-danger-fill-hover: #b91c1c;
  /* Same in both themes, like the fills above: #fff on it 5.0:1, hover
     7.1:1. The brand green measured 2.2:1. */
  --color-success-fill: #15803d;
  --color-success-fill-hover: #166534;

  /* Secondary buttons (supporting actions, Close/Cancel): the primary
     reversed, a white fill with the primary blue as border and text, filling
     with that blue on hover as the danger-subtle buttons fill red. Text 5.2:1
     at rest (#0473a9 on #fff) and on hover (#fff on #0473a9). The 1px border
     keeps heights equal to the primary button's. The border always follows
     the text at rest and the fill on hover, so a theme overrides only the
     fill and text. */
  --color-secondary-fill: var(--color-bg-elevated, #fff);
  --color-secondary-fill-hover: var(--color-primary-fill);
  --color-secondary-fg: var(--color-primary-fill);
  --color-secondary-fg-hover: var(--color-fg-inverse, #fff);
  --color-secondary-border: var(--color-secondary-fg);
  --color-secondary-border-hover: var(--color-secondary-fill-hover);

  /* Neutrals */
  --color-bg: var(--white, #fff);
  --color-surface: var(--light, #f8f9fa);
  /* Surfaces, nearest the page first: --color-bg; --color-surface-subtle
     (quiet panel fill: the light surface, the dark card tone);
     --color-surface; --color-surface-3 (striped rows, pressed controls; the
     name predates the ordering, it sits between --color-surface and
     --color-surface-2 in both themes); --color-surface-2. */
  --color-surface-subtle: var(--neutral-50);
  --color-surface-2: var(--neutral-200);
  --color-surface-3: var(--neutral-100);
  /* Hover fill for a control resting on --color-surface: a small step toward
     --color-fg, derived so it follows the configurator surface. --color-fg
     stays at 13.6:1 on it, and it stays distinct from --color-border. */
  --color-surface-hover-mix: 96%;
  --color-surface-hover: color-mix(in srgb, var(--color-surface) var(--color-surface-hover-mix), var(--color-fg));
  --color-border-subtle: var(--neutral-200); /* hairline row and section dividers */
  --color-border: var(--neutral-300);
  --color-border-strong: var(--neutral-400);
  /* Form-control boundary (inputs, selects, textareas): at least 3:1 against
     the field and the surface it sits on (WCAG 1.4.11). #868e96 measures
     3.3:1 on #fff and 3.2:1 on --color-surface; --color-border was 1.3:1. */
  --input-border: #868e96;
  --color-fg: var(--dark, #212529);
  --color-fg-strong: #111827;
  --color-fg-subtle: var(--neutral-700);
  --color-fg-subtle-strong: #556274;
  --color-fg-muted: #5f676e;
  --color-fg-inverse: #fff;
  --color-bg-elevated: var(--white, #fff);
  --surface-card: var(--color-bg-elevated, #fff);
  --surface-card-strong: var(--color-bg, #fff);
  --text-inverse: var(--color-fg-inverse, #fff);

  /* Typography & spacing */
  --font-base: var(--font-family-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif);
  /* System mono stack. No mono web font is loaded (IBM Plex Mono was named
     here but never shipped, so only visitors who had it installed saw it). */
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  --font-size-xs: var(--font-size-11); /* the step below --font-size-sm */
  --font-size-10: 10px;        /* Status badge text, compact annotations */
  --font-size-11: 11px;        /* Micro labels, compact annotations */
  --font-size-sm: 12px;        /* (existing) */
  --font-size-12: var(--font-size-sm);
  --font-size-13: 13px;        /* Small body text, nav links, status labels, monospace content */
  --font-size-md: 14px;        /* (existing) */
  --font-size-14: var(--font-size-md);
  --font-size-15: 15px;        /* Button text, action labels */
  --font-size-lg: 16px;        /* (existing) */
  --font-size-16: var(--font-size-lg);
  --font-size-18: 18px;        /* Section headings, card titles, tile values */
  --font-size-20: 20px;        /* Display metrics, hero values */
  --font-size-22: 22px;        /* Compact page titles, metric values on narrow screens */
  --font-size-24: 24px;        /* H1 headings, large metric displays, close glyphs */
  --font-size-28: 28px;        /* Large display values */

  /* Type roles (DESIGN.md Typography). Components name the role, not a
     number. Body is the dense portal body: 14px, set on #content-cloud and
     .template_togglebox through --slick-font-body; form controls use the
     field role (16px) so iOS does not zoom. */
  --text-headline-size: var(--font-size-24);
  --text-headline-weight: 600;
  --text-headline-line-height: 1.25;
  --text-title-size: var(--font-size-18);
  --text-title-weight: 600;
  --text-title-line-height: 1.3;
  --text-body-size: .875rem;
  --text-body-weight: 400;
  --text-body-line-height: 1.45;
  --text-field-size: var(--font-size-16);
  --text-field-weight: 400;
  --text-field-line-height: 1.25;
  --text-control-size: var(--font-size-13);
  --text-control-weight: 500;
  --text-control-line-height: 1.25;
  --text-control-tracking: .4px;
  --text-data-size: var(--font-size-14);
  --text-data-weight: 400;
  --text-data-line-height: 1.35;
  --text-label-size: var(--font-size-12);
  --text-label-weight: 600;
  --text-label-line-height: 1.25;
  --text-label-tracking: .03em;
  --text-mono-size: var(--font-size-13);
  --text-mono-weight: 400;
  --text-mono-line-height: 1.45;
  --font-weight-400: 400;
  --font-weight-500: 500;
  --font-weight-600: 600;
  --font-weight-650: 650;
  --font-weight-700: 700;
  --font-weight-800: 800;
  --font-weight-normal: var(--font-weight-400, 400);
  --font-weight-medium: var(--font-weight-500, 500);
  --font-weight-semibold: var(--font-weight-600, 600);
  --font-weight-bold: var(--font-weight-700, 700);
  --line-height-100: 1;
  --line-height-115: 1.15;
  --line-height-120: 1.2;
  --line-height-125: 1.25;
  --line-height-130: 1.3;
  --line-height-133: 1.33;
  --line-height-135: 1.35;
  --line-height-140: 1.4;
  --line-height-145: 1.45;
  --line-height-150: 1.5;
  --line-height-md: var(--line-height-140, 1.4);
  --letter-spacing-000: 0;
  --letter-spacing-010: 0.01em;
  --letter-spacing-020: 0.02em;
  --letter-spacing-030: 0.03em;
  --letter-spacing-040: 0.04em;
  --letter-spacing-050: 0.05em;
  --letter-spacing-100: 0.1em;
  --letter-spacing-050px: 0.5px;
  --letter-spacing-normal: var(--letter-spacing-000, 0);
  --letter-spacing-wide: var(--letter-spacing-030, 0.03em);
  --letter-spacing-wider: var(--letter-spacing-040, 0.04em);
  --letter-spacing-widest: var(--letter-spacing-100, 0.1em);
  --letter-spacing-caps: var(--letter-spacing-050px, 0.5px);

  --space-0: 0;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-7: 32px;
  --space-8: 48px;
  --space-inline-xs: 4px;
  --space-inline-sm: 8px;
  --space-inline-md: 12px;

  /* Readable line length for prose blocks (help text, ledes, empty states). */
  --measure-prose: 65ch;

  /* Canonical responsive breakpoints — documentation only.
     CSS custom properties cannot be used in @media conditions.
     Use these raw px values directly in any @media rules. */
  --bp-phone-max: 479px;
  --bp-mobile-max: 767px;
  --bp-tablet-min: 768px;
  --bp-tablet-max: 1023px;
  --bp-desktop-min: 1024px;
  --bp-wide-min: 1400px;
  /* Off-canvas boundary of the OpenNebula VM drawer: a left drawer at
     <= --bp-offcanvas-max, a rail above it. */
  --bp-offcanvas-max: 993px;
  --bp-offcanvas-min: 994px;
  /* Theme sidebar tiers: an off-canvas drawer at <= --bp-sidebar-drawer-max
     (phones), then an icon rail that expands on hover; from
     --bp-offcanvas-min the user can pin it to the full column, which is the
     default from --bp-wide-min. */
  --bp-sidebar-drawer-max: 767px;
  /* Bootstrap-aligned tier, only for rules that adjust a compiled Bootstrap
     grid column or a legacy -sm utility: 575.98px (sm) and 1199px/1200px
     (xl). Every other @media uses the values above; theme CSS uses no others
     (640, 768 max, 992, 480 and 576 were folded in). */
  --bp-bs-sm-max: 575.98px;
  --bp-bs-xl-max: 1199px;
  --bp-bs-xl-min: 1200px;

  /* Input/context sizing */
  --touch-target-min: 44px;
  --touch-target-compact: 40px;
  /* Square row action button (the settings gear): compact with a mouse,
     --touch-target-min on coarse pointers (override below). */
  --oneb-action-btn-size: 32px;

  /* Shared control and media sizing */
  --control-min-h-sm: 28px;
  --control-min-h-md: 40px;
  --size-captcha-w: 120px;
  --size-captcha-h: 60px;
  --size-icon-chip-w: 50px;
  --size-icon-chip-h: 50px;
  --table-col-xs: 20px;
  --table-col-sm: 50px;
  --table-col-md: 100px;
  --iframe-width: 100%;
  --btn-icon-opacity: .85;
  --invoice-paper-bg: #fff;
  --invoice-paper-fg: #111827;
  --invoice-paper-border: #e2e8f0;
  --action-offset-sm: 12px;
  --loader-page-bg: #f7fbff;
  --loader-inline-bg: #e0eefd;

  /* Radii & shadows */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 12px;
  --radius-pill: 999px;
  --shadow-control-inset: inset 0 1px 2px rgba(0,0,0,0.03);
  --shadow-brand-sm: 0 2px 4px rgba(41,167,230,0.2);
  --shadow-brand-md: 0 4px 10px rgba(41,167,230,0.28);
  /* Neutral like the rest of the elevation scale: depth, not an amber glow. */
  --shadow-cta-sm: 0 1px 3px rgba(0,0,0,0.12);
  --shadow-cta-md: 0 4px 12px rgba(0,0,0,0.14);
  /* White CTA labels stay at least 4.5:1 across the whole gradient. */
  --brand-cta-gradient: linear-gradient(135deg, var(--brand-cta) 0%, var(--brand-cta-hover) 100%);
  --shadow-focus-ring: rgba(41,167,230,0.1);
  --focus-ring-strong: rgba(41,167,230,0.3);
  /* Opaque focus outline for content surfaces: 5.2:1 on --color-bg, 4.4:1 on
     --color-surface-2. Outlines use this; --focus-ring-strong and
     --shadow-focus-ring are translucent halos only (1.2-2:1, never the sole
     indicator). The dark sidebar re-points it at --sidebar-focus. */
  --focus-outline: var(--blue-600);
  /* The focus recipe every control shares: a solid --focus-outline ring,
     offset outside the box (inset by the negative offset where a parent
     clips overflow, e.g. table cells and scroll rails). */
  --focus-outline-width: 2px;
  --focus-outline-offset: 2px;
  --focus-outline-offset-inset: -2px;

  /* Semantic gradients */
  /* Filled under white text (.btn-success), so it runs between the success
     fill tokens rather than the brand green. */
  --brand-success-gradient: linear-gradient(135deg, var(--color-success-fill) 0%, var(--color-success-fill-hover) 100%);
  --brand-danger-gradient: linear-gradient(135deg, var(--color-danger-fill) 0%, var(--color-danger-fill-hover) 100%);

  /* Semantic status colors */
  --brand-muted: #9BA0B3;
  --brand-accent: #6f42c1;
  --status-inactive-bg: #fce4ec;
  --status-inactive-fg: #c2185b;
  /* Tone tokens for pills, alerts, badges and small status text. Text uses
     --color-*-strong, which stays at least 4.5:1 on its own tint over every
     surface (page, card, striped row). The brand hue is kept for the tint
     (--color-*-bg) and the border (--color-*-border) only. */
  --color-success-strong: #166534;
  --color-info-strong: var(--blue-800);
  --color-primary-strong: var(--blue-800);
  --color-warning-strong: #92400e;
  --color-danger-strong: #b91c1c;
  --color-muted-strong: var(--color-fg-subtle, #495057);
  --color-success-bg: var(--brand-success-bg);
  --color-success-border: var(--brand-success);
  --color-info-bg: var(--brand-info-bg);
  --color-info-border: var(--brand-info);
  --color-primary-bg: var(--brand-primary-bg);
  --color-primary-border: var(--brand-primary);
  --color-warning-bg: var(--brand-warning-bg);
  --color-warning-border: var(--brand-warning);
  --color-danger-bg: var(--brand-danger-bg);
  --color-danger-border: var(--brand-danger);
  --color-muted-bg: var(--color-surface-2);
  --color-muted-border: var(--color-border);
  /* Input placeholder text: at least 4.5:1 on the field in both themes, and
     unchanged on focus (the compiled theme lightens it). */
  --color-placeholder: #6b7280;
  --alert-warning-text: #856404;


  --badge-label-bg: rgba(41, 167, 230, 0.1);
  /* The label sits on the always-dark sidebar: 5.2:1 on its tint over a
     hovered row; the brand blue was 3.3:1. */
  --badge-label-fg: var(--sidebar-focus);

  /* Tag/category color palette (8-color, hash-assignable)
     All colors must pass WCAG AA 4.5:1 contrast with white text.
     Colors 1,2,4,6,7 use hardcoded dark values instead of token aliases
     because the brand tokens are too bright for white-text legibility. */
  --tag-color-1: var(--blue-600);                   /* darkened blue,   5.2:1 (was #0b7dba, off the ramp) */
  --tag-color-2: #15803d;                           /* darkened green,  ~5.0:1 (#178a4c measured 4.4:1, under AA) */
  --tag-color-3: var(--brand-accent, #6f42c1);      /* purple,          ~5.5:1 */
  --tag-color-4: #b45309;                           /* darkened amber,  ~4.7:1 */
  --tag-color-5: var(--status-inactive-fg, #c2185b); /* rose,           ~5.9:1 */
  --tag-color-6: var(--blue-800);                   /* darkened sky,    7.4:1 */
  --tag-color-7: var(--cat-7);                      /* indigo,          ~7.9:1 (was Bootstrap #0056b3, a second blue) */
  --tag-color-8: var(--color-fg-subtle, #495057);    /* gray,           ~5.9:1 */

  --overlay-white-10: rgba(255,255,255,0.1);
  --overlay-white-15: rgba(255,255,255,0.15);
  --overlay-white-20: rgba(255,255,255,0.2);
  --border-subtle-inverse: rgba(255,255,255,0.1);
  /* The one scrim behind modals, drawers and slide-overs. */
  --color-scrim: rgba(15, 23, 42, 0.5);
  --color-backdrop: var(--color-scrim); /* deprecated alias */
  /* Selected state of a choice card, a current wizard step or a picked row:
     a 2px ring in this colour over the primary tint. The ring is 4.1:1 or
     better on every light surface and its tint (#0473A9); dark 4.8:1 or
     better (#7cd0f5, the focus colour). Never the only cue: pair it with the
     native radio or checkbox, or aria-current. */
  --color-selected-border: var(--color-primary-fill);
  --color-selected-bg: var(--color-primary-bg);
  /* Slide-over panel width (native <dialog>, components in slick-components.css). */
  --slideover-width: 480px;
  /* Tooltip edge: none in light (#333 on the page is 12.6:1); dark draws one,
     because #020617 is only 1.1:1 against the #0f172a page. */
  --tooltip-border: transparent;

  /* Categorical palette: types, tiers, chart series, billing segments. Eight
     hues in a fixed order (blue, green, amber, violet, pink, teal, indigo,
     slate), never the status meaning of a hue. Each value is at least 3:1
     (as a mark) against the page, card, surface and hovered row in its own
     theme. Light values also carry #fff text at 4.5:1 or better, so a filled
     light chip can use them; in dark they are marks and text only (pair a
     filled dark chip with --color-bg as text). Module palettes alias these. */
  --cat-1: var(--blue-600); /* 5.2:1 on #fff, 4.4:1 on #e9ecef */
  --cat-2: #15803D; /* 5.0 / 4.2 */
  --cat-3: #B45309; /* 5.0 / 4.2 */
  --cat-4: #7C3AED; /* 5.7 / 4.8 */
  --cat-5: #BE185D; /* 6.0 / 5.1 */
  --cat-6: #0F766E; /* 5.5 / 4.6 */
  --cat-7: #4338CA; /* 7.9 / 6.7 */
  --cat-8: #475569; /* 7.6 / 6.4 */

  /* Tooltip/dropdown tokens */
  --tooltip-bg: #333;
  --tooltip-fg: #fff;
  --dropdown-bg: var(--color-bg, #fff);
  --dropdown-fg: var(--color-fg-subtle, #495057);
  --dropdown-border: var(--color-border, #dee2e6);
  --dropdown-hover-bg: var(--color-surface, #f8f9fa);

  /* Dark-surface foreground: icons and text on the navy navbar panel, which
     stays dark in both themes. Deliberately not redefined for dark mode. */
  --color-on-dark-surface: #f8fafc;
  --color-on-dark-surface-muted: #cbd5e1; /* 9.9:1 on --sidebar-surface */

  /* Sidebar palette (always a dark surface). The compiled sidebar takes its
     background from the HostBill admin "menu-bg" setting; these tokens mirror
     it so the hand-edited states (hover, active, focus) stay on palette.
     Active labels are #fff on --brand-primary-deep (7.4:1); the focus outline
     is at least 3:1 on the surface, hover and active fills. */
  --sidebar-surface: #1e293b;
  --sidebar-surface-sunken: #17202d;
  --sidebar-hover-bg: #334155;
  --sidebar-fg: #dce2ec;
  --sidebar-fg-strong: #fff;
  --sidebar-fg-muted: #b7beca;
  --sidebar-active-bg: var(--brand-primary-deep);
  --sidebar-active-fg: #fff;
  --sidebar-focus: var(--blue-100);
  --sidebar-divider: color-mix(in srgb, var(--sidebar-fg-strong) 8%, transparent);
  --sidebar-current-bg: color-mix(in srgb, var(--sidebar-fg-strong) 6%, transparent);
  --sidebar-scrollbar-rail: var(--sidebar-surface);
  --sidebar-scrollbar-thumb: #64748b;
  --sidebar-scrollbar-thumb-hover: #94a3b8;
  /* Collapsed icon rail. The expanded width stays the compiled sidebar width
     (the HostBill admin setting), so only the rail is a token. */
  --sidebar-rail-width: 72px;

  /* OpenNebula module compatibility tokens */
  --oneb-table-row-even: var(--color-surface, #f8f9fa);
  --oneb-table-row-odd: var(--color-bg, #fff);
  --oneb-table-row-hover: var(--color-surface-2, #e9ecef);
  --oneb-table-row-muted: var(--color-surface-subtle);
  --oneb-table-head-bg: var(--color-surface-3);
  --oneb-table-cell-fg: var(--color-fg-subtle, #495057);
  --oneb-table-cell-border: var(--color-border, #dee2e6);

  --oneb-status-placeholder-bg: var(--color-surface-2, #e9ecef);
  --oneb-status-placeholder-fg: var(--color-fg-subtle, #495057);
  --oneb-status-action-bg: var(--color-bg-elevated, #fff);
  --oneb-status-action-fg: var(--color-primary-strong); /* 7.4:1 on #fff; brand blue 2.7:1 */
  --oneb-status-action-border: var(--color-border, #dee2e6);
  --oneb-status-link: var(--color-primary-strong);
  --oneb-status-link-hover: var(--color-primary-600);

  /* White glyph on it: 5.2:1 (the translucent brand blue gave 2.5:1). */
  --oneb-mobile-toggle-bg: var(--color-primary-fill);
  --oneb-mobile-toggle-border: rgba(255, 255, 255, 0.52);
  --oneb-mobile-toggle-fg: var(--color-fg-inverse, #fff);
  --oneb-mobile-menu-bg: var(--color-bg-elevated, #fff);
  --oneb-mobile-menu-border: var(--color-border, #dee2e6);
  --oneb-mobile-menu-link: var(--color-fg-strong, #212529);
  --oneb-mobile-menu-link-hover-bg: var(--color-surface, #f8f9fa);
  --oneb-mobile-menu-link-hover-fg: var(--color-fg-strong, #111827);
  --oneb-mobile-menu-current-bg: var(--color-primary-fill); /* #fff 5.2:1 */
  --oneb-mobile-menu-current-fg: var(--color-fg-inverse, #fff);

  --oneb-modal-backdrop: rgba(15, 23, 42, 0.56);
  --oneb-modal-bg: var(--color-bg-elevated, #fff);
  --oneb-modal-border: var(--color-border, #dee2e6);
  --oneb-modal-fg: var(--color-fg, #212529);
  --oneb-modal-fg-muted: var(--color-fg-muted, #5f676e);
  --color-bg-modal: var(--oneb-modal-bg, #fff);


  --oneb-progress-bg: var(--brand-primary-bg, rgba(41, 167, 230, 0.08));
  --oneb-progress-border: var(--brand-primary);


  /* Shell chrome: the fixed top navbar (keep in step with $navbar-height in
     src/scss/variables.scss) and the clearance that focus and anchor
     scrolling keep below it. */
  --navbar-height: 72px;
  --shell-scroll-padding-top: calc(var(--navbar-height) + var(--space-4));
  /* The fixed live-chat launcher (#chat-widget-container) at the viewport
     bottom, and the two clearances derived from it: --livechat-safe-bottom
     lifts bottom-fixed bars and shortens the scroll container (plus the
     device safe area); --chat-widget-clearance is the room the off-canvas
     sidebar leaves at its end (plus a gap). Modules use these, never a
     literal 63px. */
  --chat-widget-height: 63px;
  --livechat-safe-bottom: calc(var(--chat-widget-height) + env(safe-area-inset-bottom));
  --chat-widget-clearance: calc(var(--chat-widget-height) + 1px + var(--space-4));

  /* Z-index scale. Page layers follow Bootstrap's 10-step tiers (1000-1080);
     the off-canvas pair sits below them. Two tiers sit above everything on
     purpose: --z-overlay (9999, full-page blocking overlays, PNotify's own
     layer) and --z-toast (10600, above PNotify stacks). Nothing else uses
     values outside this list. */
  --z-nav: 100;
  --z-context: 99;
  --z-dropdown: 1000;         /* Standard dropdowns (Bootstrap convention) */
  --z-sticky: 1020;           /* Sticky headers/footers */
  --z-fixed: 1030;            /* Fixed position elements */
  --z-queue: 1035;            /* Upload queues and operation trays: above fixed bars, below modal backdrops */
  --z-modal-backdrop: 1040;   /* Modal overlay background */
  --z-modal: 1050;            /* Modal dialog content */
  --z-slideover: var(--z-modal); /* Slide-over panels not on the top layer (a native <dialog> needs none) */
  --z-popover: 1060;          /* Popovers */
  --z-tooltip: 1070;          /* Tooltips (highest non-nav) */
  --z-nav-dropdown: 1080;     /* Navigation dropdowns (above tooltips) */
  --z-offcanvas-backdrop: 850; /* Off-canvas drawer backdrop (theme sidebar, VM drawer) */
  --z-offcanvas: 1000;        /* Off-canvas left drawers; below modal backdrops */
  --z-overlay: 9999;          /* Full-page blocking overlay */

  /* Transitions */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.2s ease;
  --transition-slow: 0.3s ease;

  /* Motion for shared components: one ease-out curve, two durations. Module
     motion tokens (--rgw-motion-*, --checkmk-motion-*) alias these. */
  --motion-fast: 150ms;
  --motion-base: 200ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* How long transient feedback stays ("Copied", "Saved") before reverting. */
  --motion-feedback: 1500ms;

  /* Shared component tokens (.slick-toggle, .slick-meter, .slick-code, .slick-toast) */
  --z-toast: 10600;           /* Above modals (1050) and PNotify stacks */
  --shadow-control: 0 1px 3px rgba(22, 33, 43, 0.2);
  --toggle-edge: var(--color-fg-muted);
  --toggle-on: var(--color-primary-fill);
  --meter-track: color-mix(in srgb, var(--color-surface-2) 78%, var(--color-border));
  /* Meter fills keep 3:1 against the track (WCAG 1.4.11), so light mode uses the
     deeper fills; the brand hues are 1.7-2.2:1 on the light track. */
  --meter-fill-primary: var(--color-primary-fill);
  --meter-fill-success: var(--color-success-fill);
  --meter-fill-warning: #B45309;
  --meter-fill-danger: var(--color-danger-fill);
  --color-code-bg: #152133;
  --color-code-fg: #d9e6f5;
  --color-code-muted: rgba(217, 230, 245, 0.7);
  --color-code-accent: #f9e2af;
  /* Status tones on the code surface (terminal logs, operation cards). The
     surface is dark in both themes, so these are fixed: each is at least
     5.8:1 on #152133 and 7:1 on the dark-mode #060b16. */
  --color-code-success: #5bd6a0;
  --color-code-danger: #ff6b6b;
  --color-code-info: #6cb6ff;
  /* Primary-tinted surfaces (selected rows, quota cards, bulk bars): the
     elevated surface with 4 / 8 / 12% brand blue mixed in. */
  --surface-tint-primary-1: color-mix(in srgb, var(--brand-primary) 4%, var(--color-bg-elevated));
  --surface-tint-primary-2: color-mix(in srgb, var(--brand-primary) 8%, var(--color-bg-elevated));
  --surface-tint-primary-3: color-mix(in srgb, var(--brand-primary) 12%, var(--color-bg-elevated));

  /* Sticky offsets */
  --sticky-top-offset: 96px;
  --cloud-sticky-gap: 24px;

  /* Cloud layout sizing */
  --cloud-content-max-sm: 520px;
  --cloud-content-max-md: 760px;
  --cloud-content-max-lg: 920px;
  --cloud-content-max-xl: 1040px;

  --cloud-modal-content-max-sm: 560px;
  --cloud-modal-content-max-md: 720px;
  --cloud-modal-content-max-lg: 920px;
  --cloud-modal-content-max-xl: 960px;
  --cloud-modal-detail-gap: var(--space-2, 8px);
  --cloud-modal-detail-padding: var(--space-3, 12px);
}

@media (pointer: coarse) {
  :root {
    --oneb-action-btn-size: var(--touch-target-min);
  }
}

html[data-theme="dark"] {
  --brand-primary: var(--blue-400);
  --brand-primary-light: var(--blue-200);
  --brand-primary-dark: var(--color-link); /* deprecated alias, see :root */
  --brand-primary-deep: var(--blue-800);
  /* --brand-primary-gradient is not overridden: starting at the light --primary
     (#3FB7F2) left white button text at 2.3:1. The :root ramp (blue-600 to
     blue-800) keeps it at 5.2:1 or better in both themes. */
  /* A fill colour in both themes (buttons, .badge-danger, .bg-danger carry
     #fff on it: 4.8:1). No red is both 4.5:1 under white and 4.5:1 on the
     dark page, so danger text uses --color-danger-strong (#fca5a5, 9.1:1 on
     its tint); #D9252E as text measured 3.5:1. */
  --brand-danger: #dc2626;

  --color-bg: #0f172a;
  --color-bg-elevated: #111827;
  --color-surface: #1e293b;
  --color-surface-2: #334155;
  --color-surface-3: #1f2937;
  --color-surface-subtle: #111827;
  /* Smaller step than light so the fill (about #2a3446) stays distinct from
     --color-border (#334155); --color-fg on it: 10.1:1. */
  --color-surface-hover-mix: 94%;
  --color-border-subtle: #334155;
  --color-border: #334155;
  --color-border-strong: #475569;
  --input-border: #64748b; /* 3.7:1 on --surface-card, 3.1:1 on --color-surface */
  --color-fg: #e2e8f0;
  --color-fg-strong: #f8fafc;
  --color-fg-subtle: #cbd5e1;
  --color-fg-subtle-strong: #cbd5e1;
  /* 4.8:1 on --color-surface-2 and a hovered row (#334155), 8.2:1 on the
     page; #94a3b8 dipped to 4.0:1 there. */
  --color-fg-muted: #a3b1c6;
  --color-fg-inverse: #fff;
  --surface-card: #111827;
  --surface-card-strong: #0f172a;
  --text-inverse: #f8fafc;
  /* Link 5.5:1 on --color-surface-2 and a hovered row, 9.5:1 on the page;
     the dark brand blue (#38A9E0) was 3.9:1 on #334155, so it stays the icon
     accent (3:1 for non-text) and is not link text. */
  --color-link: var(--blue-200);
  --color-link-hover: var(--blue-100);
  --color-icon-accent: var(--blue-400);
  --color-warning-700: var(--brand-warning); /* 8.3:1 on the page */
  --color-primary-fill: var(--blue-600);
  --color-primary-fill-hover: var(--blue-800);
  --color-danger-fill: #dc2626;
  --color-danger-fill-hover: #b91c1c;
  /* The primary blue is under 3:1 on the dark surfaces, so at rest the
     border and text use the light focus blue on the surface itself: 8.5:1 on
     --color-surface, 6.0:1 on --color-surface-2. Hover fills with the primary
     blue (#fff text 5.2:1). */
  --color-secondary-fill: transparent;
  --color-secondary-fg: var(--focus-outline);

  --surface-gradient: linear-gradient(145deg, #0f172a 0%, #1e293b 100%);
  --brand-primary-bg: color-mix(in srgb, var(--brand-primary) 16%, transparent);
  --brand-cta: #B45309;       /* #fff on it: 5.0:1 */
  --brand-cta-hover: #92400E; /* #fff on it: 7.1:1 */
  --brand-highlight: #FAAB4D;
  --brand-highlight-bg: rgba(250, 171, 77, 0.30);
  --status-inactive-bg: rgba(244, 114, 182, 0.16);
  --status-inactive-fg: #f9a8d4;
  --color-success-strong: #86efac;
  --color-info-strong: #7dd3fc;
  --color-primary-strong: var(--blue-100); /* 4.7:1 on its tint over a hovered #334155 row */
  --color-warning-strong: #fcd34d;
  --color-danger-strong: #fca5a5;
  --color-muted-strong: #cbd5e1;
  --color-placeholder: #94a3b8;
  --color-danger-soft: #F87171;
  --alert-warning-text: #fcd34d;

  --shadow-sm: 0 1px 3px rgba(0,0,0,0.35);
  --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
  --shadow-lg: 0 8px 24px rgba(0,0,0,0.5);
  --shadow-xl: 0 20px 60px rgba(0,0,0,0.6);
  --shadow-glow: 0 0 24px color-mix(in srgb, var(--brand-primary) 20%, transparent);
  --shadow-sticky: 0 -4px 20px rgba(2,6,23,0.55);
  --shadow-control-inset: inset 0 1px 2px rgba(0,0,0,0.45);
  --shadow-control: 0 1px 3px rgba(2, 6, 23, 0.45);
  /* Code surfaces sit below the page (#0F172A) so the block keeps an edge. */
  --color-code-bg: #060b16;
  --color-code-fg: #e2e8f0;
  --color-code-muted: rgba(226, 232, 240, 0.65);
  --color-code-accent: #fcd34d;
  /* On the #334155 dark track the bright hues clear 3:1; full red does not. */
  --meter-fill-primary: var(--brand-primary);
  --meter-fill-success: var(--brand-success);
  --meter-fill-warning: var(--brand-warning);
  --meter-fill-danger: var(--color-danger-soft);
  --shadow-brand-sm: 0 2px 8px color-mix(in srgb, var(--brand-primary) 35%, transparent);
  --shadow-brand-md: 0 6px 16px color-mix(in srgb, var(--brand-primary) 40%, transparent);
  /* Dark mode: neutral, denser shadows (DESIGN.md elevation); a coloured CTA
     shadow on a dark page reads as a glow. */
  --shadow-cta-sm: 0 2px 4px rgba(0,0,0,0.45);
  --shadow-cta-md: 0 4px 12px rgba(0,0,0,0.5);
  --brand-cta-gradient: linear-gradient(135deg, var(--brand-cta) 0%, var(--brand-cta-hover) 100%);
  --shadow-focus-ring: color-mix(in srgb, var(--brand-primary) 25%, transparent);
  --focus-ring-strong: color-mix(in srgb, var(--brand-primary) 40%, transparent);
  --focus-outline: var(--blue-100); /* 6.0:1 on --color-surface-2, 10.4:1 on --color-bg */

  --tooltip-bg: #020617;
  --tooltip-fg: #f8fafc;

  --badge-label-bg: color-mix(in srgb, var(--brand-primary) 20%, transparent);
  --badge-label-fg: var(--sidebar-focus); /* 6.0:1 on its tint over the sidebar hover */

  /* Dark-mode tag colors: keep white text legible (WCAG AA 4.5:1) */
  --tag-color-5: #881337;   /* rose-900, contrast 9.57:1 */
  --tag-color-6: var(--blue-700); /* #fff on it 5.9:1 */
  --tag-color-8: #334155;   /* slate-700, contrast 10.35:1 */
  --tag-color-7: #4338CA;   /* tags carry #fff text, so not the dark --cat-7 */

  /* Categorical palette, dark: each at least 3:1 on #334155 (a hovered row)
     and 4.9:1 or better on the page, card and --color-surface. */
  --cat-1: var(--blue-400); /* 6.7:1 on #111827, 3.9:1 on #334155 */
  --cat-2: #4ADE80; /* 10.2 / 5.9 */
  --cat-3: #FBBF24; /* 10.6 / 6.2 */
  --cat-4: #A78BFA; /* 6.5 / 3.8 */
  --cat-5: #F472B6; /* 6.7 / 3.9 */
  --cat-6: #2DD4BF; /* 9.5 / 5.6 */
  --cat-7: #818CF8; /* 6.0 / 3.5 */
  --cat-8: #94A3B8; /* 6.9 / 4.0 */

  --overlay-white-10: rgba(255,255,255,0.1);
  --overlay-white-15: rgba(255,255,255,0.15);
  --overlay-white-20: rgba(255,255,255,0.2);
  --border-subtle-inverse: rgba(148,163,184,0.24);
  --color-scrim: rgba(2, 6, 23, 0.6);
  --color-selected-border: var(--focus-outline);
  --tooltip-border: var(--color-border-strong);
  --loader-page-bg: var(--color-bg, #0f172a);
  --loader-inline-bg: var(--color-bg-elevated, #111827);

  --oneb-table-row-even: #1e293b;
  --oneb-table-row-odd: #0f172a;
  --oneb-table-row-hover: #334155;
  --oneb-table-row-muted: #1f2937;
  --oneb-table-head-bg: #1e293b;
  --oneb-table-cell-fg: #cbd5e1;
  --oneb-table-cell-border: #334155;

  --oneb-status-placeholder-bg: #334155;
  --oneb-status-placeholder-fg: #e2e8f0;
  --oneb-status-action-bg: #1f2937;
  --oneb-status-action-fg: var(--color-link);
  --oneb-status-action-border: #475569;
  --oneb-status-link: var(--color-link);
  --oneb-status-link-hover: var(--color-link-hover);

  --oneb-mobile-toggle-bg: var(--color-primary-fill); /* #f8fafc glyph 5.0:1; was 3.2:1 */
  --oneb-mobile-toggle-border: color-mix(in srgb, var(--brand-primary) 60%, transparent);
  --oneb-mobile-toggle-fg: #f8fafc;
  --oneb-mobile-menu-bg: #111827;
  --oneb-mobile-menu-border: #334155;
  --oneb-mobile-menu-link: #e2e8f0;
  --oneb-mobile-menu-link-hover-bg: #1e293b;
  --oneb-mobile-menu-link-hover-fg: #f8fafc;
  --oneb-mobile-menu-current-bg: var(--color-primary-fill); /* #f8fafc 5.0:1; #3fb7f2 was 2.2:1 */
  --oneb-mobile-menu-current-fg: #f8fafc;

  --oneb-modal-backdrop: rgba(2, 6, 23, 0.76);
  --oneb-modal-bg: #0f172a;
  --oneb-modal-border: #334155;
  --oneb-modal-fg: #e2e8f0;
  --oneb-modal-fg-muted: var(--color-fg-muted, #94a3b8);
  --color-bg-modal: var(--oneb-modal-bg, #0f172a);

  --oneb-progress-bg: var(--brand-primary-bg);
  --oneb-progress-border: var(--brand-primary);

  --sidebar-surface: #020617;
  --sidebar-surface-sunken: #000104;
  --sidebar-hover-bg: #1e293b;
}

/* Respect reduced motion globally for our design system */
@media (prefers-reduced-motion: reduce) {
  :root {
    --animation-duration: 0s;

    /* Every token consumer (sidebar branch expand, chevrons, hover fades)
       changes state instantly instead of animating. */
    --transition-fast: 0s linear;
    --transition-normal: 0s linear;
    --transition-slow: 0s linear;
  }
}

/* Dark-mode compatibility for module CSS that still reads the old --gray-*
   ramp as semantic. Outside this scope --gray-* is fixed in both themes.
   The ramp follows the dark semantic tokens where a step matches one; the
   other steps (300, 400, 500, 800) keep the old values.

   Cloudflare master: cf-components.css reads --gray-50/100/700/800 (16
   uses) inside #content-cloud.cloudflare-client-area and in the DNS modal,
   which cloudflare-components.js appends to <body>. The retired example
   module's deploy copy renders in #content-cloud.rgw-client-area:
   example-components.css reads no --gray-* directly, but rgw-tokens.css
   declares the s3cmd help-icon tints from --gray-100/700 on :root, where
   the ramp is fixed, so they are redeclared here to resolve against the
   flipped ramp. rgw itself reads no --gray-*.
   Remove once cf-components.css reads the semantic tokens. */
html[data-theme="dark"] :is(#content-cloud.cloudflare-client-area, #content-cloud.rgw-client-area, body > .cf-dns-modal-overlay) {
  --gray-50: var(--color-bg-elevated);
  --gray-100: var(--color-surface-3);
  --gray-200: var(--color-surface-2);
  --gray-300: #475569;
  --gray-400: #64748b;
  --gray-500: #94a3b8;
  --gray-600: var(--color-fg-subtle);
  --gray-700: var(--color-fg);
  --gray-800: #f1f5f9;
  --gray-900: var(--color-fg-strong);
  --rgw-help-icon-s3cmd-bg: var(--gray-100);
  --rgw-help-icon-s3cmd-fg: var(--gray-700);
}
