/**
 * DAYLIGHT — the redesign layer, loaded last so it re-skins the surfaces above it.
 *
 * What lives here: the warm Darjeeling hero photo and its scrim; the SARGVISION brand lockup and
 * the frosted-glass chrome that sits over the photo; the serif greeting; and the frosted-glass +
 * accent treatment for the canvas surfaces (bubbles, grounding cards, chips, the composer,
 * the overlay sheets and the safety card). The structural CSS (layout.css) and the mic (orb.css)
 * were rewritten in place; everything a guest reads as "premium" is concentrated here.
 *
 * Palette: warm ivory ground · deep teal ink · glacier-turquoise accent · ember safety. The accent
 * ramp is `--violet*`, defined in css/glass.css, which loads after this file and owns the palette.
 */

/* ---------------------------------------------------------------------------------------------
 * Hero photo — the bright alpenglow Darjeeling scene, dissolving into the ivory canvas.
 * ------------------------------------------------------------------------------------------- */

.hero-img {
  position: absolute;
  inset: 0;
  background: url("/img/hero-day.jpg") center 24% / cover no-repeat;
}

/* A whisper of top light keeps dark ink legible on the sky; the bottom fades to ivory so the hero
   melts into the canvas rather than ending on a hard seam. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.26) 0%,
    rgba(255, 255, 255, 0) 20%,
    rgba(241, 237, 227, 0) 50%,
    rgba(241, 237, 227, 0.72) 80%,
    var(--paper) 100%
  );
}

/* Film grain over the hero only — a barely-there premium texture, below the copy (z-index 3). */
#hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: multiply;
  background-size: 160px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
}

/* ---------------------------------------------------------------------------------------------
 * Brand lockup over the photo.
 * ------------------------------------------------------------------------------------------- */

.brand-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.brand-mark {
  width: 40px;
  height: 40px;
  border-radius: 11px;
  flex-shrink: 0;
  overflow: hidden;
  box-shadow: 0 6px 16px -6px rgba(18, 41, 45, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.45);
}

.brand-mark img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.brand-text {
  min-width: 0;
}

.brand-text h1 {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: calc(19px * var(--type-scale));
  line-height: 1.08;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-shadow: 0 1px 12px rgba(255, 255, 255, 0.65);
  /* Never spill past two lines — a long property name stays a tidy lockup, not a paragraph. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The `#tagline` rules that sat here are gone with the element: the lockup is the single word
   "Atithi" now (see the note in index.html), so there is no eyebrow line to track out and uppercase.
   That also retires the `:root:not([lang="en"])` override css/glass.css carried for it — uppercase is
   a no-op in Devanagari and Bengali, and letter-spacing breaks their conjuncts. */

#topbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
}

/* Frosted-white chrome pills over the photo. The colours come from the --ghost-* / --glass tokens;
   here we add the backdrop blur and a soft lift. `.lang-field` was in this list and is gone with its
   element; theme and spoken-replies have since left too — they are `.drawer-row`s in the utility
   drawer now (markup in index.html, dressed in css/glass.css), which is flat cream paper and wants
   none of this frosting. */
.btn-ghost {
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  box-shadow: 0 4px 12px -6px rgba(18, 41, 45, 0.3);
}

/* ---------------------------------------------------------------------------------------------
 * Guest / stay control — the SKELETON only.
 *
 * It was a frosted pill of "·"-separated facts, then a two-cell row of paper, and it is a single
 * profile ICON in the chrome row now (css/glass.css THE PROFILE ICON owns its surface; the detail it
 * used to display lives in the #stay-sheet it opens). The layout rules that were here — `display:
 * flex`, `align-items`, `flex-wrap: nowrap` — described a multi-cell row and are gone with it:
 * `.chrome-icon-btn` centres the one glyph, and re-declaring `flex` here would fight it.
 *
 * `font-family: inherit` stays, because it is a <button> and a UA still hands buttons their own font.
 * ------------------------------------------------------------------------------------------- */

#guest-strip {
  font-family: inherit;
}

/* ---------------------------------------------------------------------------------------------
 * The greeting — the first thing the guest reads, in the display serif.
 * ------------------------------------------------------------------------------------------- */

#greeting {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(30px, 8.5vw, 42px);
  line-height: 1.06;
  letter-spacing: 0.01em;
  color: var(--ink);
  /* Tightened from a 24px/0.72 glow: at that radius the shadow bled well past the glyphs and read
     as a soft-edged pale card behind the greeting rather than as legibility help on the letters
     themselves — the same "white rectangle" complaint the removed #hero-copy wash drew (see
     css/glass.css). A closer, lighter shadow still lifts the serif off a bright sky without
     drawing a shape of its own. */
  text-shadow: 0 1px 10px rgba(255, 255, 255, 0.5);
}

/* 28ch measured 202px in a 448px hero and broke this into three ragged lines ending on the single
   word "answer." 34ch is still a comfortable measure; `balance` evens the lines rather than
   lengthening the first. */
#greeting-sub {
  margin: var(--sp-3) auto 0;
  max-width: 34ch;
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  font-weight: 500;
  text-wrap: balance;
  color: var(--ink);
  /* See #greeting, above — same tightening, same reason. */
  text-shadow: 0 1px 8px rgba(255, 255, 255, 0.6);
}

/* Indic greetings look best a touch smaller and lighter than the Latin serif display size. */
:root[lang="bn"] #greeting,
:root[lang="hi"] #greeting,
:root[lang="ne"] #greeting {
  font-family: var(--font-indic);
  font-weight: 700;
  font-size: clamp(26px, 7vw, 34px);
}

/* ---------------------------------------------------------------------------------------------
 * Canvas: conversation bubbles — light frosted glass on the ivory ground.
 * ------------------------------------------------------------------------------------------- */

#log {
  background-image: radial-gradient(color-mix(in srgb, var(--violet) 10%, transparent) 1px, transparent 1px);
  background-size: 18px 18px;
}

.bubble {
  background: var(--glass-2);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.72);
  box-shadow: 0 10px 24px -18px rgba(18, 41, 45, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.6);
}

.row.user .bubble {
  background: color-mix(in srgb, var(--violet) 12%, var(--glass-2));
  border-color: color-mix(in srgb, var(--violet) 30%, rgba(255, 255, 255, 0.7));
}

.row.error .bubble {
  background: color-mix(in srgb, var(--ember) 12%, var(--glass-2));
  border-color: color-mix(in srgb, var(--ember) 34%, transparent);
  color: var(--ember);
}

/* ---------------------------------------------------------------------------------------------
 * Grounding cards — the "money shot", now frosted with a gold hairline and a serif headline. The
 * status accent (green / amber / red left border) is kept: it is meaning, not decoration.
 * ------------------------------------------------------------------------------------------- */

.card {
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border: 1px solid var(--glass-line);
  border-left: 4px solid var(--violet-deep);
  box-shadow: var(--shadow-2);
}

.card-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: calc(var(--fs-h3) * 1.15);
}

.card-head,
.card-foot {
  border-color: var(--hairline);
}

/* The provenance chip is the mockup's "verified source" tag. Its fill and hairline were still
   rgba(15,181,166,…) — the glacier-turquoise of the RETIRED alpine palette — so the one chip that
   says "here is the receipt" was the only turquoise object left on an indigo-and-cream page. Its
   ink already comes from css/glass.css (--brand-ink, the readable champagne); the surface now
   matches the quiet paper the rest of the card furniture uses. */
.prov {
  background: var(--surface-2);
  border-color: var(--glass-line);
  font-weight: 600;
}

.cap-group,
.op-card,
.progress,
.segment,
.weather-tile,
.rate-row {
  background: var(--glass-2);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-color: var(--glass-line);
}

/* ---------------------------------------------------------------------------------------------
 * Composer starter chips + text field — frosted to match the cap-rail.
 * ------------------------------------------------------------------------------------------- */

#chips {
  background: transparent;
  border-top: 1px solid var(--hairline);
}

.chip {
  background: var(--glass-2);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  color: var(--ink);
  font-weight: 500;
}

.chip:not(:disabled):hover {
  background: rgba(255, 255, 255, 0.92);
}

#composer {
  background: transparent;
}

/* #text-input's own background/blur/border rule used to live here (glass(2), blur(10px), a white
   hairline) and is gone: css/glass.css's two later #text-input rules set background to transparent
   (then to `none` inside the dock) and border to 0/transparent, at equal specificity and later in
   the cascade, so every property this block set was already dead — confirmed by measuring the
   computed background, not by reading the rule. The field's frosted look now comes entirely from
   its parent, #composer-field (also de-glassed to solid --surface in the same redesign pass), which
   is the whole reason this block stopped doing anything rather than something worth tuning. */

#text-input:focus {
  border-color: color-mix(in srgb, var(--violet) 55%, transparent);
}

/* ---------------------------------------------------------------------------------------------
 * Primary actions — a deep-teal gradient, light ink, a glow in the fill's own hue.
 *
 * The gradient runs light→fill within the TEAL ramp, so one ink clears both ends: --on-indigo is
 * 5.3:1 at the top and 6.1:1 at the bottom. This was the --violet ramp with dark --on-brand ink, and
 * a solid bright turquoise at button size read as the loudest object on a warm ground.
 *
 * --on-indigo, not --accent-ink: js/theme.js overwrites --accent-ink inline from the tenant's accent,
 * so a fixed teal fill inked with it lands wherever that tenant's hue happens to be (1.8:1 here).
 *
 * css/glass.css restates this block (it loads last and owns the palette); the two must agree, or
 * reading this file tells you the wrong colour for the most-pressed control in the app.
 * ------------------------------------------------------------------------------------------- */

