/* ═══════════════════════════════════════════════════════════════════════════
   Wheel Swap — the shared layer.

   Two hand-written pages carry this product: the landing page and the studio.
   They had been drifting apart, each with its own copy of the same tokens, so a
   change to the palette meant finding it twice and a component built on one page
   was unavailable to the other. This file is the part they agree on.

   THE BRAND IS FIXED. The logo and these colours are not design decisions to be
   revisited — they are the identity, and everything here is built around them:

     #0B0E10  near black   the ground everything sits on
     #F7F7F7  off white    text
     #F4B23D  amber        the one accent
     #5B6770  slate        quiet text and hairlines
     #45584A  military green ┐ finishes the product actually sells. They
     #6B5236  bronze          │ appear only where a finish is being shown,
     #1F3A5F  navy            ┘ never as decoration.

     "Military green" is PaintChoices.cs's own name for #45584A — the word
     that is actually assembled into the prompt sent to the model. The token
     below used to be named after a different word ("matte"), which drifted
     from what the server calls this colour; the variable is named to match
     the source of truth instead of a description invented for this file.

   The rule that makes the accent mean something: AMBER IS WHAT CHANGED. The
   active chip, the comparison seam, the arrow beside a requested modification.
   Nothing else is amber. A visitor learns the colour's meaning in the hero and
   carries it into the studio, which is worth more than any amount of it used
   for emphasis.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  color-scheme: dark;

  /* ── brand, unchanged ── */
  --near-black:#0B0E10; --off-white:#F7F7F7; --amber:#F4B23D; --slate:#5B6770;
  --military-green:#45584A; --bronze:#6B5236; --navy:#1F3A5F;

  /* ── surfaces, stepping up from the ground ── */
  --void:#06080A; --surface:#12161A; --raised:#191F24; --hover:#1E252B;

  /* ── ink ── */
  --ink:var(--off-white); --ink-soft:#B9C0C7; --muted:var(--slate);

  /* ── lines. Structure, not decoration: this product is a measuring tool and
        should read like one. ── */
  --rule:#222A31; --rule-soft:#171D22;

  --amber-bright:#FFC766; --amber-deep:#9A6E1F;
  --green:#5FAE86; --red:#E0705C;

  --sans:"Golos Text","Noto Sans Georgian",system-ui,-apple-system,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,Consolas,monospace;

  /* ── spacing, a 4px base. Named by size so a gap is chosen, not invented. ── */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:72px; --s9:96px;

  /* ── radii. Deliberately restrained. A page of soft rectangles reads as a
        template; hardware reads as edges. Full round is reserved for the one
        control you drag. ── */
  --r:10px; --r-sm:6px; --r-xs:4px;

  --shell:1180px;
  --rail:248px;

  /* ── motion. Short, and eased out so it settles rather than arrives. ── */
  --quick:140ms; --settle:240ms;
  --ease:cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { box-sizing:border-box; }

/* Any rule that sets an explicit display beats the hidden attribute, and both
   pages toggle visibility from script in dozens of places. Stating it once is
   the difference between "hidden" meaning hidden and meaning nothing. */
[hidden] { display:none !important; }

body {
  margin:0;
  background:var(--near-black);
  color:var(--ink);
  font-family:var(--sans);
  -webkit-font-smoothing:antialiased;
}

img { max-width:100%; display:block; }
button, input, select, textarea { font-family:inherit; }
a { color:inherit; }

:focus-visible { outline:2px solid var(--amber); outline-offset:3px; border-radius:var(--r-xs); }

@media (prefers-reduced-motion:reduce) {
  html { scroll-behavior:auto; }
  *, *::before, *::after {
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.01ms !important;
  }
}

/* ── type ──────────────────────────────────────────────────────────────────
   One scale, used by both pages. Display sizes are fluid so a 1440 screen and
   a 390 phone both get a headline that fills its measure rather than one that
   was designed for the other. */
h1, h2, h3, h4 { margin:0; letter-spacing:-0.025em; text-wrap:balance; font-weight:800; }
h1 { font-size:clamp(38px, 5.6vw, 66px); line-height:1.03; }
h2 { font-size:clamp(26px, 3.2vw, 38px); line-height:1.12; }
h3 { font-size:19px; line-height:1.25; letter-spacing:-0.015em; font-weight:700; }

/* The instrument-panel voice: small, wide, upper. Used for metadata and
   section labels, never for anything a visitor has to read at length. */
