/**
 * Red Orchid — Design System · Master Tokens
 * ============================================================
 * Single source of truth for every visual surface: the customer
 * panel (React/Tailwind), the calculator (future migration), the
 * Elementor site (via functions.php enqueue + --e-global-* overrides,
 * Phase 3), and print templates.
 *
 * Two layers:
 *   1. PRIMITIVES  (--ro-rgb-*, --ro-font-*)  — raw brand facts.
 *      RGB triples (not hex) so Tailwind opacity modifiers work:
 *      rgb(var(--ro-rgb-primary) / 0.15).
 *   2. SEMANTIC    (background/surface/foreground/sidebar/…) — role
 *      names components consume. Components NEVER reference a hex or
 *      a primitive directly; re-theming = re-pointing semantics here
 *      or on a scoped override selector.
 *
 * Hex twins (--ro-<name>) are provided for plain-CSS consumers
 * (Elementor global overrides, print CSS) that never need alpha math.
 *
 * Style: "Soft UI Evolution" (approved 2026-07-12 from
 * .dev/prototypes/customer-panel-soft-ui) — soft layered shadows,
 * 12-20px radii, 220ms transitions, WCAG AA+ contrast.
 * Interaction rule: NO transform motion on hover (shadow/border/
 * brightness only); press-down scale on :active is allowed.
 */

/* ------------------------------------------------------------------
 * Google Sans Medium — the ONLY non-Red-Orchid typeface in the system,
 * and it is here because Google's "Sign in with Google" branding
 * requirements make Google Sans Medium 14/20 mandatory on the provider
 * button. It is applied to that one label (.ro-social__label--google)
 * and to nothing else; substituting the site font there would be a
 * branding violation, not a design choice.
 *
 * Self-hosted rather than requested from fonts.googleapis.com: the
 * family is published under SIL OFL 1.1 (design-system/fonts/
 * GoogleSans-OFL.txt), so there is no reason to hand a visitor's IP to
 * a third origin for one button label.
 *
 * Declared site-wide but INERT: a declared-but-unreferenced @font-face
 * downloads nothing, so every page that never renders the button pays
 * zero bytes for it. Latin subset only — the auth surface is
 * English-only today.
 * ------------------------------------------------------------------ */
