/*
 * staterank — base shell styles.
 *
 * Design language: clean / neutral (Beli-inspired), one-handed mobile-first.
 * - Light off-white background (matches theme_color in the manifest so the
 *   iOS splash and Android system chrome blend seamlessly into the app).
 * - Generous tap targets (≥44px) so accidental taps are rare while moving.
 * - safe-area-inset-* respected via env() — the layout never hides under
 *   the notch or the home indicator.
 * - System UI font stack to feel native on both iOS and Android without
 *   shipping a webfont (every byte costs on remote connections).
 *
 * Subsequent feature tasks should extend this file rather than fork it —
 * the visual baseline lives here so the app stays cohesive as it grows.
 */

:root {
  /*
   * Design tokens — single source of truth consumed by every screen
   * (ranked-list, comparison, Day-1 start) so the three views stay in
   * visual lockstep.  Adding a per-screen one-off override here is
   * the only sanctioned escape hatch; everywhere else, reach for a token.
   *
   * Color tokens — keep --bg in sync with manifest.webmanifest theme_color
   * and the <meta name="theme-color"> tag in index.html (drives the iOS
   * splash + Android system chrome blend).
   */
  --bg: #f5f5f7;
  --surface: #ffffff;
  --surface-sunken: #fafafa;
  --fg: #18181b;
  --fg-muted: #6b6b73;
  --fg-subtle: #9a9aa3;
  --border: #e6e6ea;
  --border-strong: #d4d4d8;

  /*
   * Beli signature: a warm coral-red accent paired with subtle tier
   * tints (top-three rank numerals pick up a soft hue, mirroring the
   * green/yellow/red rating dots in Beli's restaurant cards without
   * shouting).  --accent stays the single primary call-to-action color.
   */
  --accent: #ef4444;
  --accent-soft: #fef2f2;
  --accent-fg: #ffffff;
  --tier-top: #16a34a;     /* #1 — green; "love it" */
  --tier-2:   #0891b2;     /* #2 — teal; clearly distinct from #1 + #3 */
  --tier-3:   #ca8a04;     /* #3 — amber; warm-but-not-podium */
  --tier-mid: #ca8a04;     /* legacy alias — still used elsewhere */
  --tier-low: #71717a;     /* lower — neutral grey, not a punishment */

  /* Spacing scale (4px base). */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 2.5rem;

  /*
   * Type scale — generous, breathable; mobile users read at arm's length
   * and the comparison prompt is the visual anchor of the whole flow,
   * so it gets the top of the scale (--text-3xl) all to itself.
   */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;
  --text-3xl: 2.5rem;

  --radius: 14px;
  --radius-sm: 10px;
  --radius-pill: 999px;

  /*
   * Elevation tokens.  Cards on the ranked-list + comparison views pick
   * up --shadow-card so they read as lifted surfaces above the neutral
   * wash — a small thing but a big chunk of why Beli feels intentional
   * rather than flat-prototype.  Kept very soft (no hard drop) so the
   * app still feels native-quiet, not webby.
   */
  --shadow-card: 0 1px 2px rgba(24, 24, 27, 0.04),
    0 4px 16px rgba(24, 24, 27, 0.04);
  --shadow-cta: 0 1px 2px rgba(239, 68, 68, 0.18),
    0 6px 18px rgba(239, 68, 68, 0.16);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  /* Block iOS rubber-band overscroll glow on the body — feature tasks
     can opt in per-section with overscroll-behavior: contain if needed. */
  overscroll-behavior-y: none;
  /* Disable double-tap-to-zoom (paired with the viewport meta). */
  touch-action: manipulation;
}

/*
 * Safe-area-aware shell. env(safe-area-inset-*) returns 0 on browsers
 * without notches, so this works on every device while paying attention
 * to the notch/home-indicator on iPhones with viewport-fit=cover.
 */
