/* Light page under a near-black card. Tried a dark page twice; it can't
   work, and the reason is measurable rather than a matter of taste. The
   card is #0D1014, so the only way to separate it from its surround is to
   make the surround LIGHTER — and any page dark enough to still read as
   "dark mode" sits within ~1.9:1 of the card even when pushed to a washed
   grey (#3A434D), while brightening that far simultaneously drags the
   muted label colour under the 4.5:1 AA floor. A light page gives 16.5:1
   card separation with every text pair comfortably AA. Same reason --accent
   stays neutral near-black: it never has to get along with whichever of
   the 9 per-person line colours a card lands on.

   Every value here is measured, not eyeballed. Contrast vs its own
   background, WCAG 2.1 AA (4.5:1 body text, 3:1 non-text UI):
     --ink on --bg ............ 15.65:1
     --ink on white field ..... 18.11:1
     --muted on --bg .......... 5.08:1
     --border on white field .. 3.78:1  (was 1.72:1 — a real AA failure)
     card vs --bg ............. 16.48:1 (was 1.16:1)
   If you change a colour here, re-measure rather than assuming. */
:root {
  --ink: #14161A;
  --muted: #5F656D;
  --bg: #EDEFEB;
  --field: #FFFFFF;
  --accent: #14161A;
  --accent-contrast: #FFFFFF;
  --accent-light: #E2E4DF;
  --border: #7D848C;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

.page {
  max-width: 480px;
  margin: 0 auto;
  padding: 48px 20px 32px;
}

.hero {
  text-align: center;
  margin-bottom: 32px;
}

.hero h1 {
  font-size: 1.9rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 700;
  margin: 0 0 12px;
}

.subhead {
  color: var(--muted);
  font-size: 1rem;
  margin: 0;
}

/* Block, not flex: the card's only child is absolutely positioned, so it has
   no intrinsic width and a flex parent collapses it to 0. */
/* The form stays visible (not hidden) once a result appears, so the result
   card is a sibling sitting directly under the submit button — needs its
   own top gap, not just the bottom one inherited from the example card use. */
.cardwrap {
  display: block;
  margin-top: 28px;
  margin-bottom: 28px;
}

.cardwrap[hidden] {
  display: none;
}

/* The example card is deliberately smaller than a result card. At the full
   340px it stands 604px tall, which — under the hero — pushed "make yours"
   off a 812px phone screen entirely, so nothing on the first screen told
   anyone there was a next step. A tester stalled here, tried tapping the
   card as if it were the form, and only found the button by chance. */
/* The example card is capped against the VIEWPORT, not a fixed pixel width.
   A fixed 272px was tuned against 375x812 with no browser chrome, which is a
   fiction: Safari's URL bar and toolbar eat ~110px, so on a real phone the
   CTA dropped back below the fold. dvh tracks the actually-visible viewport
   (and updates as the chrome collapses on scroll), so the card yields space
   rather than the button falling off. ~330px is the rest of the first screen:
   hero + caption + CTA + margins. aspect-ratio then narrows the width to
   match, keeping 9:16 intact. Result cards are unaffected — they're below
   the fold by nature and should stay full size. */
.cardwrap--example .card {
  /* height-driven, width auto: .card sets width:100%, and capping only the
     height against that squashes the card to the wrong shape (measured 0.81
     instead of 9:16). Driving from height lets aspect-ratio derive the width
     so the preview always shows the true proportions of the exported image. */
  width: auto;
  max-width: 272px;
  /* clamp, not min: below ~380px tall the card stops being a showcase and
     becomes an illegible thumbnail (142x253 measured on an iPhone SE minus
     Safari chrome), which defeats the point of showing it at all. So on very
     short viewports the card holds its size and the CTA goes back below the
     fold — see the note in spec.md, that last bit is an open design call. */
  height: clamp(380px, calc(100dvh - 325px), 484px);
}

.example-caption {
  text-align: center;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px;
}

/* ============================================================
   The card — "Night Line"
   Geometry: cqw units ONLY. 9:16 is locked so 1cqh == 1.7778cqw,
   and cqh does NOT resolve against container-type:inline-size —
   it silently measures the viewport instead of the card.
   Spacing compresses as stop count (--n) rises; fixed sizes with
   a variable-length route overflow the frame.
   Verified across 2-8 stops with short, long and non-Latin names.
   ============================================================ */

.card {
  container-type: inline-size;
  aspect-ratio: 9 / 16;
  width: 100%;
  max-width: 340px;
  margin-inline: auto;
  position: relative;
  overflow: hidden;
  border-radius: 4cqw;
  background: #0D1014;
  color: #F2F4F0;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --line: #F2B33D;
  --n: 3;
  --over: max(0, var(--n) - 2);
}

.card__inner {
  position: absolute;
  inset: 0;
  /* 14.5% top / 20.5% bottom: keeps content clear of Instagram's
     progress bar and reply bar in Stories. */
  padding: 25.8cqw 8cqw 36.5cqw;
  display: flex;
  flex-direction: column;
}

.c-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 3cqw;
  margin-bottom: calc(6.5cqw - var(--over) * 0.5cqw);
}

