@import url("tokens.css");

/* ---------- reset, only what is needed ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--ui); font-size: var(--t-body); line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
/* Form controls do NOT inherit colour — the UA default is black, so typed
   text was black on a near-black surface in dark mode while the grey
   placeholder looked fine. Inherit explicitly, both of them. */
input, textarea, select { font: inherit; color: var(--ink); }
input::placeholder, textarea::placeholder { color: var(--faint); opacity: 1; }
img { display: block; max-width: 100%; }
[hidden] { display: none !important; }
:focus-visible { outline: 2px solid var(--warm); outline-offset: 3px; border-radius: var(--r-sm); }
/* Turnstile renders a short-lived, interaction-only challenge here. It must
   remain paintable for the provider, but never shifts the restaurant UI. */
.turnstile-mount { position: fixed; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }

.app {
  max-width: 460px; margin: 0 auto; min-height: 100dvh;
  display: flex; flex-direction: column;
  padding-top: env(safe-area-inset-top, 0px);
}
/* The stage owns the flex sizing, not the view classes. When the deck view
   cleared className the deck's own flex:1 had no flex parent, so the cards
   laid out at zero height and rendered invisible while the DOM looked
   perfectly correct. View classes add specifics; they never supply the
   box. */
#stage { flex: 1; min-height: 0; display: flex; flex-direction: column; }

/* ---------- masthead ---------- */
.mast {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--s-3); padding: var(--s-4) var(--s-5) var(--s-2);
}
/* Over the photograph the masthead needs light ink and no ground of its
   own — the card's top gradient is what makes it readable. */
/* The card no longer runs under the chrome, so the chrome keeps its own
   ink in both states. The on-deck class remains only as a hook. */
/* Optical sizing does the work: at display sizes Bodoni's contrast is
   the whole character of the face, and the default text-size cut was
   flattening it. The tracking opens a touch to match. */
.mast-word {
  grid-column: 2; font-family: var(--display); font-weight: 500;
  font-variation-settings: "opsz" 72; font-optical-sizing: auto;
  font-size: 1.62rem; letter-spacing: .035em; text-align: center; margin: 0;
  line-height: 1;
}
.mast-place {
  grid-column: 1; justify-self: start; display: flex; align-items: center; gap: var(--s-1);
  font-size: var(--t-meta); color: var(--muted); min-height: 44px;
  /* "Koregaon Park, Pune" wrapped to two lines and shoved the wordmark
     off its own baseline. One line, truncated, always. */
  max-width: 100%; min-width: 0;
}
.mast-place span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mast-place svg { flex: 0 0 auto; }
.mast-tune { grid-column: 3; justify-self: end; min-height: 44px; min-width: 44px;
  display: grid; place-items: center; color: var(--muted); }
.mast-place:active, .mast-tune:active { opacity: .55; }

/* ---------- the card ------------------------------------------------------
   One surface. The photograph fills it, the gradient sits on the
   photograph, and every word sits on the gradient — there is no second
   white panel, so reading never leaves the picture. */
/* Said before the cards, not after: someone who has already decided the
   app is broken will not read a footnote. */
/* A tinted panel here read as an error, which this is not: it is us being
   straight about what we could not find. Plain muted type in the page's
   own voice, no ground of its own. */
.deck-note {
  margin: 0 var(--s-5) var(--s-2); color: var(--muted);
  font-size: var(--t-meta); line-height: 1.45; text-wrap: pretty;
}
.deck-note-more {
  color: var(--ink); font-size: inherit; text-decoration: underline;
  text-underline-offset: 3px; text-decoration-color: var(--line);
}
.deck-note-more:disabled { color: var(--faint); text-decoration: none; }

.deck { position: relative; flex: 1; margin: var(--s-2) var(--s-4) var(--s-4); min-height: 0; }
.card {
  --depth: 0;                     /* 0 at rest, 1 once scrolled past the crop */
  position: absolute; inset: 0;
  border-radius: var(--r-photo); overflow: hidden;
  background: #17151A; box-shadow: var(--lift-deck);
  will-change: transform; user-select: none; -webkit-user-select: none;
}
.card--next { transform: translateY(8px) scale(.975); box-shadow: var(--lift); opacity: .96; }
.card--settling { transition: transform var(--fast) var(--ease-out); }
.card--gone { transition: transform var(--base) var(--ease), opacity var(--base) var(--ease); opacity: 0; }

/* Eases back as you read, so scrolling feels like moving into the card
   rather than past it. Transform only — animating anything that triggers
   layout here would jank on a real phone. */
.card-photo {
  position: absolute; inset: 0;
  background: #201C22 center/cover no-repeat;
  transform: scale(calc(1 + var(--depth) * 0.06));
  /* The veil alone could not carry this: darkening it far enough to make
     scrolled copy comfortable left the photograph looking greyed rather
     than dimmed. Taking the image's own brightness down with the scroll
     reads as the light going out of the room instead. */
  filter: brightness(calc(1 - var(--depth) * 0.3));
  transition: transform var(--slow) var(--ease-out), filter var(--slow) var(--ease-out);
}
.card-photo--empty { display: grid; place-items: center; }
.card-photo--empty::after {
  content: ""; width: 42px; height: 42px; border-radius: 50%;
  border: 1px solid rgba(255,255,255,.18); border-top-color: rgba(255,255,255,.6);
  animation: spin 900ms linear infinite;
}
/* No photograph: the same warm ground the onboarding tiles stand on, and
   one quiet line where the picture would have been, above the name rather
   than under it. It is a frame, not an error — the card below it reads and
   swipes exactly as any other. */
.card-photo--failed {
  background-image: linear-gradient(160deg, #3A322B 0%, #241F1B 58%, #17151A 100%);
}
.card-photo-note { display: none; }
.card-photo--failed .card-photo-note {
  display: block; position: absolute; left: var(--s-5); right: var(--s-5); top: 26%;
  text-align: center; font-family: var(--display); font-style: italic; font-size: 1rem;
  line-height: 1.3; color: rgba(255,255,255,.5);
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .card-photo--empty::after { animation: none; }
}

/* The resting gradient carries the name; every stop also rides --depth,
   so by the time the copy is up the photograph has dimmed enough to read
   a paragraph on. */
.card-veil {
  position: absolute; inset: 0; pointer-events: none;
  /* Deepened at depth: at rest the photograph still leads, but once the
     copy has risen over it the veil goes near-opaque so the writing sits
     on something solid instead of on a bright picture. The 22% stop is
     the one that mattered — it was the bright band across the middle of
     a scrolled card, and it now travels furthest (.06 -> .80). */
  background: linear-gradient(to bottom,
    rgba(19,18,16,calc(.42 + var(--depth) * .46)) 0%,
    rgba(19,18,16,calc(.06 + var(--depth) * .74)) 22%,
    rgba(19,18,16,calc(.52 + var(--depth) * .40)) 58%,
    rgba(19,18,16,calc(.88 + var(--depth) * .11)) 100%);
}

.card-scroll {
  position: absolute; inset: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  touch-action: pan-y; overscroll-behavior: contain;
  padding: 0 var(--s-4) calc(var(--s-9) + var(--s-5));
}
.card-scroll::-webkit-scrollbar { width: 0; }
/* A short fade at the top edge. Scrolled copy was being guillotined
   against the card's rounded corner, which reads as broken rather than as
   more-above. The island is a sibling, so it is unaffected. */
.card-scroll {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22px);
}
/* The open photograph before any words. A share of the card, so the crop
   reads the same on any screen. */
.card-lede { height: 54%; min-height: 200px; }

