/*
 * site-system.css — the Latenncy Tweaks website design system.
 *
 * One file, one vocabulary. Every shared primitive on the site is defined or
 * re-defined here so thirteen pages inherit one look instead of drifting into
 * thirteen.
 *
 * Load order (tested): this sheet goes immediately BEFORE
 * /assets/css/desktop-tokens.css, which is immediately before
 * /assets/css/desktop-shell.css. That means:
 *   - desktop-tokens.css wins any token name it also declares, so everything
 *     new here is namespaced --ds-* and never shadows an app token;
 *   - desktop-shell.css wins any selector it also writes, so shell chrome
 *     (header, account menu, page shell) is edited there, not fought here;
 *   - style.css / interactive.css / components.css / home.css / admin.css /
 *     checkout.css all load earlier, so a `.desktop-web`-prefixed rule here
 *     beats them without !important.
 *
 * The material rule, in one line: glass floats, soft extrusion sits, buttons
 * are flat. Nothing is two of those.
 */

/* ===========================================================================
 * 1. TOKENS
 * ======================================================================== */
:root {
  /* --- Accent -------------------------------------------------------------
   * One functional accent, taken from the desktop app's --series-usage so the
   * product and the site agree on what "interactive" looks like. It is spent
   * only on primary actions, links, focus rings and the current nav item.
   * Everything else stays monochrome; a second accent is how that stops being
   * true. 7.03:1 on --surface-base, so it carries body text, not just large.
   */
  --ds-accent: #4f9cf9;
  --ds-accent-hover: #6fb0fb;
  --ds-accent-press: #3d87e2;
  /* Label colour on an accent fill. Dark ink measures ~7.2:1 there; white is
     3.0:1 and fails AA. */
  --ds-accent-ink: #06121f;
  --ds-accent-soft: rgba(79, 156, 249, 0.13);
  --ds-accent-line: rgba(79, 156, 249, 0.42);
  --ds-focus: var(--ds-accent);
  --ds-focus-on-accent: #ffffff;

  /* --- Radius -------------------------------------------------------------
   * Five steps. Controls 8, floating 12, panels 16, largest surfaces 22.
   * Pills are reserved for status; a pill button is not in this system.
   * Nested corners are concentric: inner = outer - padding.
   */
  --ds-r-xs: 6px;
  --ds-r-sm: 8px;
  --ds-r-md: 12px;
  --ds-r-lg: 16px;
  --ds-r-xl: 22px;
  --ds-r-pill: 999px;

  /* --- Elevation ----------------------------------------------------------
   * Soft extrusion, not drop shadows. e1 is the app's own --raised; e2 and e3
   * add ambient depth under the same lit top edge, so a card and a knob read
   * as one material at two heights. e0 is a hairline only.
   */
  --ds-e0: inset 0 0 0 1px rgba(255, 255, 255, 0.045);
  --ds-e1: var(--raised);
  --ds-e2:
    inset 0 1px 0 rgba(255, 255, 255, 0.09),
    inset 0 0 0 1px rgba(255, 255, 255, 0.065),
    0 2px 4px rgba(0, 0, 0, 0.45),
    0 12px 28px -14px rgba(0, 0, 0, 0.62);
  --ds-e3:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    0 4px 10px rgba(0, 0, 0, 0.5),
    0 20px 44px -18px rgba(0, 0, 0, 0.72);
  --ds-e4:
    0 8px 20px rgba(0, 0, 0, 0.55),
    0 36px 80px -24px rgba(0, 0, 0, 0.8);
  /* Fields, tracks and wells: the extrusion inverted. */
  --ds-recess: var(--recessed);

  /* --- Glass --------------------------------------------------------------
   * Three tiers and no more. Tier 1 is page chrome, tier 2 is anything that
   * pops open, tier 3 is anything that takes the screen. A fourth tier would
   * only mean glass landed on something that is not floating.
   */
  --ds-glass-1-fill: color-mix(in srgb, var(--surface-sidebar) 66%, transparent);
  --ds-glass-1-blur: blur(14px) saturate(150%);
  /* 78% is the desktop application's own popover fill; matching it keeps the
     account menu on the site and the menu in the app the same material. */
  --ds-glass-2-fill: color-mix(in srgb, var(--surface-floating) 78%, transparent);
  --ds-glass-2-blur: blur(22px) saturate(155%);
  --ds-glass-3-fill: color-mix(in srgb, #151515 80%, transparent);
  --ds-glass-3-blur: blur(30px) saturate(160%);
  /* A lone 1px white border over a blur is the tell. A real rim is lit on top,
     shadowed underneath, and gathers a little light just inside itself. */
  --ds-glass-edge:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 0 0 1px rgba(255, 255, 255, 0.05),
    inset 0 -1px 0 rgba(0, 0, 0, 0.34),
    inset 0 10px 24px -16px rgba(255, 255, 255, 0.14);

  /* --- Spacing rhythm ----------------------------------------------------- */
  --ds-1: 4px;
  --ds-2: 8px;
  --ds-3: 12px;
  --ds-4: 16px;
  --ds-5: 24px;
  --ds-6: 32px;
  --ds-7: 48px;
  --ds-8: 64px;
  --ds-9: 96px;

  /* --- Layout -------------------------------------------------------------
   * 1060px is the width the shell already uses for page content; the header
   * runs wider. One section rhythm, one gutter.
   */
  --ds-container: 1060px;
  --ds-container-wide: 1360px;
  --ds-container-narrow: 720px;
  --ds-gutter: clamp(20px, 4vw, 40px);
  --ds-section-y: clamp(56px, 7vw, 96px);
  --ds-header-h: 76px;

  /* --- Type ---------------------------------------------------------------
   * Cal Sans throughout (self-hosted; the CSP forbids Google Fonts).
   * Hierarchy comes from weight and colour as much as from size.
   */
  --ds-text-display: clamp(32px, 4vw, 48px);
  --ds-text-h1: clamp(26px, 3vw, 36px);
  --ds-text-h2: clamp(21px, 2.1vw, 27px);
  --ds-text-h3: 17px;
  --ds-text-lede: 15.5px;
  --ds-text-body: 14px;
  --ds-text-small: 13px;
  /* Two steps between small and micro, both of which the site was already
     using as bare literals in a dozen sheets. --ds-text-help is the line under
     a field or a control ("3 of 5 device resets left"); --ds-text-tag is the
     one size every tag, chip, pill and table caption is set at. */
  --ds-text-help: 12.5px;
  --ds-text-tag: 12px;
  --ds-text-micro: 11px;
  --ds-lede-measure: 62ch;
  --ds-body-measure: 68ch;

  /* --- Motion -------------------------------------------------------------
   * Frame-aligned durations inherited from the app, plus one slower step for
   * things that travel. Two easings: --ds-ease for state, --ds-ease-emphasis
   * for anything with mass.
   */
  --ds-motion-press: var(--motion-press);
  --ds-motion-fast: var(--motion-fast);
  --ds-motion-normal: var(--motion-normal);
  --ds-motion-slow: 400ms;
  --ds-ease: var(--ease-out);
  --ds-ease-emphasis: cubic-bezier(0.16, 1, 0.3, 1);

  /* --- Surfaces the primitives below are made of -------------------------- */
  --ds-hairline: var(--line);
  --ds-hairline-strong: var(--line-strong);
  --ds-panel-fill: var(--card);
  --ds-panel-fill-hover:
    var(--grain-layer),
    linear-gradient(158deg, #181818 0%, #171717 46%, #161616 100%);
  --ds-well-fill: var(--surface-sunken);

  /* Flat control fills. No gradient, no sheen, no inner extrusion: these are
     the only values a button face is allowed to take. */
  --ds-control-fill: rgba(255, 255, 255, 0.055);
  --ds-control-fill-hover: rgba(255, 255, 255, 0.095);
  --ds-control-fill-press: rgba(255, 255, 255, 0.035);
  /*
   * 44, not 40.
   *
   * The token said 40 and the site said 44. Counted at 1440 with a fine
   * pointer, every control on every page -- the header's brand, nav, account
   * trigger and nav toggle, the five `.buy-button`s, the account page's three
   * row actions, the admin nav and its search, the booking dialog's fields and
   * its submit, the customer roster, the checkout's actions -- is 44, each one
   * pinned there by a rule of its own. Exactly two controls read this token and
   * therefore stood 4px shorter than everything else wearing the same face:
   * the home hero's "Choose a service" and the contact form's "Send message",
   * the primary call to action on the site's two most-visited pages.
   *
   * So the token now says what the site already does, and it is also the §12
   * touch floor, which means the value no longer changes under a coarse
   * pointer. The `@media (pointer: coarse)` block further down is kept: it
   * still reaches the handful of controls that state a height of their own.
   */
  --ds-control-h: 44px;
  --ds-control-h-sm: 32px;
}

/* ===========================================================================
 * 2. AMBIENT BACKDROP
 *
 * backdrop-filter frosts nothing over a flat #0a0a0a. This fixed layer is what
 * the header and the hero cards are made of glass *against*: two or three very
 * low-opacity cool lights plus the site's own grain, which dithers away the
 * banding a gradient this wide would otherwise show. Depth, not decoration —
 * no hue, no animation, no blobs.
 *
 * style.css declares its own body::before dither; this replaces it, and
 * desktop-shell.css no longer switches it off.
 * ======================================================================== */
body.desktop-web::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  display: block;
  pointer-events: none;
  opacity: 1;
  background:
    var(--grain-layer),
    radial-gradient(120% 78% at 16% -12%, rgba(152, 172, 202, 0.06), transparent 62%),
    radial-gradient(86% 64% at 88% 2%, rgba(124, 146, 180, 0.04), transparent 58%),
    radial-gradient(120% 86% at 52% 106%, rgba(118, 136, 170, 0.032), transparent 62%);
  background-size:
    var(--texture-grain-size) var(--texture-grain-size),
    auto, auto, auto;
}

/* ===========================================================================
 * 3. BASE
 * ======================================================================== */
/* clip, never hidden: hidden makes this a scroll container and the sticky
   header stops sticking. This bit the site once already. */
html,
body.desktop-web {
  max-width: 100%;
  overflow-x: clip;
}

body.desktop-web {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font);
  font-size: var(--ds-text-body);
  line-height: 1.6;
  letter-spacing: -0.012em;
  color: var(--text);
  background: var(--surface-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.desktop-web ::selection {
  background: var(--ds-accent);
  color: var(--ds-accent-ink);
}

/* A tap on a control should look like the control's own :active state, not like
   a grey rectangle the browser drew over it. Was an inline <style> on five
   pages and a body rule in services.css. */
.desktop-web * { -webkit-tap-highlight-color: transparent; }

/*
 * The page shell and its one entrance.
 *
 * This was defined three ways: home.css, services.css (under a keyframe name of
 * its own) and an inline <style> block repeated verbatim on about, contact,
 * privacy and tos — the last of which ran 0.42s on a different curve, so four
 * of the six pages arrived at a different speed from the other two. One
 * keyframe, the system's slow step and the system's emphasis easing. The global
 * prefers-reduced-motion block at the end of this file stops it.
 */
.desktop-web #app { min-height: 60vh; }
/* style.css's scroll reveal starts every section at opacity 0 and waits for a
   script to add .visible. Nothing on this site should be invisible while it
   waits for JavaScript, so the reveal is switched off at the root. */
.desktop-web #app .animate { opacity: 1; transform: none; transition: none; }
@keyframes ds-page-enter {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
.desktop-web #app.page-enter {
  animation: ds-page-enter var(--ds-motion-slow) var(--ds-ease-emphasis);
}

/* One link treatment. The accent is the only colour on the page, so a link is
   unmistakable without an underline shouting from every paragraph. */
.desktop-web :is(p, li, dd, .ds-prose) a:not([class]),
.desktop-web .ds-link {
  color: var(--ds-accent);
  text-decoration: underline;
  text-decoration-color: var(--ds-accent-line);
  text-underline-offset: 3px;
  transition:
    color var(--ds-motion-fast) var(--ds-ease),
    text-decoration-color var(--ds-motion-fast) var(--ds-ease);
}
.desktop-web :is(p, li, dd, .ds-prose) a:not([class]):hover,
.desktop-web .ds-link:hover {
  color: var(--ds-accent-hover);
  text-decoration-color: currentColor;
}

/* ===========================================================================
 * 4. TYPOGRAPHY AND THE PAGE HIERARCHY PATTERN
 *
 * Every page opens the same way: eyebrow, h1, lede. Every section below it
 * repeats the pattern one step smaller. The existing page classes are mapped
 * onto the same three roles, so pages inherit the pattern with no markup
 * changes.
 * ======================================================================== */
.desktop-web :is(h1, h2, h3, h4) {
  font-weight: 600;
  letter-spacing: -0.02em;
  text-wrap: balance;
  color: var(--text);
}

/* Only labels belong in this list. `.band-head` and `.supported-head` are
   section-head CONTAINERS, and text-transform inherits: adding them here put a
   whole heading and its lede into capitals. */
.desktop-web :is(.ds-eyebrow, .page-eyebrow) {
  display: block;
  margin: 0 0 var(--ds-3);
  color: var(--text-faint);
  font-size: var(--ds-text-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  line-height: 1.4;
  text-transform: uppercase;
}

.desktop-web :is(.ds-display, .hero-title) {
  margin: 0;
  font-size: var(--ds-text-display);
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-weight: 600;
}

.desktop-web :is(.ds-title, .page-title) {
  margin: 0;
  max-width: 22ch;
  font-size: var(--ds-text-h1);
  line-height: 1.1;
  letter-spacing: -0.028em;
}

.desktop-web :is(.ds-section-title, .section-head h2) {
  margin: 0;
  font-size: var(--ds-text-h2);
  line-height: 1.18;
  letter-spacing: -0.024em;
}

/* Page-owned section heads (home.css's .band-head / .faq-head / .opening-body)
   are deliberately NOT in this list: they carry their own margins and their
   page agent applies .ds-lede to them. Only the shared marketing classes from
   style.css are claimed here. */
.desktop-web :is(.ds-lede, .page-subtitle, .section-head p) {
  margin: var(--ds-3) 0 0;
  max-width: var(--ds-lede-measure);
  color: var(--text-muted);
  font-size: var(--ds-text-lede);
  line-height: 1.65;
  text-wrap: pretty;
}

.desktop-web .ds-body {
  max-width: var(--ds-body-measure);
  color: var(--text-muted);
  font-size: var(--ds-text-body);
  line-height: 1.7;
}

.desktop-web .ds-mono,
.desktop-web .acct-mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ===========================================================================
 * 5. LAYOUT
 * ======================================================================== */
.desktop-web .ds-container {
  width: 100%;
  max-width: var(--ds-container);
  margin-inline: auto;
  padding-inline: var(--ds-gutter);
}
.desktop-web .ds-container--narrow { max-width: var(--ds-container-narrow); }
.desktop-web .ds-container--wide { max-width: var(--ds-container-wide); }

.desktop-web .ds-section { padding-block: var(--ds-section-y); }
.desktop-web .ds-section--tight { padding-block: calc(var(--ds-section-y) * 0.6); }
.desktop-web .ds-section + .ds-section { border-top: 1px solid var(--ds-hairline); }

.desktop-web .ds-section-head {
  max-width: var(--ds-lede-measure);
  margin: 0 0 var(--ds-7);
}

.desktop-web .ds-divider {
  height: 1px;
  margin-block: var(--ds-6);
  border: 0;
  background: var(--ds-hairline);
}

.desktop-web .ds-grid { display: grid; gap: var(--ds-5); }
.desktop-web .ds-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.desktop-web .ds-grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.desktop-web .ds-stack { display: grid; gap: var(--ds-4); }

/* ===========================================================================
 * 6. SURFACES
 *
 * Three materials, and the rule for choosing between them:
 *   glass  — floats over the page and moves independently of it
 *   raised — part of the page surface, extruded out of it
 *   well   — part of the page surface, pressed into it
 * ======================================================================== */

/* --- Glass tiers ---------------------------------------------------------- */
.desktop-web .ds-glass-1 {
  -webkit-backdrop-filter: var(--ds-glass-1-blur);
  backdrop-filter: var(--ds-glass-1-blur);
  background: var(--ds-glass-1-fill);
  box-shadow: var(--ds-glass-edge);
}
.desktop-web .ds-glass-2 {
  -webkit-backdrop-filter: var(--ds-glass-2-blur);
  backdrop-filter: var(--ds-glass-2-blur);
  background: var(--ds-glass-2-fill);
  border-radius: var(--ds-r-md);
  box-shadow: var(--ds-glass-edge), var(--ds-e3);
}
.desktop-web .ds-glass-3 {
  -webkit-backdrop-filter: var(--ds-glass-3-blur);
  backdrop-filter: var(--ds-glass-3-blur);
  background: var(--ds-glass-3-fill);
  border-radius: var(--ds-r-xl);
  box-shadow: var(--ds-glass-edge), var(--ds-e4);
}

/* --- Raised (soft extrusion) ---------------------------------------------- */
.desktop-web .ds-raised {
  border: 0;
  border-radius: var(--ds-r-lg);
  background: var(--ds-panel-fill);
  box-shadow: var(--ds-e2);
}

/* --- Well (recessed) ------------------------------------------------------ */
.desktop-web .ds-recessed {
  border: 0;
  border-radius: var(--ds-r-sm);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}

/* --- Panels and cards -----------------------------------------------------
 * Every card surface on the site, from the marketing pages to the admin
 * console, is the same object. The page-specific class names are listed here
 * rather than restyled page by page; that is the whole point of this file.
 */
.desktop-web :is(
  .ds-card,
  .ds-panel,
  .dashboard-panel,
  .stat-card,
  .admin-card,
  .admin-card-panel,
  .admin-table-panel,
  .affiliate-card,
  .lt-card,
  .feature-card,
  .step-card,
  .service-card,
  .service-card-new,
  .about-card-new,
  .stat-block,
  .contact-form-box,
  .home-faq-shell,
  .payment-panel,
  .tool-panel,
  .success-box,
  .db-card,
  .db-panel,
  .customer-record,
  .customer-action,
  .device-panel
) {
  border: 0;
  border-radius: var(--ds-r-lg);
  background: var(--ds-panel-fill);
  background-clip: padding-box;
  box-shadow: var(--ds-e2);
}

.desktop-web :is(.ds-card, .ds-panel, .dashboard-panel, .admin-card, .lt-card, .affiliate-card, .contact-form-box, .about-card-new, .stat-card) {
  padding: var(--ds-5);
}
/* Card padding steps down with the viewport. DESIGN §5 said desktop-shell.css
   handled this, but it only ever did it for .dashboard-panel, so every other
   card on the site kept 24px of padding inside a 350px-wide phone column. */
@media (max-width: 800px) {
  .desktop-web :is(.ds-card, .ds-panel, .dashboard-panel, .admin-card, .lt-card, .affiliate-card, .contact-form-box, .about-card-new, .stat-card) {
    padding: 20px;
  }
}
@media (max-width: 420px) {
  .desktop-web :is(.ds-card, .ds-panel, .dashboard-panel, .admin-card, .lt-card, .affiliate-card, .contact-form-box, .about-card-new, .stat-card) {
    padding: var(--ds-4);
  }
}

.desktop-web .ds-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ds-4);
  margin-bottom: var(--ds-3);
}
.desktop-web :is(.ds-card-title, .panel-headline) {
  margin: 0;
  font-size: var(--ds-text-h3);
  font-weight: 600;
  letter-spacing: -0.015em;
}
.desktop-web .ds-card-lede {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  line-height: 1.6;
}

/* A card lifts only if it is a link or a control. Static cards do not move. */
.desktop-web :is(.ds-card--interactive, .lt-card--interactive, a.ds-card) {
  cursor: pointer;
  transition:
    box-shadow var(--ds-motion-normal) var(--ds-ease),
    background var(--ds-motion-normal) var(--ds-ease),
    transform var(--ds-motion-normal) var(--ds-ease-emphasis);
}
.desktop-web :is(.ds-card--interactive, .lt-card--interactive, a.ds-card):hover {
  background: var(--ds-panel-fill-hover);
  box-shadow: var(--ds-e3);
  transform: translateY(-2px);
}

/* The one glass card: tiles that sit over the hero artwork, where there is
   something behind them worth seeing. Anywhere else this is a grey box that
   costs a compositing layer. */
.desktop-web .ds-card--glass {
  border: 0;
  border-radius: var(--ds-r-xl);
  background: var(--ds-glass-3-fill);
  -webkit-backdrop-filter: var(--ds-glass-3-blur);
  backdrop-filter: var(--ds-glass-3-blur);
  box-shadow: var(--ds-glass-edge), var(--ds-e4);
}

/* Key/value strips read as one pressed surface split by hairlines. */
.desktop-web .transcript-summary {
  gap: 1px;
  overflow: hidden;
  border-radius: var(--ds-r-lg);
  background: rgba(255, 255, 255, 0.06);
  box-shadow: var(--ds-recess);
}
.desktop-web .transcript-summary > div { background: var(--ds-well-fill); }

/* ===========================================================================
 * 7. BUTTONS — FLAT, ALWAYS
 *
 * The client's rule, verbatim: "the only thing I don't like about neumorphism
 * is the buttons, it just looks very AI."
 *
 * So: solid fills, hairline outlines, crisp 8px corners. No inner-shadow
 * extrusion, no pillowy double shadow, no gradient face, no travelling sheen.
 * The button is the one thing on this site that is not made of the surface.
 *
 * SPECIFICITY NOTE, because this is easy to break: an :is() list takes the
 * specificity of its MOST specific argument, so one `.a:not(.b)` or one
 * `.wrap button` inside a list silently outranks every plain class rule that
 * follows. Every list below therefore holds simple classes only, and the four
 * variants are all (0,2,0) and decided by source order: base, secondary,
 * primary, ghost, danger. Controls that can only be reached by a descendant
 * selector are assigned at the end, where their higher specificity is
 * deliberate rather than accidental.
 * ======================================================================== */

/* --- Base geometry, shared by every button ------------------------------- */
.desktop-web :is(
  .ds-btn,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .site-secondary,
  .checkout-recover-action,
  .site-danger,
  .primary-button,
  .neutral-button,
  .danger-button,
  .buy-button,
  .hero-button,
  .send-btn,
  .finish,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
) {
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-2);
  min-height: var(--ds-control-h);
  padding: 0 var(--ds-4);
  border: 1px solid transparent;
  border-radius: var(--ds-r-sm);
  background: var(--ds-control-fill);
  background-image: none;
  color: var(--text);
  font-family: var(--font);
  font-size: var(--ds-text-small);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  isolation: auto;
  overflow: visible;
  filter: none;
  box-shadow: none;
  transition:
    background-color var(--ds-motion-fast) var(--ds-ease),
    border-color var(--ds-motion-fast) var(--ds-ease),
    color var(--ds-motion-fast) var(--ds-ease),
    transform var(--ds-motion-press) var(--ds-ease);
}