#send-btn,
#jump-latest,
.op-call,
.btn-accent {
  background: linear-gradient(180deg, var(--indigo-lit), var(--indigo));
  border: none;
  color: var(--on-indigo);
  box-shadow: 0 12px 24px -12px rgba(18, 101, 94, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.22);
}

#send-btn:not(:disabled):hover,
.op-call:hover,
.btn-accent:not(:disabled):hover {
  filter: brightness(1.08);
  background: linear-gradient(180deg, var(--indigo-lit), var(--indigo));
}

/* ---------------------------------------------------------------------------------------------
 * Overlay sheets (capabilities / marketplace) — frosted ivory, not a flat slab.
 * ------------------------------------------------------------------------------------------- */

/* #about-panel is absent because the id no longer exists: the capability map expands inside the
   utility drawer now (css/glass.css .drawer-about) rather than being a sheet of its own, so there
   is no overlay left here for this fill to reach.

   #market-panel/#login-panel's own fill+blur used to live here too (a cream gradient, blur(24px))
   and is gone the same way #text-input's did above: css/glass.css's later, equal-specificity rule
   ("THE OVERLAY SHEETS") repaints both with its own gradient and blur(30px), so every property this
   block set was already dead — confirmed by measuring, not by reading. */

.cap-title,
.op-name {
  font-family: var(--font-serif);
  font-weight: 700;
}

/* ---------------------------------------------------------------------------------------------
 * Hotel sign-in (guest-lifecycle P1): the top-bar affordance + the sign-in sheet's form.
 * ------------------------------------------------------------------------------------------- */

/* The [hidden] attribute (toggled from js/app.js syncSignInButton) must win over the display
   below — otherwise the button shows for a signed-in guest. UA `[hidden]{display:none}` loses on
   specificity to `.signin-btn`, so restate it. */
.signin-btn[hidden] {
  display: none;
}
.signin-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--brand, #c9992f);
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand, #c9992f) 12%, transparent);
  color: var(--ink, #12292d);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.signin-btn:hover {
  background: color-mix(in srgb, var(--brand, #c9992f) 22%, transparent);
}
.signin-btn:active {
  transform: translateY(1px);
}
.signin-btn .icon svg {
  width: 17px;
  height: 17px;
}

.login-form {
  display: flex;
  flex-direction: column;
  /* Between the form's top-level pieces now (the verification card, the CTA, the privacy line) —
     generous on purpose: three confident blocks read as considered, where four tightly-packed ones
     read as cramped AND leave the same amount of empty sheet below them. */
  gap: var(--sp-5);
  max-width: 30rem;
  margin-top: var(--sp-4);
}

/*
 * THE SHEET'S HEADER — one centred composition (mark, headline, sub-copy) shared by all three
 * screens (`screenHead()`/`renderSuccess()` in js/login.js), with back as a small pinned corner
 * icon rather than an inline row item.
 *
 * This replaces a left-aligned "dialog header" — an icon, a title/sub column, then a close
 * button, all in one flex row (still `.cap-head`'s shape, css/chat.css, and still what
 * #market-panel/#demo-guest-panel use). That row reads as a generic overlay's titlebar; this sheet
 * is the next page of the welcome screen's own stationery, read top-to-bottom like a headline, not
 * scanned left-to-right like a dialog chrome. Scoped to `#login-panel` alone — the shared
 * `.cap-head`/`.cap-title`/`.cap-close` rules (chat.css) are untouched, so the other two overlay
 * panels keep their original header exactly as it was.
 *
 * Neither screen in this sheet shows a close (X) any more — js/login.js's `screenHead` has no
 * `onClose`/close-button path left to render one from, so there is nothing here to hide or gate;
 * `.login-back` is the only corner control this header ever pins.
 */
#login-panel .login-hero {
  position: relative;
  padding-top: calc(40px * var(--type-scale));
  margin-bottom: var(--sp-2);
  text-align: center;
}
#login-panel .login-hero .cap-title,
#login-panel .login-hero .cap-sub {
  margin: 0;
  text-align: center;
}
/* The headline, in the welcome screen's own display serif and a size closer to its H1 than to a
   dialog title — the one line on this sheet that has to read as a HEADLINE, not a caption. */
#login-panel .login-hero .cap-title {
  font-size: var(--fs-h1);
  line-height: var(--lh-tight);
}
#login-panel .login-hero .cap-sub {
  margin: var(--sp-1) auto 0;
  max-width: 34ch;
}

/* The Atithi crest, centred above the headline on EVERY screen of the flow — reusing the exact
   mark the header and drawer already show (brand/atithivani-mark.svg), not a new asset. It used to
   appear on the first screen only; the OTP step dropped it, which is exactly the moment a guest is
   about to hand over a phone number and benefits most from re-reading "this is Atithi". Sized a
   step up from its old 40px, closer to the welcome screen's own crest, for the same presence at
   the top of the one screen a guest actually pauses on. */
.login-mark {
  display: flex;
  justify-content: center;
  margin-bottom: var(--sp-3);
}
.login-mark img {
  width: calc(48px * var(--type-scale));
  height: calc(48px * var(--type-scale));
}

/* Back (OTP screen returns to details; the details screen itself returns to /welcome/ — see
   js/login.js's goToWelcome), pinned to the hero's own top-left corner rather than sharing a row
   with the title — same control, same handler, same `.login-back` class chat.css/daylight.css's
   own un-pinned rule styles everywhere else, just repositioned for this one centred composition. */
#login-panel .login-hero .login-back {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
}

.login-label {
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink-soft);
  margin-top: 6px;
}

/* ── the three details, as labelled groups ───────────────────────────────────────────────────
 * A field is label + (why we ask) + control + (what is wrong with it), in that reading order, so
 * a screen reader meets the hint before the input it describes and the error immediately after.
 * .login-form's gap handles the space between groups; the wrapper's own margin-top on the label
 * would double it. */
.login-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.login-field .login-label {
  margin-top: 0;
}
.login-hint {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
  line-height: var(--lh-body);
}
.login-field-error {
  margin: 0;
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--danger-ink);
}
/* The field itself carries the state too — colour alone is never the only cue (the error line
   below it says which field and why, in words). */
.login-input[aria-invalid="true"],
.login-select[aria-invalid="true"] {
  border-color: var(--danger-line);
}
.login-input[aria-invalid="true"]:focus,
.login-select[aria-invalid="true"]:focus {
  border-color: var(--danger-line);
}

/* ── the verification group — hotel + phone as ONE card, not two floating fields ─────────────
 * `--surface-2` (a shade deeper/warmer than `--surface`, in both themes) is what makes each field
 * read as inset INTO the card rather than merely bordered on the same paper — the same "grouped
 * list" depth relationship a settings screen uses, one step of contrast, not a second visual
 * system. `.otp-group` (the OTP screen's 6 boxes) reuses this exact card via `.login-group--otp`. */
.login-group {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  /* Clips the botanical corner accent below, placed deliberately past the edge. */
  overflow: hidden;
}
/*
 * A whisper of the welcome card's own botanical line art in one corner — champagne, at an opacity
 * where it reads as texture in the card rather than as illustration (welcome.css's own sprig, same
 * asset, same idea). "Subtle" is the operative word: ONE corner, one asset, no second sprig — this
 * is a form, not the welcome screen, and it only has to feel like the same stationery, not repeat
 * the same composition. A pseudo-element rather than `js/login.js` markup, so the accent costs this
 * file a few lines of CSS and the DOM nothing.
 */
.login-group::before {
  content: "";
  position: absolute;
  z-index: 0;
  right: -34px;
  bottom: -38px;
  width: 128px;
  aspect-ratio: 200 / 270;
  opacity: 0.08;
  pointer-events: none;
  transform: scaleX(-1) rotate(-8deg);
  background-color: var(--gold);
  -webkit-mask: url(/brand/botanical-sprig.svg) no-repeat center / contain;
  mask: url(/brand/botanical-sprig.svg) no-repeat center / contain;
}
.login-group > * {
  position: relative;
  z-index: 1;
}
.login-group-note {
  margin: 0;
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--ink-soft);
}
/* The hairline between hotel and phone — ON TOP of the group's own `gap`, so the two fields are
   visibly one list with a row divider, the way "Hotel" and "Phone" belong on one card at a front
   desk rather than two separate slips. */
.login-group .login-field + .login-field {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}
/* The OTP screen's box row gets the same card treatment as the details card above it, for the same
   reason: the 6 digits are one object, not six floating inputs. Centred rather than left-aligned
   (login-group's default), and no internal field divider to fight (there is only one child here). */
.login-group--otp {
  align-items: center;
}

/* ── the leading field icon (hotel/phone) — decorative, reusing js/icons.js, never a second way to
 * say what the label already says. Inset into the control's own left edge rather than a sibling,
 * so the field stays one bordered box, not an icon-plus-box pair. */
.login-control {
  position: relative;
}
.login-control-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-faint);
  pointer-events: none;
}
.login-select.has-icon,
.login-input.has-icon {
  padding-left: calc(40px * var(--type-scale));
}