.card-copy, .card-body { color: var(--on-photo); }
.card-kicker {
  font-size: var(--t-micro); letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-photo-dim); margin-bottom: var(--s-2);
}
.card-name {
  font-family: var(--display); font-weight: 500; font-size: 1.72rem;
  line-height: 1.08; letter-spacing: -.01em; margin: 0; text-wrap: balance;
}
.card-facts {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
  margin-top: var(--s-3); font-size: var(--t-meta); color: var(--on-photo-dim);
  font-variant-numeric: tabular-nums;
}
.card-rating { display: inline-flex; align-items: baseline; gap: 5px; }
.card-rating b { font-size: 1.14rem; font-weight: 600; color: var(--on-photo); letter-spacing: -.01em; }
.card-rating i { font-style: normal; font-size: var(--t-meta); color: var(--on-photo-dim); }
.card-star { color: var(--warm-lift); position: relative; top: 1px; }
/* The one blue on the card, kept pale so it sits with the warm facts
   rather than shouting over them. Its name is on the element, not in it. */
.card-access { display: inline-grid; place-items: center; color: #9CBBE6; position: relative; top: 1px; }
.card-body { margin-top: var(--s-6); }
.card-stretch {
  margin: 0 0 var(--s-4); padding: var(--s-3) var(--s-3) var(--s-3) var(--s-4);
  border-left: 2px solid var(--warm-lift); background: rgba(255,255,255,.07);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--t-meta); line-height: 1.45; color: var(--on-photo);
}
.card-block-label {
  font-size: var(--t-micro); letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255,255,255,.55); margin-bottom: var(--s-2);
}
/* A line of the display face in italic, not another uppercase label: it
   introduces the remark the way a guide would, and has no ground of its
   own. */
.card-why-head {
  margin: 0 0 var(--s-2); font-family: var(--display); font-style: italic; font-weight: 400;
  font-size: 1rem; line-height: 1.3; color: var(--on-photo-dim);
}
.card-why-lead { margin: 0; font-size: 1.06rem; line-height: 1.5; color: var(--on-photo); }
.card-why-text { margin: var(--s-3) 0 0; font-size: var(--t-body); line-height: 1.6;
  color: rgba(255,255,255,.78); }

/* Plain lines under a hairline. The green "+" and salmon "-" that marked
   these were the only saturated colour on the card, and they turned a
   friend's suggestion into a pros-and-cons table. The heading says which
   group this is; the glyph added nothing the words did not. */
.card-notes { list-style: none; margin: var(--s-5) 0 0; padding: 0; }
.card-notes li {
  margin-top: var(--s-2); font-size: var(--t-meta); line-height: 1.55;
  color: rgba(255,255,255,.82);
}
.card-aside { margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid rgba(255,255,255,.12); }
.card-aside .card-notes { margin-top: var(--s-2); }

.card-dishes { margin-top: var(--s-6); padding-top: var(--s-4);
  border-top: 1px solid rgba(255,255,255,.14); }
.card-dishes ul { list-style: none; margin: var(--s-2) 0 0; padding: 0; }
.card-dishes li { padding: var(--s-3) 0; border-bottom: 1px solid rgba(255,255,255,.1); }
.card-dishes li:last-child { border-bottom: 0; }
.card-dish-name { font-family: var(--display); font-size: 1.08rem; font-weight: 500;
  color: var(--on-photo); }
.card-dish-quote { display: block; font-size: var(--t-meta); line-height: 1.45;
  color: rgba(255,255,255,.66); font-style: italic; margin-top: 3px; }

.card-foot { display: flex; gap: var(--s-2); margin-top: var(--s-6); }
.card-foot-btn {
  flex: 1; min-height: 46px; display: inline-flex; align-items: center;
  justify-content: center; gap: var(--s-2);
  border: 1px solid rgba(255,255,255,.2); border-radius: var(--r-sm);
  background: rgba(255,255,255,.08); color: var(--on-photo);
  font-size: var(--t-meta); text-decoration: none; backdrop-filter: blur(10px);
}
.card-foot-btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-foot-btn:active { background: rgba(255,255,255,.16); }

/* The island floats over the words, so the words need somewhere to go.
   Without this the copy simply collided with the controls — half a
   sentence legible, the other half behind a button. The card's own veil
   could not do it: that sits UNDER the scrolling text, not over it. */
.card-fade {
  position: absolute; left: 0; right: 0; bottom: 0; height: 132px; z-index: 3;
  pointer-events: none;
  background: linear-gradient(to top, rgba(19,18,16,.94) 26%, rgba(19,18,16,0));
}

/* The floating island. Over the words, not beside them, so the verdict is
   always to hand however far you have read. */
.card-acts {
  position: absolute; left: 50%; bottom: var(--s-4); transform: translateX(-50%);
  z-index: 4; display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2); border-radius: var(--r-full);
  /* Opaque enough to stand on its own. backdrop-filter is an enhancement,
     not the plan — it is unsupported or disabled often enough that an
     island relying on it becomes a smear of text you cannot read. */
  background: rgba(22,19,24,.86); border: 1px solid rgba(255,255,255,.16);
  backdrop-filter: blur(20px) saturate(140%);
  box-shadow: 0 10px 30px -12px rgba(0,0,0,.6);
}
/* Pass and shortlist are outlines; only the primary is filled. The red
   and green discs they replace were the loudest tell that this was a
   dating-app template — and the meaning was never in the colour anyway,
   it was in the cross and the bookmark. */
.card-acts .act { width: 46px; height: 46px; background: transparent;
  border: 1px solid rgba(255,255,255,.26); color: var(--on-photo-dim); }