@font-face {
  font-family: "Google Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/GoogleSans-Medium-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ------------------------------------------------------------------
 * Merriweather — supplementary character sets (v3.216.0).
 *
 * The primary Merriweather and Manrope faces are NOT declared here:
 * Elementor Custom Fonts owns them (font posts 6560 / 6558, emitted into
 * the kit CSS) and points them at fonts/merriweather-var-latin.woff2 and
 * fonts/manrope-var.woff2. Manrope's file carries its whole character set;
 * Merriweather's latin file does not, because its other scripts would add
 * ~300 KB to every page for text the site's own copy never uses.
 *
 * These three faces cover the rest (customer names in the panel and
 * booking). They download only when such a character renders. They have
 * NO weight/style/stretch descriptors on purpose: Elementor emits its
 * variable face without them, and identical descriptors make all four one
 * composite face, so the variable wght/opsz axes behave exactly as on the
 * primary face. Adding a descriptor here would split them apart.
 *
 * Built by scripts/fonts/build-webfonts.py; SIL OFL 1.1
 * (fonts/Merriweather-OFL.txt).
 * ------------------------------------------------------------------ */
@font-face {
  font-family: "Merriweather";
  font-display: swap;
  src: url("fonts/merriweather-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Merriweather";
  font-display: swap;
  src: url("fonts/merriweather-var-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0460-052F, U+0490-0491, U+04B0-04B1,
    U+1C80-1C8A, U+20B4, U+2116, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

@font-face {
  font-family: "Merriweather";
  font-display: swap;
  src: url("fonts/merriweather-var-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}

:root {
  /* ---------------------------------------------------------- */
  /* 1 · PRIMITIVES — brand palette                              */
  /* ---------------------------------------------------------- */
  --ro-rgb-primary: 241 107 137;         /* Red Orchid pink   #F16B89 */
  --ro-rgb-primary-strong: 224 74 111;   /* deep pink (text/CTA on white) #E04A6F */
  --ro-rgb-secondary: 111 197 216;       /* teal              #6FC5D8 */
  --ro-rgb-secondary-strong: 62 151 173; /* deep teal         #3E97AD */
  --ro-rgb-accent: 247 168 83;           /* orange            #F7A853 */
  --ro-rgb-accent-strong: 239 139 46;    /* deep orange       #EF8B2E */
  --ro-rgb-success: 79 191 101;          /* green             #4FBF65 */
  --ro-rgb-success-strong: 46 158 72;    /* deep green (text) #2E9E48 */
  --ro-rgb-ink: 51 51 51;                /* legacy body ink   #333333 */

  --ro-primary: #F16B89;
  --ro-primary-strong: #E04A6F;
  --ro-secondary: #6FC5D8;
  --ro-secondary-strong: #3E97AD;
  --ro-accent: #F7A853;
  --ro-accent-strong: #EF8B2E;
  --ro-success: #4FBF65;
  --ro-success-strong: #2E9E48;

  /* Accessible text tier (R6, v3.99.0). Decorative hue and semantic text
     colour are SEPARATE tokens: `-strong` drives decorative fills, rules,
     borders and icons -- NEVER text at any size (accent-strong is 2.4992:1 on
     white, 2.2354 on the #F6F1F2 canvas, missing even the 3:1 large-text
     threshold), `-text` is what any text on a light surface takes.
     `-strong` is NOT deprecated and is NOT to be redefined -- its ~104 other
     consumers keep it. A surface carrying small text opts into `-text`
     EXPLICITLY, one declaration at a time.
     Ratios are vs #FFF / vs the warm canvas #F6F1F2, the two backdrops these
     actually land on. The canvas margins are thin ON PURPOSE and were
     measured, not rounded: re-derive against >= 4.5, never against a
     published figure. (The spec's --ro-secondary-text #317889 measures
     4.4952 on canvas and FAILS; #317788 is the corrected value.) */
  --ro-primary-text:   #D42450; /* 5.04 / 4.51 */
  --ro-secondary-text: #317788; /* 5.09 / 4.55 */
  --ro-accent-text:    #AB590D; /* 5.04 / 4.51 */
  /* First consumer: discounted Pricing Table current price. Owner decision D1
     keeps this an all-four-token release so the set cannot go inconsistent;
     R6 does not audit or re-point any --ro-success-strong consumer. */
  --ro-success-text:   #2E7E3E; /* 5.04 / 4.50 */
  --ro-ink: #333333;

  /* Typography */
  --ro-font-sans: "Manrope", system-ui, sans-serif;
  --ro-font-serif: "Merriweather", serif;

  /* Weight scale (R1, 2026-08-17). Five steps, each with ONE job, so a
     surface never has to guess. Emphasis is DECLARED, never inherited:
     until R1 the whole site inherited 700 from a malformed Elementor Kit
     global (see the guard in elementor-overrides.css), which made body copy
     read as bold and collapsed <strong> into its own background.
       body      normal prose, list copy, callout prose
       medium    secondary / meta text (durations, taglines, subtitles)
       semibold  inline <strong> emphasis, UI labels
       bold      UI chrome only — buttons, badges, pills, eyebrows, figures
       display   Merriweather headings
     semibold sits between body and bold on purpose: `strong { font-weight:
     bolder }` resolves against the inherited value, so from a corrected 400
     it computes 700 and would be indistinguishable from a control. */
  --ro-fw-body: 400;
  --ro-fw-medium: 500;
  --ro-fw-semibold: 600;
  --ro-fw-bold: 700;
  --ro-fw-display: 900;

  /* ---------------------------------------------------------- */
  /* 2 · SEMANTIC — light "Soft UI" surface (default scope)      */
  /* ---------------------------------------------------------- */
  --ro-rgb-background: 246 241 242;      /* warm pink-tinted canvas #F6F1F2 */
  --ro-rgb-surface: 255 255 255;         /* cards                   #FFFFFF */
  --ro-rgb-surface-2: 251 248 249;       /* nested tiles            #FBF8F9 */
  --ro-rgb-foreground: 56 46 51;         /* warm ink, 10.9:1        #382E33 */
  --ro-rgb-muted-fg: 113 99 106;         /* secondary text, 5.1:1   #71636A */
  --ro-rgb-border: 237 228 231;          /* hairlines               #EDE4E7 */
  --ro-rgb-border-hover: 229 205 214;    /* card hover tint         #E5CDD6 */
  --ro-rgb-ring: 224 74 111;             /* focus ring = primary-strong */

  --ro-background: #F6F1F2;
  --ro-surface: #FFFFFF;
  --ro-surface-2: #FBF8F9;
  --ro-foreground: #382E33;
  --ro-muted-fg: #71636A;
  --ro-border: #EDE4E7;
  --ro-border-hover: #E5CDD6;    /* hex twin of --ro-rgb-border-hover */
  --ro-border-accent: #C9771B;

  /* Dark sidebar scope (also reusable for any dark surface) */
  --ro-rgb-sidebar: 42 32 38;            /* #2A2026 */
  --ro-rgb-sidebar-deep: 36 27 32;       /* gradient end #241B20 */
  --ro-rgb-sidebar-2: 52 40 48;          /* raised chips #342830 */
  --ro-rgb-sidebar-fg: 243 235 238;      /* #F3EBEE */
  --ro-rgb-sidebar-muted: 180 163 171;   /* #B4A3AB */
  /* Neutral overlay behind controls that sit ON a photograph, where a branded backing would
     tint the image. Deliberately NOT --ro-rgb-sidebar-deep: at .65 this carries the viewer's
     dots capsule to counter 4.63:1 (needs 4.5) and inactive dot 5.20:1 (needs 3.0) measured
     against a pure-white photo, the case a sweep over real images cannot rule out (#223 S6). */
  --ro-rgb-overlay-neutral: 0 0 0;       /* #000000 */

  /* ---------------------------------------------------------- */
  /* 3 · EFFECTS — Soft UI Evolution spec                        */
  /* ---------------------------------------------------------- */
  --ro-shadow-card: 0 1px 2px rgba(56, 46, 51, 0.05), 0 6px 20px rgba(56, 46, 51, 0.07);
  --ro-shadow-card-hover: 0 2px 4px rgba(56, 46, 51, 0.06), 0 14px 32px rgba(56, 46, 51, 0.11);
  --ro-shadow-btn: 0 2px 6px rgba(224, 74, 111, 0.35), 0 8px 20px rgba(224, 74, 111, 0.22);
  --ro-shadow-btn-hover: 0 4px 10px rgba(224, 74, 111, 0.4), 0 12px 28px rgba(224, 74, 111, 0.28);
  --ro-shadow-sidebar-active: 0 6px 18px rgba(224, 74, 111, 0.4);

  --ro-radius-card: 20px;
  --ro-radius-control: 12px;

  --ro-dur: 220ms;
  --ro-ease: cubic-bezier(0.33, 0.9, 0.35, 1);

  /* ---------------------------------------------------------- */
  /* 4a · HORIZONTAL INSETS (R2) — the one-gutter model          */
  /* ------------------------------------------------------------ */
  /* Rule this encodes: below ~600px there is exactly ONE page-level
     horizontal inset, and any surface carrying body copy must still
     render text at >= 84% of viewport width. Measured before R2, the
     service-page callout rendered text at 40.3% of a 390px viewport
     because four insets stacked (gutter -> card -> callout -> icon).

     --ro-gutter is the page edge, owned by the shared .wrap rule.
     4.5vw keeps it proportional on small phones and reaches the
     previous fixed 24px at 533px, so nothing above tablet moves:
       360px -> 16.2   390px -> 17.6   533px+ -> 24 */
  --ro-gutter: clamp(16px, 4.5vw, 24px);

  /* Card interior and nested trim. Deliberately small at the narrow
     end: a card below the component threshold keeps a visible inset
     but must not spend the budget above. --ro-pad-card is the Service
     Detail card's interior in its wide state; --ro-pad-inset is the
     callout's rule-to-text trim, the one nested inset A-1 permits. */
  --ro-pad-card: clamp(14px, 3.5vw, 32px);
  --ro-pad-inset: clamp(12px, 2.5vw, 24px);

  /* ---------------------------------------------------------- */
  /* 4b · READABLE MEASURE + LONG-FORM SCALE (R2)                */
  /* ------------------------------------------------------------ */
  /* Derived, not chosen. Measured on live Manrope prose 2026-08-17:
     1ch (the "0" glyph) = 9.455px at 15.5px, while the mean advance
     over real prose is 6.955px — so 1ch = 1.36 REAL characters.
     That is why the previous `max-width: 78ch` rendered 106 characters
     per line, not 78. 48ch = 65 real characters at any size.
     Do not "correct" this to 66ch: that would be 90 real characters. */
  --ro-measure: 48ch;          /* 65 real characters — long-form prose */
  --ro-measure-tight: 42ch;    /* 57 real characters — dense list copy */
  /* Short lead copy inside a narrow box (quick-booking card, footer CTA).
     NAMES the 46ch those two already stated as literals — it is not a new
     number. Folding them into --ro-measure would WIDEN them, which the
     no-widen constraint forbids. */
  --ro-measure-lead: 46ch;     /* 62 real characters — lead copy, narrow box */

  /* Standalone text-only Reading Content column (Owner Option B, v3.206.0).
     A LENGTH, not a `ch` measure: the Owner set the width in pixels, and px
     sidesteps the ch-basis trap. Consumed by one rule in sections.css W5.
     Supersedes A-2 there: median ~78-82 real characters per line from ~680px
     viewport up (measured 2026-10-20) - accepted by the Owner. */
  --ro-measure-editorial: 720px;

  /* 65 characters at 15.5px is only ~454px, which wastes a 1200px
     canvas — so long-form prose scales UP rather than spreading out.
     Tuned to hold 15.5px at 390px exactly (no mobile change) and
     reach the desktop cap at 1440px: slope 1.5px / 1050px = 0.142857vw.
       390 -> 15.50   1440+ -> 17
     v3.133.2 restores the desktop cap to 17px for normal prose. Only the
     cap and the slope moved: the model, the four
     roles, the measures and the mobile floor are unchanged, and the ramp
     is still ONE continuous clamp with no container query, so continuity
     stays structural. A cap change re-opens every threshold derived
     against this ramp — but it moves them the SAFE way, since smaller type
     fits MORE characters into the same column. The 740 / 880 thresholds
     were therefore measured and deliberately left alone: re-solving them
     downward would be a layout change, not a type change.
     The former wide-state 22px type / 52ch measure override is retired;
     one continuous ramp now governs editorial prose at the 48ch measure. */
  --ro-fs-prose: clamp(15.5px, 0.142857vw + 14.9429px, 17px);
  --ro-lh-prose: 1.75;
  /* The four editorial ROLES. Every editorial surface consumes one of these;
     none states a px font-size of its own. All four derive from the one ramp
     above, so there is a single number to move. UI typography — buttons,
     badges, nav, cart, pricing-table rows, filter chrome — remains a SEPARATE
     family with its own semantic roles below.
       body        --ro-fs-prose        lh 1.75  fw 400  running editorial copy
       body-dense  --ro-fs-prose-dense  lh 1.6   fw 400  list copy
       lead        --ro-fs-lead         lh 1.6   fw 500  supporting copy under a heading
       meta        --ro-fs-meta         lh 1.5   fw 400  captions, conditions, notes
     body-dense keeps its distinct role, but the 2026-09-14 Owner D2 decision
     makes it the same size as body; density is carried by line-height alone.
     lead is deliberately the SAME size as body — the role is carried by
     weight and line-height, not by a size step, so a lead never competes
     with the heading above it. meta is flat, not fluid: a caption that grew
     with the viewport would stop reading as a caption. */
  --ro-fs-prose-dense: var(--ro-fs-prose);
  --ro-fs-lead: var(--ro-fs-prose);
  --ro-fs-meta: 13px;

  /* UI ROLES (v3.128.0, Owner D1, 2026-09-14). UI remains a separate family:
       ui        --ro-fs-ui        primary readable UI/content: names,
                                  navigation, main labels and paired prices
       ui-sm     --ro-fs-ui-sm     controls and secondary chrome
       meta      --ro-fs-meta      durations, struck prices, notes, captions,
                                  counters and hints
       micro     --ro-fs-ui-micro  uppercase, letter-spaced badges, pills,
                                  eyebrows and chips
     ui-sm and meta are corpus-derived; micro is the chosen 12px tie-break.
     ui retains the prior 16px prose ramp: prose must not resize navigation,
     buttons, prices or other interface chrome. ui-sm, meta and micro stay flat so controls and annotations do
     not grow with the viewport. Adjacent tiers keep a >=1px gap at every
     viewport. Scope guard: micro is uppercase micro chrome only — never
     primary navigation, service names, action labels or other meaningful
     customer-facing text. */
  --ro-fs-ui: clamp(15.5px, 0.047619vw + 15.3143px, 16px);
  --ro-fs-ui-sm: 14px;
  --ro-fs-ui-micro: 12px;

  /* The four heading RANKS. RANK IS NOT THE HTML TAG — importance chooses
     the rank, and the tag only says which rank a configurable widget gets.
     Two live proofs, both of which a tag-based rule would get wrong:
       .hero-title takes h1 rank as an <h1> and h2 rank as an <h2>, because a
         promotional SLIDE title is a section head, not the page title. This
         is the defect the ranks exist to fix: a deal slide used to render at
         43.2px, the same size as the page h1 beside it.
       .ro-maintenance-path__heading is an <h2> ELEMENT at h4 RANK, because it
         labels a link group rather than heading a section.
     So every rank selector is TAG-QUALIFIED wherever the widget takes a
     heading_tag setting (.hero-title, .values-title, .cx-title). A bare
     class -> rank map would size a page's only <h1> as an h2 and invert the
     hierarchy. Heading LEVELS are never changed by this file; only the size
     attached to a rank moves. Verify a rank gate over RANK, never over tag.
     .pt-group-label is a <div> and is deliberately NOT tag-qualified. */
  --ro-fs-h1: clamp(28px, 3vw, 44px);    /* lh 1.15  fw 900  serif — page title, ONE per page */
  --ro-fs-h2: clamp(24px, 2.8vw, 34px);  /* lh 1.2   fw 900  serif — section head */
  --ro-fs-h3: clamp(20px, 2.2vw, 26px);  /* lh 1.3   fw 900  serif — sub-head / card title */
  --ro-fs-h4: clamp(17px, 1.5vw, 20px);  /* lh 1.35  fw 700  serif — labelled group head */

  /* ---------------------------------------------------------- */
  /* 4 · LAYOUT RHYTHM — section-library composition beat        */
  /* ---------------------------------------------------------- */
  /* Full white-space between two adjacent sections (each section
     root takes half as padding-block, so any two neighbors always
     sum to one gap regardless of composition order). */
  --ro-section-gap: clamp(56px, 9vw, 128px);
  --ro-content-max: 1200px;

  /* Battle.net measured 1600px content cap from 1632px viewport; only Works archive and singles consume.
     Gutter stays 24px on phones and reaches 40px from 1334px (see the responsive-width research memo). */
  --ro-works-max: 1600px;
  --ro-works-gutter: clamp(24px, 3vw, 40px);

  /* Intra-section stack — spacing between elements INSIDE a section.
     Four steps only, chosen by relationship, so different elements get
     different-but-consistent breathing room:
       xs  eyebrow → title (tightly bound pair)
       sm  title → supporting text
       md  text → CTAs / meta / list content
       lg  section head → section body

     FLUID SINCE R2. These were fixed px while --ro-section-gap above is
     a clamp, so the hierarchy inverted on a phone: inter-section vs
     intra-section was 128:44 (2.9:1) at desktop but 56:44 (1.27:1) at
     390px, where sections stopped reading as separate exactly where the
     reader most needs the cue. Now ~2:1 at every width.
       390px -> 8 / 12 / 18 / 28      1440px -> 10 / 16 / 26 / 44 */
  --ro-stack-xs: clamp(8px, 1.6vw, 10px);
  --ro-stack-sm: clamp(12px, 2.6vw, 16px);
  --ro-stack-md: clamp(18px, 4.2vw, 26px);
  --ro-stack-lg: clamp(28px, 6vw, 44px);
}
