﻿/* ============ DQT — PC Optimization Service — original design ============
   Theme: BLACK & GOLD
   - Near-black warm base  → premium, focused, high-end feel
   - Gold accents          → luxury, quality, "top tier" service
   - Brighter gold CTAs    → the eye lands on the action
   - Green confirmations   → safety / success signals
*/

/* ============================== web fonts ==============================
   Self-hosted from /assets/fonts, not fonts.googleapis.com. The bytes are the
   same OFL files Google serves; re-fetch them with `node fetch-fonts.js`,
   which documents the exact query behind each one. Serving them here buys
   three things the CDN could not: no DNS+TLS to two third-party origins before
   the first glyph can be asked for, no visitor IP handed to a third party, and
   a CSP that can say `font-src 'self'` instead of naming an outside host.

   THE SYSTEM
   ----------
     Manrope           — everything that is read: body, UI, buttons, labels.
     Space Grotesk     — headings and the technical figures (--font-head).
     IBM Plex Sans Arabic / Hebrew — the same roles, in those scripts.

   Manrope replaces Inter as the text face. It is metrically near-identical
   (cap height 72 vs 73, x-height 54 vs 55 per 100px), so nothing that was
   tuned against Inter's proportions had to move — but it is a semi-geometric
   with flat-sided bowls, which rhymes with Space Grotesk's squared skeleton in
   a way Inter's neo-grotesque roundness never did. It is also half the weight:
   24 KB against Inter's 47 KB for the same latin subset.

   WHY unicode-range MATTERS HERE
   ------------------------------
   Each face declares only the codepoints it is for, so the Arabic and Hebrew
   files are fetched only by a reader who is actually looking at that script —
   an English visitor never pays for them, and no `html[lang]` rule is needed
   to arrange that. It also means a mixed-script string works properly: the
   "FPS" inside an Arabic sentence is Manrope, the Arabic around it is Plex.

   That is why the RTL faces are listed IN the shared stack below rather than
   behind a `html[lang="ar"]` override. The old override was defeated by every
   `font:` shorthand in this file (a shorthand resets font-family), which is
   why Arabic in the language button and the buttons rendered in whatever
   sans-serif the OS happened to offer.

   The Plex ranges are Google's own minus the punctuation that overlaps the
   latin subset (U+200C-200E, U+2010-2011, U+204F, U+2E41 for Arabic;
   U+0307-0308, U+200C-2010, U+25CC for Hebrew). Manrope is earlier in the
   stack and covers those already, so leaving them in could only ever cause a
   40 KB download for a hyphen.

   WEIGHTS
   -------
   Manrope and Space Grotesk are variable — one file each spans the range the
   design uses (400-800 and 500-700). Plex Arabic/Hebrew have no variable cut
   on Google Fonts, so they ship as two static weights per script. At ~42 KB an
   Arabic weight that is a real cost to a real reader, and CSS weight matching
   resolves the gaps cleanly: 500 picks 400, 600 and 800 pick 700.

   `font-display: swap` on every face: text paints immediately in the fallback
   and swaps when the file lands. Never `block` — a dark page with no text is
   worse than a dark page with the wrong text for 100ms. */
@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/fonts/manrope-latin-var.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;
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk-latin-var.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;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-sans-arabic-400.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891,
    U+0897-08E1, U+08E3-08FF, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-sans-arabic-700.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891,
    U+0897-08E1, U+08E3-08FF, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
}
@font-face {
  font-family: "IBM Plex Sans Hebrew";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-sans-hebrew-400.woff2") format("woff2");
  unicode-range: U+0590-05FF, U+20AA, U+FB1D-FB4F;
}
@font-face {
  font-family: "IBM Plex Sans Hebrew";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-sans-hebrew-700.woff2") format("woff2");
  unicode-range: U+0590-05FF, U+20AA, U+FB1D-FB4F;
}

:root {
  --bg: #070604;
  --bg-alt: #0d0b07;
  --surface: #14110a;
  --surface-2: #1c1810;
  --border: #2e2718;
  --text: #f7f3e8;
  --muted: #b0a68d;
  --faint: #988c72;
  --accent: #e3b341;
  /* The two gold stops, named so a second gradient can reuse them at a
     different angle without restating the palette. */
  --gold-lo: #f7dd8a;
  --gold-hi: #c9921a;
  --accent-grad: linear-gradient(135deg, var(--gold-lo), var(--gold-hi));
  /* Same gold, laid out horizontally: the tier meter samples slices of this
     across its whole width, so it needs a purely horizontal ramp. */
  --tier-base-grad: linear-gradient(to right, var(--gold-lo), var(--gold-hi));
  --cta-grad: linear-gradient(135deg, #ffd75e, #e8a318);
  /* One travelling gold sheen shared by the MOST POPULAR badge and the tier
     meter, so the two read as the same material rather than two golds. */
  --gold-sheen: linear-gradient(110deg, #f7dd8a 20%, #c9921a 40%, #ffe9a8 50%, #c9921a 60%, #f7dd8a 80%);
  --on-gold: #1a1402;
  --success: #34d399;
  --danger: #f87171;
  --radius: 16px;
  --shadow: 0 10px 40px rgba(0, 0, 0, 0.55);
  --maxw: 1140px;

  /* Two roles. --font-text is the site's default and is set on <body>;
     --font-head is the display face, opted into by h1-h4 and by the technical
     figures. Every rule that used to name "Inter" directly now names one of
     these two, INCLUDING the `font:` shorthands — a shorthand resets
     font-family, which is why the old `html[lang="ar"] body` override never
     reached the buttons or the language picker.

     The RTL faces are NOT here. They are added to these same two variables by
     the html[lang] rules below, and the reason is measured: the language
     picker lists every language in its own script, so `العربية` and `עברית`
     are in the DOM of every page. Naming the Arabic family in the default
     stack made an English visitor download 47 KB of Arabic to render one menu
     item. Scoping it to the document language costs those two words the branded
     face on a page written in neither — a trade worth making, and the only
     place on the site where it applies. */
  --font-text: "Manrope", system-ui, "Segoe UI", sans-serif;
  --font-head: "Space Grotesk", "Manrope", system-ui, "Segoe UI", sans-serif;

  /* ======================= THE BENCH LAYER =============================
     Added by the homepage design pass. Nothing above this comment changed:
     the nine brand tokens (--bg, --surface, --surface-2, --border, --text,
     --muted, --accent, --on-gold, --radius) are exactly the values they were,
     because the identity is not what this pass is revising. What it adds is a
     SURFACE GRAMMAR the whole page can share, in place of the one opaque brown
     box that every card used to be.

     WHY FILMS AND NOT COLOURS
     -------------------------
     Each level is an alpha film of the text colour over whatever is already
     behind it. A module nested inside a panel therefore agrees with its parent
     automatically instead of patching over it, and the same token works on the
     page ground, on a section-alt ground, and on top of one of the eight game
     photographs — none of which a fixed hex could do.

     THE LADDER
     ----------
       PAGE      --bg / --bg-alt        the deck
       PANEL     --surface (opaque)     shells that must not let art through
       MODULE    --sf-1                 a thing seated in the panel
       ACTIVE    --sf-2                 selected, hovered, or one level deeper
       LIP       --lip                  a lit machined edge along the top

     The lip replaces the drop shadow. A blurred shadow says "this card is
     floating above the page", which is the SaaS reading this pass is moving
     away from; a 1px inset highlight says "this part is raised out of the same
     piece of metal", which is what a bench instrument looks like.

     1px and not the 0.5px hairline the references use: <body> carries
     `zoom: var(--ui-scale)` at 0.95, which resolves 0.5px to 0.475px and drops
     the line entirely on a 1x display. */
  --sf-1: rgba(247, 243, 232, 0.035);
  --sf-2: rgba(247, 243, 232, 0.06);
  --ring: rgba(247, 243, 232, 0.09);
  --ring-gold: rgba(227, 179, 65, 0.42);
  --lip: inset 0 1px 0 rgba(247, 243, 232, 0.05);
  --lip-lit: inset 0 1px 0 rgba(255, 240, 205, 0.18);

  /* The instrument register. Tail is --font-text, not `monospace`, so the RTL
     faces stay in the chain: the browser resolves per codepoint, so a Latin
     "FPS" or "ms" gets the mono face and the Arabic or Hebrew around it still
     gets Plex. Same trick as --font-text itself, see the html[lang] rules. */
  --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, var(--font-text);

  /* ----------------------------- motion ------------------------------
     Three durations for the whole homepage, and no fourth. Before this pass
     the file carried 0.15s, 0.2s, 0.22s, 0.24s, 0.25s, 0.26s, 0.28s, 0.3s,
     0.32s, 0.6s and more, all chosen locally, which is why nothing on the page
     felt like it belonged to one machine.

       FAST     a switch closing under the finger
       UI       a state changing: selection, hover, disclosure
       SECTION  content arriving on scroll

     One easing for everything that travels. No overshoot anywhere: a bench
     instrument that springs past its reading is a broken instrument. */
  --t-fast: 150ms;
  --t-ui: 200ms;
  --t-sec: 420ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Whole-site render scale. 1 = 100%, 0.95 = one browser-zoom step down.
     Change this single value to retune it; see the `zoom` rule on body. */
  --ui-scale: 0.95;
}

/* Phones stay at 100%. Below 560px the layout is already a single column, so
   shrinking buys no extra content — it only costs text legibility and pushes
   tap targets further under the 44px minimum. */
@media (max-width: 560px) {
  :root { --ui-scale: 1; }
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* The hidden attribute must always win, even over display:grid/flex rules below */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }
body {
  font-family: var(--font-text);
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* ------------------------------ UI scale ------------------------------
     Renders the site one browser-zoom step down (95%), so a little more fits
     on screen without touching a single size value anywhere else.

     `zoom` rather than `transform: scale()` on purpose: zoom participates in
     layout, so there is no leftover gap at the edges, no doubled scrollbar,
     and position:sticky/fixed keep working. A transform would create a
     containing block and break the sticky header.

     Applied to <body>, not <html>: zooming the root element changes the
     coordinate space media queries are evaluated in, which would shift every
     breakpoint by ~5% and could flip a layout right at the boundary.
     On <body> the breakpoints stay honest. */
  zoom: var(--ui-scale, 1);
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { font-family: var(--font-head); }

/* ------------------------- technical figures -----------------------------
   Tabular figures, and only here. Both text faces default to PROPORTIONAL
   digits with a lot of spread — in Manrope a `1` is 39 units wide against an
   `8` at 58, in Space Grotesk 43.5 against 61.1 — which is right for a price
   sitting in a sentence and wrong for a number that changes in place.

   The hero counters animate from 0 to their final value, so with proportional
   digits the label under them shifts left and right the whole way up, and
   `1189 → 1471` in the results panel does not line up with the row above it.
   `tabular-nums` pins every digit to one advance and the movement stops.

   Not set globally: proportional digits are the better default in running
   prose, and this site has plenty of it. Applied per-slot instead, to the
   figures that are read as measurements.

   A happy accident worth keeping: Manrope and Space Grotesk have the SAME
   tabular advance (0.62em), so a price in the display face and its `/ session`
   suffix in the text face sit on one grid rather than two. Changing either
   family means re-checking that. */
.stat strong, .plan-price, .plan-was, .value-box-num, .mt-big-val, .mt-stat b,
.result-big, .rec-price, .est-box b {
  font-variant-numeric: tabular-nums;
}

section { scroll-margin-top: 84px; }
::selection { background: var(--accent); color: var(--on-gold); }

/* gold-tinted scrollbar */
::-webkit-scrollbar { width: 11px; }
::-webkit-scrollbar-track { background: var(--bg-alt); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }

.container { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
.container-narrow { max-width: 780px; }

/* ------------------------------- buttons ------------------------------- */
/* --------------------------------- actions -------------------------------
   The pair, and the rule the whole gold system rests on: FILLED GOLD IS AN
   ACTION AND NOTHING ELSE. Once the eyebrows, the metric tiles and the card
   outlines gave their gold back, there are exactly two filled gold objects
   left in a viewport — this button and, in the plans grid, the recommended
   plan's button — which is what makes them findable without a glow.

   PRIMARY is metal, not a coloured rectangle. Three layers, in this order:
     - the gold ramp;
     - `inset 0 1px 0` in warm white along the top edge, which is a lit bevel;
     - `inset 0 -1px 0` in dark gold along the bottom, which is the shadow the
       bevel implies. Without the second one the highlight reads as a stray
       line rather than as a formed edge.
   The 24px coloured drop shadow is gone. A gold button that glows is a gold
   button that is trying to be noticed; a gold button that is the only filled
   thing on the screen already has been.

   SECONDARY is a hairline, and it stays plainly interactive: it lights its
   ring and thickens its film on hover, the same illumination every other
   control on the page uses.

   Neither one moves any more. `translateY(-2px)` on hover, on a control the
   pointer has to travel across to reach, is a target that steps away from the
   cursor at the moment of arrival. The press state keeps its scale, because
   that one is a real report: the button was hit. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 20px; border-radius: 8px; border: 1px solid transparent;
  font: 600 14.5px/1 var(--font-text); cursor: pointer; white-space: nowrap;
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease,
              color var(--t-ui) ease, box-shadow var(--t-ui) ease, filter var(--t-ui) ease;
}
.btn-lg { padding: 15px 26px; font-size: 15.5px; }
.btn-primary {
  background: var(--cta-grad); color: var(--on-gold);
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 219, 0.55),
    inset 0 -1px 0 rgba(120, 78, 0, 0.35);
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-ghost { background: var(--sf-1); border-color: var(--ring); color: var(--text); box-shadow: var(--lip); }
.btn-ghost:hover { background: var(--sf-2); border-color: var(--ring-gold); color: var(--text); }
.btn:active:not(:disabled) { transform: scale(0.96); transition-duration: 0.08s; }
.btn:disabled { opacity: 0.6; cursor: wait; transform: none; }
/* shared "picked" pop for toggles (chips, tabs) */
@keyframes pressPop { 40% { transform: scale(1.06); } }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --------------------------------- nav --------------------------------- */
/* Compact and technical, not a glass slab. Two states:

     at rest    — the header is a film over the hero, so the page starts at the
                  hero and not at a bar. Hairline bottom edge only.
     scrolled   — it becomes an opaque deck with a lit lip, because from here
                  on there is content sliding underneath it that must not read
                  through the labels.

   `.nav-solid` is set on <html> by the scroll handler main.js already runs for
   the back-to-top button, so this costs no second listener. Without JS the
   header simply stays in its at-rest state, which is legible on every section
   ground the page has — verified against all eight. */
.nav {
  position: sticky; top: 0; z-index: 50;
  background: rgba(7, 6, 4, 0.72); backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--ring);
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
.nav-solid .nav {
  background: rgba(7, 6, 4, 0.94);
  border-bottom-color: var(--border);
  box-shadow: var(--lip);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 66px; gap: 24px; }
/* never let flex squeeze the logo — it must always render at its full aspect */
.brand { flex: none; display: flex; align-items: center; gap: 11px; }
.brand img { height: 50px; width: auto; max-width: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }
.brand-sub {
  font-size: 10.5px; font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase; margin-top: 0;
  /* animated gold sheen, matching the hero H1 */
  background: var(--gold-sheen);
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: gradSheen 5.5s ease-in-out infinite;
}
.brand-lockup { display: inline-flex; align-items: center; gap: 11px; }
.brand-lockup img { height: 46px; width: auto; }

/* ---------------------------- the plan mark ------------------------------
   FOUR MARKS AGAIN, ONE SYSTEM.

   The previous pass flattened all four tiers onto the single gold mark, on the
   argument that a logo which changes colour to mean "more expensive" is not a
   logo. That argument is sound about a LOGO — and wrong about what these four
   files are. They are not four brands: they are one silhouette, one geometry,
   one set of bevels, finished in four metals. A rack of instruments from the
   same manufacturer does exactly this, and the reason is legibility: at four
   cards across, the mark is the only element large enough to tell you which
   tier you are looking at before you have read a word.

   So the tint comes back, and this time it is SYSTEMATISED rather than four
   hand-tuned drop shadows. Each card declares one token — `--tier-rgb` — and
   everything tier-coloured on that card is derived from it:

     the mark's own finish   (the .webp file: silver, blue, gold, red)
     the bloom behind it     (.plan::before, a radial wash at 10%)
     the lit lip             (inset hairline along the card's top edge)
     the resting ring        (16%, so it reads at a glance and never shouts)
     the hover ring          (38%)

   Five uses, one value, no per-card exceptions — which is what stops this
   being the four-companies problem it was before. What deliberately does NOT
   take the tint: the tier meter, the price, the feature ticks and the button.
   The meter is the shared instrument and stays gold on every card, the ticks
   stay green because they mean "included" and not "which tier", and gold fill
   still means action and nothing else. */
.plan {
  /* Warm silver. The free tier is the one that must not look like a colour
     grade of the paid ones, so it takes the neutral end of the same warm axis
     the whole palette sits on rather than a fifth hue. */
  --tier-rgb: 198, 190, 172;
}
.plan--free      { --tier-rgb: 198, 190, 172; }
.plan--ignition  { --tier-rgb: 62, 148, 255; }
.plan--overdrive { --tier-rgb: 227, 179, 65; }
.plan--apex      { --tier-rgb: 255, 96, 96; }
/* The one per-tier exception, and it is a property of the ASSET rather than a
   design decision. logo-mark-red.webp is a much paler file than its blue and
   gold siblings — screenshotted side by side it reads as dusty pink where the
   blue reads as blue — so Apex alone gets the mark's own colour pushed back up
   to where the other three already sit. Saturation and brightness only: the
   shadows are still the shared derived pair above, so nothing about how the
   tier system works is special-cased here, only how one PNG was exported. */
.plan--apex .plan-logo { filter: saturate(1.45) brightness(1.18) drop-shadow(0 4px 10px rgba(0, 0, 0, 0.55)) drop-shadow(0 0 12px rgba(var(--tier-rgb), 0.42)); }
.plan-logo {
  display: block; height: 58px; width: auto; margin: 2px auto 14px;
  position: relative; z-index: 1;
  /* Two shadows, both derived: a black one for weight against the deck, and a
     tier-coloured one that is the mark's own light rather than a glow bolted
     on behind it. 0.42 and not the old 0.9 — at full strength the red and blue
     haloes were the brightest objects in the rack, brighter than the one gold
     button that is supposed to be. */
  filter:
    drop-shadow(0 4px 10px rgba(0, 0, 0, 0.55))
    drop-shadow(0 0 12px rgba(var(--tier-rgb), 0.42));
}
.nav-links { display: flex; gap: 26px; }
/* The link is a slot with a lamp under it. The lamp is the state; the colour
   is the confirmation. Two channels, so the current section is still findable
   with the gold desaturated away.

   `inset-inline` + `scaleX` from the centre, so it needs no RTL twin: a bar
   that grows out of the middle grows the same way in both directions. */
.nav-links a {
  position: relative; color: var(--muted); font-size: 14.5px; font-weight: 500;
  padding-block: 6px;
  transition: color var(--t-ui) ease;
}
.nav-links a::after {
  content: ""; position: absolute; inset-inline: 0; bottom: -2px; height: 2px;
  border-radius: 2px; background: var(--accent-grad);
  transform: scaleX(0); transform-origin: center;
  transition: transform var(--t-ui) var(--ease);
}
.nav-links a:hover { color: var(--text); }
.nav-links a.active { color: var(--accent); }
.nav-links a.active::after { transform: scaleX(1); }
.nav-cta { display: flex; align-items: center; gap: 12px; }
/* A 44x44 INTERACTIVE area on a 36x33 visible one, and the split is the whole
   point.

   The obvious fix — min-inline-size: 44px on the button — was tried and
   reverted, because it is measurably wrong here. It grew the control by 6px,
   and the header row at 371px has 3.63px of slack in Hebrew and 4.71px in
   Arabic (see nav-rtl-narrow-overflow.test.mjs for the derivation). Measured
   after the change: `.nav-cta` started at x = -1 and the document scrolled to
   375px in both languages. Fixing a touch target by re-opening a horizontal
   overflow is not a fix.

   A pseudo-element hit area costs the layout nothing. The button keeps the
   exact box it shipped with, so every figure the RTL fit arithmetic depends on
   is still valid, and the ::after extends the clickable region 4px past each
   edge and ~5.5px past the top and bottom to reach 44 square. It overlaps
   half of the 8px .nav-cta gap and nothing else, so no neighbouring control
   loses any of its own target.

   `position: relative` on a control that had none, and nothing else changes. */
.nav-burger {
  display: none; position: relative;
  background: none; border: 1px solid var(--border); color: var(--text);
  font-size: 18px; border-radius: 10px; padding: 6px 12px; cursor: pointer;
}
.nav-burger::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  translate: -50% -50%;
  min-width: 44px; min-height: 44px;
  width: 100%; height: 100%;
}
/* ------------------- a third party's brand, hosted ----------------------
   Discord blurple filled the whole control and threw a 24px blurple glow, and
   on a page whose entire palette is warm near-black and gold that made the
   single loudest colour in the header belong to somebody else's brand.

   What actually needs to be recognisable is the MARK, not the fill. So the
   button takes DQT's own material — the same recessed dark surface and
   hairline every secondary control on this site wears — and spends the
   blurple where it does the recognising: on the logo, which stays #5865f2 at
   full strength because `fill: #5865f2` on the svg rather than `currentColor`
   decouples it from the label beside it.

   The brand then comes forward on interaction rather than at rest: the border
   and the ring go blurple on hover and on focus, which is more Discord than
   the flat block was, and it happens at the moment somebody is reaching for
   it. */
.btn-discord {
  background: var(--sf-1); color: var(--text);
  border: 1px solid var(--ring);
  box-shadow: var(--lip);
}
.btn-discord:hover {
  background: var(--sf-2); color: var(--text);
  border-color: rgba(88, 101, 242, 0.75);
  box-shadow: var(--lip), 0 0 0 3px rgba(88, 101, 242, 0.18);
}
.btn-discord:focus-visible {
  border-color: rgba(88, 101, 242, 0.9);
  box-shadow: var(--lip), 0 0 0 3px rgba(88, 101, 242, 0.3);
}
.btn-discord svg { width: 20px; height: 20px; fill: #5865f2; flex: none; }
.plan-discord { width: 100%; margin-top: auto; }
.lang { position: relative; }
.lang-btn {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 8px 10px; font: 600 13px var(--font-text); cursor: pointer;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.lang-btn:hover, .lang.open .lang-btn { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(227, 179, 65, 0.14); }
.lang-caret { width: 13px; height: 13px; color: var(--muted); transition: transform 0.25s ease; }
.lang.open .lang-caret { transform: rotate(180deg); }
.lang-menu {
  position: absolute; top: calc(100% + 8px); inset-inline-end: 0; z-index: 60;
  min-width: 130px; margin: 0; padding: 6px; list-style: none;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45);
  opacity: 0; transform: translateY(-8px) scale(0.97); transform-origin: top;
  visibility: hidden; pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}
.lang.open .lang-menu { opacity: 1; transform: none; visibility: visible; pointer-events: auto; }
.lang-opt {
  padding: 9px 12px; border-radius: 8px; font: 600 13px var(--font-text); color: var(--muted);
  cursor: pointer; white-space: nowrap; text-align: start; transition: background 0.15s, color 0.15s;
}
.lang-opt:hover, .lang-opt:focus-visible { background: var(--surface); color: var(--text); outline: none; }
.lang-opt[aria-selected="true"] { color: var(--accent); }
/* ----------------------------- RTL typography ---------------------------
   The Arabic and Hebrew faces enter through the two font variables rather than
   through a `body { font-family: … }` override, so they reach every rule —
   including the `font:` shorthands that defeated the old override and left the
   language button and the buttons rendering in whatever the OS offered.

   Manrope stays FIRST in both stacks. Its unicode-range covers Latin only, so
   the browser resolves per codepoint: the "FPS" inside an Arabic sentence is
   Manrope, the Arabic around it is Plex. Putting Plex first would work too —
   it has its own Latin — but then the site's Latin would change identity
   depending on which page you were on.

   Set on html[lang] and not on [dir="rtl"]: the two RTL languages need
   different families, and `dir` cannot tell them apart. */
html[lang="ar"] {
  --font-text: "Manrope", "IBM Plex Sans Arabic", system-ui, "Segoe UI", sans-serif;
  --font-head: "Space Grotesk", "Manrope", "IBM Plex Sans Arabic", system-ui, sans-serif;
}
html[lang="he"] {
  --font-text: "Manrope", "IBM Plex Sans Hebrew", system-ui, "Segoe UI", sans-serif;
  --font-head: "Space Grotesk", "Manrope", "IBM Plex Sans Hebrew", system-ui, sans-serif;
}

/* --------------------------- RTL letter-spacing --------------------------
   Every letter-spacing value in this file was chosen for Latin, and none of
   them should reach these scripts:
   Arabic is cursive, so positive tracking pulls joined letters apart and the
   word stops looking like a word; Hebrew has no case, so the "uppercase label"
   convention that justifies +2px on a kicker does not exist there either. The
   negative tracking on the headings is the same mistake in the other
   direction — -1px crowds a script whose legibility lives in its counters.

   Listed rather than applied with a blanket `[dir="rtl"] *`, so that adding a
   tracked element is a deliberate decision about whether it carries translated
   text. `.brand-sub` is deliberately absent: the wordmark is "PC Optimization
   Service" in every language, so it keeps its Latin tracking under RTL. */
/* `.pill` was missing from this list, and it is the hero eyebrow — 11.5px of
   translated copy at +0.11em. In Arabic that is +1.27px pulling apart the
   joins of "ضبط يدوي · إصلاح الأعطال · نتائج قابلة للقياس", which is the exact
   failure the list exists to prevent; it was only ever absent because `.pill`
   and `.kicker` are declared together but were listed apart. Measured at
   390px before the fix: letter-spacing 1.265px in ar and he.

   `.result-state`, `.value-scenario` and `.fps-read i` are new in this pass
   and are added at the same time: all three are tracked mono labels carrying
   translated strings, which is the whole membership test for this rule.

   `.value-vs` replaces the three `.vf-*` register labels the value card used
   to carry (`.vf-cell dt`, `.vf-finding-label`, `.vf-ways`), which went with
   the scorecard. It is the same membership test: 11px mono at +0.14em, and it
   carries a translated word — "مقابل" in Arabic, "מול" in Hebrew — which at
   that tracking is two Hebrew letters pulled apart from each other.

   `.consent-always` is new with the consent panel and joins on the same test:
   11px mono at +0.08em carrying a translated phrase — "دائماً" in Arabic,
   "תמיד" in Hebrew — which is exactly the shape this list protects. */
[dir="rtl"] :is(h1, h2, h3, h4,
                .pill, .kicker, .strip-label, .fps-card-title, .ticker-track span,
                .value-box-label, .mt-stat span, .sev,
                .est-label, .perf-state, .rail-label,
                .stat span, .tool-panel label:not(.chip) > span,
                .result-state, .value-scenario, .fps-read i,
                .value-vs,
                .plan-duration, .est-unit, .plan ul li:first-child,
                .badge-track, .footer-cols h4, .plan-flag, .plan-was,
                .consent-always) {
  letter-spacing: normal;
}

/* ---------------------------- Arabic hero leading ------------------------
   The one place a script needed more room than the Latin design gives it.
   Measured ink extent (tallest ascender to deepest descender) of each
   language's actual hero string, as a multiple of the font size:

       en 0.92   es 0.92   he 0.93   ar 1.12

   `.hero h1` sets line-height 1.08, which the first three clear with room to
   spare. Arabic does not — it needs 1.12em and gets 1.08em, so consecutive
   lines of the four-line Arabic headline overlap and a descender can land on
   the ascender below it. 1.25 clears the ink by 0.13em.

   Scoped to Arabic and to the hero on purpose. Hebrew at 0.93em would only
   pay for height it does not need, and every other heading either sets a
   looser line-height already or inherits the body's 1.6: the masthead h2 is
   the next tightest at 1.12 against Arabic ink of 0.99em, which clears. */
html[lang="ar"] .hero h1 { line-height: 1.25; }

/* --------------------------------- hero -------------------------------- */
.hero {
  position: relative; overflow: hidden;
  background:
    radial-gradient(900px 500px at 85% -10%, rgba(201, 146, 26, 0.14), transparent 60%),
    radial-gradient(700px 400px at -10% 30%, rgba(247, 221, 138, 0.07), transparent 60%),
    var(--bg);
}
.hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(rgba(227, 179, 65, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(227, 179, 65, 0.05) 1px, transparent 1px);
  background-size: 44px 44px;
  mask-image: radial-gradient(700px 460px at 70% 20%, #000 30%, transparent 75%);
}
/* Three areas, not two columns of stacked blocks. The metric strip, the
   ticker and the footnote used to be the tail of .hero-copy, which meant the
   phone had exactly one possible order: all of the copy, then all of the
   readout, then — at y=1317 on an 844px screen — the proof.

   Splitting the readout into its own grid AREA costs desktop nothing (row 2 of
   the copy column is where it already sat) and buys the phone the one
   reordering that matters: copy, PROOF, readout. `row-gap: 0` because the
   ticker and the strip carry their own margins; a grid gap here would add to
   them and re-open the height this pass exists to close. */
.hero-inner {
  position: relative;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  grid-template-areas: "copy visual" "readout visual";
  column-gap: 48px; row-gap: 0;
  align-content: center; padding: 92px 24px 80px;
}
.hero-copy { grid-area: copy; }
.hero-visual { grid-area: visual; align-self: center; }
.hero-readout { grid-area: readout; }
.hero-inner > div { min-width: 0; } /* let the ticker shrink instead of forcing overflow */
/* ------------------------------- the rail --------------------------------
   The eyebrow, and the one element this design is built around. It replaces
   the filled pill that used to sit here and the filled gold pill on every
   section head, for a reason the research is unanimous about: Axiom reserves
   its accent for actions and active states and calls decorative use of it a
   mistake; Superlative goes further and keeps its accent OFF actions entirely,
   spending it only on functional indicators. A gold lozenge that says
   "FREE TOOLS" is neither. It is gold spent on a noun.

   What replaces it is an instrument label on a rail: a lit lamp, the label in
   the mono register, and a hairline that runs from the label out to the edge
   of the block. The lamp is the only gold, it is 6px across, and it means the
   same thing here as everywhere else on the page — this one is live.

   Built entirely from pseudo-elements on the existing element. It has to be:
   every one of these labels carries data-i18n, and build-ar.js replaces the
   whole inner content of a data-i18n element, so a wrapper <span> would exist
   in English and be gone from /ar/. Nothing here adds a node, and no string
   changed in any of the four languages. */
.pill, .kicker {
  /* `start`, not `center`. These labels are one line at desktop and two on a
     390px phone — "⚡ Manual tuning · Issue fixing · Measurable gains" wraps —
     and a centred lamp on a two-line label floats down beside the SECOND line,
     which reads as a stray dot rather than as a status light on the label.
     Aligning to the start pins the lamp and the rail to the first line at
     every width, and the margin below re-centres the lamp within that line
     box: (11.5px x 1.35 line-height - 6px lamp) / 2 = 4.7px. */
  display: flex; align-items: flex-start; gap: 10px;
  padding: 0; border: 0; border-radius: 0; background: none;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 11.5px; font-weight: 600; line-height: 1.35;
  text-transform: uppercase; letter-spacing: 0.11em;
}
/* The hero eyebrow carries a three-part sentence rather than a two-word
   section name, so it sits one step brighter than the quiet tier — readable as
   copy, still clearly a label. Its lamp is lit (see the promoted-lamp rule
   below), which is what puts it in the same tier as .kicker-main without
   giving a 60-character string the full gold. */
.pill { margin-bottom: 20px; color: var(--muted); }
/* Base is the QUIET tier, exactly as it was before this pass: the three
   .svc-kicker labels introduce card groups rather than sections and must not
   out-shout the cards under them. The three tiers survive the change of
   material — they are now told apart by how brightly the lamp burns and how
   far the rail carries, instead of by how much gold the pill is filled with. */
/* The lamp. A filled core inside a dim halo, so it reads as lit rather than
   printed — the same two-part construction as the status dots in the proof
   card head, at a third of the size. */
.pill::before, .kicker::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  margin-block-start: 4.7px;
  background: rgba(227, 179, 65, 0.45);
}
.pill::before, .kicker.kicker-main::before, .kicker.kicker-special::before {
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(227, 179, 65, 0.14);
}
/* The rail. flex:1 rather than a fixed width, so it always reaches the end of
   whatever block the label sits in and never has to be re-measured for a
   longer translation. min-width lets it collapse rather than push a long
   Arabic label into overflow. */
/* The rail sits on the first line's optical centre for the same reason, and
   disappears rather than wrapping when the label takes the whole row: at
   min-width 0 a flex item with no content simply collapses. */
.pill::after, .kicker::after {
  content: ""; flex: 1 1 auto; min-width: 0; height: 1px;
  margin-block-start: 7.2px;
  background: linear-gradient(to right, var(--ring), transparent 88%);
}
[dir="rtl"] .pill::after, [dir="rtl"] .kicker::after {
  background: linear-gradient(to left, var(--ring), transparent 88%);
}

/* -------------------------- the hero headline ----------------------------
   Four things, and the first is the one that matters.

   1. `text-wrap: balance`. The headline is two translated strings in one h1
      and it was breaking wherever the line box happened to run out — "Check
      your system before you / book" in the tools head, and a one-word fourth
      line in the hero at several common widths. Balance evens the lines by
      construction, in every language, and it costs nothing on a heading this
      short (the spec's 6-line cap is never approached). Chromium, Firefox and
      Safari all ship it; anything older simply gets the greedy break it got
      before.

   2. A max width in `ch`, not px. The measure is a property of the type, so
      the limit has to be expressed in the type's own units or it changes
      meaning the moment a translation is set in a different face. 26ch is
      wider than the 570px hero column at desktop, so it does nothing there;
      it binds below 1000px, where the hero collapses to one column and the
      headline would otherwise run the full 710px container.

   3. The clamp tops out at 44px, not the 60px it was, and that number is
      measured rather than chosen. The English headline is 73 characters in a
      570px hero column, and the break pattern changes at 48px:

          56px   Gaming PC / Optimization Service / for FPS Boost, Lower /
                 Latency & Stutter Fix              4 lines, 226px, first
                                                    line a 263px stub
          48px   same four lines                    193px
          44px   Gaming PC Optimization /
                 Service for FPS Boost, /
                 Lower Latency & Stutter Fix        3 lines, 133px, widths
                                                    463 / 426 / 531

      44px is the largest size that fits three lines, and three lines is where
      the breaks land on phrase boundaries instead of stranding "Gaming PC" on
      a half-width first line. It also returns 93px of hero height, which is
      what keeps the primary action above the fold on a 1440x960 laptop with
      the promo bar showing.

      `balance` cannot fix a size that is simply too big for its column: it
      chooses WHERE to break, not how many lines to break into. Both settings
      were needed.

   4. -0.02em instead of -1px. Tracking that is stated in pixels gets looser as
      the clamp scales up, which is backwards — larger type wants tighter
      tracking, not looser. The [dir="rtl"] reset already exempts h1. */
.hero h1 {
  font-size: clamp(32px, 3.6vw, 44px); font-weight: 700;
  line-height: 1.06; letter-spacing: -0.02em;
  text-wrap: balance; max-inline-size: 26ch;
}
.grad {
  background: var(--gold-sheen);
  background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: gradSheen 5.5s ease-in-out infinite;
}
@keyframes gradSheen { 0%, 100% { background-position: 0% center; } 50% { background-position: 100% center; } }
/* `pretty` and not `balance`: on a paragraph, balance evens every line and
   makes running prose look like a poster. pretty only fixes the last line,
   which is the actual defect — a single stranded word under four full ones. */
.lead {
  color: var(--muted); font-size: 17px; margin: 22px 0 30px;
  max-inline-size: 54ch; text-wrap: pretty;
}
.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; }

/* ------------------------- the hero on a phone ---------------------------
   Everything below is one change with five parts, and the measurement it is
   answering is this: at 390x844, the copy stack ran 1148px and the benchmark
   started at y=1317 — a screen and a half of prose before the first piece of
   evidence. The five parts, in order of how much height each returns:

     the readout moves below the proof (grid areas, above)   ~330px
     the service inventory drops out of the lead              ~66px
     the headline steps down to 27px with a tighter measure   ~34px
     the two actions stop being two equal 48px slabs          ~55px
     the guarantee stops being a two-line pill                ~21px

   None of it is a proportional scale-down: the h1 keeps its weight and its
   tracking, the primary action keeps its full size, and the guarantee keeps
   its sentence. What shrinks is what was repeating something the page says
   again a few hundred pixels later. */
@media (max-width: 720px) {
  /* 27px, not the 32px the clamp bottomed out at. The English headline is 73
     characters; at 32px in a 342px column it sets five lines (136px + a
     stranded word), at 27px it sets four at 118px and the breaks land on
     phrase boundaries. 24ch is the measure that keeps them there — `balance`
     picks WHERE to break, never how many lines there are to break into. */
  .hero-inner { padding-top: 48px; }
  .hero .pill { margin-bottom: 14px; }
  .hero h1 { font-size: 27px; line-height: 1.12; max-inline-size: 24ch; }
  .lead { font-size: 15.5px; margin: 16px 0 22px; }
  /* The service inventory. It is the ticker's entire content, verbatim, and
     the first three cards of "Everything we do" — the one sentence in the
     hero that is pure repetition on a screen this size. Nothing else in the
     lead is dropped: what DQT is, and what it costs if it does not work, both
     stay. */
  .lead-detail { display: none; }
  /* One dominant action and one quiet one — but BOTH still actions.
     What this was: the secondary lost its fill, its border and its box
     shadow, dropped to `--muted` and sat flush against the column edge on
     2px of side padding. It saved about 55px of hero height and it cost the
     hero its second action outright: at 390px "Discover your gains" rendered
     as a line of grey type between a gold slab and the guarantee badge, with
     no boundary, no surface and nothing but hover — which a phone does not
     have — to say it could be pressed. A control that only announces itself
     to a pointer is not a control on a touch device.

     It is a recessed glass button now. Quieter than the gold in every channel
     that matters: no fill, no bevel, a warm hairline instead of a ramp, and
     text at --text rather than --on-gold. But it has a boundary, a ground
     darker than the hero art behind it, the same width as the primary, and a
     46px box — which is the 44px touch minimum with the 0.95 UI scale that
     applies between 561 and 720 already taken off (46 x 0.95 = 43.7; below
     561 the scale is 1 and it is a true 46).

     The pair reads as an action and an alternative because of weight, not
     because one of them stopped looking like a button. */
  .hero-actions { flex-direction: column; align-items: stretch; gap: 10px; }
  .hero-actions .btn-ghost {
    min-block-size: 46px; padding: 12px 20px; font-size: 15px;
    /* Opaque enough to stop the hero photograph. --sf-1 is a 0.035 film of
       the text colour, which over a lit circuit-board image is not a surface
       at all; a dark ground is what makes this read as recessed rather than
       as a rectangle drawn on the art. */
    background: rgba(0, 0, 0, 0.42);
    border-color: rgba(227, 179, 65, 0.3);
    color: var(--text);
    /* A recess, not a lip: dark along the top edge, a hint of light along the
       bottom. The inverse of every raised object on the page, which is what
       says "this one is set into the surface". */
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5),
                inset 0 -1px 0 rgba(247, 243, 232, 0.05);
  }
  /* :active is the state a finger actually reaches. It warms the ground and
     lights the ring — the same illumination the pointer gets — so a tap is
     acknowledged before the anchor navigates. */
  .hero-actions .btn-ghost:hover,
  .hero-actions .btn-ghost:active {
    background: rgba(227, 179, 65, 0.11);
    border-color: var(--ring-gold); color: var(--text);
  }
  /* A strip, not a lozenge: the radius comes down to the housing's own 4px,
     the padding halves, and the type steps down one notch. The sentence is
     unchanged — this is the guarantee, and shortening it would be shortening
     the offer. */
  /* `.hero` in front of both: the base .guarantee-badge rule is declared 2700
     lines further down (it is shared with the booking modal), and a media
     query adds no specificity — an unqualified copy here would simply lose on
     source order. */
  .hero .guarantee-badge {
    margin-top: 16px; padding: 9px 12px; border-radius: 4px; gap: 9px;
    font-size: 12.5px; line-height: 1.45;
  }
  .hero .guarantee-badge .gbadge-ico { width: 17px; height: 17px; }
}
/* The hero's primary used to add a 30px gold bloom on top of the base
   button's own. Deleted rather than tuned: the base treatment is the whole
   design of this control, and a section-specific glow is exactly the kind of
   local override that made the old page read as several designs at once. */

/* services ticker */
.ticker {
  margin-top: 26px; max-width: min(540px, 100%); overflow: hidden; white-space: nowrap;
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.ticker-track { display: inline-flex; width: max-content; animation: tickerMove 30s linear infinite; }
.ticker-track span { color: var(--faint); font-size: 13.5px; font-weight: 600; letter-spacing: 0.5px; padding-inline-end: 6px; }
@keyframes tickerMove { to { transform: translateX(-50%); } }
[dir="rtl"] .ticker-track { animation-name: tickerMoveRtl; }
@keyframes tickerMoveRtl { to { transform: translateX(50%); } }

/* ---------------------------- the metric strip ---------------------------
   Three readings on one rail, divided by hairlines rather than by 40px of air.
   A flex row with a gap is three unrelated numbers that happen to be near each
   other; a ruled strip is one instrument with three channels, which is what
   these actually are.

   Grid, not flex, and minmax(0, …): the third stat is a PHRASE, not a counter,
   and under flex it set its own width and pushed the two real counters out of
   step with the captions under them. Equal columns pin all three to one grid
   at every width and in every language. */
.hero-stats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 32px; padding-top: 20px;
  border-top: 1px solid var(--ring);
}
.hero-stats .stat { padding-inline: 18px; border-inline-start: 1px solid var(--ring); }
.hero-stats .stat:first-child { padding-inline-start: 0; border-inline-start: 0; }

/* Below 560px the strip turns from three vertical channels into three READOUT
   ROWS — label at the inline start, value at the inline end, one hairline
   between them. Exactly the collapse .est-box and .mt-stat already make at
   680px, so the page has one answer to "this row is too narrow" rather than
   three.

   It is also a bug fix. Three equal columns at 320px are 75px wide, and the
   third stat is a PHRASE — "Performance-first" measures 133px — so it
   overflowed its cell and was silently cut off by `.hero { overflow: hidden }`.
   The old flex row wrapped instead of clipping, which is what hid this until
   the strip was measured.

   `row-reverse` rather than reordering the DOM: flex direction is
   writing-mode aware, so row-reverse puts the caption at the inline start and
   the figure at the inline end in BOTH directions, while the DOM keeps reading
   "55" then "% avg. FPS gain*" to a screen reader. */
@media (max-width: 560px) {
  .hero-stats { grid-template-columns: minmax(0, 1fr); margin-top: 28px; }
  .hero-stats .stat {
    display: flex; flex-direction: row-reverse; align-items: baseline;
    justify-content: space-between; gap: 14px;
    padding-inline: 0; padding-block: 11px;
    border-inline-start: 0; border-top: 1px solid var(--ring);
  }
  .hero-stats .stat:first-child { border-top: 0; padding-top: 0; }
  .hero-stats .stat span { margin-top: 0; min-width: 0; }
  /* The phrase stat gets the counters' size back once it has a whole row to
     itself: the 20px override existed only to stop it stacking three lines
     deep inside a narrow column, and there is no narrow column any more. */
  .stat-text strong { font-size: 22px; line-height: 1.2; text-align: end; }
}
/* 700, not the 800 this asked for: Space Grotesk tops out at 700, so a
   variable-font weight above that is clamped, not synthesised — 800 rendered
   exactly the same pixels while reading as if the counters were heavier than
   the h1 next to them. Declaring the weight that actually paints. */
.stat strong { font-size: 32px; font-weight: 700; color: var(--accent); font-family: var(--font-head); }
/* THE FIGURE IS AN LTR RUN, WHATEVER THE PAGE IS.
   "1000+" ends in a bidi-neutral character. In an RTL paragraph a trailing
   neutral takes the paragraph's direction, so Arabic and Hebrew rendered the
   claim as "+1000" — the sign on the wrong side of the number, which reads as
   a different figure. `direction: ltr` + `unicode-bidi: isolate` pins the run
   itself to LTR (digits are LTR in all four languages here) without letting it
   leak into the caption beside it.

   Isolating alone was not enough: `text-align: start` then follows the
   element's own LTR direction and dragged the number to the LEFT edge of an
   Arabic column while its caption stayed right. So the box is sized to its
   content and positioned by the GRID instead — `justify-self: start` is
   resolved against the .stat's inherited direction, which puts the figure on
   the right in RTL and the left in LTR, exactly where it has always sat.
   Scoped to [data-count] so the third stat, which is a translated PHRASE and
   must keep the document's direction, is untouched. */
.stat strong[data-count] {
  direction: ltr; unicode-bidi: isolate;
  justify-self: start; width: max-content;
}
/* The third stat is a phrase, not a counter. At the counters' 32px it breaks at
   its own hyphen and stacks three lines deep on a 320px screen, so it gets a
   text size of its own — scoped to .stat-text, leaving the two real counters at
   32px. The line box is pinned to the counters' own (32px x the inherited 1.6)
   so the three captions still sit on one baseline when they share a row. */
/* The third stat is a PHRASE, not a counter, and it wraps: "Performance-first"
   sets 175px wide at 20px against 164px of column, so it breaks at its own
   hyphen. The old rule handled that by pinning the line box to the counters'
   height (32px x 1.6 = 51.2px) so a ONE-line phrase would sit on their
   baseline — but a two-line phrase in a 51.2px line box opens a 50px hole
   between the two words and drops the caption far below its neighbours'.

   Bottom-aligning the whole strip fixes it at the level it actually belongs
   to: whatever height each value takes, the three captions still land on one
   line. The phrase can then wrap where it likes, in any language, and it looks
   deliberate rather than broken. */
.hero-stats .stat { display: grid; align-content: end; }
.stat-text strong { font-size: 19px; line-height: 1.2; text-wrap: balance; }
/* The caption is in the instrument register (see the shared rule above); all
   that is left here is the block layout and the gap to the figure. */
.stat span { display: block; margin-top: 5px; }
.fineprint { color: var(--faint); font-size: 12px; margin-top: 14px; }

/* ------------------------- the proof console -----------------------------
   The strongest evidence on the page is a photograph of somebody's real
   benchmark, and until this pass it was mounted like a stock image: a bright
   white screenshot in a light card, with three readings floating OUTSIDE it
   on the corners.

   Two things were wrong with that, and neither was the screenshot.

   1. The readings had no provenance. "632 FPS avg" hanging in the air beside
      a card is a marketing number; the same figure printed inside the bezel,
      one line above the caption that names the machine and the game it was
      measured on, is a reading. Nothing about the values changed — they are
      the same three numbers, off the same session, and they are still the
      ones written into the image's alt text.

   2. The white had no frame. A 1175px screenshot of a light benchmark tool
      dropped into a near-black page reads as a paste-in unless something
      mediates the edge. What mediates it here is an inset shadow and a
      hairline drawn ON TOP of the image, which is what a screen behind glass
      actually looks like, plus a dark metadata band above and below so the
      white is a panel inside an instrument rather than the instrument.

   The traffic-light dots are gone with the chips. Three coloured circles
   borrowed from a macOS title bar say "this is a window"; the lamp that
   replaces them says "this channel is live", which is the same thing every
   other lamp on this page means. */
.fps-wrap { position: relative; }

.fps-card {
  background: var(--sf-1); border: 1px solid var(--ring); border-radius: 12px;
  box-shadow: var(--lip); overflow: hidden;
  transition: transform var(--t-fast) ease-out; will-change: transform;
}
.fps-card-head {
  display: flex; align-items: center; gap: 9px; padding: 11px 14px;
  border-bottom: 1px solid var(--ring); background: var(--sf-2);
}
/* The same 6px lamp the section labels and the FAQ rows wear, at the same
   two-part construction — filled core, dim halo. One signal, one meaning. */
.fps-card-lamp {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 3px rgba(227, 179, 65, 0.14);
}
.fps-card-title {
  color: var(--accent); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.6px; font-family: var(--font-head);
}

/* In the instrument register (shared rule above), so the provenance line under
   the benchmark reads as the machine's own caption rather than as a sentence
   someone added. Overrides only the two things that differ from the register:
   it is dimmer than a label and it has to wrap. */
/* Mono, because it is the machine's own caption on the benchmark above it —
   but NOT uppercase and NOT tracked, because it is a sentence with hardware
   names in it, and the register's Latin conventions stop being readable the
   moment the label becomes prose. The register rule deliberately does not
   list this selector for that reason. */
.fps-card-foot {
  padding: 12px 16px; border-top: 1px solid var(--ring);
  color: var(--faint); font: 400 11px/1.6 var(--mono);
}

/* --------------------- section ambience backgrounds --------------------- */
.section { position: relative; }
.section > .container { position: relative; z-index: 1; }
.section-bg { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.section-bg img {
  width: 100%; height: 100%; object-fit: cover; opacity: 0.13;
  filter: saturate(0.85) brightness(0.9);
  transform: scale(1.12);
  animation: bgDrift 38s ease-in-out infinite alternate;
  will-change: transform;
}
.section-bg video {
  width: 100%; height: 100%; object-fit: cover; opacity: 0.18;
  filter: saturate(0.9);
}
/* ------------------------- art direction, by job -------------------------
   One overlay strength everywhere is not art direction, it is a default. The
   artwork now sits at one of three levels, chosen by what the section is FOR:

     quiet   0.06   a section the visitor has to READ or DECIDE in — the
                    calculator, the pricing rack, the contact form. Anything
                    behind a feature list or a form field is interference.
     medium  0.13   the narrative sections between them — how it works, the
                    value comparison, the FAQ, everything we do. This is the
                    page's atmosphere and its transitions.
     strong  0.22   the one brand moment: studios & companies.

   The hero keeps its own level (video at 0.18) and the game rail keeps the
   loudest treatment on the page, which is not an overlay at all — it is the
   tags and the live ECG trace. */
.section-bg-quiet img { opacity: 0.06; filter: saturate(0.7) brightness(0.85); }
.section-bg-strong img { opacity: 0.22; }
.section-bg::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--bg) 0%, rgba(7, 6, 4, 0.3) 30%, rgba(7, 6, 4, 0.3) 70%, var(--bg) 100%);
}
.section-alt .section-bg::after {
  background: linear-gradient(180deg, var(--bg-alt) 0%, rgba(13, 11, 7, 0.3) 30%, rgba(13, 11, 7, 0.3) 70%, var(--bg-alt) 100%);
}
@keyframes bgDrift {
  from { transform: scale(1.12) translate3d(-1.4%, 0, 0); }
  to { transform: scale(1.12) translate3d(1.4%, -1.2%, 0); }
}