.c-handle {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 3.2cqw;
  letter-spacing: 0.06em;
  color: #78848C;
  padding-top: 1.4cqw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* the show-off number */
.c-badge {
  flex: 0 0 auto;
  text-align: center;
  border: 0.8cqw solid var(--line);
  border-radius: 50%;
  width: 17cqw;
  height: 17cqw;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.c-badge b {
  font-size: 8.4cqw;
  font-weight: 700;
  letter-spacing: -0.04em;
  color: var(--line);
  font-variant-numeric: tabular-nums;
}

.c-badge i {
  font-style: normal;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 2.2cqw;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--line);
  margin-top: 0.5cqw;
  opacity: 0.85;
}

/* overflow-wrap is load-bearing, not defensive: the identity is a single
   invented word, so it has no spaces to break at. "the moskvetersburger"
   ran off the edge and was silently clipped by .card's overflow:hidden —
   the card showed "the moskvetersburge". Blends can legitimately get long,
   so it has to be able to break mid-word. */
.c-identity {
  /* --ident-scale is set by JS (setIdentityScale) using the same measurement
     the canvas renderer uses, so the on-page card and the exported PNG shrink
     identically. Defaults to 1 so the card is still correct without JS. */
  font-size: calc((11.5cqw - var(--over) * 0.28cqw) * var(--ident-scale, 1));
  line-height: 0.92;
  letter-spacing: -0.04em;
  font-weight: 700;
  margin: 0 0 calc(5cqw - var(--over) * 0.3cqw);
  text-wrap: balance;
  overflow-wrap: anywhere;
}

.c-line {
  font-size: calc(4.7cqw - var(--over) * 0.1cqw);
  line-height: 1.3;
  font-weight: 500;
  color: #E4E8E3;
  max-width: 27ch;
  margin: 0 0 calc(7cqw - var(--over) * 0.8cqw);
}

/* margin-bottom guarantees a gap from the footer even when the
   footer's margin-top:auto collapses to 0 on a long route. */
.route {
  list-style: none;
  margin: 0 0 4cqw;
  padding: 0;
}

.route li {
  position: relative;
  padding-left: 7.6cqw;
  padding-bottom: calc(5cqw - var(--over) * 0.45cqw);
  /* explicit: CJK / Arabic inflate the default line box enough to
     blow the vertical budget. */
  line-height: 1.15;
  display: flex;
  align-items: baseline;
  gap: 2.2cqw;
}

.route li:last-child { padding-bottom: 0; }

.route li::before {
  content: "";
  position: absolute;
  left: 1.4cqw;
  top: 1.75cqw;
  width: 2.5cqw;
  height: 2.5cqw;
  border-radius: 50%;
  box-sizing: border-box;
  background: #0D1014;
  border: 0.72cqw solid var(--line);
  z-index: 1;
}

.route li::after {
  content: "";
  position: absolute;
  left: 2.42cqw;
  top: 3.5cqw;
  bottom: -1.3cqw;
  width: 0.72cqw;
  background: var(--line);
  z-index: 0;
}

.route li:last-child::after { display: none; }

.route li.is-now::before {
  width: 4.4cqw;
  height: 4.4cqw;
  left: 0.45cqw;
  top: 0.9cqw;
  background: var(--line);
  border-color: var(--line);
}

.route .city {
  font-size: calc(4.5cqw - var(--over) * 0.12cqw);
  font-weight: 500;
  letter-spacing: -0.015em;
  /* Casing is normalised in JS (formatCity) rather than CSS, because CSS
     capitalize can't fix "MOSCOW" and the canvas renderer needs the same
     result anyway. */
  overflow: hidden;
  text-overflow: ellipsis;
}

.route li.is-now .city { font-weight: 700; }

.route .meta {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 2.6cqw;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #6E7A83;
  white-space: nowrap;
  line-height: 1;
  flex: 0 0 auto;
}

.route li.is-now .meta { color: var(--line); }

.c-foot {
  margin-top: auto;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 3cqw;
  letter-spacing: 0.04em;
  color: #78848C;
}

.cta {
  text-align: center;
  margin-bottom: 8px;
}

/* Slides up from the bottom once the real CTA scrolls away. visibility (not
   display) so it stays out of the tab order and the accessibility tree when
   hidden while still being able to animate. env(safe-area-inset-bottom) keeps
   it clear of the iPhone home indicator. */
.sticky-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  justify-content: center;
  padding: 12px 20px calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--bg);
  border-top: 1px solid var(--border);
  visibility: hidden;
  transform: translateY(100%);
  /* Only transform is transitioned. Transitioning visibility would make
     appearing depend on a transition finishing, and transitions are frozen
     whenever the document is hidden — the bar would then never show. This way
     the worst case is no animation, not no button. */
  transition: transform 0.22s ease;
}