/* The metallic sheen was a child element, not just a gradient. Remove the
   child or it keeps painting over the flat face. */
.desktop-web :is(
  .ds-btn,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .primary-button,
  .buy-button,
  .hero-button,
  .neutral-button
)::after {
  content: none;
}
.desktop-web button.u-control::after { content: none; }

/* --- Secondary is the default: flat fill, hairline outline ---------------- */
.desktop-web :is(
  .ds-btn,
  .ds-btn--secondary,
  .site-secondary,
  .checkout-recover-action,
  .neutral-button,
  .hero-button,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
) {
  border-color: var(--ds-hairline-strong);
  background: var(--ds-control-fill);
  color: var(--text);
}
.desktop-web :is(
  .ds-btn,
  .ds-btn--secondary,
  .site-secondary,
  .checkout-recover-action,
  .neutral-button,
  .hero-button,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
):hover:not(:disabled) {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
}

/* --- Primary: solid accent, dark ink -------------------------------------- */
.desktop-web :is(
  .ds-btn--primary,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .primary-button,
  .buy-button,
  .send-btn,
  .finish
),
.desktop-web .hero-button.primary {
  border-color: transparent;
  background: var(--ds-accent);
  color: var(--ds-accent-ink);
  font-weight: 650;
}
.desktop-web :is(
  .ds-btn--primary,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .primary-button,
  .buy-button,
  .send-btn,
  .finish
):hover:not(:disabled),
.desktop-web .hero-button.primary:hover:not(:disabled) {
  background: var(--ds-accent-hover);
  color: var(--ds-accent-ink);
}
.desktop-web :is(
  .ds-btn--primary,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .primary-button,
  .buy-button,
  .send-btn,
  .finish
):active:not(:disabled),
.desktop-web .hero-button.primary:active:not(:disabled) {
  background-color: var(--ds-accent-press);
}
/* On an accent fill the accent ring disappears, so this one turns white. */
.desktop-web :is(
  .ds-btn--primary,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .primary-button,
  .buy-button,
  .send-btn,
  .finish
):focus-visible,
.desktop-web .hero-button.primary:focus-visible {
  outline-color: var(--ds-focus-on-accent);
}