.card-acts .act:hover { color: var(--on-photo); border-color: rgba(255,255,255,.46); }
.card-acts .act--book { width: 52px; height: 52px; border: 0;
  background: var(--on-photo); color: #1A1917; }
.card-acts .act:active { transform: scale(.92); }

.card-verdict {
  position: absolute; top: var(--s-5); left: 0; right: 0; text-align: center; z-index: 3;
  font-size: var(--t-micro); letter-spacing: .16em; text-transform: uppercase;
  color: var(--on-photo); opacity: 0; pointer-events: none;
}
.card-verdict span { padding: var(--s-2) var(--s-4); border: 1px solid var(--on-photo-line);
  border-radius: var(--r-full); backdrop-filter: blur(6px); }

.act { display: grid; place-items: center; border-radius: var(--r-full);
  transition: transform var(--fast) var(--ease), background var(--fast) var(--ease); }
/* ---------- onboarding ----------
   One decision per screen. Different question types get different
   components on purpose — one component reused five times is how a
   sequence turns back into a form. */
.ask { padding: 0 var(--s-5) var(--s-5); }
.ask-bar { display: flex; gap: var(--s-1); padding: var(--s-3) 0 var(--s-6); }
.ask-tick { height: 2px; flex: 1; background: var(--line); border-radius: var(--r-full);
  transition: background var(--base) var(--ease); }
.ask-tick--done { background: var(--ink); }
.ask-q {
  font-family: var(--display); font-weight: 500; font-size: var(--t-ask);
  line-height: 1.12; letter-spacing: -.01em; margin: 0; text-wrap: balance;
}
.ask-note { color: var(--muted); font-size: var(--t-meta); margin: var(--s-3) 0 var(--s-6); }

.opts { display: flex; flex-direction: column; gap: var(--s-2); }
/* Photographic options for mood and atmosphere: the answer is a feeling,
   and a photograph says it faster than a word does. */
.opt-photo {
  position: relative; height: 104px; border-radius: var(--r-sm); overflow: hidden;
  background: linear-gradient(135deg, #3A322B 0%, #241F1B 100%) center/cover no-repeat;
  text-align: left; transition: transform var(--fast) var(--ease);
}
.opt-photo:active { transform: scale(.985); }
/* Two layers, not one. The horizontal pass carries the type on the left;
   the bottom pass catches the second line. A single 105deg wash left the
   note sitting on whatever the photograph happened to be doing there. */
.opt-photo::before {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(19,18,16,.80) 6%, rgba(19,18,16,.34) 52%, rgba(19,18,16,.12) 92%),
    linear-gradient(to top, rgba(19,18,16,.45) 0%, rgba(19,18,16,0) 62%);
}
.opt-photo-copy {
  position: absolute; inset: auto auto var(--s-4) var(--s-4);
  right: var(--s-5); color: var(--on-photo);
}
/* Block, not inline: as spans these ran together on one line — "Italian
   Pasta, wood-fire, wine" — which is the whole hierarchy collapsing. */
.opt-photo-name { display: block; font-family: var(--display); font-size: 1.34rem;
  font-weight: 500; line-height: 1.12; }
.opt-photo-note { display: block; font-size: var(--t-meta); color: var(--on-photo-dim);
  margin-top: 3px; }
/* A dark 2px ring on a dark photograph was nearly invisible, and the
   extras underneath depend on which tile is chosen — so the choice has
   to be unmistakable. A light ring that reads on any image, plus the
   same filled check the budget rows use: shape as well as colour. */
.opt-photo[aria-checked="true"] { box-shadow: inset 0 0 0 2px var(--on-photo), 0 0 0 2px var(--ink); }
.opt-photo-mark {
  position: absolute; top: var(--s-3); right: var(--s-3); z-index: 2;
  display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%;
  background: var(--on-photo); color: #1A1917;
  opacity: 0; transform: scale(.8);
  transition: opacity var(--fast) var(--ease), transform var(--fast) var(--ease-out);
}
.opt-photo[aria-checked="true"] .opt-photo-mark { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) { .opt-photo-mark { transition: none; } }

/* Ten cuisines will not fit as full-width photo rows, so they become a
   two-up grid of smaller tiles: enough photograph to recognise a cuisine
   at a glance, small enough that the whole set is one screen. */
.opt-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
.opt-tile {
  position: relative; height: 94px; border-radius: var(--r-sm); overflow: hidden;
  /* A warm layered ground under the photograph, not flat grey: if an
     image never arrives the tile still reads as a designed surface with a
     legible label rather than as a hole. */
  background: linear-gradient(135deg, #3A322B 0%, #241F1B 100%) center/cover no-repeat;
  text-align: left; transition: transform var(--fast) var(--ease);
}
.opt-tile:active { transform: scale(.98); }
/* Deeper and taller than it looks like it needs to be. A cuisine tile is
   a photograph of food, which means the bottom-left corner can be a white
   plate or a lime — the label has to survive the brightest case, not the
   average one. */
.opt-tile::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(19,18,16,.92) 0%,
    rgba(19,18,16,.62) 34%, rgba(19,18,16,.10) 82%);
}
.opt-tile-name {
  position: absolute; left: var(--s-3); bottom: var(--s-3); right: var(--s-3);
  font-family: var(--display); font-size: 1.05rem; font-weight: 500; color: var(--on-photo);
}
/* Only one tile carries a hint, so it lifts the NAME rather than sitting
   below it — otherwise this one tile's label would sit a line higher than
   the nine beside it and the grid would read as misaligned. */