.sticky-cta.is-visible {
  visibility: visible;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .sticky-cta { transition: none; }
}

#make-yours-btn,
#sticky-make-yours,
#submit-btn,
#save-btn {
  background: var(--accent);
  color: var(--accent-contrast);
  border: none;
  border-radius: 999px;
  padding: 14px 32px;
  font-size: 1rem;
  font-weight: 600;
  min-height: 48px;
  cursor: pointer;
}

#make-yours-btn:hover,
#submit-btn:hover,
#save-btn:hover {
  opacity: 0.9;
}

#submit-btn:disabled,
#save-btn:disabled,
#regenerate-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Saving is the point of the card, so it's the primary action; regenerate is
   quieter because it spends one of only three generations an hour. */
#regenerate-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 13px 24px;
  font-size: 0.95rem;
  font-weight: 600;
  min-height: 48px;
  color: var(--accent);
  cursor: pointer;
}

#regenerate-btn:hover {
  border-color: var(--accent);
}

.result-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
}

/* The rate-limit count belongs to regenerate, not to sharing, so it sits last
   and quieter rather than interrupting the button and its own next-step hint. */
.remaining-note {
  text-align: center;
  font-size: 0.8rem;
  color: var(--muted);
  margin: 8px 0 0;
  min-height: 1em;
}

/* The rendered PNG, shown in place of the DOM card so that press-and-hold and
   right-click save a real image. Same box as .card so nothing shifts. */
.card-image {
  display: block;
  width: 100%;
  max-width: 340px;
  height: auto;
  aspect-ratio: 9 / 16;
  /* matches 4cqw on .card at its ~335px rendered width */
  border-radius: 13px;
  margin-inline: auto;
}

