/* ATLAS landing — one screen: release line, craft, three figures.
   v38: the message box (dialog shares the invitation's styles), the footer
   links row under the CTA, and the hint / foot / error lines in a card.
   v39: the pre-order steps inside the panel: number grid, summary, fields,
   agreement, the payment step (Stripe's elements), the waitlist.
   v40: the legal links and the contact address in the page footer.
   v41: [hidden] always wins.
   v42: the links line sits under the footer row instead of inside it.
   v43: the panel after iain's 3 Oct review: Tesla's order page as the model,
   the number grid four rows deep, the wallet and card buttons, the card area
   in place, the chat square and the chat sheet; the footer line is gone.
   v44: Tesla's sections: grey cards for the number and the contact info,
   boxed inputs, the summary plain; the wallet buttons with their marks.
   v45: one palette (the tokens above): cards and inputs are v1's glass over
   the studio, not flat grey; the chat square is ink; the country selector.
   v49 (Claude, 4 Oct): the stage controls of the order view in the same palette,
   the licence line as one bold line.
   v50 (Claude, 4 Oct, iain): the dots without their pill, smaller arrows.
   v51 (Claude, 5 Oct, iain): the Pre-order button's pressed state for the scroll open.
   v52 (Claude, 5 Oct, iain): the end of the landing, the company line and how to reach
   us, top left, type only.
   v53 (Claude, 5 Oct, iain): that block set compact, at the panel's scale; on a wide
   screen the chat square at the order view's bottom-left and the chat as a card there. */

:root {
  --ink: #171a20; /* the figures and the release line */
  --muted: #5c5e62; /* the labels under them */
  --rule: rgba(23, 26, 32, 0.12); /* hairlines between the figures */
  --paper: #ffffff;
  --studio: #ececec; /* the render's own backdrop, under the canvas before it paints */

  /* One rhythm for the page's side margins, so the release line, the craft
     and the figures all sit inside the same column. */
  --gutter: clamp(14px, 2.6vw, 40px);

  /* The panel's palette, from v1's own surfaces: glass over the studio (the
     Pre-order button's recipe), lines at the button's border strength, one
     red for a missed field. Nothing new is painted outside these. */
  --glass: rgba(255, 255, 255, 0.55); /* a card or button over the studio */
  --glass-strong: rgba(255, 255, 255, 0.82); /* an input, a hovered button */
  --glass-blur: blur(14px) saturate(1.4); /* the Pre-order button's frost */
  --line: rgba(23, 26, 32, 0.22); /* the Pre-order button's border */
  --line-strong: rgba(23, 26, 32, 0.4); /* the same, hovered */
  --tint: rgba(23, 26, 32, 0.06); /* a quiet fill on paper */
  --danger: #c2453d; /* the invitation's red */

  /* The type and spacing the page already uses, named once (v54, iain 5 Oct: "매번
     디자인컴포넌트 넣을때마다 폰트나이런거새로하지마 ... 우리 디자인시스템하에있는 스페이싱이나
     폰트 크기나 폰트종류 등 맞게 사용해"). A new piece takes these and nothing new; the rules
     that defined them now read them, so nothing already on the page moved.
     Three faces, each with its place: Archivo for all text, Zen Dots for the name only,
     the spec bar's face (Tesla's, Montserrat as the stand-in) for figures and their labels. */
  --font-text: 'Archivo', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  /* The craft's name in the company's logotype (5 Nov 2026; Zen Dots before).
     The face keeps its flat-topped A on the lowercase glyphs, so whatever
     carries it is set in lowercase below. */
  --font-name: '911 Porscha', 'Zen Dots', 'Archivo', system-ui, sans-serif;
  --font-spec: 'Universal Sans Text', 'Gotham SSm', 'Montserrat', 'Archivo', Arial, sans-serif;
  --size-kicker: clamp(9px, 0.9vw, 13px); /* INTRODUCING: Archivo 500, spaced capitals, muted */
  --track-kicker: 0.28em;
  --size-label: clamp(11px, 1vw, 14px); /* Flight Time: the small line under or beside a value */
  --size-heading: 15px; /* the panel's section heads: Archivo 600 */
  --size-lead: clamp(17px, 1.35vw, 20px); /* the panel's one bold line */
  --space-kicker: clamp(4px, 0.8vh, 8px); /* a kicker to the line it names */
  --space-stack: clamp(16px, 2.6vh, 26px); /* between blocks (the panel's rhythm; 14px on a phone) */
}

* {
  box-sizing: border-box;
}

/* The hidden attribute wins over any display rule below: a step the script
   hides stays hidden whatever its layout. */
[hidden] {
  display: none !important;
}

/* No scrollbar drawn anywhere (5 Nov 2026): the page and every panel still
   scroll — the motion itself is the indicator. */
* {
  scrollbar-width: none;
}

::-webkit-scrollbar {
  display: none;
}

html,
body {
  height: 100%;
  overflow: hidden; /* one screen, and it stays put */
  overscroll-behavior: none; /* no rubber-banding at the edges either */
}