.opt-tile:has(.opt-tile-hint) .opt-tile-name { bottom: calc(var(--s-3) + 0.95rem); }
.opt-tile-hint {
  position: absolute; left: var(--s-3); bottom: var(--s-3); right: var(--s-3);
  font-size: 0.68rem; letter-spacing: .02em; color: var(--on-photo);
  opacity: .78; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.opt-tile[aria-checked="true"] { box-shadow: inset 0 0 0 2px var(--on-photo), 0 0 0 2px var(--ink); }
.opt-custom { margin-top: var(--s-2); width: 100%; }

/* The manual channel: a plain field, styled like the list rows so it reads
   as one more option rather than as an escape hatch. */
.field {
  width: 100%; min-height: 52px; margin-top: var(--s-2);
  padding: var(--s-3) var(--s-4);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
.field:focus { outline: none; border-color: var(--ink); }
.field--note { min-height: 108px; line-height: 1.5; resize: none; margin-top: 0; }
/* Small independent toggles: dietary needs, a mood's extras, things to
   avoid. Pressed is a border AND an ink change, never colour alone. */
.chip { min-height: 44px; border: 1px solid var(--line); border-radius: var(--r-full);
  padding: var(--s-2) var(--s-3); background: var(--surface); color: var(--muted); font: inherit;
  font-size: var(--t-meta); cursor: pointer; }
.chip[aria-pressed="true"] { color: var(--ink); border-color: var(--ink); }
.chip:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
/* A budget is a number, and a number wants a list, not a photograph. */
.opt-row {
  display: flex; align-items: center; justify-content: space-between;
  min-height: 56px; padding: 0 var(--s-4);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); text-align: left;
  transition: border-color var(--fast) var(--ease);
}
.opt-row[aria-checked="true"] { border-color: var(--ink); }
.opt-row-mark { width: 20px; height: 20px; border-radius: var(--r-full);
  border: 1px solid var(--line); display: grid; place-items: center; color: transparent; }
.opt-row[aria-checked="true"] .opt-row-mark { background: var(--ink); border-color: var(--ink);
  color: var(--surface); }
.opt-row[aria-pressed="true"] { border-color: var(--ink); }
.opt-row[aria-pressed="true"] .opt-row-mark { background: var(--ink); border-color: var(--ink);
  color: var(--surface); }
.ask-group { border: 0; padding: 0; margin: 0 0 var(--s-6); min-width: 0; }
.ask-group legend { padding: 0; margin-bottom: var(--s-3); font-size: var(--t-meta);
  font-weight: 500; color: var(--muted); }
.chip-grid { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.clear-choice { display: inline-flex; width: auto; min-height: 38px; align-items: center;
  white-space: nowrap; padding: var(--s-2) var(--s-3); margin-top: var(--s-3); }

.ask-foot { margin-top: auto; padding-top: var(--s-6); display: flex; gap: var(--s-3); align-items: center; }
.btn {
  flex: 1; min-height: 52px; border-radius: var(--r-full);
  background: var(--primary); color: var(--on-primary);
  font-size: var(--t-body); font-weight: 500; letter-spacing: .01em;
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
  transition: transform var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.btn:active { transform: scale(.985); }
.btn[disabled] { opacity: .35; pointer-events: none; }
.btn--quiet { background: none; color: var(--muted); border: 1px solid var(--line); flex: 0 0 auto;
  width: 52px; }

/* ---------- shortlist ----------
   A list, not a wall of cards. Thumbnail, name, two lines of fact, a rule
   between. It should read as a collection someone is building. */
.sheet { padding: 0 var(--s-5) var(--s-8); overflow-y: auto; }
.sheet-title { font-family: var(--display); font-weight: 500; font-size: var(--t-title);
  margin: var(--s-4) 0 var(--s-5); }
.saved { list-style: none; margin: 0; padding: 0; }
.saved li { display: flex; gap: var(--s-4); padding: var(--s-4) 0; border-bottom: 1px solid var(--line);
  align-items: center; }
.saved li:last-child { border-bottom: 0; }
.saved-thumb { width: 68px; height: 68px; border-radius: var(--r-sm); flex: 0 0 auto;
  background: var(--surface-sunk) center/cover no-repeat; }
/* min-width:0 or the flex child refuses to shrink and the name pushes
   straight through the action on its right. */
.saved-body { min-width: 0; flex: 1; }
/* One line each. A saved place is scanned, not read — a name wrapping to
   two lines and a neighbourhood wrapping to three turned a collection
   into a paragraph. */
.saved-name { font-family: var(--display); font-size: var(--t-name); font-weight: 500;
  line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.saved-meta { font-size: var(--t-meta); color: var(--muted); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.saved-rating { font-variant-numeric: tabular-nums; }
.saved-rating svg { vertical-align: -1px; margin-right: 2px; color: var(--warm); }
.saved-act { margin-left: auto; align-self: center; color: var(--muted); min-height: 44px;
  min-width: 44px; display: grid; place-items: center; }

/* ---------- empty + status ---------- */
/* Flex, not grid, and a real gap from the masthead: place-content centred
   the tracks but the heading still started hard against the header, and
   the button inherited .ask-foot's row layout without its flex context,
   so it collapsed to content width and sat at the left edge. */
.empty {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  padding: var(--s-8) var(--s-6) var(--s-9); gap: var(--s-4);
}
.empty .empty-line { max-width: 18ch; }
.empty .ask-foot { margin-top: var(--s-5); padding: 0; width: 100%; max-width: 260px;
  display: flex; }
.empty-line { font-family: var(--display); font-size: 1.5rem; font-weight: 500; line-height: 1.25; }
.empty-note { color: var(--muted); font-size: var(--t-meta); max-width: 30ch; margin: 0 auto; }

/* --- boot ---------------------------------------------------------------
   Shown once, for as long as the fonts and the first question's
   photographs take. Wordmark, a hairline that fills, one line of copy —
   anything more would be a splash screen, which is a thing to sit through
   rather than a thing to wait behind. */
/* Flex, not grid: place-content sized the column to the widest child (the
   tagline) and left the wordmark sitting at its left edge, so the three
   elements disagreed about where the centre was. */
.boot { flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--s-4); padding: var(--s-6); text-align: center; }
.boot-word { font-family: var(--display); font-weight: 500; font-size: 2.6rem;
  letter-spacing: .01em; }
.boot-line { width: 108px; height: 1px; background: var(--line); overflow: hidden; }
.boot-line i { display: block; height: 100%; width: 40%; background: var(--ink);
  animation: boot-sweep 1.15s var(--ease) infinite; }
@keyframes boot-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}
.boot-note { font-size: var(--t-meta); color: var(--faint); margin: 0; text-align: center; }

/* No drifting blurred orb behind the type. It was decoration that moved
   while the words underneath were being read, and a soft-focus gradient
   blob is the house style of every AI product this one is trying not to
   look like. */
.working { flex: 1; display: grid; place-content: center; text-align: center; gap: var(--s-4);
  justify-items: center; padding: 0 var(--s-4); }
.working-word { font-family: var(--display); font-size: var(--t-ask); font-weight: 500;
  line-height: 1.12; letter-spacing: -.01em; }
/* The request as it was sent — area and choices — so the wait reads as
   "for this", not as a generic spinner. */
.working-note { color: var(--muted); font-size: var(--t-meta); margin: 0 auto;
  max-width: 32ch; line-height: 1.5; text-wrap: balance; }
.working-late { color: var(--muted); font-size: var(--t-meta); margin: 0; min-height: 1.5em; }

/* Same 108px rule as the boot screen, deliberately: this is the second
   time you see it in a session and it should read as the same object.
   The difference is that this one fills rather than sweeps, because here
   there IS a duration worth showing. */
/* #stage is display:flex column at a higher specificity, so .working's
   own display:grid never wins and justify-items does nothing here. Centre
   the rule on itself instead — correct under flex and grid alike. */
.working-progress { position: relative; align-self: center; margin-inline: auto;
  margin-top: var(--s-2); }
.working-rule { width: 108px; height: 1px; background: var(--line); overflow: hidden;
  margin-inline: auto; align-self: center; }
/* Hung off the rule's right end rather than sharing a row with it, so the
   rule stays on the centre line the motif and copy above it use. */
.working-elapsed { position: absolute; left: calc(100% + var(--s-2)); top: 50%;
  transform: translateY(-50%); color: var(--faint); font-size: var(--t-meta);
  font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; }
.working-rule i { display: block; height: 100%; width: 100%; background: var(--ink);
  transform: scaleX(0); transform-origin: left center;
  transition: transform 0ms linear; }

.flash, .undo-swipe { position: fixed; z-index: 30; left: 50%; bottom: calc(68px + env(safe-area-inset-bottom));
  transform: translateX(-50%); max-width: calc(100vw - 32px); border-radius: var(--r-full);
  padding: var(--s-3) var(--s-4); background: var(--ink); color: var(--surface);
  box-shadow: var(--lift); font-size: var(--t-meta); white-space: nowrap; }
.undo-swipe { display: flex; align-items: center; gap: var(--s-4); }
/* On the deck it clears the action island. At 68px it sat across the lower
   half of Pass, Maps and Shortlist for 4.5s after every swipe, so a thumb
   pressing low on a button pressed the toast instead: nav 52 + deck gap 16
   + island inset 16 + island 70, then 12 of air. */
body.on-deck .undo-swipe { bottom: calc(166px + env(safe-area-inset-bottom)); }
/* A flash has nothing to press, so it never takes a press meant for a card. */
.flash { pointer-events: none; }
.undo-swipe span { overflow: hidden; text-overflow: ellipsis; }
.undo-swipe button { color: var(--warm); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .working-rule i { transition: none; transform: scaleX(.35); }
}

/* ---------- nav ----------
   Four destinations, icons only. A label under every icon is four words
   nobody reads after the first session. */
.nav {
  display: flex; border-top: 1px solid var(--line); background: var(--bg);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  position: sticky; bottom: 0;
}
.nav button { flex: 1; min-height: 52px; display: grid; place-items: center; color: var(--faint);
  transition: color var(--fast) var(--ease); }
.nav button[aria-current="page"] { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---------- the end-of-run form ----------
   Reached only after the whole deck has been seen. Nothing is
   required, and the copy says so — this collects testers, and the fastest
   way to collect none is to make a stranger feel cornered on the way out. */
/* The body scrolls under a sticky nav (#stage grows to fit rather than
   scrolling itself), so the last control needs room to clear the bar. */
.fb { padding: var(--s-5) var(--s-5) calc(var(--s-6) + 16px);
  max-width: 40rem; margin-inline: auto; }
.fb-kicker {
  margin: 0 0 var(--s-2); font-size: var(--t-micro); letter-spacing: .16em;
  text-transform: uppercase; color: var(--faint);
}
.fb-title { font-family: var(--display); font-weight: 500; font-size: var(--t-ask); margin: 0; }
.fb-note { color: var(--faint); font-size: var(--t-meta); margin: var(--s-2) 0 var(--s-6); }
.fb-label {
  display: block; margin: var(--s-5) 0 var(--s-2);
  font-size: var(--t-meta); font-weight: 500; color: var(--ink);
}
.fb-area { resize: vertical; min-height: 4.5rem; line-height: 1.5; }

.fb-scale { display: flex; flex-direction: column; gap: var(--s-2); }
.fb-scale input[type="range"] { width: 100%; accent-color: var(--ink); }
.fb-scale-row {
  display: flex; align-items: center; justify-content: space-between;
  font-size: var(--t-micro); color: var(--faint);
}
.fb-nps-out {
  font-family: var(--display); font-size: 1.3rem; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.fb-said { margin: var(--s-3) 0 0; font-size: var(--t-meta); color: var(--faint); }
.fb-ends {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-2) var(--s-5);
  margin-top: var(--s-7);
}
.fb-restart {
  color: var(--faint); font-size: var(--t-meta);
  text-decoration: underline; text-underline-offset: 3px;
}

/* ---------- location suggestions ----------
   A bundled list filtered in the browser, not Google Autocomplete, which
   bills per typing session. Sits directly under the field it belongs to. */
.suggest {
  list-style: none; margin: var(--s-2) 0 0; padding: var(--s-1);
  border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  box-shadow: var(--lift); max-height: 15rem; overflow-y: auto;
}
.suggest li {
  padding: var(--s-3); border-radius: 8px; cursor: pointer;
  font-size: var(--t-meta); color: var(--ink);
}
.suggest li[aria-selected="true"], .suggest li:hover { background: var(--line); }

/* ---------- the adjust sheet ----------
 * Behind the tuning control, because five refinements that only re-sort
 * cards already in hand do not deserve a permanent band across the hero
 * screen. Rows rather than pills: each carries state, and a row can show
 * a mark without leaning on colour to do it. */
.sheet-over { position: fixed; inset: 0; z-index: 40; display: flex; align-items: flex-end;
  justify-content: center; }
.sheet-scrim { position: absolute; inset: 0; background: rgba(19,18,16,.44);
  opacity: 0; transition: opacity var(--base) var(--ease); }
.sheet-over--in .sheet-scrim { opacity: 1; }
.sheet-panel { position: relative; width: 100%; max-width: 460px;
  background: var(--bg); border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding: var(--s-3) var(--s-5) calc(var(--s-5) + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%); transition: transform var(--base) var(--ease-out); }
.sheet-over--in .sheet-panel { transform: translateY(0); }
.sheet-grip { width: 36px; height: 4px; border-radius: var(--r-full); background: var(--line);
  margin: 0 auto var(--s-4); }
.sheet-head { font-family: var(--display); font-weight: 500; font-size: 1.5rem;
  margin: 0 0 var(--s-1); }
.sheet-sub { color: var(--muted); font-size: var(--t-meta); margin: 0 0 var(--s-4); }
.sheet-rows { display: flex; flex-direction: column; }
.sheet-row { display: flex; align-items: center; justify-content: space-between;
  min-height: 52px; padding: var(--s-2) 0; border-bottom: 1px solid var(--line);
  color: var(--muted); text-align: left; }
.sheet-row:last-child { border-bottom: 0; }
.sheet-row[aria-pressed="true"] { color: var(--ink); font-weight: 500; }
.sheet-mark { display: grid; place-items: center; width: 22px; height: 22px;
  color: var(--ink); opacity: 0; transition: opacity var(--fast) var(--ease); }
.sheet-row[aria-pressed="true"] .sheet-mark { opacity: 1; }
.sheet-foot { display: flex; gap: var(--s-3); margin-top: var(--s-5); }
.sheet-foot .btn { flex: 1; }
.ask-sub-optional { color: var(--muted); font: inherit; font-size: var(--t-micro); font-weight: 400; }

@media (prefers-reduced-motion: reduce) {
  .sheet-scrim, .sheet-panel { transition: none; }
}

/* The swipe-learning disclosure. A plain note, not a control: learning is
   always on, so there is nothing here to press. */
.fb-consent {
  display: grid; gap: var(--s-1); width: 100%; text-align: left;
  margin: var(--s-6) 0 0; padding-top: var(--s-4);
  border-top: 1px solid var(--line); color: var(--muted);
}
.fb-consent span { color: var(--ink); font-size: var(--t-meta); }
.fb-consent small { font-size: var(--t-micro); line-height: 1.5; }

/* ---------- the distance slider ---------- */
.ask-group--range { margin-top: var(--s-6); }
.range-value {
  display: block; font-family: var(--display); font-size: 1.5rem; font-weight: 500;
  margin-bottom: var(--s-3);
}
.range {
  -webkit-appearance: none; appearance: none; width: 100%; height: 28px;
  background: transparent; cursor: pointer;
}
.range::-webkit-slider-runnable-track { height: 2px; background: var(--line); border-radius: 2px; }
.range::-moz-range-track { height: 2px; background: var(--line); border-radius: 2px; }
.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 26px; height: 26px; margin-top: -12px;
  border-radius: 50%; background: var(--primary); border: 0;
}
.range::-moz-range-thumb { width: 26px; height: 26px; border: 0; border-radius: 50%;
  background: var(--primary); }
.range:focus-visible { outline: none; }
.range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--warm); }
.range-ends {
  display: flex; justify-content: space-between; margin-top: var(--s-2);
  color: var(--faint); font-size: var(--t-micro);
}

/* ---------- sub-sections within a question ----------
 * The mood screen stacked a legend straight onto the last photo tile
 * with no gap, then left loose, uneven gaps further down — the rhythm
 * was set by whatever each block happened to carry as its own margin.
 * One rule now: every sub-section after the main answer starts one
 * --s-6 below it, its label sits --s-3 above its controls, and nothing
 * else adds vertical space. */
.ask-sub { margin-top: var(--s-6); }
.ask-sub + .ask-sub { margin-top: var(--s-6); }
.ask-sub-label {
  margin: 0 0 var(--s-3); font-family: var(--ui); font-size: var(--t-meta);
  font-weight: 500; color: var(--muted);
}
.ask-sub-hint { margin: var(--s-2) 0 0; color: var(--faint); font-size: var(--t-micro); }
.ask-sub-empty { margin: 0; color: var(--faint); font-size: var(--t-meta); }

/* ---------- segmented control ----------
 * Seating (one of three) and weekday/weekend (any of two). One line,
 * equal widths, a hairline between segments; the pressed state is a fill
 * AND a weight change, so it never rests on colour alone. */
.seg { display: flex; border: 1px solid var(--line); border-radius: var(--r-full);
  padding: 3px; gap: 3px; background: var(--surface); }
.seg-opt { flex: 1; min-height: 44px; border-radius: var(--r-full); color: var(--muted);
  font-size: var(--t-meta); transition: background var(--fast) var(--ease), color var(--fast) var(--ease); }
.seg-opt:hover { color: var(--ink); }
.seg-opt[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); font-weight: 600; }