/* --- Ghost: text until you point at it ------------------------------------ */
.desktop-web .ds-btn--ghost {
  border-color: transparent;
  background: transparent;
  color: var(--text-muted);
}
.desktop-web .ds-btn--ghost:hover:not(:disabled) {
  border-color: transparent;
  background: var(--surface-hover);
  color: var(--text);
}

/* --- Danger: flat tinted fill, never a red slab --------------------------- */
.desktop-web :is(.ds-btn--danger, .site-danger, .danger-button) {
  border-color: rgba(229, 104, 109, 0.42);
  background: var(--danger-soft);
  color: var(--danger-text);
}
.desktop-web :is(.ds-btn--danger, .site-danger, .danger-button):hover:not(:disabled) {
  border-color: var(--danger);
  background: rgba(229, 104, 109, 0.22);
  color: var(--danger-text);
}
.desktop-web :is(.ds-btn--danger, .site-danger, .danger-button):focus-visible {
  outline-color: var(--danger);
}

/* --- Shared states -------------------------------------------------------- */
.desktop-web :is(
  .ds-btn,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .site-secondary,
  .checkout-recover-action,
  .site-danger,
  .primary-button,
  .neutral-button,
  .danger-button,
  .buy-button,
  .hero-button,
  .send-btn,
  .finish,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
):active:not(:disabled) {
  transform: translateY(1px);
}
.desktop-web :is(
  .ds-btn,
  .site-secondary,
  .checkout-recover-action,
  .neutral-button,
  .hero-button,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
):active:not(:disabled) {
  background-color: var(--ds-control-fill-press);
}
.desktop-web :is(
  .ds-btn,
  .site-primary,
  .checkout-submit,
  .checkout-confirm,
  .site-secondary,
  .checkout-recover-action,
  .site-danger,
  .primary-button,
  .neutral-button,
  .danger-button,
  .buy-button,
  .hero-button,
  .send-btn,
  .finish,
  .payment-methods-button,
  .payment-methods-done,
  .auth-discord-button
):disabled,
.desktop-web .ds-btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Loading: the label stays and a spinner joins it. A button that empties
   itself changes width and shoves everything beside it. */