/* -------------------- studios & companies (special) ---------------------
   Subject is the badge itself, not `.section-head .kicker…`: the studios
   heading sits in its own masthead now (see "studios / creative services"
   below), so a descendant selector anchored to .section-head would silently
   stop matching the only element that ever wears this class.

   Every section wears the pill; only this one is special. It therefore adds
   nothing but the difference — the travelling sheen, a heavier glow and half
   a point of type — on top of the shared .kicker shape. Restating the padding
   and radius here is how the two would quietly drift apart later. The ✦ in
   front of the label is part of the translated string, not this rule, and it
   is deliberately the only starred label on the site. */
/* The studios badge keeps the top tier and keeps its star, which lives in the
   translated string and not in this rule. It is now the only label on the page
   whose rail carries the travelling sheen — the same one the MOST POPULAR flag
   and the tier meter use, so the three read as one material — and the only one
   whose text is at full brightness. Everything it adds is a difference on top
   of the shared shape; nothing here restates the base. */
.kicker.kicker-special {
  color: var(--text);
}
.kicker.kicker-special::after {
  background: var(--gold-sheen);
  background-size: 200% auto;
  animation: gradSheen 5.5s ease-in-out infinite;
  mask-image: linear-gradient(to right, #000, transparent 88%);
  -webkit-mask-image: linear-gradient(to right, #000, transparent 88%);
}
[dir="rtl"] .kicker.kicker-special::after {
  mask-image: linear-gradient(to left, #000, transparent 88%);
  -webkit-mask-image: linear-gradient(to left, #000, transparent 88%);
}

/* ------------------ hero video + gold-framed proof card ----------------- */
.hero { position: relative; overflow: hidden; }
.hero > .container { position: relative; z-index: 1; }
/* The proof card was framed in a 2px gold gradient border with a 38px gold
   bloom around it — the loudest object on the page, spent on a frame rather
   than on what is inside it. The photograph it holds is the single strongest
   piece of evidence DQT has, and it was competing with its own housing.

   Now the housing is the same module as everything else, one level brighter:
   hairline ring, lit lip, no bloom. The only gold left on it is the card
   title, which names what you are looking at. */
.hero .fps-card {
  border: 1px solid var(--ring); overflow: hidden;
  background: var(--sf-1);
  box-shadow: var(--lip-lit), 0 24px 60px rgba(0, 0, 0, 0.5);
}
/* The glass. The inset ring and shadow are painted by a pseudo-element over
   the image rather than as a border on it, because a border on the container
   would sit outside the white and change nothing about where the white meets
   the dark — the whole problem being solved is that edge. */
.fps-shot { position: relative; background: #fff; }
.fps-shot img { display: block; width: 100%; height: auto; }
.fps-shot::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow:
    inset 0 0 0 1px rgba(10, 8, 5, 0.18),
    inset 0 10px 22px -10px rgba(10, 8, 5, 0.5),
    inset 0 -10px 22px -10px rgba(10, 8, 5, 0.38);
}
/* The three readings, mounted. Same figures as the old floating chips, now
   inside the bezel and above the caption that names the session they came
   from. Grid rather than flex so the three channels are equal at every width
   and in every language, and hairlines rather than gaps so the strip reads as
   one instrument with three channels — the same construction as .hero-stats.

   Exactly one of the three is gold: the FPS average, which is the headline
   reading. The other two are warm white. Gold means "this is the measurement
   being reported", not "this is a number". */
.fps-readout {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--ring); background: var(--sf-2);
}
.fps-read {
  padding: 10px 12px; text-align: center;
  border-inline-start: 1px solid var(--ring);
}
.fps-read:first-child { border-inline-start: 0; }
.fps-read b {
  display: block; font-family: var(--font-head); font-size: 19px; font-weight: 700;
  line-height: 1.1; letter-spacing: -0.01em; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.fps-read:first-child b { color: var(--accent); }
.fps-read i {
  display: block; margin-top: 3px; font-style: normal;
  color: var(--faint); font: 600 9.5px var(--mono);
  text-transform: uppercase; letter-spacing: 0.09em;
}

/* --------------------------- seo learn card ---------------------------- */

/* -------------------------------- strip --------------------------------
   THE GAME RAIL.

   What was wrong with it: a centred label on one line and nine pills wrapped
   and centred on the line under it, spread across the full 1140px container.
   At desktop width that is two shallow bands of mostly air — the section was
   about 100px tall and carried perhaps 30% ink — and centre-aligned wrapping
   rows always read as an accident rather than a decision, because the ragged
   edge lands somewhere different at every viewport.

   It is now ONE horizontal rail: a label plate at the reading edge, a vertical
   hairline, then the titles running off it as keys on a deck. The composition
   is anchored at both ends instead of floating, it is about 40% shorter, and
   the ECG behind it finally has a rail to run along rather than a box to fill.

   The pills are gone with the centring. A 999px radius is a tag you click; a
   7px radius on a recessed ground is a key on a panel, which is what these are
   — nine titles the bench is set up for, not nine filters. Same reasoning that
   turned the tools tabs into a segmented control two sections down.

   ---------------------------------------------------------------------------
   SECOND PASS. The rail above was right in principle and thin in practice.
   Measured at 1440 before this pass: 104px tall, nine chips wrapping 7 + 2, a
   two-item second row hanging off the end, and the ECG reading straight
   THROUGH every chip because their ground was rgba(0,0,0,0.3) — a trace you
   can see through a key does not read as a trace behind a key, it reads as a
   line someone forgot to mask. Four things change, and they are all the same
   idea: make the keys real objects and the rail a real bay.

     WRAP -> GRID. flex-wrap put 7 keys on row one and 2 on row two, at
       whatever widths their titles happened to be. The keys are now equal
       columns in an auto-fit grid, so row two lands UNDER row one on the same
       column centres. A partial last row in an aligned grid reads as an empty
       slot in a keypad; a ragged wrap reads as an accident. That is the whole
       "row balance" fix, and it costs one declaration.

     CHIP -> KEY. Each key gets an opaque cap and a recessed WELL under its
       glyph — the same figure/ground move the tool tabs make, one level down.
       The well is drawn on the <svg> itself (background + padding + radius on
       an SVG box) so no wrapper element had to be invented for it.

     OPACITY. The cap is opaque, so the ECG is genuinely occluded and visibly
       passes BETWEEN the keys instead of under them.

     THE PLATE STRETCHES. `align-items: stretch` turns the label's
       border-inline-end into a full-height divider instead of a 13px stub
       floating beside a two-row block. The rail now has a left bay and a key
       field, which is what it was always trying to be.

   The accent variation is inside the key rather than across the keys: at rest
   the glyph is gold at 0.62 and the title is a neutral grey, so every key has
   its own two-step hierarchy and the row does not read as nine identical
   brown chips. Hover promotes the whole object. Nothing here is a new colour;
   the gold is --accent at an alpha and the grey is a desaturated --muted.

   ---------------------------------------------------------------------------
   THIRD PASS supersedes the last two bullets above: the label bay and the
   grid are gone, and so is the ECG-behind-the-keys idea that the opaque cap
   was invented to serve. What survives from the second pass is the key itself
   — opaque cap, glyph in a well, warm mark over neutral title — and the
   channel tokens below. The layout reasoning lives at THE RAIL ITSELF, a few
   hundred lines down, and the two bullets are left standing because the tokens
   they explain are still the tokens in use. */
.strip {
  border-block: 1px solid var(--border);
  /* A shade below the page rather than above it: the rail is a slot milled
     into the deck between two sections, not a card sitting on top of one. */
  background: var(--bg);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.45);
  position: relative; overflow: hidden;

  /* ---- the rail's own channel ----------------------------------------
     Scoped to `.strip`, and named --gr-* rather than reusing --sf-*: those
     are declared once in :root and are alpha films of the TEXT colour, which
     is exactly what this pass needs not to be. A film over the rail's ground
     cannot occlude the ECG running behind it; an opaque cap can. */
  --gr-cap: #101012;                        /* the key's cap. opaque on purpose */
  --gr-cap-lit: #191509;                    /* and its lit twin */
  --gr-edge: rgba(238, 241, 247, 0.10);
  --gr-well: rgba(0, 0, 0, 0.5);            /* the recess under the glyph */
  /* The title at rest. A desaturated --muted rather than --muted itself:
     #b0a68d is a warm brown, and nine of them in a row is most of why this
     rail read as one flat brown band. Same luminance, no cast, so nothing
     about the contrast budget moves — 9.9:1 on the cap. */
  --gr-ink: #b6b4b0;
  /* The glyph at rest. Gold, held back, and the only warm thing on a key that
     is not being hovered. Composites to #937530 over the well: 4.6:1, well
     over the 3:1 a graphic needs. */
  --gr-glyph: rgba(227, 179, 65, 0.62);
}
/* Heart-monitor trace behind the game tags: the line itself stays put (dim),
   while a bright gold pulse sweeps along it like a live ECG reading.
   All three <use> share one path; dasharray sum (1600) > path length, so
   exactly one "comet" travels the line per cycle and the loop is seamless. */
/* The trace gets a BAND, not the whole box. `inset: 0` handed a 64-unit
   viewBox the strip's full height with preserveAspectRatio="none", so every
   change to the rail's padding silently restretched the waveform — the peaks
   were about 25% taller than drawn, which is why the line read as decoration
   rather than as a reading. Pinning it to its own 64px track centred on the
   rail means the ECG is now drawn at its true proportions at every viewport,
   and the rail can get as compact as it likes without touching it.

   Centred with top:50% + translateY(-50%): both are symmetric, so there is no
   RTL twin to keep in step (the horizontal mask below is symmetric too — only
   the travel direction mirrors, which it already does). */
/* THIRD PASS: THE TRACE LEAVES THE BACKGROUND.

   It used to be an absolutely-positioned band centred on the whole rail, i.e.
   a waveform painted BEHIND nine opaque keys. Two things were wrong with that.
   The keys occluded almost all of it, so what the reader actually saw was a
   line entering from the left margin, disappearing, and coming back out on the
   right — decoration, not a reading. And it ran edge to edge through 200px of
   empty deck before it reached anything, which is what read as "a line someone
   forgot to mask".

   It is now a FLEX CHILD of the rail's header row, taking the slack between
   the nameplate and the far edge. That single move fixes all of it: the trace
   starts at the plate the lamp is on, it is never occluded, it is drawn at its
   own scale, and it needs no masking arithmetic to stay clear of the label in
   either reading direction — flex order does that in both.

   64px of waveform on a ~17px row: the negative block margins let the trace
   keep the height it is drawn at without the header reserving it, so the peaks
   are true and the row is still a row. `.strip` is `overflow: hidden`, so the
   two extremes cannot escape the rail. */
.strip-pulse {
  position: relative; flex: 1 1 auto; min-width: 0;
  height: 64px; margin-block: -29px -17px;
  pointer-events: none;
  /* Fades at the FAR end only, so the signal reads as leaving the nameplate
     rather than materialising next to it. The one asymmetric rule in the rail,
     so it takes an RTL twin — and the comet already travels the other way
     there (`animation-direction: reverse`, below), so it still runs OUT. */
  mask-image: linear-gradient(90deg, #000 62%, transparent);
  -webkit-mask-image: linear-gradient(90deg, #000 62%, transparent);
}
[dir="rtl"] .strip-pulse {
  mask-image: linear-gradient(270deg, #000 62%, transparent);
  -webkit-mask-image: linear-gradient(270deg, #000 62%, transparent);
}
.strip-pulse svg { width: 100%; height: 100%; display: block; }
.strip-pulse use { fill: none; stroke-linejoin: round; stroke-linecap: round; }
/* Deliberately NOT retuned by any pass, and the third one is the strongest
   case yet for leaving it alone. The first instinct each time the rail changes
   is to brighten the base line; it has been wrong each time, because
   `.strip-pulse` keeps its fixed 64px height (see above) whatever the rail does
   around it, so the trace is drawn at exactly the size it always was and 0.13
   reads exactly as it did. What has changed twice is what sits in FRONT of it:
   the keys went opaque, and now nothing sits in front of it at all. */
.pulse-base { stroke: rgba(231, 192, 90, 0.13); stroke-width: 1.2; }
.pulse-glow { stroke: rgba(231, 192, 90, 0.26); stroke-width: 3.6; stroke-dasharray: 150 1650; animation: pulseRun 5.5s linear infinite; }
.pulse-head { stroke: #ffe9a8; stroke-width: 1.5; stroke-dasharray: 70 1730; animation: pulseRun 5.5s linear infinite; }
@keyframes pulseRun { from { stroke-dashoffset: 1800; } to { stroke-dashoffset: 0; } }
[dir="rtl"] .pulse-glow, [dir="rtl"] .pulse-head { animation-direction: reverse; }
/* `infinite` above is meant literally, so main.js marks the two states where
   the frame buys nothing — the strip scrolled out of view (.strip-idle) and
   the tab in the background (.page-hidden) — and either one parks the comet.
   Doing the AND here rather than in JS keeps the two gates independent of each
   other.

   play-state only. `animation: none`, or any shorthand, would reset the name
   and the comet would restart from the left edge on resume instead of carrying
   on from where it stopped.

   Both selectors are (0,2,0) against the (0,1,0) rules that declare the
   shorthand, so this wins on specificity and not on source order. Neither
   class is ever authored into the markup, which is what keeps the animation
   running as originally designed when this JS does not get to run. Reduced
   motion drops the animation and the opacity entirely a few hundred lines
   down, leaving nothing here to act on. */
.strip-idle .pulse-glow, .strip-idle .pulse-head,
.page-hidden .pulse-glow, .page-hidden .pulse-head { animation-play-state: paused; }
/* ---------------------------- THE RAIL ITSELF ----------------------------
   THIRD PASS: FROM A DECORATED STRIP TO A RACK OF PROFILES.

   The second pass made the keys real objects. What it could not fix from
   inside its own layout was the COMPOSITION, and measured at 1440 the numbers
   said so plainly: the rail was 1440px wide and its nine keys occupied
   x=409-1238. Everything to the reading side of that — a 200px full-bleed
   margin with a flat ECG lying in it, then a 187px nameplate bay — was empty.
   A quarter of a signature component doing nothing is why it read as a strip
   with some tags on it rather than as a rail.

   Three changes, and they are one idea: give the rail a HEAD and a RACK.

     THE NAMEPLATE GOES ON TOP. It was a side bay whose only job was to hold a
       divider; as a header it costs one text line and hands the whole
       container width back to the keys. The hairline it used to carry is now
       the trace itself (see `.strip-pulse` above), which is a better line for
       a rail that is making a live claim.

     THE KEYS BECOME PROFILE TILES. Glyph in a recessed well ON TOP, title
       under it, two lines of title reserved on every key so the rack is one
       height. A horizontal chip is a tag; a tile with its mark over its name
       is what a supported-profile key looks like, and it is also what lets
       nine of them stand in ONE row at desktop.

     THE RACK IS A TRAY. The keys are mounted in a recessed housing rather
       than floating on the deck, so the rail has a centre of gravity and a
       visible boundary. Two levels: the tray is darker than the deck, the caps
       are lighter than the tray.

   NINE KEYS, AND COLUMN COUNTS THAT DO NOT LEAVE A HOLE
   ----------------------------------------------------
   The previous pass chose 5 columns above 1100 and shipped exactly the fault
   it was opened to fix: 5 + 4 leaves one empty slot at the end of row two.

   The fix is to stop using grid tracks and use a flex basis that IS a track:
   calc((100% - (n-1) * gap) / n) gives n identical columns, and
   `justify-content: center` then CENTRES a short last row instead of leaving a
   gap at its end. Every band is either exact or symmetric:

       9 columns   9              one row, and the point of the pass
       5 columns   5 + 4 centred  a taper, not a hole
       3 columns   3 + 3 + 3      exact

   1080 IS MEASURED, and it is a physical viewport width — media queries are
   evaluated on <html>, which `body { zoom: 0.95 }` does not touch, so the body
   sees 1080/0.95 = 1136.8px of layout there. Nine tracks then measure
   (1088.8 - 20 tray - 64 gaps) / 9 = 111.6px, and 111.6 sets "League of" on
   one line at 12px with room to spare. Below that the rack takes five, and
   below 500 three — 320 is the binding case for three and it comes out at
   78.7px, which holds "Legends" at 11px.

   The titles are the one thing in this rail that is the same string in every
   language: they are proper nouns and carry no data-i18n. So unlike the label
   bay it replaced, the rack's arithmetic does not translate, and none of these
   numbers has a Spanish worst case. */
.strip-inner { padding: 26px 24px 28px; position: relative; }

/* The head: nameplate, then the trace running out of it to the far edge. */
.strip-head {
  display: flex; align-items: center; gap: 16px;
  margin-block-end: 15px;
}
/* The nameplate. Same lit lamp as before — the 6px gold circle the value
   card's finding row uses — because this rail is a live statement about what
   the bench is set up for. What it no longer needs is the opaque background
   and the divider it used to carry: it is not standing in front of the trace
   any more, it is the thing the trace runs OUT of. */
.strip-label {
  flex: none; display: inline-flex; align-items: center; gap: 10px;
  color: var(--muted); font: 600 11.5px var(--mono);
  text-transform: uppercase; letter-spacing: 0.1em;
}
.strip-label::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 8px rgba(227, 179, 65, 0.5);
}

/* THE RACK. A recessed tray with the keys mounted in it. The inset shadow is
   two lines and not one: dark along the top edge is the wall the tray is cut
   into, light along the bottom is the far wall catching the deck light — the
   inverse of `--lip`, which is what a recess is. */
.strip-tags {
  --gr-cols: 3;
  --gr-gap: 7px;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--gr-gap);
  padding: 10px; border-radius: 14px;
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(238, 241, 247, 0.07);
  box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.6),
              inset 0 -1px 0 rgba(247, 243, 232, 0.045);
}
@media (min-width: 500px) { .strip-tags { --gr-cols: 5; --gr-gap: 8px; } }
@media (min-width: 960px) { .strip-tags { --gr-cols: 9; --gr-gap: 8px; } }

/* A key: an opaque cap, a glyph in a well, a title under it. `align-items` is
   deliberately left at `stretch` on the container, so every key in a row is
   the height of the tallest — and because the title block reserves two lines
   on every key, that height is the same in every row too. */
.strip-tags .gkey {
  flex: 0 0 calc((100% - (var(--gr-cols) - 1) * var(--gr-gap)) / var(--gr-cols));
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  min-width: 0;
  padding: 10px 6px 11px; border-radius: 10px;
  border: 1px solid var(--gr-edge); background: var(--gr-cap);
  box-shadow: var(--lip), 0 1px 2px rgba(0, 0, 0, 0.4);
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease,
              box-shadow var(--t-ui) ease;
}
/* Two lines reserved on every key, always. "League of Legends" wraps at every
   column width this rail uses and the other eight do not, so without a reserve
   one key in the rack is taller than its neighbours — and stretch would then
   hand that height to its whole row and not to the rows above it. A fixed
   two-line box makes the rack one height at every breakpoint, which is the
   difference between a keypad and a wrap.

   `text-wrap: balance` so the two lines of the one title that uses both come
   out even rather than a full line over one stranded word. */
.strip-tags .gkey b {
  /* Top-aligned inside the reserved box, not centred. Centring put the one
     two-line title 5px higher than its eight one-line neighbours, so the row
     of names had no common line — which on a rack of identical keys is the
     first thing the eye catches. Aligned to the top, every FIRST line sits on
     one baseline and the title that needs a second one simply grows down into
     the reserve that was already there for it. */
  display: flex; align-items: flex-start; justify-content: center;
  min-block-size: 2.5em;
  color: var(--gr-ink); font-size: 12px; font-weight: 600; line-height: 1.25;
  text-align: center; text-wrap: balance;
  transition: color var(--t-ui) ease;
}
/* The glyph sits in its own recess, and it is the key's figure — bigger than
   it was, because a tile has room for it and because the mark is what makes a
   profile key recognisable at a glance. Gold held back at rest, so every key
   carries its own two-step hierarchy (warm mark / neutral title) and the rack
   does not read as nine identical brown chips. */
.strip-tags .gkey svg {
  box-sizing: content-box; flex: none;
  width: 17px; height: 17px;
  padding: 6px; border-radius: 9px;
  background: var(--gr-well); box-shadow: inset 0 0 0 1px rgba(238, 241, 247, 0.05);
  color: var(--gr-glyph);
  transition: color var(--t-ui) ease, background var(--t-ui) ease,
              box-shadow var(--t-ui) ease;
}
/* Illuminates, does not lift. Nine keys that all jump under a pointer crossing
   the rack is the exact noise this design is removing from the page. The cap,
   the well and the title all warm together, so the key comes forward as one
   object rather than as a border that changed colour. */
.strip-tags .gkey:hover {
  background: var(--gr-cap-lit); border-color: var(--ring-gold);
  box-shadow: var(--lip-lit), 0 1px 2px rgba(0, 0, 0, 0.4);
}
.strip-tags .gkey:hover b { color: var(--text); }
.strip-tags .gkey:hover svg {
  color: var(--accent); background: rgba(0, 0, 0, 0.45);
  box-shadow: inset 0 0 0 1px rgba(227, 179, 65, 0.28);
}

/* Phone. The rack tightens; it does not scroll. A horizontally scrolling rail
   would be the gaming-native answer and is deliberately not used — every key
   here is a plain <span> with no tab stop, so a scroll container built out of
   them is one a keyboard cannot reach.

   Three columns still, down to 320. That is what the tile format buys: the old
   horizontal chip needed 104px to hold "League of Legends" beside a glyph and
   fell to two columns and FIVE rows at 320; a tile only has to hold "Legends"
   on one line, which fits in 78.7px, so the rack stays 3 x 3 on every phone. */
@media (max-width: 560px) {
  .strip-inner { padding: 20px 24px 22px; }
  .strip-head { gap: 12px; margin-block-end: 12px; }
  .strip-tags { padding: 8px; border-radius: 12px; }
  .strip-tags .gkey { padding: 8px 4px 9px; gap: 5px; border-radius: 9px; }
  .strip-tags .gkey b { font-size: 11px; line-height: 1.2; min-block-size: 2.4em; }
  .strip-tags .gkey svg { width: 14px; height: 14px; padding: 4px; border-radius: 7px; }
}

/* ------------------------------- sections ------------------------------ */
.section { padding: 88px 0; }
.section-alt { background: var(--bg-alt); border-block: 1px solid var(--ring); }

/* ---------------------------- the section head ---------------------------
   Off centre, and that is the point. Six of the seven heads on this page were
   the identical object — a centred 640px column of pill / h2 / paragraph —
   which is most of why the page read as `heading, paragraph, cards` seven
   times over. Moving the head to the inline start does three things at once:
   the rail now has an edge to run to, the h2 shares the text block's own
   measure instead of floating over the middle of it, and the eye starts every
   section in the same place it starts every paragraph.

   `--head-w` rather than a hardcoded max-width, so the split variant below can
   widen the head to the full container without restating anything else. */
/* --------------------------- section masthead ----------------------------
   ONE pattern for every major section introduction on the page, centred on
   the container's own axis. Seven sections used to introduce themselves three
   different ways — a start-aligned stack, a two-column split that put the
   paragraph opposite the heading, and the studios masthead with its own rule
   — and the result was a homepage with no visual centre and a lot of weight
   pinned to one edge.

   Centred, and specifically centred HERE: `margin-inline: auto` on a block
   inside the section's own `.container`, so the masthead sits on the page's
   axis rather than on the axis of whatever column happens to be beneath it.
   That is the difference between this and centring inside a grid cell, and it
   is why the split variant had to go rather than be centred in place.

   The instrument identity survives the move intact — the lamp, the mono
   register, the tracked eyebrow, the warm-white heading and the muted
   description are all unchanged. What is dropped is the RAIL: a hairline that
   runs from the label to the edge of the block is a start-aligned device by
   construction, and the symmetric version of it (short rails either side)
   needs a third pseudo-element that .kicker does not have, on an element that
   cannot gain a wrapper without disappearing from /ar/ (build-ar.js replaces
   the whole inner content of a data-i18n element). A centred lamp and a
   centred label is the compact form, and it is one signal instead of two.

   The rhythm, in the terms the spacing system already uses:
       eyebrow  →  14px  →  heading  →  12px  →  description  →  48px  →  content
   tightening to 10 / 10 / 34 below 720px. */
.section-head {
  --head-w: 660px;
  text-align: center;
  max-inline-size: var(--head-w);
  margin: 0 auto 48px;
  display: grid; justify-items: center;
}
/* Centring the label unit, lamp included. `justify-content: center` on the
   flex row moves the lamp and the text together, so the lamp stays welded to
   the first line — the same property that keeps it from floating beside a
   second line at 320px. Nothing directional, so RTL mirrors for free. */
.section-head .kicker { justify-content: center; }
/* The rail, retired inside a masthead only. It is still the device that marks
   every SUBSECTION label (.svc-kicker) and the hero eyebrow, which are both
   start-aligned and both still want an edge to run to. */
.section-head .kicker::after { content: none; }
/* Every label on the page wears this pill; the three tiers differ only in how
   they fill it.

     .kicker              secondary — tint + hairline (this rule)
     .kicker.kicker-main  a major section's own name — filled gold
     .kicker.kicker-special  the studios badge — filled, sheened, starred

   Base is the *quiet* tier on purpose. The three `.svc-kicker` labels inside
   "Everything we do" introduce card groups, not sections, and they sit among
   the tune/repair/results grids where a filled pill would out-shout the cards
   it is only labelling. Anything that is not explicitly promoted stays here,
   which is the safe default: a new label added without thinking is quiet, not
   loud.

   Solid gold otherwise means interactive on this site — .btn-primary, the
   active .tool-tab, a chosen .chip — so .kicker-main has to be filled without
   reading as a control; see the distinctions listed on that rule. That
   constraint is why the quiet tier keeps the filled pill away from the
   subsection labels that sit nearest the actual buttons and tabs.

   The rule itself lives with the hero eyebrow further up the file, because
   .pill and .kicker are the same object and had to be declared together. What
   is left here is the doc comment, which is where a reader looking for the
   label hierarchy will come first. */

/* A major section's own name. Filled with the house gold so the top-level
   sections read louder than the subsection labels inside them — the tier this
   variant exists to create.

   It has to be filled *and* obviously not a control, so it takes the fill and
   drops everything that makes gold mean "press me":
     - no hover, no :active, no transform — a <span> with no pointer cursor;
     - no CTA drop shadow. .btn-primary throws 0 6px 24px / 0.3; this glow is
       0 2px 10px / 0.18, close to the surface and far dimmer;
     - an inset top highlight instead, which reads as a printed label rather
       than the flat pressed fill of .tool-tab.active (same --accent-grad,
       nothing else in common);
     - no travelling sheen and no animation — that stays exclusive to
       .kicker-special, which is what keeps the studios badge the loudest pill
       on the page and this one merely the second.

   `border-color: transparent`, not `border: none`, for the same reason
   .kicker-special does it: the pill keeps the base rule's border-box height,
   so promoting a label cannot shift the 10px gap to the h2 under it. Nothing
   here is directional, so RTL needs no counterpart. */
.kicker.kicker-main {
  color: var(--accent);
}
.kicker.kicker-main::after {
  background: linear-gradient(to right, var(--ring-gold), transparent 88%);
}
[dir="rtl"] .kicker.kicker-main::after {
  background: linear-gradient(to left, var(--ring-gold), transparent 88%);
}
/* Same three corrections as the hero h1, at section scale: balance so the
   break is designed rather than greedy, a measure in ch, and tracking in em so
   it stays proportional across the clamp. `Check your system before you /
   book` — the stranded word this pass was asked to fix — is a 34-character
   string that balance splits 17/17. */
.section-head h2 {
  font-size: clamp(27px, 3.2vw, 38px); font-weight: 700;
  margin: 14px auto 12px; letter-spacing: -0.015em; line-height: 1.12;
  text-wrap: balance; max-inline-size: 20ch;
}
/* 52ch rather than the 58 it was. A centred paragraph is read from both
   edges, so the measure that was comfortable when it was ragged-right on a
   fixed left edge is a little wide once both edges move — and the shorter
   measure is what makes the description look like part of the masthead
   instead of the first paragraph of the section. */
.section-head p {
  color: var(--muted); max-inline-size: 52ch; margin-inline: auto;
  /* `balance`, not the `pretty` a paragraph normally wants. These are one to
     three lines of centred introduction, and `pretty` only repairs the LAST
     line — which on a centred two-liner leaves "...and ends with proof / of
     gains." sitting under a full-width line as a two-word stub, dead centre.
     Balance evens all of them, and the 6-line cap the spec puts on it is
     never approached by a masthead description in any of the four languages.
     Running prose elsewhere on the page keeps `pretty`. */
  text-wrap: balance;
}
@media (max-width: 720px) {
  .section-head { margin-bottom: 34px; }
  .section-head h2 { margin: 10px auto 10px; }
}

.grid { display: grid; gap: 22px; }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-2 { grid-template-columns: 1fr 1fr; }

/* --------------------------------- module --------------------------------
   Was: an opaque brown box with a 16px radius and, on hover, a 32px black
   shadow and a 3px lift. Three separate SaaS tells in one rule.

   Now: a film with a hairline ring and a lit top lip. The radius comes down to
   12px because 16 is the radius of a content card and this is a housing; the
   shadow is gone entirely, replaced by the lip, which says raised-out-of
   rather than floating-above; and hover ILLUMINATES instead of lifting — the
   ring warms and the film thickens. A page of cards that all jump under the
   pointer is the template look this pass is trying to leave behind, and it is
   also the thing the pointer keeps re-triggering as it crosses the grid.

   The lift survives in exactly one place, on the plan the visitor is choosing
   between (see .plan:hover), where a physical response to a decision is worth
   the movement. */
.card {
  background: var(--sf-1); border: 1px solid var(--ring);
  border-radius: 12px; padding: 26px; box-shadow: var(--lip);
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
/* Note what is NOT here: `transform`. The old rule had to write
   `translateY(calc(var(--reveal-y) - 3px))` because it wanted a 3px lift and
   had to compose it with the reveal offset or one would cancel the other. This
   rule wants no lift, and the correct way to want no lift is to say nothing —
   then `.reveal`'s own `translateY(var(--reveal-y))` governs, at lower
   specificity, and there is no cascade interaction to get wrong. Writing
   `transform: none` here instead would out-rank the reveal and freeze every
   card at its settled position mid-animation. */
.card:hover {
  background: var(--sf-2); border-color: var(--ring-gold);
  box-shadow: var(--lip-lit);
}

/* ------------------------------ inline icons ---------------------------- */
.svg-ico {
  width: 100%; height: 100%;
  fill: none; stroke: var(--accent); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.feature-ico, .trust-ico, .repair-ico {
  width: 46px; height: 46px; padding: 10px; margin-bottom: 16px;
  border-radius: 10px; background: var(--sf-1);
  border: 1px solid var(--ring); box-shadow: var(--lip);
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
/* The tile fills gold and the glyph flips dark — the same answer the studios
   cells give a pointer. These three classes appear only inside #services, so
   the class list is the section scope.

   Descendant selectors on purpose: the reveal-vs-hover cascade only bites
   rules whose whole subject is one class plus :hover and which set transform.
   Nothing here touches transform, so the card keeps its own lift untouched. */
/* The tile used to FILL with the gold gradient on hover and flip its glyph to
   near-black. Eleven cards on this page did that, which meant eleven gold
   fills competing with the one gold fill that is an actual button. It now
   lights instead: the ring warms, the film thickens, the glyph stays gold.
   Same hover grammar as every other module on the page.

   Descendant selectors on purpose: the reveal-vs-hover cascade only bites
   rules whose whole subject is one class plus :hover and which set transform.
   Nothing here touches transform. */
.feature:hover .feature-ico,
.trust:hover .trust-ico,
.repair-card:hover .repair-ico {
  background: var(--sf-2); border-color: var(--ring-gold);
  box-shadow: var(--lip-lit);
}

/* -------------------------------- steps -------------------------------- */
/* The three process cards were the loudest modules on the page: a gold-tinted
   border, a full-width gold bar across the top, a 4px lift and TWO shadows —
   a 42px black one and a 26px gold one — on hover. Three cards, each announcing
   itself as hard as the primary action.

   They are now the same module as everything else, with one difference that is
   earned: the top bar stays, because these three cards ARE a sequence and the
   bar is the only thing that says so at a glance. It is dimmed to the ring
   weight and only the hovered card's bar goes to full gold, which turns a
   permanent decoration into a pointer position. */
.step {
  padding-top: 22px; position: relative; overflow: hidden;
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
.step::after {
  content: ""; position: absolute; top: 0; inset-inline: 18px; height: 2px;
  border-radius: 0 0 2px 2px; background: var(--accent-grad); opacity: 0.28;
  transition: opacity var(--t-ui) ease;
}
/* Deliberately declares no transform — see the note on .card:hover. */
.step:hover { border-color: var(--ring-gold); }
.step:hover::after { opacity: 1; }
.step-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.step-head .step-num { margin-bottom: 0; width: 38px; height: 38px; border-radius: 10px; flex: none; }
.step-points { list-style: none; margin-top: 14px; display: grid; gap: 8px; padding: 0; }
.step-points li {
  display: flex; align-items: flex-start; gap: 9px;
  color: var(--text); font-size: 13.5px; line-height: 1.45;
}
.step-points li::before {
  content: "✓"; flex: none; width: 18px; height: 18px; border-radius: 6px;
  display: grid; place-items: center; font-size: 11px; font-weight: 800;
  background: rgba(52, 211, 153, 0.16); color: var(--success);
}
.how-note {
  margin-top: 26px; padding: 15px 22px; text-align: center;
  border: 1px solid rgba(227, 179, 65, 0.35); border-radius: 12px;
  background: linear-gradient(90deg, rgba(227, 179, 65, 0.09), rgba(227, 179, 65, 0.02));
  color: var(--muted); font-size: 14.5px;
}
.step-art {
  height: 140px; margin: -20px -28px 18px; border-bottom: 1px solid rgba(227, 179, 65, 0.22);
  background: linear-gradient(180deg, rgba(227, 179, 65, 0.05), transparent);
  border-radius: var(--radius) var(--radius) 0 0; overflow: hidden;
}
.step-art svg { width: 100%; height: 100%; }
.art-panel { fill: var(--surface-2); stroke: var(--border); stroke-width: 1.5; }
.art-line { stroke: #564b30; stroke-width: 2; fill: none; stroke-linecap: round; }
.art-line.thick { stroke-width: 4; stroke: #6a5d3c; }
.art-dot-grid circle { fill: #564b30; }
.art-knob { fill: url(#gBrand); }
.art-meter { fill: none; stroke: #564b30; stroke-width: 2; }
.art-badge { fill: var(--surface); stroke: url(#gBrand); stroke-width: 2.5; }
.art-check { fill: none; stroke: url(#gBrand); stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; }
.art-bar { fill: #3a3422; }
.art-bar-hot { fill: url(#gBrand); }
.art-trend { fill: none; stroke: #ffd75e; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.art-halo { fill: rgba(227, 179, 65, 0.055); }
.art-outline { fill: var(--surface-2); stroke: #6a5d3c; stroke-width: 2; }
.art-band { fill: url(#gBrand); opacity: 0.9; }
.art-gold { fill: url(#gBrand); }
/* knobs glide along their slider tracks like a live tuning session —
   mechanical ease, no cartoon scaling */
.art-knob { animation: knobSlideL 4.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; transform-box: fill-box; transform-origin: center; }
.art-knob.k2 { animation: knobSlideR 5.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite 0.7s; }
.art-knob.k3 { animation: knobSlideL 6.4s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite 1.4s; }
@keyframes knobSlideL { 50% { transform: translateX(-22px); } }
@keyframes knobSlideR { 50% { transform: translateX(24px); } }
/* benchmark bars rise into place when the card reveals, then breathe gently */
.art-bar, .art-bar-hot { transform-box: fill-box; transform-origin: bottom; }
.art-bar { animation: barSurge 4.4s ease-in-out 1.6s infinite; }
.step.visible .art-bar { animation: barGrow 0.7s cubic-bezier(0.22, 1, 0.36, 1) backwards, barSurge 4.4s ease-in-out 1.6s infinite; }
.step.visible .art-bar + .art-bar { animation-delay: 0.12s, 1.75s; }
.step.visible .art-bar + .art-bar + .art-bar { animation-delay: 0.24s, 1.9s; }
.step.visible .art-bar + .art-bar + .art-bar + .art-bar { animation-delay: 0.36s, 2.05s; }
.art-bar-hot { animation: barSurge 4.4s ease-in-out 2.3s infinite; }
.step.visible .art-bar-hot { animation: barGrow 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.5s backwards, barSurge 4.4s ease-in-out 2.3s infinite; }
@keyframes barGrow { from { transform: scaleY(0); } }
@keyframes barSurge { 50% { transform: scaleY(1.06); } }
/* result chip pops softly */
rect.art-gold, .art-chip-text { transform-box: fill-box; transform-origin: center; animation: chipPop 3.8s ease-in-out infinite 1.5s; }
@keyframes chipPop { 10% { transform: scale(1.09); } 20% { transform: scale(1); } }
/* glow dot lands at the end of the trend line, then pulses like a live reading */
.art-trend-dot { fill: #ffd75e; transform-box: fill-box; transform-origin: center; animation: dotPulse 3s ease-in-out 2s infinite; }
.step.visible .art-trend-dot { animation: dotIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 1.35s backwards, dotPulse 3s ease-in-out 2s infinite; }
@keyframes dotIn { from { transform: scale(0); } }
@keyframes dotPulse { 50% { transform: scale(1.45); opacity: 0.55; } }
/* halo breathes softly behind each scene */
.art-halo { animation: haloBreathe 6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes haloBreathe { 50% { transform: scale(1.05); opacity: 0.65; } }
/* gold sheen sweeps across the artwork on hover */
.step-art { position: relative; }
.step-art::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 42%, rgba(247, 221, 138, 0.12) 50%, transparent 58%);
  transform: translateX(-130%);
}
.step:hover .step-art::after { transform: translateX(130%); transition: transform 0.9s ease; }
/* the checkmark and trend line draw themselves in when the card scrolls into view */
.art-check, .art-trend { stroke-dasharray: 160; }
.step.visible .art-check { animation: strokeDraw 0.9s ease-out 0.35s backwards; }
.step.visible .art-trend { animation: strokeDraw 1.2s ease-out 0.25s backwards; }
@keyframes strokeDraw { from { stroke-dashoffset: 160; } to { stroke-dashoffset: 0; } }
.art-needle {
  stroke: url(#gCta); stroke-width: 3.5; stroke-linecap: round;
  transform-box: fill-box; transform-origin: 0% 100%;
  animation: needleWiggle 3.2s ease-in-out infinite;
}
@keyframes needleWiggle { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(14deg); } }
.art-chip-text { fill: var(--on-gold); font: 800 11px var(--font-text); }
.step-num {
  font-size: 14px; font-weight: 800; color: var(--on-gold); background: var(--accent-grad);
  width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; margin-bottom: 18px;
  font-family: var(--font-head);
  box-shadow: 0 6px 18px rgba(227, 179, 65, 0.35);
}
.step h3, .feature h3, .trust h3 { font-size: 18px; margin-bottom: 10px; }
.step p, .feature p, .trust p { color: var(--muted); font-size: 14.5px; }

/* ------------------------------- repairs -------------------------------- */
.repair-card { position: relative; }
.repair-card h3 { font-size: 20px; margin-bottom: 10px; }
.repair-card > p { color: var(--muted); font-size: 14.5px; margin-bottom: 16px; }
.repair-card > p b { color: var(--text); }
.repair-list { list-style: none; display: grid; gap: 9px; margin-bottom: 8px; }
.repair-list li { color: var(--muted); font-size: 14px; padding-inline-start: 28px; position: relative; line-height: 1.5; }
.repair-list li::before {
  content: "✓"; position: absolute; inset-inline-start: 0; top: 1px;
  width: 18px; height: 18px; border-radius: 6px; display: grid; place-items: center;
  font-size: 11px; font-weight: 800;
  background: rgba(52, 211, 153, 0.16); color: var(--success);
}

/* -------------------------------- plans -------------------------------- */
/* 4 equal cards in one row; align-items:stretch keeps them the same height,
   the feature <ul> flexes to fill so every CTA pins to the same bottom line. */
.plans-grid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 18px; row-gap: 30px; align-items: stretch; padding-top: 24px;
}
.plans-loading, .plans-error { grid-column: 1 / -1; text-align: center; color: var(--muted); padding: 40px 0; }
/* ---------------------------------- plans --------------------------------
   Four modules in a rack. The hierarchy between them is carried by three
   things that are all TRUE of the tiers rather than decorative:

     - the tier meter, which already encodes how many of the four power levels
       a plan lights (untouched by this pass — it shipped, it works, and it is
       the one place on the page a running animation reports something);
     - elevation. The recommended plan is the only one raised out of the rack:
       one film level up, a lit lip, a gold ring and a lamp along its top edge;
     - the action. It is the only card whose button is filled gold. The other
       three take the hairline secondary, which is the same rule the whole page
       follows — one filled gold action in view at a time.

   The 44px gold bloom under the featured card is gone. On a four-across rack
   it lit the two cards either side of it as much as itself, which made the
   recommendation look like a lighting accident rather than a choice. */
/* An OPAQUE deck, not a film. Every other surface on this page is a
   translucent wash over whatever is behind it, and that is right for a
   section you look at — but a plan card is four hundred words of feature list
   and a price, and at --sf-1's 3.5% alpha a racing game was showing through
   the middle of it. Pricing is where the visitor decides, and a decision
   surface has to be quiet.

   Near-opaque rather than flat: 93% over a section whose artwork is already
   dropped to the quiet tier, so the deck still picks up the ambient light at
   its edges and belongs to the page — it just stops competing with its own
   contents. The gradient is the same top-lit direction every raised surface
   here uses. */
.plan {
  display: flex; flex-direction: column; position: relative;
  /* Own stacking context, so the tier bloom can sit at z-index 0 above the
     card's own background and still stay behind every line of content. */
  isolation: isolate;
  padding: 24px 20px; min-width: 0; border-radius: 12px;
  background: linear-gradient(180deg, rgba(21, 18, 12, 0.93), rgba(13, 11, 7, 0.95));
  /* Resting tier signals: a hairline of the tier's own light along the top
     edge, and a ring at 16%. Both are derived from --tier-rgb, so the four
     cards differ by one number and not by four rules. */
  border-color: rgba(var(--tier-rgb), 0.16);
  box-shadow: inset 0 1px 0 rgba(var(--tier-rgb), 0.2);
}
/* The bloom behind the mark. Symmetric about the vertical axis, so it is
   identical under RTL mirroring and needs no direction override, and pinned
   to the top of the card where the mark actually is rather than washed over
   the feature list. `inset 0` + `border-radius: inherit` so it can never
   escape the rounded corner it is painted inside. */
.plan::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(74% 34% at 50% 0%, rgba(var(--tier-rgb), 0.1), rgba(var(--tier-rgb), 0) 70%);
  transition: background var(--t-ui) ease;
}
/* Everything the reader reads sits above the bloom. One rule rather than a
   z-index on each child: the bloom is the only positioned thing below them. */
.plan > * { position: relative; z-index: 1; }
/* The one lift left on the page. A plan is a decision, and a decision is worth
   a physical response; a feature card is not.

   Composed with --reveal-y rather than replacing it: `.reveal` sets
   `transform: translateY(var(--reveal-y))` and this rule out-ranks it, so a
   bare `translateY(-3px)` here would cancel the reveal offset the moment the
   pointer touched a card that had not finished arriving. calc() adds to it. */
.plan:hover { transform: translateY(calc(var(--reveal-y) - 3px)); }
/* `.card:hover` swaps the background to --sf-2, which on a plan would trade
   the opaque deck back for a 6% film the moment a pointer touched it. The
   hover feedback that matters — the gold edge and the lit lip — comes from
   .card:hover and is left alone; only the material is pinned. */
.plan.card:hover {
  background: linear-gradient(180deg, rgba(28, 23, 15, 0.94), rgba(16, 13, 9, 0.95));
  /* The pointer lights the card in the card's own colour. `.card:hover` would
     warm all four to the same gold, which is exactly the flattening this pass
     is undoing — and it would tell an Apex reader that hovering Apex means
     something gold. */
  border-color: rgba(var(--tier-rgb), 0.38);
  box-shadow: inset 0 1px 0 rgba(var(--tier-rgb), 0.32);
}
.plan.card:hover::before {
  background: radial-gradient(74% 34% at 50% 0%, rgba(var(--tier-rgb), 0.16), rgba(var(--tier-rgb), 0) 70%);
}
.plan.featured.card:hover { background: linear-gradient(180deg, rgba(38, 31, 18, 0.96), rgba(20, 17, 10, 0.96)); }
/* The recommended card is one film level up and one ring step brighter than
   its own tier's resting state — not a different colour. Overdrive's tier IS
   gold, so `rgba(var(--tier-rgb), 0.42)` resolves to exactly the --ring-gold
   this rule used to name literally; writing it through the token means the
   recommendation would still be legible if the featured plan ever moved. */
.plan.featured {
  background: linear-gradient(180deg, rgba(32, 26, 15, 0.95), rgba(18, 15, 9, 0.96));
  border-color: rgba(var(--tier-rgb), 0.42);
  box-shadow: inset 0 1px 0 rgba(var(--tier-rgb), 0.34);
}
.plan.featured::before {
  background: radial-gradient(74% 38% at 50% 0%, rgba(var(--tier-rgb), 0.15), rgba(var(--tier-rgb), 0) 72%);
}
/* The lamp along the top edge is GONE, and this is the redundancy audit's
   clearest case. The recommended card was marked four times over: a gold
   border, a 2px gold bar along the top, the sheened DQT PICK flag centred ON
   that bar, and a gold primary button. The bar and the flag occupy the same
   12px of the card — the flag literally sits on top of the bar and hides its
   middle third — so one of the two was pure duplication.

   The flag is the one that survives, because it is the one that says WHY the
   card is marked. What is left: the ring places the card, the flag names the
   recommendation, the button is the action. Three signals, three meanings.

   The lit-edge construction itself is not retired — .tool-result::before still
   uses it, where nothing else says "this panel is live output". */
/* The flag stays gold-filled and keeps the travelling sheen: it is one 11px
   tag, it is the label ON the recommendation, and it shares its material with
   the studios badge and the tier meter. What it loses is the 18px coloured
   drop shadow, which was doing the bloom's job at a smaller radius. */
.plan-flag {
  position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
  background: var(--gold-sheen);
  background-size: 200% auto; color: var(--on-gold); font: 800 10.5px var(--mono);
  padding: 5px 13px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.08em;
  white-space: nowrap; z-index: 2;
  box-shadow: 0 0 0 4px var(--bg);
  animation: gradSheen 5.5s ease-in-out infinite;
}
.plan-pricing { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.plan-was { color: var(--faint); text-decoration: line-through; font-size: 15px; font-weight: 600; }
.plan-off {
  background: rgba(52, 211, 153, 0.14); color: var(--success);
  border: 1px solid rgba(52, 211, 153, 0.3);
  font: 700 10.5px var(--mono); padding: 2px 7px; border-radius: 4px; letter-spacing: 0.06em;
}
/* --------------------------- plan tier meter ---------------------------
   Four segments that must read as ONE meter, not four bars that happen to
   animate. A single energy head travels the *enabled run* in meter
   coordinates and is masked to the bar rhythm, so it crosses the gaps as one
   continuous object; the bright tip of that same gradient is the spark, which
   is why it can never drift loose from the wave.

   Everything scales off --on (how many segments are lit), so tiers differ by
   intensity alone — one gold palette, no per-plan animation selectors. */
.plan-tier {
  --tier-seg: 14px; --tier-gap: 4px; --tier-pitch: 18px; --tier-head: 40px;
  --tier-meter: calc(4 * var(--tier-seg) + 3 * var(--tier-gap));   /* 68px */
  --tier-bleed: 2px;                             /* ramp overscan, see below */
  --on: 4;
  --tier-run: calc(var(--on) * var(--tier-pitch) - var(--tier-gap));
  --tier-energy: calc(var(--on) / 4);            /* 0.25 … 1 */
  --tier-cycle: 4.6s;                            /* 65% travel ≈ 3.0s, then rest */
  --end-lo: 0.16; --end-hi: 0.32;                /* endpoint breathing amplitude */
  position: relative; display: flex; gap: var(--tier-gap); margin-bottom: 12px;
  width: max-content;
}
/* Tier count is read off the segments themselves rather than restated in an
   attribute, so the English page, the Arabic generator and renderPlans() can
   never disagree about it — the markup they already share IS the source.
   `.off` is always trailing, so these deliberately overlap and the last match
   wins: a Tier 1 meter matches all three and resolves to 1. Equal specificity
   throughout ((0,3,1) — :has() takes its argument's), so order decides. */
.plan-tier:has(> i:nth-child(4).off) { --on: 3; }
.plan-tier:has(> i:nth-child(3).off) { --on: 2; }
.plan-tier:has(> i:nth-child(2).off) { --on: 1; }
/* Bar rhythm as a mask, so the head paints on segments and skips the gaps. */
.plan-tier::before, .plan-tier::after {
  -webkit-mask-image: repeating-linear-gradient(to right, #000 0 var(--tier-seg), transparent var(--tier-seg) var(--tier-pitch));
          mask-image: repeating-linear-gradient(to right, #000 0 var(--tier-seg), transparent var(--tier-seg) var(--tier-pitch));
}

/* Lit segments are windows onto ONE meter-wide gradient, not four copies of a
   14px one: each samples the slice sitting at its own offset, so the gold runs
   continuously across the row before anything moves. --tier-x is the segment's
   distance from the meter's left edge, which is what background-position
   measures — and RTL reverses the flex row, so that distance has to be
   recomputed there or the slices land in sawtooth order. */
.plan-tier i {
  --tier-x: calc(var(--i) * var(--tier-pitch));
  position: relative;
  width: var(--tier-seg); height: 4px; border-radius: 3px;
  background-image: var(--tier-base-grad);
  background-repeat: no-repeat;
  /* Overscanned by --tier-bleed on each side. Sized to exactly --tier-meter the
     ramp ended on the last segment's right edge, and sub-pixel rounding left
     that rounded corner unpainted — the segment looked squared off. Widening
     the ramp and shifting the sample by the same amount keeps every slice
     consecutive, so continuity is untouched. */
  background-size: calc(var(--tier-meter) + 2 * var(--tier-bleed)) 100%;
  background-position: calc(-1 * var(--tier-x) - var(--tier-bleed)) center;
  transition: background 0.3s ease, filter 0.3s ease;
}
[dir="rtl"] .plan-tier i { --tier-x: calc(var(--tier-meter) - var(--tier-seg) - var(--i) * var(--tier-pitch)); }
/* Shorthand on purpose: it resets image/size/position so an unlit socket
   cannot sample the active gradient. */
.plan-tier i.off { background: var(--surface-2); }
.plan-tier i:nth-child(1) { --i: 0; }
.plan-tier i:nth-child(2) { --i: 1; }
.plan-tier i:nth-child(3) { --i: 2; }
.plan-tier i:nth-child(4) { --i: 3; }

/* ---- charge: segments light in reading order as the card reveals ---- */
.plan.visible .plan-tier i:not(.off) {
  transform-origin: var(--tier-origin, left) center;
  /* `backwards`, not `both`: the segment must sit collapsed through its stagger
     delay, but a forwards fill would pin filter/transform at the final keyframe
     forever and silently swallow the hover brightness. The 100% keyframe is
     identical to the resting style, so releasing the fill is invisible. */
  animation: tierCharge 0.52s cubic-bezier(0.22, 1, 0.36, 1) backwards;
  /* The card is still fading in for its first ~240ms; segment 1 used to peak
     while the whole card sat under 93% opacity, so the start of the sequence
     was spent behind the entrance. The offset lets the card establish itself
     first — the 100ms stagger is unchanged. */
  animation-delay: calc(0.18s + var(--i) * 0.1s);
}
[dir="rtl"] .plan.visible .plan-tier i:not(.off) { --tier-origin: right; }
/* Brightness alone, no saturation boost. Pushing a warm gold past ~1.5 clips
   red first and then saturating what is left drives the hue to chartreuse — it
   photographed as pure yellow, nowhere near the palette. The halo carries the
   sense of energy instead, so the segment itself only has to go warm-white. */
@keyframes tierCharge {
  0%   { transform: scaleX(0);    filter: brightness(1); box-shadow: 0 0 0 rgba(247, 221, 138, 0); }
  60%  { transform: scaleX(1.04); filter: brightness(1.35); box-shadow: 0 0 7px rgba(247, 221, 138, 0.5); }
  100% { transform: scaleX(1);    filter: brightness(1); box-shadow: 0 0 0 rgba(247, 221, 138, 0); }
}

/* ---- endpoint: only the highest enabled segment marks the ceiling ---- */
.plan-tier i:not(.off):last-child::after,
.plan-tier i:not(.off):has(+ i.off)::after {
  content: ""; position: absolute; inset: -2px -3px; border-radius: 5px;
  background: radial-gradient(ellipse at center, rgba(247, 221, 138, 0.55), transparent 70%);
  opacity: var(--end-lo); pointer-events: none;
  animation: tierEndpoint 5.9s ease-in-out infinite;
}
@keyframes tierEndpoint { 0%, 100% { opacity: var(--end-lo); } 50% { opacity: var(--end-hi); } }

/* ---- continuous flow: one head, one clock, masked to the lit run ---- */
.plan-tier::after {
  content: ""; position: absolute; inset-block: 0; inset-inline-start: 0;
  width: var(--tier-run); border-radius: 3px; pointer-events: none;
  /* The tip used to jump from #fff8e0 at 95% straight to transparent at 100% —
     about 2px — which composited as a rectangular bright block rather than a
     spark. The peak now sits at 90% and decays over the remaining 9%, so the
     head reads soft body → concentrated white-gold tip → fade. Body centre
     ~48% and tip ~90% of a 40px head keeps the lead at roughly 16px. */
  background-image: linear-gradient(var(--tier-head-dir, 90deg),
    transparent 0,
    rgba(247, 221, 138, 0.04) 20%,
    rgba(247, 221, 138, 0.15) 48%,               /* pulse body */
    rgba(247, 221, 138, 0.30) 70%,
    rgba(255, 240, 194, 0.62) 84%,
    rgba(255, 248, 224, 0.90) 90%,               /* spark: the tip of this wave */
    rgba(255, 244, 210, 0.38) 95%,
    transparent 99%);
  background-repeat: no-repeat;
  background-size: var(--tier-head) 100%;
  opacity: calc(0.42 + 0.38 * var(--tier-energy));
  /* 1.18s ≈ the last segment finishing its charge now that it starts 0.18s later. */
  animation: tierFlow var(--tier-cycle) linear 1.18s infinite;
}
/* Mirrored so the reading origin samples the same end of the ramp it does in
   LTR — the offset fix above restores continuity, this restores equivalence. */
[dir="rtl"] .plan-tier { --tier-base-grad: linear-gradient(to left, var(--gold-lo), var(--gold-hi)); }
[dir="rtl"] .plan-tier::after { --tier-head-dir: 270deg; animation-name: tierFlowRtl; }
@keyframes tierFlow {
  0%       { background-position-x: calc(var(--tier-head) * -1); }
  65%, 100% { background-position-x: calc(100% + var(--tier-head)); }
}
@keyframes tierFlowRtl {
  0%       { background-position-x: calc(100% + var(--tier-head)); }
  65%, 100% { background-position-x: calc(var(--tier-head) * -1); }
}

/* ---- Apex only: an occasional white-gold sweep, off-beat from the flow ---- */
.plan-tier:not(:has(i.off))::before {
  content: ""; position: absolute; inset-block: 0; inset-inline-start: 0;
  width: var(--tier-run); border-radius: 3px; pointer-events: none;
  background-image: linear-gradient(var(--tier-head-dir, 90deg),
    transparent 0, rgba(255, 248, 224, 0.55) 50%, rgba(247, 221, 138, 0.2) 66%, transparent 100%);
  background-repeat: no-repeat; background-size: 30px 100%;
  animation: tierSweep 6.4s ease-in-out 2.2s infinite;
}
[dir="rtl"] .plan-tier:not(:has(i.off))::before { animation-name: tierSweepRtl; }
@keyframes tierSweep {
  0%, 74%   { background-position-x: -30px; opacity: 0; }
  78%       { opacity: 0.85; }
  94%, 100% { background-position-x: calc(100% + 30px); opacity: 0; }
}
@keyframes tierSweepRtl {
  0%, 74%   { background-position-x: calc(100% + 30px); opacity: 0; }
  78%       { opacity: 0.85; }
  94%, 100% { background-position-x: -30px; opacity: 0; }
}

/* ---- overclock: one interaction, not five effects ---- */
.plan:hover .plan-tier, .plan:focus-within .plan-tier {
  --tier-cycle: 3.2s;                            /* 1.4× — responsive, not double speed */
  --end-lo: 0.24; --end-hi: 0.44;
}
.plan:hover .plan-tier i:not(.off), .plan:focus-within .plan-tier i:not(.off) { filter: brightness(1.1); }
.plan:hover .plan-tier i.off, .plan:focus-within .plan-tier i.off { background: rgba(247, 221, 138, 0.12); }
.plan h3 { font-size: 18px; letter-spacing: -0.01em; }
.plan-tagline { color: var(--muted); font-size: 12.5px; line-height: 1.45; margin: 5px 0 14px; min-height: 34px; text-wrap: pretty; }
.plan-price { font-size: 34px; font-weight: 700; font-family: var(--font-head); line-height: 1.05; letter-spacing: -0.02em; }
/* The billing unit is the scale the price is read against, not part of the
   figure — same split as the unit beside every metric tile on the page. */
.plan-price small { font: 600 11px var(--mono); color: var(--faint); letter-spacing: 0.06em; }
/* Duration is a reading, so it takes the register (shared rule above) — but it
   gives its gold back. Gold on this page means action, live, or gained, and
   "1 h" is none of the three. */
.plan-duration { margin: 8px 0 16px; }
.plan ul { list-style: none; display: grid; gap: 7px; margin: 0 0 18px; flex: 1 1 auto; align-content: start; }
.plan li { color: var(--muted); font-size: 12.5px; line-height: 1.45; padding-inline-start: 22px; position: relative; }
.plan li::before {
  content: "✓"; position: absolute; inset-inline-start: 0; top: 1px;
  width: 15px; height: 15px; border-radius: 4px; display: grid; place-items: center;
  font-size: 9.5px; font-weight: 800;
  background: rgba(52, 211, 153, 0.14); color: var(--success);
}
/* --------------------------- the inheritance line ------------------------
   Three of the four plans open with "Everything in <the tier below>". That
   line is not a feature, it is the LADDER — the single most useful piece of
   comparison on the page, and it was set in the same grey as the eight bullets
   under it with the same green tick in front of it.

   Promoting it to the instrument register and swapping the tick for a rising
   chevron makes the rack readable as a ladder at a glance, which is the
   comparison a pricing table is usually built to provide. No table, no extra
   markup, no new copy: the sentence was already there and already translated.

   Scoped off .plan--free, which is the bottom of the ladder and inherits from
   nothing — its first line is a feature like any other. */
.plan:not(.plan--free) ul li:first-child {
  color: var(--text); font: 600 10.5px/1.5 var(--mono);
  text-transform: uppercase; letter-spacing: 0.08em;
  padding-bottom: 12px; margin-bottom: 4px; border-bottom: 1px solid var(--ring);
}
.plan:not(.plan--free) ul li:first-child::before {
  content: "↑"; background: rgba(227, 179, 65, 0.14); color: var(--accent); font-size: 11px;
}
.plan .btn { margin-top: auto; width: 100%; white-space: normal; text-align: center; line-height: 1.3; }

/* ------------------------------ value flip -------------------------------
   THE COMPARISON.

   Two passes turned this card into an instrument — a banded scorecard with a
   three-cell readout plate, a lit finding band and a priced choice band, each
   introduced by a mono register label. It was well built and it was the wrong
   instrument. Everything else on this page reports a measurement; this one
   section makes a SALES argument, and the argument is one sentence: matching
   this performance in new parts costs about $1,130, unlocking what the machine
   already has starts at $49.99. Six console labels and three full-width
   hairlines is a reader being made to parse a dashboard before they are
   allowed to hear it.

   So the composition is the simple one again — a stamp, a paragraph, two
   priced panels either side of a divider, the footnote, the close, the button
   — and what the instrument passes actually taught is kept:

   1. THE GROUND STAYS OPAQUE. This is the one finding from those passes worth
      more than the layout it was built for. Every surface in the card used to
      be an alpha film of --sf-1 over a photograph; a film over art cannot
      separate from another film over the same art, and the photograph's warm
      cast came through all of it, which is why a card whose palette is three
      greys and one gold read as "everything is brown". --vf-well is opaque and
      neutral, so the art stops at the card's edge and the two panels can hold
      a real tonal step off it.

   2. THE LABELS ARE OFF --faint. #988c72 is a warm brown, and a dozen of them
      in one card was the other half of the brown reading. --vf-label is the
      same luminance with the cast taken out.

   3. THE HARDWARE PRICE IS --muted, NOT RED. #f0816b said "danger" about a
      number that is not a failure — it is what the same performance costs in
      new parts, and the argument of the section is that spending it is
      unnecessary, not that it is dangerous. Three values, three roles:

        --accent   from $49.99   the route that gets you the frames
        --text     the scenario's own figures, inside the paragraph
        --muted    ~$1,130       the same kind of fact, on the route not taken

   4. THE FOOTNOTE SITS UNDER THE FIGURES. It used to come after the price, the
      pitch and the button — i.e. after the decision it qualifies.

   Gold appears three times in the whole card: the winning panel's cap rail and
   its lamp, its figure, and the button. That is the same budget the rest of the
   page keeps. */
.value-card {
  text-align: center;
  padding: 30px 32px 32px;
  border-color: var(--ring);

  /* ---- the card's own channel ----------------------------------------
     Scoped to `.value-card`. Named --vf-* rather than reusing the --sf-*
     ladder because these are not films: --vf-well has to be OPAQUE to stop the
     section's photograph, which is point 1 above, and the ladder in :root is
     alpha by definition. */
  --vf-well:   #0a0a0b;                       /* the housing. neutral, opaque */
  --vf-plate:  #121316;                       /* a panel seated in the well */
  --vf-edge:   rgba(233, 238, 247, 0.12);     /* a neutral hairline */
  /* The divider's own two hairlines. --vf-edge is a border drawn ON a panel;
     these are drawn on the CARD's ground in the gap between two panels, and at
     0.12 a rule that already fades to transparent at one end never becomes
     visible at the other. */
  --vf-rule:   rgba(233, 238, 247, 0.28);
  --vf-label:  #8f9095;                       /* secondary: labels, captions */
  --vf-dim:    #7d7f85;                       /* tertiary: units, fine print */

  background: var(--vf-well);
}
/* The card is a housing, not a control: it does not warm under the pointer.
   `.card:hover` would otherwise light the ring of a 780px-wide block every time
   the pointer crossed it, which reports nothing. Background and shadow are
   pinned; `transform` is deliberately not mentioned, so `.reveal` keeps
   governing the entrance offset at its own lower specificity. */
.value-card.card:hover {
  background: var(--vf-well); border-color: var(--ring); box-shadow: var(--lip);
}

/* ------------------------------- the stamp -------------------------------
   One small plate at the top of the card, centred, saying what the numbers
   below it are. The hollow ring is what the FAQ uses for a row that is not
   open and what the hardware panel below uses for the route not taken — on a
   page where a lit gold lamp means "this channel is live", an unlit one says
   "this card models a cost, it does not report one" with no new vocabulary.

   This is the ONE mono register left in the card, and it earns its place: it
   is the honesty label. The five that sat beside the figures did not. */
.value-scenario {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 6px 12px; border-radius: 6px;
  border: 1px solid var(--vf-edge); background: var(--vf-plate);
  box-shadow: var(--lip);
  color: var(--vf-label); font: 600 11px var(--mono);
  text-transform: uppercase; letter-spacing: 0.11em;
}
.value-scenario::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: transparent; box-shadow: inset 0 0 0 1.5px rgba(233, 238, 247, 0.4);
}

/* ------------------------------ the scenario -----------------------------
   The paragraph the last pass took apart into a readout plate. It reads at the
   page's body size and one step brighter than --muted, because it is the
   argument and not a caption, and it is capped at a comfortable measure and
   centred under the stamp — the card is 780px wide, so a 64ch centred
   paragraph sets three even lines rather than a ragged block.

   `text-wrap: pretty` rather than `balance`: balance is capped at a handful of
   lines and this paragraph is longer than that in every language. */
.value-lead {
  margin: 18px auto 0; max-inline-size: 64ch;
  color: var(--text); font-size: 16px; line-height: 1.75;
  text-wrap: pretty;
}

/* ------------------------------ the comparison ---------------------------
   Two panels with a divider between them — not the shared housing split by a
   seam that the last pass drew. That construction says "one object with two
   halves", which is right for a readout and wrong for a choice: the reader is
   picking between two routes, and two routes are two objects.

   `1fr auto 1fr`, so the divider takes exactly its own width and the two
   panels stay identical whatever the longest translation happens to be. Below
   700 the three tracks become three rows and the same rule stacks them
   hardware / vs / DQT, which is the sequence the copy argues in. */
.value-compare {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 14px; margin-block-start: 26px;
  align-items: stretch;
}
@media (min-width: 700px) {
  .value-compare { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 18px; }
}
.value-box {
  display: grid; gap: 9px; align-content: start; justify-items: center;
  min-width: 0; text-align: center;
  background: var(--vf-plate); padding: 20px 20px 21px;
  border: 1px solid var(--vf-edge); border-radius: 14px;
  box-shadow: var(--lip);
}
.value-box-num {
  font-family: var(--font-head); font-size: 32px; font-weight: 700;
  line-height: 1.1; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.value-box-sub { font: 400 11.5px/1.55 var(--mono); color: var(--vf-dim); }
/* A LAMP on each label, and the pair is the whole comparison said twice: the
   hardware panel takes the hollow ring the scenario stamp wears at the top of
   this card, the DQT panel takes the lit gold circle. Both are constructions
   the reader met two inches ago, doing the same job here — which is why the
   winning state gets a redundant channel for no new vocabulary and no extra
   gold.

   `align-items: start` with a nudge, not `center`: three of the four labels
   wrap to two lines somewhere in the four languages, and a lamp that drifts to
   the middle of a two-line label stops reading as the label's marker. */
.value-box-label {
  display: inline-flex; align-items: start; gap: 8px;
  /* The mono label register itself is NOT restated here: this selector is in
     the shared list near .plan-duration, several hundred lines down, which
     wins on source order at equal specificity. Repeating it would be a rule
     that silently does nothing. */
}
.value-box-label::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  margin-block-start: 5px;
  background: transparent; box-shadow: inset 0 0 0 1.5px rgba(233, 238, 247, 0.4);
}
.value-box-win .value-box-label::before {
  background: var(--accent); box-shadow: 0 0 8px rgba(227, 179, 65, 0.55);
}
.value-bad { color: var(--muted); }
.value-good { color: var(--accent); }
/* The lit panel. A 2px gold cap rail along the block-start edge and a vertical
   ramp off it, so the panel reads as lit from its own rail rather than tinted
   all over. The rail stays on the top edge under RTL and in the stacked layout
   with no twin, and it is the same construction the studios plate's lead bay
   wears — one way, across two sections, of saying "this is the one". */
.value-box-win {
  border-color: var(--ring-gold);
  background:
    linear-gradient(180deg, rgba(227, 179, 65, 0.13), rgba(227, 179, 65, 0.035)),
    var(--vf-plate);
  box-shadow: inset 0 2px 0 var(--accent);
}

/* ---------------------------------- the vs -------------------------------
   Intentional and quiet. A small mono word, centred in its own track, with a
   hairline running off it in whichever axis the comparison is laid out on: two
   vertical rules beside it when the panels are side by side, two horizontal
   ones above and below when they are stacked. That is the whole device — it
   marks the join without becoming the thing the eye lands on.

   `align-self: center` on a stretched grid item, so the word sits at the
   vertical middle of the taller panel rather than at the top of its own track. */
.value-vs {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  color: var(--vf-label); font: 600 11.5px var(--mono);
  text-transform: uppercase; letter-spacing: 0.14em;
}
.value-vs::before, .value-vs::after {
  content: ""; flex: 1 1 auto; height: 1px; min-width: 24px;
  background: linear-gradient(90deg, transparent, var(--vf-rule));
}
.value-vs::after { background: linear-gradient(90deg, var(--vf-rule), transparent); }
@media (min-width: 700px) {
  .value-vs {
    flex-direction: column; align-self: stretch; gap: 10px; padding-inline: 2px;
  }
  .value-vs::before, .value-vs::after {
    width: 1px; height: auto; min-width: 0; min-height: 12px;
  }
  .value-vs::before { background: linear-gradient(180deg, transparent, var(--vf-rule)); }
  .value-vs::after { background: linear-gradient(180deg, var(--vf-rule), transparent); }
}

/* ------------------------------- the footnote ----------------------------
   Against the figures it qualifies, in the register the stamp above already
   established, and centred with them. Capped at 74ch so it never runs the full
   780px and starts reading as body copy. */
.value-fine {
  margin: 14px auto 0; max-inline-size: 74ch;
  color: var(--vf-dim); font: 400 11.5px/1.6 var(--mono);
}

/* ------------------------------- the close -------------------------------
   The end of the argument and the action, in one centred block separated from
   the comparison by a full-width hairline. The close is not a new part of the
   argument — it is what the comparison above it means — so it keeps the card's
   own ground and takes only a rule, where before the rebuild it sat on a
   lighter fill of its own and read as a strip stuck underneath.

   The pitch is capped at 56ch and centred; the button keeps its intrinsic
   width and sits under it, which is where a centred module puts its action. */
.value-close-block {
  margin-block-start: 24px; padding-block-start: 22px;
  border-block-start: 1px solid var(--vf-edge);
  display: grid; justify-items: center; gap: 18px;
}
.value-close {
  max-inline-size: 56ch;
  color: var(--text); font-size: 15px; font-weight: 600; line-height: 1.65;
  text-wrap: pretty;
}

/* ------------------------- the figures are LTR runs ----------------------
   Found by zooming the Arabic page to 4x and reading the glyphs: `~$1,130` was
   painting with its tilde on the wrong side.

   The cause is the bidi algorithm doing exactly what it is specified to do.
   `~` and `+` are neutrals or European Separators; between Arabic text and a
   number run a neutral takes the paragraph direction, so it lands on the
   right-hand side of the digits and an Arabic reader, reading the Latin run
   left to right, sees something other than what was written.

   The fix is to say what the token actually is: a self-contained Latin
   measurement, not a fragment of the sentence around it. `isolate` stops it
   resolving against its neighbours and `direction: ltr` sets its internal
   order.

   `.value-good` is deliberately absent and must stay absent: it carries a
   translated string — "من $49.99" in Arabic, "מ-$49.99" in Hebrew — and forcing
   that to LTR would drag the preposition to the wrong side of its own number. */
.value-bad { direction: ltr; unicode-bidi: isolate; }

@media (max-width: 620px) {
  .value-card { padding: 22px 18px 24px; }
  .value-lead { font-size: 15px; margin-block-start: 16px; }
  .value-compare { margin-block-start: 20px; gap: 12px; }
  .value-box { padding: 17px 16px 18px; }
  .value-box-num { font-size: 28px; }
  .value-close-block { margin-block-start: 20px; padding-block-start: 18px; }
  /* Full width, and 46px tall. The base .btn is 40.5px — fine for a pointer,
     4px under the touch minimum for a thumb — and this is the only action in
     the card, at the end of the argument the whole card is making. Same 46 as
     the hero's secondary, for the same reason and with the same arithmetic:
     46 x 0.95 = 43.7 in the 561-620 band where the UI scale still applies, a
     true 46 below 561 where it does not. */
  .value-close-block .btn { justify-self: stretch; min-block-size: 46px; }
}

/* --------------------------------- faq -----------------------------------
   WHAT CHANGED AND WHY

   Before this pass the FAQ was fourteen rounded cards, each with its own
   border, its own ground and a gold gradient wash when open — which is a lot
   of chrome around what is, structurally, a list. The Refero references are
   unanimous on the alternative: Era's FAQ rows are transparent with a single
   1px bottom border and no radius at all, and Clockwise builds the same
   section out of "thin rules rather than cards or shadows". A boxed row asks
   to be read as an object; a ruled row asks to be read as a line in a
   document, which is what a question in a list of questions is.

   So the cards are gone and four things replace them:

   1. THE RAIL. `border-inline-start` on the <details> itself — a real border,
      not a pseudo-element — transparent when closed, gold when open. Because
      it is a border on the element that contains both the summary and the
      answer, it grows down the side of the answer as the answer opens, at
      exactly the answer's height, with no JavaScript and nothing to keep in
      sync. That is the progress-line indicator, and it is free.

      A logical property, so it is on the left in English and the right in
      Arabic without a second rule.

   2. THE LAMP. `summary::before` — a hollow gold ring that fills when the item
      opens. It is the same 6px lamp the section labels wear, doing the same
      job: this one is live. It also means the open/closed state is reported at
      the START of the row, where the reader's eye already is, instead of only
      at the far end of it.

   3. THE MORPH, KEPT AND QUIETENED. The + / − is one painted box with a
      two-stripe mask cut out of it: opening retracts the vertical stripe into
      the horizontal one. It is already the right answer — no glyph, no
      rotation, no overshoot, and above all NO EXTRA ELEMENT, which the markup
      forbids: every summary carries data-i18n and build-ar.js replaces the
      whole inner content of a data-i18n element, so a wrapper <span> would
      exist in English and be gone from /ar/. What changed is that it now rests
      in --faint and only goes gold on the open row, so thirteen closed
      questions are no longer wearing thirteen gold plus signs.

   4. THE REVEAL, SHORTER AND CHEAPER. Two legs instead of four: height and
      opacity. The 2px blur is deleted outright — it was the one expensive
      property in the transition, it was there to soften a card appearing, and
      a ruled row has no card to soften. Both legs sit inside --t-ui.

   Nothing loops. The whole section's motion is one 200ms disclosure. */
.faq-list { display: grid; }
.faq-item {
  position: relative; background: none;
  border: 0; border-top: 1px solid var(--ring);
  border-inline-start: 2px solid transparent;
  border-radius: 0; overflow: hidden; interpolate-size: allow-keywords;
  transition: border-color var(--t-ui) ease, background var(--t-ui) ease;
}
/* The last row closes the list, so the block reads as a ruled table rather
   than as thirteen rules and an open end. */
.faq-item:last-child { border-bottom: 1px solid var(--ring); }
/* THE OPEN STATE, as a rail rather than as a panel.
   `background: var(--sf-1)` on the whole <details> was filling the entire open
   row — question, answer and all — with a hard-edged rectangle, and a
   rectangle is a card. Thirteen ruled rows with one card sitting in the middle
   of them is two design languages in one list, which is the defect this
   refinement is fixing.

   What replaces it is two things, both of which stay inside the ruled system:

     the RAIL — the 2px inline-start edge going gold. It is already there on
     every row, transparent, so nothing shifts when it lights; it marks the
     open row down its whole height the way a selected row is marked in any
     instrument list.

     the WASH — a tint on ::details-content only, fading to nothing before it
     reaches the bottom. It groups the answer with its question and gives the
     indented text something to sit on, and because it has no bottom edge it
     never closes into a box. The question row itself stays on the page's own
     ground, so an open row is still a row.

   The wash rides on the same pseudo-element the disclosure animates, so it
   arrives and leaves with the answer for free. */
.faq-item[open] {
  border-inline-start-color: var(--accent);
  background: none;
}
.faq-item[open]::details-content {
  background: linear-gradient(
    180deg,
    rgba(247, 243, 232, 0.05) 0%,
    rgba(247, 243, 232, 0.022) 45%,
    rgba(247, 243, 232, 0) 100%
  );
}

/* Chromium animates ::details-content (block-size auto is animatable thanks to
   interpolate-size above); browsers without it snap open — graceful either
   way. Everything the answer needs rides on this one pseudo-element, so the
   paragraphs carry no animation of their own. */
.faq-item::details-content {
  block-size: 0; overflow: hidden; opacity: 0;
  transition:
    block-size var(--t-ui) var(--ease),
    opacity 0.16s ease-out 0.04s,
    content-visibility var(--t-ui) allow-discrete;
}
.faq-item[open]::details-content { block-size: auto; opacity: 1; }

.faq-item summary {
  cursor: pointer; list-style: none; font-weight: 600; font-size: 15px;
  padding: 17px 20px; padding-inline-start: 18px;
  display: flex; align-items: center; gap: 14px;
  text-wrap: pretty;
  transition: color var(--t-ui) ease;
}
.faq-item summary:hover { color: var(--accent); }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item[open] > summary { color: var(--accent); }

/* The lamp. Hollow when closed, filled and haloed when open. */
.faq-item summary::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: transparent; box-shadow: inset 0 0 0 1.5px rgba(227, 179, 65, 0.5);
  transition: background var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
.faq-item[open] summary::before {
  background: var(--accent);
  box-shadow: inset 0 0 0 1.5px var(--accent), 0 0 0 3px rgba(227, 179, 65, 0.14);
}

/* The morph is pushed to the end of the row by its own auto margin rather than
   by justify-content: space-between on the row. The row has three children now
   — lamp, question, morph — and space-between would put air on BOTH sides of
   the question, detaching the lamp from the text it reports on. */
.faq-item summary::after {
  content: ""; width: 13px; height: 13px; flex: none; margin-inline-start: auto;
  background: var(--faint);
  -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  -webkit-mask-position: center, center;
  mask-position: center, center;
  -webkit-mask-repeat: no-repeat, no-repeat;
  mask-repeat: no-repeat, no-repeat;
  -webkit-mask-size: 100% 1.5px, 1.5px 100%;
  mask-size: 100% 1.5px, 1.5px 100%;
  transition: -webkit-mask-size var(--t-ui) cubic-bezier(0.4, 0, 0.2, 1),
              mask-size var(--t-ui) cubic-bezier(0.4, 0, 0.2, 1),
              background var(--t-ui) ease;
}
.faq-item summary:hover::after { background: var(--accent); }
.faq-item[open] summary::after {
  background: var(--accent);
  -webkit-mask-size: 100% 1.5px, 1.5px 0%;
  mask-size: 100% 1.5px, 1.5px 0%;
}
/* The indent is derived, not chosen: the summary pads 18px, then the 6px lamp,
   then the 14px gap — so 38px is exactly where the QUESTION's first character
   sits, and the answer hangs from the same vertical. 2px of that is the rail's
   own border, which the padding sits inside of, so the declared figure is 36.
   Get this wrong in either direction and the answer reads as a separate block
   that happens to be nearby. */
.faq-item p {
  color: var(--muted); font-size: 14.5px; line-height: 1.75;
  padding: 0 20px 20px; padding-inline-start: 36px; text-wrap: pretty;
  max-inline-size: 68ch;
}
.faq-item p + p { padding-top: 12px; }

/* -------------------------------- forms -------------------------------- */
.form { display: grid; gap: 16px; }
/* minmax(0, …) rather than a bare 1fr: this row holds inputs whose min-content
   width is set by their placeholder, and a bare 1fr track refuses to shrink
   below that — the mechanism behind three separate narrow-viewport overflows
   in this file's history. */
.form-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; }
label { display: grid; gap: 8px; font-size: 13.5px; font-weight: 600; color: var(--muted); }
/* The control is recessed into its module rather than raised out of it: a
   darker ground than the panel around it and an inset top shadow, which is the
   inverse of the lip every raised object wears. A form field you can type into
   should look like a slot, and the whole surface grammar of this page depends
   on raised and recessed meaning two different things. */
input, select, textarea {
  background: rgba(0, 0, 0, 0.24); border: 1px solid var(--ring); border-radius: 8px;
  padding: 11px 13px; color: var(--text); font: 400 14.5px var(--font-text); width: 100%;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
  transition: border-color var(--t-ui) ease, box-shadow var(--t-ui) ease, background var(--t-ui) ease;
}
input:hover, select:hover:not(:disabled), textarea:hover { border-color: var(--ring-gold); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); background: rgba(0, 0, 0, 0.12);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35), 0 0 0 3px rgba(227, 179, 65, 0.16);
}
input::placeholder, textarea::placeholder { color: #5c5440; }

/* Custom animated <select> dropdowns (Chromium 135+). The picker slides/fades
   open, options get themed hover states and the chevron flips. Browsers
   without base-select support skip this block and keep the native picker. */
@supports (appearance: base-select) {
  select { appearance: base-select; }
  select:open { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(227, 179, 65, 0.14); }
  select::picker-icon { color: var(--accent); transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1); }
  select:open::picker-icon { transform: rotate(180deg); }
  select::picker(select) {
    appearance: base-select;
    background: var(--surface-2); border: 1px solid rgba(227, 179, 65, 0.35);
    border-radius: 12px; padding: 6px; margin-block-start: 6px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.6);
    opacity: 0; transform: translateY(-8px) scale(0.985); transform-origin: top;
    transition: opacity 0.2s ease, transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
                overlay 0.28s allow-discrete, display 0.28s allow-discrete;
  }
  select:open::picker(select) { opacity: 1; transform: none; }
  @starting-style {
    select:open::picker(select) { opacity: 0; transform: translateY(-8px) scale(0.985); }
  }
  select option {
    padding: 9px 12px; border-radius: 8px; font-size: 14px;
    background: transparent; color: var(--text);
    transition: background 0.15s ease, color 0.15s ease;
  }
  select option:hover, select option:focus { background: rgba(227, 179, 65, 0.12); color: var(--accent); }
  select option:checked { color: var(--accent); font-weight: 600; }
  select option::checkmark { color: var(--accent); }
}
.form-foot { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.form-status { font-size: 13.5px; font-weight: 600; }
.form-status.ok { color: var(--success); }
.form-status.err { color: var(--danger); }

/* recommended add-on checkbox */
.check {
  display: flex; gap: 12px; align-items: flex-start;
  background: rgba(227, 179, 65, 0.06); border: 1px solid rgba(227, 179, 65, 0.28);
  border-radius: 12px; padding: 14px 16px; cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}
.check:hover { border-color: rgba(227, 179, 65, 0.55); background: rgba(227, 179, 65, 0.09); }
.check input { width: 16px; height: 16px; margin-top: 3px; accent-color: var(--accent); flex: none; }
.check span { font-weight: 400; color: var(--muted); font-size: 13px; line-height: 1.5; }
.check b { color: var(--text); }
.check em { color: var(--accent); font-style: normal; font-weight: 600; }

/* -------------------------------- tools ---------------------------------
   "Instrument Black" — a CONTAINED design experiment. Everything below is
   scoped to #tools; no global token is touched, so the rest of the site keeps
   the design it has and the two can be judged side by side.

   Three ideas, and the reason each one is here:

   1. TRANSLUCENT NESTING, OPAQUE SHELL. The inner modules (metric tiles, the
      graph card, the mouse stage) are a white film over whatever is behind
      them rather than their own opaque colour, so their tint always agrees
      with their parent instead of patching over it. The shell — .tool-panel —
      stays opaque on purpose: this section carries a background photograph at
      0.13 opacity, and a translucent shell would put running copy and form
      labels on top of game art. Translucency expresses DEPTH here, never
      transparency to the page.

   2. A MACHINED EDGE, NOT A SHADOW. One inset hairline along the top of each
      module reads as a lit metal lip. A blurred drop shadow would read as
      "floating card", which is the SaaS look this experiment is trying to get
      away from. Deliberately 1px and not the 0.5px hairline the references
      use: <body> carries `zoom: var(--ui-scale)` (0.95), which would resolve
      0.5px to 0.475px and drop the line entirely on 1x displays.

   3. AN INSTRUMENT REGISTER. Small technical labels move to monospace,
      uppercase and tracked; values stay in the display face with tabular
      figures. The numbers are the brand, the labels are the instrument. */
/* ------------------------ the tools scrim -------------------------------
   #tools is the only section whose CONTENT is a form. Everywhere else the game
   photograph at 0.13 opacity sits behind prose, where it reads as atmosphere;
   behind a <select> it reads as dirt on the control. The section-wide answer
   would be to darken the whole band, which throws away the game imagery this
   brand is built on.

   So the scrim is LOCAL: an ellipse of page-ground centred on the tool panel,
   opaque at the middle and gone by the section edges, painted on top of the
   photograph and under the content. The art still frames the section; it just
   stops competing with the controls in the middle of it.

   Sits on .section-bg::after — which already carries the vertical fade — as a
   second background layer rather than a third element, so the stacking order
   is fixed by the shorthand and needs no z-index. */
#tools .section-bg::after {
  background:
    radial-gradient(76% 56% at 50% 64%, var(--bg-alt) 0%, rgba(13, 11, 7, 0.88) 46%, transparent 76%),
    linear-gradient(180deg, var(--bg-alt) 0%, rgba(13, 11, 7, 0.3) 30%, rgba(13, 11, 7, 0.3) 70%, var(--bg-alt) 100%);
}

/* ---------------------------- dark instrument glass ----------------------
   The shell was opaque --surface, on the argument that a translucent panel
   would put form labels on top of the game photograph behind the section.
   That argument was correct when it was written and is no longer true: the
   local scrim above lays near-solid page ground under the whole panel, so what
   a translucent shell actually composites against there is 88-100% --bg-alt,
   not the picture. Measured at the panel's own edge — the least covered point
   — the residual image is about 1.6% of one 0.13-opacity photograph.

   So the shell can be glass, and glass is the honest material for it: this is
   the one block on the page you reach INTO rather than read, and a panel with
   depth behind its controls says that in a way a flat rectangle cannot.

   WHAT KIND OF GLASS
   ------------------
   Dark instrument glass, not the iOS kind. The whole difference is which side
   the light comes from. Frosted white glass is lit from behind — it is a
   bright film you look through. This is lit from ABOVE and in front: a nearly
   opaque dark body, one warm highlight along the top edge where the light
   catches, a warm hairline around it, and a shadow underneath. That reads as a
   smoked panel in a rack. It is the same top-lit logic as the --lip the rest
   of the page uses, given a body to sit on.

   Hence the numbers, and none of them is a default:

     0.66 body      not 0.2. Above ~0.5 the panel is unmistakably a solid
                    object with depth behind it; below it, it is a film and
                    the copy inside starts to swim.
     10px blur      enough to defocus the ground into a soft field, not enough
                    to smear the section's artwork into grey mush. Excessive
                    blur is the single loudest tell of the style this is
                    avoiding.
     warm hairlines every border in here is gold-derived at low alpha, so the
                    edges are the brand's warm channel rather than the neutral
                    white the glass idiom defaults to.

   THE LADDER, in glass terms — three levels, each one darker as it recedes:

     SHELL    --tg-shell    the panel body; blurred, warm-edged, top-lit
     MODULE   --tg-module   a group seated in the shell; a lighter film on it
     RECESS   --tg-recess   a control milled into the module; darker than both

   Raised and recessed keep meaning what they mean everywhere else on the
   page. A field you type into goes DOWN. */
#tools {
  --tg-shell: rgba(10, 8, 5, 0.66);
  --tg-module: rgba(247, 243, 232, 0.045);
  --tg-recess: rgba(0, 0, 0, 0.46);
  --tg-edge: rgba(227, 179, 65, 0.17);
  --tg-edge-soft: rgba(227, 179, 65, 0.1);
  --tg-lip: inset 0 1px 0 rgba(255, 240, 205, 0.14);
  --tg-lip-soft: inset 0 1px 0 rgba(255, 240, 205, 0.07);
  /* The underside. A dark inset along the bottom edge is what gives a pane
     THICKNESS — without it a top highlight alone reads as a printed line. */
  --tg-under: inset 0 -1px 0 rgba(0, 0, 0, 0.55);
}
/* -------------------------- the instrument label -------------------------
   ONE rule for every technical caption on the page — metric names, state
   names, field labels, the hero's stat captions, the plan meta. Mono,
   uppercase, tracked, and always smaller than the value it names.

   This is the register that separates the two kinds of text the site has:
   things you READ (Manrope), and things a machine PRINTED (this). Numbers stay
   in the display face with tabular figures, because the number is the brand
   and the label is only the instrument around it.

   Every selector added here must also be added to the [dir="rtl"] tracking
   reset near the top of the file — uppercase + positive tracking is a Latin
   convention that breaks cursive Arabic. */
/* `label:not(.chip)` and not a bare `label > span`, because a .chip IS a
   <label> — `<label class="chip"><input><span>1080p</span></label>` — so the
   bare selector put every resolution, issue and goal chip into the register.
   On the three resolution chips that happened to look right; on "Overheating /
   loud fans" it produced a shouty uppercase mono sentence that is wider than
   the phrase it replaced, which is how a chip row overflows a 320px screen. */
.est-label, .mt-stat span, .perf-state, .rail-label,
.stat span, .tool-panel label:not(.chip) > span,
.plan-duration, .value-box-label {
  font-family: var(--mono);
  font-size: 11.5px; font-weight: 600; line-height: 1.35;
  text-transform: uppercase; letter-spacing: 0.085em;
  color: var(--muted);
}

/* ------------------------------ the mode rail ----------------------------
   Three separate rounded buttons with a gap between them are three separate
   controls that happen to be adjacent — which is why the previous pass still
   read as buttons even after the fill came off the selected one. A segmented
   control is ONE control with a position, and that is the true shape of this:
   the panel below never shows two tools at once.

   So the housing is now the bordered object, recessed into the page with an
   inset shadow like a slot milled into the deck, and the three tabs are
   segments seated in it with no border of their own. The selected segment is
   the one that is RAISED — lit film, lit lip, gold label, and a 2px lamp along
   its bottom edge. Raised-in-a-recess is the physical reading, and it is the
   same raised/recessed grammar the form fields use two blocks down.

   `width: fit-content` + `margin-inline: auto` rather than
   `justify-content: center` on a stretched flex row: the rail has to be as
   wide as its segments and no wider, or the recess is visible either side of
   them and it stops looking milled.

   Deliberately not naming the pressed attribute in this comment:
   tool-tab-aria.test.mjs greps the whole stylesheet for it to prove that no
   RULE selects on it, and prose would be indistinguishable from a selector. */
.tools-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  width: fit-content; max-width: 100%; margin: 0 auto 24px;
  padding: 4px; border-radius: 12px;
  /* The rail is milled out of the same pane as the panel below it, so it takes
     the recess ground and the warm hairline rather than a neutral one. */
  background: var(--tg-recess); border: 1px solid var(--tg-edge-soft);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.45);
}
.tool-tab {
  position: relative; flex: 1 1 auto; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 11px 18px; border-radius: 9px; border: 1px solid transparent;
  background: transparent; color: var(--muted); font: 600 14px var(--font-text);
  cursor: pointer;
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease,
              color var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
/* currentColor, so each icon follows its tab through muted → gold, with no
   per-state rule of its own. */
.tool-tab-ico { width: 16px; height: 16px; flex: none; }
/* Hover illuminates rather than lifts: the film thickens, nothing moves. */
.tool-tab:hover { background: var(--sf-1); color: var(--text); }
.tool-tab:active { transform: scale(0.98); }
/* Selected is RAISED and LIT, never filled. Solid gold is reserved for actions
   — Calculate, Book — so that across the whole page gold fill means "this does
   something" and gold light means "this is the one you are looking at".
   Colour is never the only channel: the raised film, the lit lip, the lamp and
   the label colour are four redundant signals over the pressed state the
   button itself carries. */
.tool-tab.active {
  background: var(--sf-2); color: var(--accent);
  border-color: var(--ring); box-shadow: var(--lip-lit);
}
.tool-tab.active::after {
  content: ""; position: absolute; inset-inline: 16px; bottom: 5px; height: 2px;
  border-radius: 2px; background: var(--accent-grad);
}

/* --------------------------------- the panel -----------------------------
   The pane. Body, blur, warm rim, lit top edge, dark underside, and a shadow
   on the deck below it — six declarations that together are the difference
   between "a translucent rectangle" and "a smoked panel in a rack".

   The order of the background layers is the whole trick. The highlight
   gradient is FIRST (it paints on top), the body last, so the light sits on
   the surface of the glass rather than being mixed into it. Reverse them and
   the panel goes cloudy. */
.tool-panel {
  max-width: 900px; margin: 0 auto; display: grid; gap: 20px;
  background:
    linear-gradient(180deg, rgba(247, 243, 232, 0.05), rgba(247, 243, 232, 0) 42%),
    var(--tg-shell);
  border: 1px solid var(--tg-edge);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(10px) saturate(1.04);
          backdrop-filter: blur(10px) saturate(1.04);
  box-shadow:
    var(--tg-lip),
    var(--tg-under),
    0 22px 54px rgba(0, 0, 0, 0.46);
}
/* No backdrop-filter, no glass: the panel falls back to a near-solid body so
   the copy inside never sits on an unblurred photograph. Firefox before 103
   and any engine with the filter disabled land here. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tool-panel { background: linear-gradient(180deg, rgba(247, 243, 232, 0.05), rgba(247, 243, 232, 0) 42%), rgba(10, 8, 5, 0.96); }
  .tools-tabs { background: rgba(0, 0, 0, 0.62); }
}
/* The shell is a container, not a card: it does not light up and it does not
   move. Overrides .card:hover, which would otherwise warm the ring of the
   entire form every time the pointer crossed it — a 900px-wide hover target
   reports nothing. Keeping --reveal-y in the transform preserves the reveal;
   writing `transform: none` here would out-rank `.reveal.visible` and kill it. */
#tools .tool-panel.card:hover {
  background:
    linear-gradient(180deg, rgba(247, 243, 232, 0.05), rgba(247, 243, 232, 0) 42%),
    var(--tg-shell);
  border-color: var(--tg-edge);
  box-shadow: var(--tg-lip), var(--tg-under), 0 22px 54px rgba(0, 0, 0, 0.46);
}

/* ------------------------------ field groups -----------------------------
   The CPU and GPU cascades are three <select>s each, and stacked bare they
   read as six unrelated dropdowns in a column. Boxing each cascade into its
   own recess makes the grouping structural rather than something the reader
   has to infer from the labels, and it is the one place in the form where the
   grouping carries real meaning: those three controls are one answer.

   No custom select anywhere. Every control here is the native element, keeps
   its native keyboard behaviour and its native picker on every browser that
   does not support ::picker() — the accessibility floor is not worth trading
   for a matching chevron. */
/* One level up from the shell, not down: a group seated ON the pane catches
   more light than the pane does, which is what makes the two read as separate
   pieces of material rather than one panel with a line drawn on it. The
   hairline is the soft warm one — the shell owns the bright rim, and a second
   equally bright edge 10px inside it would flatten both. */
.cascade {
  display: grid; gap: 7px; padding: 12px; border-radius: 10px;
  background:
    linear-gradient(180deg, rgba(247, 243, 232, 0.035), rgba(247, 243, 232, 0) 60%),
    var(--tg-module);
  border: 1px solid var(--tg-edge-soft);
  box-shadow: var(--tg-lip-soft);
}
.tool-panel form { display: grid; gap: 16px; }
.tool-intro { color: var(--muted); font-size: 14.5px; }
/* minmax(0, …) rather than a bare 1fr: these two columns hold <select>s whose
   min-content width is set by their longest option — some GPU model names are
   long — and a bare 1fr track refuses to shrink below that, which is how this
   file has produced narrow-viewport overflow before. Same reasoning as the
   metric grids below. */
.tool-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; }
/* -------------------------- the recessed controls ------------------------
   Scoped to #tools rather than edited on the global `input, select, textarea`
   rule: the booking modal and the contact form are not glass and their fields
   are already correct against the surfaces they sit on. Only the panel's own
   controls need to go deeper, because they are cut into a pane that is itself
   translucent — a field at the site-wide 0.24 black would read as roughly the
   same value as the module around it and the slot would disappear.

   Two inset shadows, and they do different jobs: the dark one at the top is
   the shadow the recess casts on itself, and the faint warm one at the bottom
   is the light that reaches the far wall of the slot. That pair is what makes
   a rectangle read as milled rather than merely painted darker. */
#tools :is(input, select, textarea) {
  background: var(--tg-recess);
  border-color: rgba(247, 243, 232, 0.1);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.5),
    inset 0 -1px 0 rgba(247, 243, 232, 0.035);
}
#tools :is(input, select, textarea):hover:not(:disabled) { border-color: var(--ring-gold); }
/* Focus is the one state that gets real gold: it is where the reader IS. The
   ring is the site-wide one, restated here only because the box-shadow above
   would otherwise wipe it. */
#tools :is(input, select, textarea):focus {
  border-color: var(--accent);
  background: rgba(0, 0, 0, 0.34);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.5),
    0 0 0 3px rgba(227, 179, 65, 0.16);
}
.cascade select:disabled { opacity: 0.5; cursor: not-allowed; }
.chips-block { display: grid; gap: 10px; }
/* Mono like the field labels, but never uppercase: half of these are questions
   with parentheses in them, and the register's Latin conventions stop being
   readable the moment a label becomes a sentence. */
.chips-label { font: 600 12.5px/1.4 var(--mono); color: var(--muted); }
.chips-label { font-size: 13.5px; font-weight: 600; color: var(--muted); }
.chips { display: flex; gap: 10px; flex-wrap: wrap; }
.chip { display: inline-flex; position: relative; cursor: pointer; }
.chip input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.chip span {
  padding: 9px 15px; border-radius: 999px; border: 1px solid var(--tg-edge-soft);
  background: var(--tg-recess); color: var(--muted);
  /* Named explicitly, not inherited: these carry translated phrases, not
     labels, and the instrument register's uppercase + tracking is a Latin
     convention that makes a phrase both louder and wider. */
  font: 600 13.5px var(--font-text); text-transform: none; letter-spacing: normal;
  transition: background var(--t-ui), border-color var(--t-ui), color var(--t-ui); pointer-events: none;
}
/* Chips keep the gold FILL when checked where the tabs gave it up: they are
   ~40px wide radio buttons inside a form, where a positive fill is the clearer
   affordance, and at that size the gold costs the section almost no area. */
.chip:hover span { border-color: var(--ring-gold); background: var(--tg-module); color: var(--text); }
.chip:active span { transform: scale(0.94); }
/* The real control is a radio/checkbox laid over the whole chip at opacity 0,
   so the global :focus-visible outline lands on an invisible element and a
   keyboard user tabbing through the resolution, issue and goal chips saw
   nothing at all. Found while auditing focus for this pass; it predates it.

   :has() rather than a sibling selector because the input is the FIRST child
   and the visible span the second — and the sheet already selects the checked
   state this way one line down, so the two states stay the same shape. */
.chip:has(input:focus-visible) span { outline: 2px solid var(--accent); outline-offset: 2px; }
.chip:has(input:checked) span { background: var(--accent-grad); color: var(--on-gold); border-color: transparent; animation: pressPop 0.3s ease; }

/* ------------------- mouse polling rate test (embedded) ------------------ */
.mt-big { text-align: center; display: grid; gap: 4px; }
.mt-big-val { font-family: var(--font-head); font-size: 62px; font-weight: 700; color: var(--accent); line-height: 1; }
.mt-big-val small { font-size: 22px; color: var(--muted); font-weight: 600; }
.mt-big-sub { color: var(--faint); font-size: 13px; }
.mt-stage {
  min-height: 240px; border: 1px dashed rgba(227, 179, 65, 0.35); border-radius: 14px;
  background: var(--sf-1); box-shadow: var(--lip);
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 20px; cursor: crosshair; touch-action: none; user-select: none;
  transition: border-color var(--t-ui), background var(--t-ui), box-shadow var(--t-ui);
}
/* Armed is a measuring state, so gold: the ring solidifies and the panel picks
   up an inner glow, like a bench instrument that has been switched to live. */
.mt-stage.armed { border-color: var(--accent); background: rgba(227, 179, 65, 0.05); box-shadow: var(--lip), 0 0 24px rgba(227, 179, 65, 0.08) inset; }
.mt-hint { color: var(--muted); font-size: 14px; max-width: 440px; }
.mt-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
/* .mt-stat and .est-box are the same object — one metric tile — so they share
   every rule below. Start-aligned, not centred: a column of readings lines up
   on its labels the way a datasheet does, and centring three tiles of unequal
   value length makes the numbers wander. */
.mt-stat, .est-box {
  background: var(--sf-1); border: 1px solid var(--ring); border-radius: 12px;
  box-shadow: var(--lip);
  padding: 12px 14px; display: grid; gap: 3px; text-align: start;
  transition: background var(--t-ui), border-color var(--t-ui);
}
.mt-stat b, .est-box b { font-family: var(--font-head); font-size: 22px; color: var(--text); line-height: 1.15; }
/* The live reading is the one number that IS a measurement in progress, so it
   is the one that gets gold. Max and min are context, and stay neutral. */
.mt-stat.hot b { color: var(--accent); }
.mt-controls { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.mt-controls .btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.mt-graph-card { border: 1px solid var(--ring); border-radius: 14px; background: var(--sf-1); box-shadow: var(--lip); padding: 14px 16px; display: grid; gap: 10px; }
.mt-graph-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
/* h3, not h4. The only heading inside the mouse panel used to be an <h4>
   under the section's <h2> with no <h3> anywhere between them — a heading-level
   skip, which is a real defect for anyone navigating by headings and invisible
   in every visual check. The size is unchanged; only the level moved. */
.mt-graph-head h3 { font-size: 14px; }
.mt-graph-head span { color: var(--faint); font-size: 12px; }
.mt-graph { width: 100%; height: 170px; display: block; }
.mt-note { color: var(--muted); font-size: 13.5px; line-height: 1.7; border-top: 1px solid var(--border); padding-top: 16px; }

/* ------------------------------- the readout -----------------------------
   INPUT -> ANALYSIS -> RESULT, expressed structurally rather than with three
   labels nobody would need translating.

   The form above is RECESSED: dark slots, quiet rings, no gold anywhere except
   the one button that runs the calculation. The readout is RAISED: it steps a
   level up out of the panel onto --sf-2, takes a lit lip, and is capped by a
   full-width gold rule. Everything above the rule is a question; everything
   below it is an answer, and the surface says so before a word is read.

   The rule is the analysis line. It is symmetric, so it needs no RTL twin, and
   it is a real border-image rather than a pseudo-element so that it cannot
   collide with the `role="status"` live region the panel already carries.

   Nothing here invents a figure. Every value in the block below still comes
   straight off /api/bottleneck or /api/discover — the presentation changed and
   the numbers did not. The language is deliberately precise about which is
   which: the tiles are labelled ESTIMATED because they are computed from the
   hardware the visitor picked, not measured on it. */
.tool-result {
  position: relative;
  margin-top: 4px; padding: 24px; display: grid; gap: 16px;
  border-radius: 12px; border: 1px solid var(--ring);
  background: var(--sf-2);
  box-shadow: var(--lip-lit);
}
/* The analysis line, as a pseudo-element rather than a border-box gradient.

   The first draft used the two-layer background trick — an inner fill clipped
   to padding-box over a gradient clipped to border-box — which is the standard
   way to paint a gradient border. It CANNOT work here, and the reason is worth
   recording: that technique needs the inner fill to be OPAQUE, because it works
   by hiding the gradient everywhere except under the transparent border. Every
   surface in this design is a translucent film, so --sf-2 at 6% alpha hid
   nothing: the gold gradient showed through the entire panel and the readout
   rendered as a solid gold block with unreadable text on it. Caught in a
   screenshot, invisible to every DOM assertion in this repo.

   The pseudo-element is also the better answer on its own terms — it is the
   identical construction .plan.featured::before and .value-box-win::before
   already use, so the three lit edges on the page are one mechanism instead of
   two. Symmetric ramp, so no RTL twin. */
.tool-result::before {
  content: ""; position: absolute; inset-inline: 20px; top: -1px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent) 18%, var(--accent) 82%, transparent);
}
/* The headline reading gets the whole first line to itself, with the title and
   the severity chip beneath it. Baseline-aligning a 44px figure against 18px
   of text put the chip halfway up the number and left the title stranded on a
   second line at any width narrower than about 560px. */
/* --------------------------- the scorecard -------------------------------
   The result had every part it needed and no relationships between them: a
   figure, a title, a chip, a gauge, a spec line, a paragraph and three tiles,
   each one a sibling of the last, spaced 16px apart by the panel's grid gap.
   Read from a distance it was a list of eight things, which is what made a
   real diagnostic output look like a generic dashboard.

   Three bands, separated by hairlines rather than by boxes:

     VERDICT   state, figure + severity, gauge, spec.
               These four are the answer, so they are grouped tight (6px, not
               16) and the gauge sits directly under the number it plots — a
               gauge with a paragraph between it and its value is a decoration.
     READING   the advice.
     READINGS  the three estimates, under a label that names them as estimates
               before the reader gets to them.

   Nothing is nested. Every band is a hairline on the panel's own surface,
   which is the same construction as the FAQ list and the metric strip, and it
   is what keeps this from being a card inside a card. */
.result-verdict { display: grid; gap: 10px; justify-items: start; }
.result-figure {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
/* The state, in the instrument register, with the lamp every live reading on
   this page wears. It is gold because it IS the result — the one thing the
   calculator was asked for. */
.result-state {
  display: flex; align-items: center; gap: 9px;
  color: var(--accent); font: 700 11.5px var(--mono);
  text-transform: uppercase; letter-spacing: 0.11em;
}
.result-state::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 3px rgba(227, 179, 65, 0.14);
}
.result-big {
  font-family: var(--font-head); font-size: clamp(40px, 6vw, 54px); font-weight: 700;
  color: var(--accent); line-height: 1; letter-spacing: -0.02em;
}
/* The gauge spans the verdict band rather than sitting at its content width,
   so the fill is read against the whole panel and the percentage above it has
   a scale to mean something on. */
.result-verdict .meter { justify-self: stretch; width: 100%; }
/* The hardware line is the caption on the reading, not a paragraph: mono,
   dim, and tight under the gauge. */
.result-spec {
  color: var(--faint); font: 400 11.5px/1.55 var(--mono);
}
/* The bands. A hairline and the panel's own ground — no fill, no radius, no
   second border. */
.result-band {
  display: grid; gap: 12px; justify-items: start;
  padding-top: 16px; border-top: 1px solid var(--ring);
}
.result-readings .est-grid, .result-readings .result-sub { justify-self: stretch; width: 100%; }
.result-title { font-size: 17px; font-weight: 700; }
.result-verdict .sev { justify-self: start; }
/* 4px, not 999. Every tag on this page takes the housing's own radius —
   the plan flag, the readout strip, the discount badge — and a lozenge
   sitting beside a mono state label and a milled gauge was the one pill
   left in the scorecard. */
.sev { padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em; font-family: var(--mono); }
.sev.ok, .sev.normal { background: rgba(52, 211, 153, 0.15); color: var(--success); }
.sev.moderate, .sev.high { background: rgba(227, 179, 65, 0.15); color: var(--accent); }
.sev.severe { background: rgba(248, 113, 113, 0.15); color: var(--danger); }
/* Gauge: a channel milled into the panel, with a scale printed on it. The
   ticks are a repeating-linear-gradient on the track rather than ten elements,
   they sit at every 10% by construction, and they are what turns a progress
   bar into something you can read a value off. The fill covers them as it
   travels, which is exactly what a filled gauge does.

   `background-clip` is not needed: the ticks are drawn INSIDE the padding box
   and the ring is an inset shadow, so nothing bleeds past the radius. */
.meter {
  position: relative; height: 12px; border-radius: 6px; overflow: hidden;
  background:
    repeating-linear-gradient(90deg, var(--ring) 0 1px, transparent 1px 10%),
    rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 0 0 1px var(--ring), inset 0 1px 2px rgba(0, 0, 0, 0.4);
}
.meter i {
  display: block; height: 100%; background: var(--accent-grad); border-radius: 6px;
  box-shadow: inset 0 1px 0 rgba(255, 246, 219, 0.4);
  transition: width var(--t-sec) var(--ease);
}
.meter.warn i { background: linear-gradient(135deg, #ffd75e, #ef4444); }
.result-sub { color: var(--faint); font-size: 12.5px; }
.result-advice { color: var(--muted); font-size: 14.5px; }
.ram-note {
  color: var(--accent); background: rgba(227, 179, 65, 0.07);
  border: 1px solid rgba(227, 179, 65, 0.3); border-radius: 10px; padding: 10px 14px; font-size: 13.5px;
}
/* Kept gold, deliberately: this is the one block in the results that carries
   an action, and gold-as-action is the top of the grammar. */
.rec-card {
  display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap;
  border: 1px solid rgba(227, 179, 65, 0.45); background: rgba(247, 221, 138, 0.05);
  border-radius: 14px; padding: 18px 20px; box-shadow: var(--lip);
}
.rec-name { font-family: var(--font-head); font-size: 21px; font-weight: 700; }
.rec-price { color: var(--accent); font-weight: 700; font-size: 16px; }
.rec-fresh { color: var(--success); font-size: 13px; font-weight: 600; margin-top: 4px; }
/* ---------------------- measured output (metric tiles) -------------------
   Label / value / unit, in that reading order. The unit is split out of the
   value string so it can sit in the instrument register at its own size: the
   number is the reading, the unit is the scale it is read against, and running
   them together in one display-face string makes the scale look like part of
   the figure.

   The values are NEUTRAL here, and that is the point of the whole gold pass.
   Every one of these tiles used to be gold, which meant gold said nothing —
   an estimate of the machine as it stands today is not an achievement. The
   gold is spent one block down instead, on the tuned row, where it means
   "this is the state we move you to". */
.est-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
/* Value and unit sit in normal INLINE flow, not a flex row, and the space
   between them is a real space in the markup. A flex row with `gap` looked
   identical but put no space in the text, so the accessible name for the frame
   time collapsed to "~34.5ms" — one token, at the mercy of whichever screen
   reader happened to split it. Inline flow also gets baseline alignment and
   wrapping for free, which is all the flex was buying. */
.est-unit { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--faint); }

/* ------------------------- before / after readout ------------------------
   Two stacked rows on a shared column grid, replacing the old
   column | arrow | column. Three reasons it reads better here:

     - the same metric now sits in the same column in both rows, so the
       comparison is a vertical glance rather than a horizontal one;
     - the arrow is gone. It was a bare "→" text node with no aria-hidden, so
       a screen reader announced it between the two sets of figures, and it
       needed a scaleX(-1) flip under RTL. The two state labels already carry
       that meaning, in every language;
     - it collapses to narrow screens without a rotated glyph.

   The tuned row is the lit one: a gold rule along its top edge, gold values,
   gold state label. Colour is never the only channel — the row is labelled. */
.perf-compare { display: grid; gap: 10px; }
.perf-row {
  position: relative;
  /* The state column is a PERCENTAGE, and that is the whole trick. Each row is
     its own grid, so an `auto` track sized itself to its own label: measured,
     "Now (estimated)" gave 115.7px and "After tuning (estimated)" gave 185.1px,
     the two rows' metric tiles landed 22px out of step, and the vertical
     comparison this layout exists for did not survive its own copy. A
     percentage resolves against the grid container, which both rows share, so
     the tracks are identical by construction — in every language, without
     subgrid, and without a hardcoded width that the next translation breaks.
     The clamp keeps a long label from eating the readings and a short one from
     leaving the state unreadable. */
  display: grid; grid-template-columns: clamp(88px, 18%, 150px) minmax(0, 1fr);
  gap: 8px 16px; align-items: center;
  padding: 14px 16px; border-radius: 14px;
  border: 1px solid var(--ring); background: var(--sf-1);
  box-shadow: var(--lip);
}
.perf-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
/* One level deeper than the row, so the tiles step up instead of repeating the
   row's own film — depth reads as depth. */
.perf-row .est-box { background: var(--sf-2); }
.perf-row.is-after { border-color: var(--ring-gold); }
.perf-row.is-after::before {
  content: ""; position: absolute; inset-inline: 16px; top: -1px; height: 2px; border-radius: 2px;
  /* Symmetric ramp, so it needs no RTL twin. */
  background: linear-gradient(90deg, transparent, var(--accent) 20%, var(--accent) 80%, transparent);
}
.perf-row.is-after .perf-state { color: var(--accent); }
.perf-row.is-after .est-box { border-color: rgba(227, 179, 65, 0.3); }
.perf-row.is-after .est-box b { color: var(--accent); }

/* booking tracking */
.track-card { margin-top: 18px; }
.track-card h3 { font-size: 19px; margin-bottom: 4px; }
.track-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 15px; }
.badge-track { padding: 4px 12px; border-radius: 999px; font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px; }
.badge-track.new { background: rgba(227, 179, 65, 0.15); color: var(--accent); }
.badge-track.confirmed { background: rgba(96, 165, 250, 0.15); color: #60a5fa; }
.badge-track.completed { background: rgba(52, 211, 153, 0.15); color: var(--success); }
.badge-track.cancelled { background: rgba(248, 113, 113, 0.15); color: var(--danger); }
#trackResult { border-top: 1px solid var(--border); padding-top: 16px; display: grid; gap: 10px; }
.track-timeline { list-style: none; display: grid; gap: 8px; }
.track-timeline li {
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  font-size: 13px; padding-inline-start: 18px; position: relative;
}
.track-timeline li::before { content: ""; position: absolute; inset-inline-start: 0; top: 6px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-grad); }
.track-timeline li span { color: var(--faint); }

/* booking modal extras */
.tz-line { font-size: 13px; color: var(--muted); }
.tz-line b { color: var(--accent); }
.check-priority { border-color: rgba(248, 113, 113, 0.35); background: rgba(248, 113, 113, 0.05); }
.check-priority em { color: #f87171; }

/* Booking total. Label/amount pairs held apart by space-between, which follows
   the writing direction on its own — so Arabic and Hebrew mirror correctly
   without a single physical margin or a [dir="rtl"] override. Tabular figures
   keep the amounts on one optical column as they change, and nowrap stops a
   price breaking across lines on a narrow phone. */
.book-total {
  display: grid; gap: 7px;
  padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface-2);
}
.bt-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 13.5px; color: var(--muted); }
.bt-row b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bt-sum { border-top: 1px solid var(--border); padding-top: 8px; font-size: 15px; color: var(--text); font-weight: 600; }
.bt-sum b { color: var(--accent); font-size: 17px; }

/* footer social icons — springy lift + sheen sweep + brand-color glow on hover */
.socials { display: flex; gap: 10px; flex-wrap: wrap; }
/* .footer prefix outranks the later generic ".footer-cols a { display:block }" rule */
.footer .socials a {
  position: relative; overflow: hidden; will-change: transform;
  width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
  padding: 0; background: var(--surface); border: 1px solid var(--border);
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.25s, background 0.25s, box-shadow 0.3s;
}
/* light sheen that sweeps across on hover */
.footer .socials a::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(115deg, transparent 42%, rgba(255, 255, 255, 0.16) 50%, transparent 58%);
  transform: translateX(-130%); transition: transform 0.6s ease;
}
.footer .socials a:hover::before { transform: translateX(130%); }
.socials a svg {
  fill: var(--muted); display: block; position: relative; z-index: 2;
  transition: fill 0.25s, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.socials .s-discord svg { width: 23px; height: 23px; }
.socials .s-yt svg { width: 21px; height: 21px; }
.socials .s-x svg { width: 17px; height: 17px; }
.socials .s-mail svg { width: 19px; height: 19px; }
.socials .s-ig svg { width: 20px; height: 20px; }
.socials .s-fb svg { width: 20px; height: 20px; }
.socials .s-tt svg { width: 19px; height: 19px; }
.socials a:hover { transform: translateY(-5px) scale(1.08); border-color: var(--accent); }
.socials a:hover svg { transform: scale(1.16); }
.socials a:active { transform: translateY(-2px) scale(0.97); transition-duration: 0.1s; }
.socials a:active svg { transform: scale(1.02); }
/* per-brand: border tint + soft glow + icon color */
.socials .s-discord:hover { border-color: #5865f2; background: rgba(88, 101, 242, 0.14); box-shadow: 0 10px 26px rgba(88, 101, 242, 0.4); }
.socials .s-discord:hover svg { fill: #5865f2; }
.socials .s-yt:hover { border-color: #ff3333; background: rgba(255, 51, 51, 0.12); box-shadow: 0 10px 26px rgba(255, 51, 51, 0.38); }
.socials .s-yt:hover svg { fill: #ff3333; }
.socials .s-x:hover { border-color: #f7f3e8; background: rgba(247, 243, 232, 0.1); box-shadow: 0 10px 26px rgba(247, 243, 232, 0.16); }
.socials .s-x:hover svg { fill: #f7f3e8; }
.socials .s-mail:hover { border-color: var(--accent); background: rgba(227, 179, 65, 0.12); box-shadow: 0 10px 26px rgba(227, 179, 65, 0.35); }
.socials .s-mail:hover svg { fill: var(--accent); }
.socials .s-ig:hover { border-color: #e4405f; background: rgba(228, 64, 95, 0.12); box-shadow: 0 10px 26px rgba(228, 64, 95, 0.4); }
.socials .s-ig:hover svg { fill: #e4405f; }
.socials .s-fb:hover { border-color: #1877f2; background: rgba(24, 119, 242, 0.14); box-shadow: 0 10px 26px rgba(24, 119, 242, 0.42); }
.socials .s-fb:hover svg { fill: #1877f2; }
.socials .s-tt:hover { border-color: #25f4ee; background: rgba(37, 244, 238, 0.12); box-shadow: 0 10px 26px rgba(37, 244, 238, 0.34); }
.socials .s-tt:hover svg { fill: #25f4ee; }
@media (prefers-reduced-motion: reduce) {
  .footer .socials a, .socials a svg { transition: border-color 0.2s, background 0.2s, fill 0.2s; }
  .footer .socials a:hover { transform: none; }
  .socials a:hover svg { transform: none; }
  .footer .socials a::before { display: none; }
}

.reason-list { list-style: none; display: grid; gap: 8px; margin-top: 8px; }
.reason-list li { color: var(--muted); font-size: 13.5px; padding-inline-start: 24px; position: relative; }
.reason-list li::before { content: "→"; position: absolute; inset-inline-start: 0; color: var(--accent); }
[dir="rtl"] .reason-list li::before { content: "←"; }

/* -------------------------------- footer ------------------------------- */
.footer { border-top: 1px solid var(--border); background: var(--bg-alt); padding-top: 56px; }
.footer-inner { display: grid; grid-template-columns: 1.2fr 2fr; gap: 48px; padding-bottom: 44px; }
.footer-brand p { color: var(--muted); font-size: 14px; margin-top: 16px; }
/* Three columns whenever three fit, fewer when they don't. A fixed
   `repeat(3, 1fr)` cannot wrap and cannot shrink past its own min-content, so on
   a narrow container the row grew wider than the page instead — a translation
   with longer words was enough to trigger it.

   The floor is the widest thing a column must hold, and that is not always text.
   90px covered the widest column min-content across the four languages, but
   Community also holds two 42px social buttons either side of their 10px gap,
   which is 94px. At a 375px viewport three tracks came out 93px wide — one pixel
   short — and all seven icons stacked one per row. 96px clears the buttons with
   2px against sub-pixel track rounding, and only moves the wrap point for
   366-383px viewports, which drop to two wider tracks; every other width keeps
   the track count it already had. Spare tracks still collapse to 0 at desktop. */
.footer-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 24px; }
.footer-cols h4 { font-size: 13px; text-transform: uppercase; letter-spacing: 1.5px; color: var(--text); margin-bottom: 14px; }
.footer-cols a { display: block; color: var(--muted); font-size: 14px; padding: 5px 0; transition: color 0.2s, transform 0.2s; }
.footer-cols a:hover { color: var(--accent); transform: translateX(4px); }
[dir="rtl"] .footer-cols a:hover { transform: translateX(-4px); }
.footer-base {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  border-top: 1px solid var(--border); padding-top: 20px; padding-bottom: 24px;
  color: var(--faint); font-size: 13px;
}

/* ------------------------- floating utilities ----------------------------
   Two gold-filled circles with coloured glows, parked over the content at the
   same weight as the page's primary action. Neither of them is a primary
   action: one scrolls you up and one opens an FAQ bot.

   Both are re-cut as UTILITIES on the same rules:

     - the fill goes. They take the recessed dark surface and hairline the
       rest of the site's secondary controls wear, with the gold spent only on
       the glyph;
     - the coloured drop shadow goes, replaced by the ordinary lip;
     - they come forward on hover and focus-visible — gold edge, lit lip —
       which is the same feedback every other control here gives;
     - the visible circle shrinks, and min-inline-size / min-block-size hold
       the INTERACTIVE box at 44px so the touch target does not follow it down.

   The stack keeps 14px of air between the two, measured against the toggle's
   own 52px rather than assumed. */
/* One shared token block, so the two controls cannot drift apart again. They
   are the same object at two sizes: a near-black puck, a hairline, a gold
   glyph at reduced strength, and a warm rim that only appears when the pointer
   or the keyboard is on it.

   Darker than the last pass by a wide margin. At rgba(14,12,8,0.86) over the
   page's own #070604 the puck was measurably LIGHTER than the ground it
   floated on, which is why it read as a chip stuck to the screen rather than a
   control recessed into the corner — a floating utility over a near-black page
   has to be at or below the page's value or it announces itself. 0.9 alpha of
   a colour darker than --bg lands just under it.

   The blur stays but comes down to 4px: at 6 it was smearing the game
   photography behind the corner into a grey smudge that moved with the scroll,
   which is more attention, not less. */
.to-top, .bot-toggle {
  --fab-bg: rgba(6, 5, 3, 0.9);
  --fab-ring: rgba(247, 243, 232, 0.1);
  --fab-ring-lit: rgba(227, 179, 65, 0.5);
  /* The glyph at rest. Gold, but held back from full: present enough that the
     pair reads as part of the brand, quiet enough that neither competes with
     an actual gold CTA scrolling past behind them.

     0.72 and not lower, and the figure is measured rather than picked. The
     glyph composites to #a58330 over the puck's own #060503, which is 5.6:1 —
     comfortably over the 4.5:1 that the back-to-top arrow needs as a text
     character, and well over the 3:1 the chat glyph needs as a graphic. At
     0.62, where this started, the same arrow measured 4.36:1 and failed. */
  --fab-ink: rgba(227, 179, 65, 0.72);
  position: fixed; z-index: 60;
  display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  background: var(--fab-bg);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  border: 1px solid var(--fab-ring);
  color: var(--fab-ink);
  box-shadow: var(--lip), 0 6px 18px rgba(0, 0, 0, 0.5);
  transition: color var(--t-ui) ease, border-color var(--t-ui) ease,
              background var(--t-ui) ease, box-shadow var(--t-ui) ease;
}
/* Forward on hover and focus, and by LIGHT rather than by movement. The
   translateY(-2px) both controls used to make was the only lift left on a page
   that spent this whole design pass removing them — and on a fixed element
   parked over scrolling content it reads as a jitter, not a response. */
.to-top:hover, .to-top:focus-visible,
.bot-toggle:hover, .bot-toggle:focus-visible {
  color: var(--accent);
  border-color: var(--fab-ring-lit);
  background: rgba(16, 13, 8, 0.94);
  box-shadow: var(--lip-lit), 0 8px 22px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(227, 179, 65, 0.1);
}
/* Both pucks come up one step. The last pass cut them from gold-filled discs
   to dark utilities and took the diameter down with the fill, which was one
   move too many: at 42 and 46 over a 1440px viewport they read as afterthoughts
   parked in the corner rather than as controls. 48 and 52 give them presence
   without giving them weight — the surface, the ring and the held-back gold
   glyph are all unchanged, so they are still secondary to anything gold
   scrolling past behind them.

   The visible circle and the INTERACTIVE box are now the same 48px, so the
   min-*-size pair is holding the floor rather than inflating a smaller puck. */
.to-top {
  /* 98 and not 92. The gap between the two pucks is measured, not assumed:
     the toggle is 52 tall and sits 24 from the bottom, so its top edge is at
     76 and 98 leaves the same 22px of air the pair had before. */
  bottom: 98px; inset-inline-end: 26px;
  inline-size: 48px; block-size: 48px;
  min-inline-size: 48px; min-block-size: 48px;
  font-size: 17px; font-weight: 700;
}

/* -------------------------------- modal -------------------------------- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  background: rgba(4, 3, 2, 0.82); backdrop-filter: blur(6px); padding: 20px;
}
.modal {
  position: relative; width: 100%; max-width: 560px; max-height: 90vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
  padding: 32px; box-shadow: var(--shadow);
}
/* Desktop-sized gutters cost 104px of a 320px screen — more than a third of it,
   on the one dialog that has to hold a fixed-width reCAPTCHA. Trimmed rather
   than removed: 20px inline still clears the close button and keeps the fields
   comfortable to tap.
   The cut-off is 480 rather than a phone-sized number on purpose. With the full
   32px gutters the 304px widget needs a 421px viewport to fit, so 421-480 was
   landing within a scrollbar's width of overflowing — narrow enough to break on
   a platform whose classic scrollbar is wider than the 11px measured here. */
@media (max-width: 480px) {
  .modal-backdrop { padding: 10px; }
  .modal { padding: 26px 20px; }
}
/* The close button is absolutely positioned out of flow, so the title's line box
   runs underneath it. Hebrew — the longest title — cleared the button by 6px at
   320px, and a 10% text enlargement was enough to put glyphs on top of it.
   36px = the button (36) plus its inset (16), less the 20px inline padding this
   modal has below 480px, plus a 4px gap. It reserves rather than pushes: a title
   that already fits keeps its exact position, and one that grows wraps at the
   button's edge instead of crossing it. Logical, so RTL reserves on the left. */
.modal h3 { font-size: 22px; padding-inline-end: 36px; }
.modal-sub { color: var(--muted); font-size: 14px; margin: 6px 0 22px; }
.modal form { display: grid; gap: 16px; }
.modal-close {
  position: absolute; top: 16px; inset-inline-end: 16px; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--muted); border-radius: 10px;
  width: 36px; height: 36px; cursor: pointer; font-size: 14px;
}
.modal-close:hover { color: var(--text); border-color: var(--accent); }
.booking-success { text-align: center; padding: 24px 0 8px; display: grid; gap: 12px; justify-items: center; }
.success-ico { font-size: 44px; }

/* ------------------------------- promo bar ------------------------------ */
.promo {
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  /* Logical, not physical. The 44px is the gap reserved for .promo-x, which is
     placed with inset-inline-end — so in RTL the button moves to the left while
     a physical `padding-right: 44px` left the reserve on the right, and the X
     sat on top of the Arabic/Hebrew text. Identical rendering in LTR. */
  padding-block: 9px; padding-inline: 16px 44px; position: relative;
  background: var(--accent-grad); color: var(--on-gold);
  font-size: 13.5px; font-weight: 700;
}
.promo-cta {
  display: inline-block; background: var(--on-gold); color: var(--accent); border: none; cursor: pointer;
  padding: 5px 14px; border-radius: 999px; font: 700 12.5px var(--font-text); transition: 0.2s;
}
.promo-cta:hover { transform: translateY(-1px); }
/* The glyph stays 14px; the *target* is 24px square (WCAG 2.5.8) — it measured
   11x19 before, which is a fiddly thing to hit on a phone. The inline-end inset
   drops from 10px to 4px so the larger box keeps the X within half a pixel of
   where it has always sat: previously centred 15.5px from the edge (10 + 11/2),
   now 16px (4 + 24/2). */
.promo-x {
  position: absolute; inset-inline-end: 4px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; line-height: 1;
  background: none; border: none; color: var(--on-gold); font-size: 14px; cursor: pointer; opacity: 0.7;
}
.promo-x:hover { opacity: 1; }

/* ------------------------------ contact alt ----------------------------- */
.contact-alt { text-align: center; margin-top: 18px; color: var(--muted); font-size: 14px; }
.contact-alt a { color: var(--accent); font-weight: 600; }
.contact-alt a:hover { text-decoration: underline; }
.contact-sep { margin: 0 8px; color: var(--faint); }

/* ------------------------------ cursor glow ----------------------------- */
.cursor-glow {
  position: fixed; top: 0; left: 0; width: 400px; height: 400px;
  pointer-events: none; z-index: 1;
  background: radial-gradient(circle, rgba(227, 179, 65, 0.07), transparent 65%);
  will-change: transform;
}

/* --------------------------- payment return banner ---------------------- */
.pay-banner {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
  z-index: 120; max-width: min(560px, calc(100vw - 32px));
  display: flex; align-items: center; gap: 14px;
  padding: 14px 18px; border-radius: 14px; box-shadow: var(--shadow);
  font-size: 14.5px; font-weight: 600;
}
.pay-banner.ok { background: rgba(52, 211, 153, 0.16); border: 1px solid var(--success); color: #d8ffe9; }
.pay-banner.err { background: rgba(248, 113, 113, 0.16); border: 1px solid var(--danger); color: #ffe1e1; }
.pay-x { background: none; border: none; color: inherit; cursor: pointer; font-size: 15px; opacity: 0.7; }
.pay-x:hover { opacity: 1; }

/* -------------------------------- chatbot ------------------------------- */
/* 24px from the corner and 52px tall, so the back-to-top puck's `bottom: 98px`
   leaves exactly 22px of air between the two — measured against the toggle's
   real height rather than assumed. Change either number and the other one has
   to move with it. */
.bot { position: fixed; bottom: 24px; inset-inline-end: 24px; z-index: 90; }
/* Everything this control looks like is declared in the shared `.to-top,
   .bot-toggle` block up in "floating utilities" — this is only its geometry
   and the two states that belong to it alone.

   `position: relative` and not the shared `fixed`: the puck is positioned by
   its `.bot` wrapper, which is the fixed element, because the panel has to
   anchor to the same corner. The declaration order matters — this rule is
   later in the file than the shared one, so it wins. */
.bot-toggle {
  position: relative;
  inline-size: 52px; block-size: 52px;
}
/* The glyph comes up with the puck. The <svg> carries width/height attributes
   of 24 in the markup; CSS out-ranks presentational attributes, so this is the
   one place the size is set. 26 keeps the same ~half-of-diameter proportion
   the 24-on-46 pair had. */
.bot-toggle svg { width: 26px; height: 26px; transition: transform 0.25s ease; }
.bot-toggle.open svg { transform: rotate(-14deg) scale(0.9); }
/* Smaller, and the halo's travel is halved. It still reads as an unread
   marker; it no longer pulses a 31px yellow ring over the content every 1.6
   seconds on a control that is not a primary action. */
.bot-ping {
  position: absolute; top: 1px; inset-inline-end: 1px;
  width: 7px; height: 7px; border-radius: 50%;
  /* Its ring is the puck's own ground, not --bg: the marker is punched out of
     the control it sits on, and against the darker puck a --bg ring showed as
     a lighter halo around the dot.

     Held at 0.86 rather than full --accent, and the reason is relative: once
     the puck went to a near-black ground with a 0.72 glyph, a 100% gold dot
     was the brightest object in that corner of the viewport — louder than the
     control it is attached to, which inverts what the two are for. It is still
     the only saturated thing there, so it still reads instantly. */
  background: rgba(227, 179, 65, 0.86); box-shadow: 0 0 0 2px rgba(6, 5, 3, 0.9);
}
.bot-ping::after {
  content: ""; position: absolute; inset: -1px; border-radius: 50%;
  background: rgba(227, 179, 65, 0.34); animation: botPing 2.4s ease-out infinite;
}
@keyframes botPing { to { transform: scale(1.9); opacity: 0; } }
/* 72 = the toggle's 52 plus the 20px of air the panel has always cleared it
   by. It moved with the puck; it is not an independent number. */
.bot-panel {
  position: absolute; bottom: 72px; inset-inline-end: 0;
  width: min(360px, calc(100vw - 40px)); height: 460px; max-height: calc(100vh - 140px);
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
  box-shadow: var(--shadow); overflow: hidden;
  transform-origin: bottom right;
  animation: botPanelIn 0.3s cubic-bezier(0.34, 1.45, 0.64, 1) both;
}
[dir="rtl"] .bot-panel { transform-origin: bottom left; }
.bot-panel.closing { animation: botPanelOut 0.2s ease-in both; }
@keyframes botPanelIn { from { opacity: 0; transform: translateY(16px) scale(0.9); } }
@keyframes botPanelOut { to { opacity: 0; transform: translateY(16px) scale(0.9); } }
.bot-head {
  display: flex; align-items: center; gap: 10px; padding: 14px 16px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  font-family: var(--font-head);
}
.bot-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--success); box-shadow: 0 0 8px var(--success); }
.bot-close {
  margin-inline-start: auto; background: none; border: none; color: var(--muted);
  font-size: 14px; cursor: pointer;
}
.bot-close:hover { color: var(--text); }
.bot-msgs { flex: 1; overflow-y: auto; padding: 16px; display: grid; gap: 10px; align-content: start; }
.bot-msg {
  max-width: 85%; padding: 10px 14px; border-radius: 14px; font-size: 13.5px; line-height: 1.55;
  animation: botMsgIn 0.28s cubic-bezier(0.34, 1.3, 0.64, 1) both;
}
@keyframes botMsgIn { from { opacity: 0; transform: translateY(10px) scale(0.96); } }
.bot-typing { display: flex; gap: 5px; align-items: center; padding: 13px 16px; }
.bot-typing i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  animation: typingDot 1s ease-in-out infinite;
}
.bot-typing i:nth-child(2) { animation-delay: 0.18s; }
.bot-typing i:nth-child(3) { animation-delay: 0.36s; }
@keyframes typingDot { 30% { transform: translateY(-5px); opacity: 0.45; } }
.bot-msg.ai { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); justify-self: start; border-start-start-radius: 4px; }
.bot-msg.user { background: var(--accent-grad); color: var(--on-gold); justify-self: end; border-start-end-radius: 4px; font-weight: 600; }
.bot-msg a { color: var(--accent); font-weight: 600; text-decoration: underline; }
.bot-msg .bot-book { margin-top: 4px; padding: 8px 16px; font-size: 13px; }
.bot-chips { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 16px 10px; }
.bot-chip {
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; padding: 6px 12px; font: 600 12px var(--font-text); cursor: pointer; transition: 0.2s;
  text-align: start;
  animation: botMsgIn 0.3s ease both;
}
.bot-chip:nth-child(2) { animation-delay: 0.1s; }
.bot-chip:nth-child(3) { animation-delay: 0.2s; }
.bot-chip:hover { border-color: var(--accent); color: var(--accent); }
.bot-input { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--border); }
.bot-input input { flex: 1; padding: 10px 12px; font-size: 13.5px; }
.bot-input .btn { padding: 10px 16px; font-size: 13.5px; }

/* --------------------------- render performance -------------------------
   Animations use transform/opacity only (GPU-composited); below-the-fold
   sections skip rendering until needed via content-visibility. */
.reveal, .ticker-track { will-change: transform, opacity; }
#optimize, #repairs, #results, #plans, #faq, #contact { content-visibility: auto; contain-intrinsic-size: auto 800px; }

/* -------------------------------- reveal -------------------------------
   The slide-in offset lives in --reveal-y instead of being written straight
   into `transform`, and the settled state only zeroes it. Hover lifts then
   *add* to that offset with calc() instead of racing this rule in the cascade:
   `.reveal.visible { transform: none }` used to outrank `.card:hover` — equal
   specificity, later in the file — which killed the lift on every card that
   reveals, i.e. all of them. Registered non-inheriting so a `.svc-group.reveal`
   wrapper never shifts the cards nested inside it, and defaulted to 0px so
   elements that never reveal can read the var with no fallback. */
@property --reveal-y {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
/* Progressive enhancement: the hidden start state is gated behind .js-reveal,
   which main.js sets on <html> at the moment it wires the observer up. Without
   JavaScript — disabled, blocked, failed to load, or throwing before that line
   — the class never lands and every .reveal element simply renders as normal
   content. Pricing can no longer be hidden by a script that never ran.

   No first-paint flash: main.js adds the class and settles everything already
   in the viewport inside one synchronous block, so the browser has no chance to
   paint the intermediate state. --reveal-y stays at its registered 0px default
   in the un-enhanced path, which keeps the hover-lift maths identical. */
.reveal { transform: translateY(var(--reveal-y)); transition: opacity 0.6s ease, transform 0.6s ease; }
.js-reveal .reveal { --reveal-y: 18px; opacity: 0; }
.js-reveal .reveal.visible { --reveal-y: 0px; opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Must out-rank `.js-reveal .reveal` (0,2,0), not just `.reveal` — a media
     query adds no specificity, so the plain selector would lose and leave
     off-screen content hidden. */
  .reveal, .js-reveal .reveal { --reveal-y: 0px; transition: none; opacity: 1; }
  /* Colour and shadow feedback stay; the movement goes — same trade the
     footer socials make above. */
  /* .card:hover and .step:hover are absent: neither declares a transform any
     more, so there is nothing here to switch off. The plan lift is the one
     hover on the page that still moves, so it is the one listed. */
  .plan:hover { transform: translateY(var(--reveal-y)); }
  /* Tools section ("Instrument Black"): the hover states are colour-only by
     design and need no exception here, but the press feedback and the one
     animated metric do. The gauge SNAPS to its measured width instead of
     travelling to it — the reading is the information, the travel was only
     ever decoration, and nothing in the section depends on it being seen to
     move. Same for the tab and chip press pops. */
  .tool-tab:active, .chip:active span { transform: none; }
  .meter i { transition: none; }
  .section-bg img { animation: none; }
  .grad, .ticker-track { animation: none; }
  /* `.kicker-special::after`, not `.kicker-special`. The studios badge used to
     carry the travelling sheen on the element itself; the design pass moved it
     onto the rail — the ::after — and this rule kept naming the element, so
     the one animated label on the page went on travelling for readers who had
     asked it not to. A media query adds no specificity and pseudo-elements are
     not covered by their originating element's rules, so it has to be named. */
  /* `.kicker.kicker-special::after` and not `.kicker-special::after`, for the
     same reason .k2 and .k3 are named below: the authoring rule is (0,2,1) and
     a media query adds no specificity, so the one-class version at (0,1,1)
     lost and the badge went on travelling. Matching the authoring specificity
     is enough — this block is later in the file — and it is what every other
     neutraliser here does.

     The selector names the ::after rather than the element because the design
     pass moved the sheen off the badge and onto its rail; a rule on the
     originating element does not reach its pseudo-elements. */
  /* `.sv-masthead .sv-rule`, not `.sv-rule` — and this was caught by measuring
     rather than by reading. The studios masthead's centred gold bar now
     carries the travelling sheen the badge's rail used to, but `.sv-rule` is
     AUTHORED 400 lines below this block, and a media query adds no
     specificity: the one-class neutraliser lost on source order and the bar
     went on travelling for readers who had asked it not to. Verified with
     prefers-reduced-motion emulated: `.sv-rule -> gradSheen` was the only
     animation still running. Two classes out-rank one, whatever the order. */
  .brand-sub, .kicker.kicker-special::after, .plan-flag { animation: none; }
  .sv-masthead .sv-rule { animation: none; }
  /* Tier meter keeps its shape and its active/inactive reading, loses all
     movement: no charge, no travelling head, no spark, no sweep. The endpoint
     stays lit at a fixed value so the power ceiling is still identifiable. */
  .plan-tier i { animation: none; transform: none; filter: none; }
  .plan-tier::before, .plan-tier::after { display: none; }
  .plan-tier i:not(.off):last-child::after,
  .plan-tier i:not(.off):has(+ i.off)::after { animation: none; opacity: 0.26; }
  select::picker(select), select::picker-icon, select option { transition: none; }
  .lang-menu, .lang-caret { transition: none; }
  .nav-links, .nav-links a { transition: none; }
  /* .k2 and .k3 are named explicitly, and this is a real fix rather than
     tidying: `.art-knob` is (0,1,0) and `.art-knob.k2` is (0,2,0), and a media
     query adds NO specificity — so the bare selector stopped only the first of
     the three knobs and two of them kept sliding under reduced motion. Found
     by enumerating running animations with the media feature emulated; it
     predates this design pass. */
  .art-knob, .art-knob.k2, .art-knob.k3, .art-needle { animation: none; }
  .art-bar, .art-bar-hot, rect.art-gold, .art-chip-text, .art-halo, .art-trend-dot { animation: none; }
  .step.visible .art-check, .step.visible .art-trend { animation: none; }
  .step.visible .art-bar, .step.visible .art-bar-hot, .step.visible .art-trend-dot { animation: none; }
  .pulse-glow, .pulse-head { animation: none; opacity: 0; }
  .chip:has(input:checked) span, .tool-tab.active { animation: none; }
  /* FAQ: the answer still opens, it just opens at once — no height ramp, no
     slide, no blur. The sweep is removed outright rather than shortened; a
     line racing across an edge is exactly the motion this query exists to
     suppress. The icon still reads + and −, it simply stops tweening. */
  /* The answer still opens, it just opens at once. The lamp and the morph both
     STAY — they are the two channels that report open/closed, and removing a
     state indicator is not what this query asks for; only their tweening goes.
     (The rule that used to sit here hid summary::before outright, because
     under the old design that pseudo-element was a gold line racing across the
     top edge. It is now the state lamp, so hiding it would remove
     information.) */
  .faq-item::details-content { transition: none; opacity: 1; }
  .faq-item, .faq-item summary, .faq-item summary::before, .faq-item summary::after { transition: none; }
  .bot-toggle, .bot-ping::after, .bot-panel, .bot-msg, .bot-chip { animation: none; }
}

/* ------------------------------ responsive ----------------------------- */
@media (max-width: 1000px) {
  .hero-inner {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "copy" "visual" "readout";
    padding-top: 64px;
  }
  /* The proof is now a sibling BETWEEN the copy and the readout rather than
     the last thing in the column, so it needs the air the grid's row-gap: 0
     deliberately does not provide. */
  .hero-visual { margin-block-start: 22px; }
  .grid-3 { grid-template-columns: 1fr 1fr; }
  .plans-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-inner { grid-template-columns: 1fr; }
}
@media (max-width: 880px) {
  /* tighten the nav so the full logo always fits at its proper aspect */
  .nav-inner { gap: 14px; }
  .brand img { height: 40px; }
  .brand-sub { font-size: 11px; letter-spacing: 2px; }
  .nav-links { gap: 14px; }
  .nav-links a { font-size: 13.5px; }
}
/* The horizontal nav switches to the burger here rather than at the 680px used
   by the rest of the mobile layout, because the row stops fitting long before
   680px is reached — and it stops fitting at a different width in each language.
   Measured document overflow with the desktop row forced on (Chrome, DPR 1):

              681px      740px      758px      790px
       en     +18.4       fits       fits       fits
       he     +23.4       fits       fits       fits
       ar     +33.4       fits       fits       fits
       es     +95.4      +36.0      +17.6       fits

   Spanish is the binding case: `document.scrollWidth` settles at 776px no matter
   how narrow the viewport gets, so the row needs 776px and English needs ~717px.
   Below that the row does not wrap or shrink — `.nav-cta` is simply pushed past
   the right edge (94px past it at 681px), and because nothing scrolls
   horizontally there, the Discord button was clipped off-screen and could not be
   reached at all.

   820 rather than the 775 that would only just clear Spanish, because the two
   numbers are not measured against the same thing: `max-width` resolves against
   the scrollbar-inclusive viewport, while the 776px the row needs is content
   width. The desktop row therefore first appears at an inner width of 821, which
   on a 17px classic scrollbar leaves 804px of content — 28px of real margin. A
   775 or 800 cut-off would leave 8px or less on that same platform, which is
   how the booking modal's 421-480 gap got in: clearing the requirement by less
   than a scrollbar's width is not clearing it.

   Only the menu rules move — the Discord padding below, and the grid/content
   collapses in the 680px block, are unrelated to whether the links fit and stay
   where they were. No JavaScript changes: the burger's state, aria-expanded and
   aria-label swap are width-agnostic already. */
@media (max-width: 820px) {
  .nav-links {
    display: flex; position: absolute; top: 74px; left: 0; right: 0; z-index: 55;
    flex-direction: column; gap: 0; background: var(--bg-alt); border-bottom: 1px solid var(--border);
    opacity: 0; transform: translateY(-12px); visibility: hidden; pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;
  }
  .nav-links.open { opacity: 1; transform: none; visibility: visible; pointer-events: auto; }
  .nav-links a {
    padding: 15px 24px; border-top: 1px solid var(--border);
    opacity: 0; transform: translateY(-8px);
    transition: opacity 0.25s ease, transform 0.25s ease, color 0.2s;
  }
  .nav-links.open a { opacity: 1; transform: none; }
  .nav-links.open a:nth-child(2) { transition-delay: 0.04s; }
  .nav-links.open a:nth-child(3) { transition-delay: 0.08s; }
  .nav-links.open a:nth-child(4) { transition-delay: 0.12s; }
  .nav-links.open a:nth-child(5) { transition-delay: 0.16s; }
  .nav-burger { display: block; }
}
@media (max-width: 680px) {
  /* Icon-only from here down. The width was already exactly 44px (a 20px mark
     inside 12px of padding either side) and is left at 44 so the header row's
     fit arithmetic is untouched; only the HEIGHT is raised, from 42 to 44,
     which costs the row nothing. The mark steps down to 18px so the visible
     square reads smaller than the old block of solid blurple did. */
  .nav-cta .btn-discord {
    padding: 0; min-inline-size: 44px; min-block-size: 44px;
  }
  .nav-cta .btn-discord span { display: none; }
  .nav-cta .btn-discord svg { width: 18px; height: 18px; }
  .grid-3, .grid-2, .plans-grid, .form-row, .tool-grid { grid-template-columns: 1fr; }
  /* Metric tiles turn into READOUT ROWS rather than three tall stacked boxes:
     label at the inline start, value at the inline end, sharing a baseline.
     Three full-width tiles of two stacked lines each was 6 lines of vertical
     scroll for 3 numbers; this is 3 lines, and it is closer to how a real
     readout is laid out anyway. minmax(0, …) on the label column so a long
     translation wraps instead of forcing the row wider than the viewport. */
  .est-grid, .mt-grid, .perf-metrics { grid-template-columns: minmax(0, 1fr); }
  .est-box, .mt-stat {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline; column-gap: 12px;
  }
  /* State label above its own row of readings once the row can no longer hold
     both side by side. */
  .perf-row { grid-template-columns: minmax(0, 1fr); }
  /* The ECG is desktop-only. It now occupies the slack in the rail's header
     row rather than lying behind the keys, so dropping it would leave the
     nameplate alone on a row with nothing to run to — the `::after` takes the
     same slot and draws the plain hairline the trace was standing in for.
     Symmetric fade at both ends, so it needs no RTL twin. */
  .strip-pulse { display: none; }
  .strip-head::after {
    content: ""; flex: 1 1 auto; min-width: 0; height: 1px;
    background: linear-gradient(90deg, transparent, var(--border) 14%,
                                var(--border) 86%, transparent);
  }
  .mt-big-val { font-size: 46px; }
  .mt-stage { min-height: 200px; }
}
/* The header row's own shrink point. It used to be the 420px content
   breakpoint below, and between the width at which the untightened row stops
   fitting and that 420px there was a band the row simply overflowed: nothing in
   it can give way (`.brand` is `flex: none`, `.brand img` is `max-width: none`,
   every `.nav-cta` child is sized by its content), so `.nav-cta` was pushed
   past the right edge and the document scrolled sideways. Measured content
   width the untightened row needs:

       en 446      he 454      ar 457      es 444

   Arabic binds, because `.lang-cur` shows the current language in its own
   script — the language button is 75.4px wide there against 57.4px in English.
   `.brand-sub` is not translated, so the brand costs the same everywhere and
   that button is the whole of the spread. English at HEAD, innerWidth →
   scrollWidth: 480 → 470, 450 → 446, 430 → 446, 425 → 446, 414 → 404. Clean
   widths on both sides of the band are why it went unnoticed.

   500 rather than the 457 Arabic actually needs, for the reason the burger
   breakpoint is 820 rather than 776: `max-width` resolves against the
   scrollbar-inclusive viewport, and the requirement is content width. The
   untightened row therefore first appears at an inner width of 501, which on a
   17px classic scrollbar leaves 484px of content — 27px of real margin. A
   cut-off set to the requirement itself would leave none.

   Only the nav rules moved. The three below were never about the header, and
   dragging them up to 500 would restyle the hero and the results panel at
   tablet widths under cover of a nav fix. The tightened sizes are unchanged —
   they are the ones that already shipped below 420px, and they are the smallest
   the header gets; the wordmark still drops at 370px and nowhere else. */
@media (max-width: 500px) {
  /* 2px, not the 8px this was, because between 371px and 375px the gap WAS the
     overflow. `.nav-inner` is `justify-content: space-between` with two items,
     so this gap is only ever a floor: wherever the row has slack the items are
     already pushed to opposite ends and the value is invisible. It is applied,
     and therefore costs real width, only once the row is packed — which is
     exactly the band where the row had none to spare.

     Measured row width (`.brand` + this gap + `.nav-cta`), by language, and
     re-measured at 371px after the typography refresh:

              brand            nav-cta          sum
              was     now      was     now      was     now
       en     181.34  176.63   149.36  148.43   330.70  325.05
       es     181.34  176.63   148.05  147.50   329.39  324.13
       he     176.24  176.63   164.03  164.95   340.26  341.57
       ar     176.24  176.63   166.05  163.86   342.29  340.49

     The spread across languages is `.lang-cur`, which shows the current
     language in its own script. Arabic used to bind and no longer does: العربية
     is now set in IBM Plex Sans Arabic rather than falling through to whatever
     Arabic the OS had, and Plex draws it 2.9px narrower, which hands the
     tightest band its margin back. Hebrew went 1.3px the other way and is now
     the widest row. RTL is not itself the cause of the spread — Manrope's
     wordmark measures the same in both directions.

     The wordmark drops at 370px, so 371px is the narrowest viewport that still
     has to carry it, and there the row has 371 - 24 = 347px. The widest
     language now needs 341.57px of that (Hebrew), leaving 5.43px for the gap —
     it was Arabic at 342.29px and 4.71px before the typography refresh. 2px
     therefore keeps 3.43px in hand for font-rendering variance, up from 2.71px;
     8px overflowed by 3.29px, which reached the viewport edge as the 0.53px
     that rounded `scrollWidth` up to 376.

     Nothing else moves. Above 400px the row has slack, space-between spreads it
     and this value stops being read at all — verified identical layout at 400px
     and 430px in both directions. Below 400px it hands the width back to the
     inline gutter the row had been eating. */
  .nav-inner { gap: 2px; }
  .brand img { height: 25px; }
  .lang-btn { padding: 7px 8px; font-size: 12px; }
  /* The visible box tightens with the rest of the row; the hit area does not
     follow it, because the ::after's 44px minimum is declared on the base rule
     and no breakpoint here touches it. */
  .nav-burger { padding: 5px 9px; }
  .nav-cta { gap: 8px; }
  .brand img { height: 34px; }
  .brand-sub { font-size: 8.5px; letter-spacing: 1.2px; }
}
@media (max-width: 420px) {
  /* .hero-stats is a single column of ruled rows from 560px down, so the old
     `gap: 24px` here was tuning a flex row that no longer exists. */
  .result-big { font-size: 36px; }
  /* The proof console's readout: three channels of a 19px display figure do
     not fit across 372px of card, so the strip stacks into ruled rows — the
     same collapse .hero-stats and .est-grid already make. */
  .fps-readout { grid-template-columns: minmax(0, 1fr); }
  .fps-read {
    display: flex; flex-direction: row-reverse; align-items: baseline;
    justify-content: space-between; gap: 12px;
    padding: 8px 12px; text-align: start;
    border-inline-start: 0; border-top: 1px solid var(--ring);
  }
  .fps-read:first-child { border-top: 0; }
  .fps-read b { display: inline; font-size: 17px; }
  .fps-read i { display: inline; margin-top: 0; }
}
/* The header row stops shrinking at 363px: the logo, the wordmark, the language
   button, the Discord button and the burger cannot compress further, so any
   viewport narrower than that is scrolled sideways by the difference (2px at
   360px, 42px at 320px — 360px being one of the most common phone widths).
   Dropping the wordmark returns ~140px, far more than the 43px worst case, and
   is the whole fix: the `.container` gutter is deliberately left at 24px so
   every other section keeps its spacing. Nothing is lost to assistive tech —
   the <a class="brand"> carries the full name in its aria-label.

   363 is ENGLISH's number, and English is not the language to derive it from:
   `.lang-cur` renders the current language in its own script, so the RTL
   languages need more. Re-measured after the typography refresh, the row needs
   en 349.05, es 348.13, ar 364.49 and he 365.57 — which is how 371-375px
   stayed broken in Arabic after this breakpoint was first set from English.
   370 is kept because the nav gap above was reduced to buy those languages the
   width instead; if the gap is ever raised again, re-derive this number from
   Hebrew, which the refresh made the widest of the four. */
@media (max-width: 370px) {
  .brand-sub { display: none; }
}

/* --------------------- policy consent + captcha ----------------------- */
.check-policy a { color: var(--accent); font-weight: 600; text-decoration: underline; }
.check-policy a:hover { color: #ffd75e; }
/* grecaptcha.render() injects a child with a hard `width: 304px; height: 78px`.
   This box is a grid item of `.modal form`, so its automatic minimum size
   (min-width: auto) resolved to that 304px and sized the form's whole column
   track — every other field was stretched to 304px inside a 203px content box
   and the modal scrolled sideways on phones. `min-width: 0` lets the track
   collapse to the width actually available; the rules further down make the
   widget itself fit into it. */
.recaptcha-box { display: flex; justify-content: center; min-width: 0; }
.recaptcha-box:not([hidden]) { margin-top: 2px; }
/* The image challenge normally hangs off the tickbox on this little arrow, but
   main.js re-centres it in the window (the tickbox lives in a scrolled modal,
   which reCAPTCHA's own placement cannot account for). An arrow pointing at
   nothing is worse than no arrow. */
.g-recaptcha-bubble-arrow { display: none !important; }
/* With the trimmed gutters above, 304px of widget plus the modal's own chrome
   (backdrop gutter, border, modal padding, scrollbar) still needs ~377px of
   viewport, so below that the widget is scaled down in two steps — each one
   chosen to keep a comfortable margin at the far end of its range rather than
   to only just clear it at the boundary.
   The old rule here scaled it too, but `transform` only changes what is
   painted — the element kept its full 304px layout box, which is the
   part that was overflowing. The negative margins pull the layout box in by
   exactly what the transform takes off, so the widget genuinely fits instead
   of merely looking smaller. Symmetric inline margins pair with the
   `top center` origin, which keeps the whole thing direction-agnostic: no RTL
   variant needed. Nothing is clipped, scaled or otherwise — the tickbox, the
   badge and the audio/refresh controls all stay fully visible and clickable. */
@media (max-width: 390px) {
  .recaptcha-box { --rc-scale: 0.88; }
  .recaptcha-box > div {
    transform: scale(var(--rc-scale));
    transform-origin: top center;
    margin-inline: calc(152px * (var(--rc-scale) - 1));
    margin-block-end: calc(78px * (var(--rc-scale) - 1));
  }
}
@media (max-width: 356px) {
  .recaptcha-box { --rc-scale: 0.78; }
}

/* ----------------------- footer payment logos ------------------------- */
.footer-pay { display: flex; align-items: center; gap: 10px; }
.footer-pay-label { color: var(--faint); font-size: 12px; }
.pay-logos { display: inline-flex; gap: 8px; }
.pay-card { display: inline-flex; }
.pay-card svg { width: 40px; height: 27px; border-radius: 4px; display: block; box-shadow: 0 0 0 1px var(--border); }

/* ------------------------- legal / policy pages ------------------------ */
.policy-top {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; padding: 20px 0; border-bottom: 1px solid var(--border);
}
.policy-top img { height: 38px; }
.policy-top-actions { display: flex; align-items: center; gap: 12px; }
.legal { max-width: 820px; margin: 0 auto; padding: 40px 0 72px; }
.legal h1 { font-size: 30px; margin-bottom: 8px; }
.legal .policy-updated { color: var(--faint); font-size: 13px; margin-bottom: 28px; }
.legal h2 { font-size: 19px; margin: 30px 0 10px; color: var(--accent); }
.legal h3 { font-size: 16px; margin: 18px 0 8px; }
.legal p { color: var(--muted); margin-bottom: 12px; font-size: 15px; }
.legal ul { margin: 8px 0 16px; padding-inline-start: 22px; color: var(--muted); font-size: 15px; }
.legal li { margin-bottom: 8px; }
.legal a { color: var(--accent); text-decoration: underline; }
.legal .legal-contact { margin-top: 28px; padding: 16px 18px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.policy-lang { display: none; }
.policy-lang.active { display: block; }

/* ------------------------- purchase success page -------------------------
   /purchase-success. One centred module on the page's own near-black ground,
   in the same instrument vocabulary the rest of the site uses: an opaque well,
   a hairline edge, a mono state pill with a lamp on it, and gold spent on
   exactly two things — the confirmation glyph and the primary action.

   Deliberately NOT a giant glass card with a glowing blob behind it. Nothing
   here floats: the module is seated on the page with a lip, the way every
   other housing on this site is, and the only motion is a confirmation that
   draws itself once and stops.

   FOUR STATES, ONE BOX. `[data-state]` on the card selects the glyph parts and
   the accent; the copy is written by public/js/purchase.js. Keeping the states
   as attribute selectors on one element rather than as four hidden blocks
   means the page cannot ever paint two of them at once, and it means the
   confirmed styling physically cannot appear until the script sets the
   attribute — which is the same guarantee the script is making in JavaScript,
   restated in CSS. */
.ps {
  display: grid; justify-items: center;
  padding-block: clamp(28px, 7vw, 64px) clamp(48px, 10vw, 96px);
}
.ps-card {
  --ps-well:  #0a0a0b;
  --ps-edge:  rgba(233, 238, 247, 0.1);
  --ps-label: #8f9095;

  inline-size: min(100%, 560px);
  display: grid; justify-items: center; text-align: center;
  padding: clamp(28px, 6vw, 44px) clamp(20px, 5vw, 40px) clamp(30px, 6vw, 42px);
  border: 1px solid var(--ps-edge); border-radius: 18px;
  background: var(--ps-well);
  box-shadow: var(--lip);
}

/* ------------------------------- the glyph --------------------------------
   One 96x96 drawing with five parts, of which each state shows two or three.
   The ring is always there; the arc turns for `verifying`, the tick draws for
   `verified`, the bar sits for the two states that are neither. */
.ps-glyph {
  inline-size: clamp(72px, 18vw, 88px); block-size: auto;
  fill: none; stroke-linecap: round; stroke-linejoin: round;
  margin-block-end: 22px;
}
.ps-ring { stroke: var(--ps-edge); stroke-width: 2; }
.ps-arc, .ps-tick, .ps-bar, .ps-pulse { opacity: 0; }

/* verifying — the only looping motion on the page, and it is a loading
   indicator, which is the one thing a loop is honest for. */
.ps-card[data-state="verifying"] .ps-arc {
  opacity: 1; stroke: var(--accent); stroke-width: 2.5;
  transform-origin: 48px 48px;
  animation: psSpin 1.1s linear infinite;
}
@keyframes psSpin { to { transform: rotate(360deg); } }

/* verified — draws ONCE and settles. The ring traces, the tick follows it, and
   a single halo expands and fades out. `forwards` on all three and no
   iteration count anywhere: when the sequence finishes, the picture that is
   left is the finished picture, and nothing on this page moves again.

   226.2 is the ring's circumference (2 x pi x 36) and 46 comfortably exceeds
   the tick path's length, which is all a dash-offset draw needs. */
.ps-card[data-state="verified"] .ps-ring {
  stroke: var(--accent); stroke-width: 2.5;
  stroke-dasharray: 226.2; stroke-dashoffset: 226.2;
  transform: rotate(-90deg); transform-origin: 48px 48px;
  animation: psDraw 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.ps-card[data-state="verified"] .ps-tick {
  opacity: 1; stroke: var(--accent); stroke-width: 3.5;
  stroke-dasharray: 46; stroke-dashoffset: 46;
  animation: psTick 0.34s cubic-bezier(0.22, 1, 0.36, 1) 0.48s forwards;
}
.ps-card[data-state="verified"] .ps-pulse {
  stroke: var(--accent); stroke-width: 1.5;
  transform-origin: 48px 48px;
  animation: psPulse 0.85s ease-out 0.72s forwards;
}
@keyframes psDraw { to { stroke-dashoffset: 0; } }
@keyframes psTick { from { opacity: 1; } to { stroke-dashoffset: 0; opacity: 1; } }
@keyframes psPulse {
  from { opacity: 0.45; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.32); }
}

/* unverified / pending — a bar, not a cross and not a red anything. Neither
   state is an error the visitor caused, and one of them may well be a payment
   that went through; a danger colour would be a claim the page cannot make. */
.ps-card[data-state="unverified"] .ps-bar,
.ps-card[data-state="pending"] .ps-bar {
  opacity: 1; stroke: var(--muted); stroke-width: 3;
}

/* ------------------------------- the copy -------------------------------- */
/* The state pill, with the same lamp grammar as the rest of the site: lit gold
   when the channel is live, a hollow ring when it is not. */
.ps-state {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 6px 12px; border-radius: 6px;
  border: 1px solid var(--ps-edge); background: var(--sf-1);
  color: var(--ps-label); font: 600 11px var(--mono);
  text-transform: uppercase; letter-spacing: 0.11em;
}
.ps-state::before {
  content: ""; flex: none; inline-size: 6px; block-size: 6px; border-radius: 50%;
  background: transparent; box-shadow: inset 0 0 0 1.5px rgba(233, 238, 247, 0.4);
}
.ps-card[data-state="verified"] .ps-state::before {
  background: var(--accent); box-shadow: 0 0 8px rgba(227, 179, 65, 0.55);
}
.ps-h1 {
  margin-block-start: 18px;
  font-size: clamp(24px, 5.4vw, 32px); font-weight: 700;
  letter-spacing: -0.5px; line-height: 1.18;
}
.ps-lead {
  margin-block-start: 12px; max-inline-size: 46ch;
  color: var(--muted); font-size: 15px; line-height: 1.7; text-wrap: pretty;
}

/* ---------------------------- order reference ----------------------------
   A plate, because it is the one thing on the page the customer may need to
   copy down. `user-select: all` makes one click select the whole reference and
   nothing around it. The value is a Latin run in every language, so it is
   isolated LTR — see the same treatment on the value card's figures. */
.ps-ref {
  margin-block-start: 24px;
  display: grid; justify-items: center; gap: 7px;
  padding: 14px 22px; border-radius: 12px;
  border: 1px solid var(--ps-edge); background: var(--sf-1);
  box-shadow: var(--lip);
}
.ps-ref-label {
  color: var(--ps-label); font: 600 11px var(--mono);
  text-transform: uppercase; letter-spacing: 0.11em;
}
.ps-ref-val {
  direction: ltr; unicode-bidi: isolate; user-select: all;
  color: var(--text); font-family: var(--mono); font-size: 20px; font-weight: 700;
  letter-spacing: 0.06em; font-variant-numeric: tabular-nums;
  /* Long enough to need it on a 320px screen once the plate's padding is
     taken off. `anywhere` and not `break-word`: a reference has no spaces, so
     break-word never gets a chance to act. */
  overflow-wrap: anywhere;
}

/* -------------------------------- actions --------------------------------
   Track Your Order is the primary and Return to Home is unmistakably
   secondary — a ghost button under a filled one, which is the same pair the
   hero uses. They stack on a narrow screen with the primary on top, so the
   action the page exists to offer is always the one nearest the copy. */
.ps-actions {
  margin-block-start: 26px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;
  inline-size: 100%;
}
.ps-actions .btn { min-block-size: 46px; }
@media (max-width: 460px) {
  .ps-actions { flex-direction: column; }
  .ps-actions .btn { inline-size: 100%; }
}

/* ------------------------------- countdown --------------------------------
   Visual only — aria-hidden in the markup, so the number changing every second is
   never announced. Quiet by design: it reports something that is about to
   happen anyway and that two buttons above it already offer. */
.ps-count {
  margin-block-start: 20px;
  color: var(--faint); font: 400 12px/1.6 var(--mono);
}

/* The live region. Visually hidden rather than display:none — a hidden node is
   not in the accessibility tree at all, so nothing in it would ever be
   announced, which is the opposite of what a status region is for. */
.ps-live {
  position: absolute; inline-size: 1px; block-size: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Reduced motion: the finished state, immediately. The ring is drawn, the tick
   is drawn, the halo never happens and the loading arc stops turning — a
   visitor who asked for no motion still gets every piece of information the
   animation carried, just without being animated at it. */
@media (prefers-reduced-motion: reduce) {
  .ps-card[data-state="verifying"] .ps-arc { animation: none; }
  .ps-card[data-state="verified"] .ps-ring,
  .ps-card[data-state="verified"] .ps-tick { animation: none; stroke-dashoffset: 0; }
  .ps-card[data-state="verified"] .ps-pulse { animation: none; opacity: 0; }
}

/* ----------------------- studios / creative services -------------------
   THE CAPABILITY PLATE.

   What was here before: five separately-bordered cards, floating on the studio
   photograph with a 16px gap between them, each lifting 3px and taking a 38px
   drop shadow under the pointer. Every detail inside a cell was already good —
   an index numeral, a masthead rule, a glyph — and none of it could fix the
   section, because the thing reading as generic was the SILHOUETTE. Five
   rounded rectangles in a grid is the construction a feature grid uses, and a
   reader recognises it before they read a word of it.

   So the five capabilities are BAYS OF ONE PLATE. One bordered, opaque
   housing; the cells divided by one-pixel seams rather than separated by gaps.
   That is the object a studio's own service sheet is — a capability index —
   and it is also, exactly, the construction the value card's readout plate and
   its comparison already use (`gap: 1px` over a painted ground, opaque cells).
   Two sections of this page now share one way of drawing "several facts that
   belong to one object", which is what makes them read as one system.

   WHY THE PLATE IS OPAQUE
   -----------------------
   The same diagnosis the value card's second pass made. Every surface in here
   used to be an alpha film of gold over a photograph, so the cards, the icon
   tiles and the section ground were all the same warm cast composited a
   different number of times — which is why a section whose palette is two
   greys and one gold read as "everything is brown". The plate stops the art at
   its own edge and the bays step off it in tone:

     BAY    --sv-bay     an opaque capability cell. The art stops here.
     LIT    --sv-bay-hi  the lead bay, and any bay under the pointer.
     SEAM   --sv-seam    the one pixel between two bays, and the only place
                         anything behind the plate is still visible.

   The photograph is not gone — it is what the plate is mounted on, and the
   vignette over it is opened up next to the old one, because the plate no
   longer needs the ground darkened to hold its own contrast.

   THE COMPOSITION
   ---------------
   Seams at column 7, then column 4, then none. Three bands, no two divided the
   same way, and the division is derived from the copy rather than imposed on
   it: Graphic design has the shortest description of the five (88 characters)
   and takes the narrowest bay, Studio-level production (106) the wider half of
   that band, and Streaming (215) a band of its own laid out in two columns.

   THE GOLD BUDGET
   ---------------
   Eight objects in the whole section, and not one of them is a filled gold
   surface — filled gold is an action on this site and there is no action here:

     the masthead badge and its sheened rule   (unchanged)
     the index numeral on each bay
     the masthead hairline inside each bay
     the discipline glyph, stroked
     the lead bay's 2px cap rail               (one bay only)
     a hairline ring and a tile film on hover
     the closing lozenge                       (unchanged)

   RTL: grid-template-areas plus logical properties, so the whole plate mirrors
   itself. The only physical values left are gradients symmetric about the axis
   they would be mirrored across, plus the one bay hairline that has a
   [dir="rtl"] twin below. */

/* One gold channel, one surface ladder and one motion feel for the whole
   section. Every alpha variant derives from --sv-gold so a tint can never
   drift into a second colour by accident, and every micro-interaction shares
   two speeds and one easing curve. Scoped to .studios rather than :root
   deliberately: the rest of the site writes its own rgba() literals, and
   unifying those is a separate change. */
.studios {
  --sv-gold: 227, 179, 65;
  --sv-lite: 255, 240, 205;
  --sv-ink: 7, 6, 4;
  /* The plate's own ladder. Opaque hex rather than the :root alpha films, for
     the reason spelled out above: a film cannot stop a photograph, and
     stopping the photograph is the entire point of the plate. */
  --sv-bay:    #0b0a07;
  --sv-bay-hi: #16130c;
  --sv-edge:   rgba(247, 243, 232, 0.09);
  /* The seam, and it is a different value from the edge on purpose. A border
     is drawn ON a surface; a seam is one pixel of the container showing
     BETWEEN two opaque bays, so it composites against #0b0a07 rather than
     against the section ground and needs more alpha to read as a line at all.
     Measured: at 0.09 it resolves to #1a1a18 against a #0b0a07 bay, which is
     a rounding error; at 0.2 it resolves to #34322d, which is a line. */
  --sv-seam:   rgba(247, 243, 232, 0.2);
  --sv-fast: 0.22s;
  --sv-slow: 0.3s;
  /* --sv-enter is gone with the composition that needed it. Its only consumer
     was the 65ms per-cell entrance stagger, and five bays of one plate arrive
     together — the plate carries the .reveal now, so the entrance is the site's
     shared one and the section owns no duration for it. */
  --sv-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}


/* --------------------------- section ambience ---------------------------
   The studio photo establishes the room. It used to have to be darkened to
   0.94 behind the centre, because the cards sitting on it were alpha films and
   needed a dark ground to hold any contrast at all. The plate is opaque, so it
   holds its own contrast and the vignette can open up: more visible texture
   around the plate, and the same calm ground beneath the closing statement,
   which is the only copy still sitting directly on the art. */
.studios .section-bg img {
  opacity: 0.34;
  filter: saturate(0.4) brightness(0.7) contrast(0.92);
}
.studios .section-bg::after {
  background:
    radial-gradient(80% 60% at 50% 45%, rgba(var(--sv-ink), 0.86) 0%, rgba(var(--sv-ink), 0.7) 48%, rgba(var(--sv-ink), 0.3) 100%),
    linear-gradient(180deg, var(--bg) 0%, rgba(var(--sv-ink), 0.5) 24%, rgba(var(--sv-ink), 0.5) 76%, var(--bg) 100%);
}

/* ------------------------------- masthead -------------------------------
   Centred, sitting on a hairline that runs the full width of the plate below
   it, so the heading belongs to the same composition instead of floating above
   it — and without spending a column on a sidebar. The short gold bar sits
   *on* that hairline, centred under the heading.

   The bar is centred with inset-inline:0 + a fixed width + margin-inline:auto,
   not with inset-inline-start:50% and a translate. Those insets are logical
   but translateX is physical, so under RTL the two disagree and the bar lands
   half its own width off centre. Auto margins have no such split. */
.sv-masthead {
  position: relative;
  text-align: center;
  padding-block-end: 24px;
  margin-block-end: 30px;
  border-block-end: 1px solid rgba(var(--sv-gold), 0.16);
}
.sv-masthead h2 {
  font-size: clamp(28px, 3.5vw, 42px); font-weight: 700;
  letter-spacing: -0.6px; line-height: 1.12;
  /* Wide enough that the longest translation still fits one line at desktop
     ("Para estudios y empresas" is 24 characters), and narrow enough to break
     naturally rather than at a forced point once the clamp bottoms out. */
  margin: 16px auto 0; max-width: 24ch;
}
/* The studios masthead was already centred — heading, rule and all — and the
   one thing in it still anchored to the inline start was the badge's rail.
   Centring the badge unit closes that, and the sheen the rail was carrying
   moves down onto this rule, which is the masthead's own centred gold bar and
   a better home for it: it is symmetric by construction, so it needs no RTL
   twin and it cannot go lopsided when the badge is centred above it. */
.sv-masthead .kicker { justify-content: center; }
.sv-masthead .kicker::after { content: none; }
.sv-rule {
  position: absolute; inset-block-end: -1px; inset-inline: 0;
  width: 58px; height: 2px; margin-inline: auto; border-radius: 2px;
  background: var(--gold-sheen);
  background-size: 200% auto;
  animation: gradSheen 5.5s ease-in-out infinite;
}

/* -------------------------------- the plate -----------------------------
   THE SEAMS ARE THE GAP. `gap: 1px` over a container painted --sv-seam, with
   every bay opaque, draws a hairline between every pair of bays in both axes
   for free — and it keeps drawing them correctly at every breakpoint, because
   the gap has no opinion about how the areas are arranged. Borders on the bays
   could not do that: a bay that starts a new row would carry a start border
   and no top one, and every layout below would have to re-derive which edge
   belonged to whom.

   `minmax(0, 1fr)` and not a bare `1fr`: a bare 1fr refuses to shrink below
   its content's min-content width, which is how the footer columns overflowed
   at 320px.

   The container's own paint is what shows through the gaps, so --sv-seam is
   the plate's background and never a border colour anywhere. */
.sv-index {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: minmax(0, auto);
  gap: 1px;
  background: var(--sv-seam);
  border: 1px solid var(--sv-edge);
  border-radius: 16px;
  overflow: hidden;                   /* so the bays' fills meet the radius */
  box-shadow: var(--lip);
  grid-template-areas:
    "vid vid vid vid vid vid vid aud aud aud aud aud"
    "gfx gfx gfx gfx stu stu stu stu stu stu stu stu"
    "str str str str str str str str str str str str";
}
.sv-vid { grid-area: vid; }
.sv-aud { grid-area: aud; }
.sv-gfx { grid-area: gfx; }
.sv-stu { grid-area: stu; }
.sv-str { grid-area: str; }

/* -------------------------------- the bay -------------------------------- */
.sv-bay {
  position: relative; min-width: 0;
  display: flex; flex-direction: column;
  padding: 24px 26px 26px;
  background: var(--sv-bay);
  /* Colour and edge only. No `translate`, no `transform`, no drop shadow: a
     bay is part of a plate, and a plate does not have parts that float. */
  transition:
    background var(--sv-fast) var(--sv-ease),
    box-shadow var(--sv-slow) var(--sv-ease);
}

/* ----------------------------- the bay masthead --------------------------
   Index at the reading edge, hairline across the slack, glyph at the far end —
   a rule that is a different length in every bay, which is what makes an
   asymmetric plate look composed rather than merely uneven.

   All three parts are logical (`flex`, a fading rule, a gradient run through
   `to right` that is mirrored below), so the whole row flips correctly under
   RTL with one override for the gradient direction. */
.sv-head {
  display: flex; align-items: center; gap: 14px;
  margin-block-end: 20px;
}
/* Mono numerals, tabular so 01-05 sit on one advance and the five mastheads
   across the plate start at the same x. Not content: see the aria-hidden on it
   in the markup. */
.sv-idx {
  flex: none;
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.12em; font-variant-numeric: tabular-nums;
  color: rgba(var(--sv-gold), 0.62);
  transition: color var(--sv-fast) var(--sv-ease);
}
/* The rule itself. It fades out towards the glyph rather than butting into it,
   which keeps the icon reading as the end of a line and not as a box parked on
   one. */
.sv-hair {
  flex: 1 1 auto; min-width: 12px; height: 1px;
  background: linear-gradient(to right, rgba(var(--sv-gold), 0.34), rgba(var(--sv-gold), 0.06));
  transition: background var(--sv-slow) var(--sv-ease);
}
[dir="rtl"] .sv-hair {
  background: linear-gradient(to left, rgba(var(--sv-gold), 0.34), rgba(var(--sv-gold), 0.06));
}
/* A GHOST TILE, not a gold one. The tile used to carry a gold film and a gold
   border at rest and fill with --accent-grad on hover — five gold-edged boxes
   in one section, one of which turned into a filled gold surface under the
   pointer, which is the one thing filled gold is reserved for on this site
   (see "actions" near the top of this file: filled gold is an action and
   nothing else). The tile is neutral now and the GLYPH inside it carries the
   gold — icon detail rather than icon container. */
.sv-ico {
  /* grid, not the flex item's implicit blockification: it also blockifies the
     inline <svg> inside, so the glyph does not sit on a text baseline. */
  display: grid;
  width: 42px; height: 42px; flex: none; padding: 10px;
  border-radius: 11px;
  background: rgba(var(--sv-lite), 0.035);
  border: 1px solid var(--sv-edge);
  box-shadow: inset 0 1px 0 rgba(var(--sv-lite), 0.06);
  transition: background var(--sv-fast) var(--sv-ease), border-color var(--sv-fast) var(--sv-ease);
}
.sv-ico .svg-ico { stroke: rgba(var(--sv-gold), 0.72); transition: stroke var(--sv-fast) var(--sv-ease); }

.sv-body { position: relative; }
.sv-bay h3 { font-size: 18.5px; line-height: 1.3; margin-block-end: 9px; letter-spacing: -0.01em; }
.sv-bay p {
  color: var(--muted); font-size: 14.5px; line-height: 1.62;
  /* Short copy must not stretch across a wide bay, but the cap has to suit the
     widest bay it lands in: at 52ch a 7-column bay left ~200px of bare surface
     beside every line. 62ch is still inside the comfortable 45-75 range and
     never binds on a 4-column bay, which is narrower than that. */
  max-width: 62ch;
}

/* ------------------------------- the lead bay ----------------------------
   More presence, not more brightness. One tonal step up, a 2px gold cap rail
   along its top edge, a bigger glyph and a bigger title.

   The cap rail is the same construction the value card's winning column wears
   — `inset 0 2px 0 var(--accent)` — and that is the point of using it here:
   two sections, one way of saying "this is the one being pointed at". Inside a
   shared housing there is no ring to light, and a rail is the better figure
   anyway: it is the direction every lip in this file lights from, and it stays
   on the top edge under RTL and in the stacked layout with no twin.

   ITS GEOMETRY IS THE SAME AS EVERY OTHER BAY'S, and that is deliberate. The
   first version of this gave it 30px of block padding against the others' 24
   and a 54px glyph against their 42, which put its index numeral 12px lower
   than the numeral beside it across the seam, and its inline padding 6px
   further in than the numeral below it down the plate's edge. Five mastheads
   that start at the same x — and, within a band, sit on the same y — is most
   of what makes a plate read as composed rather than as five cells that happen
   to be adjacent. So the lead bay's billing is carried entirely by channels
   that move nothing: ground, rail, type scale, glyph colour. Only the
   block-end padding differs, and only because a larger last line needs it. */
.sv-bay--lead {
  padding-block-end: 32px;
  background:
    linear-gradient(180deg, rgba(var(--sv-gold), 0.055), rgba(var(--sv-gold), 0) 62%),
    var(--sv-bay-hi);
  box-shadow: inset 0 2px 0 var(--accent);
}
.sv-bay--lead .sv-idx { color: rgba(var(--sv-gold), 0.95); }
.sv-bay--lead .sv-ico { background: rgba(var(--sv-gold), 0.09); border-color: rgba(var(--sv-gold), 0.3); }
.sv-bay--lead .sv-ico .svg-ico { stroke: var(--accent); }
.sv-bay--lead h3 { font-size: 25px; }
.sv-bay--lead p { font-size: 15.5px; max-width: 54ch; }

/* ------------------------------- the wide bay ----------------------------
   The third band is one bay across all twelve columns, which no single column
   of text can fill: at ~1000px a stacked paragraph either runs to a
   110-character measure (unreadable) or stops at a comfortable 68ch and leaves
   ~380px of bare surface beside it.

   So this one bay lays its copy out horizontally instead — title in the first
   column, description in the second. Two comfortable measures side by side use
   the width that one cannot, and a bay shaped differently from its neighbours
   is the point of an index rather than a wrinkle in it. The masthead rule
   spans both columns so it still reads as one bay.

   Desktop only: below 1081px this bay is no wider than the rest and the
   stacked order is the right one.

   The base measure is declared first and the query second on purpose — a media
   query adds no specificity, so the two `.sv-bay--wide p` rules are a tie
   broken on source order, and the desktop value has to come last to win. */
.sv-bay--wide p { max-width: 68ch; }
@media (min-width: 1081px) {
  .sv-bay--wide .sv-body {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 7fr);
    column-gap: 44px;
    align-items: start;
  }
  /* start, not center: the masthead rule sits at the top of the bay, and a
     vertically-centred title floats away from it with no visible link. */
  .sv-bay--wide h3 { margin-block-end: 0; }
  .sv-bay--wide p { max-width: none; }
}

/* ------------------------------ hover state ------------------------------
   Surface, edge, glyph — and nothing that moves. The old hover lifted the card
   3px and gave it `0 16px 38px rgba(0,0,0,0.45)`, which is the floating-card
   reading this whole pass moves away from; inside a plate it would also tear a
   bay off its own seams.

   What is left is what a bay under a pointer should actually do: its ground
   comes up one step, a gold hairline lights its inner edge, and the glyph and
   its tile warm. Decorative only — nothing here carries information, so a
   visitor with no pointer loses nothing.

   The ring is an INSET shadow, so it is drawn inside the bay's own box and can
   never overlap the seam or the neighbouring bay. */
.sv-bay:hover { background: var(--sv-bay-hi); box-shadow: inset 0 0 0 1px rgba(var(--sv-gold), 0.26); }
.sv-bay--lead:hover {
  background:
    linear-gradient(180deg, rgba(var(--sv-gold), 0.075), rgba(var(--sv-gold), 0) 62%),
    var(--sv-bay-hi);
  box-shadow: inset 0 2px 0 var(--accent), inset 0 0 0 1px rgba(var(--sv-gold), 0.26);
}
.sv-bay:hover .sv-idx { color: rgba(var(--sv-gold), 0.95); }
/* The rule brightens along its whole length rather than a stub growing 18px:
   the masthead already spans the bay, so the honest hover for it is more
   light, not more width — and width would relayout the row on every pointer
   crossing. */
.sv-bay:hover .sv-hair {
  background: linear-gradient(to right, rgba(var(--sv-gold), 0.7), rgba(var(--sv-gold), 0.16));
}
[dir="rtl"] .sv-bay:hover .sv-hair {
  background: linear-gradient(to left, rgba(var(--sv-gold), 0.7), rgba(var(--sv-gold), 0.16));
}
.sv-bay:hover .sv-ico {
  background: rgba(var(--sv-gold), 0.1);
  border-color: rgba(var(--sv-gold), 0.34);
}
/* (0,3,0) — beats both `stroke: var(--accent)` on .svg-ico and the resting
   rule above, so the glyph reaches full gold under the pointer. */
.sv-bay:hover .sv-ico .svg-ico { stroke: var(--accent); }

/* The plate enters as ONE object, because it is one: `.reveal` moved off the
   five cells and onto the housing. The per-card 65ms stagger that used to live
   here belonged to five separate cards arriving; five bays of a single plate
   arriving one at a time would tear the seams open on the way in, and there is
   no longer anything for a stagger to describe. */

/* --------------------------- closing statement ---------------------------
   The conclusion of the index, not a sixth bay: no border, no surface, lighter
   than anything in the plate. A hairline, a gold lozenge sitting on it, then
   the two statements separated by weight and colour rather than by another
   box. */
.sv-close {
  position: relative; isolation: isolate;
  margin-block-start: 42px;
  display: grid; justify-items: center; text-align: center; gap: 18px;
}
.sv-close::before {
  content: ""; width: min(440px, 72%); height: 1px;
  background: linear-gradient(90deg, rgba(var(--sv-gold), 0), rgba(var(--sv-gold), 0.42), rgba(var(--sv-gold), 0));
}
.sv-close::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  /* inset-inline stays at 0. Bleeding this glow past the container widened
     documentElement.scrollWidth and put a horizontal scrollbar on the whole
     page at every width below 1200 — a pseudo-element does not show up in a
     querySelectorAll sweep, so it is worth stating plainly: the reach comes
     from the gradient radius, never from a negative inline inset. */
  inset-block: -18% -30%; inset-inline: 0;
  background: radial-gradient(72% 62% at 50% 52%, rgba(var(--sv-gold), 0.06), rgba(var(--sv-gold), 0) 72%);
}
.sv-close-mark {
  width: 7px; height: 7px; rotate: 45deg;
  margin-block-start: -22px; margin-block-end: -4px;
  background: var(--accent-grad);
  box-shadow: 0 0 14px rgba(var(--sv-gold), 0.45);
}
.sv-close-lead { max-width: 660px; color: var(--muted); font-size: 15.5px; line-height: 1.7; }
.sv-close-bi { max-width: 620px; color: var(--text); font-weight: 600; font-size: 14.5px; line-height: 1.65; }

/* ------------------------------ responsive -------------------------------
   Tablet: the interlocking three-band composition needs width to read as
   deliberate, so below that it is re-derived rather than squeezed. The lead
   bay takes a band of its own — at six columns that is the only way it keeps
   any billing at all — and the remaining four pair off two to a band. Pairing
   them matters: alone they were four wide bands of mostly empty surface, and
   side by side the short one shares a row height with the long one instead. */
@media (max-width: 1080px) {
  .sv-index {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-template-areas:
      "vid vid vid vid vid vid"
      "aud aud aud gfx gfx gfx"
      "stu stu stu str str str";
  }
  .sv-bay--lead p, .sv-bay--wide p { max-width: 62ch; }
}
/* Phone: a single column of bays. Still a plate and not a wall of cards — the
   seams, the shared housing, the lead bay's rail and its two type steps are
   all still there, which is the difference between an index that continues
   down the page and five identical rectangles stacked on each other. */
@media (max-width: 760px) {
  .sv-index {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "vid" "aud" "gfx" "stu" "str";
  }
  .sv-masthead { padding-block-end: 20px; margin-block-end: 24px; }
  .sv-masthead h2 { max-width: none; }
  .sv-bay { padding: 21px 22px 23px; }
  .sv-head { margin-block-end: 16px; }
  .sv-bay--lead { padding-block-end: 27px; }
  .sv-bay--lead h3 { font-size: 21px; }
  .sv-bay--lead p { font-size: 15px; }
  .sv-close { margin-block-start: 34px; }
  .sv-close-lead { font-size: 15px; }
}
@media (max-width: 420px) {
  .sv-bay { padding: 18px 17px 20px; }
  .sv-bay--lead { padding-block-end: 22px; }
  .sv-head { gap: 11px; margin-block-end: 15px; }
  .sv-ico { width: 38px; height: 38px; padding: 9px; border-radius: 10px; }
  .sv-bay h3 { font-size: 17px; }
  .sv-bay--lead h3 { font-size: 19px; }
  .sv-bay p { font-size: 14px; }
  .sv-bay--lead p { font-size: 14.5px; }
  /* The plate keeps its radius but loses a little of it: 16px of curve on a
     320px-wide housing eats visibly into the first and last bay's copy. */
  .sv-index { border-radius: 12px; }
}

/* Touch devices never reach :hover, so the resting state has to already look
   finished. It does: the numeral, the masthead rule and the stroked glyph are
   all legible at rest, and the lead bay's rail is not a hover state. There is
   nothing left to nudge up — which is itself the point of having moved the
   section's presence out of its hover and into its construction. */

/* Placed after the rules above rather than in the shared block up by .reveal:
   a media query adds no specificity, so an earlier copy would lose on source
   order. There is no movement left in a bay to switch off — the hover is
   colour and edge only — so this is down to cancelling the transitions
   themselves. The one animated object the section still owns is the masthead
   bar, and that is named in the shared reduced-motion block by its two-class
   selector (`.sv-masthead .sv-rule`). */
@media (prefers-reduced-motion: reduce) {
  .sv-bay { transition: none; }
}

/* combined "Everything we do" section — sub-groups */
.svc-group { margin-top: 44px; }
/* inline-block, not block: .kicker is a pill now, and a block-level one would
   stretch the gold across the whole column. */
.svc-kicker { display: inline-block; margin-bottom: 16px; }

/* ----------------------- guarantee (no gains, no fee) ------------------ */
.guarantee-badge {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 20px;
  padding: 9px 16px; border-radius: 999px; text-align: start;
  background: rgba(52, 211, 153, 0.1); border: 1px solid rgba(52, 211, 153, 0.42);
  color: #d8ffe9; font-size: 13px; font-weight: 600; line-height: 1.35; max-width: 540px;
}
.guarantee-badge .gbadge-ico {
  width: 18px; height: 18px; flex: none; fill: none;
  stroke: var(--success); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.modal .guarantee-badge { display: flex; margin: 0 0 20px; max-width: none; }
.guarantee-banner {
  display: flex; align-items: center; gap: 12px; justify-content: center; text-align: center;
  max-width: 780px; margin: 0 auto 30px; padding: 14px 20px; border-radius: 14px;
  background: linear-gradient(180deg, rgba(52, 211, 153, 0.12), rgba(52, 211, 153, 0.04));
  border: 1px solid rgba(52, 211, 153, 0.45); color: #d8ffe9; font-size: 14.5px; font-weight: 600; line-height: 1.45;
}
.guarantee-banner .gbadge-ico {
  width: 26px; height: 26px; flex: none; fill: none;
  stroke: var(--success); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}

/* ----------------------- FAQ — two columns when there is room -----------
   Fourteen ruled rows in one column is a long scroll for a section most
   visitors sample rather than read. Two columns halves it, and ruled rows
   survive the split where boxed cards did not: the rules on the two columns
   line up into a grid instead of reading as two stacks of floating objects.

   `column-gap` is 48px so the two columns are separated by air rather than by
   a third rule, and `align-items: start` so an open answer in the left column
   does not stretch the closed row beside it. */
@media (min-width: 760px) {
  .faq-list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 48px; align-items: start; }
}

/* ------------------- pro review (the conversion strip) -------------------
   A dashed gold box with its own fill, its own 12px radius and 18px of its
   own padding, sitting inside a panel that already had all three. Two cards,
   one inside the other, and the inner one was outlined in the accent colour —
   so the last thing on the result competed with the result.

   It is now the scorecard's final BAND: the same hairline that separates the
   verdict from the advice, the same eyebrow register the readings block uses,
   and the field and the button on one row. The gold left on it is where it
   was always meant to be — the submit button, which is the action.

   The sub-line moves BELOW the row. It explains what happens after you send
   the address, which is information you want after the control, not a
   paragraph standing between you and it. */
.lead-capture {
  margin-top: 20px; padding: 16px 0 0; border-radius: 0;
  display: grid; gap: 8px; justify-items: start;
  background: none; border: 0; border-top: 1px solid var(--ring);
}
.lead-cap-eyebrow { color: var(--accent); }
.lead-cap-title { font-weight: 700; color: var(--text); font-size: 15px; }
.lead-cap-sub { color: var(--faint); font-size: 12.5px; line-height: 1.5; }
.lead-cap-row {
  display: flex; gap: 10px; flex-wrap: wrap; margin-top: 2px;
  justify-self: stretch; width: 100%;
}
.lead-cap-row input { flex: 1 1 220px; }
.lead-cap-row .btn { flex: none; }
.lead-cap-done { color: var(--success); font-weight: 600; font-size: 14.5px; }

/* honeypot anti-spam field — visually hidden, off-screen, not announced.
   `inset-inline-start`, never a physical `left`. Parking the field 9999px to the
   physical left is free in LTR — the left edge is the inline START, and overflow
   in the start direction is not scrollable, so the browser simply clips it. In
   RTL the inline start is the RIGHT edge, so that same value becomes inline-END
   overflow, which IS scrollable: the document grew to 10294px against a 320px
   viewport and Arabic/Hebrew visitors could pan the page sideways into ~10,000px
   of blank space.
   It went unnoticed because the only honeypot in the static markup sits inside
   #contact, which carries `content-visibility: auto` (see the reveal block near
   the top of this file) and therefore implicit paint containment that clipped it.
   The identical field that main.js injects with the tool lead-capture form lands
   in #tools, which has no containment, so it escaped — the overflow appeared the
   moment a visitor ran the bottleneck or Discover calculator.
   The logical property is off the START edge in both writing modes, so it stays
   unreachable and unscrollable either way. */
.hp-field { position: absolute !important; inset-inline-start: -9999px; top: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* track-your-booking form: breathing room between the inputs and the button */
#trackForm { display: grid; gap: 18px; }

/* ========================= CONSENT: BANNER + PANEL =========================
   The first-visit privacy request, and the preferences dialog behind it.

   MATERIAL
   --------
   It is a bench panel, not a cookie bar: the same --sf-* film over the same
   --ring hairline every card on this site is built from, so it reads as part
   of the page rather than as a compliance widget bolted to it. The gold is
   spent on exactly one thing — the lamp in the kicker, and the Accept button
   that is already the site's primary CTA. No glow, no border-glow, no second
   accent. The one departure from a normal card is the opaque backstop under
   the blur: this floats over moving content (the hero video, the ticker), and
   a translucent panel over a moving background is unreadable.

   PLACEMENT
   ---------
   Bottom inline-start, and deliberately NOT the full width of a desktop
   viewport: the bottom-END corner belongs to the chat puck and the
   back-to-top button. Below 720px there is no room for that split, so the
   banner spans the width and the two floating controls move up by its
   measured height instead — see --consent-h, published by consent.js.

   Z-ORDER, from the bottom: header 50, floating utilities 60, chat 90,
   THIS 95, booking modal 100, payment banner 120, THE PREFERENCES PANEL 130.
   The banner sits above the floating controls (it is the more urgent thing)
   but below the booking modal (a visitor mid-booking is not interrupted by
   it), while the preferences dialog outranks everything because it is the
   thing the visitor just asked for. */
.consent-bar {
  position: fixed; z-index: 95;
  bottom: 20px; inset-inline-start: 20px;
  width: min(460px, calc(100vw - 40px));
  max-height: calc(100vh - 40px); overflow-y: auto;
  border-radius: var(--radius);
  border: 1px solid var(--ring);
  background: var(--surface);
  background-image: linear-gradient(var(--sf-1), var(--sf-1));
  box-shadow: var(--shadow), var(--lip);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  /* The entrance. One short rise-and-fade, no loop, no bounce — it is a
     notice, not an animation. The "from" state lives here and `.in` (added a
     frame later by consent.js) is the "to". */
  opacity: 0; transform: translateY(12px);
  transition: opacity var(--t-sec) var(--ease), transform var(--t-sec) var(--ease);
}
.consent-bar.in { opacity: 1; transform: none; }
.consent-card { padding: 20px; }
.consent-kicker { margin: 0 0 10px; }
.consent-title {
  margin: 0 0 8px; font-family: var(--font-head);
  font-size: 19px; line-height: 1.25; color: var(--text); font-weight: 700;
}
.consent-body { margin: 0 0 16px; color: var(--muted); font-size: 13.5px; line-height: 1.6; }

/* EQUAL WEIGHT, NOT EQUAL PAINT.
   Accept is the site's primary button and Reject is the site's secondary one,
   which is the same pair every other decision on this page offers. What makes
   the choice a real one is that they are the same SIZE, the same shape, sit on
   the same row, are the same distance from the pointer and carry the same
   plain wording — the only difference is the fill. Reject is never a text
   link, never smaller, never greyed, and never below the fold of the card. */
.consent-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.consent-btn {
  flex: 1 1 0; min-width: 148px; min-height: 44px;   /* 44px: WCAG 2.5.8 */
  padding-inline: 14px; white-space: normal; text-align: center; line-height: 1.25;
}
.consent-links {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--ring);
}
/* A button and an <a> side by side have to look like one another, so the
   button is stripped back to the anchor's own resting appearance rather than
   the anchor being dressed up as a control. */
.consent-link {
  display: inline-flex; align-items: center; min-height: 24px;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--muted); font: 600 12.5px var(--font-text);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--ring-gold);
  transition: color var(--t-ui) ease;
}
.consent-link:hover { color: var(--text); }

/* ------------------------------ preferences ------------------------------ */
.consent-panel { z-index: 130; }
.consent-panel-card { max-width: 520px; }
.consent-panel-close { font-size: 14px; line-height: 1; }
.consent-rows { display: grid; gap: 2px; margin: 20px 0 22px; }
.consent-row { padding: 14px 0; border-top: 1px solid var(--ring); }
.consent-row:first-child { border-top: 0; padding-top: 0; }
.consent-row-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.consent-row-label { cursor: pointer; }
.consent-row-name {
  font-family: var(--font-head); font-size: 15px; font-weight: 700; color: var(--text);
}
.consent-row-desc { margin: 6px 0 0; color: var(--faint); font-size: 12.5px; line-height: 1.55; }
/* Necessary is a STATEMENT, not a disabled control. A greyed-out checked box
   is the standard way to imply a choice that is not being offered; saying
   "Always enabled" in words is the honest version, and it is not focusable
   because there is nothing to operate. */
.consent-always {
  flex: none; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--ring); background: var(--sf-1);
  color: var(--faint); font: 600 11px var(--mono);
  text-transform: uppercase; letter-spacing: 0.08em;
}

/* THE SWITCH. A real <input type="checkbox" role="switch"> underneath — it
   keeps the native keyboard behaviour (Space toggles), the native form
   semantics and the native accessible state; the track and thumb are painted
   by CSS. The input is not display:none, because a hidden input cannot be
   focused, and its focus ring is transplanted onto the track. */
.consent-switch {
  position: relative; flex: none; display: inline-flex; align-items: center;
  inline-size: 52px; block-size: 44px; cursor: pointer;   /* 44px touch target */
}
.consent-switch-input {
  position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer;
  inline-size: 100%; block-size: 100%;
}
.consent-switch-track {
  position: relative; inline-size: 44px; block-size: 26px; border-radius: 999px;
  background: var(--sf-2); border: 1px solid var(--ring); box-shadow: var(--lip);
  transition: background var(--t-ui) ease, border-color var(--t-ui) ease;
}
/* The thumb travels on an inset, not a transform: `inset-inline-start` is
   writing-mode aware, so "on" is to the right in English and to the left in
   Arabic and Hebrew — which is the direction those readers expect a switch to
   move — with no RTL override anywhere. */
.consent-switch-track::after {
  content: ""; position: absolute; top: 50%; inset-inline-start: 3px;
  inline-size: 18px; block-size: 18px; border-radius: 50%;
  background: var(--muted); transform: translateY(-50%);
  transition: inset-inline-start var(--t-ui) var(--ease), background var(--t-ui) ease;
}
.consent-switch-input:checked + .consent-switch-track {
  background: var(--accent-grad); border-color: var(--ring-gold);
}
.consent-switch-input:checked + .consent-switch-track::after {
  inset-inline-start: 21px; background: var(--on-gold);
}
.consent-switch-input:focus-visible + .consent-switch-track {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.consent-panel-actions { display: flex; justify-content: flex-end; }
.consent-panel-actions .consent-btn { flex: 0 1 auto; min-width: 180px; }

/* --------------------------- the footer control -------------------------- */
/* Lives in the copyright row, which is the one strip present on every page
   that carries a footer. Styled as the row's own text, one shade brighter on
   hover, so it reads as a link among links rather than as a control bolted on. */
.consent-settings-wrap { display: inline-flex; }
.consent-settings {
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--faint);
  /* Longhands, not `font: inherit`. The shorthand resets font-family to
     whatever it names, and a <button> inheriting the row would still have
     dropped the Arabic and Hebrew faces off the end of the stack — the exact
     defect the typography guard exists to catch. These four inherit the
     copyright row's type without touching the family chain. */
  font-family: inherit; font-size: inherit; font-weight: inherit; line-height: inherit;
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--ring);
  transition: color var(--t-ui) ease, text-decoration-color var(--t-ui) ease;
}
.consent-settings:hover { color: var(--text); text-decoration-color: var(--ring-gold); }

/* ------------------------------- narrow ---------------------------------- */
/* At 720px the banner spans the viewport, which puts it over the corner the
   chat puck and the back-to-top button occupy. Both are lifted by the
   banner's own measured height (published as --consent-h by consent.js) plus
   the 12px the banner is inset by, so the clearance is exact at any card
   height and in any language rather than a guess that a longer translation
   would break. The lift is scoped to this query: on a desktop the banner is
   nowhere near those controls and they must not move. */
@media (max-width: 720px) {
  .consent-bar {
    bottom: 12px; inset-inline: 12px; width: auto;
    max-height: calc(100vh - 24px);
  }
  .bot { bottom: calc(24px + var(--consent-h, 0px) + 12px); }
  .to-top { bottom: calc(98px + var(--consent-h, 0px) + 12px); }
}
/* Below 420px the two actions stop sharing a row: at 320px an equal split
   gives each 134px, and "Reject optional data" needs three lines in that.
   Stacked, they stay the same size as each other — which is the property that
   matters — and each gets the full width of the card. */
@media (max-width: 420px) {
  .consent-card { padding: 16px; }
  .consent-actions { flex-direction: column; }
  .consent-btn { flex: none; width: 100%; }
  .consent-panel-actions { display: block; }
  .consent-panel-actions .consent-btn { width: 100%; min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* The panel still appears; it just stops travelling to get there. */
  .consent-bar { transition: none; transform: none; }
  .consent-switch-track, .consent-switch-track::after { transition: none; }
}