.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; }

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

/* ---------- the start choice ----------
 * Two rows under a hairline, like the adjust sheet — not three bordered
 * cards. The serif title does the work a card border was doing. */
.start-q { margin-top: var(--s-8); }
.start-rows { margin-top: var(--s-6); border-top: 1px solid var(--line); }
.start-row {
  display: grid; grid-template-columns: 1fr auto; column-gap: var(--s-4); row-gap: var(--s-1);
  width: 100%; padding: var(--s-5) 0; text-align: left;
  border-bottom: 1px solid var(--line);
  transition: opacity var(--fast) var(--ease);
}
.start-row:active { opacity: .6; }
.start-name { grid-column: 1; font-family: var(--display); font-weight: 500;
  font-size: 1.45rem; line-height: 1.15; color: var(--ink); }
.start-copy { grid-column: 1; color: var(--muted); font-size: var(--t-meta); line-height: 1.5; }
.start-go { grid-column: 2; grid-row: 1 / span 2; align-self: center; color: var(--faint);
  transition: transform var(--fast) var(--ease), color var(--fast) var(--ease); }
.start-row:hover .start-go { color: var(--ink); transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) { .start-row, .start-go { transition: none; } }

/* =====================================================================
   UI refinements, 25 September 2026. One block so the change is legible
   as a whole: onboarding photographs as lazy <img>, a dietary section you
   can find, a location field that looks like where to start, interaction
   states, the finding sequence, Compare, and the feedback sign-off.
   ===================================================================== */