/* ── the phone field's country-code picker + number box as ONE bordered row ──────────────────
 * The phone field is the one field with two controls, not one — `js/login.js#buildPhoneField`
 * builds this row directly rather than through `field()`'s single-control icon inset above, so the
 * icon here is an ordinary flex item (not the absolute-positioned one `.login-control-icon` is
 * elsewhere) and the row itself, not either control, carries the border/background/focus ring —
 * the two controls read as one field, the same way `.login-select`/`.login-input` normally do
 * alone. */
.login-control--phone {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}
.login-control--phone .login-control-icon {
  position: static;
  flex-shrink: 0;
  align-self: center;
  margin-left: 14px;
  transform: none;
}
.login-control--phone .login-country-select,
.login-control--phone .login-phone-input {
  min-height: calc(48px * var(--type-scale));
  border: none;
  border-radius: 0;
  background: transparent;
}
.login-control--phone .login-country-select {
  flex: 0 0 auto;
  width: auto;
  max-width: calc(108px * var(--type-scale));
  padding: 12px 6px 12px 10px;
  border-right: 1px solid var(--hairline);
}
.login-control--phone .login-phone-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 14px;
}
/* The row shows ONE focus ring for either control — a ring per-control inside an already-bordered
   row would read as two fields again, exactly what this layout exists to avoid. */
.login-control--phone:focus-within {
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
.login-control--phone .login-country-select:focus,
.login-control--phone .login-phone-input:focus {
  outline: none;
  box-shadow: none;
}
/* The invalid state still needs to read on the row itself — `.login-input[aria-invalid="true"]`
   above only ever coloured a control's OWN border, which this layout has deliberately removed. */
.login-control--phone:has(.login-phone-input[aria-invalid="true"]) {
  border-color: var(--danger-line);
}

/* Disabled once a screen has nothing left to change (there is no in-flight submit any more — the
   hotel+phone screen hands off to the OTP screen instantly, see js/login.js's module docstring for
   why that is not a network call). Kept for any control a future pass disables deliberately. */
.login-select:disabled,
.login-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ── the back button (every screen but success) ──────────────────────────────────────────────
 * The same size/shape `.cap-close` still uses on the other two overlay panels (chat.css) — this
 * sheet no longer shows a close X anywhere (js/login.js's `screenHead` has no such button left to
 * render), so `.login-back` is the one corner control both the details and OTP screens pin; see
 * `#login-panel .login-hero .login-back` above for where it's actually PINNED. This rule is only
 * the button's own face (size, border, hover). */
.login-back {
  flex-shrink: 0;
  width: calc(36px * var(--type-scale));
  height: calc(36px * var(--type-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--hairline-strong);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
}
.login-back:hover {
  background: var(--bubble-out);
  color: var(--ink);
}
.login-back svg {
  width: 18px;
  height: 18px;
}

/* The one-line reassurance under the button — the same restrained footnote register as the
 * composer dock's own privacy line (#dock-privacy), not a legal paragraph. */
.login-privacy {
  margin: 2px 0 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
  text-align: center;
}

/* ── the OTP screen ───────────────────────────────────────────────────────────────────────────
 * Wired to the real `/api/otp/send`/`/api/otp/verify` (js/login.js's module docstring) — every
 * rule below dresses working UI, not a scaffold. */

/* Two tight clusters, each ONE child of .login-form's own (generous) gap: the target line + sent
 * tag belong next to each other, and Verify + Resend belong next to each other — the form's
 * `gap: var(--sp-5)` is for the space BETWEEN these clusters (and the alert slot between them and
 * the OTP card), not inside either cluster itself. */
.login-otp-header,
.login-otp-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Who the code is for — the one line on this screen that carries real, guest-specific content, so
 * it is the thing given weight: body-size ink, the phone number bold rather than the whole line
 * shouting equally. */
.otp-target {
  margin: 0;
  font-size: var(--fs-body, 1rem);
  color: var(--ink);
}
.otp-target strong {
  font-weight: 700;
}

/* A quiet, CONFIDENT confirmation tag — "a code really did just go out" — not a dashed placeholder.
 * It used to read like a build watermark from when this screen had no backend behind it; now that
 * `/api/otp/send` is real, a dashed outline and faint ink undersell the one reassurance a guest
 * wants right after tapping "Send OTP". A soft success tint instead: same register as the field-
 * level "sent" confirmations elsewhere in the app, not filled/loud, but no longer apologetic. */
.login-devnote {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  background: var(--ok-bg);
  border: 1px solid var(--ok-line);
  color: var(--ok-ink);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: var(--lh-body);
}
.login-devnote .icon {
  width: 14px;
  height: 14px;
}

/*
 * ── inline alert banners ─────────────────────────────────────────────────────────────────────
 * One component for every "something went wrong" state this sheet shows: a send failure, a resend
 * failure, or a verify outcome (invalid code, expired code, locked out). Two tones only —
 * `--danger` (the guest's code/number: correct it or ask for a new one) and `--neutral` (a
 * transport/server problem that is not their fault) — see js/login.js's module docstring for why
 * "invalid", "expired" and a hotel/phone that never matched a stay all render in the SAME `danger`
 * tone with only their wording differing: the server gives this screen one signal, not three, and
 * a colour this screen cannot honestly back up is worse than no distinction at all.
 */
.login-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  font-weight: 600;
}
.login-alert-icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}
.login-alert--danger {
  background: var(--danger-bg);
  border: 1px solid var(--danger-line);
  color: var(--danger-ink);
}
.login-alert--neutral {
  background: var(--info-bg);
  border: 1px solid var(--info-line);
  color: var(--ink-soft);
}
/* Collapses to nothing (no stray gap in .login-form's flex column) when no alert is showing —
 * js/login.js empties this slot rather than hiding it, so `:empty` is the one selector that needs
 * to know which state that is. */
.login-otp-alert-slot:empty {
  display: none;
}

.otp-group {
  display: flex;
  justify-content: center;
  gap: var(--sp-2);
  /* `.login-group--otp`'s own `align-items: center` (rather than the base `.login-group`'s stretch)
     means this row is centred at its own preferred width, not stretched to the card's — so without
     an explicit width it renders at 6 boxes' full `max-width` and gaps (328px) regardless of how
     narrow the card actually is, silently overflowing at 320-375px (invisible before the botanical
     accent above added `overflow: hidden` to `.login-group`; the accent didn't create this, it just
     turned a few px of harmless bleed past the card's rounded corner into a hard, visible clip).
     `width: 100%` puts this row back in its container's own width, so `.otp-box`'s `min-width: 0` +
     `flex: 1 1 0` can actually shrink the six boxes to fit it, the same as any other flex row here. */
  width: 100%;
}
.otp-box {
  flex: 1 1 0;
  min-width: 0;
  max-width: calc(48px * var(--type-scale));
  aspect-ratio: 1 / 1;
  box-sizing: border-box;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  text-align: center;
  font: inherit;
  font-size: var(--fs-h2);
  font-weight: 700;
  /* Same iOS-zoom guard as .login-input/.login-select — a single digit box is still a text input
     the guest's thumb lands on first. */
  font-size: max(20px, var(--fs-h2));
  padding: 0;
}
.otp-box:focus {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
/* The row-level invalid cue (js/login.js's `markRowInvalid`, on a wrong/expired/locked-out verify)
 * — every box rings danger at once, since the guest cannot tell which single digit was wrong
 * (there wasn't one told to them; see the module docstring). Digits are left in place: this rings
 * the row, it does not clear it. Clears itself the moment any box is edited. */
.otp-group.is-invalid .otp-box {
  border-color: var(--danger-line);
}
.otp-group.is-invalid .otp-box:focus {
  border-color: var(--danger-line);
  box-shadow: 0 0 0 3px rgba(178, 59, 59, 0.3);
}

/* The secondary action (Resend) is a plain text control now, not a second bordered pill competing
 * with Verify above it — CTA hierarchy on an OTP screen is exactly two weights: one filled primary,
 * one quiet text link, the shape a guest already reads correctly without looking twice. Still a
 * full 44px target (the whole row is tappable, not just the word), just with no fill or border to
 * carry that target visually. */
.login-resend {
  width: 100%;
  min-height: calc(44px * var(--type-scale));
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-weight: 600;
  font-variant-numeric: tabular-nums; /* the mm:ss countdown must not jitter the button width */
  text-decoration: underline;
  text-decoration-color: var(--hairline-strong);
  text-underline-offset: 3px;
  cursor: pointer;
}
.login-resend:hover {
  color: var(--ink);
  text-decoration-color: currentColor;
}
/* While the courtesy cooldown counts down (js/login.js's RESEND_COOLDOWN_MS) or a resend is
 * in flight — quieter and inert, same "explains itself before a guest tries" treatment
 * .login-submit:disabled already gives Verify. */
.login-resend:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  text-decoration-color: transparent;
}

/* --surface-1 was never defined anywhere in the app, so every field below fell through to a
   hardcoded #fff — a flat white tile in BOTH themes, and a bright hole punched in the dark canvas.
   --surface is the real token and it follows the theme. */
.login-select,
.login-input {
  width: 100%;
  /* This is a guest-facing MOBILE form first: a comfortable target, and a box that cannot be
     wider than the sheet it sits in (`border-box`, because the padding here is real). */
  box-sizing: border-box;
  min-height: calc(48px * var(--type-scale));
  padding: 12px 14px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  /* NEVER below 16px. --fs-body clamps down to 15px on a narrow viewport, and iOS Safari zooms
     the whole page in on focusing any field under 16px — which on this sheet means the guest's
     first act in the product is pinching back out to find the button. */
  font-size: max(16px, var(--fs-body, 1rem));
}
/* --focus-ring is the house focus token (translucent indigo in light, lavender in dark). These
   fields were drawing a ring of their own in a colour that never inverted. */