.eyebrow, .meta {
  font-family:var(--mono); text-transform:uppercase; letter-spacing:0.2em;
  font-size:10.5px; margin:0;
}
.eyebrow { color:var(--amber); }
.meta { color:var(--muted); letter-spacing:0.14em; }

.lede { color:var(--ink-soft); font-size:17.5px; line-height:1.6; max-width:54ch; margin:0; }

/* ── layout ─────────────────────────────────────────────────────────────── */
.shell { max-width:var(--shell); margin:0 auto; padding:0 var(--s5); }
@media (max-width:560px) { .shell { padding:0 18px; } }

/* ── buttons ────────────────────────────────────────────────────────────── */
.cta {
  display:inline-block; border:0; cursor:pointer; text-decoration:none;
  font-weight:700; font-size:14.5px; padding:11px 20px; border-radius:var(--r-sm);
  background:var(--amber); color:var(--near-black); white-space:nowrap;
  transition:background var(--quick) var(--ease), transform var(--quick) var(--ease);
}
.cta:hover { background:var(--amber-bright); }
.cta:active { transform:translateY(1px); }

.cta.ghost { background:transparent; color:var(--ink); border:1px solid var(--rule); }
.cta.ghost:hover { border-color:var(--slate); background:var(--surface); }

.cta.big { font-size:16px; padding:14px 26px; }

/* Touch targets. Below 44px a control is a coin toss on a phone, and the
   language switch — the control a Georgian visitor reaches for first — was
   28px tall. */