.desktop-web .ds-btn[data-loading]::before,
.desktop-web .ds-btn--loading::before {
  content: "";
  flex: none;
  width: 13px;
  height: 13px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  opacity: 0.85;
  animation: ds-spin 720ms linear infinite;
}
@keyframes ds-spin { to { transform: rotate(360deg); } }

.desktop-web .ds-btn--sm {
  min-height: var(--ds-control-h-sm);
  padding-inline: var(--ds-3);
  font-size: 12.5px;
}
.desktop-web .ds-btn--block { width: 100%; }

/* --- Controls reachable only through a descendant selector ---------------
 * Assigned last and on purpose: this selector is more specific than the
 * variant rules above, so it has to state its own look rather than inherit one.
 *
 * Only controls that carry NO variant class belong here. `.customer-search
 * button` and `.customer-action button` were in this list and should not have
 * been: the admin templates render `.site-primary` and `.site-danger` on those
 * buttons, and a descendant selector outranks a class, so search lost its
 * primary fill and two destructive controls lost their danger tone. They are
 * reached by their own variant rules above now, and admin.css no longer has to
 * re-state them.
 */
.desktop-web :is(.payment-result-actions a, .payment-result-actions button) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-2);
  min-height: var(--ds-control-h);
  padding: 0 var(--ds-4);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-sm);
  background: var(--ds-control-fill);
  background-image: none;
  color: var(--text);
  font-size: var(--ds-text-small);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  box-shadow: none;
}
.desktop-web :is(.payment-result-actions a, .payment-result-actions button):hover:not(:disabled) {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
}
.desktop-web .payment-result-actions .primary {
  border-color: transparent;
  background: var(--ds-accent);
  color: var(--ds-accent-ink);
}
.desktop-web .payment-result-actions .primary:hover:not(:disabled) {
  background: var(--ds-accent-hover);
}

/*
 * Touch targets grow where fingers are (DESIGN.md §12: 44px minimum).
 *
 * The button list was the list of buttons §6 maps, minus three that arrived
 * with the dialogs and never got added: the payment-methods trigger and its
 * "Got it", and the Discord entry button. All three are the only control in
 * their view, so all three were the one 40px target on a phone.
 */
@media (pointer: coarse) {
  .desktop-web :is(
    .ds-btn,
    .site-primary,
    .site-secondary,
    .site-danger,
    .primary-button,
    .neutral-button,
    .danger-button,
    .buy-button,
    .hero-button,
    .send-btn,
    .finish,
    .payment-methods-button,
    .payment-methods-done,
    .auth-discord-button
  ) { min-height: 44px; }

  /*
   * And the fields, which the rule never reached at all.
   *
   * A 44px button above a 38px input is not a system; a finger does not get
   * more accurate because the target is a text box. Checkbox, radio and range
   * are excluded because they are reset out of the text-field treatment
   * entirely (§7) and a 44px tall checkbox is a 44px tall box, not a 44px
   * target — their own rules size them.
   */
  .desktop-web :is(
    input:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"]),
    textarea,
    select
  ) { min-height: 44px; }

  /*
   * The two header/footer controls that are links rather than buttons, so
   * nothing above reaches them. The brand is an icon-plus-word target at the
   * very top left, where a thumb is least accurate; the legal links sit in a
   * row at the very bottom, where they are easiest to hit by mistake.
   */
  .desktop-web .web-brand { min-width: 44px; }
  .desktop-web .web-legal a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* A quiet text action. Not a button, and never dressed as one. */
.desktop-web .quiet-link {
  color: var(--ds-accent);
  font-weight: 500;
  text-decoration: none;
}
.desktop-web .quiet-link:hover { color: var(--ds-accent-hover); }
/* ===========================================================================
 * 8. FIELDS — RECESSED
 *
 * Inputs are pressed into the surface. This is where the neumorphic half of
 * the brief lives, and it works precisely because it never reaches a button.
 * ======================================================================== */
/* Written as four plain selectors rather than one :is() with :not() filters:
   an :is() list inherits its most specific member, and `input:not([type])`
   three times over would have outranked every state rule below it. The
   non-text input types are reset back out on the next rule instead. */
.desktop-web input,
.desktop-web textarea,
.desktop-web select,
.desktop-web .ds-input {
  width: 100%;
  min-height: var(--ds-control-h);
  padding: 10px var(--ds-3);
  border: 0;
  border-radius: var(--ds-r-sm);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--ds-text-body);
  line-height: 1.4;
  transition: box-shadow var(--ds-motion-fast) var(--ds-ease);
}
/* Checkboxes, radios and range thumbs are not text fields and must not be
   stretched to one. */
.desktop-web input[type="checkbox"],
.desktop-web input[type="radio"],
.desktop-web input[type="range"] {
  width: auto;
  min-height: 0;
  padding: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  accent-color: var(--ds-accent);
}