.login-select:focus,
.login-input:focus {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
.login-error {
  margin: 2px 0 0;
  color: var(--danger-ink);
  font-size: var(--fs-small);
}
.login-submit {
  margin-top: 12px;
  /* The primary action on a phone: a full-width target no smaller than 48px, which the padding
     alone lands just under. */
  min-height: calc(48px * var(--type-scale));
  padding: 13px 18px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.login-submit:hover {
  filter: brightness(1.05);
}
.login-submit:active {
  transform: translateY(1px);
}
.login-submit.is-busy {
  opacity: 0.7;
  cursor: progress;
}
/* The OTP screen's Verify button starts (and stays, until all 6 boxes are filled — js/login.js
   syncVerifyEnabled) genuinely disabled, not merely inert: without this it kept its full indigo
   fill and read as tappable while doing nothing, which is a worse trap than a greyed-out button
   that at least explains itself before a guest tries. */
.login-submit:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  filter: none;
}

/* ── screen three: verified (js/login.js renderSuccess) ──────────────────────────────────────
 * A beat of confirmation, not a loading screen — verification has already finished by the time
 * this paints. Genuinely green (--ok-*), the one place on this sheet that departs from the
 * indigo/champagne pair: success is the one state this product-wide palette leaves to the status
 * system rather than the brand (css/tokens.css's own status palette, unchanged since the
 * "Kanchenjunga" redesign), so a guest reads it as "done" rather than as another branded moment. */
.login-success-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--sp-3);
  border-radius: var(--radius-pill);
  background: var(--ok-bg);
  border: 1px solid var(--ok-line);
  color: var(--ok-ink);
}
.login-success-icon .icon {
  width: 26px;
  height: 26px;
}
.login-success-card {
  text-align: left;
}
.login-success-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}
/* The same row-divider convention .login-group's own fields use — one card, a list of facts, not
   four floating lines. */