.app-shell {
  min-height: 100vh;
  /* iOS 100vh excludes the dynamic toolbar; svh handles that natively
     where supported, falling back to vh otherwise. */
  min-height: 100svh;
  padding-top: max(var(--space-4), env(safe-area-inset-top));
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
  padding-left: max(var(--space-4), env(safe-area-inset-left));
  padding-right: max(var(--space-4), env(safe-area-inset-right));
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 640px;
  margin: 0 auto;
}

.app-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.app-header__title-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.app-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 800;
  letter-spacing: -0.025em;
  /* Tiny accent dot before the wordmark — borrows from Beli's own
   * "anchor mark next to the logotype" treatment.  Inline pseudo-
   * element so the title row stays a single flex child elsewhere. */
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
}

.app-title::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--accent);
  /* Pull the dot down slightly so it visually sits on the baseline
   * rather than the cap-line of the bold wordmark. */
  transform: translateY(-0.08em);
  flex: 0 0 auto;
}

.app-subtitle {
  margin: 0;
  color: var(--fg-muted);
  font-size: var(--text-base);
}

.app-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.app-placeholder {
  margin: 0;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--fg-muted);
  box-shadow: var(--shadow-card);
  text-align: center;
}

/*
 * Install affordances. The Android button is shown imperatively from
 * app.js when beforeinstallprompt fires; the iOS hint is shown when we
 * detect iOS Safari running in a regular tab (not already standalone).
 *
 * Minimum 44px tap target — Apple HIG threshold.
 */
.install-button {
  min-height: 48px;
  padding: var(--space-3) var(--space-5);
  font: inherit;
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: 0.01em;
  color: var(--accent-fg);
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  box-shadow: var(--shadow-cta);
  /* Defensive tap target — full-width buttons are easier to hit
     one-handed than narrow centered ones. */
  width: 100%;
}

.install-button:active {
  /* Subtle press feedback without a heavy ripple. */
  filter: brightness(0.95);
}

/* Once the user has installed the PWA and launched it from the home
 * screen, the install affordance is dead weight — and on iOS the
 * standalone display mode means there's no Share-sheet hint to show
 * either. Hide both, plus the surrounding card, so the bottom of the
 * screen isn't permanent dead space. */
@media (display-mode: standalone) {
  .install-button,
  .ios-install-hint,
  .app-body {
    display: none;
  }
}

.ios-install-hint {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  text-align: center;
}

.ios-install-hint strong {
  color: var(--fg);
}

/* Hide [hidden] reliably even when display is overridden elsewhere. */
[hidden] {
  display: none !important;
}

/* ── Ranked-list home screen ─────────────────────────────────────────
 *
 * Beli-inspired: white-card list on a neutral wash, big rank numerals,
 * state name as primary text, generous row height so taps are easy
 * one-handed.  Layout collapses cleanly from 12 rows → 1 row → empty
 * without re-jigging the surrounding shell.
 */

.ranked-list-root {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Fill the remaining shell height so the bottom-anchored CTA
   * (start-trip on Day 1, add-state on Days 2–12) lands inside the
   * thumb-zone on every screen size rather than clustering against
   * the rank heading at the top of the viewport. */
  flex: 1 1 auto;
  min-height: 0;
}

/* "Day X of 12" — informational chip in the upper third of the screen,
   well out of the way of thumb-reach interactives at the bottom. */
.day-indicator {
  margin: 0;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--fg-muted);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  /* Tabular figures so the "X" doesn't wiggle as it ticks up. */
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-card);
  /* Never wrap — chip should stay on one line even when the title
   * eats most of the title-row's horizontal space. */
  white-space: nowrap;
  flex-shrink: 0;
}

.day-indicator__label {
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  font-weight: 600;
}

.day-indicator__value {
  color: var(--fg);
  font-weight: 700;
  font-size: var(--text-base);
}

.day-indicator__of {
  color: var(--fg-subtle);
}