.desktop-web :is(input, textarea, select)::placeholder {
  color: var(--text-faint);
  opacity: 1;
}
.desktop-web :is(input, textarea, select):hover:not(:disabled) {
  box-shadow: var(--ds-recess), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.desktop-web :is(input, textarea, select):disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.desktop-web textarea { min-height: 120px; resize: vertical; }

/* Label above, help below, error below that. Never a placeholder as a label. */
.desktop-web .ds-field { display: grid; gap: var(--ds-2); }
.desktop-web .ds-label {
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  font-weight: 500;
}
.desktop-web .ds-help {
  margin: 0;
  color: var(--text-faint);
  /* The token this class is the reference for (DESIGN §4). It was still the
     bare literal the token was named after. */
  font-size: var(--ds-text-help);
  line-height: 1.6;
}
/* Supporting prose inside a panel, not help under a field: three of these run
   one after another in the device panel and in each admin action card, so it
   keeps its own spacing and the reading colour. Was defined twice, in
   account.css and admin.css. */
.desktop-web .device-help {
  margin: 0 0 var(--ds-3);
  max-width: 65ch;
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  line-height: 1.65;
}
.desktop-web .device-help.muted { color: var(--text-faint); }
.desktop-web .ds-error-text {
  color: var(--danger-text);
  /* The same step as .ds-help directly above it: DESIGN §7 stacks help and
     error under one field, and two sizes there would read as two ranks. */
  font-size: var(--ds-text-help);
}
/*
 * The danger rim (DESIGN §7: aria-invalid draws it).
 *
 * It did not draw. Root `style.css` — frozen for everyone — carries
 *
 *     input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
 *     select, textarea { border: none !important;
 *                        background: var(--surface-sunken) !important;
 *                        box-shadow: var(--recessed) !important; }
 *
 * and an author `!important` beats any specificity this file can reach for.
 * So the box-shadow below was discarded on every text field on the site, and a
 * field marked aria-invalid was pixel-identical to a valid one: measured on the
 * booking dialog, an empty field computed the plain three-layer recess and no
 * fourth layer. The only marking a missed field had was the sentence above the
 * form.
 *
 * `outline` is the way out, not a second `!important`. That block claims
 * border, background and box-shadow; it does not claim outline, so the rim
 * lands without a cascade war. `outline-offset: -2px` draws it just inside the
 * field's edge, which is where the discarded inset ring was going. The
 * box-shadow stays for `.ds-input` and anything that is not one of those three
 * elements, which the frozen block never reaches.
 *
 * Fixing it properly means deleting three `!important`s from `style.css`; the
 * report names them. That file also puts `inset 0 0 0 1px var(--accent)` on
 * :focus, so every field on the site still rings in the retired neutral rather
 * than `--ds-focus` (§2, §12). Same block, same fix, not this pass.
 */
.desktop-web :is(input, textarea, select)[aria-invalid="true"],
.desktop-web .ds-input[aria-invalid="true"] {
  outline: 2px solid var(--danger);
  outline-offset: -2px;
  box-shadow: var(--ds-recess), inset 0 0 0 1px var(--danger);
}

/* Toggles, tracks and meters are recessed; their knobs are the raised half. */
.desktop-web .ds-track {
  border-radius: var(--ds-r-pill);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
.desktop-web .ds-knob {
  border-radius: var(--ds-r-pill);
  background: var(--surface-floating);
  box-shadow: var(--ds-e1);
}
/* `.usage-bars` is NOT in this list. It is a column chart whose bars each carry
   their own --bar-height, and a track sets every child to height: 100%, which
   drew twelve identical full-height columns and threw the data away. It is the
   next rule instead: the same well, but as the chart's ground. */
.desktop-web :is(.progress, .ds-meter) {
  overflow: hidden;
  border-radius: var(--ds-r-pill);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
.desktop-web :is(.progress, .ds-meter) span {
  display: block;
  height: 100%;
  background: var(--ds-accent);
}

/* The usage chart. The well is the ground, the bars stand in it, and each one
   keeps the height the page computed for it. */
.desktop-web .usage-bars {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 190px;
  padding: var(--ds-4) var(--ds-3) 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--ds-r-md);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
.desktop-web .usage-bars span {
  flex: 1 1 0;
  min-width: 3px;
  height: var(--bar-height, 8px);
  min-height: 8px;
  border-radius: 3px 3px 0 0;
  background: var(--ds-accent);
}
/* With nothing to plot the template puts a sentence in the chart's place, and a
   190px well holding one line reads as a chart that failed to draw. */
.desktop-web .usage-bars:has(p) { height: auto; padding: var(--ds-6) var(--ds-4); }
/* Centred horizontally it was the one piece of text on these dashboards not
   starting at the left edge its panel heading starts at (DESIGN §3), so the
   block read as a placeholder graphic rather than as the panel's content. The
   auto margins stay on the block axis only, which is what keeps it in the
   middle of the well's height. */
.desktop-web .usage-bars p {
  margin: auto 0;
  color: var(--text-faint);
  font-size: var(--ds-text-small);
}
@media (max-width: 700px) {
  .desktop-web .usage-bars { height: 150px; gap: 4px; padding-inline: var(--ds-2); }
}

/* ===========================================================================
 * 9. FOCUS
 *
 * One ring, everywhere, in the accent. 2px at 7:1 clears the 3:1 a focus
 * indicator needs; the offset keeps it off the control's own edge.
 * ======================================================================== */
.desktop-web :is(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--ds-focus);
  outline-offset: 2px;
}
.desktop-web :is(.ds-card, .dashboard-panel, .lt-card):focus-visible {
  outline-offset: 3px;
}

/*
 * A label that is read but not seen.
 *
 * DESIGN.md §7 requires every input to have a `<label for>`, and two controls in
 * the admin console are labelled by their surroundings for a sighted reader but
 * had nothing for a screen reader: the roster's lookup field, and the per-code
 * percent-off input, which is one cell of a table whose only visible label is
 * the "Rate" column header. Both templates emit `class="sr-only"` on that
 * label, and no sheet defined it, so the labels were rendering as visible text
 * in the middle of the console.
 *
 * Clipped rather than `display: none` or `visibility: hidden`, both of which
 * take the element out of the accessibility tree along with the layout, which
 * would defeat the whole point. The 1px box with `clip-path` is the treatment
 * that stays announced.
 */
.desktop-web .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ===========================================================================
 * 10. TAGS, BADGES, STATUS
 * ======================================================================== */
.desktop-web :is(.ds-tag, .lt-tag, .role-chip, .db-chip, .discount-badge) {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 24px;
  padding: 0 9px;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-xs);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-muted);
  font-size: var(--ds-text-tag);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.desktop-web :is(.ds-tag--accent, .lt-tag--strong) {
  border-color: var(--ds-accent-line);
  background: var(--ds-accent-soft);
  color: var(--ds-accent);
}
.desktop-web :is(button.lt-tag, button.ds-tag) {
  cursor: pointer;
  transition:
    background var(--ds-motion-fast) var(--ds-ease),
    border-color var(--ds-motion-fast) var(--ds-ease);
}
.desktop-web :is(button.lt-tag, button.ds-tag):hover {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
}

/* Status pills are the only pill on the site: they report state, they are not
   pressed. Colour is never the only carrier — each one also carries a word. */
.desktop-web .status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 24px;
  padding: 0 10px;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-pill);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-muted);
  /* One size and one weight, everywhere the templates render a pill. The
     account page used to inherit 13px from its own .panel-heading > span rule
     and then set 600 back on top of it, so the same word was two different
     things depending on which dashboard you were standing on. */
  font-size: var(--ds-text-tag);
  font-weight: 600;
  line-height: 1;
}
.desktop-web .status-pill.active {
  border-color: rgba(85, 199, 133, 0.42);
  background: var(--ok-soft);
  color: var(--ok);
}
.desktop-web .status-pill.ending {
  border-color: rgba(221, 164, 79, 0.42);
  background: var(--warn-soft);
  color: var(--warn);
}
/* The remaining tones the JS templates render. They were defined twice, in
   account.css and admin.css, which is how the account page and the console can
   drift apart on the same word; they are one definition here now. Every one of
   them still carries a word, so none is a colour on its own. */
.desktop-web :is(.status-pill.off, .status-pill.muted) {
  border-color: var(--ds-hairline-strong);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text-faint);
}
.desktop-web .status-pill.bound {
  border-color: rgba(85, 199, 133, 0.42);
  background: var(--ok-soft);
  color: var(--ok);
}
.desktop-web .status-pill.current {
  border-color: var(--ds-accent-line);
  background: var(--ds-accent-soft);
  color: var(--ds-accent);
}
/* A blocked entitlement is neither a warning nor an ending. --danger-text
   rather than --danger: it is the higher-contrast pair for a danger-soft fill. */
.desktop-web .status-pill.blocked {
  border-color: rgba(229, 104, 109, 0.42);
  background: var(--danger-soft);
  color: var(--danger-text);
}
.desktop-web .status-pill.compact { min-height: 21px; padding: 0 8px; }

/* ===========================================================================
 * 11. NOTICES, STATES, EMPTY AND LOADING
 * ======================================================================== */
.desktop-web :is(.ds-notice, .site-notice, .site-error-notice, .u-state, .success-notice, .pre-info-banner, .checkout-error, .checkout-payment-error) {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-3);
  padding: var(--ds-3) var(--ds-4);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-md);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  line-height: 1.6;
}
.desktop-web :is(.ds-notice--error, .site-error-notice, .u-state.is-error, .checkout-error) {
  border-color: rgba(229, 104, 109, 0.42);
  background: var(--danger-soft);
  color: var(--danger-text);
}
.desktop-web :is(.ds-notice--ok, .u-state.is-ok, .success-notice) {
  border-color: rgba(85, 199, 133, 0.42);
  background: var(--ok-soft);
  color: var(--ok);
}
.desktop-web :is(.ds-notice--warn, .site-notice.warn) {
  border-color: rgba(221, 164, 79, 0.42);
  background: var(--warn-soft);
  color: var(--warn);
}
/*
 * The leading glyph sits on the FIRST LINE, not on the top of the box.
 *
 * The row is `align-items: flex-start` because the sentence beside it wraps.
 * A font glyph brought its own line box to that row and landed on the first
 * line for free; an <svg> is 1em square and landed 4.1px high, which on a
 * three-line warning is the difference between a glyph that belongs to the
 * sentence and one floating above it.
 *
 * So the box is stretched to the row's line-height and the 24x24 viewBox
 * centres itself in it (`preserveAspectRatio` fits the shorter side), which
 * lands the ink within 0.4px of the first line's cap-height centre. The
 * `1.6em` is the notice's own `line-height: 1.6` above; the two move together.
 * Measured: -4.13px before, -0.38px after.
 */
.desktop-web :is(
  .ds-notice, .site-notice, .u-state, .success-notice,
  .pre-info-banner, .checkout-error, .checkout-payment-error
) > .ui-icon { height: 1.6em; }

/* The interior of an error notice: the glyph aligned to the first line, the
   headline one step up, the sentence under it. Defined twice before, in
   account.css and admin.css.

   The glyph leads a 14px/600 headline here, not 13px body, so the icon takes
   that step: a 13px hairline beside a 14px semibold headline read as a
   different, lighter element rather than as part of it. The span is the
   wrapper `interactive.js` renders, so it carries the line-box height for the
   same reason as the notices above. */
.desktop-web .site-error-notice > span {
  display: grid;
  flex: none;
  place-items: center;
  height: calc(var(--ds-text-body) * 1.6);
  font-size: var(--ds-text-body);
}
.desktop-web .site-error-notice strong {
  display: block;
  margin-bottom: 3px;
  color: var(--text);
  font-size: var(--ds-text-body);
  font-weight: 600;
}
.desktop-web .site-error-notice p { margin: 0; }

.desktop-web :is(.site-state, .empty-state, .db-empty, .ds-empty) {
  display: grid;
  justify-items: start;
  gap: var(--ds-2);
  padding: var(--ds-7) var(--ds-5);
  border-radius: var(--ds-r-lg);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
  color: var(--text-muted);
  font-size: var(--ds-text-small);
}
/* A terminal state: the notice, then the way out of it. Left-aligned like every
   other block of text on the page rather than centred in a box of its own. */
.desktop-web .site-state { gap: var(--ds-4); padding: var(--ds-6) var(--ds-5); }
.desktop-web .site-state .site-error-notice { width: 100%; max-width: 62ch; margin: 0; }

/* The referral strip above the header. It shipped as three inline style
   attributes repeated on six pages, painted with the NEUTRAL --accent (#e6e6e6)
   rather than the functional one, which drew a near-white hairline across the
   top of the page. It is the accent notice it always meant to be, stated once.
   It stays display:none until a script shows it. */