.card-image[hidden] { display: none; }

.save-hint {
  text-align: center;
  font-size: 0.85rem;
  color: var(--muted);
  margin: 12px 0 0;
}

.save-hint--done {
  color: var(--accent);
  font-weight: 600;
}

.form-section {
  margin-top: 32px;
  border-top: 1px solid var(--border);
  padding-top: 28px;
}

.field {
  margin-bottom: 22px;
}

/* Uppercase, letter-spaced, muted: echoes the card's own micro-labels
   (ORIGIN / NOW / STOPS) rather than the generic bold-label convention the
   form used before the card existed — a small, deliberate tie between the
   two rather than two unrelated type systems on one page. */
label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

.hint {
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
}

/* min-height 48px meets the ~44-48px touch-target guidance (Apple HIG /
   Material); 16px font-size is also load-bearing — anything smaller triggers
   iOS Safari's auto-zoom on focus. */
input[type="text"],
input[type="number"] {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  font-size: 1rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--field);
  color: var(--ink);
}

input[type="text"]:focus,
input[type="number"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The "@" is part of this box, not the input's typed value — border and
   background live here; the input inside is a bare text field. */
.input-prefix {
  display: flex;
  align-items: center;
  min-height: 48px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--field);
  padding-left: 14px;
}

.input-prefix:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.input-prefix span {
  color: var(--muted);
  font-size: 1rem;
  flex-shrink: 0;
}

.input-prefix input {
  border: none;
  background: none;
  padding-left: 2px;
}

.input-prefix input:focus {
  outline: none;
}

.between-city-row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.between-city-input {
  flex: 3;
}

.between-city-years {
  flex: 1;
  min-width: 0;
}

.remove-city-btn {
  flex-shrink: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 10px;
  width: 48px;
  min-height: 48px;
  font-size: 1.1rem;
  color: var(--muted);
  cursor: pointer;
}

/* Previously a 1px dashed var(--border) — border and page background were
   both near-white, so the button read as barely there. Solid border in
   --muted (visible grey) plus a faint fill gives it real button affordance. */
.secondary-btn {
  background: var(--accent-light);
  border: 1px solid var(--muted);
  border-radius: 10px;
  padding: 12px 14px;
  min-height: 44px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  width: 100%;
}

.secondary-btn:hover {
  background: var(--border);
}

.secondary-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.cap-note {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 6px 0 0;
  min-height: 1em;
}

#submit-btn {
  width: 100%;
  margin-top: 4px;
}

.form-status {
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  margin-top: 12px;
}

.site-footer {
  text-align: center;
  margin-top: 40px;
}

.site-footer a {
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: none;
}

/* ---- feedback (footer) ---------------------------------------------------
   Collapsed by default. The point is to exist for the few people who want to
   say something, not to ask anything of everyone else — so it's a quiet
   footer word, not a prompt in the share flow where friction costs shares. */
.footer-link {
  background: none;
  border: none;
  padding: 4px 8px;
  font: inherit;
  font-size: 0.85rem;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}

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

#feedback-panel {
  margin: 12px auto 0;
  max-width: 380px;
  text-align: left;
}

#feedback-panel[hidden] { display: none; }

#feedback-panel textarea,
#feedback-panel input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  margin-bottom: 8px;
  font-family: inherit;
  font-size: 1rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--field);
  color: var(--ink);
  resize: vertical;
}

#feedback-panel textarea:focus,
#feedback-panel input[type="text"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.feedback-status {
  font-size: 0.85rem;
  color: var(--muted);
  margin: 8px 0 0;
}

.feedback-status[hidden] { display: none; }

/* The disclosure sits under the submit button: honest, but not an obstacle
   between someone and the thing they came to do. */
.disclosure {
  font-size: 0.75rem;
  color: var(--muted);
  text-align: center;
  margin: 10px 0 0;
}

/* Visible to screen readers only. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