body {
  margin: 0;
  background: var(--studio);
  color: var(--ink);
  font-family: var(--font-text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* The whole page is one screen: the craft takes whatever height the release
   line and the figures leave it, and never pushes them off. */
.page {
  /* A definite height, not just a minimum: the craft below sizes itself
     against this row, and a percentage height cannot resolve against a
     min-height. With only a minimum the image fell back to its natural size
     and spilled out of a short window. */
  position: relative;
  z-index: 1;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* everything reads along the bottom edge */
  padding: clamp(14px, 2.2vh, 26px) var(--gutter) clamp(14px, 2.6vh, 30px);
  gap: clamp(6px, 1.2vh, 14px);
  pointer-events: none; /* the layer is type only; nothing to click through to */
}

/* ── masthead ─────────────────────────────────────────────────────────── */

/* One line along the bottom: the name at the left, the figures in the middle,
   the invitation at the right. Gone everywhere since 5 Nov 2026 — the panel
   carries PRE-ORDER and the name, the order bar carries the figures, and the
   footer only flashed during the opening turn. The markup stays for the
   Pre-order button's id, which the scroll's opener still presses. */
.footer {
  display: none;
  /* The row of type never gives up its height: when the window runs short it
     is the craft above that scales away, not the figures. */
  flex: 0 0 auto;
  /* over the picture, which fills the whole window behind it */
  position: relative;
  z-index: 1;
  display: flex;
  /* All three read off one line: the name and the invitation take their
     vertical centre from the figures between them, not from the page edge. */
  align-items: center;
  gap: clamp(20px, 4vw, 72px);
}

/* The name, set in the face it is meant to be seen in. */
.masthead {
  margin: 0;
  flex: 0 0 auto;
  text-align: left;
  font-family: var(--font-name);
  font-weight: 400;
  font-size: clamp(26px, min(4.6vw, 8vh), 64px);
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* ── the craft ────────────────────────────────────────────────────────── */

/* The picture takes the room the type leaves it — the flexible row above the
   figures — and the script draws each frame contained in that box, so the
   craft is whole at every window shape and never runs under the text. */
.backdrop {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  pointer-events: auto; /* the type layer ignores the pointer; the craft takes it */
  cursor: default;
  touch-action: pinch-zoom; /* vertical gestures advance the rendered stages */
  opacity: 1;
  transition: opacity 420ms ease;
}

.backdrop.is-ready {
  opacity: 1;
}

.backdrop__view,
.backdrop__still {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The studio render is the page's backdrop: the canvas covers the window, and
   the script fits the craft into this row, the room the type leaves it. */
.backdrop__view {
  position: fixed;
  inset: 0;
}

.flight-poster {
  position: absolute;
  inset: 0;
  object-fit: contain;
}

.backdrop.is-ready:not(.has-fallback) .flight-poster {
  display: none;
}

/* The stage controls of the pre-order view (left, the five dots, right), in
   the panel's own palette: glass over the studio, the Pre-order button's
   border. Placed by flight.js under the craft, in the room the panel leaves.
   Hidden on the landing. */
.flight-controls {
  position: fixed;
  z-index: 3;
  left: 50%;
  top: 75%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: auto;
  max-width: calc(100vw - 28px);
}

.flight-controls__row {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* iain, 4 Oct: no white pill behind the dots, only the dots; smaller arrows. */
.flight-arrow {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  color: var(--ink);
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease;
}

.flight-arrow:hover:not(:disabled) {
  background: var(--glass-strong);
  border-color: var(--line-strong);
}

.flight-arrow svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

.flight-arrow:disabled {
  opacity: 0.3;
  cursor: default;
}

.flight-dots {
  display: flex;
  align-items: center;
  gap: 0;
  height: 32px;
  padding: 0 2px;
  background: none;
  border: 0;
}

.flight-dot {
  width: 22px;
  height: 32px;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.flight-dot::after {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line-strong);
  transition: background 180ms ease, transform 180ms ease;
}

.flight-dot[aria-current='true']::after {
  background: var(--ink);
  transform: scale(1.15);
}

.flight-arrow:focus-visible,
.flight-dot:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.flight-controls--compact .flight-arrow { width: 28px; height: 28px; }
.flight-controls--compact .flight-arrow svg { width: 13px; height: 13px; }
.flight-controls--compact .flight-dots { height: 28px; }
.flight-controls--compact .flight-dot { height: 28px; width: 20px; }

/* Below square the crop would eat the wings and leave a slice of fuselage, so
   the craft is shown whole there instead of filling the screen. */

/* The craft fills the row it is given, whole, at every window shape. */
/* A quiet nudge pinned just under the craft itself — the script keeps
   --craft-bottom at the drawn image's lower edge — shown only if nobody has
   taken hold of it a few seconds after the opening turn settles, and gone for
   good at the first touch. */
.hero__hint {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: var(--craft-bottom, 76%);
  margin: 0;
  white-space: nowrap;
  font-size: clamp(10px, 0.85vw, 13px);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transition: opacity 600ms ease;
  pointer-events: none;
}

.hero__hint.is-shown {
  opacity: 1;
}

.hero__hint.is-gone {
  opacity: 0;
}

/* ── the figures ──────────────────────────────────────────────────────── */

.specs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
  /* Takes whatever the name leaves, rather than a fixed measure of its own. */
  flex: 1 1 auto;
  min-width: 0;
}

.spec {
  /* The spec bar is drawn from Tesla's: their face first for anyone who has
     it, Montserrat from the web as the stand-in everyone actually gets. */
  font-family: var(--font-spec);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: clamp(4px, 0.7vh, 9px);
  /* The hairlines read as rules on a spec bar, not as tight dividers around
     the words: they run a little past the type at both ends. */
  padding: clamp(5px, 1vh, 12px) clamp(8px, 2vw, 28px);
}

.spec + .spec {
  border-left: 1px solid var(--rule);
}

.spec__figure {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 0.16em;
  line-height: 1;
  font-weight: 600;
  white-space: nowrap;
}

.spec__value {
  font-size: clamp(22px, 3.3vw, 40px);
  letter-spacing: 0;
}

.spec__unit {
  font-size: clamp(10px, 1vw, 14px);
  font-weight: 600;
  letter-spacing: 0;
}

.spec__label {
  margin: 0;
  font-size: var(--size-label);
  font-weight: 400;
  color: var(--muted);
  text-align: center;
}

/* A phone has no footer at all (5 Nov 2026): the order sheet itself is the
   page's foot, always present and partly revealed — its own name and figures
   showing — and opening simply lifts the same surface. preorder.js keeps
   --buy-peek at the height of that head. */
@media (max-width: 760px) {
  .footer {
    display: none;
  }

  /* The craft's room ends above the resting sheet. */
  .page {
    padding-bottom: calc(var(--buy-peek, 200px) + 10px);
  }

  /* The craft's row starts below the wordmark. */
  .backdrop {
    margin-top: clamp(52px, 8vh, 72px);
  }

  .buy {
    border-left: 0;
    border-top: 1px solid rgba(23, 26, 32, 0.07);
    background: #f7f7f6;
  }
}

/* ── the end of the landing ───────────────────────────────────────────── */

/* After the flight (iain, 5 Oct): the company line and how to reach us, in the
   empty top-left of the studio, as type with no box behind it. scroll.js sets
   --ending from 0 to 1 over the last stretch of the page, so it fades in as the
   visitor scrolls on and out again on the way back; the order view fades it out
   and closing it brings it back. */
.ending {
  position: absolute;
  z-index: 2;
  top: clamp(24px, min(6.5vh, 4.5vw), 72px);
  left: var(--gutter);
  max-width: min(46vw, 520px);
  opacity: var(--ending, 0);
  transform: translateY(calc((1 - var(--ending, 0)) * 14px));
  pointer-events: none;
}

html.ending-on .ending {
  pointer-events: auto;
}

.ending__in {
  transition: opacity 450ms ease;
}

body.is-buying .ending__in {
  opacity: 0;
}

body.is-buying .ending {
  pointer-events: none;
}

/* v55 (iain, 6 Oct: "유토피아랩스 디자인 매뉴팩쳐~이문장 아예 빼기로 했고 로케이션이랑 컨택트를 아우르는
   제목을 정해서 그하위에 둘다 넣기로"). No company line: one kicker, exactly as INTRODUCING, over the
   address (Google Maps) and the email, stacked, at the figures' label size in ink. Still X-free, and
   still only the page's own tokens (:root). */
.ending__kicker {
  margin: 0 0 var(--space-stack);
  font-size: var(--size-kicker);
  font-weight: 600;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--ink); /* v57 (iain, 7 Oct): the heading in ink, so it holds the block together */
}

.ending__facts {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-kicker);
  font-style: normal;
}

.ending__value {
  font-size: var(--size-label);
  line-height: 1.5;
  color: var(--muted); /* v57: the facts step back behind the heading and the founder link */
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms ease;
}

.ending__value:hover,
.ending__value:focus-visible {
  color: var(--ink);
  border-bottom-color: currentColor;
  outline: none;
}

/* v56 (iain, 6 Oct): "talk to founder ... 클릭가능하게 ... 클릭하면 똑같은 메세지 채팅폼". Words,
   not an icon: the facts' size, a step heavier, underlined from the start so it reads as
   something to press; the stack's gap above it. */
.ending__talk {
  margin: 0 0 var(--space-stack);
  padding: 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  background: none;
  font: inherit;
  font-size: var(--size-label);
  font-weight: 600;
  line-height: 1.5;
  color: var(--ink);
  cursor: pointer;
  transition: opacity 160ms ease;
}

.ending__talk:hover,
.ending__talk:focus-visible {
  opacity: 0.65;
  outline: none;
}

@media (max-width: 760px) {
  /* Under the name, which keeps the top of a phone, with the panel's gap twice over. */
  .ending {
    top: calc(clamp(34px, 6vh, 64px) + 48px + 2 * var(--space-stack));
    right: var(--gutter);
    max-width: none;
  }
}

/* On a phone the three figures still stand side by side, as they do on the
   spec bar this is drawn from; only the type steps down to fit. */
@media (max-width: 420px) {
  .spec {
    padding: 6px;
  }

  .spec__figure {
    gap: 0.04em;
  }
}

/* ── the invitation ───────────────────────────────────────────────────── */

/* The page's type layer ignores the pointer so the picture stays clean; the
   one thing you can actually press has to take it back. */
.preorder {
  flex: 0 0 auto;
  pointer-events: auto;
  margin: 0;
  padding: clamp(9px, 1.3vh, 14px) clamp(18px, 2.2vw, 30px);
  border: 1px solid rgba(23, 26, 32, 0.22);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  color: var(--ink);
  font: inherit;
  font-size: clamp(11px, 0.95vw, 14px);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 220ms ease, border-color 220ms ease, transform 220ms ease;
}

.preorder:hover {
  background: rgba(255, 255, 255, 0.82);
  border-color: rgba(23, 26, 32, 0.4);
}

.preorder:active {
  transform: translateY(1px);
}

/* The landing scrolled to its end opens the order view as the button does, and the
   button shows the press for a moment (scroll.js, iain 5 Oct). */
.preorder.is-pressed {
  background: rgba(255, 255, 255, 0.82);
  border-color: rgba(23, 26, 32, 0.4);
  transform: translateY(1px);
}

.preorder:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* The modal is a native <dialog>, so the browser handles the top layer, Esc,
   and returning focus to the button when it closes. */
.invite {
  width: min(560px, calc(100vw - 2 * var(--gutter)));
  max-height: calc(100dvh - 2 * var(--gutter));
  margin: auto;
  padding: 0;
  border: 0;
  /* No card: the form floats free, and the backdrop below frosts the whole
     screen behind it. */
  background: transparent;
  color: var(--ink);
  overflow: auto;
  overscroll-behavior: contain; /* scrolling the form must not reach the page */

  /* Fades both ways. Closing needs the discrete properties in the transition
     as well: without them the dialog is removed from the top layer the
     instant close() is called and the fade never gets to run. */
  opacity: 0;
  transition:
    opacity 260ms ease,
    overlay 260ms ease allow-discrete,
    display 260ms ease allow-discrete;
}

.invite[open] {
  opacity: 1;
}

/* The card holds the opening focus on touch screens; that is plumbing, not
   selection, and gets no ring. */
.invite:focus {
  outline: none;
}

/* Where the fade starts from on the way in. */
@starting-style {
  .invite[open] {
    opacity: 0;
  }
}

/* The whole screen goes to glass: the craft and the figures carry through it,
   softened, and the form reads against that. It fades with the dialog. */
.invite::backdrop {
  background: rgba(255, 255, 255, 0.35);
  -webkit-backdrop-filter: blur(28px) saturate(1.4);
  backdrop-filter: blur(28px) saturate(1.4);
  opacity: 0;
  transition:
    opacity 260ms ease,
    overlay 260ms ease allow-discrete,
    display 260ms ease allow-discrete;
}

.invite[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .invite[open]::backdrop {
    opacity: 0;
  }
}

/* The way out is the way in, run backwards. The browser does not transition
   opacity when a dialog closes, so the script fades it with this class first
   and only then actually closes it. */
.invite.is-closing,
.invite.is-closing::backdrop {
  opacity: 0;
}

/* The card and the thank-you both sit on the same padded field. */
.invite__card,
.invite__thanks {
  position: relative;
  padding: clamp(26px, 4vh, 40px) clamp(22px, 4vw, 40px) clamp(22px, 3.4vh, 34px);
}

.invite__thanks[hidden] {
  display: none;
}

.invite__close {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  margin-right: -8px; /* the glyph's own air, so the X reads flush with the fields */
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}

.invite__close:hover {
  color: var(--ink);
  background: rgba(23, 26, 32, 0.06);
}

.invite__title {
  margin: 0;
  font-size: clamp(19px, 2vw, 25px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The title and the way out share one line above the fields. */
.invite__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: clamp(18px, 2.8vh, 28px);
}

/* The thank-you has no head row, so its title keeps its own air. */
.invite__thanks .invite__title {
  margin-bottom: clamp(18px, 2.8vh, 28px);
}

.invite__lede {
  margin: 6px 0 0;
  font-size: clamp(12px, 1.05vw, 14px);
  color: var(--muted);
}

.invite__fields {
  display: grid;
  gap: 8px;
}

/* The honeypot. Taken out of the grid's flow entirely, so it adds neither a
   row nor a gap, and pushed off-screen rather than display:none because a
   number of form-filling bots skip anything that is outright hidden. */
.invite__fields .invite__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Nothing sits inside the fields: the card's own blurred glass shows straight
   through them. A single line under the words is all that marks one out. */
.invite__fields input,
.invite__fields textarea {
  width: 100%;
  padding: 11px 2px;
  border: 0;
  border-bottom: 1px solid rgba(23, 26, 32, 0.2);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: clamp(13px, 1.05vw, 15px);
  transition: border-color 180ms ease;
}

.invite__fields textarea {
  resize: none; /* the drag handle is the one piece of chrome in the card */
  min-height: 68px;
}

.invite__fields input::placeholder,
.invite__fields textarea::placeholder {
  color: rgba(92, 94, 98, 0.72);
}

.invite__fields input:focus,
.invite__fields textarea:focus {
  outline: none;
  border-bottom-color: var(--ink);
}

/* Marked only after a failed send, never while someone is still typing. */
.invite__fields .is-missing {
  border-bottom-color: var(--danger);
}

/* A field with a line of explanation under it. */
.invite__group {
  display: grid;
  gap: 6px;
}

.invite__hint,
.invite__foot {
  margin: 0;
  font-size: clamp(11px, 0.9vw, 12px);
  line-height: 1.5;
  color: var(--muted);
}

.invite__foot {
  margin-top: 10px;
  text-align: center;
}

.invite__error {
  margin: 10px 0 0;
  font-size: clamp(12px, 0.95vw, 13px);
  line-height: 1.5;
  color: #c2453d;
  text-align: center;
}

.invite__error[hidden] {
  display: none;
}

.invite__send:disabled {
  opacity: 0.6;
  cursor: progress;
}

.invite__send {
  width: 100%;
  margin-top: clamp(14px, 2.2vh, 20px);
  padding: 13px 20px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font: inherit;
  font-size: clamp(12px, 1vw, 14px);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: opacity 200ms ease;
}

.invite__send:hover {
  opacity: 0.86;
}

/* On a phone the invitation is the whole screen, so nothing of the page shows
   around it. It is still glass: what is behind carries through it, softened,
   rather than being replaced. */
@media (max-width: 760px) {
  .invite {
    width: 100vw;
    max-width: none;
    height: 100dvh;
    max-height: none;
    margin: 0;
    border: 0;
    border-radius: 0;
  }

  /* Under 16px here and iOS Safari zooms the page in when a field takes
     focus. 16px keeps it still. */
  .invite__fields input,
  .invite__fields textarea {
    font-size: 16px;
  }

  .invite__card,
  .invite__thanks {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    /* Centred, but never at the cost of the top: safe centring falls back to
       the start rather than pushing content out of reach when the form is
       taller than the screen. */
    justify-content: center;
    justify-content: safe center;
    padding-left: clamp(22px, 7vw, 40px);
    padding-right: clamp(22px, 7vw, 40px);
  }

}

/* ── pre-order ────────────────────────────────────────────────────────── */

/* The panel takes the right-hand column; the craft slides left by half of it,
   so it ends up centred in the room that is left. On a phone the panel rises
   from the bottom and the craft moves up instead. */
:root {
  --buy-w: clamp(320px, 30vw, 440px);
  --slide: cubic-bezier(0.22, 0.61, 0.36, 1);
}

.footer {
  transition: opacity 500ms ease, transform 700ms var(--slide);
}

/* The craft moves into the middle of the room the panel leaves inside the
   canvas itself (turntable.js), so the studio stays whole around it. */
body.is-buying .backdrop {
  cursor: default;
}

body.is-buying .footer {
  opacity: 0;
  transform: translateY(14px);
}

body.is-buying .footer .preorder {
  pointer-events: none;
}

body.is-buying .hero__hint {
  opacity: 0;
}

/* The page can arrive with the view already open (preorder.js `instant`):
   for that one frame nothing slides or fades, it is simply there. */
.buy.no-anim {
  transition: none !important;
}

/* Arrival (preorder.js): the view comes in long, starting while the opening
   turn is still slowing, so panel and craft come to rest together. */
.buy.is-arriving {
  transition: transform 1400ms var(--slide), opacity 900ms ease;
}

.buy {
  position: fixed;
  z-index: 2;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--buy-w);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding: clamp(24px, 6vh, 64px) clamp(22px, 2.6vw, 40px);
  /* Its own block (5 Nov 2026, after Tesla's order page): a tone apart from
     the studio, one hairline where they meet. Light throughout — no dark. */
  background: #f7f7f6;
  border-left: 1px solid rgba(23, 26, 32, 0.07);
  color: var(--ink);
  overflow: hidden;
  transform: translateX(100%);
  opacity: 0;
  transition: transform 950ms var(--slide), opacity 600ms ease;
}

/* The content scrolls inside the panel; the close and chat squares stay in
   the corners. Centred when short, from the top when tall. */
.buy__scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: auto;
  overscroll-behavior: contain;
  /* No scrollbar drawn (5 Nov 2026): the content still scrolls. */
  scrollbar-width: none;
}

.buy__scroll::-webkit-scrollbar {
  display: none;
}

/* The chat square is gone everywhere (5 Nov 2026): Contact and the page end's
   line open the full-screen form instead. */
.buy__chat {
  display: none;
}

/* The contact fields sit straight on the panel (5 Nov 2026): no card —
   in the waitlist panel exactly as in the order form. */
#buy-form .buy__card,
#buy-waitlist .buy__card {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Contact, quiet at the panel's foot: a link that opens the chat. */
.buy__contact {
  flex: 0 0 auto;
  align-self: flex-start;
  margin: 14px 0 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.buy__contact:hover {
  color: var(--ink);
}

/* The small square that opens the chat: Tesla's, in our greys. */
.buy__chat {
  position: absolute;
  z-index: 1;
  top: clamp(12px, 2.2vh, 24px);
  left: clamp(12px, 1.6vw, 24px);
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease;
}

.buy__chat svg {
  width: 22px;
  height: 22px;
}

.buy__chat:hover,
.buy__chat[aria-expanded="true"] {
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  color: var(--ink);
}

.buy__chat:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.buy[hidden] {
  display: none;
}

.buy.is-open {
  transform: none;
  opacity: 1;
}

.buy__close {
  position: absolute;
  top: clamp(12px, 2.2vh, 24px);
  right: clamp(12px, 1.6vw, 24px);
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}

.buy__close:hover {
  background: rgba(23, 26, 32, 0.06);
  color: var(--ink);
}

/* The X never wears the focus ring (5 Nov 2026): a tap on iOS can read as
   keyboard focus and drew a black circle around it; its focus cue is the
   hover tint instead. */
.buy__close:focus-visible {
  outline: none;
  background: rgba(23, 26, 32, 0.06);
  color: var(--ink);
}

.buy__link:focus-visible,
.pay:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.buy__body,
.buy__done {
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
  /* centred when the content is short; scrolls from the top when it is not */
  margin: auto 0;
  padding-top: 28px; /* room for the two corner squares */
}

.buy.is-done .buy__body,
.buy__done[hidden] {
  display: none;
}

.buy__kicker {
  margin: 0 0 -8px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

.buy__title {
  margin: 0;
  font-family: var(--font-name);
  font-weight: 400;
  font-size: clamp(30px, 2.8vw, 42px);
  line-height: 1;
  text-transform: lowercase; /* the logotype's flat-topped A */
}

.masthead [data-name] {
  text-transform: lowercase; /* the logotype's flat-topped A */
}

.buy__specs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Dividers between the figures only (5 Nov 2026): no rules above or below. */
}

.buy__spec {
  font-family: var(--font-spec);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 6px;
}

.buy__spec + .buy__spec {
  border-left: 1px solid var(--rule);
}

.buy__fig {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.buy__fig span {
  margin-left: 0.16em;
  font-size: 11px;
}

.buy__lab {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.buy__license {
  margin: 0 0 14px;
  font-size: var(--size-lead);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.buy__sum {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The summary rows, as Tesla writes them (5 Nov 2026): Due Today leads, large;
   the delivery balance follows small and quiet; one run of fine print holds
   everything explanatory; the agreement line closes in the same small voice. */
.buy__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-top: 16px;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: -0.005em;
}

.buy__row--today {
  margin-top: 0;
  font-size: 19px;
  font-weight: 500;
}

.buy__row > span:last-child {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.buy__fine {
  margin: 16px 0 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
}

/* The small print under a line of the summary. */
.buy__sub {
  margin: -4px 0 2px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
}

.buy__sub a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The wire instructions on the page after the order (iain, 3 Nov 2026): the summary's own rows, with long values
   (addresses) allowed to wrap, selectable for copying. No new sizes: the row and small-print tokens above. */
.buy__wire {
  display: flex;
  flex-direction: column;
  gap: var(--space-stack);
}

.buy__wire .buy__row {
  align-items: flex-start;
  margin-top: 0;
}

.buy__wire .buy__row > span:first-child {
  flex: none;
  color: var(--muted);
}

.buy__wire .buy__row > span:last-child {
  white-space: normal;
  text-align: right;
  overflow-wrap: anywhere;
}

/* ── the steps ── */

/* Tesla's order page: each section a grey card; the summary at the end has
   no card behind it, which is what tells the eye the end has come. */
.buy__card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

.buy__h {
  margin: 0;
  font-size: var(--size-heading);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.005em;
}

.buy__summary {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 6px 2px 0;
}

.buy__soon,
.buy__status,
.buy__hint,
.buy__error,
.buy__notebox {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

.buy__soon {
  font-size: 14px;
  color: var(--ink);
}

.buy__error {
  color: var(--danger);
}

/* The delivery estimate, the one line Gustau supplies, set like Tesla's. */
.buy__delivery {
  margin: 0 0 8px;
  text-align: center;
  font-size: 18px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/* The small print under a line of the summary. */
.buy__sub {
  margin: -6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

.buy__sub--balance {
  margin-top: 2px;
}

/* The (i) next to the purchase price and the note it opens. */
.buy__info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  margin-left: 4px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 10px;
  font-weight: 600;
  font-style: italic;
  font-family: Georgia, 'Times New Roman', serif;
  line-height: 1;
  vertical-align: 1px;
  cursor: pointer;
}

.buy__info:hover,
.buy__info[aria-expanded="true"] {
  border-color: var(--ink);
  color: var(--ink);
}

.buy__notebox {
  margin: -2px 0 2px;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--glass);
  color: var(--ink);
}

/* The fields: Tesla's boxed inputs, white on the grey card. */
.buy__form {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.6vh, 26px);
}

.buy__fields {
  display: grid;
  gap: 8px;
}

.buy__two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.buy__phone {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 8px;
}

.buy__input {
  width: 100%;
  min-width: 0;
  height: 48px;
  padding: 0 16px;
  /* Flat and quiet (5 Nov 2026): one hairline, one fill, nothing modelled. */
  border: 1px solid rgba(23, 26, 32, 0.1);
  border-radius: 8px;
  background: rgba(23, 26, 32, 0.04);
  color: var(--ink);
  /* The page's own text face, at the summary rows' size and weight. */
  font: inherit;
  font-family: var(--font-text);
  font-size: 14px;
  font-weight: 400;
  transition: box-shadow 160ms ease, border-color 160ms ease, background 160ms ease;
}

.buy__input--area {
  height: auto;
  min-height: 72px;
  padding: 14px 16px;
  resize: none;
}

.buy__input::placeholder {
  color: var(--muted);
  font-weight: 400;
}

.buy__input:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

/* Tesla's complaint: a red frame and a red placeholder, after a failed send. */
.buy__input.is-missing {
  border-color: var(--danger);
  box-shadow: inset 0 0 0 1px var(--danger);
}

.buy__input.is-missing::placeholder {
  color: var(--danger);
}

.buy__codewrap {
  position: relative;
  display: block;
  min-width: 0;
}

.buy__codelabel {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
}

.buy__code {
  color: transparent; /* the label above shows "US +1"; the list shows the names */
  padding-right: 32px;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%23171a20' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 14px;
  cursor: pointer;
}

.buy__code option {
  color: var(--ink);
}

.buy__check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 14px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
  cursor: pointer;
}

.buy__check input {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 2px 0 0;
  accent-color: var(--ink);
}

.buy__check.is-missing span {
  color: var(--danger);
}

/* The agreement, by the act of ordering: one small line, no box. */
.buy__agree {
  margin: 14px 0 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
}

.buy__agree a,
.buy__check a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.buy__continue {
  width: 100%;
  padding: 13px 20px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: opacity 200ms ease;
}

.buy__continue:hover {
  opacity: 0.86;
}

.buy__continue:disabled {
  opacity: 0.6;
  cursor: progress;
}

.buy__continue:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* The two ways to pay, Tesla's order page: the device's wallet in black,
   the card in an outline that opens the card area in place. */
.buy__pay {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.buy__express {
  min-height: 48px;
}

.pay--wallet,
.pay--place,
.pay--card,
.pay--join {
  width: 100%;
  height: 48px;
  /* The hairline of glass every control wears (5 Nov 2026). */
  border: 1px solid rgba(23, 26, 32, 0.1);
  border-radius: 8px;
  font-family: 'Archivo', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
}

/* The wallet marks: Apple's mark then "Pay", Google's G then "Pay". */
.pay--wallet {
  background: #000;
  color: #fff;
  border-color: transparent; /* flat black: a rim read as gloss, not glass */
  gap: 5px;
  font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.01em;
}

.pay--wallet .pay__logo {
  width: 20px;
  height: 20px;
}

#buy-wallet-mock-apple .pay__logo {
  margin-top: -3px;
  width: 18px;
  height: 18px;
}

.pay--card {
  /* The quiet filled pair to the black wallet button: flat, one hairline. */
  background: rgba(23, 26, 32, 0.05);
  color: var(--ink);
}

.pay--card:hover:not(:disabled) {
  opacity: 1;
  background: rgba(23, 26, 32, 0.11);
}

/* The waitlist's one call to action (5 Nov 2026): the order page's button
   geometry, black like the wallet button, Archivo like the rest of the panel. */
.pay--join {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  color: #fff;
  border-color: transparent;
}

.pay--join:hover:not(:disabled) {
  opacity: 0.88;
}

.pay:disabled {
  opacity: 0.5;
  cursor: progress;
  transform: none;
}

.pay.is-busy {
  opacity: 0.5;
  cursor: progress;
}

/* While a payment is confirmed: one quiet spinner under the buttons, no words
   (iain, 14 Oct). Colours from the palette; sized to the text around it. */
.buy__confirming {
  display: flex;
  justify-content: center;
}

.buy__confirming[hidden] {
  display: none;
}

.buy__spinner {
  width: 1.6em;
  height: 1.6em;
  border: 2px solid var(--rule);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: buy-spin 0.8s linear infinite;
}

@keyframes buy-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .buy__spinner { animation-duration: 2.4s; }
}

/* The card area, open in place under the buttons. */
.buy__cardarea {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 0 2px;
  border-top: 1px solid var(--rule);
}

.buy__card-element {
  min-height: 0;
}

@media (max-width: 760px) {
  .buy {
    top: auto;
    left: 0;
    width: auto;
    /* 58dvh, down from 72 (5 Nov 2026): the render kept too little of the
       screen; the sheet's own scroll carries what no longer fits. */
    max-height: 58dvh;
    justify-content: flex-start;
    padding: 0 var(--gutter);
    opacity: 1;
    /* Off the screen until the opening turn wraps (preorder.js adds
       is-resting); resting, only the head shows — name, figures, Pre-order. */
    transform: translateY(100%);
    transition: transform 950ms var(--slide);
  }

  .buy.is-resting:not(.is-open) {
    transform: translateY(calc(100% - var(--buy-peek, 230px)));
  }

  /* The resting sheet is one tap from rising. */
  .buy:not(.is-open) {
    cursor: pointer;
  }

  .buy:not(.is-open) .buy__scroll {
    overflow: hidden;
  }

  /* The X belongs to the risen sheet alone. */
  .buy .buy__close {
    transition: opacity 320ms ease;
  }

  .buy:not(.is-open) .buy__close {
    opacity: 0;
    pointer-events: none;
  }

  /* 16px keeps iOS Safari from zooming when a field takes focus. */
  .buy__fields input:not([type="checkbox"]),
  .buy__fields textarea,
  .buy__cardfields input {
    font-size: 16px;
  }

  .buy__chat {
    top: 12px;
    left: var(--gutter);
  }

  /* The scroller carries the sheet's vertical padding, so its content runs
     to the very edges instead of being cut short of them. */
  .buy__scroll {
    padding: 16px 0 calc(24px + env(safe-area-inset-bottom, 0px));
  }

  .buy__body {
    padding-top: 4px;
  }

  .buy__body > .buy__title {
    margin-bottom: 10px;
  }

  /* The figures belong to the resting head: the risen sheet is the form's.
     They fade out quickly and their room folds away with the slide. */
  .buy__body > .buy__specs {
    margin-bottom: 18px;
    max-height: 220px;
    overflow: hidden;
    transition: opacity 180ms ease, max-height 600ms var(--slide), margin-bottom 600ms var(--slide);
  }

  .buy.is-open .buy__body > .buy__specs {
    opacity: 0;
    max-height: 0;
    margin-bottom: 0;
  }

  /* The X floats over whatever scrolls under it, and stays clickable. */
  .buy__close {
    z-index: 2;
    background: rgba(247, 247, 246, 0.85);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }


  /* The sheet's head, centred on a phone, as the foot it stands in for. */
  .buy__body > .buy__kicker,
  .buy__body > .buy__title {
    text-align: center;
  }

  /* a phone sheet: a touch tighter than the desktop's rhythm */
  :root {
    --space-stack: 18px;
  }

  .buy__spec {
    padding: 10px 4px 9px;
  }

  .buy__fig {
    font-size: 24px;
  }

  .buy__lab {
    font-size: 13px;
  }

  .pay {
    height: 44px;
  }
}

/* ── the chat ─────────────────────────────────────────────────────────── */

/* Rises from the bottom over the panel's column (the whole width on a phone):
   a header, the log, a composer. Tesla Assist's shape in our greys. */
.chat {
  position: fixed;
  z-index: 4;
  right: 0;
  bottom: 0;
  width: var(--buy-w);
  height: min(640px, 86dvh);
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -8px 40px var(--rule);
  transform: translateY(100%);
  opacity: 0;
  transition: transform 420ms var(--slide), opacity 300ms ease;
}

.chat.is-open {
  transform: none;
  opacity: 1;
}

.chat__head {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  padding: 0 56px;
  border-bottom: 1px solid var(--rule);
}

.chat__title {
  font-size: 14px;
  font-weight: 500;
}

.chat__min {
  position: absolute;
  left: 10px;
  top: 10px;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.chat__min svg {
  width: 18px;
  height: 18px;
}

.chat__min:hover {
  background: rgba(23, 26, 32, 0.06);
}

.chat__log {
  overscroll-behavior: contain;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px 16px 10px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  scrollbar-width: thin;
}

.chat__msg {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  max-width: 100%;
}

.chat__msg--bot {
  justify-content: flex-start;
}

.chat__msg--me {
  justify-content: flex-end;
}

.chat__avatar {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--ink);
}

.chat__avatar img {
  width: 20px;
  height: 20px;
  object-fit: contain;
  /* the mark as a white silhouette on the dark circle */
  filter: brightness(0) invert(1);
}

.chat__bubble {
  max-width: calc(100% - 48px);
  padding: 14px 16px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chat__msg--bot .chat__bubble {
  background: var(--tint);
  color: var(--ink);
  border-bottom-left-radius: 4px;
}

.chat__msg--me .chat__bubble {
  background: var(--ink);
  color: #fff;
  border-bottom-right-radius: 4px;
}

.chat__bubble a {
  color: inherit;
}

.chat__time {
  align-self: center;
  margin: 2px 0;
  font-size: 11px;
  color: var(--muted);
}

/* The form, sent into the log as a message. */
.chat__form {
  display: grid;
  gap: 8px;
  width: calc(100% - 40px);
  margin-left: 40px;
  padding: 14px 16px 16px;
  border-radius: 12px;
  border-top-left-radius: 4px;
  background: var(--tint);
}

.chat__form input,
.chat__form textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  transition: border-color 180ms ease;
}

.chat__form textarea {
  resize: none;
  min-height: 60px;
}

.chat__form input:focus,
.chat__form textarea:focus {
  outline: none;
  border-color: var(--ink);
}

.chat__form .is-missing {
  border-color: var(--danger);
}

.chat__form .is-missing::placeholder {
  color: var(--danger);
}

.chat__form small {
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
}

.chat__form button {
  justify-self: end;
  margin-top: 4px;
  padding: 9px 18px;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}

.chat__form button:disabled {
  opacity: 0.6;
  cursor: progress;
}

.chat__composer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin: 0 16px calc(16px + env(safe-area-inset-bottom, 0px));
  padding: 6px 6px 6px 12px;
  border-radius: 4px;
  background: var(--tint);
}

.chat__composer textarea {
  flex: 1 1 auto;
  max-height: 120px;
  padding: 8px 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  line-height: 1.4;
  resize: none;
}

.chat__composer textarea:focus {
  outline: none;
}

.chat__send {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  margin-bottom: 4px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: var(--ink);
  color: #fff;
  cursor: pointer;
}

.chat__send svg {
  width: 16px;
  height: 16px;
}

.chat__send:disabled {
  opacity: 0.5;
  cursor: progress;
}

/* v53 (iain, 5 Oct: "데스크탑에서는 메세지 버튼이 좌측하단에 있어야지 프리오더 화면 좌측하단에"):
   on a wide screen the chat square leaves the panel's top corner for the bottom-left corner
   of the order view, standing off the bottom by the panel's own bottom padding, the line the
   stage arrows now end on too; the chat opens from that corner as a card, not over the
   panel. The square is still inside the panel in the page, and a panel in motion would carry
   it, so it appears once the panel has come to rest and is gone the moment it leaves. On a
   phone both stay as they were. */
@media (min-width: 761px) {
  .buy__chat {
    position: fixed;
    top: auto;
    left: var(--gutter);
    bottom: clamp(24px, 6vh, 64px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0s, visibility 0s, background 180ms ease, color 180ms ease;
  }

  .buy.is-open .buy__chat {
    opacity: 1;
    visibility: visible;
    transition: opacity 320ms ease 980ms, visibility 0s linear 980ms, background 180ms ease, color 180ms ease;
  }

  .chat {
    right: auto;
    left: var(--gutter);
    bottom: clamp(24px, 6vh, 64px);
    width: min(360px, calc(100vw - var(--buy-w) - 2 * var(--gutter)));
    height: min(600px, calc(100dvh - 2 * clamp(24px, 6vh, 64px)));
    border-radius: 16px;
    box-shadow: 0 12px 48px rgba(23, 26, 32, 0.14);
    transform: translateY(16px);
    transform-origin: bottom left;
  }

  .chat:not(.is-open) {
    pointer-events: none;
  }
}

@media (max-width: 760px) {
  .chat {
    width: 100vw;
    height: 88dvh;
  }

  .chat__form input,
  .chat__form textarea,
  .chat__composer textarea {
    font-size: 16px;
  }
}

/* ── the opening turn (a placeholder) ───────────────────────────────────── */

/* intro.js: the previous craft's studio turntable (assets/spin, the render the
   site shipped with) turns once over the page as it arrives, then dissolves
   away onto the flight render's parked craft. The veil is the studio tone, so
   the ground holds still and only the craft appears to change. It stands in
   until the Portal has a turntable render of its own. */
.intro {
  position: fixed;
  inset: 0;
  z-index: 2; /* over the page (1), under the panel and the chat (4) */
  background: var(--studio);
  pointer-events: none; /* the panel is live from the first moment */
  transition: opacity 700ms ease;
}

.intro.is-gone {
  opacity: 0;
}

.intro canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ── the flight-state line ──────────────────────────────────────────────── */

/* flight-state.js: the craft's state, named, in the top-left — Parked, Launch,
   Hover, Cruise, Landing, Parked — over a hairline that fills with the flight.
   Quiet by design: the muted small caps of the spec labels, a 2 px track, and
   it yields to the get-in-touch ending as that fades in (--ending). */
.flight-state {
  position: fixed;
  left: 50vw;
  bottom: clamp(118px, 17vh, 180px); /* under the craft, above the page's type */
  transform: translateX(-50%);
  z-index: 3; /* over the page and the opening veil, under the panel */
  display: grid;
  gap: 7px;
  justify-items: center;
  pointer-events: none;
  opacity: 0; /* until the player is ready */
  transition:
    opacity 600ms ease,
    left 750ms var(--slide),
    bottom 750ms var(--slide);
}

/* The order view open: centred in the room the panel leaves, at its foot,
   gliding there on the room's own curve as the panel arrives. */
body.is-buying .flight-state {
  left: calc((100vw - var(--buy-w)) / 2);
  bottom: clamp(28px, 6vh, 64px);
}

@media (max-width: 760px) {
  .flight-state {
    bottom: calc(var(--buy-peek, 200px) + 26px);
  }

  /* Above the resting sheet; gone while the sheet is up. */
  body.is-buying .flight-state {
    display: none;
  }
}

.flight-state.is-on {
  opacity: calc(1 - var(--ending, 0));
}

/* Set as the panel's PRE-ORDER kicker is, so the two read as one voice. */
.flight-state__label {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: var(--track-kicker, 0.26em);
  text-transform: uppercase;
  color: var(--muted);
  animation: flight-state-in 460ms ease both;
}

/* The offer breathes (5 Nov 2026): a slow bob, only while it is the offer —
   the state names hold still. */
.flight-state.is-prompt .flight-state__label {
  animation:
    flight-state-in 460ms ease both,
    flight-state-bob 2200ms ease-in-out 700ms infinite;
}

@keyframes flight-state-bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-7px);
  }
}

@keyframes flight-state-in {
  from {
    opacity: 0;
    transform: translateY(7px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.flight-state__track {
  display: block;
  width: clamp(64px, 6vw, 96px);
  height: 2px;
  border-radius: 1px;
  background: rgba(23, 26, 32, 0.12);
  overflow: hidden;
  transition: opacity 400ms ease;
}

/* The offer, before anything has been scrolled: the words alone. */
.flight-state.is-prompt .flight-state__track {
  opacity: 0;
}

.flight-state__track i {
  display: block;
  height: 100%;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 140ms linear;
}

@media (prefers-reduced-motion: reduce) {
  .flight-state__label {
    animation: none;
  }
}

/* ── the wordmark ───────────────────────────────────────────────────────── */

/* The company's name in its logotype, fixed top-centre over everything but
   the panel. The face is served from assets/fonts; until it lands the name
   waits (block, then swap) rather than flashing in the text face. */
@font-face {
  font-family: '911 Porscha';
  src:
    url('/waitlist/assets/fonts/911porscha.woff2') format('woff2'),
    url('/waitlist/assets/fonts/911porscha.otf') format('opentype');
  font-display: swap;
}

.wordmark {
  position: fixed;
  top: clamp(16px, 2.8vh, 26px);
  left: var(--gutter); /* the top-left corner (5 Nov 2026; centred before) */
  z-index: 3; /* over the page and the opening veil, under the panel */
  font-family: '911 Porscha', 'Archivo', sans-serif;
  font-size: clamp(16px, 1.5vw, 22px);
  font-weight: 400;
  letter-spacing: 0.12em;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
}

/* ── the order bar ──────────────────────────────────────────────────────── */

/* The order view, tidied (5 Nov 2026): the name and the figures leave the
   panel for one bar along the foot of the screen — the chat square, then the
   name under its kicker, then the three figures — and the panel keeps only
   what the buyer actually fills in. A phone keeps its sheet as it was. */
.buy-bar {
  display: none;
}

@media (min-width: 761px) {
  body.is-buying .buy-bar {
    position: fixed;
    left: 0;
    right: var(--buy-w); /* centred under the render, in the room it keeps */
    bottom: clamp(24px, 6vh, 64px);
    z-index: 3;
    display: flex;
    /* One line: the figures share a baseline, the labels hang under it, and
       one generous gap carries the row. */
    align-items: baseline;
    justify-content: center;
    gap: clamp(48px, 7vw, 110px);
    pointer-events: none;
    animation: buy-bar-in 600ms ease 400ms both;
  }

  /* The pay actions sit in the panel's own flow, under the terms, as on
     Tesla's block. */

  /* On a wide screen the view is the page: there is nothing to close. */
  .buy__close {
    display: none;
  }

  /* The panel heads with PRE-ORDER over the name, as Tesla's block heads with
     the car's (5 Nov 2026); the figures live on the bar under the render, and
     the license line is one of them now. */
  .buy__body > .buy__license,
  .buy__body > .buy__specs {
    display: none;
  }

  .buy__body > .buy__title {
    margin-bottom: clamp(16px, 2.6vh, 28px);
  }

  /* The state line steps above the bar. */
  body.is-buying .flight-state {
    bottom: clamp(104px, 17vh, 170px);
  }
}

@keyframes buy-bar-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.buy-bar__fig {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 0.14em;
  font-family: var(--font-spec);
  font-size: 27px;
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}

.buy-bar__fig span {
  font-size: 13px;
  font-weight: 600;
}

.buy-bar__lab {
  margin: 7px 0 0;
  font-family: var(--font-spec);
  font-size: 13px;
  font-weight: 400;
  color: var(--muted);
  white-space: nowrap;
}

/* The placeholders wear the finished buttons' clothes (5 Nov 2026): Apple Pay
   black, the card button plain — disabled, but not faded. */
#buy-wallet-mock-apple:disabled,
#buy-place-mock:disabled {
  opacity: 1;
  cursor: default;
}

/* The place action, dressed as the card button: the quiet fill. */
.pay--place {
  background: rgba(23, 26, 32, 0.05);
  color: var(--ink);
}

.pay--place:hover:not(:disabled) {
  opacity: 1;
  background: rgba(23, 26, 32, 0.11);
}

/* The card fields Pay with Card opens in place. */
.buy__cardfields {
  display: grid;
  gap: 10px;
}

.buy__cardfields .buy__two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}


/* ── speak to our founder ───────────────────────────────────────────────── */

/* contact.js: the whole screen goes to glass and the small form floats on it,
   the invitation pattern from the shipped site. The dialog fades both ways;
   .is-closing carries the way out, since a closed dialog otherwise snaps. */
.contact {
  width: min(460px, calc(100vw - 2 * var(--gutter)));
  margin: auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  opacity: 0;
  transition:
    opacity 300ms ease,
    overlay 300ms ease allow-discrete,
    display 300ms ease allow-discrete;
}

.contact[open] {
  opacity: 1;
}

@starting-style {
  .contact[open] {
    opacity: 0;
  }
}

.contact:focus {
  outline: none;
}

.contact.is-closing,
.contact.is-closing::backdrop {
  opacity: 0;
}

.contact::backdrop {
  background: rgba(236, 236, 236, 0.45); /* the studio, washed */
  -webkit-backdrop-filter: blur(26px) saturate(1.2);
  backdrop-filter: blur(26px) saturate(1.2);
  opacity: 0;
  transition:
    opacity 300ms ease,
    overlay 300ms ease allow-discrete,
    display 300ms ease allow-discrete;
}

.contact[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .contact[open]::backdrop {
    opacity: 0;
  }
}

.contact__card,
.contact__thanks {
  position: relative;
}

.contact__thanks[hidden] {
  display: none;
}

.contact__close {
  position: absolute;
  top: -2px;
  right: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}

.contact__close:hover {
  color: var(--ink);
}

.contact__title {
  margin: 0 0 18px;
  font-size: clamp(20px, 2vw, 26px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.contact__fields {
  display: grid;
  gap: 10px;
}

.contact__two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

textarea.buy__input.contact__message {
  height: auto;
  min-height: 112px;
  padding: 12px 16px;
  resize: none;
}

.contact .is-missing {
  border-color: #c2453d;
}

.contact__error {
  margin: 10px 0 0;
  font-size: 12px;
  color: #c2453d;
}

.contact__send {
  width: 100%;
  height: 48px;
  margin-top: 14px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--ink);
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 200ms ease;
}

.contact__send:hover:not(:disabled) {
  opacity: 0.88;
}

.contact__send:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* Where we are, quietly under the form. */
.contact__where {
  margin: 20px 0 0;
  display: grid;
  gap: 4px;
  font-style: normal;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  text-align: center;
}

.contact__where a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.contact__lede {
  margin: 8px 0 0;
  font-size: 13px;
  color: var(--muted);
}

/* 16px keeps iOS Safari from zooming when a field takes focus. */
@media (max-width: 760px) {
  .contact .buy__input {
    font-size: 16px;
  }
}

/* The wordmark, centred on a phone (5 Nov 2026); after the base rule, which
   sits late in this sheet, so the centring wins. */
@media (max-width: 760px) {
  .wordmark {
    left: 50%;
    transform: translateX(-50%);
  }
}

/* The pre-order button is an invisible anchor now (5 Nov 2026): the old
   landing footer is gone for good, and preorder.js still needs the element. */
.preorder--anchor {
  display: none !important;
}

/* The render holds back until the opening turn begins (5 Nov 2026): intro.js
   sets the class as it loads and lifts it on the turn's first frame, so the
   craft arrives already spinning; .backdrop's own transition makes it a fade. */
html.intro-hold .backdrop {
  opacity: 0 !important; /* the hold beats every other opacity rule */
  transition: none !important; /* and is never eased into — it IS the start */
}