/* ---------- onboarding photographs ----------
   Real <img loading="lazy"> under the same gradient, so tiles below the
   fold wait for a scroll instead of downloading with the screen. The warm
   gradient ground stays underneath for the moment before one arrives. */
.opt-tile, .opt-photo { isolation: isolate; }
.opt-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  z-index: -1; pointer-events: none; user-select: none;
  transition: transform var(--base) var(--ease-out); }
.opt-tile::before, .opt-photo::before { z-index: 0; }
.opt-tile-name, .opt-tile-hint, .opt-photo-copy { z-index: 1; }

/* ---------- interaction states ----------
   Quiet and physical: hover only where there is a real pointer, a press
   that gives a little, and a focus ring that is always visible. Selected
   is never colour alone — a border, a weight, a tick. */
@media (hover: hover) and (pointer: fine) {
  .opt-tile:hover .opt-img, .opt-photo:hover .opt-img { transform: scale(1.03); }
  .chip:hover, .opt-row:hover, .idea:hover { border-color: var(--ink-soft); color: var(--ink); }
  .btn:not([disabled]):hover { opacity: .9; }
  .btn--quiet:hover { color: var(--ink); border-color: var(--ink-soft); }
  .card-foot-btn:hover { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.34); }
  .sheet-row:hover { color: var(--ink); }
  .saved-act:hover, .text-act:hover, .mast-place:hover, .mast-tune:hover { color: var(--ink); }
  .loc-live:hover { border-color: var(--ink-soft); }
  .fb-restart:hover, .fb-anon:hover { color: var(--ink); }
}
.chip, .idea, .loc-live, .card-foot-btn, .text-act {
  transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease),
    background var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.chip:active, .opt-row:active, .seg-opt:active, .idea:active, .loc-live:active,
.card-foot-btn:active, .sheet-row:active, .text-act:active { transform: scale(.97); }
.opt-tile:focus-visible, .opt-photo:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.seg-opt:focus-visible, .opt-row:focus-visible, .btn:focus-visible { outline: 2px solid var(--warm);
  outline-offset: 3px; }
.card-foot-btn:focus-visible, .card-acts .act:focus-visible { outline: 2px solid var(--on-photo);
  outline-offset: 2px; }

/* A tick that appears inside a pressed toggle, so "on" has a shape. */
.chip { display: inline-flex; align-items: center; gap: 6px; }
.chip-tick { display: none; place-items: center; width: 14px; height: 14px; }
.chip[aria-pressed="true"] .chip-tick { display: inline-grid; }
.chip[aria-pressed="true"] { background: var(--surface-sunk); font-weight: 500; }

/* ---------- Food: dietary needs ----------
   Its own section under a hairline, with a real heading and one line of
   instruction, and bigger square-cornered toggles with a box that fills —
   a checklist, which is what it is, rather than a trailing row of pills. */
.ask-sub--diet { margin-top: var(--s-7); padding-top: var(--s-5); border-top: 1px solid var(--line); }
.ask-sub-title { margin: 0; font-family: var(--display); font-weight: 500; font-size: 1.35rem;
  line-height: 1.15; color: var(--ink); }
.ask-sub--diet .ask-sub-hint { margin: var(--s-1) 0 var(--s-4); font-size: var(--t-meta); color: var(--muted); }
.chip-grid--diet { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
.chip-grid--diet .chip { justify-content: flex-start; min-height: 52px; border-radius: var(--r-sm);
  padding: 0 var(--s-4); gap: var(--s-3); color: var(--ink); font-size: var(--t-body); }
.chip-grid--diet .chip-tick { display: inline-grid; width: 20px; height: 20px; flex: 0 0 auto;
  border: 1.5px solid var(--faint); border-radius: 5px; color: transparent;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease); }
.chip-grid--diet .chip[aria-pressed="true"] { border-color: var(--ink); background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--ink); }
.chip-grid--diet .chip[aria-pressed="true"] .chip-tick { background: var(--ink); border-color: var(--ink);
  color: var(--surface); }

/* ---------- location ----------
   The field is the first action, so it carries the page's one strong
   border and the magnifier that says "search". Live location is the
   quieter alternative underneath, after an "or". */
.loc-search { position: relative; }
.loc-icon { position: absolute; left: 0; top: var(--s-2); bottom: 0; width: 48px;
  display: grid; place-items: center; color: var(--ink); cursor: text; z-index: 1; }
.loc-field { padding-left: 46px; min-height: 56px; font-size: 1.02rem;
  border: 1.5px solid var(--ink-soft);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease); }
.loc-field:focus { border-color: var(--ink); box-shadow: 0 0 0 4px rgba(176,125,46,.18); }
.loc-or { display: flex; align-items: center; gap: var(--s-3); margin: var(--s-5) 0 var(--s-4);
  color: var(--faint); font-size: var(--t-micro); letter-spacing: .12em; text-transform: uppercase; }
.loc-or::before, .loc-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.loc-live { display: flex; align-items: center; gap: var(--s-3); width: 100%; min-height: 60px;
  padding: var(--s-3) var(--s-4); text-align: left; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface); }
.loc-live > svg { flex: 0 0 auto; color: var(--ink-soft); }
.loc-live-copy { display: grid; gap: 2px; }
.loc-live-name { font-size: var(--t-body); font-weight: 500; }
.loc-live-note { font-size: var(--t-micro); color: var(--muted); }
.loc-live:focus-visible { outline: 2px solid var(--warm); outline-offset: 3px; }
.loc-note { margin: var(--s-3) 0 0; min-height: 1.4em; }
.loc-note:empty { margin: 0; min-height: 0; }

/* ---------- Anything else: ideas ----------
   Not chips: plain text buttons, because they carry no state and are
   never an answer. Tapping one shows an example sentence. */
.ideas { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); margin: 0 0 var(--s-4); }
.ideas-label { grid-column: 1 / -1; font-size: var(--t-meta); font-weight: 500; color: var(--muted); }
.idea { min-height: 44px; padding: 0 var(--s-3); border: 1px dashed var(--line); border-radius: var(--r-sm);
  color: var(--ink-soft); font-size: var(--t-meta); background: transparent; text-align: left; }
.idea[data-on] { border-style: solid; border-color: var(--ink); color: var(--ink); }
.note-example { margin: 0 0 var(--s-3); font-family: var(--display); font-style: italic;
  font-size: 1.02rem; line-height: 1.45; color: var(--ink-soft); }

/* ---------- the finding sequence ----------
   A search radius around a point, places surfacing and fading inside it.
   Transform and opacity only, one slow loop, nothing that counts. Still
   under reduced motion and under Save-Data. */
.working-word { font-size: 1.6rem; }
.finding { display: block; width: 120px; height: 120px; margin: 0 auto var(--s-2); align-self: center;
  overflow: visible; }
.finding circle { transform-box: fill-box; transform-origin: center; }
.finding-edge { fill: none; stroke: var(--line); stroke-width: 1; }
.finding-ring { fill: none; stroke: var(--ink-soft); stroke-width: .8; opacity: 0;
  animation: finding-ring 3.6s var(--ease-out) infinite; }
.finding-ring--late { animation-delay: 1.8s; }
.finding-place { fill: var(--ink-soft); opacity: 0;
  animation: finding-place 5.5s var(--ease) infinite; animation-delay: var(--d, 0s); }