.rank-heading {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* The list itself — white card, hairline dividers between rows so it
 * reads as a single grouped surface rather than 12 disconnected chips.
 * Soft elevation lifts it off the neutral wash background — the most
 * visible "this is a designed app, not raw HTML" cue on the screen. */
.rank-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-card);
}

.rank-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  /* 64px row height — generous Beli-style density, well above the
     44px tap-target floor.  Same gauge on every row so the list reads
     as a single rhythm rather than ragged stripes. */
  min-height: 64px;
  /* Right padding tightened to make room for the per-row Move-Up /
   * Move-Down nudge buttons (a11y fallback for the drag gesture)
   * without truncating long state names like "New Hampshire". */
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border);
}

/* Scoped to .rank-list so the dashed candidate card (also a .rank-row
 * but standalone, last child of its wrapper) keeps its border-bottom.
 * Without the scope, this rule strips the candidate's bottom edge. */
.rank-list .rank-row:last-child {
  border-bottom: none;
}

.rank-row__rank {
  /* Rank numeral — sized to balance with the state-icon silhouette
     to its right.  Top three pick up tier tints (see below).  Was
     too dominant at --text-xl; --text-lg lets the icon read as the
     primary identifier. */
  flex: 0 0 auto;
  min-width: 2.25ch;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--fg-subtle);
  font-variant-numeric: tabular-nums;
  text-align: right;
  letter-spacing: -0.02em;
}

/* Tier-color top three — subtle, just enough to suggest "podium" without
 * shouting.  Mirrors Beli's green/yellow/grey rating dots applied to the
 * rank numeral instead of a separate glyph. */
.rank-row[data-rank="1"] .rank-row__rank {
  color: var(--tier-top);
}
.rank-row[data-rank="2"] .rank-row__rank {
  color: var(--tier-2);
}
.rank-row[data-rank="3"] .rank-row__rank {
  color: var(--tier-3);
}

/* State silhouette icon — small SVG outline rendered between the rank
 * numeral and the state name. SVG <use> references symbols injected
 * into the document by app.js (state-defs.svg). Sized to match the
 * rank numeral's vertical rhythm so the row reads as a unified strip. */
.rank-row__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--fg-subtle);
}

.rank-row__icon svg {
  width: 100%;
  height: 100%;
  fill: currentColor;
}

.rank-row[data-rank="1"] .rank-row__icon {
  color: var(--tier-top);
}
.rank-row[data-rank="2"] .rank-row__icon {
  color: var(--tier-2);
}
.rank-row[data-rank="3"] .rank-row__icon {
  color: var(--tier-3);
}

.rank-candidate .rank-row__icon {
  color: var(--accent);
}

.rank-row__name {
  flex: 1 1 auto;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.005em;
  /* Truncate gracefully on absurdly long names rather than wrapping
     into a multi-line row that breaks the density rhythm. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rank-row--tappable {
  cursor: pointer;
}

.rank-row--tappable:active {
  background: var(--bg);
}

.rank-row--tappable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Empty state — clear, calm, points the user at the Start-trip CTA
 * below without screaming about it.  Day 1 is the first impression of
 * the app; a generous, hopeful card beats a terse error-looking strip. */
.rank-empty {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-7) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
  box-shadow: var(--shadow-card);
}

.rank-empty__headline {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg);
}

.rank-empty__sub {
  margin: 0;
  max-width: 28ch;
  color: var(--fg-muted);
  font-size: var(--text-base);
  line-height: 1.45;
}

/* Placeholder add-state slot.  Sits in the lower portion of the screen
 * so it stays in thumb reach across all list lengths.  Disabled until
 * the sibling task wires up the real flow — the visual affordance is
 * here so the empty/partial layouts are not jarring rewrites later. */
.rank-add-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 56px;
  padding: var(--space-3) var(--space-5);
  font: inherit;
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--fg-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: not-allowed;
  box-shadow: var(--shadow-card);
  /* Pin to bottom of shell — same anchor as .rank-start-trip so the
   * primary action lives in the thumb-zone on every day of the trip. */
  margin-top: auto;
}