.login-success-row + .login-success-row {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}
.login-success-row-icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  color: var(--ink-faint);
}
.login-success-row-body {
  min-width: 0;
}
.login-success-row-label {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}
.login-success-row-value {
  margin: 2px 0 0;
  font-size: var(--fs-body, 1rem);
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------------------------
 * Room-preference card (guest-lifecycle P3) — captured, never sold.
 * ------------------------------------------------------------------------------------------- */

/* THE PAPER MATERIAL. The same recipe css/glass.css applies to .card / .op-card / .cond-card —
   near-opaque ivory, an indigo hairline, a whisper of blur, one soft warm shadow. Every card below
   reads it, so a booking card and a receipt are visibly the same stock. */
.pref-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.pref-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
/* --violet-deep, not --brand. --brand resolves to --violet (#6b5bb0) and css/glass.css never re-declares
   it for dark, so a card headline glyph measured ~2.4:1 on the dark card face. --violet-deep IS the
   documented per-theme ICON step (#4a3a86 light / #b9a8e8 dark) and is what .cap-pill-glyph and the
   drawer rows already use. Same treatment on every card head below. */
.pref-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.pref-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.pref-sub {
  margin: 0 0 10px;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.pref-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.pref-chip {
  padding: 7px 12px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
  cursor: pointer;
  /* Colour is NOT transitioned here, and that is the same call css/glass.css records for --orb-size:
     when a transitioned property resolves through a custom property that a THEME swaps on an
     ancestor, Chromium keeps the value it started from — no animation is registered and the property
     never re-resolves. Measured: after tapping the theme toggle with a preference card on screen,
     these chips stayed #fffaf4 (the light surface) on the dark card indefinitely. It only bites now
     because the fill finally follows the theme; it used to be a hardcoded #fff that never moved.
     The chip keeps its selected feedback from the .is-on rule below, which is a class swap. */
  transition: transform var(--dur-fast) var(--ease);
}
/* Selected = the lavender wash, which is how "this is the state in effect" is already said on
   .drawer-row.active. It is theme-aware; the brand-wash it replaces was a light-mode-only alpha. */
.pref-chip.is-on {
  background: var(--lavender);
  border-color: var(--violet-deep);
  font-weight: 600;
}
.pref-note {
  width: 100%;
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
}
.pref-note:focus {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
/* The fill, the ink and the hover come from the shared primary-action rule in css/glass.css — this
   keeps only the box. Same for .cab-submit below. */
.pref-submit {
  margin-top: 11px;
  padding: 11px 16px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.pref-submit.is-busy {
  opacity: 0.7;
  cursor: progress;
}
.pref-done {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.pref-check {
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  /* --ok-line, NOT --ok. css/glass.css aliases --pine to the indigo anchor and tokens.css defines
     --ok as --pine, so --ok resolves to #2b1e5e in the light theme — an indigo "success" tick.
     --ok-line is green in both themes (#2f7d5b / #2e7a56) and carries white at 4.6:1. */
  background: var(--ok-line);
  color: #fff;
  font-weight: 700;
}
.pref-sent {
  margin: 0 0 3px;
  color: var(--ink, #12292d);
  font-weight: 600;
  font-size: var(--fs-small);
}
.pref-summary {
  margin: 0 0 6px;
  color: var(--ink-soft);
  font-size: var(--fs-small);
}
.pref-status {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--ok-bg);
  color: var(--ok-ink);
  font-size: 0.76rem;
  font-weight: 600;
}
.pref-error {
  margin: 8px 0 0;
  color: var(--danger-ink);
  font-size: var(--fs-small);
}

/* Complaint card (guest-lifecycle P7) reuses the .pref-* chrome, with an alert-amber accent so it
   reads as "report a problem", not "set a preference". */
.complaint-card {
  border-left: 3px solid var(--warn-line);
}
.complaint-card .pref-head .icon svg {
  color: var(--warn-ink);
}

/* ---------------------------------------------------------------------------------------------
 * Airport-pickup / driver-dispatch card (guest-lifecycle P4) — show the dispatch happen.
 * ------------------------------------------------------------------------------------------- */

.cab-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.cab-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.cab-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.cab-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.cab-sub {
  margin: 0 0 10px;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.cab-label {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink-soft);
  margin: 6px 0 4px;
}
.cab-select,
.cab-input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
}
.cab-select:focus,
.cab-input:focus {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
.cab-drop {
  margin: 8px 0 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.cab-row {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}
.cab-row .cab-input {
  flex: 1;
  min-width: 0;
}
.cab-submit {
  margin-top: 11px;
  padding: 11px 16px;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.cab-submit.is-busy {
  opacity: 0.7;
  cursor: progress;
}
.cab-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.cab-step {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
}
.cab-step.is-done {
  background: var(--ok-bg);
  color: var(--ok-ink);
}
.cab-step.is-pending {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  color: var(--ink-soft);
}
.cab-step .cab-dot {
  width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 0.7rem;
}
.cab-step.is-done .cab-dot {
  background: var(--ok-line); /* green in both themes — see the note on .pref-check */
  color: #fff;
}
.cab-step.is-pending .cab-dot {
  border: 1.5px dashed color-mix(in srgb, var(--ink) 30%, transparent);
}
.cab-driver {
  margin: 2px 0 3px;
  color: var(--ink, #12292d);
  font-weight: 600;
  font-size: var(--fs-small);
}
.cab-driver-pending {
  font-weight: 500;
  color: var(--ink-soft);
}
.cab-route {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.cab-call {
  display: inline-block;
  margin: 2px 0 4px;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--accent, #2f6f7a);
  text-decoration: underline;
}
.cab-fare {
  margin: 0 0 3px;
  font-size: var(--fs-small);
  color: var(--ink, #12292d);
}
.cab-error {
  margin: 8px 0 0;
  color: var(--danger-ink);
  font-size: var(--fs-small);
}

/* ---------------------------------------------------------------------------------------------
 * Local finds card (guest-lifecycle P5) — curated regional products + a real-shops bridge.
 * ------------------------------------------------------------------------------------------- */

.finds-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.finds-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.finds-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.finds-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.finds-sub {
  margin: 4px 0 10px;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.finds-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.finds-item {
  padding: 10px 0;
  border-top: 1px solid var(--hairline);
}
.finds-item:first-child {
  border-top: 0;
  padding-top: 0;
}
.finds-name {
  font-weight: 700;
  color: var(--ink, #12292d);
  font-size: 0.98rem;
}
.finds-blurb {
  margin-top: 2px;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.finds-shop-btn {
  margin-top: 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  /* The SECONDARY action treatment already in this file (.place-maps): paper, a hairline, and the
     accent as ink rather than as a fill. It was an outlined brand-wash pill, which on a card that
     also carries a primary action read as a second primary. */
  border: 1px solid var(--glass-line);
  border-radius: 999px;
  background: var(--glass-2);
  color: var(--violet-deep);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 600;
  cursor: pointer;
}
.finds-shop-btn:hover {
  background: var(--glass-3);
  border-color: var(--glass-line-strong);
}
.finds-shop-btn.is-busy {
  opacity: 0.7;
  cursor: progress;
}
.finds-shop-btn .icon svg {
  width: 15px;
  height: 15px;
}
.finds-shops {
  margin-top: 8px;
}
.finds-shops-note {
  margin: 0 0 6px;
  font-size: 0.76rem;
  color: var(--ink-soft);
}
.finds-scroller {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-bottom: 4px;
}
.finds-scroller > * {
  scroll-snap-align: start;
  flex: 0 0 auto;
}
.finds-none {
  margin: 4px 0 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}

/* ---------------------------------------------------------------------------------------------
 * Day tracks (curated itineraries) — real Places stops + real Directions hops.
 * ------------------------------------------------------------------------------------------- */

.tracks-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.tracks-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.tracks-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.tracks-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.track-emoji {
  font-size: 1.15rem;
  line-height: 1;
}
.tracks-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.track-opt {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  text-align: left;
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--hairline);
  border-radius: 13px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.track-opt:hover {
  border-color: var(--violet-deep);
}
.track-opt:active {
  transform: translateY(1px);
}
.track-opt-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.track-opt-title {
  font-weight: 700;
  font-size: 0.98rem;
}
.track-opt-blurb {
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.track-opt-meta {
  font-size: 0.76rem;
  color: var(--violet-deep);
  font-weight: 600;
  text-transform: capitalize;
}
.itin-planning {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
  font-style: italic;
}
/* ─────────────────────────────────────────────────────────────────────────────────────────────
 * The Journey Thread — the one canonical itinerary visual (js/journey.js).
 *
 * One continuous thread down the day. The DESTINATION is a plate: its name, its photograph, what
 * it is for. Everything else — the window, the drive, the map link, the ordinal — is set below
 * it, and travel is a dashed length of the same thread rather than an entry of its own. That
 * ranking is the design; give the visit window a filled pill and it competes with the place, and
 * the whole thing reads as a table with pictures.
 *
 * Used by the planner's itinerary card and the curated track card, so there is one thread to
 * improve rather than two to keep in step. Every colour is a token, so light and dark come free.
 * ───────────────────────────────────────────────────────────────────────────────────────────── */
.journey {
  display: flex;
  flex-direction: column;
  gap: 30px;
}
.journey-day {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* A trip is chapters, not one long scroll: each day after the first opens above a hairline with
   real air over it. */
.journey-day + .journey-day {
  padding-top: 26px;
  border-top: 1px solid var(--hairline);
}

/* --- the day's head: a chapter title, not three lines of metadata --------------------------- */
.journey-day-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.journey-day-titles {
  min-width: 0;
}
.journey-day-eyebrow {
  margin: 0 0 3px;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold-deep, var(--ink-faint));
}
.journey-day-date {
  /* The display serif is the brand's own voice, and the reason a day header reads as a hotel's
     stationery rather than as a calendar row. */
  margin: 0;
  font-family: var(--font-serif, var(--serif));
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
}
.journey-day-span {
  margin: 4px 0 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.journey-day-weather {
  flex: none;
  margin: 0;
  display: inline-flex;
  gap: 5px;
  align-items: center;
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.journey-day-weather .icon svg {
  width: 15px;
  height: 15px;
  opacity: 0.75;
}

/* --- the thread ----------------------------------------------------------------------------- */
.journey-thread {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.journey-node,
.journey-leg {
  position: relative;
  display: flex;
  gap: 14px;
  align-items: flex-start;
}
.journey-node {
  padding: 0 0 4px;
}
.journey-leg {
  padding: 7px 0;
}

/* One segment PER ITEM rather than one line down the container. A single absolute line has to
   guess where to stop, and it guessed wrong: a last stop with a long name or a photograph is far
   taller than its dot, so the line trailed past the end of the day. */
.journey-thread > li::before {
  content: "";
  position: absolute;
  left: 13px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: color-mix(in srgb, var(--pine) 24%, transparent);
}
/* Movement is dashed, arrival is solid — the thread itself says which is which, before a word
   of it is read. */
.journey-leg::before {
  width: 0;
  border-left: 2px dashed color-mix(in srgb, var(--pine) 34%, transparent);
  background: none;
}
.journey-thread > li:first-child::before {
  top: 14px;
}
.journey-thread > li:last-child::before {
  bottom: auto;
  height: 14px;
}

/* --- markers: a 28px column, so the thread runs dead centre through all of them ------------- */
.journey-marker {
  flex: none;
  width: 28px;
  display: grid;
  place-items: center;
}
.journey-marker--stop {
  height: 28px;
  border-radius: 50%;
  /* Opaque, because it is what hides the thread where it passes behind. */
  background: color-mix(in srgb, var(--pine) 16%, var(--surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pine) 26%, transparent);
  color: var(--pine-ink);
}
.journey-ordinal {
  font-size: 0.76rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.journey-marker--origin {
  height: 28px;
  border-radius: 50%;
  background: var(--pine);
  color: var(--on-brand, #fff);
}
.journey-marker--origin .icon svg {
  width: 15px;
  height: 15px;
}
/* Arriving in another town: the same weight as the hotel, but a ring rather than a disc, so a
   glance tells "you are somewhere else now" from "you are home". */
.journey-marker--arrive {
  height: 28px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: inset 0 0 0 2px var(--pine);
  /* The text colour, not a green: pine-ink on a dark surface all but disappeared. */
  color: var(--ink);
}
.journey-marker--arrive .icon svg {
  width: 15px;
  height: 15px;
}
/* No disc at all: travel is a glyph ON the thread, sitting in the gap in the dashes. */
.journey-marker--leg {
  height: 20px;
  background: var(--surface);
  color: var(--ink-faint);
}
.journey-marker--leg .icon svg {
  width: 14px;
  height: 14px;
}

/* --- the destination plate ------------------------------------------------------------------ */
.journey-body {
  flex: 1;
  min-width: 0;
  padding-bottom: 6px;
}
.journey-time {
  margin: 0;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.journey-place {
  /* The one loud element per stop. */
  margin: 2px 0 0;
  font-family: var(--font-serif, var(--serif));
  font-size: 1.12rem;
  font-weight: 700;
  line-height: 1.24;
  color: var(--ink);
  /* A long official name wraps into the full column; it is never truncated, because half a name
     finds nothing on a map. */
  overflow-wrap: anywhere;
}
.journey-place--origin {
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink-soft);
}
/* The town a day trip reaches: a heading, because it is the day's destination, but a step below
   a stop's own name so the thread still reads as places visited. */
.journey-place--arrive {
  font-size: 1.02rem;
}
/* "Optional" beside the sunrise: a quiet pill, so it reads as a qualifier and never a warning. */
.journey-tag {
  display: inline-block;
  margin-top: 4px;
  padding: 1px 8px;
  border-radius: var(--radius-pill, 999px);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.03em;
}
.journey-tag--optional {
  color: var(--ink-soft);
  background: color-mix(in srgb, var(--ink-faint) 14%, transparent);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
.journey-figure {
  /* A CINEMATIC band, not a postcard. At 16:10 four photographs pushed a single day past 2500px
     on a phone and the itinerary itself fell below the fold — the complaint the redesign started
     from. A wide crop keeps the photograph unmistakably the second thing you see and gives back
     ~90px a stop. */
  margin: 9px 0 0;
  aspect-ratio: 2.2 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 6%, var(--surface));
}
.journey-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.journey-note {
  margin: 8px 0 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ink-soft);
}
.journey-meta {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}
.journey-rating {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  font-size: var(--fs-micro);
  color: var(--ink-soft);
}
.journey-rating .op-star {
  color: var(--gold);
}
/* Secondary by design: Atithi explains the journey, the map app navigates it. */
.journey-maps {
  display: inline-flex;
  gap: 1px;
  align-items: center;
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: none;
}
.journey-maps:hover,
.journey-maps:focus-visible {
  color: var(--violet-deep);
}
.journey-maps .icon svg {
  width: 14px;
  height: 14px;
}

/* --- travel: one muted line, no pill -------------------------------------------------------- */
.journey-leg-text {
  margin: 0;
  padding-top: 1px;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  align-items: baseline;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}
.journey-leg-drive {
  font-weight: 600;
}
.journey-leg-est {
  font-style: italic;
}

/* --- a wide screen is a different composition, not a stretched narrow one ------------------- */
@media (min-width: 720px) {
  .journey-node--stop .journey-body {
    display: grid;
    grid-template-columns: 1fr 208px;
    /* The fifth row is a spacer that absorbs whatever the photograph is taller than the text.
       Without it the last real row swallows the slack and the map link drifts to the bottom of
       the picture, a hand's width below the name it belongs to. */
    grid-template-areas:
      "time  figure"
      "place figure"
      "note  figure"
      "meta  figure"
      ".     figure";
    grid-template-rows: auto auto auto auto 1fr;
    align-content: start;
    column-gap: 18px;
  }
  .journey-node--stop .journey-time {
    grid-area: time;
  }
  .journey-node--stop .journey-place {
    grid-area: place;
    font-size: 1.2rem;
  }
  .journey-node--stop .journey-figure {
    grid-area: figure;
    margin: 0;
    align-self: start;
    aspect-ratio: 3 / 2;
  }
  .journey-node--stop .journey-note {
    grid-area: note;
  }
  /* NOT `align-self: end`. The figure spans all four rows, so pinning the actions to the bottom
     of that span left a hand's width of nothing between the place name and its own map link. */
  .journey-node--stop .journey-meta {
    grid-area: meta;
    align-self: start;
  }
  .journey-day-date {
    font-size: 1.42rem;
  }
}

.tracks-foot {
  margin: 10px 0 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------------------------------------------------------------------------------------------
 * Live conditions card — real Google Weather + Air Quality + Elevation/AMS.
 * ------------------------------------------------------------------------------------------- */

/* .cond-card is also in css/glass.css's paper list, which loads later and already won for these
   three properties. Restated in the same tokens rather than left as a contradicting dead rule. */
.cond-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.cond-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.cond-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.cond-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.cond-checking,
.cond-none {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
  font-style: italic;
}
.cond-tiles {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.cond-tile {
  flex: 1;
  min-width: 90px;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
}
.cond-val {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--ink, #12292d);
  line-height: 1.1;
}
.cond-lbl {
  font-size: var(--fs-small);
  color: var(--ink-soft);
  margin-top: 2px;
}
.cond-sub {
  font-size: 0.76rem;
  color: var(--ink-soft);
  margin-top: 1px;
}
.cond-tile.aqi-good .cond-val {
  color: var(--ok-ink);
}
.cond-tile.aqi-moderate .cond-val {
  color: var(--warn-ink);
}
.cond-tile.aqi-poor .cond-val {
  color: var(--danger-ink);
}
.cond-ams {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  margin-top: 10px;
  padding: 9px 11px;
  border-radius: 12px;
  border: 1px solid var(--warn-line);
  background: var(--warn-bg);
  font-size: var(--fs-small);
  color: var(--ink);
}
.cond-ams-ico .icon svg {
  width: 17px;
  height: 17px;
  color: var(--warn-ink);
}
.cond-foot {
  margin: 10px 0 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------------------------------------------------------------------------------------------
 * Proactive road advisory (guest-lifecycle P6) — the slim banner + the full status card.
 * ------------------------------------------------------------------------------------------- */

.road-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  padding: 9px 11px;
  border-radius: 12px;
  /* The semantic warn/danger pairs, not two loose hexes over a white that did not exist. Both
     tokens invert with the theme, so a closed-road advisory stays legible in dark mode instead of
     painting a near-white slab. (css/glass.css re-tints the DEFAULT banner ember and loads later;
     `.sev-closed` is two classes and still resolves here, which is why this one had to be fixed.) */
  border: 1px solid var(--warn-line);
  background: var(--warn-bg);
  animation: sheet-in var(--dur) var(--ease) both;
}
.road-banner.sev-closed {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
.road-banner-ico {
  flex: none;
  display: grid;
  place-items: center;
}
.road-banner-ico .icon svg {
  width: 18px;
  height: 18px;
  color: var(--warn-ink);
}
.road-banner.sev-closed .road-banner-ico .icon svg {
  color: var(--danger-ink);
}
.road-banner-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: left;
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  padding: 0;
}
.road-banner-route {
  font-weight: 700;
  font-size: var(--fs-small);
  color: var(--ink, #12292d);
}
.road-banner-note {
  font-size: 0.8rem;
  color: var(--ink-soft);
  /* Allow up to two lines so the actual delay ("…vs 3 hours 10 mins typical") is never cut off
     mid-number the way a single nowrap+ellipsis line did. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.road-banner-x {
  flex: none;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  padding: 4px;
  border-radius: 8px;
}
.road-banner-x .icon svg {
  width: 15px;
  height: 15px;
}

.road-card {
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  padding: 14px 15px;
  margin: 6px 0;
  box-shadow: var(--shadow-2);
  color: var(--ink);
}
.road-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.road-head .icon svg {
  width: 18px;
  height: 18px;
  color: var(--violet-deep);
}
.road-title {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 1.06rem;
  color: var(--ink, #12292d);
}
.road-rows {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.road-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--hairline);
}
.road-row:first-child {
  border-top: 0;
}
.road-status-dot {
  flex: none;
  width: 10px;
  height: 10px;
  margin-top: 5px;
  border-radius: 50%;
  /* The same three inks cards.css already paints its traffic-light dots with (.light::before), so
     an open road here and an open segment on a grounding card are the same green. --ok would be
     indigo in the light theme — see the note on .pref-check. */
  background: var(--ok-ink);
}
.road-row.sev-caution .road-status-dot {
  background: var(--warn-ink);
}
.road-row.sev-closed .road-status-dot {
  background: var(--danger-ink);
}
.road-row-body {
  flex: 1;
  min-width: 0;
}
.road-route {
  font-weight: 600;
  font-size: var(--fs-small);
  color: var(--ink, #12292d);
}
.road-note {
  font-size: 0.82rem;
  color: var(--ink-soft);
}
.road-badge {
  flex: none;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
  background: var(--ok-bg);
  color: var(--ok-ink);
}
.road-row.sev-caution .road-badge {
  background: var(--warn-bg);
  color: var(--warn-ink);
}
.road-row.sev-closed .road-badge {
  background: var(--danger-bg);
  color: var(--danger-ink);
}
.road-foot {
  margin: 10px 0 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------------------------------------------------------------------------------------------
 * Safety card — ember, not alarm-red. Calm and warm, still unmistakable.
 * ------------------------------------------------------------------------------------------- */

.safety-card {
  /* Mixed toward transparent rather than toward white: a white-mixed hairline is a light ring in
     both themes, which on the dark canvas made this the brightest-edged card on the page. The ember
     left rail — the part that actually identifies the card — is untouched. */
  border-color: color-mix(in srgb, var(--ember) 40%, transparent);
  border-left: 4px solid var(--ember);
}

.safety-card .card-head .icon {
  color: var(--ember);
}

/* The call rows inherit cards.css; only the emergency tone is re-hued, so the whole card stays
   ember rather than mixing this theme's warm red with the base palette's alarm red. The pill is
   deliberately a shade past --ember: white on the ember itself measures 4.47:1, and this is the
   one label in the app that a guest reads while panicking. */
.safety-call[data-tone="emergency"] {
  border-color: color-mix(in srgb, var(--ember) 45%, transparent);
}

.safety-call[data-tone="emergency"] .safety-call-cta {
  background: color-mix(in srgb, var(--ember) 88%, #000);
}

/* =============================================================================================
 * INTERACTION MODES — welcome (the hero) transforms into active (the conversation).
 *
 * The core fix for "it's static / it shows nothing": the moment the guest taps the mic or sends a
 * message, app.js flips #app[data-mode] to "active". The tall photo hero collapses to a slim
 * control band, the mic shrinks and rises to the top (still animating its listening/speaking
 * state), and the conversation — the guest's words and Atithi's, live — takes the stage. The app
 * visibly *moves while you talk*, so a human can watch the concierge work instead of trusting a
 * frozen orb.
 * ============================================================================================= */

/* Smooth every part of the transformation. */
#hero {
  transition: min-height 0.55s var(--ease-out-quart), padding 0.55s var(--ease-out-quart);
}
/*
 * The mic transitions its TRANSFORM only.
 *
 * `width`/`height` used to be in this list, and animating them here did not work — it left the mic
 * stuck at the wrong size for the rest of the session. orb.css sets `width: var(--orb-size)`, and
 * --orb-size is re-declared per mode on an ANCESTOR (#app, in css/glass.css). Transitioning a
 * length that resolves through a custom property changed higher up is the case Chromium does not
 * animate reliably: the transition is queued, no animation is registered, and the computed width
 * keeps the value it started from.
 *
 * Measured on a 390x844 phone in active mode: --orb-size resolved to 64px on #orb while the computed
 * width read 85.8px — the welcome size, `clamp(80px, 22cqi, 96px)` at that pane width — and it
 * stayed there. Forcing `transition: none` snapped it to 64px immediately, which is what confirmed
 * the transition rather than the cascade. The size is also read by #composer-toggle and .help-btn to
 * reserve their clearance either side of the mic (`calc(50% - var(--orb-size)/2 - ...)`, css/orb.css),
 * and those were computing against 64 while the sphere really occupied 86 — so the safety control
 * sat 11px closer to the mic than the rule that guards it intended.
 *
 * Dropping the two lengths means the mic changes size in one frame. Against the hero collapsing and
 * the greeting fading over 0.55s that is imperceptible, and a mic that is the right size instantly
 * beats one that animates to the wrong size permanently.
 */
#orb {
  transition: transform var(--dur) var(--ease-out-quart);
}
#greeting,
#greeting-sub,
#orb-hint,
#guest-strip {
  transition: opacity 0.35s var(--ease), max-height 0.5s var(--ease-out-quart),
    margin 0.5s var(--ease-out-quart);
}

/* --- WELCOME: hide the conversation log so the hero is clean (no cramped, top-faded strip). The
       dock stays pinned to the foot while the log is out. --- */
#app[data-mode="welcome"] #log-wrap {
  display: none;
}
#composer-dock {
  margin-top: auto;
}

/* --- ACTIVE: the hero becomes a compact control band; the conversation takes over. --- */
#app[data-mode="active"] #hero {
  min-height: 0;
  padding-bottom: var(--sp-3);
}
/* The greeting cluster folds away when the conversation starts. #guest-strip used to be in this list
   and is deliberately not: the stay row STAYS in active mode (css/glass.css restores and tightens it),
   because the transcript is exactly where a guest wonders whether the thing answering them knows
   their booking. It keeps its transition above, which is now unused in this direction — harmless, and
   it is what will animate the row if it is ever collapsed again. */
#app[data-mode="active"] #greeting,
#app[data-mode="active"] #greeting-sub,
#app[data-mode="active"] #orb-hint {
  opacity: 0;
  max-height: 0;
  margin-top: 0;
  margin-bottom: 0;
  padding: 0;
  overflow: hidden;
  pointer-events: none;
}
#app[data-mode="active"] #hero-copy {
  margin-top: var(--sp-2);
  padding-top: 0;
}

/* The status line drops its uppercase display treatment in active mode: while a conversation is
   running it is live status ("Listening…", "Thinking…"), not a headline inviting the first tap.
   The mic's own size per mode lives with the rest of the foot, in css/glass.css. */
#app[data-mode="active"] #orb-label {
  margin: 0;
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: var(--fs-micro);
  color: var(--ink-soft);
}
/* Nudge the photo so the sliver still framed behind the compact band is sky, not foreground. */
#app[data-mode="active"] .hero-img {
  background-position: center 28%;
}
#app[data-mode="active"] #hero::after {
  opacity: 0.03;
}

/* =============================================================================================
 * MARKETPLACE — rich visual listings + a visible booking journey. A human trusts what they can
 * see: the photo, the price, the rating, "checking availability…", "held for you", the amount, a
 * reference, and a clear next step. So each listing is a real card and each booking is a flow.
 * ============================================================================================= */

.op-list {
  grid-template-columns: 1fr; /* one rich card per row */
  gap: var(--sp-4);
}

/* Radius and the box beneath it now match `.card` (cards.css) exactly — 16px, not the 22px this
   used to override to. `.op-card` is a receipt too (a listing with a real photo and a live booking
   journey instead of a status pill), and a marketplace card sitting at a visibly rounder corner
   than every grounding card beside it in the same transcript read as a different object rather
   than a different tone of the same one. Only radius/padding/overflow are set here — the box
   itself (background, border, shadow) is cards.css's `.op-card` rule; this file layers the
   media-first layout on top of it. */
.op-card {
  padding: 0;
  overflow: hidden;
  border-radius: var(--radius);
}

/* Image header — photo over a warm gradient underlay, price + rating floated on top. */
.op-media {
  position: relative;
  height: 158px;
  background: linear-gradient(135deg, var(--violet-lit), var(--indigo));
  overflow: hidden;
}
.op-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.op-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.18) 0%, rgba(0, 0, 0, 0) 34%,
    rgba(0, 0, 0, 0) 60%, rgba(0, 0, 0, 0.36) 100%);
}
.op-price-tag {
  position: absolute;
  left: var(--sp-3);
  bottom: var(--sp-3);
  z-index: 1;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-small);
  font-weight: 700;
  /* One flat indigo fill and the ivory ink, like every other filled object in the app. The
     lavender->indigo gradient it replaces was the last two-stop fill on a card. */
  color: var(--on-indigo);
  background: var(--indigo);
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.5);
}
.op-rating {
  position: absolute;
  right: var(--sp-3);
  top: var(--sp-3);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--ink);
  background: var(--glass-2);
  /* var(--blur) (12px), not its own 10px literal — a floating rating badge is exactly the small
     pane the shared blur token exists for, and .mini-rating just below carried a THIRD value
     (8px) for what is visually the same object on a different card. Both now read one token. */
  -webkit-backdrop-filter: blur(var(--blur));
  backdrop-filter: blur(var(--blur));
}
.op-star {
  color: var(--violet);
}
.op-reviews {
  color: var(--ink-faint);
  font-weight: 500;
}

.op-body {
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.op-amenities {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin-top: 2px;
}
.op-amenity {
  font-size: var(--fs-micro);
  color: var(--indigo-ink);
  background: color-mix(in srgb, var(--indigo) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--indigo) 24%, transparent);
  padding: 2px var(--sp-2);
  border-radius: var(--radius-pill);
}

/* The primary action, full-width inside the card. (`display:*` would override [hidden], so the
   booking flow's `bookBtn.hidden = true` needs this explicit re-hide to actually take.) */
.op-book[hidden] {
  display: none;
}
.op-book {
  margin: 0 var(--sp-4) var(--sp-4);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 46px;
  border-radius: var(--radius);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 700;
  cursor: pointer;
  /* Fill, ink and shadow come from the shared primary-action rule in css/glass.css (.op-book is in
     its selector list). The turquoise-glow gradient that was here described the retired palette and
     had already been overridden for a release. */
}
.op-book .icon {
  width: 17px;
  height: 17px;
}
.op-book:hover {
  filter: brightness(1.05);
}

/* The booking journey — the visible flow with progress, amount and a next step. `display:flex`
   would otherwise override the [hidden] attribute's display:none, so re-hide it explicitly. */
.op-journey[hidden] {
  display: none;
}
.op-journey {
  margin: 0 var(--sp-4) var(--sp-4);
  padding: var(--sp-4);
  border-radius: var(--radius);
  background: var(--glass-2);
  border: 1px solid var(--glass-line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.op-step {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}
.op-step-dot {
  width: 12px;
  height: 12px;
  margin-top: 3px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 2px solid var(--violet);
  background: transparent;
}
.op-step[data-state="active"] .op-step-dot {
  animation: op-pulse 1s ease-in-out infinite;
}
.op-step[data-state="done"] .op-step-dot {
  background: var(--violet);
}
@keyframes op-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--violet-glow); }
  50% { box-shadow: 0 0 0 6px transparent; }
}
.op-step-label {
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}
.op-step-sub {
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}

.op-held {
  border-top: 1px dashed var(--hairline);
  padding-top: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  animation: bubble-in var(--dur) var(--ease) both;
}
.op-held-head {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.op-held-check {
  width: 22px;
  height: 22px;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  background: var(--indigo);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}
.op-held-title {
  font-weight: 700;
  color: var(--ink);
}
.op-held-amount {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1;
}
.op-held-ref {
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.op-held-ref b {
  color: var(--violet-deep);
  font-variant-numeric: tabular-nums;
}
.op-held-note {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}
.op-held .op-call {
  margin-top: var(--sp-1);
}

/* =============================================================================================
 * IN-CONVERSATION OPTIONS CAROUSEL (js/booking.js) — the visual booking experience, inline in the
 * chat, when the guest asks to book/see stays, cabs, activities or a guide.
 * ============================================================================================= */

.supply-card {
  width: 100%;
  border-radius: var(--radius-lg);
  padding: var(--sp-3) 0 var(--sp-3);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  /* An indigo hairline, not a white one: a white edge on cream reads as a highlight ring around
     every card, which is what made this lane the loudest object in the transcript. The 50px aqua
     glow went with it — a card is paper lying on paper, and paper does not emit light. */
  border: 1px solid var(--glass-line);
  box-shadow: var(--shadow-2);
  animation: bubble-in var(--dur) var(--ease) both;
}
.supply-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-3);
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--fs-lead);
  color: var(--ink);
}
.supply-head .icon {
  color: var(--violet-deep);
  width: 20px;
  height: 20px;
}
.supply-count {
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--violet-deep);
  /* Was the retired alpine turquoise, and unlike .prov nothing overrode it — so the "4 options"
     badge at the head of the booking carousel was a live glacier-green chip on an indigo page. */
  background: var(--lavender);
  border: 1px solid var(--glass-line);
  border-radius: var(--radius-pill);
  padding: 2px 10px;
}
.supply-scroller {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  padding: 0 var(--sp-4) var(--sp-2);
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  align-items: flex-start;
}
.supply-scroller::-webkit-scrollbar {
  display: none;
}
.supply-note {
  margin: 0;
  padding: var(--sp-1) var(--sp-4) 0;
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}

.mini-card {
  flex: 0 0 234px;
  width: 234px;
  scroll-snap-align: start;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--glass-line);
  /* --shadow-2, not a hand-rolled teal-tinted lift: rgba(18,41,45,…) is the retired alpine palette,
     so this card cast a green-grey shadow on an indigo-shadowed page. Matches --glass-line above:
     together they are exactly what glass.css's shared "PANE" rule paints .card/.op-card with too
     (border/shadow there win over cards.css's OWN .card declaration at equal specificity, since
     glass.css loads later) — .mini-card is not in that shared rule's selector list, so it states
     the same two values directly rather than relying on it, and already matches on both. */
  box-shadow: var(--shadow-2);
  display: flex;
  flex-direction: column;
}
.mini-media {
  position: relative;
  height: 120px;
  background: linear-gradient(135deg, var(--violet-lit), var(--indigo));
  overflow: hidden;
}
.mini-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.mini-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.32) 100%);
}
.mini-price {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  z-index: 1;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--on-indigo);
  background: var(--indigo);
}
.mini-rating {
  position: absolute;
  right: var(--sp-2);
  top: var(--sp-2);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--ink);
  background: var(--glass-2);
  /* var(--blur) — see .op-rating's note; the same floating-badge role on a smaller card. */
  -webkit-backdrop-filter: blur(var(--blur));
  backdrop-filter: blur(var(--blur));
}
.mini-body {
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  flex: 1;
}
.mini-name {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--fs-body);
  color: var(--ink);
  line-height: 1.15;
}
.mini-meta {
  font-size: var(--fs-micro);
  color: var(--ink-soft);
  margin-top: 2px;
}
/* The compact booking button, inside the inline mini card in the transcript.
 *
 * It was 40px, which is under Apple's 44pt floor and 8px under the --tap (48px) minimum every other
 * control in this app holds to — and it is a real control, in the conversation, that commits a guest
 * to a booking. "The card is small" is not a reason for the button inside it to be the one target a
 * thumb can miss; the card gives up 8px instead. --tap so it can never drift from the house floor
 * again. */