.finding-place--warm { fill: var(--warm); }
.finding-here { fill: var(--ink); }
@keyframes finding-ring {
  0%   { transform: scale(.12); opacity: .55; }
  100% { transform: scale(1);   opacity: 0; }
}
@keyframes finding-place {
  0%, 100% { opacity: 0; transform: scale(.4); }
  18%, 55% { opacity: .85; transform: scale(1); }
  75%      { opacity: 0; transform: scale(1); }
}
/* Slower when late: the same motion, calmer, not a new alarm. */
.finding--late .finding-ring { animation-duration: 5.4s; }
.finding--still .finding-ring, .finding--still .finding-place { animation: none; }
.finding--still .finding-place { opacity: .6; }
@media (prefers-reduced-motion: reduce) {
  .finding-ring, .finding-place { animation: none !important; }
  .finding-place { opacity: .6; }
}

/* ---------- compare ---------- */
.sheet-over--compare .sheet-panel { max-height: 88dvh; display: flex; flex-direction: column; }
.cmp-panel .sheet-head:focus { outline: none; }
.cmp-scroll { overflow: auto; min-height: 0; margin: 0 calc(var(--s-5) * -1); padding: 0 var(--s-5);
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.cmp-scroll:focus-visible { outline: 2px solid var(--warm); outline-offset: -2px; }
.cmp { border-collapse: collapse; width: max(100%, calc(96px + var(--cols) * 128px));
  table-layout: fixed; font-size: var(--t-meta); }
.cmp th, .cmp td { vertical-align: top; text-align: left; }
.cmp thead th { padding: 0 var(--s-3) var(--s-3) 0; font-weight: 400; }
.cmp-corner { width: 96px; }
.cmp tbody th { position: sticky; left: 0; background: var(--bg); z-index: 1;
  padding: var(--s-3) var(--s-3) var(--s-3) 0; font-weight: 500; color: var(--muted);
  font-size: var(--t-micro); letter-spacing: .04em; }
.cmp tbody td { padding: var(--s-3) var(--s-3) var(--s-3) 0; color: var(--ink); line-height: 1.45; }
.cmp tbody tr { border-top: 1px solid var(--line); }
.cmp-cap { text-transform: capitalize; }
.cmp-thumb { display: block; width: 100%; aspect-ratio: 3 / 2; border-radius: var(--r-sm);
  background: var(--surface-sunk) center/cover no-repeat; margin-bottom: var(--s-2); }
.cmp-kicker { display: block; font-size: var(--t-micro); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 2px; }
.cmp-name { display: block; font-family: var(--display); font-size: 1.08rem; font-weight: 500;
  line-height: 1.2; color: var(--ink); }
.cmp-best { font-weight: 600; }
.cmp-sub { color: var(--muted); }
.cmp-none { color: var(--faint); }
.cmp svg { color: var(--warm); vertical-align: -1px; }
.cmp-long { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }

.sheet-title-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.text-act { display: inline-flex; align-items: center; gap: var(--s-2); min-height: 44px;
  padding: 0 var(--s-1); color: var(--ink-soft); font-size: var(--t-meta); font-weight: 500; }
.saved-hint { margin: var(--s-4) 0 0; font-size: var(--t-meta); color: var(--muted); }
.card-foot-btn { padding: 0 var(--s-2); min-width: 0; }

/* ---------- feedback sign-off ---------- */
.fb-who { border: 0; margin: var(--s-7) 0 0; padding: var(--s-5) 0 0; min-width: 0;
  border-top: 1px solid var(--line); }
/* Floated so the legend sits inside the section like a heading, instead of
   being drawn into the fieldset's top border. */
.fb-who-title { float: left; width: 100%; padding: 0; font-family: var(--display); font-weight: 500;
  font-size: 1.35rem; line-height: 1.15; color: var(--ink); }
.fb-who-note { clear: both; margin: 0; padding-top: var(--s-1); font-size: var(--t-meta); color: var(--muted); line-height: 1.5; }
.fb-who .fb-label:first-of-type { margin-top: var(--s-4); }
.fb-note { color: var(--ink-soft); line-height: 1.55; }

/* The part worth having, set apart on a raised surface and flagged, so a
   tester who does only one thing does this. */
.fb-core { padding: var(--s-4) var(--s-4) var(--s-5); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-photo); box-shadow: var(--lift); }
.fb-core-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.fb-core-title { margin: 0; font-family: var(--display); font-weight: 500; font-size: 1.35rem;
  line-height: 1.15; color: var(--ink); }
/* Ink on a sunk ground with a warm dot: the accent marks it, the ink
   carries the words, because the warm tone is too light for small text. */
.fb-flag { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 3px var(--s-2); border-radius: var(--r-full); background: var(--surface-sunk);
  font-size: var(--t-micro); font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); }
.fb-flag::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-full);
  background: var(--warm); }
.fb-core .fb-label:first-of-type { margin-top: var(--s-4); }
.fb-nps-out--unset { font-family: var(--ui); font-size: var(--t-micro); color: var(--muted); }

/* One filled button that plainly sends, and a text-weight way to send
   the same answers without the contact fields. */
.fb-actions { display: flex; flex-direction: column; align-items: stretch; gap: var(--s-2);
  margin-top: var(--s-6); }
.fb-send { flex: 0 0 auto; width: 100%; }
.fb-anon { align-self: center; min-height: 44px; padding: 0 var(--s-2); color: var(--muted);
  font-size: var(--t-meta); text-decoration: underline; text-underline-offset: 3px; }
.fb-anon[disabled] { opacity: .45; pointer-events: none; }
.fb-actions + .fb-said { text-align: center; }
.fb-hint { margin: 0; text-align: center; font-size: var(--t-meta); color: var(--faint); }
.fb-hint[hidden] { display: none; }
.btn--done, .btn--done[disabled] { background: var(--surface-sunk); color: var(--ink); opacity: 1; }

/* =====================================================================
   First-deck tutorial (web/tutorial.js). A sheet over the first real deck
   of a tester session. Its cards are made-up restaurants, drawn rather
   than photographed and stamped "Practice · not a real place"; the real
   deck stays put, dimmed, behind a deeper scrim than the other sheets use.
   ===================================================================== */
.sheet-over--tutorial .sheet-scrim { background: rgba(19,18,16,.58); }
.tut { padding-top: var(--s-5); }
.tut-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.tut-head .sheet-head { margin: 0; }
.tut-head .sheet-head:focus { outline: none; }
.tut-skip { min-height: 44px; padding: 0 var(--s-1); color: var(--muted); font-size: var(--t-meta);
  transition: color var(--fast) var(--ease); }
.tut-stage { margin-top: var(--s-4); }

/* A small deck: two practice cards and the end card, offset only as far
   as it takes to read as a stack. */
.tut-deck { position: relative; height: 212px; margin: 0 var(--s-3) var(--s-6); }
.tut-card {
  position: absolute; inset: 0; border-radius: 14px; overflow: hidden;
  touch-action: none; user-select: none; -webkit-user-select: none; cursor: grab;
  box-shadow: var(--lift);
  transition: transform var(--base) var(--ease-out), opacity var(--base) var(--ease);
}
.tut-card[data-depth="0"] { z-index: 3; }
.tut-card[data-depth="1"] { z-index: 2; transform: translateY(12px) scale(.95); }
.tut-card[data-depth="2"] { z-index: 1; transform: translateY(23px) scale(.9); }
.tut-card--drag { transition: none; cursor: grabbing; }
.tut-card--gone { z-index: 4; opacity: 0; pointer-events: none; }
/* Warm tones, fixed in both themes, like the photographs they stand in for;
   the bottom darkens the way a real card's veil does, for the same words. */