.rank-add-slot__plus {
  font-size: var(--text-xl);
  line-height: 1;
  font-weight: 400;
}

/* Day-1 "Start trip" control.  Visually distinct from the inert
 * .rank-add-slot — this is the one moment in the lifecycle where the
 * bottom control is an active call-to-action, so it gets the accent
 * fill to draw the eye.  Same min-height (48px) and full-width footprint
 * as the add-slot so the layout doesn't jump when the user taps it and
 * the screen re-renders into the seeded state. */
.rank-start-trip {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  width: 100%;
  min-height: 64px;
  padding: var(--space-4) var(--space-5);
  font: inherit;
  font-weight: 700;
  color: var(--accent-fg);
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  box-shadow: var(--shadow-cta);
  /* Pin the Day-1 primary CTA into the lower portion of the shell so
   * it's always inside the thumb-zone on a one-handed grip — matches
   * the same anchor used by .rank-add-slot on Days 2–12. */
  margin-top: auto;
}

.rank-start-trip:active:not(:disabled) {
  filter: brightness(0.95);
}

.rank-start-trip:disabled {
  opacity: 0.7;
  cursor: progress;
}

.rank-start-trip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.rank-start-trip__label {
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.rank-start-trip__sub {
  font-size: var(--text-sm);
  font-weight: 500;
  opacity: 0.9;
}

/* Active state for the Add-state CTA.  Becomes the bottom-screen
 * call-to-action on Days 2–12 once a candidate is queued — accent
 * fill (matches .rank-start-trip on Day 1) so the eye lands on it
 * immediately and thumb reach is preserved. */
.rank-add-slot--active {
  color: var(--accent-fg);
  background: var(--accent);
  border-color: var(--accent);
  cursor: pointer;
  box-shadow: var(--shadow-cta);
  min-height: 64px;
  font-weight: 700;
}

.rank-add-slot--active:active:not(:disabled) {
  filter: brightness(0.95);
}

.rank-add-slot--active:disabled {
  opacity: 0.7;
  cursor: progress;
}

.rank-add-slot--active:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Head-to-head comparison view ───────────────────────────────────
 *
 * Replaces the ranked-list screen while a comparison session is
 * active.  Two large vertically-stacked tap targets, each ≥40% of
 * the viewport height so thumbs land cleanly one-handed.  No extra
 * chrome — just the question, the two choices, and a small "or" hinge.
 */

.compare-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Fill the available vertical space inside the shell so the two
   * cards expand to fill the screen on phones rather than clustering
   * at the top. */
  flex: 1 1 auto;
  min-height: 0;
}

/* Hero prompt for the comparison.  This is the AC-named focal point of
 * the screen — large, centered, and given room to breathe.  Tracks the
 * top of the type scale so the question reads instantly when the user
 * glances down at their phone mid-trip; clamp() pulls it back on
 * narrow / short viewports (iPhone SE class) so the two choice cards
 * both stay above the fold without scrolling. */
.compare-heading {
  margin: 0;
  padding: 0 var(--space-2);
  font-size: clamp(var(--text-2xl), 7.5vw, var(--text-3xl));
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
  text-align: center;
  color: var(--fg);
}

.compare-progress {
  margin: 0;
  color: var(--fg-subtle);
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: center;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.compare-stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-height: 0;
  /* Keep the stack inside thumb-reach: cap the total height so the
   * cards stay big enough to tap but never push the user's reach
   * past the top of the screen on tall phones. */
  max-height: 75svh;
}