.mini-book {
  margin: 0 var(--sp-3) var(--sp-3);
  min-height: calc(var(--tap) * var(--type-scale));
  font-size: var(--fs-small);
}
.mini-card .op-journey {
  margin: 0 var(--sp-3) var(--sp-3);
  padding: var(--sp-3);
}
.mini-card .op-held-amount {
  font-size: 20px;
}

/* Price/room card photo header — the room quote, seen not just read. */
.price-media {
  position: relative;
  height: 132px;
  background: linear-gradient(135deg, var(--violet-lit), var(--indigo));
  overflow: hidden;
}
.price-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.price-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, 0.22) 100%);
}

/* Real Google Places cards (find_places) — reuse the mini-card carousel, add open-now + Maps. */
.place-badges {
  margin: 2px 0 4px;
}
.place-open,
.place-closed {
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-micro);
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
}
.place-open {
  color: var(--indigo-ink);
  background: color-mix(in srgb, var(--indigo) 15%, transparent);
}
.place-closed {
  color: var(--ink-faint);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
.place-maps {
  margin: 0 var(--sp-3) var(--sp-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  min-height: 38px;
  border-radius: var(--radius);
  border: 1px solid rgba(18, 41, 45, 0.14);
  background: var(--glass-2);
  color: var(--violet-deep);
  font-size: var(--fs-small);
  font-weight: 600;
  text-decoration: none;
}
.place-maps .icon {
  width: 15px;
  height: 15px;
}
.place-maps:hover {
  background: rgba(255, 255, 255, 0.92);
}

/* Real-places marketplace bits: the Maps action row, the "Sample" tag on cabs/guides, loading. */
.op-actions-row {
  margin: 0 var(--sp-4) var(--sp-3);
  display: flex;
}
.op-actions-row .place-maps {
  margin: 0;
  flex: 1;
}
.op-source {
  font-size: var(--fs-micro);
  font-weight: 600;
  color: var(--violet-deep);
}
.op-sample-tag {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  border: 1px solid var(--hairline);
  padding: 2px 9px;
  border-radius: var(--radius-pill);
}
.op-sample-note {
  margin: calc(-1 * var(--sp-1)) 0 var(--sp-2);
  font-size: var(--fs-micro);
  color: var(--ink-faint);
}
.op-loading {
  padding: var(--sp-5) var(--sp-4);
  color: var(--ink-soft);
  font-size: var(--fs-small);
}

/* -------------------------------------------------------------------------------------------
 * "Your requests" — what the guest asked for, and where each one stands (plan §9.2).
 * ----------------------------------------------------------------------------------------- */

.req-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.req-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline);
}