.tut-card--clay  { background: linear-gradient(170deg, #B5754F 0%, #6B3C29 55%, #2E1B13 100%); }
.tut-card--olive { background: linear-gradient(170deg, #75704A 0%, #45432B 55%, #201F15 100%); }
.tut-art { position: absolute; top: 34px; left: 0; right: 0; display: grid; place-items: center; }
.tut-art svg { width: 108px; fill: none; stroke: rgba(255,255,255,.55);
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tut-tag { position: absolute; top: var(--s-3); left: var(--s-3); padding: 2px var(--s-2);
  border: 1px dashed rgba(255,255,255,.66); border-radius: var(--r-sm);
  font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; color: var(--on-photo); }
/* The same order as a real card: cuisine, serif name, then the facts. */
.tut-copy { position: absolute; left: var(--s-4); right: var(--s-4); bottom: var(--s-4);
  display: grid; gap: 3px; color: var(--on-photo); }
.tut-kicker { font-size: .64rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--on-photo-dim); }
.tut-name { font-family: var(--display); font-weight: 500; font-size: 1.36rem; line-height: 1.1;
  letter-spacing: -.01em; }
.tut-facts { display: flex; align-items: baseline; gap: 6px; margin-top: 3px;
  font-size: var(--t-micro); color: var(--on-photo-dim); font-variant-numeric: tabular-nums; }
.tut-facts b { color: var(--on-photo); font-weight: 600; }
.tut-facts i { font-style: normal; }
.tut-star { color: var(--warm-lift); position: relative; top: 1px; }
.tut-verdict { position: absolute; top: var(--s-3); right: var(--s-3); padding: 2px var(--s-2);
  border-radius: var(--r-sm); background: var(--on-photo); color: #1A1917;
  font-size: .64rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; opacity: 0; }
.tut-verdict:empty { display: none; }

.tut-card--end { cursor: default; box-shadow: none; background: var(--surface);
  border: 1px solid var(--line); display: flex; flex-direction: column; justify-content: center;
  gap: var(--s-4); padding: var(--s-4) var(--s-5); color: var(--ink); }
.tut-end-kicker { font-size: var(--t-micro); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); }
.tut-end-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.tut-end-list li { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-meta);
  line-height: 1.3; }
.tut-end-list svg { flex: 0 0 auto; color: var(--muted); }

/* The lesson: not-for-me on the left, keep on the right, where the card
   goes. Each is also the button that does it. */
.tut-legend { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.tut-side { display: grid; justify-items: center; gap: 2px; min-height: 44px; padding: var(--s-1) 0;
  color: var(--ink); text-align: center; }
.tut-dot { display: grid; place-items: center; width: 46px; height: 46px; margin-bottom: var(--s-2);
  border-radius: var(--r-full); border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-soft);
  transition: transform var(--fast) var(--ease), border-color var(--fast) var(--ease),
    background var(--fast) var(--ease); }
.tut-verb { font-size: var(--t-body); font-weight: 600; }
.tut-means { font-size: var(--t-meta); color: var(--muted); white-space: nowrap; }
.tut-side:active .tut-dot { transform: scale(.92); }
.tut-side:disabled { opacity: .32; cursor: default; }
.tut-side:focus-visible { outline: none; }
.tut-side:focus-visible .tut-dot { outline: 2px solid var(--warm); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
  .tut-side:not(:disabled):hover .tut-dot { border-color: var(--ink-soft); color: var(--ink); }
  .tut-skip:hover { color: var(--ink); }
}

.tut-status { margin: var(--s-4) 0 0; min-height: 1.5em; text-align: center; color: var(--ink-soft);
  font-size: var(--t-meta); line-height: 1.5; text-wrap: balance; }
.tut .sheet-foot { margin-top: var(--s-4); }
/* The swipe-learning disclosure: fine print, not a control. */
.tut-privacy { margin: var(--s-3) auto 0; max-width: 34ch; text-align: center; color: var(--muted);
  font-size: var(--t-micro); line-height: 1.5; text-wrap: balance; }

/* The one demonstration: lean left while "Not for me" lights, lean right
   while "Want to keep it" does. Played once; skipped under reduced motion. */
.tut-card--nudge { animation: tut-nudge 2s var(--ease) 1; }
.tut-stage--nudge [data-tut="-1"] .tut-dot { animation: tut-lit-pass 2s var(--ease) 1; }
.tut-stage--nudge [data-tut="1"] .tut-dot { animation: tut-lit-save 2s var(--ease) 1; }
@keyframes tut-nudge {
  0%, 44%, 100% { transform: none; }
  20% { transform: translateX(-30px) rotate(-3deg); }
  66% { transform: translateX(30px) rotate(3deg); }
}
@keyframes tut-lit-pass {
  0%, 40%, 100% { border-color: var(--line); color: var(--ink-soft); }
  14%, 26% { border-color: var(--ink); color: var(--ink); }
}
@keyframes tut-lit-save {
  0%, 46%, 86%, 100% { border-color: var(--line); color: var(--ink-soft); }
  60%, 72% { border-color: var(--ink); color: var(--ink); }
}
@media (prefers-reduced-motion: reduce) {
  .tut-card, .tut-dot { transition: none; }
  .tut-card--nudge, .tut-stage--nudge .tut-dot { animation: none !important; }
}

/* ---------- the first-open intro ----------
 * The same sheet as Compare, a little more generous at the top. One serif
 * line, two short paragraphs, three numbered steps and Start. The
 * prototype note sits on the sunk surface so it reads as a plain fact,
 * not as fine print, and not as a warning either. */
.intro { padding-top: var(--s-6); }
.intro-x { position: absolute; top: var(--s-2); right: var(--s-2); display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: var(--r-full); color: var(--muted);
  transition: color var(--fast) var(--ease); }
.intro-head { font-size: var(--t-ask); line-height: 1.1; padding-right: var(--s-7); margin: 0; }
.intro-head:focus { outline: none; }
.intro-what { margin: var(--s-3) 0 0; color: var(--ink-soft); font-size: var(--t-body); line-height: 1.5; }
.intro-proto { margin: var(--s-4) 0 0; padding: var(--s-3) var(--s-4); border-radius: var(--r-sm);
  background: var(--surface-sunk); color: var(--ink-soft); font-size: var(--t-meta); line-height: 1.5; }
.intro-steps { list-style: none; margin: var(--s-5) 0 0; padding: 0; counter-reset: intro;
  display: grid; gap: var(--s-3); }
.intro-steps li { counter-increment: intro; display: flex; align-items: center; gap: var(--s-3);
  font-size: var(--t-body); color: var(--ink); line-height: 1.35; }
.intro-steps li::before { content: counter(intro); flex: 0 0 auto; display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: var(--r-full); border: 1px solid var(--line);
  color: var(--muted); font-size: var(--t-micro); font-weight: 600; }
.intro .sheet-foot { margin-top: var(--s-6); }
@media (hover: hover) and (pointer: fine) { .intro-x:hover { color: var(--ink); } }
@media (prefers-reduced-motion: reduce) { .intro-x { transition: none; } }

/* ---------- questionnaire: where you are, and what Continue waits for ----------
 * The ticks above are decorative (aria-hidden); this line is the progress
 * a remote tester and a screen reader actually read. The need line sits
 * just above the footer and disappears the moment the answer is given. */
.ask-bar { padding-bottom: var(--s-2); }
.ask-step { margin: 0 0 var(--s-5); font-size: var(--t-micro); letter-spacing: .04em; color: var(--muted); }
.ask-need { margin: auto 0 0; padding-top: var(--s-6); font-size: var(--t-meta); color: var(--ink-soft);
  text-align: center; }
.ask-need[hidden] { display: none; }
/* #stage is a flex column: the visible need line takes the footer's
   auto margin, so the two stay together at the bottom. */
.ask-need:not([hidden]) + .ask-foot { margin-top: 0; padding-top: var(--s-3); }