.desktop-web .ref-banner {
  position: relative;
  z-index: 1001;
  display: none;
  padding: 9px 44px;
  border-bottom: 1px solid var(--ds-accent-line);
  background: var(--ds-accent-soft);
  color: var(--text);
  font-size: var(--ds-text-small);
  text-align: center;
}
.desktop-web .ref-banner-dismiss {
  position: absolute;
  top: 50%;
  right: var(--ds-2);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--ds-r-xs);
  background: none;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transform: translateY(-50%);
}
.desktop-web .ref-banner-dismiss:hover { background: var(--surface-hover); color: var(--text); }

.desktop-web .site-success-mark {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--ok-soft);
  color: var(--ok);
  box-shadow: inset 0 0 0 1px rgba(85, 199, 133, 0.42);
}

/* Skeletons take the shape of what is coming, so nothing jumps when it
   arrives. One shimmer, and it stops under reduced motion. */
.desktop-web :is(.ds-skeleton, .ui-skeleton) {
  border-radius: var(--ds-r-sm);
  background:
    linear-gradient(90deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.04) 100%)
    0 0 / 200% 100%;
  box-shadow: var(--ds-recess);
  animation: ds-shimmer 1.4s linear infinite;
}
@keyframes ds-shimmer { to { background-position: -200% 0; } }
.desktop-web .ds-skeleton--text { height: 12px; margin-block: 6px; }
.desktop-web .ds-skeleton--title { height: 20px; width: 40%; }
.desktop-web .ds-skeleton--block { height: 120px; border-radius: var(--ds-r-lg); }

/* The loading state every JS-rendered page opens on. `.ui-loading-state` had no
   rule anywhere, so its three `.ui-skeleton` spans rendered at zero height and
   account, affiliate, transcript and the admin console all opened on a bare
   sentence. The shape is what is coming: a title bar, then two lines. */
.desktop-web .ui-loading-state {
  display: grid;
  gap: var(--ds-2);
  margin: 0;
  padding: var(--ds-5);
  border-radius: var(--ds-r-lg);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
/* The well it sits in is already recessed; a second inset reads as a groove. */
.desktop-web .ui-loading-state .ui-skeleton { height: 12px; border-radius: var(--ds-r-xs); box-shadow: none; }
.desktop-web .ui-loading-state .ui-skeleton:first-child { width: 42%; height: 18px; margin-bottom: var(--ds-1); }
.desktop-web .ui-loading-state .ui-skeleton:nth-child(2) { width: 88%; }
.desktop-web .ui-loading-state .ui-skeleton:nth-child(3) { width: 64%; }
.desktop-web .ui-loading-label { margin: var(--ds-3) 0 0; color: var(--text-faint); font-size: var(--ds-text-small); }
@media (max-width: 420px) {
  .desktop-web :is(.ui-loading-state, .site-state) { padding: var(--ds-5) var(--ds-4); }
}

/* ===========================================================================
 * 12. DIALOGS AND MODALS — GLASS TIER 3
 * ======================================================================== */
.desktop-web :is(.site-modal-backdrop, .auth-entry-backdrop, .ds-backdrop) {
  background: rgba(0, 0, 0, 0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.desktop-web :is(
  .ds-dialog,
  .site-booking-modal,
  .site-contact-modal,
  .payment-methods-dialog,
  .auth-entry-card,
  .lt-copy-dialog,
  .payment-result-dialog
) {
  border: 0;
  border-radius: var(--ds-r-xl);
  background: var(--ds-glass-3-fill);
  -webkit-backdrop-filter: var(--ds-glass-3-blur);
  backdrop-filter: var(--ds-glass-3-blur);
  box-shadow: var(--ds-glass-edge), var(--ds-e4);
  color: var(--text);
}
.desktop-web .lt-copy-dialog::backdrop { background: rgba(0, 0, 0, 0.62); }

/*
 * The payment-return dialog (index.js, reached at /?payment=success, which is
 * where /success sends the customer back from Stripe).
 *
 * It used to inject its own stylesheet at runtime and was the one surface on
 * the site still speaking a different language: a #12141c blue-black panel, a
 * #4b86f4 second accent, a #82cdff status line, a coloured decorative dot, a
 * 14px radius and a 9px button radius. It is the site's dialog now, and
 * index.js no longer ships a style block. State is carried by the word, and the
 * word is coloured by the state.
 */
.desktop-web .payment-result-dialog {
  width: min(500px, calc(100vw - 32px));
  padding: 0;
}
.desktop-web .payment-result-dialog::backdrop {
  background: rgba(0, 0, 0, 0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.desktop-web .payment-result-body { position: relative; padding: var(--ds-6); }

/*
 * The dialog polls for up to a minute. Nothing else in it moves, so without
 * this it reads as frozen at the moment someone is waiting to hear whether
 * their money went through.
 *
 * It sits on the body's top edge, inside the dialog's radius, and exists only
 * while the poll does — every terminal state answers the question in words, so
 * a bar still running under them would be saying the opposite. It carries no
 * information the status line above does not, which is what lets it be
 * aria-hidden and be removed outright for a reader who has asked for less
 * motion.
 */
.desktop-web .payment-result-progress {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  width: 100%;
  height: 2px;
  border-radius: var(--ds-r-xl) var(--ds-r-xl) 0 0;
  background: var(--ds-hairline);
}
.desktop-web .payment-result-progress::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 38%;
  height: 100%;
  background: var(--ds-accent);
  animation: payment-result-sweep 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@keyframes payment-result-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(363%); }
}
/* Only while the answer is still unknown. */
.desktop-web .payment-result-dialog:not([data-state="working"]) .payment-result-progress { display: none; }
@media (prefers-reduced-motion: reduce) {
  .desktop-web .payment-result-progress { display: none; }
}
/*
 * The dialog's close, on the same two edges and at the same size as the one in
 * every other dialog on the site (`.site-modal-close`, 44px square,
 * desktop-shell.css).
 *
 * It was 36px square inset 12px from the corner, which made it three things at
 * once: 8px below the 44px touch floor (DESIGN §12) with no coarse-pointer
 * rule to raise it, 20px outside the right edge the eyebrow, the headline, the
 * sentence and the action all line up on, and 9.3px above the eyebrow's
 * optical centre. Now it ends where the text ends and centres on the line it
 * shares, like the booking dialog's (services.css).
 */
.desktop-web .payment-result-x {
  position: absolute;
  top: calc(var(--ds-6) + (var(--ds-text-micro) * 1.4 - 44px) / 2);
  right: var(--ds-6);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-sm);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  cursor: pointer;
}
.desktop-web .payment-result-x:hover {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
  color: var(--text);
}
.desktop-web .payment-result-status {
  /* The close button's own width plus a gap, so a long status word stops
     before it rather than under it. It said 44 while the control occupied 48. */
  padding-right: calc(44px + var(--ds-3));
  color: var(--text-faint);
  font-size: var(--ds-text-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  line-height: 1.4;
  text-transform: uppercase;
}
.desktop-web .payment-result-dialog[data-state="ready"] .payment-result-status { color: var(--ok); }
.desktop-web .payment-result-dialog[data-state="error"] .payment-result-status { color: var(--danger-text); }
/* Eyebrow then title then sentence, at the same 12/16 the page behind this
   dialog uses for its own eyebrow, h1 and lede. The title sat 8px off its
   sentence, which bound the two more tightly than the eyebrow to the title
   it labels. */
.desktop-web .payment-result-dialog h2 {
  margin: var(--ds-3) 0 var(--ds-4);
  font-size: var(--ds-text-h2);
  line-height: 1.18;
  letter-spacing: -0.024em;
}
.desktop-web .payment-result-dialog p {
  margin: 0;
  max-width: var(--ds-lede-measure);
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  line-height: 1.65;
}
.desktop-web .payment-result-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-2);
  margin-top: var(--ds-5);
}
.desktop-web .payment-result-actions [hidden] { display: none; }

/* One close control, one size. `.site-modal-close` has been 44px square since
   the touch pass (desktop-shell.css); `.auth-entry-close` is the same control
   in the sign-in card and was still 36, which is below the §12 floor and a
   different button for the same job two dialogs apart. */
.desktop-web :is(.site-modal-close, .auth-entry-close) {
  display: grid;
  place-items: center;
  width: 44px;
  min-width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-sm);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
}
.desktop-web :is(.site-modal-close, .auth-entry-close):hover {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
  color: var(--text);
}

/* ===========================================================================
 * 13. MENUS, DROPDOWNS AND TOASTS — GLASS TIER 2
 * ======================================================================== */
.desktop-web :is(.lt-dropdown-panel, .ds-menu) {
  padding: 5px;
  border: 1px solid var(--line-raised);
  border-radius: var(--ds-r-md);
  background: var(--ds-glass-2-fill);
  -webkit-backdrop-filter: var(--ds-glass-2-blur);
  backdrop-filter: var(--ds-glass-2-blur);
  box-shadow: var(--ds-glass-edge), var(--ds-e3);
}
.desktop-web :is(.lt-dropdown-item, .ds-menu-item) {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 36px;
  padding: var(--ds-2) 10px;
  border: 0;
  border-radius: var(--ds-r-xs);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  text-align: left;
}
.desktop-web :is(.lt-dropdown-item, .ds-menu-item):is(:hover, :focus-visible) {
  background: var(--surface-hover);
  color: var(--text);
}
.desktop-web :is(.lt-dropdown-item[aria-current="true"], .ds-menu-item[aria-current="true"]) {
  color: var(--ds-accent);
}

