/* Livery Studio.

   The ground is neutral grey on purpose. A paint booth and a colour matching
   bay are built neutral so the paint on the panel reads true, and this tool
   exists to judge a colour on a car, so its chrome must not tint the thing it
   is judging. Every surface here is the same grey family, separated by
   hairlines rather than by tint.

   Red is the one saturated thing we chose. It marks the brand, the single
   filled action, and whatever is currently active. Nothing else gets it,
   because the car is the saturated object on this screen and the interface
   must not compete with it.

   Depth is committed flat. No shadow, no blur, no glow anywhere: surfaces meet
   at a drawn line the way panels meet on a body shell.
*/

/* Bergen Sans, self-hosted, so it renders the same on every machine rather than
   only where the font happens to be installed. Weights map to the cuts we have:
   Regular carries 400 and 500, SemiBold 600, Bold everything heavier. */
@font-face {
  font-family: 'Bergen Sans';
  src: url('fonts/BergenSans-Regular.otf') format('opentype');
  font-weight: 400 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bergen Sans';
  src: url('fonts/BergenSans-SemiBold.otf') format('opentype');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bergen Sans';
  src: url('fonts/BergenSans-Bold.otf') format('opentype');
  font-weight: 700 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Bergen Sans';
  src: url('fonts/BergenSans-Italic.otf') format('opentype');
  font-weight: 400 500; font-style: italic; font-display: swap;
}

:root {
  /* Three grounds and an inset well. */
  --stage: #0d0e0f;
  --chrome: #17191a;
  --raised: #212426;
  --inset: #101213;

  /* The seam between two surfaces. */
  --rule: #2b2e30;
  --rule-soft: #212426;
  /* The boundary of anything you can operate. It is lighter than a seam
     on purpose: a control has to be findable, and 3:1 is the floor. */
  --edge: #646a6d;
  --edge-hot: #9aa0a3;

  --ink: #edeeef;
  --ink-2: #a5abad;
  --ink-3: #868c8f;

  /* The colour on the car, mirrored back by ui-shell.js. */
  --paint: #e10600;
  /* SIMRPM's own colour. It belongs to the brand and to the one action that
     finishes the job, and to nothing else, which is what keeps it meaning
     "this app" rather than "this control". */
  --brand: #22d2d1;
  --brand-hot: #6ce0df;
  --flag: var(--brand);
  --flag-hot: var(--brand-hot);
  --flag-wash: rgba(50, 217, 208, .14);
  --live: #4ade80;

  /* Every tool gets its own colour, the way a paint rack does and the way a
     livery editor in a game does. It is how you find the panel you want without
     reading. They are spread right around the wheel and deliberately kept clear
     of the brand teal, so a tool can never be mistaken for the brand.
     --tool-ink is what stays legible on top of each one. */
  --c-red: #ff3b2f;
  --c-amber: #ffb302;
  --c-magenta: #ff3ea5;
  --c-violet: #b45cff;
  --c-blue: #4a7bff;

  --tool: var(--c-red);
  --tool-ink: #fff;

  --r: 8px;
  --r-sm: 6px;
  --r-card: 12px;

  /* Two curves. The press is a console switch: it goes and it stops, no tail.
     The draw is a tool tray coming out on its rail, quick off the mark and
     settling onto its stop. */
  --press: cubic-bezier(.35, 0, .2, 1);
  --draw: cubic-bezier(.22, .72, 0, 1);
  /* An engine at idle: even going in, even coming out. */
  --hum: cubic-bezier(.45, 0, .55, 1);

  --t-press: 90ms;
  --t-state: 160ms;
  --t-panel: 200ms;

  --rail: 78px;
  --topbar: 52px;
  --panel-w: 322px;

  /* Floating chrome sits over a lit 3-D scene, so it earns a real shadow: it is
     the one place depth is honest rather than decorative, because the surface
     genuinely hovers above the car. */
  --float-shadow: 0 14px 34px -10px rgba(0, 0, 0, .72), 0 3px 10px rgba(0, 0, 0, .45);
}

/* Greek is longer than English, and these labels sit in a column that was
   measured against English.

   "Rotate" is 54 px of tracked micro capitals and "Περιστροφή" is 66, so every
   turn slider in the product had its own label running underneath it the
   moment the language changed. The column is a variable rather than a number
   so the rows still line up with each other — a label column that sized itself
   per row would fix the overlap and lose the alignment, which is the thing the
   column is for. */
html[lang="el"] { --label-col: 74px; --hud-label-col: 70px; }

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--stage);
  color: var(--ink);
  font-family: 'Bergen Sans', system-ui, sans-serif;
  font-size: 13px;
  font-synthesis: none;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

h1, h2, h3, p { margin: 0; }
p { text-wrap: pretty; }
h1, h2, h3 { text-wrap: balance; }
button, input, output { font: inherit; color: inherit; }
button { cursor: pointer; }
button:disabled { cursor: not-allowed; }

/* A tracked micro capital is the label voice throughout: it is the stamp on a
   setup sheet, and it keeps labels from competing with the values beside them. */
.field > label,
.field-label,
.rail-label,
.state-pill,
.file-control > span,
.control-row > label,
.paint-effects label > span {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
}

/* Values are read in columns, so their figures line up. */
output,
.variant-stats,
.variant-meta,
.skin-name,
.stage-status,
.number-preview,
.number-input {
  font-variant-numeric: tabular-nums;
}

:focus-visible { outline: 2px solid var(--flag-hot); outline-offset: 1px; border-radius: 0; }