.compare-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  /* Huge tap target — each card is at least 30% of a 700px-tall
   * viewport.  Flex-grow lets them split the remaining space evenly. */
  min-height: 30vh;
  flex: 1 1 0;
  padding: var(--space-5) var(--space-4);
  font: inherit;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  box-shadow: var(--shadow-card);
  /* Prevent iOS Safari long-press menu over the tappable cards. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  transition: transform 120ms ease, filter 120ms ease, background 120ms ease,
    box-shadow 120ms ease;
}

.compare-choice:active:not(:disabled) {
  transform: scale(0.98);
  background: var(--bg);
}

.compare-choice:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.compare-choice__name {
  font-size: clamp(var(--text-xl), 6vw, var(--text-2xl));
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

.compare-choice__sub {
  font-size: var(--text-sm);
  color: var(--fg-muted);
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* Variants — subtle differentiation, the candidate gets the accent
 * outline + soft tinted surface so the user has a visual anchor for
 * which side is "new" without it screaming louder than the existing
 * entry.  Inner ring stacks on top of the shared card elevation so the
 * card still feels lifted off the page. */
.compare-choice--candidate {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px var(--accent) inset,
    0 1px 2px rgba(239, 68, 68, 0.08),
    0 4px 16px rgba(239, 68, 68, 0.08);
}

.compare-choice--candidate:active:not(:disabled) {
  /* Keep the tinted-surface treatment on press rather than reverting
   * to the neutral bg; otherwise the candidate visually swaps identity
   * mid-tap which reads as confusing. */
  background: var(--accent-soft);
  transform: scale(0.98);
}

.compare-choice--candidate .compare-choice__sub {
  color: var(--accent);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.compare-divider {
  align-self: center;
  font-size: var(--text-sm);
  color: var(--fg-muted);
  text-transform: lowercase;
  letter-spacing: 0.1em;
  font-weight: 500;
  /* Pull the "or" tight against both cards. */
  padding: 0 var(--space-3);
}

.compare-cancel {
  align-self: center;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  min-height: 44px;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--fg-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  text-decoration: underline;
}

/*
 * Top bar above the comparison cards.  Holds the undo affordance on
 * the left when there's history to revert.  Always rendered (even
 * empty) so the cards' vertical position doesn't shift when undo
 * appears mid-flow.  min-height matches the undo button's tap target
 * so the empty state reserves the same space.
 */
.compare-topbar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  min-height: 44px;
}

/*
 * Undo pill in the comparison view.  Visually subordinate to the two
 * primary choice cards (no accent fill, smaller text, outline style)
 * so the eye lands on the choices first.  Sits at the top of the
 * screen — comfortably reachable with a thumb during one-handed use
 * but well clear of the primary tap targets so a misfire on the
 * choice can't land on undo.
 *
 * Pill geometry (44px tap height, ample horizontal padding) keeps
 * the touch target above the iOS HIG floor while keeping the visual
 * footprint compact.
 */
.compare-undo,
.rank-undo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  /* Same tap-feedback hygiene as the comparison cards so iOS doesn't
   * paint its default highlight overlay on top of our own styling. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  transition: filter 120ms ease, background 120ms ease;
}

.compare-undo:active:not(:disabled),
.rank-undo:active:not(:disabled) {
  background: var(--bg);
  filter: brightness(0.97);
}

.compare-undo:focus-visible,
.rank-undo:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.compare-undo:disabled,
.rank-undo:disabled {
  opacity: 0.6;
  cursor: progress;
}

.compare-undo__glyph,
.rank-undo__glyph {
  font-size: var(--text-lg);
  line-height: 1;
  /* Pull the glyph slightly upward so the curl sits visually centred
   * with the lowercase x-height of the label. */
  transform: translateY(-1px);
}

.compare-undo__label,
.rank-undo__label {
  letter-spacing: 0.02em;
}

/*
 * Top row on the home screen (day indicator + optional undo pill).
 * Flex row so the undo pill anchors to the right while the day
 * chip stays on the left; collapses cleanly when undo is absent.
 */
.rank-toprow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.compare-cancel:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Slide-in highlight for the row that just landed.  Brief — reinforces
 * the ranking moment on-brand for Beli's feel, then fades back to the
 * neutral row so the list reads as a normal tier list within a second.
 * Tint uses the accent token's red-channel so the highlight rebrands
 * automatically if --accent ever shifts. */