.desktop-web .lt-dropdown-trigger {
  min-height: var(--ds-control-h);
  border: 1px solid var(--ds-hairline-strong);
  border-radius: var(--ds-r-sm);
  background: var(--ds-control-fill);
  color: var(--text);
  font-size: var(--ds-text-small);
  font-weight: 500;
  box-shadow: none;
}
.desktop-web .lt-dropdown-trigger:hover {
  border-color: var(--text-faint);
  background: var(--ds-control-fill-hover);
}

/* Toasts float, so they are glass too. */
.desktop-web .lt-toast {
  border: 1px solid var(--line-raised);
  border-radius: var(--ds-r-md);
  background: var(--ds-glass-2-fill);
  -webkit-backdrop-filter: var(--ds-glass-2-blur);
  backdrop-filter: var(--ds-glass-2-blur);
  box-shadow: var(--ds-glass-edge), var(--ds-e3);
}

/* ===========================================================================
 * 14. TABLES
 * ======================================================================== */
.desktop-web :is(.table-wrap, .db-table-wrap, .affiliate-table-wrap) {
  overflow-x: auto;
  border-radius: var(--ds-r-lg);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
.desktop-web :is(.table-wrap table, .db-table, .affiliate-table, .acct-table, .ds-table) {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--ds-text-small);
}
.desktop-web :is(.table-wrap table, .db-table, .affiliate-table, .acct-table, .ds-table) th {
  padding: 10px var(--ds-3);
  border-bottom: 1px solid var(--ds-hairline-strong);
  background: var(--surface-raised);
  color: var(--text-faint);
  font-weight: 500;
  letter-spacing: 0;
  text-align: left;
  text-transform: none;
  white-space: nowrap;
}
.desktop-web :is(.table-wrap table, .db-table, .affiliate-table, .acct-table, .ds-table) td {
  padding: 10px var(--ds-3);
  border-bottom: 1px solid var(--ds-hairline);
  color: var(--text-muted);
  vertical-align: top;
}
.desktop-web :is(.table-wrap table, .db-table, .affiliate-table, .ds-table) tbody tr:last-child td {
  border-bottom: 0;
}
.desktop-web :is(.table-wrap table, .db-table, .affiliate-table, .ds-table) tbody tr:hover td {
  background: var(--surface-hover);
  color: var(--text);
}

/* ===========================================================================
 * 15. TABS
 * ======================================================================== */
.desktop-web :is(.ds-tabs, .admin-tabs, .range-tabs) {
  display: inline-flex;
  gap: var(--ds-1);
  padding: var(--ds-1);
  border-radius: var(--ds-r-md);
  background: var(--ds-well-fill);
  box-shadow: var(--ds-recess);
}
.desktop-web :is(.ds-tabs, .admin-tabs, .range-tabs) button {
  min-height: var(--ds-control-h-sm);
  padding-inline: var(--ds-3);
  border: 0;
  border-radius: var(--ds-r-sm);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--ds-text-small);
  font-weight: 500;
  box-shadow: none;
}
.desktop-web :is(.ds-tabs, .admin-tabs, .range-tabs) button:hover:not(:disabled) {
  background: var(--surface-hover);
  color: var(--text);
}
.desktop-web :is(.ds-tabs, .admin-tabs, .range-tabs) button:is(.active, [aria-selected="true"], [aria-current="true"]) {
  background: var(--ds-control-fill-hover);
  color: var(--text);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
}

/* ===========================================================================
 * 16. DISCLOSURE (FAQ)
 * ======================================================================== */
.desktop-web :is(.home-faq-item, .ds-disclosure) {
  border: 0;
  border-bottom: 1px solid var(--ds-hairline);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.desktop-web :is(.home-faq-item, .ds-disclosure) summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-4);
  padding: var(--ds-4) 0;
  background: transparent;
  color: var(--text);
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  transition: color var(--ds-motion-fast) var(--ds-ease);
}
.desktop-web :is(.home-faq-item, .ds-disclosure) summary::-webkit-details-marker { display: none; }
.desktop-web :is(.home-faq-item, .ds-disclosure) summary:hover { color: var(--ds-accent); }
.desktop-web :is(.home-faq-item, .ds-disclosure) summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  margin-right: 4px;
  border: 0;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  border-radius: 1px;
  background: none;
  opacity: 0.6;
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--ds-motion-normal) var(--ds-ease-emphasis);
}
.desktop-web :is(.home-faq-item, .ds-disclosure)[open] summary::after {
  transform: rotate(225deg) translateY(-2px);
}
.desktop-web :is(.home-faq-item, .ds-disclosure) p {
  margin: 0;
  padding: 0 0 var(--ds-4);
  max-width: var(--ds-lede-measure);
  color: var(--text-muted);
  font-size: var(--ds-text-body);
  line-height: 1.7;
}

/* ===========================================================================
 * 17. FOOTER
 * ======================================================================== */