@media (pointer:coarse) {
  .cta { min-height:44px; padding-top:12px; padding-bottom:12px; }
  .langs button { min-height:40px; min-width:40px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   The comparison stage.

   Shared, and that sharing is the point: the landing page uses it to prove the
   product works, and the studio uses it to show a customer their own render.
   Two implementations of this would drift, and the one nobody was looking at
   would be the broken one.

   Revealed with clip-path rather than by resizing a wrapper, so the "after"
   photograph is never squashed as the handle moves. The two frames stay in
   register at every position, which is the whole point of a comparison — and
   it is why every frame fed to it must share exact pixel dimensions.
   ═══════════════════════════════════════════════════════════════════════════ */
.compare {
  /* display:block is the load-bearing line, and it is here rather than at the
     call sites because a component that positions its own children must own the
     box they are positioned against.

     The landing page writes <figure class="compare">, which is block already, so
     this rule changed nothing there and the comparison worked from the day it
     shipped. The studio writes <span class="compare">, which is INLINE — and an
     inline element's containing block for absolutely positioned descendants is
     built from its own inline fragments. Every child here is display:block, so
     those fragments are empty and the containing block is ZERO WIDTH.

     Measured in the browser, the same markup under both:

                       inline (studio)   block (landing)
       knob  left:50%        x = -21          x = 278
       seam  left:50%        x =   1          x = 300
       reveal inset:0    width =   0      width = 598

     So the studio's Compare tab drew the before photograph with a zero-width
     after frame on top of it and the handle jammed against the left edge, and
     dragging rewrote percentages of nothing. "Does not move an inch" was the
     literal truth, and no amount of work in compare.js could have moved it.

     overflow:hidden is inert on an inline box too, which is the same bug wearing
     a different hat: the frame rendered 385px tall where it should be 338. */
  display:block;
  position:relative; margin:0; overflow:hidden;
  border:1px solid var(--rule); border-radius:var(--r); background:var(--void);
  touch-action:pan-y;
}
/* height:auto is load-bearing. The width and height attributes on the markup are
   there so the browser can reserve the space before the photograph arrives — but
   the height attribute is a presentational hint that maps to the CSS height
   property, and it beats aspect-ratio. Without this the frame renders at the
   image's full pixel height, 781px tall inside a 627px column. */
/* BOTH HALVES, ONE RULE, ONE VALUE — and that is the whole point of writing them
   together rather than apart.

   They used to be apart, and the cascade split them down the middle. `.compare .reveal
   img` is specificity (0,2,1); `.compare > img` is (0,1,1). The studio nests this
   component inside `.stagepic`, whose own `.stagepic img` rule is also (0,1,1) and loads
   after this file — so the AFTER half kept `cover` and the BEFORE half lost its tie and
   took the studio's `contain`. Measured on production:

       before   object-fit: contain   height: auto
       after    object-fit: cover     height: 100%

   One half letterboxed the whole car small; the other cropped to fill and cut the rear
   wheel off the frame. A before-and-after showing two different crops at two different
   scales is not a comparison, it is two pictures — and it shipped that way, because the
   seam moved correctly and nobody checked what was either side of it.

   contain, not cover, and that is a product decision rather than a tidy-up. A phone
   photograph of a car side-on is around 2.2:1; cover crops roughly a fifth off the sides
   of it to reach 16:9, and on a side-on car the sides are where the wheels are. The one
   thing this frame exists to show is the thing cover throws away. */
.compare > img,
.compare .reveal img {
  width:100%; height:100%; display:block;
  aspect-ratio:16/9; object-fit:contain; object-position:center;
}

/* Named .reveal, not .after. It was .after, which is also the modifier on the
   AFTER label — so `inset:0` matched the label too and stretched it across the
   whole frame. One word, two meanings, in the same component. */
.compare .reveal { position:absolute; inset:0; }
.compare .reveal img { height:100%; }

/* The seam. Amber, because amber is what changed. */
.compare .seam {
  position:absolute; top:0; bottom:0; width:2px; background:var(--amber);
  pointer-events:none; box-shadow:0 0 0 1px rgba(6,8,10,0.35);
}

.compare .knob {
  position:absolute; top:50%; width:44px; height:44px; margin:-22px 0 0 -22px;
  border-radius:50%; background:var(--amber); color:var(--near-black);
  display:grid; place-items:center; font-size:15px; font-weight:700;
  cursor:ew-resize; border:0; box-shadow:0 4px 20px rgba(0,0,0,0.55);
  touch-action:none;
}

/* Before / After. Stated rather than left to be inferred — a comparison whose
   sides are unlabelled makes the visitor work out which half is the product. */
.compare .stamp {
  position:absolute; top:14px; font-family:var(--mono); font-size:9.5px;
  letter-spacing:0.16em; text-transform:uppercase; padding:5px 10px;
  border-radius:var(--r-xs); background:rgba(6,8,10,0.82); color:var(--off-white);
  border:1px solid rgba(247,247,247,0.16); pointer-events:none;
}
.compare .stamp.before { left:14px; }
.compare .stamp.after  { right:14px; color:var(--amber); border-color:rgba(244,178,61,0.35); }

@media (max-width:560px) {
  .compare .stamp { top:10px; font-size:9px; padding:4px 8px; }
  .compare .stamp.before { left:10px; }
  .compare .stamp.after { right:10px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   The price card.

   One renderer draws these — /packs.js — and until now three pages carried three
   copies of the CSS it renders into, with the modifier for "nothing can be
   bought" sitting down here on its own, separated from the rules it modifies.
   That is how the homepage and /pricing drifted apart in the first place.

   The component lives with its renderer's other half now. A page supplies a
   container and the section around it; everything inside the card is here.
   ═══════════════════════════════════════════════════════════════════════════ */
.tiers { display:grid; gap:var(--s4);
         grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr)); }
.tier { border:1px solid var(--rule); border-radius:var(--r); background:var(--surface);
        padding:24px 22px 22px; display:flex; flex-direction:column; position:relative; }
.tier.best { border-color:var(--amber); background:var(--raised); }
.tier .flag { position:absolute; top:-10px; left:20px; background:var(--amber);
              color:var(--near-black); font-family:var(--mono); font-size:9.5px;
              letter-spacing:0.16em; text-transform:uppercase; padding:4px 10px;
              border-radius:var(--r-xs); font-weight:700; }
.tier h3 { margin:0 0 10px; font-size:17px; }
.tier .figure { font-size:38px; font-weight:800; letter-spacing:-0.035em; line-height:1;
                margin:0; }
.tier .credits { color:var(--amber); font-weight:700; font-size:15px; margin:9px 0 0; }
.tier .who { color:var(--muted); font-size:14px; line-height:1.5; margin:12px 0 18px; flex:1; }
.tier .each { font-family:var(--mono); font-size:10.5px; letter-spacing:0.1em;
              color:var(--muted); margin:0 0 18px; }
.tier .cta { text-align:center; }

/* The card that stands where the prices were when credits are not sold on the website —
   see packs.js contactCard. Alone in the grid it would stretch across the whole shell. */
.tier.contact { max-width:44rem; }
.tier.contact .write { font-size:15px; margin:0 0 18px; }
.tier.contact .write a { color:var(--amber); }
.tier.contact .freeline { color:var(--muted); font-size:13px; margin:14px 0 0; }

/* ── a price nobody can act on ─────────────────────────────────────────────
   Card payments are not switched on, so every price on the site is currently a
   statement of intent. The card says so in its own colour rather than putting a
   confident button next to a number and letting the visitor find out three
   clicks later that nothing can be bought. */
.tier.unbuyable .figure { color:var(--ink-soft); }
.tier.unbuyable .credits { color:var(--slate); }
.notyet {
  grid-column:1 / -1;
  margin:0;
  padding:14px 18px;
  border:1px solid var(--rule);
  border-left:3px solid var(--amber);
  border-radius:var(--r);
  background:var(--surface);
  color:var(--ink-soft);
  font-size:14px;
  line-height:1.55;
}

/* ═══════════════════════════════════════════════════════════════════════════
   The partner strip.

   Every site in this category has a scrolling row of customer logos, and we have
   no customers. Borrowing wheel manufacturers' marks to fill it would claim
   relationships that do not exist, so the strip shows what is actually true: the
   slots are open and unfilled, and the last card is how you take one.

   An empty shelf is not a weakness on a pre-launch page. It is the offer.

   The track is duplicated in the markup and translated by half its own width, so
   the loop has no seam. Paused on hover, and stopped outright under
   prefers-reduced-motion by the global rule above — which is why the strip must
   read correctly standing still, and does: it is a row of cards, not an
   animation with cards in it.
   ═══════════════════════════════════════════════════════════════════════════ */
.marquee {
  overflow:hidden; position:relative;
  border-block:1px solid var(--rule-soft);
  padding:var(--s5) 0;
  /* Faded at both ends so the row reads as continuing rather than as cut off. */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.marquee .track {
  display:flex; gap:var(--s4); width:max-content;
  animation:ws-slide 34s linear infinite;
}
.marquee:hover .track, .marquee:focus-within .track { animation-play-state:paused; }
@keyframes ws-slide { to { transform:translateX(-50%); } }

.slot {
  flex:none; width:190px; min-height:88px;
  display:grid; place-content:center; text-align:center; gap:6px;
  border:1px dashed var(--rule); border-radius:var(--r);
  background:var(--void); color:var(--muted);
  font-family:var(--mono); font-size:10px; letter-spacing:0.16em; text-transform:uppercase;
}
.slot.open { border-style:solid; border-color:var(--amber-deep); color:var(--amber); }
a.slot { text-decoration:none; }
a.slot:hover { background:var(--surface); }

/* ═══════════════════════════════════════════════════════════════════════════
   The guided tour — see tour.js, which is the only thing that builds these.

   A spotlight rather than a dimmed overlay with a cut-out, because the cut-out
   has to follow an element that moves when the page scrolls or the viewport
   changes, and one enormous box-shadow tracks that with two numbers instead of
   four rectangles. The hole is not clickable — pointer-events stay off the
   whole veil except the popover, so a customer being shown what Generate does
   cannot press Generate by accident while reading about it.
   ═════════════════════════════════════════════════════════════════════════ */
.tourveil { position:fixed; inset:0; z-index:90; pointer-events:auto; }

.tourspot {
  position:fixed; border-radius:var(--r);
  box-shadow:0 0 0 9999px rgba(6,8,10,0.72);
  border:2px solid var(--amber);
  /* The four geometric properties only, never `all`. `all` also animates that
     9999px box-shadow, which is the most expensive thing on the element and
     changes by nothing — and it leaves getBoundingClientRect reporting where
     the spotlight USED to be for the length of the animation, which anything
     reading the box mid-move gets wrong. */
  transition:top var(--settle, 320ms) var(--ease), left var(--settle, 320ms) var(--ease),
             width var(--settle, 320ms) var(--ease), height var(--settle, 320ms) var(--ease);
  pointer-events:none;
}

.tourpop {
  position:fixed; z-index:91; width:min(320px, calc(100vw - 28px));
  background:var(--surface); border:1px solid var(--rule); border-radius:var(--r);
  padding:16px 18px 14px; box-shadow:0 18px 44px rgba(0,0,0,0.5);
  transition:top var(--settle, 320ms) var(--ease), left var(--settle, 320ms) var(--ease);
}
.tourpop h3 { margin:0 0 6px; font-size:15.5px; letter-spacing:-0.015em; }
.tourpop .tourbody { margin:0; font-size:13.5px; line-height:1.6; color:var(--ink-soft); }
.tourpop .tourcount {
  margin:0 0 8px; font-family:var(--mono); font-size:9.5px; letter-spacing:0.18em;
  text-transform:uppercase; color:var(--muted);
}

.tourrow { display:flex; align-items:center; gap:8px; margin-top:16px; }
.tourrow .tourskip {
  border:0; background:none; cursor:pointer; padding:6px 2px;
  color:var(--muted); font:inherit; font-size:12.5px; margin-right:auto;
}
.tourrow .tourskip:hover { color:var(--ink); }
.tourrow .btn { padding:8px 16px; font-size:13.5px; }

/* Under 600px the popover is a sheet at the foot of the screen instead of a
   box beside the control. There is no room to sit beside anything on a phone,
   and a 320px card floating over a 375px viewport covers the very thing it is
   pointing at. tour.js still positions it; these rules win. */
@media (max-width:600px) {
  .tourpop {
    top:auto !important; left:0 !important; right:0;
    width:auto; border-radius:var(--r) var(--r) 0 0;
    border-left:0; border-right:0; border-bottom:0;
    bottom:0; padding-bottom:calc(14px + env(safe-area-inset-bottom, 0px));
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   API keys, in the account panel. See keys.js.

   The freshly-issued key gets amber and a border and sits above everything,
   because it is the only thing on this page that cannot be fetched again. A
   customer who scrolls past it has lost their key.
   ═════════════════════════════════════════════════════════════════════════ */
.keysrow { display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-top:14px; }

/* The name field, which replaced a window.prompt. flex:1 with a min-width so the row
   still wraps to one control per line on a phone instead of squeezing the field to
   nothing beside two buttons. */
.keysrow input[type="text"] {
  flex:1 1 220px; min-width:180px; font:inherit; font-size:14px;
  color:var(--off-white); background:var(--void);
  border:1px solid var(--rule); border-radius:var(--r-sm); padding:10px 12px;
}
.keysrow input[type="text"]::placeholder { color:var(--muted); }
.keysrow input[type="text"]:focus-visible { outline:2px solid var(--amber); outline-offset:1px; }

/* Visible to a screen reader, not on screen. The key field's label: the placeholder
   names the field for somebody who can see it and disappears the moment they type,
   which is exactly when somebody using a screen reader still needs it. */
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* ═══ THE KEYS TABLE ═══
   Usage is a set of numbers per key, and numbers meant to be compared belong in
   columns. tabular-nums so the digits line up and the big one is obvious without
   being read — which is the whole reason this stopped being a sentence per key. */
.keystable { overflow-x:auto; margin-top:4px; }
.keystable table { width:100%; border-collapse:collapse; min-width:520px; }

.keystable th {
  text-align:left; padding:0 12px 8px 0;
  border-bottom:1px solid var(--rule);
  font-family:var(--mono); font-size:10px; letter-spacing:0.14em;
  text-transform:uppercase; color:var(--muted); font-weight:500; white-space:nowrap;
}
.keystable td {
  padding:11px 12px 11px 0; border-bottom:1px solid var(--rule-soft); vertical-align:top;
}
.keystable tr:last-child td { border-bottom:0; }

.keystable th.num, .keystable td.num {
  text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap;
}
.keystable td.num { font-family:var(--mono); font-size:13px; }
.keystable td.soft { color:var(--muted); font-size:12px; }

.keystable .keyname { font-size:14px; font-weight:600; }
.keystable .keyfacts { font-family:var(--mono); font-size:11px; color:var(--muted); margin-top:3px; }

.keystable tr.dead .keyname,
.keystable tr.dead .keyfacts { color:var(--muted); text-decoration:line-through; }

.keystate { font-family:var(--mono); font-size:10px; letter-spacing:0.12em;
            text-transform:uppercase; }
.keystate.off { color:var(--muted); }

/* Small enough to sit in a table cell without setting the row height. */
.btn.small { font-size:12px; padding:5px 10px; }

.keysnone { color:var(--muted); }

.keynew {
  border:1px solid var(--amber-deep); border-radius:var(--r);
  background:var(--raised); padding:16px 18px; margin-bottom:18px;
}
.keynew .keyonce {
  margin:0 0 12px; font-size:13px; line-height:1.55; color:var(--amber);
}
.keynew .keyvalue {
  display:block; font-family:var(--mono); font-size:12.5px; line-height:1.6;
  word-break:break-all; user-select:all;
  background:var(--void); border:1px solid var(--rule); border-radius:var(--r-sm);
  padding:11px 13px; color:var(--ink);
}
.keynew .keysrow { margin-top:12px; }