@keyframes rank-just-placed-slide {
  0% {
    transform: translateX(-12px);
    opacity: 0;
    background: var(--bg);
  }
  40% {
    transform: translateX(0);
    opacity: 1;
    background: rgba(239, 68, 68, 0.08);
  }
  100% {
    transform: translateX(0);
    opacity: 1;
    background: transparent;
  }
}

.rank-row--just-placed {
  animation: rank-just-placed-slide 700ms ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .rank-row--just-placed {
    animation: none;
    /* Still mark the row briefly — a static tint preserves the
     * "this is the one that just landed" cue without motion. */
    background: rgba(239, 68, 68, 0.06);
  }
  .compare-choice {
    transition: none;
  }
}

/*
 * Short-viewport adjustments — iPhone SE / iPhone 8 (≤ 700px tall) have
 * less headroom than modern 6"+ phones, and the comparison view's two
 * cards can get pushed below the fold by the header + hero prompt.
 * Tighten the vertical rhythm so both choice cards land inside the
 * thumb-zone without requiring a scroll.  Pure styling; the cards stay
 * comfortably above the 44px tap-target floor.
 */
@media (max-height: 700px) {
  .app-shell {
    gap: var(--space-3);
  }
  .compare-wrap {
    gap: var(--space-3);
  }
  .compare-stack {
    gap: var(--space-2);
  }
  .compare-choice {
    min-height: 25vh;
    padding: var(--space-4) var(--space-4);
  }
}

/* ── Drag-to-reorder (manual override) ──────────────────────────────
 *
 * Pickup affordance applied to a rank-row while the user has long-
 * pressed it.  Lifts the row off the surface (subtle scale + heavier
 * shadow) so the dragging element reads as "in motion" against the
 * settled siblings.  Transitions are kept tight so the lift fires the
 * instant the long-press timer elapses — any latency here would make
 * the gesture feel unresponsive.
 *
 * The rank-row defaults to `transition: transform 160ms` so live-
 * reflow of siblings into the projected drop slot animates rather than
 * snap-jumping (the JS toggles inline transforms during a drag).  The
 * lifted row itself overrides the transition to `none` while engaged
 * so the finger-tracking translate has zero perceived lag.
 */
.rank-list {
  /* Make sure the lifted row's z-index puts it above siblings — the
   * .rank-list card has its own stacking via box-shadow, so we need
   * to establish a stacking context here. */
  position: relative;
  z-index: 0;
}