.desktop-web footer {
  margin-top: var(--ds-8);
  border-top: 1px solid var(--ds-hairline);
  background: transparent;
}
.desktop-web .footer-links h2 {
  margin: 0 0 var(--ds-3);
  color: var(--text-faint);
  font-size: var(--ds-text-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.desktop-web :is(.footer-links a, .web-legal a, .footer-brand p) {
  color: var(--text-muted);
  font-size: var(--ds-text-small);
}
.desktop-web :is(.footer-links a, .web-legal a) {
  transition: color var(--ds-motion-fast) var(--ds-ease);
}
.desktop-web :is(.footer-links a:hover, .web-legal a:hover) { color: var(--text); }

/* ===========================================================================
 * 18. ICONS
 *
 * Inline HugeIcons stroke SVGs, sized by `font-size` like the subset font they
 * replaced. The names in use are listed in DESIGN.md §13; a name outside the
 * set now renders nothing at all rather than an empty box.
 *
 * Three rules, and they are the whole system (DESIGN §13):
 *
 *   SIZE      An icon is 1em of the text beside it. `.ui-icon` is already
 *             `width: 1em; height: 1em`, so it needs no size of its own and
 *             stating one is what put the same "icon then label" pairing at
 *             11px, 13px and 14px in three different sheets.
 *   GAP       One value, `--ds-2` (8px), between an icon and its label.
 *             It was 5, 8, 10, 11 and 12 across the site.
 *   BASELINE  `align-items: center` on a single-line row lands the ink within
 *             0.4px of the text's cap-height centre, because Cal Sans'
 *             ascent minus descent is its cap height to within 0.06em. A row
 *             that top-aligns (a notice, a feature list) has to state a box as
 *             tall as its line-height instead; see §11 and services.css.
 * ======================================================================== */
.desktop-web .ui-icon { flex: none; opacity: 0.85; }

/*
 * One gap, everywhere an icon leads a label.
 *
 * These are the flex rows `interactive.css` and the page sheets build around
 * an icon; each of them used to name its own gap. Stated once here, after
 * `interactive.css` and at a specificity a bare class cannot reach, so the
 * pairing is the same distance apart in a button, a menu, a tag and a nav item.
 */
.desktop-web :is(
  .ds-btn, .site-primary, .site-secondary, .site-danger,
  .primary-button, .neutral-button, .danger-button, .buy-button,
  .hero-button, .send-btn, .finish, .account-login,
  .ds-tag, .status-pill, .admin-role, .admin-nav button,
  .admin-tabs button, .stat-card small, .message a,
  .dashboard-heading h1, .site-booking-modal label > span
) { gap: var(--ds-2); }

/*
 * Inside a control with a fill of its own, the icon is part of the label.
 *
 * The site-wide 85% is what keeps an icon from competing with the sentence it
 * decorates, and on the page ground that is right. On a solid accent button it
 * is not: the label is at full strength and the glyph beside it read as a
 * second, weaker colour sitting on the same slab. A button's own colour has
 * already set how loud it is; the icon does not get dimmed again inside it.
 */
.desktop-web :is(
  .ds-btn, .site-primary, .site-secondary, .site-danger,
  .primary-button, .neutral-button, .danger-button, .buy-button,
  .hero-button, .send-btn, .finish, .account-login, .account-trigger,
  .ds-tag, .status-pill, .admin-role
) .ui-icon { opacity: 1; }

/* ===========================================================================
 * 19. FALLBACKS
 *
 * Glass is decoration. All three of these must leave the page legible.
 * ======================================================================== */

/* No backdrop-filter: translucency without blur is just low contrast, so go
   solid. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .desktop-web :is(
    .ds-glass-1, .ds-glass-2, .ds-glass-3, .ds-card--glass, .ds-dialog, .ds-menu,
    .site-booking-modal, .site-contact-modal, .payment-methods-dialog,
    .auth-entry-card, .lt-copy-dialog, .lt-dropdown-panel, .lt-toast,
    .payment-result-dialog
  ) {
    background: var(--surface-floating);
  }
  .desktop-web :is(.site-modal-backdrop, .auth-entry-backdrop, .ds-backdrop) {
    background: rgba(0, 0, 0, 0.86);
  }
}

/* Asked for less transparency: no blur anywhere, opaque surfaces, no ambient
   light behind them. */
@media (prefers-reduced-transparency: reduce) {
  body.desktop-web::before { background: none; }
  .desktop-web :is(
    .ds-glass-1, .ds-glass-2, .ds-glass-3, .ds-card--glass, .ds-dialog, .ds-menu,
    .site-booking-modal, .site-contact-modal, .payment-methods-dialog,
    .auth-entry-card, .lt-copy-dialog, .lt-dropdown-panel, .lt-toast,
    .payment-result-dialog
  ) {
    background: var(--surface-floating);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: var(--ds-e3);
  }
  .desktop-web :is(.site-modal-backdrop, .auth-entry-backdrop, .ds-backdrop) {
    background: rgba(0, 0, 0, 0.9);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* The same rules behind a class, so the demo page can show the fallback
   without an OS setting (tools/design-system.html?reduced=1). */
.ds-reduced-transparency body.desktop-web::before { background: none; }
.ds-reduced-transparency .desktop-web :is(
  .ds-glass-1, .ds-glass-2, .ds-glass-3, .ds-card--glass, .ds-dialog, .ds-menu,
  .site-booking-modal, .site-contact-modal, .payment-methods-dialog,
  .auth-entry-card, .lt-copy-dialog, .lt-dropdown-panel, .lt-toast,
  .payment-result-dialog
) {
  background: var(--surface-floating);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: var(--ds-e3);
}
.ds-reduced-transparency .desktop-web .web-header {
  background: var(--surface-sidebar);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.ds-reduced-transparency .desktop-web .web-navigation {
  background: var(--surface-sidebar);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.ds-reduced-transparency .desktop-web .account-menu {
  background: var(--surface-floating);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.ds-reduced-transparency .desktop-web :is(.site-modal-backdrop, .auth-entry-backdrop, .ds-backdrop) {
  background: rgba(0, 0, 0, 0.9);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Hover sticks on a touch screen until the next tap, so the physical half of a
   hover state would simply stay on. Colour changes are fine; lift is not. */
@media (hover: none) {
  .desktop-web :is(.ds-card--interactive, .lt-card--interactive, a.ds-card):hover {
    transform: none;
    box-shadow: var(--ds-e2);
  }
}

/* Asked for less motion: nothing travels, nothing loops. */
@media (prefers-reduced-motion: reduce) {
  .desktop-web *,
  .desktop-web *::before,
  .desktop-web *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  .desktop-web :is(.ds-card--interactive, .lt-card--interactive, a.ds-card):hover {
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
   Footer column. style.css sizes .footer-inner at min(1180px, 100%), 120px
   wider than the page container, so its left edge missed the content column
   by ~100px at 1440 on every page. Shared chrome, so it is corrected once
   here: same container, same gutters as every section above it.

   The footer's OWN 20px side padding has to go with it. Above 1140 the centring
   absorbed it symmetrically and the two edges happened to line up; below that
   there is no slack left to absorb, so it stacked on top of the gutter and the
   footer sat 20px (32px at the 800 breakpoint) inboard of every heading on the
   page. One gutter, stated once, on .footer-inner.
   --------------------------------------------------------------------------- */
.desktop-web footer { padding-inline: 0; }
.desktop-web .footer-inner {
  width: 100%;
  max-width: var(--ds-container);
  margin-inline: auto;
  padding-inline: var(--ds-gutter);
}

/* ---------------------------------------------------------------------------
   Page hero inner. style.css centres it (text-align on .page-hero-inner, a
   declaration, beats the inherited left from .page-hero), and caps it at the
   full container width inside .page-hero's own gutter, so every h1 sat
   centred and one gutter left of every h2 below it. Shared chrome: fixed once.
   --------------------------------------------------------------------------- */
.desktop-web .page-hero-inner {
  text-align: left;
  max-width: calc(var(--ds-container) - var(--ds-gutter) * 2);
}

/* interactive.css still uppercases .status-pill and draws a decorative dot
   before it. Status is carried by the tag colour and its text; the dot is
   ornament and the caps fight every other label on the site. */
.desktop-web .status-pill { text-transform: none; letter-spacing: 0; }
.desktop-web .status-pill::before { content: none; }

/* ===========================================================================
 * 20. THE RETURN NOTICE — retired, and the number is kept so §21 stays §21.
 *
 * The ?paid=1 notice had a panel of its own here: 20px corners, a 34px close
 * hung outside the top-right corner, centred 23px/800 text, an accent tick,
 * and a bare backdrop-filter with no -webkit- pair and no entry in any of
 * the three §19 fallbacks. It was also the one modal on the site with no
 * role, no accessible name, no focus trap and no Escape of its own; ui.js
 * retrofitted those at runtime and overwrote the radius, the shadow and the
 * blur while it was there.
 *
 * It is the payment-return dialog now (§12, .payment-result-*), because the
 * two answer the same question and differed only by accident. The three page
 * scripts build it; nothing styles it here any more.
 * ======================================================================== */

/* ===========================================================================
 * 21. THE LIFTED VARIANT — the account and checkout pages
 *
 * The two pages where a customer reads figures and types a card number sit on
 * a warmer, lighter ramp than the marketing pages. The ground stays dark, so
 * the shared header still reads as glass over it, but every layer above it
 * takes a clearer step (ground, well, panel, field), the hairlines are one
 * shade stronger, secondary text is brighter, and the three state colours are
 * more saturated. Nothing else changes: one accent, flat buttons, the same
 * type scale, the same materials.
 *
 * It is a scope, not a theme. The names below are the ones every rule in this
 * file already reads, redeclared on <body> for the two pages, so the pages
 * inherit the whole system and only the values move. The --ds-* aliases are
 * restated because a custom property is resolved where it is declared: the
 * :root copies were computed against the neutral ramp and would not follow
 * --card or --line on their own. checkout-elements.js reads --checkout-* off
 * <body>, which alias these, so Stripe's iframes lift with the page.
 *
 * Contrast on the panel fill (#1c1a18): --text 16.9:1, --text-muted 8.2:1,
 * --text-faint 5.6:1, --ds-accent 5.3:1, --ok 9.1:1, --warn 8.0:1, --danger 5.4:1.
 * ======================================================================== */
body.desktop-web[data-page="account"],
body.desktop-web[data-page="checkout"] {
  --surface-base: #0d0c0b;
  --surface-sidebar: #0b0a09;
  --surface-sunken: #131211;
  --surface-raised: #1b1a18;
  --surface-floating: #242220;
  --surface-hover: rgba(255, 248, 238, 0.06);
  --surface-active: rgba(255, 248, 238, 0.09);
  --background: var(--surface-base);
  --line: #2c2926;
  --line-strong: #3a3632;
  --line-raised: #48433e;
  --text: #fbf9f6;
  --text-muted: #b6b0a9;
  --text-faint: #948e87;
  --ok: #62d692;
  --ok-soft: rgba(98, 214, 146, 0.15);
  --ok-line: rgba(98, 214, 146, 0.5);
  --warn: #ebb55f;
  --warn-soft: rgba(235, 181, 95, 0.15);
  --warn-line: rgba(235, 181, 95, 0.5);
  --danger: #f0767c;
  --danger-soft: rgba(240, 118, 124, 0.15);
  --danger-line: rgba(240, 118, 124, 0.5);
  --danger-text: #f7a3a8;
  --card:
    var(--grain-layer),
    linear-gradient(158deg, #1e1c19 0%, #1b1917 46%, #191715 100%);
  --card-hover:
    var(--grain-layer),
    linear-gradient(158deg, #242220 0%, #211f1c 46%, #1e1c1a 100%);
  --ds-hairline: var(--line);
  --ds-hairline-strong: var(--line-strong);
  --ds-panel-fill: var(--card);
  --ds-panel-fill-hover: var(--card-hover);
  --ds-well-fill: var(--surface-sunken);
  --ds-control-fill: rgba(255, 248, 238, 0.07);
  --ds-control-fill-hover: rgba(255, 248, 238, 0.11);
  --ds-control-fill-press: rgba(255, 248, 238, 0.05);
  background: var(--surface-base);
  color: var(--text);
}
/* The state pills' rims are literals in §10; on the lifted ramp they follow
   the stronger state colours. */
body.desktop-web[data-page="account"] .status-pill.active,
body.desktop-web[data-page="checkout"] .status-pill.active { border-color: var(--ok-line); }
body.desktop-web[data-page="account"] .status-pill.ending,
body.desktop-web[data-page="checkout"] .status-pill.ending { border-color: var(--warn-line); }
body.desktop-web[data-page="account"] .status-pill.blocked,
body.desktop-web[data-page="checkout"] .status-pill.blocked { border-color: var(--danger-line); }