.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.icon {
  width: 17px; height: 17px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
symbol { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }


/* One accent for the whole tool, and it is the brand's. Colour-coding five
   panels was my invention; with five labelled tools it bought nothing that the
   labels do not already do, and it put four colours on screen competing with
   the one that means SIMRPM. The per-tool hook stays wired in case a future
   tool genuinely needs to stand apart. */
.toolpanel, .rail-button, .sticker-hud { --tool: var(--brand); --tool-ink: #06241f; }

/* The shell */

.app-shell {
  height: 100dvh;
  display: grid;
  grid-template-rows: var(--topbar) minmax(0, 1fr);
}

/* The topbar */

.topbar {
  position: relative; z-index: 10;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 0 12px;
  background: linear-gradient(180deg, #202426 0%, #171a1c 68%, #131618 100%);
  border-bottom: 1px solid #05070800;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 1px 0 #050708;
}

.topbar-left { display: flex; align-items: center; gap: 14px; min-width: 0; }
.topbar-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }

/* Discord lives beside the mark, not among the editing controls - joining is
   a way out of the tool, not an action on the car. The mark itself carries
   Discord's own blurple square, so the button around it stays the same quiet
   grey every other secondary button wears - two blurple shapes touching would
   read as one smear, not a logo. */
.button-discord {
  background: linear-gradient(180deg, #2b2f31, #1d2022);
  border-color: var(--edge);
  color: var(--ink-2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
}
.button-discord .icon { width: 20px; height: 20px; }
.button-discord:hover { border-color: #5865f2; color: var(--ink); }
.button-discord:active { transform: translateY(1px); box-shadow: none; }

/* Reuses .segmented's own two-state pattern rather than a bespoke toggle,
   sized down to sit in the topbar next to the mark. --tool has no value out
   here (that custom property only exists inside a tool panel), so the
   selected state names the brand colour directly instead of inheriting it. */
.lang-switch { flex: none; width: 74px; }
.lang-switch button { height: 26px; font-size: 11px; letter-spacing: .02em; }

.brand { display: flex; align-items: center; gap: 8px; color: var(--ink); text-decoration: none; }
.brand-word {
  display: inline-block; flex: none;
  font-size: 17px; font-weight: 800;
  letter-spacing: .015em; text-transform: uppercase; line-height: 1;
  color: var(--ink);
  transform: skewX(-9deg);
}
.brand-word em { font-style: normal; color: var(--brand); }

/* An early-access product should say so where the visitor first looks, not
   bury it in a help dialog. Sized to sit beside the mark without competing
   with it. */
.beta-tag {
  flex: none;
  display: inline-flex; align-items: center;
  height: 18px; padding: 0 7px;
  border: 1px solid var(--brand); border-radius: 999px;
  color: var(--brand);
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  line-height: 1;
}

.topbar-center {
  display: flex; align-items: center; justify-content: center; gap: 0; min-width: 0;
  height: 30px; padding: 0 14px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, .55);
}
/* With the status lamp empty the box holds only the title, so it centres it
   rather than leaving the pill's old gap on one side. */
.topbar-center:not(:has(.state-pill:not(:empty))) { padding: 0 16px; }
.topbar-center h1 {
  font-size: 14px; font-weight: 700; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 34ch;
}

/* The state tag reads like a status lamp: a dot, then what it is doing. */
.state-pill {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  height: 24px; margin-left: 12px; padding: 0 10px 0 12px;
  border: 0; border-left: 1px solid var(--edge);
  border-radius: 0;
  background: transparent;
  color: var(--ink-3);
  font-weight: 700;
  white-space: nowrap;
}
/* No status is a status the visitor does not need: before a car is loaded the
   lamp says nothing and takes no room. */
.state-pill:empty { display: none; }
.state-pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; opacity: .9;
  box-shadow: 0 0 7px currentColor;
  animation: lamp-wait 1.7s var(--hum) infinite;
}
.state-pill.state-ready::before { animation: none; }
/* The Decal Studio's pill states a fact - the canvas is transparent - rather
   than waiting for anything, and it used a modifier no sheet defined, so its
   lamp blinked for ever. An indicator that always blinks is one nobody reads. */
.state-pill.state-idle::before { animation: none; opacity: .5; }
@keyframes lamp-wait { 50% { opacity: .18; } }
.state-pill.state-ready { color: var(--live); }

/* Buttons. The filled treatment belongs to one action on the screen, the
   export, so that the hierarchy of intent is legible without reading. */

.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 32px; padding: 0 14px;
  border: 1px solid transparent; border-radius: var(--r);
  font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; white-space: nowrap;
  transition: background var(--t-state) var(--press),
              border-color var(--t-state) var(--press),
              color var(--t-state) var(--press);
}
.button.block { width: 100%; }
.button:active:not(:disabled) { transform: translateY(1px); box-shadow: none; transition-duration: var(--t-press); }

.button-quiet {
  background: linear-gradient(180deg, #2b2f31, #1d2022);
  border-color: var(--edge); color: var(--ink-2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
}
.button-quiet:disabled { opacity: .38; }
.button-quiet.selected { border-color: var(--tool); color: var(--ink); box-shadow: inset 0 0 0 1px var(--tool); }

/* The one action that finishes the job, and it looks like it. */
.button-accent {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--brand) 72%, #fff) 0%,
    var(--brand) 48%,
    color-mix(in srgb, var(--brand) 68%, #000) 100%);
  border-color: color-mix(in srgb, var(--brand) 45%, #000);
  color: #06241f;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .38);
}
.button-accent:disabled { background: var(--inset); border-color: var(--rule); color: var(--ink-3); box-shadow: none; text-shadow: none; }

/* Inside a panel the main action wears that tool's colour. Red belongs to
   Export alone, which is the only action that ends the job. */
.toolpanel .button-accent:not(:disabled) {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--tool) 74%, #fff) 0%,
    var(--tool) 50%,
    color-mix(in srgb, var(--tool) 74%, #000) 100%);
  border-color: color-mix(in srgb, var(--tool) 52%, #000);
  color: var(--tool-ink); text-shadow: none;
}
.toolpanel .button-accent:not(:disabled):hover { filter: brightness(1.1); }

.icon-button {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: var(--r);
  color: var(--ink-2); background: transparent;
  transition: background var(--t-state) var(--press), color var(--t-state) var(--press);
}
.icon-button:active:not(:disabled) { background: var(--inset); transition-duration: var(--t-press); }
.icon-button:disabled { color: var(--ink-3); opacity: .45; }

.topbar-import { position: relative; }
.import-button { pointer-events: none; }
.import-file-input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
/* The visible button is markup-order BEFORE the file input, so a sibling
   combinator can never match it. Scope to the container instead, which does
   not care about order: without this, tabbing to Import shows nothing. */
.topbar-import:focus-within .import-button { outline: 2px solid var(--flag-hot); outline-offset: 1px; }

/* The workspace. The car owns everything the chrome does not. */

.workspace { position: relative; overflow: hidden; background: var(--stage); }

/* Centred, but "safe": a translated heading or a taller notice can grow this
   stack past what plain centring allows, and centring that ignores its own
   overflow pushes the top line up under the bar above it. `safe center`
   falls back to flush-top once the stack no longer fits, which a fixed
   percentage offset cannot do. The plain centring above it is the fallback
   for a browser that does not parse the safe keyword yet. */
.stage {
  position: absolute; inset: 0; background: var(--stage);
  display: flex; align-items: center; justify-content: center;
  align-items: safe center; justify-content: safe center;
}

/* The bay.

   An empty stage is where a tool says what it is for, so this one is not a
   black box: it is a lit photographic bay with nothing parked in it yet, which
   is what the car chooser of every racing game looks like. It is built from
   markup that was already in the page and switched off.

   The garage cliche is black and neon over a glowing grid floor, so the grid
   stays off and the light comes from what car photography actually uses: a
   seamless cyclorama, two soft boxes overhead, two fill lights washing the
   floor, and a pool of light on the spot where the car will stand.

   This is a depicted space rather than chrome, so it is allowed the light the
   interface around it refuses. It is also only ever on screen while no car is
   loaded, so there is no paint being judged against it. */

/* The bay outline: the lit strip a car is parked inside. NFS marks the spot the
   same way, and it earns its place because it is information rather than
   decoration, it says where the car will stand. Two clipped shapes make the
   ring, so there is no image to fetch and nothing for the GPU to shade. It
   takes the paint colour like the pool does, because the same light is making
   both. */
.stage-grid {
  position: absolute; z-index: 0;
  left: 50%; top: 76%;
  width: min(46%, 640px); height: 210px;
  transform: translate(-50%, -50%) perspective(600px) rotateX(64deg);
  border: 2px solid color-mix(in srgb, var(--brand) 38%, #f4fafc);
  border-radius: 2px;
  box-shadow:
    0 0 16px color-mix(in srgb, var(--brand) 50%, transparent),
    inset 0 0 40px color-mix(in srgb, var(--brand) 14%, transparent);
  opacity: 0;
  animation: bay-strike 620ms var(--draw) 700ms forwards;
}
.stage.real-model-loaded .stage-grid, .stage.analysis-only .stage-grid { display: none; }

/* The room is one surface, so it has no seams. Layered gradients on a single
   full bleed element give the cyclorama its horizon; anything drawn in its own
   box would show that box, which is how a light becomes a decorative blob. */
.garage-room {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
  background:
    radial-gradient(48% 30% at 50% 73%, color-mix(in srgb, var(--brand) 7%, transparent), transparent 74%),
    radial-gradient(86% 56% at 50% 63%, rgba(200, 216, 224, .26), transparent 70%),
    linear-gradient(180deg, #090a0b 0%, #111416 30%, #262b2e 61%, #15181a 80%, #08090a 100%);
}
.stage.real-model-loaded .garage-room, .stage.analysis-only .garage-room { display: none; }

.garage-ceiling, .garage-back-wall, .garage-led-grid { display: none; }

/* The pool of light on the spot where the car will stand. */
.garage-floor {
  position: absolute; inset: 0;
  background:
    radial-gradient(22% 11% at 50% 80%, color-mix(in srgb, var(--brand) 24%, transparent), transparent 74%),
    radial-gradient(40% 21% at 50% 79%, color-mix(in srgb, var(--brand) 11%, transparent), transparent 76%),
    radial-gradient(56% 32% at 50% 79%, rgba(232, 240, 243, .26), transparent 72%);
  transition: background 320ms var(--hum);
}

/* The bay falls into shadow at its edges, which is what makes it read as a
   room rather than as a backdrop. */
.garage-side-wall { position: absolute; top: 0; bottom: 0; width: 30%; }
.garage-side-left { left: 0; background: linear-gradient(90deg, rgba(6, 7, 8, .92), transparent); }
.garage-side-right { right: 0; background: linear-gradient(270deg, rgba(6, 7, 8, .92), transparent); }

/* Two fixtures, drawn as fixtures. A tube that is actually there beats a soft
   patch that is meant to suggest one, and its glow is cast outside its own box
   so nothing clips. This is the one place in the whole interface where a glow
   is honest: the object really is a light. */
.garage-led-strip {
  position: absolute; top: 12%; height: 3px; width: 25%;
  border-radius: 2px;
  background: rgba(244, 250, 252, .95);
  box-shadow:
    0 0 14px 2px rgba(233, 243, 247, .55),
    0 0 52px 12px rgba(206, 226, 236, .26),
    0 0 150px 54px rgba(176, 200, 214, .14);
}
.garage-led-strip-left { left: 10%; transform: rotate(-7deg); }
.garage-led-strip-right { right: 10%; transform: rotate(7deg); }

/* Two low fills washing the floor from the wings. */
.garage-led-grid-side { position: absolute; inset: 0; }
.garage-led-grid-side-left { background: radial-gradient(36% 27% at 12% 73%, rgba(202, 216, 222, .17), transparent 74%); }
.garage-led-grid-side-right { background: radial-gradient(36% 27% at 88% 73%, rgba(202, 216, 222, .15), transparent 74%); }

/* Motion 1. The lights strike one after another the way workshop tubes do when
   the switch goes on, with the stutter a tube actually has. */
.garage-led-strip, .garage-led-grid-side, .garage-floor {
  opacity: 0;
  animation: bay-strike 620ms var(--draw) forwards;
}
.garage-led-strip-left { animation-delay: 140ms; }
.garage-led-strip-right { animation-delay: 250ms; }
.garage-floor { animation: bay-strike 620ms var(--draw) 400ms forwards, bay-hum 9s var(--hum) 1100ms infinite; }
@keyframes bay-hum { 50% { opacity: .86; } }
.garage-led-grid-side-left { animation-delay: 540ms; }
.garage-led-grid-side-right { animation-delay: 620ms; }
@keyframes bay-strike {
  0% { opacity: 0; }
  38% { opacity: .5; }
  45% { opacity: .1; }
  58% { opacity: .92; }
  66% { opacity: .3; }
  100% { opacity: 1; }
}

#viewerCanvas {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  display: none; touch-action: none; cursor: grab;
}
#viewerCanvas.ready { display: block; }
#viewerCanvas:active { cursor: grabbing; }

.empty-stage {
  /* The readability scrim behind the heading is wider than this box by
     design (bled and clipped) - `overflow: auto` on both axes turns that
     intentional overhang into a sideways scrollbar the instant this box can
     scroll at all, so only the vertical axis gets one. */
  z-index: 2; flex: none; max-height: 100%; overflow-y: auto; overflow-x: hidden;
  display: grid; gap: 10px; justify-items: center; text-align: center;
  max-width: 40ch; padding: 0 20px;
  pointer-events: none;
  animation: invite-in 620ms var(--draw) 560ms backwards;
}
@keyframes invite-in { from { opacity: 0; transform: translateY(10px); } }
.empty-stage[hidden] { display: none; }
/* Fluid, with a floor and a ceiling. On a phone this line was reaching four
   lines at a fixed 32px, which is a wall rather than an invitation. */
.empty-stage strong { font-size: clamp(21px, 2.6vw + 12px, 32px); font-weight: 600; letter-spacing: -.025em; line-height: 1.12; text-wrap: balance; }
.empty-stage span { color: var(--ink-3); font-size: 13px; line-height: 1.55; max-width: 34ch; }
/* The stage ignores the pointer so a drag reaches the car under it, but the one
   thing to do here is a real button, so it takes clicks back. */
.empty-import {
  margin-top: 8px; height: 42px; padding: 0 22px;
  font-size: 13px; pointer-events: auto; cursor: pointer;
}
.empty-import .icon { width: 18px; height: 18px; }

.stage-loading {
  position: absolute; z-index: 12; left: 50%; top: 50%;
  min-width: 240px; transform: translate(-50%, -50%);
  display: grid; justify-items: center; gap: 10px; padding: 20px 24px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--chrome);
  text-align: center;
}
.stage-loading[hidden] { display: none; }
.stage-loading strong { font-size: 13px; font-weight: 600; }
#loadingDetail { max-width: 30ch; color: var(--ink-3); font-size: 11.5px; line-height: 1.5; }

/* The upload bar: a real percentage of bytes sent, so the visitor knows exactly
   how far a big car has to go. It shows only while the file is uploading; once
   the bytes are up, the server is building the car and the lights take over. */
.loading-bar {
  display: none; width: 208px; height: 6px; margin-top: 2px;
  border-radius: 3px; background: var(--inset);
  box-shadow: inset 0 0 0 1px var(--edge); overflow: hidden;
}
.loading-bar i {
  display: block; height: 100%; width: var(--upload, 0%);
  background: linear-gradient(90deg, var(--brand), var(--brand-hot));
  transition: width .12s linear;
}
.stage-loading.uploading .loading-bar { display: block; }
.stage-loading.uploading .loading-spinner { display: none; }
/* Motion 2. Five lights fill, hold, then go out together. In this game that
   is not a loading indicator, it is what go looks like. */
.loading-spinner {
  position: relative;
  width: 108px; height: 13px; border: 0; border-radius: 0;
  background-image: radial-gradient(circle at 50% 50%, rgba(216, 40, 28, .22) 0 42%, transparent 46%);
  background-size: 21.6px 13px; background-repeat: repeat-x;
}
.loading-spinner::after {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle at 50% 50%, var(--flag) 0 42%, transparent 46%);
  background-size: 21.6px 13px; background-repeat: repeat-x;
  -webkit-mask-image: linear-gradient(#000, #000); mask-image: linear-gradient(#000, #000);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 0% 100%; mask-size: 0% 100%;
  animation: start-lights 2.2s infinite;
}
@keyframes start-lights {
  0% { -webkit-mask-size: 0% 100%; mask-size: 0% 100%; animation-timing-function: steps(5, start); }
  62% { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
  84% { -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
  85%, 100% { -webkit-mask-size: 0% 100%; mask-size: 0% 100%; }
}

.stage-status {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* The CSS placeholder car, kept for the pre-geometry surrogate flow. */
.car-ghost {
  position: absolute; z-index: 2; left: 50%; top: 50%;
  width: min(70%, 720px); height: 340px;
  transform: translate(-50%, -42%);
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--draw);
}
.car-ghost.ready { opacity: 1; pointer-events: auto; }
.stage.real-model-loaded .car-ghost, .stage.analysis-only .car-ghost { opacity: 0; pointer-events: none; }
.car-shadow { position: absolute; width: 90%; height: 40px; left: 5%; bottom: 9%; border-radius: 50%; background: rgba(0, 0, 0, .8); filter: blur(12px); }
.car-body { position: absolute; left: 4%; right: 4%; bottom: 16%; height: 54%; background: linear-gradient(160deg, #eceded 0 28%, #bcbfc0 29% 38%, #292c2d 39% 70%, #151718 71%); border-radius: 37% 22% 17% 23% / 45% 32% 22% 25%; transform: skew(-8deg); border: 2px solid rgba(237, 238, 239, .3); overflow: hidden; }
.car-roof { position: absolute; width: 42%; height: 85%; top: -46%; left: 29%; border-radius: 55% 45% 0 0; background: linear-gradient(160deg, #363a3b, #131516); border: 3px solid #7d8284; }
.car-glass { position: absolute; width: 30%; height: 31%; top: -29%; left: 35%; border-radius: 28% 28% 6% 6%; background: linear-gradient(150deg, #a8acad, #212425); }
.car-hood { position: absolute; width: 34%; height: 55%; top: 0; left: -8%; background: linear-gradient(160deg, #f7f8f8, #a2a6a7); border-radius: 50%; }
.car-stripe { position: absolute; height: 25%; left: -5%; right: -5%; transform: rotate(-14deg); background: var(--c-red); opacity: .92; }
.stripe-two { top: 61%; height: 8%; background: var(--ink); }
.stripe-one { top: 35%; }
.car-number { position: absolute; right: 18%; top: 18%; color: #0d0e0f; font-size: clamp(46px, 8vw, 84px); font-weight: 700; letter-spacing: -.04em; transform: skew(8deg); opacity: .88; }
.wheel { position: absolute; width: 20%; aspect-ratio: 1; bottom: -18%; border-radius: 50%; background: radial-gradient(circle, #74797b 0 9%, #131516 10% 34%, #363a3b 35% 43%, #090a0b 44%); border: 5px solid #060707; }
.wheel-front { left: 14%; } .wheel-rear { right: 14%; }

.surface-pin { position: absolute; z-index: 3; left: 59%; top: 48%; width: 14px; height: 14px; border-radius: 50%; border: 2px solid #fff; background: var(--flag); box-shadow: 0 0 0 6px var(--flag-wash); animation: pin-pulse 1.6s infinite; }
.surface-pin[hidden] { display: none; }
@keyframes pin-pulse { 50% { box-shadow: 0 0 0 12px rgba(216, 40, 28, 0); } }
.decal-preview { position: absolute; z-index: 3; left: 58%; top: 47%; padding: 7px 12px; border: 2px solid #fff; border-radius: var(--r); background: #0d0e0f; color: #fff; font-size: 16px; font-weight: 600; letter-spacing: .04em; transform-origin: center; transition: transform var(--t-state) var(--draw); }
.decal-preview[hidden] { display: none; }

/* The camera bar */

.camera-controls {
  /* The workspace already starts below the masthead, so this offset is measured
     from under it. Adding the masthead height here pushed the bar a second
     masthead down the screen. */
  position: absolute; z-index: 7; top: 8px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 0;
  padding: 4px;
  border: 1px solid var(--edge); border-radius: var(--r-card);
  background: linear-gradient(180deg, #202426, #14181a);
  box-shadow: var(--float-shadow);
}
.camera-controls[hidden] { display: none; }
.camera-button, .showroom-button {
  position: relative; white-space: nowrap;
  height: 32px; padding: 0 14px;
  border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--ink-3);
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  transition: color var(--t-state) var(--press), background var(--t-state) var(--press);
}
.showroom-button { padding: 0 10px; }
.camera-button.active, .showroom-button.active {
  color: var(--brand);
  background: color-mix(in srgb, var(--brand) 15%, transparent);
  box-shadow: inset 0 0 0 1px var(--brand);
  animation: throw-x 190ms var(--press);
}
.camera-button:active, .showroom-button:active { background: var(--inset); transition-duration: var(--t-press); }
.control-divider { width: 1px; height: 16px; margin: 0 5px; background: var(--rule); }

/* The tool bar floats at the bottom centre, the way a game's customise menu
   does, because that is where the hand rests and it keeps the car uncovered.
   It is a raised console, not an edge the chrome is bolted to. */

.toolrail {
  position: absolute; z-index: 9;
  left: 50%; bottom: 18px; transform: translateX(-50%);
  display: flex; flex-direction: row; gap: 4px;
  padding: 6px;
  border: 1px solid var(--edge); border-radius: var(--r-card);
  background: linear-gradient(180deg, #202426, #14181a);
  box-shadow: var(--float-shadow);
}
/* Nothing to paint until a car is in: the tool bar stays out of the way while
   the stage is empty, so the only thing to do is import. */
.app-shell:not(.is-loaded) .toolrail { display: none; }
.rail-button {
  position: relative;
  width: 76px; padding: 8px 4px 7px;
  display: grid; justify-items: center; gap: 5px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--ink-3);
  transition: background var(--t-state) var(--press), color var(--t-state) var(--press), border-color var(--t-state) var(--press);
}
.rail-button .icon {
  width: 22px; height: 22px; color: var(--ink-3);
  transition: color var(--t-state) var(--press);
}
/* min-width: 0 overrides a grid item's default of never shrinking below its
   own content width, which is what let a long translated label (Greek runs
   longer than English throughout this rail) push past its 76px column into
   the next button instead of wrapping inside its own. */
.rail-label { min-width: 0; width: 100%; line-height: 1.15; letter-spacing: .08em; font-weight: 700; text-transform: uppercase; text-wrap: balance; overflow-wrap: break-word; }
.rail-button:active { transform: translateY(1px); transition-duration: var(--t-press); }
/* The active tool is a boxed teal tile, the customise-menu convention, rather
   than a solid fill: it reads as selected without shouting over the car. */
.rail-button[aria-expanded="true"] {
  background: color-mix(in srgb, var(--brand) 15%, transparent);
  border-color: var(--brand);
  color: var(--brand);
}
.rail-button[aria-expanded="true"] .icon,
.rail-button[aria-expanded="true"] .rail-label { color: var(--brand); }
@keyframes throw-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes throw-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.rail-divider { display: none; }

/* Tool panels. Docked flush against the rail, full height, square where they
   meet it. The panel is part of the chrome; it never resizes the viewport
   under it, because the car is the product. */

.toolpanel {
  position: absolute; z-index: 8;
  left: 18px; bottom: 18px; top: auto;
  width: var(--panel-w);
  max-height: calc(100dvh - var(--topbar) - 36px);
  display: flex; flex-direction: column;
  border: 1px solid var(--edge); border-radius: var(--r-card);
  background: linear-gradient(180deg, #1b1e20 0%, #141719 40%, #111416 100%);
  box-shadow: var(--float-shadow);
  overflow: hidden;
  animation: panel-in var(--t-panel) var(--draw);
}
.toolpanel[hidden] { display: none; }
@keyframes panel-in {
  from { transform: translateY(8px); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.panel-head {
  position: relative; flex: none;
  display: flex; align-items: center; justify-content: space-between;
  height: var(--topbar); padding: 0 8px 0 16px;
  background: linear-gradient(104deg,
    color-mix(in srgb, var(--brand) 18%, transparent) 0 40%,
    transparent 72%);
  border-bottom: 2px solid color-mix(in srgb, var(--brand) 55%, transparent);
}
.panel-head h2 {
  font-size: 12px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink);
}
.panel-body {
  padding: 17px 15px 22px; display: grid; gap: 19px; align-content: start; overflow-y: auto;
  background: linear-gradient(180deg, color-mix(in srgb, var(--tool) 6%, transparent), transparent 220px);
}
.panel-rule { height: 1px; background: var(--edge); margin: 2px -14px; }

.field { display: grid; gap: 8px; }
/* Text labels only. Not the colour field, which is also a <label>, and was
   picking up the tint and a stray marker meant for a caption. */
.field > label:not([class]), .field-label {
  color: color-mix(in srgb, var(--brand) 45%, var(--ink-2));
  font-weight: 700;
}

.inline-pair { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }
.inline-pair .button { padding: 0 11px; }

/* Anything that takes input is recessed below the panel, the way a socket is
   sunk into a bench. */
.color-field {
  position: relative;
  display: flex; align-items: center; gap: 9px;
  height: 32px; padding: 0 9px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset);
  cursor: pointer;
}
.color-field i { width: 16px; height: 16px; flex: none; border-radius: 1px; border: 1px solid rgba(0, 0, 0, .5); background: var(--paint); }
.color-field output { font-size: 11.5px; font-weight: 500; letter-spacing: .04em; color: var(--ink-2); }
.color-field input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
.color-field:focus-within { border-color: var(--tool); box-shadow: 0 0 0 2px color-mix(in srgb, var(--tool) 26%, transparent); }

.segmented { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 1px; padding: 1px; border: 1px solid var(--edge); border-radius: var(--r); background: var(--inset); }
.segmented button {
  position: relative;
  height: 30px; border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--ink-3);
  font-size: 12px; font-weight: 700;
  transition: background var(--t-state) var(--press), color var(--t-state) var(--press);
}
.segmented button:active { background: var(--stage); transition-duration: var(--t-press); }
.segmented .selected {
  background: var(--tool); color: var(--tool-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
  animation: throw-x 190ms var(--press);
}

/* Which language the site is in belongs to the site, not to whichever tool you
   happen to be standing in — so it wears the brand rather than the tool's own
   colour. It has to be stated after the general rule above: the two have the
   same weight, and written before it, this lost and the switch came out in the
   livery editor's red on all three tools. */
.lang-switch .selected {
  background: var(--brand);
  color: #06241f;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
}

.paint-effects { gap: 14px; }
.paint-effects label { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px; color: color-mix(in srgb, var(--brand) 45%, var(--ink-2)); }
.paint-effects label::before { display: none; }
.paint-effects label[hidden] { display: none; }
.paint-effects output { font-size: 11.5px; font-weight: 600; color: var(--ink-2); }
.paint-effects input[type="range"] { grid-column: 1 / -1; }

input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 18px; background: transparent; }
input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: 1px;
  background: linear-gradient(90deg,
    var(--tool) 0 var(--fill, 0%),
    var(--inset) var(--fill, 0%) 100%);
  box-shadow: inset 0 0 0 1px var(--edge);
}
input[type="range"]::-moz-range-track {
  height: 5px; border-radius: 1px;
  background: linear-gradient(90deg,
    var(--tool) 0 var(--fill, 0%),
    var(--inset) var(--fill, 0%) 100%);
  box-shadow: inset 0 0 0 1px var(--edge);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 6px; height: 16px; margin-top: -6px;
  border-radius: 1px; background: var(--ink); border: 0;
  box-shadow: 0 0 0 1px #000;
}
input[type="range"]::-moz-range-thumb {
  width: 6px; height: 16px; border: 0; border-radius: 1px;
  background: var(--ink); box-shadow: 0 0 0 1px #000;
}
input[type="range"]:active::-webkit-slider-thumb { background: var(--tool); }
input[type="range"]:active::-moz-range-thumb { background: var(--tool); }

.control-row { display: grid; grid-template-columns: var(--label-col, 56px) minmax(0, 1fr) 42px; align-items: center; gap: 10px; }
.control-row > label { color: color-mix(in srgb, var(--brand) 45%, var(--ink-2)); }
.control-row output { text-align: right; font-size: 11.5px; font-weight: 600; color: var(--ink-2); }

.text-input {
  height: 32px; width: 100%; padding: 0 10px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset); color: var(--ink);
  font-size: 13px; outline: 0;
}
.text-input::placeholder { color: var(--ink-3); }
.text-input:focus { border-color: var(--tool); box-shadow: 0 0 0 2px color-mix(in srgb, var(--tool) 26%, transparent); }

.align-row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 1px; padding: 1px; border: 1px solid var(--edge); border-radius: var(--r); background: var(--inset); }
.align-row button {
  position: relative;
  height: 28px; display: grid; place-items: center;
  border: 0; border-radius: 0;
  background: transparent; color: var(--ink-3);
  transition: background var(--t-state) var(--press), color var(--t-state) var(--press);
}
.align-row button:active { background: var(--stage); transition-duration: var(--t-press); }
.align-row .selected {
  background: var(--tool); color: var(--tool-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3);
  animation: throw-x 190ms var(--press);
}

.number-editor { display: grid; grid-template-columns: var(--label-col, 56px) minmax(0, 1fr); align-items: center; gap: 10px; }
.number-preview {
  display: grid; place-items: center; height: 42px;
  font-size: 32px; font-weight: 800;
  letter-spacing: -.05em; color: var(--ink);
  text-shadow: 2px 2px 0 color-mix(in srgb, var(--tool) 70%, #000);
}
.number-input { text-align: center; letter-spacing: .06em; }

.position-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; padding: 1px; border: 1px solid var(--edge); border-radius: var(--r); background: var(--inset); }
.position-pad button { height: 22px; display: grid; place-items: center; border: 0; border-radius: 0; background: transparent; transition: background var(--t-state) var(--press); }
.position-pad button::after { content: ""; width: 3px; height: 3px; border-radius: 50%; background: var(--ink-3); transition: background var(--t-state) var(--press), transform var(--t-state) var(--press); }
.position-pad button.selected { background: color-mix(in srgb, var(--tool) 22%, var(--raised)); }
.position-pad button.selected::after { background: var(--tool); transform: scale(2); }

.pattern-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.pattern-grid button { position: relative; height: 42px; padding: 0; border: 1px solid var(--rule-soft); border-radius: var(--r); background: repeating-linear-gradient(135deg, #131516 0 7px, #45494b 8px 11px); }
.pattern-grid button:nth-child(2) { background: repeating-linear-gradient(155deg, #232628 0 4px, #676c6e 5px 7px); }
.pattern-grid button:nth-child(3) { background: repeating-linear-gradient(45deg, #16181900 0 2px, #2e3234 3px 5px), #191b1c; }
.pattern-grid button:nth-child(4) { background: linear-gradient(145deg, #16181900 0 35%, var(--c-red) 36% 48%, #edeeef 49% 62%, #161819 63%), #161819; }
.pattern-grid button:nth-child(5) { background: radial-gradient(circle at 25% 30%, #a3a8aa 0 5%, transparent 6%), radial-gradient(circle at 68% 72%, #a3a8aa 0 4%, transparent 5%), #272b2c; }
.pattern-grid button:nth-child(6) { background: radial-gradient(circle, #1f2223 0 20%, #52575a 22% 24%, #191b1c 25%) 0 / 14px 14px; }
.pattern-grid button:nth-child(7) { background: conic-gradient(#e4e6e6 25%, #0d0e0f 0 50%, #e4e6e6 0 75%, #0d0e0f 0) 0 / 16px 16px; }
.pattern-grid button:nth-child(8) { background: repeating-linear-gradient(135deg, #0d0e0f 0 11px, #0d0e0f 12px 15px, #c9cdcf 16px 19px, #0d0e0f 20px 32px); }
.pattern-grid button:nth-child(9) { background: radial-gradient(ellipse at 20% 30%, #b1b6b8 0 12%, transparent 13%), radial-gradient(ellipse at 75% 65%, #090a0b 0 22%, transparent 23%), #45494b; }
.pattern-grid button:nth-child(10) { background: linear-gradient(112deg, #0e0f10 0 38%, #eef0f0 39% 45%, var(--c-red) 46% 52%, #0e0f10 53%); }
.pattern-grid button:nth-child(11) { background: repeating-radial-gradient(ellipse at 20% 110%, #16181900 0 5px, #787d7f 6px 9px, #191b1c 10px 15px); }
.pattern-grid button:nth-child(12) { background: linear-gradient(135deg, #eef0f0 0 25%, #0d0e0f 26% 49%, var(--live) 50% 70%, #272b2c 71%); }
.pattern-grid button.pattern-active, .pattern-grid button.selected { border-color: var(--tool); box-shadow: 0 0 0 2px color-mix(in srgb, var(--tool) 34%, transparent); }
.pattern-grid button.pattern-active::after, .pattern-grid button.selected::after {
  content: ""; position: absolute; inset: 0; border: 1px solid rgba(255, 255, 255, .35);
}

.decal-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.decal-grid button {
  min-height: 34px; display: grid; place-items: center; padding: 0 4px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: linear-gradient(180deg, #2a2e30, #1a1d1f); color: var(--ink-2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  transition: background var(--t-state) var(--press), border-color var(--t-state) var(--press), color var(--t-state) var(--press);
}
.decal-grid button:active { background: var(--stage); box-shadow: none; transform: translateY(1px); transition-duration: var(--t-press); }
.decal-grid button.selected { border-color: var(--tool); background: color-mix(in srgb, var(--tool) 18%, var(--inset)); color: var(--ink); }

.file-control { display: grid; gap: 8px; color: var(--ink-3); }
.file-control input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.file-button {
  display: block; height: 32px; line-height: 30px; padding: 0 11px;
  border: 1px dashed var(--edge); border-radius: var(--r);
  background: var(--inset); color: var(--ink-2);
  text-align: center; font-size: 11.5px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  cursor: pointer;
  transition: border-color var(--t-state) var(--press), color var(--t-state) var(--press);
}
.file-control:focus-within .file-button { border-color: var(--tool); border-style: solid; }

.helper { color: var(--ink-3); font-size: 11.5px; line-height: 1.5; }

.variant-list { display: grid; gap: 6px; }
.variant {
  display: grid; gap: 3px; justify-items: start;
  width: 100%; padding: 10px 11px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset); text-align: left;
  transition: background var(--t-state) var(--press), border-color var(--t-state) var(--press);
}
.variant:active:not(:disabled) { background: var(--stage); transition-duration: var(--t-press); }
.variant:disabled { opacity: .4; }
.variant.selected { border-color: var(--tool); background: color-mix(in srgb, var(--tool) 12%, var(--inset)); }
.variant-name { font-size: 13px; font-weight: 600; word-break: break-all; }
.variant-meta { color: var(--ink-2); font-size: 11.5px; word-break: break-all; }
.variant-stats { color: var(--ink-3); font-size: 11.5px; }
.variant.selected .variant-stats { color: var(--tool); font-weight: 700; }

.variant-group { display: grid; gap: 6px; }
.skin-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; padding: 0 0 4px; }
.skin {
  display: grid; gap: 5px; padding: 0;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset); overflow: hidden; text-align: left;
  transition: border-color var(--t-state) var(--press), background var(--t-state) var(--press);
}
.skin-thumb {
  display: block; aspect-ratio: 16 / 9;
  background: var(--stage) center / cover no-repeat;
  border-bottom: 1px solid var(--rule-soft);
}
.skin-thumb.is-empty { background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--tool) 16%, #1c1f21) 0 5px, #121415 6px 10px); }
.skin-name {
  padding: 0 5px 5px; color: var(--ink-3);
  font-size: 10px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.skin.selected { border-color: var(--tool); background: color-mix(in srgb, var(--tool) 14%, var(--inset)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--tool) 26%, transparent); }
.skin.selected .skin-name { color: var(--ink); }

/* The paint switch. A rocker, not a pill: the thing it stands for is a switch
   on a console, and this whole interface is drawn with straight edges. */

.switch-field { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 11px; cursor: pointer; }
.switch-field input { position: absolute; width: 1px; height: 1px; opacity: 0; }
/* A switch reads as a switch because the knob is a knob.

   The track was a pill - 18px tall on an 8px radius rounds it fully - while the
   knob inside it kept `border-radius: 1px`, so every toggle in the product had
   a square block sliding around inside a rounded slot. It looked like a
   rendering fault rather than a control. */
.switch-track { position: relative; width: 32px; height: 18px; border-radius: 999px; background: var(--inset); border: 1px solid var(--edge); transition: border-color var(--t-state) var(--press); }
.switch-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--ink-3); transition: transform var(--t-state) var(--press), background var(--t-state) var(--press); }
.switch-field input:checked + .switch-track { border-color: var(--tool); background: color-mix(in srgb, var(--tool) 22%, var(--inset)); }
.switch-field input:checked + .switch-track::after { transform: translateX(14px); background: var(--tool); }
.switch-field input:focus-visible + .switch-track { outline: 2px solid var(--flag-hot); outline-offset: 2px; }
.switch-text { display: grid; gap: 3px; }
.switch-text strong { font-size: 13px; font-weight: 600; }
.switch-text em { font-style: normal; color: var(--ink-3); font-size: 11.5px; line-height: 1.45; }

/* The sticker list */

.sticker-list { display: grid; gap: 4px; }
.sticker-list .helper { padding: 2px 0; }
.sticker-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px;
  border: 1px solid var(--edge); border-radius: var(--r); background: var(--inset);
  transition: border-color var(--t-state) var(--press), background var(--t-state) var(--press);
}
.sticker-row.selected { border-color: var(--tool); background: color-mix(in srgb, var(--tool) 14%, var(--inset)); }
.sticker-pick {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8px;
  min-width: 0; padding: 7px 4px 7px 8px; border: 0; background: transparent; text-align: left;
}
.sticker-kind {
  flex: none; min-width: 34px; padding: 2px 5px; border-radius: 1px;
  background: var(--rule); color: var(--ink-3);
  font-size: 10px; font-weight: 600; letter-spacing: .09em; text-align: center;
}
.sticker-row.selected .sticker-kind { background: var(--tool); color: var(--tool-ink); }
.sticker-name { min-width: 0; font-size: 13px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sticker-row.selected .sticker-name { color: var(--ink); }
.sticker-drop { width: 26px; height: 26px; margin-right: 3px; }
.sticker-drop .icon { width: 13px; height: 13px; }

/* The selected sticker readout, parked on the stage next to the car it
   describes. Flat and opaque like every other surface here. */

/* The readout belongs to the sticker tool, so it wears the sticker colour
   rather than the default red. */
.sticker-hud {
  position: absolute; z-index: 6; right: 14px; top: 14px; width: 272px;
  border: 1px solid var(--edge); border-radius: var(--r-card);
  background: linear-gradient(180deg, #1b1e20 0%, #141719 40%, #111416 100%);
  box-shadow: var(--float-shadow);
  overflow: hidden;
}
.sticker-hud[hidden] { display: none; }
.hud-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 7px 6px 7px 10px; background: linear-gradient(104deg, color-mix(in srgb, var(--brand) 16%, transparent) 0 42%, transparent 74%); border-bottom: 2px solid color-mix(in srgb, var(--brand) 55%, transparent); }
.hud-kind { padding: 3px 6px; border-radius: 1px; background: var(--tool); color: var(--tool-ink); font-size: 10px; font-weight: 600; letter-spacing: .09em; }
.hud-name { min-width: 0; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hud-body { display: grid; gap: 10px; padding: 11px 10px 12px; }
.hud-field { display: grid; gap: 6px; }
.hud-field > span, .hud-row > label { color: var(--ink-3); font-size: 10px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; }
.hud-row { display: grid; grid-template-columns: var(--hud-label-col, 54px) minmax(0, 1fr) 40px; align-items: center; gap: 8px; }
.hud-row output { text-align: right; font-size: 11.5px; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.hud-inline { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 6px; }
.hud-select {
  height: 30px; padding: 0 6px;
  border: 1px solid var(--edge); border-radius: var(--r);
  background: var(--inset); color: var(--ink-2); font-size: 11.5px;
}
.hud-check { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-3); font-size: 11.5px; cursor: pointer; }
.hud-check input { accent-color: var(--tool); }
.hud-actions { display: flex; gap: 4px; }
.hud-actions .icon-button { width: 32px; height: 28px; border: 1px solid var(--edge); border-radius: var(--r); background: var(--inset); }
.hud-hint { color: var(--ink-3); font-size: 11px; line-height: 1.55; }
@media (pointer: coarse) { .hud-hint { display: none; } }
/* Two hints in a row are one legend, so the panel gap must not push them
   apart into two blocks. The shortcut line sits back a step from the
   sentence above it: read the sentence, glance at the keys. */
.hud-hint + .hud-hint { margin-top: -5px; opacity: .72; }
.sticker-hud.kind-image .hud-text-only { display: none; }

#viewerCanvas.grabbing { cursor: grabbing; }
#viewerCanvas.grabable { cursor: grab; }

/* The guide.

   A centred dialog rather than a panel, because it is read once and dismissed
   rather than worked in. It dims the car behind it so there is one thing to
   look at, and it is the only surface in the app that scrolls a long read. */

.guide-backdrop {
  position: fixed; inset: 0; z-index: 30;
  background: rgba(4, 6, 7, .72);
  animation: fade-in 160ms var(--press);
}
.guide-backdrop[hidden] { display: none; }
@keyframes fade-in { from { opacity: 0; } }

.guide {
  position: fixed; z-index: 31;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(660px, calc(100vw - 32px));
  max-height: min(78vh, 760px);
  display: flex; flex-direction: column;
  border: 1px solid var(--edge); border-radius: var(--r-card);
  background: linear-gradient(180deg, #1b1e20 0%, #141719 40%, #111416 100%);
  box-shadow: 0 30px 70px -18px rgba(0, 0, 0, .85), 0 4px 14px rgba(0, 0, 0, .5);
  overflow: hidden;
  animation: guide-in 200ms var(--draw);
}
.guide[hidden] { display: none; }
@keyframes guide-in {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 10px)); }
}

.guide-head {
  flex: none;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 10px 0 20px; height: 56px;
  background: linear-gradient(104deg, color-mix(in srgb, var(--brand) 18%, transparent) 0 42%, transparent 74%);
  border-bottom: 2px solid color-mix(in srgb, var(--brand) 55%, transparent);
}
.guide-head h2 {
  font-size: 14px; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink);
}

.guide-body { padding: 20px; overflow-y: auto; display: grid; gap: 22px; }

/* The three steps are the whole product in one glance, so they lead and they
   are numbered: this really is a sequence. */
.guide-steps {
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: 10px; counter-reset: step;
}
/* The number is positioned, not a grid cell: a grid would make every inline
   piece of the sentence its own cell and break the line one word at a time. */
.guide-steps li {
  counter-increment: step;
  position: relative; padding-left: 38px; min-height: 26px;
  font-size: 13.5px; line-height: 1.55; color: var(--ink-2);
}
.guide-steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 26px; height: 26px; display: grid; place-items: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--brand) 18%, transparent);
  box-shadow: inset 0 0 0 1px var(--brand);
  color: var(--brand); font-size: 12px; font-weight: 800;
}
.guide-steps b, .guide-tools b, .guide-list b { color: var(--ink); font-weight: 700; }

.guide-section { display: grid; gap: 10px; }
.guide-section h3 {
  font-size: 10px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: color-mix(in srgb, var(--brand) 55%, var(--ink-2));
}
.guide-note { font-size: 13px; line-height: 1.55; color: var(--ink-3); }

/* Two columns: what you press, then what it does. */
.guide-keys, .guide-tools { margin: 0; display: grid; gap: 8px 16px; }
.guide-keys { grid-template-columns: minmax(96px, auto) minmax(0, 1fr); align-items: baseline; }
.guide-keys dt {
  font-size: 12px; font-weight: 700; color: var(--ink);
  white-space: nowrap;
}
.guide-keys dd { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-2); }

.guide-tools { grid-template-columns: minmax(0, 1fr); gap: 4px; }
.guide-tools dt {
  font-size: 12px; font-weight: 800; color: var(--brand);
  letter-spacing: .06em; text-transform: uppercase;
  margin-top: 8px;
}
.guide-tools dt:first-child { margin-top: 0; }
.guide-tools dd { margin: 0 0 2px; font-size: 13px; line-height: 1.6; color: var(--ink-2); }

.guide-list { margin: 0; padding-left: 18px; display: grid; gap: 8px; }
.guide-list li { font-size: 13px; line-height: 1.55; color: var(--ink-2); }

.guide code, .guide kbd {
  font-size: 12px; padding: 1px 5px; border-radius: 4px;
  background: var(--inset); box-shadow: inset 0 0 0 1px var(--edge);
  color: var(--ink);
}

.guide-foot {
  flex: none; display: flex; justify-content: flex-end;
  padding: 14px 20px; border-top: 1px solid var(--rule);
}

@media (max-width: 620px) {
  .guide { max-height: calc(100dvh - 24px); width: calc(100vw - 20px); }
  .guide-body { padding: 16px; gap: 18px; }
  .guide-keys { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .guide-keys dd { margin-bottom: 6px; }
}

/* The toast */

.toast {
  position: fixed; z-index: 20; right: 16px; bottom: 16px;
  max-width: 340px; padding: 12px 15px;
  border: 1px solid var(--edge); border-left: 4px solid var(--live);
  border-radius: var(--r);
  background: var(--chrome); color: var(--ink);
  font-size: 13px; line-height: 1.5;
  opacity: 0; transform: translateY(6px);
  pointer-events: none;
  transition: opacity var(--t-panel) var(--draw), transform var(--t-panel) var(--draw);
}
.toast.visible { opacity: 1; transform: none; }

/* Hover is gated: ungated, a phone fires it on first tap and the state sticks,
   so every control looks stuck on. */

@media (hover: hover) and (pointer: fine) {
  .button-quiet:hover:not(:disabled) { background: var(--rule); border-color: var(--edge-hot); color: var(--ink); }
  .button-accent:hover:not(:disabled) { background: var(--flag-hot); }
  .icon-button:hover:not(:disabled) { background: var(--raised); color: var(--ink); }
  .topbar-actions .icon-button:hover:not(:disabled) { color: var(--brand); }
  .topbar-import:hover .import-button { background: var(--rule); border-color: var(--edge-hot); color: var(--ink); }
  .camera-button:hover, .showroom-button:hover { color: var(--ink); }
  .rail-button:hover { color: var(--ink); background: var(--raised); }
  .color-field:hover { border-color: var(--edge-hot); }
  .segmented button:hover { color: var(--ink); }
  .align-row button:hover { color: var(--ink); }
  .position-pad button:hover::after { background: var(--ink-2); }
  .pattern-grid button:hover { border-color: var(--edge-hot); }
  .decal-grid button:hover { border-color: var(--edge-hot); color: var(--ink); }
  .file-control:hover .file-button { border-color: var(--ink-3); color: var(--ink); }
  .variant:hover:not(:disabled) { background: var(--raised); border-color: var(--edge-hot); }
  .skin:hover { border-color: var(--edge-hot); background: var(--raised); }
  .skin:hover .skin-name { color: var(--ink-2); }
  .sticker-row:hover { border-color: var(--edge-hot); background: var(--raised); }
  .hud-actions .icon-button:hover { background: var(--raised); color: var(--ink); }
  .hud-delete:hover { border-color: var(--flag); background: var(--flag-wash); color: var(--ink); }
}

/* A finger needs more than a cursor does. */

@media (pointer: coarse) {
  .button, .icon-button, .text-input, .color-field { height: 40px; }
  .icon-button { width: 40px; }
  .segmented button, .align-row button, .camera-button, .showroom-button { height: 38px; }
  .decal-grid button { min-height: 40px; }
  .position-pad button { height: 32px; }
  .text-input { font-size: 16px; }
}

/* Responsive */

@media (max-width: 900px) {
  /* On a phone the panel is a sheet over the bottom of the screen, and the
     viewer does not reframe on its own: it only follows the size it is given.
     So the stage gives up exactly the height the sheet takes and the car
     recentres itself in what is left. The sheet is a fixed height for the same
     reason, because the stage has to be able to subtract it. */
  :root { --rail-h: 58px; --sheet: 0px; }
  .app-shell.panel-open,
  .app-shell:has(.sticker-hud:not([hidden])) { --sheet: 50vh; }

  .topbar { grid-template-columns: auto 1fr auto; gap: 10px; padding: 0 8px; }
  .topbar-center h1 { max-width: 20ch; }
  .state-pill { display: none; }
  .import-button { padding: 0 9px; }
  .import-label { display: none; }

  .stage { inset: 0 0 calc(var(--rail-h) + var(--sheet)) 0; }
  .toolrail {
    flex-direction: row; width: auto;
    top: auto; right: 0; bottom: 0; left: 0; height: var(--rail-h);
    transform: none;
    padding: 0; gap: 0;
    border: 0; border-top: 1px solid var(--rule); border-radius: 0;
    background: linear-gradient(180deg, #191c1e, #121416);
    box-shadow: none;
  }
  .rail-button { flex: 1; width: auto; padding: 8px 2px 7px; border-radius: 0; }
  .rail-button[aria-expanded="true"] { border-color: transparent; }

  .toolpanel {
    top: auto; right: 0; left: 0; bottom: var(--rail-h); width: auto;
    height: var(--sheet); max-height: none;
    border: 0; border-top: 1px solid var(--rule); border-radius: 0;
    box-shadow: none;
    animation: sheet-in var(--t-panel) var(--draw);
  }
  @keyframes sheet-in {
    from { transform: translateY(10px); opacity: 0; }
    to { transform: none; opacity: 1; }
  }
  /* Below the breakpoint there is room for exactly one surface over the
     canvas. A tool sheet and a floating readout together leave nothing of the
     car to look at, and selecting a sponsor preset opens both at once, so it
     is the normal path into that state rather than an edge case.

     So while a sticker is selected the readout IS the sheet, and the tool
     panel stands down until it is deselected. The stage keeps the same height
     either way, so the car does not jump.

     Browsers without :has() fall back to the floating card capped below, which
     is the previous behaviour rather than a broken one. */
  .app-shell:has(.sticker-hud:not([hidden])) .toolpanel { display: none; }
  /* The readout is a child of .stage, and .stage already stops exactly where
     the sheet begins, so top:100% drops it straight into the sheet slot. */
  .app-shell:has(.sticker-hud:not([hidden])) .sticker-hud {
    top: 100%; left: 0; right: 0; bottom: auto;
    width: auto; height: var(--sheet); max-height: none;
    border: 0; border-top: 1px solid var(--edge); border-radius: 0;
    display: flex; flex-direction: column;
  }
  .app-shell:has(.sticker-hud:not([hidden])) .hud-head { flex: none; }
  .app-shell:has(.sticker-hud:not([hidden])) .hud-body { overflow-y: auto; }

  .sticker-hud { top: auto; left: 8px; right: 8px; bottom: 10px; width: auto; max-height: 62%; overflow-y: auto; }
  .hud-body { gap: 8px; padding: 9px 10px 10px; }
  .hud-row { grid-template-columns: 46px minmax(0, 1fr) 38px; }
  .camera-controls { top: 8px; bottom: auto; left: 50%; right: auto; transform: translateX(-50%); }
}

@media (max-width: 620px) {
  .topbar-center { display: none; }
}

@media (max-width: 430px) {
  .brand-word { font-size: 14px; }
  .topbar { gap: 8px; padding: 0 6px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .garage-led-strip, .garage-led-grid-side, .garage-floor, .stage-grid { opacity: 1; }
  .loading-spinner::after { animation: none; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
  .state-pill::before { animation: none; }
}

/* A car its author encrypted. It stays selectable - a person should be able to
   click it and read why - but it must not look like the ones that work. */
.variant-locked .variant-name { opacity: 0.55; }
.variant-locked .variant-stats { color: var(--warn, #c8a24a); opacity: 0.9; }


/* The quieter of the two ways in. A folder is the normal shape of a car on
   disk, so the option belongs next to the file button rather than hidden. */
.empty-import-alt {
  margin-top: 10px;
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint, #8a8f98);
  border-bottom: 1px solid transparent;
  cursor: pointer;
}
.empty-import-alt:hover { color: var(--ink); border-bottom-color: var(--edge-hot, currentColor); }

/* No car on the stage: the rail must not look ready to paint one. */
.rail-button[disabled] {
  opacity: 0.35;
  pointer-events: none;
}

/* The paint template, shown rather than downloaded on sight.

   The sheet is transparent line work, so it needs a ground to be read against:
   a light card, the one place in this dark product where that is the right
   answer, because it is what the artwork will sit on in Photoshop. */
/* Working surfaces in this product are teal; the amber belongs to the card that
   leads here, not to the tools once you have arrived. Inheriting from the body
   made this the one screen whose sliders came out red. */
.template-view { --tool: var(--brand); }

.template-view {
  /* Fixed, not absolute inside the stage. The camera rail and the tool dock
     live outside the stage, so an absolute sheet had "Front 3/4" and "Top"
     printed across its title. This view is its own screen: the flat sheet is
     the only thing being looked at, and the car behind it is not the subject.
     Anyone who wants the car has a button that says so. */
  position: fixed; inset: 0; z-index: 60;
  /* An undefined custom property resolves to nothing, so the panel was
     transparent and the top bar read straight through it. `--stage` is the
     page's own ground, which is what this view stands on. */
  /* Title, tools, sheet, actions. The sheet is the one row that takes what is
     left; naming three rows for four children handed the free space to the
     toolbar and collapsed the sheet to nothing. */
  display: grid; grid-template-rows: auto auto 1fr auto; gap: 14px;
  padding: 22px clamp(16px, 4vw, 48px) 26px;
  background: var(--stage);
  pointer-events: auto;
}
.template-view[hidden] { display: none; }
.template-head { display: grid; gap: 3px; justify-items: center; text-align: center; }
.template-head strong { font-size: 14px; font-weight: 600; }
.template-head span { font-size: 12px; color: var(--ink-3); }
.template-sheet {
  /* The image is positioned against this box rather than sized by it. A
     percentage max-height inside an auto-sized grid area resolves against the
     content it is meant to be limiting, so a 2048 square sheet came out 1822px
     tall in a 937px space and ran off the bottom of a 1080p screen. */
  position: relative; min-height: 0;
  border: 1px solid var(--rule); border-radius: var(--r-card);
  /* The artwork layer is transparent, and a flat pale card made white text
     invisible while implying the sheet had a colour of its own. The chequer
     says what is actually there: nothing, until something is placed. */
  background-color: #c9c9c9;
  background-image:
    linear-gradient(45deg, #b4b4b4 25%, transparent 25%, transparent 75%, #b4b4b4 75%),
    linear-gradient(45deg, #b4b4b4 25%, transparent 25%, transparent 75%, #b4b4b4 75%);
  background-size: 24px 24px;
  background-position: 0 0, 12px 12px;
  overflow: hidden;
}
.template-sheet img {
  position: absolute; inset: 10px;
  width: calc(100% - 20px); height: calc(100% - 20px);
  object-fit: contain; display: block;
}
.template-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }


/* The template is its own category, not a panel over the editor.

   Someone who came for a flat sheet to paint in Photoshop has no use for a car
   in three-quarter view, a camera rail or a dock of paint tools, and showing
   them says this is a detour inside the paint tool rather than a thing of its
   own. While the sheet is the subject, the editor is not on screen at all. */
body.template-mode .stage,
body.template-mode .camera-controls,
body.template-mode .toolrail,
body.template-mode .stage-status,
body.template-mode .export-button,
body.template-mode .topbar-actions { display: none; }

.template-loading {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center; gap: 10px;
  color: var(--ink-3); font-size: 13px;
}
.template-loading[hidden] { display: none; }
.template-body[hidden] { display: none; }

/* Working on the flat sheet.

   The line work is a guide, not paint: it sits under a transparent canvas that
   holds the artwork, and only the canvas is exported. Drawing them into one
   picture would bake the wireframe into the car. */
.template-sheet img,
.template-sheet canvas {
  position: absolute; inset: 10px;
  width: calc(100% - 20px); height: calc(100% - 20px);
  object-fit: contain; display: block;
}
.template-sheet canvas { cursor: crosshair; touch-action: none; }
.template-tools {
  display: flex; gap: 8px; align-items: center; justify-content: center;
  flex-wrap: wrap;
}
.template-tools-gap { flex: 0 0 12px; }
.template-field {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ink-3);
}
.template-field input[type="color"] {
  width: 34px; height: 26px; padding: 0; border: 1px solid var(--rule);
  border-radius: 6px; background: none; cursor: pointer;
}


/* The baked shading, under the guide lines and under the artwork.

   Its own layer with its own dial, never a picture merged into the others, so
   a painter who wants none of it can have none of it and the guide lines stay
   readable either way. */
/* Over the line art, multiplied rather than stacked. The guide sheet fills each
   panel with near-white, so shading placed underneath it was completely hidden;
   multiplying darkens those fills while leaving the panel outlines readable. */
#templateShading { opacity: 0.7; mix-blend-mode: multiply; pointer-events: none; }
.template-field input[type="range"] { width: 96px; }

.template-field select {
  height: 26px; padding: 0 6px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--inset); color: var(--ink); font-size: 12px; max-width: 140px;
}


/* The template screen when the car will not open.

   It used to keep saying "Reading the car..." for ever while the explanation
   was painted underneath it, because the overlay is z-index 60 and the toast is
   20. A customer who picked the wrong file had no way out but to reload. */
.template-failed {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center; gap: 12px;
  text-align: center; padding: 24px;
}
.template-failed[hidden] { display: none; }
.template-failed strong { font-size: 15px; font-weight: 600; }
.template-failed span { font-size: 13px; color: var(--ink-3); max-width: 46ch; }

/* Above the full-screen views, not behind them. A message nobody can read is
   the same as no message at all. */
.toast { z-index: 80; }

/* One rule for both tools. Decal Studio set these classes on its toast and the
   sheet never defined them, so every message it has ever shown was invisible. */
.toast.error { border-color: rgba(255, 120, 120, .45); }
.toast.success { border-color: rgba(98, 220, 152, .35); }


/* The stage's own message, separate from the landing choice it sits inside. */
.stage-message {
  display: grid; justify-items: center; gap: 10px; text-align: center;
  max-width: 46ch; pointer-events: auto;
}
.stage-message[hidden] { display: none; }
.stage-message strong { font-size: 15px; font-weight: 600; }
.stage-message span { font-size: 13px; color: var(--ink-3); }
.empty-stage.showing-message .choice-lede,
.empty-stage.showing-message .choice-grid,
.empty-stage.showing-message > strong { display: none; }

/* The honest sentence about mod cars.

   Amber rather than red: nothing has gone wrong, and dressing a caveat as an
   error teaches people to ignore the real errors. It is placed above the tool
   dock rather than over the car, because the car is the thing somebody came to
   look at, and it takes itself away after a quarter of a minute so that the
   second car does not feel like being told off twice. */
.car-notice {
  display: flex; align-items: flex-start; gap: 10px;
  max-width: 62ch; padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--c-amber) 34%, var(--rule));
  border-radius: 10px;
  background: color-mix(in srgb, var(--c-amber) 9%, var(--chrome));
  color: var(--ink-2); font-size: 12.5px; line-height: 1.5;
  pointer-events: auto;
  animation: notice-in .28s ease both;
}
.car-notice[hidden] { display: none; }
.car-notice strong { color: var(--ink); font-weight: 600; }
.car-notice-mark {
  flex: none; width: 17px; height: 17px; margin-top: 1px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--c-amber); color: #241a00;
  font-size: 12px; font-weight: 700; line-height: 1;
}
.car-notice-text { flex: 1 1 auto; }
.car-notice-close {
  flex: none; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; color: var(--ink-3);
  font-size: 17px; line-height: 1; cursor: pointer;
}
.car-notice-close:hover { background: var(--raised); color: var(--ink); }

.stage > .car-notice {
  position: absolute; left: 50%; bottom: 104px; transform: translateX(-50%);
  z-index: 12;
  backdrop-filter: blur(6px);
}
#templateNotice { margin: 0 auto; }

@keyframes notice-in {
  from { opacity: 0; transform: translateX(-50%) translateY(6px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
#templateNotice { animation: none; }

/* On a narrow screen the dock is taller and the sentence is longer, so it
   stops floating and simply sits above the dock with the rest. */
@media (max-width: 720px) {
  .stage > .car-notice { left: 12px; right: 12px; bottom: 92px; transform: none; max-width: none; }
}

/* A control that does not apply to what is selected says so by looking spent,
   rather than by accepting a change nothing acts on. */
.template-field input:disabled,
.template-field select:disabled { opacity: .4; cursor: default; }

/* The account corner.

   Sits in the top bar next to the export button and borrows its metrics, so it
   reads as part of the same row rather than as something bolted on. Hidden
   entirely until Firebase is configured. */
.account-corner { display: inline-flex; align-items: center; }
.account-corner[hidden] { display: none; }
.account-corner[data-state="loading"] { width: 30px; }
.account-signin { height: 30px; padding: 0 12px; }

.account-corner { gap: 8px; }
.account-face {
  width: 30px; height: 30px; flex: none;
  border: 1px solid var(--rule); border-radius: 50%;
  background: var(--inset); color: var(--ink);
  display: inline-grid; place-items: center; overflow: hidden;
}
.account-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.account-initial { font-size: 12px; font-weight: 600; }
.account-signout { height: 30px; padding: 0 12px; }

/* The sign-in ask.

   One card, one sentence, two buttons. It exists so the Google window is never
   the first thing somebody sees after clicking a tool - a login screen from
   another company, with no explanation, is where people leave. */
.signin-backdrop {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center; padding: 20px;
  background: rgba(8, 9, 10, .72);
  animation: signin-fade 160ms var(--draw, ease) both;
}
@keyframes signin-fade { from { opacity: 0; } }

.signin-card {
  display: grid; gap: 12px;
  width: min(420px, 100%); padding: 24px;
  background: var(--raised); border: 1px solid var(--rule);
  border-radius: var(--r-card);
  animation: signin-rise 200ms var(--draw, ease) both;
}
@keyframes signin-rise { from { opacity: 0; transform: translateY(8px); } }

.signin-card strong { font-size: 16px; font-weight: 600; letter-spacing: -.015em; }
.signin-card p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink-3); }
.signin-actions { display: flex; gap: 8px; margin-top: 4px; }
.signin-actions .button { height: 34px; }
.signin-google { flex: 1; }



/* The way out asks first.

   Shared, because the component is: every tool on this site keeps its exit
   beside the wordmark, and all of them mount the same dialog from
   leave-guard.js. It borrows the guide's shell and only says what is different
   about it — that it is small, and that its button hierarchy runs the other
   way round from usual. */
.leave-guard { width: min(440px, calc(100vw - 32px)); }
.leave-guard .guide-body { padding: 20px; }
.leave-guard-foot { gap: 8px; flex-wrap: wrap; }
/* Leaving is allowed, and it is the one thing in this dialog that cannot be
   undone, so it stands apart and says so quietly rather than looking like the
   way forward. The filled treatment goes to staying. */
.leave-guard-go { margin-right: auto; color: var(--ink-3); }
.leave-guard-go:hover { color: var(--c-red); border-color: color-mix(in srgb, var(--c-red) 55%, var(--edge)); }