.req-item:last-child {
  padding-bottom: 0;
  border-bottom: none;
}

.req-item-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.req-item-what {
  font-size: var(--fs-small);
  color: var(--ink, #12292d);
}

/* The manager's own words to the guest. Shown in full rather than clipped — this is the answer they
   were waiting for, and truncating it would be truncating the point of the whole feature. */
.req-item-note {
  font-size: var(--fs-small);
  color: var(--ink-soft);
  font-style: italic;
}

.req-item-status {
  flex-shrink: 0;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

/* Three tones, not eight. A guest needs "waiting / good / no" — the schema's eight statuses are for
   the staff queue, and colouring each one differently here would be noise dressed as information. */
.req-item-status[data-tone="wait"] {
  color: var(--ink-soft);
}

.req-item-status[data-tone="ok"] {
  /* Was a hardcoded green independent of the palette (status token cleanup) — now the same
     ok/danger ink+wash pair the grounding cards and road chips use, so a repaint of the live
     palette recolours this too instead of drifting out of sync with it. */
  color: var(--ok-ink);
  background: var(--ok-bg);
}

.req-item-status[data-tone="no"] {
  color: var(--danger-ink);
  background: var(--danger-bg);
}

.req-item-status[data-tone="off"] {
  color: var(--ink-faint, var(--ink-soft));
}

/* The answer landing in the conversation (plan §9.3) — the visible payoff of the whole plan, so it
   is marked as the hotel speaking rather than looking like another of our own cards. */
.request-answer {
  border-left: 3px solid var(--violet-deep);
}

/* =============================================================================================
 * THE DATE + TIME PICKER — js/datetime.js, shared by the pickup card and the front-desk request.
 *
 * Both cards used to ask for this as one free-text box. These are native `type="date"` /
 * `type="time"` inputs, so the control the guest gets is the OS picker on a phone rather than a
 * text keyboard — the point of the change, and the reason the box is styled to match `.cab-input`
 * / `.pref-note` rather than being given a look of its own.
 * ============================================================================================= */
.dt-row {
  /* A GRID that folds, not a flex row.
   *
   * `min-width: 0` does not rescue a native date input: its value is rendered by internal
   * shadow-DOM fields (dd/mm/yyyy plus the picker button) that refuse to compress below their own
   * intrinsic width, so a two-up flex row silently forced the whole card 110px wider than the pane
   * and the guest got a horizontally scrolling card. `auto-fit` + `minmax` asks for two columns
   * only while both can hold their real minimum, and drops to one stacked column below that —
   * which is the behaviour the control needs on the narrowest phones this app targets. */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 8px;
  margin-top: 10px;
}
.dt-field {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.dt-label {
  font-size: var(--fs-tiny, 0.72rem);
  font-weight: 600;
  color: var(--ink-soft);
  letter-spacing: 0.02em;
}
.dt-input {
  width: 100%;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
}
.dt-input:focus {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
/* Safari/iOS gives a date input a taller intrinsic box than a text input and centres its value
   oddly; pinning the height keeps the pair level with the fields above them. */
.dt-input::-webkit-date-and-time-value {
  text-align: left;
  min-height: 1.2em;
}
/* The calendar/clock glyph is near-black by default and reads as a third, heavier element in a card
   built out of hairlines. Tint it to the same soft ink as the label above it. */
.dt-input::-webkit-calendar-picker-indicator {
  opacity: 0.55;
  cursor: pointer;
}
.dt-input::-webkit-calendar-picker-indicator:hover {
  opacity: 0.85;
}
/* The request card's optional-time line, between the note and the picker. */
.pref-when-hint {
  margin: 10px 0 0;
  font-size: var(--fs-tiny, 0.72rem);
  color: var(--ink-soft);
}
/* The note is a textarea now (service_request.js) — it inherits .pref-note's box, and only needs
   the two things a textarea does not get from an input. */
.pref-note-multi {
  resize: vertical;
  line-height: 1.45;
}

/* =============================================================================================
 * "HOW IS YOUR STAY?" — the rating scale (js/feedback.js).
 *
 * A SCALE, not five toggles: tapping the fourth star lights the first four, because that is what a
 * rating means and lighting only the tapped one reads as five unrelated options. The word for the
 * chosen level is spelled out under it — a star count alone is the kind of thing every guest reads
 * slightly differently, and the word is what the front desk will see on the queue.
 * ============================================================================================= */
.fb-scale {
  display: flex;
  gap: 6px;
  margin-top: 10px;
}
.fb-star {
  flex: 1;
  min-width: 0;
  /* A real tap target. These sit close together, and a mis-tap on a rating is a wrong number sent
     to the hotel rather than a cosmetic slip. */
  min-height: var(--tap);
  padding: 6px 0;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  cursor: pointer;
  transition: background var(--dur, 160ms) ease, border-color var(--dur, 160ms) ease;
}
.fb-star-glyph {
  font-size: 1.15rem;
  line-height: 1;
  /* The empty state is a HOLLOW star, so an unrated scale never looks like a rating of five. */
  opacity: 0.35;
}
.fb-star.is-on {
  background: var(--lavender);
  border-color: var(--violet-deep);
}
.fb-star.is-on .fb-star-glyph {
  opacity: 1;
  color: var(--violet-deep);
}
.fb-star:focus-visible {
  outline: none;
  border-color: var(--glass-line-strong);
  box-shadow: var(--focus-ring);
}
.fb-chosen {
  margin: 6px 0 0;
  min-height: 1.2em; /* reserved, so the card does not jump the first time a rating is picked */
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}
.fb-hint {
  margin: 10px 0 0;
  font-size: var(--fs-tiny, 0.72rem);
  color: var(--ink-soft);
}

/* =============================================================================================
 * THE REQUEST TIMELINE — js/my_requests.js requestTimelineCard.
 *
 * Reuses the pickup card's `.cab-steps` / `.cab-step` ladder rather than growing a second one: the
 * two say the same kind of thing (where a thing the guest asked for has got to), and a guest who
 * learned to read one should not have to learn another. Only the "you are here" marking is new.
 * ============================================================================================= */
.request-timeline .cab-steps {
  margin-top: 2px;
}
/* The rung the request is ACTUALLY on, as opposed to the ones behind it. Without this every
   completed rung looks identical and the card says "sent, seen, answered" with no indication that
   only the first has happened. */
.request-timeline .cab-step[data-current="true"] {
  font-weight: 600;
  color: var(--ink);
}
.request-timeline .cab-step[data-current="true"] .cab-dot {
  background: var(--lavender);
  border-color: var(--violet-deep);
  color: var(--violet-deep);
}
.request-timeline .pref-sub {
  margin-top: 8px;
}