.rank-row {
  transition: transform 160ms ease;
  /* The drag module sets touch-action: none on the row only after the
   * long-press fires, so the steady-state row continues to pass touch
   * gestures through to the page scroller. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  position: relative;
}

.rank-row--draggable {
  cursor: grab;
}

/* Candidate card wrapper + "new state unlocked" banner.  The banner
 * is a small accent-colored uppercase label that sits flush above the
 * dashed card so the candidate reads as a fresh delivery, not just
 * another row.  Letter-spaced + small caps give it a tactile-receipt
 * feel without taking the visual weight of the dragged card itself.
 */
.rank-candidate-wrap {
  margin-bottom: var(--space-3);
}

.rank-candidate__banner {
  margin: 0 0 var(--space-1);
  padding: 0 var(--space-3);
  font-size: var(--text-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Date-gated waiting line — shown when the next TRIP_ORDER state
 * exists but hasn't unlocked yet.  Anonymous on purpose: the state
 * name + silhouette stay hidden so each day's reveal is a small
 * surprise.  Light line-style, not a card, so it doesn't compete
 * with the actual ranking. */
.rank-locked {
  margin: 0 0 var(--space-3);
  padding: var(--space-3) var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--fg-subtle);
}

/* Trip-complete celebratory banner — replaces the candidate card after
 * the user has placed all 12 states.  Soft accent surface, not loud,
 * since the user revisits this screen daily after completion. */
.rank-trip-complete {
  margin-bottom: var(--space-3);
  padding: var(--space-4);
  background: var(--surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius-md, 12px);
  text-align: center;
}

.rank-trip-complete__banner {
  margin: 0 0 var(--space-1);
  font-size: var(--text-lg);
  font-weight: 800;
  color: var(--accent);
  letter-spacing: -0.01em;
}

.rank-trip-complete__sub {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--fg-muted);
  line-height: 1.4;
}

/* Stats footer — terse progress readout below the ranked list. */
.rank-stats {
  margin: var(--space-3) 0 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}

.rank-stats__count {
  font-weight: 600;
  color: var(--fg);
}

.rank-stats__pct {
  font-weight: 700;
  color: var(--accent);
}

.rank-stats__sep {
  color: var(--fg-subtle);
}

/* Candidate card — the next state up for placement, rendered above the
 * ranked list.  Visually a row but standalone (no parent .rank-list,
 * no border-bottom continuation) and dashed-outlined to read as
 * "pending" rather than ranked.  The "+" sigil sits where the rank
 * numeral would, and the trailing hint cues the gesture.
 */
.rank-candidate {
  background: var(--surface);
  border: 2px dashed var(--accent);
  border-radius: var(--radius-md, 12px);
  /* Override the .rank-row border-bottom so the dashed border shows
   * cleanly on all four sides. */
  border-bottom: 2px dashed var(--accent);
  /* Drag-reorder needs touch-action: pan-y → none on engagement, and
   * the shared .rank-row rule already handles that.  No extra rules
   * needed here for gesture wiring. */
}

.rank-row__rank--candidate {
  color: var(--accent);
  font-weight: 800;
}

.rank-candidate__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}

.rank-candidate__hint {
  font-size: var(--text-xs, 0.75rem);
  color: var(--fg-subtle);
  font-style: italic;
  line-height: 1.2;
}

.rank-row--lifted {
  transition: none;
  transform-origin: center;
  background: var(--surface);
  box-shadow: 0 12px 32px rgba(24, 24, 27, 0.18),
    0 2px 6px rgba(24, 24, 27, 0.08);
  /* Hairline accent border so the lifted row reads as "selected" in
   * addition to being elevated.  Subtle on light backgrounds, distinct
   * on the rank-list card. */
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
  cursor: grabbing;
}

/* Nudge button column — small chevron pair on the right edge of each
 * row.  Provides the keyboard / non-drag accessibility fallback for
 * the drag-to-reorder feature.  Sized small enough to stay visually
 * subordinate to the state name (the row's primary content) but each
 * button still clears a 32px tap target stacked vertically — combined
 * height ≥ 64px sits inside the row.
 */
.rank-row__nudge {
  display: inline-flex;
  flex-direction: row;
  flex: 0 0 auto;
  gap: var(--space-1);
  margin-left: var(--space-2);
  /* Reserve a fixed footprint so rows with only one nudge button (top
   * row, bottom row, neighbours of the seed) align horizontally with
   * rows that show both — prevents the state-name column from wiggling
   * across rows.  Sized to fit two 32px buttons + a 4px gap with a
   * little breathing room, leaving the bulk of the row width for the
   * state name even on long ones like "New Hampshire". */
  min-width: 72px;
  justify-content: flex-end;
}

.rank-row__nudge-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  font: inherit;
  font-size: 0.75rem;
  color: var(--fg-subtle);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}

.rank-row__nudge-btn:hover,
.rank-row__nudge-btn:focus-visible {
  color: var(--fg);
  border-color: var(--border-strong);
  background: var(--bg);
}

.rank-row__nudge-btn:active {
  background: var(--bg);
  color: var(--accent);
}

.rank-row__nudge-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .rank-row {
    transition: none;
  }
  .rank-row--lifted {
    transform: none !important;
  }
}

/* Visually-hidden text for screen readers only.  Used inside rank rows
 * to give assistive tech a readable "Rank 1, Vermont" instead of just
 * the bare numeral + state. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
