/* Spain trip — mobile-first stylesheet. */

:root {
  --clay:      #8c3b21;
  --clay-soft: #b8532e;
  --saffron:   #e0a12a;
  --sea:       #2c4a52;
  --olive:     #6b7a4a;
  /* Your own position. Deliberately outside the warm palette — where you are
     standing is not one of the trip's pins and should not read as one. */
  --gps:       #1f6feb;

  --paper:     #fdf8f1;
  --card:      #ffffff;
  --ink:       #2a2320;
  --ink-soft:  #6d6157;
  --line:      #e8ded1;
  --shadow:    0 1px 2px rgba(70,45,30,.06), 0 8px 24px rgba(70,45,30,.07);
  --radius:    14px;
  --tabbar-h:  62px;
  /* How much screen lies BELOW the window. The shell measures it and writes
     it here; everything else reads --sab. See views/layout.php. */
  --below: 0px;
  /* What the home indicator's keep-out actually costs us: the part of it
     that falls inside the window, and nothing for the part that is already
     outside. Window 762 of an 812 screen leaves 50 px below, which covers
     the 34 px indicator — nothing to pay. Window 812 leaves nothing below,
     so the full 34, exactly as a native app has.

     iOS hands this app one or the other with nothing to choose between
     them, which is why this is a sum and not a constant: photographed off
     the phone, the tab labels ended 49 px above the glass on Live and 2 px
     above it on Places. */
  /* How far the tab labels stand off the floor of the window.
     Dropping viewport-fit=cover moved the floor to the bottom of the glass,
     so they landed on it — "move them all up a tad. They are a bit low."
     16px is a tad: clear of the white home-indicator line, which takes the
     bottom thirteen, and still reading as the bottom of the screen rather
     than a bar with a gap under it. One number if it wants to be 10 or 22. */
  --tab-lift: 12px;
  /* Plus whatever part of a reported inset falls inside the window — zero
     now that iOS is doing the insetting, and capped at 10 if it ever goes
     back to reporting one, so it can never grow into a band again. */
  --sab: calc(var(--tab-lift) +
              min(10px, max(0px, calc(env(safe-area-inset-bottom) - var(--below)))));

  /* The trail's green, which is also the one the check-out line wears. */
  --green: #0f9d58;

  /* The one colour on this site that means stop: the line round a historic
     centre you must not drive into. Deliberately not the clay — clay is the
     brand and it is on every heading, so a warning wearing it would read as
     decoration. */
  --red: #c62828;

}


@media (prefers-color-scheme: dark) {
  :root {
    --clay:      #d9714b;
    --clay-soft: #e08a63;
    --saffron:   #edb44c;
    --sea:       #8fb3bd;
    --olive:     #a3b47a;
    --gps:       #58a6ff;
    --green:     #4ec97f;
    --red:       #ff6b6b;

    --paper:     #191512;
    --card:      #221d19;
    --ink:       #f0e8de;
    --ink-soft:  #a89a8c;
    --line:      #3a322b;
    --shadow:    0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
  }
}

* { box-sizing: border-box; }

/* An author `display` rule outranks the browser's own [hidden] rule, so
   .substepper{display:flex} was showing bars that JS had hidden. */
[hidden] { display: none !important; }

/* The canvas — what shows outside the page's own box. An iOS home-screen
   app paints the strip it keeps below the window with this, and that strip
   sits directly under the tab bar, so it wears the bar's colour and reads as
   part of it rather than as a hole. It is also what an overscroll bounce
   shows. body keeps the page colour, below. */
/* A definite height here is what lets a page say "as tall as the window" in
   percent instead of in vh. On a home-screen app those are not the same
   number: vh and dvh are worth what iOS says a viewport is, while a
   percentage resolves against the initial containing block, which is the
   window the tab bar is pinned to. The two views that fill the screen were
   sized in dvh and both came up short of the tabs. */
html { -webkit-text-size-adjust: 100%; background: var(--card); height: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--clay); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { line-height: 1.25; margin: 0 0 .5rem; font-weight: 650; letter-spacing: -.01em; }
h1 { font-size: 1.5rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }

/* ---------- app chrome ---------- */

.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: .7rem 1rem;
  background: var(--clay);
  color: #fff;
  position: sticky; top: 0; z-index: 50;
  padding-top: calc(.7rem + env(safe-area-inset-top));
}
.brand { display: flex; align-items: center; gap: .5rem; color: #fff; font-weight: 700; }
.brand:hover { text-decoration: none; }
.brand-flag { font-size: 1.2rem; }
.brand-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45vw; }

.topnav { display: none; margin-left: auto; gap: .25rem; }
.topnav a {
  color: rgba(255,255,255,.85); padding: .4rem .75rem; border-radius: 999px; font-size: .92rem;
}
.topnav a:hover { background: rgba(255,255,255,.14); color: #fff; text-decoration: none; }
.topnav a.is-active { background: rgba(255,255,255,.22); color: #fff; font-weight: 600; }

.topbar-account {
  margin-left: auto;
  width: 32px; height: 32px; flex: 0 0 32px;
  display: grid; place-items: center;
  border-radius: 50%; background: rgba(255,255,255,.2); color: #fff;
  font-weight: 700; font-size: .85rem;
}
.topbar-account:hover { text-decoration: none; background: rgba(255,255,255,.32); }

.page {
  max-width: 860px; margin: 0 auto;
  padding: 1rem 1rem calc(var(--tabbar-h) + var(--sab) + 1.5rem);
}
/* The map page carries a side panel, so it gets more room. Declared after
   .page so it wins at equal specificity. */
.page-wide { max-width: 1240px; }

/* Bottom tab bar (mobile) */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  /* Column per tab, counted by the browser — a hardcoded repeat() went stale
     the moment Places was added as a sixth. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  /* Solid, not a 92% blur.

     The bar never moves: the phone reports its top at 699 on every tab. But
     on the three pages that fill the screen and stop — Live, Calendar, Map —
     there is no content behind it to blur, so a near-transparent bar on a
     page the same colour dissolves, and what is left is a row of labels
     floating in a hundred-odd pixels of nothing. "The bottom tabs move up
     and create empty space under them." They do not move; they stop looking
     like a bar.

     The colour is the page's, not the card's, and that is the whole fix for
     the band under it. iOS paints the 50 px below the window — screen 812,
     window 762 — with the BODY's background, whatever we put in the strip
     ourselves: measured off the phone at 11:12, white (255,255,255) above
     CSS 762 and paper (252,248,242) below it, a line you could see across
     the screen. Nothing can be drawn or placed down there; the one thing
     that reaches it is the page's own background colour. So the bar wears
     that colour too, and the bar and the strip are one band from the line
     along its top to the bottom edge of the glass. */
  /* Over content, with the page's own colour where there is none behind it.
     Both halves matter: the blur is what makes it read as a bar rather than
     a band, and the paper is what makes it continuous with the 50 px below
     the window, which iOS paints with the body's colour and nothing else. */
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(1.5) blur(14px);
  border-top: 1px solid var(--line);
  padding-bottom: var(--sab);
}
/* The skirt.

   Measured on the phone: window 762, screen 812, and `100vh` is 812 while
   `100dvh` and the initial containing block are both 762. So iOS pins
   everything fixed to a 762 px window and keeps the last 50 px of glass for
   itself — but the web view still paints there, because a viewport unit
   knows about all 812.

   Both pages that fill the screen end exactly at 762, which is correct and
   still looked wrong: the bar stopped short of the bottom edge with a flat
   band under it. The other tabs scroll, so their content is cut off by the
   bar and reads as "more below" instead.

   So the bar drops a skirt of its own colour over the difference. Where the
   window is the screen the difference is zero and nothing is drawn; where
   dvh is not supported at all the calc is invalid and nothing is drawn.
   Nothing is ever clickable down there — it is colour and nothing else. */
.tabbar::after {
  content: ''; position: absolute; top: 100%; left: 0; right: 0;
  height: max(0px, calc(100vh - 100dvh));
  background: var(--paper);
  pointer-events: none;
}
/* The display's corners are round, and when the window runs to the bottom of
   the screen the bar sits on the glass — where the first and last labels ran
   into them: "oday" and "Bookin" in the photograph. A side gutter keeps them
   clear, and seven tabs still fit: the widest label is 48 px in a 51 px slot. */
.tabbar { padding-left: max(8px, env(safe-area-inset-left));
          padding-right: max(8px, env(safe-area-inset-right)); }
.tabbar a {
  /* flex-end, not center. The label is the thing that has to sit at a known
     distance off the floor of the window; everything else stacks up from it.
     Centring the pair in the bar threw away pixels at the bottom that the
     window had already made scarce.

     The gap is what lifts the glyph off its label — "just move the icons up
     a tad" — rather than moving the whole bar, which would take the labels
     with it. */
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 6px; padding: .45rem 0 0; min-height: var(--tabbar-h);
  color: var(--ink-soft); font-size: .68rem; font-weight: 600;
}
.tabbar a:hover { text-decoration: none; }
/* The tab you are on.

   Colour alone did not do it. The icons are emoji, which carry their own
   colour and ignore `color` entirely, so the only thing that changed between
   the active tab and the other six was the weight of a 0.68rem label — and
   on a phone held at arm's length in sunlight that is no signal at all:
   "find a way to highlight the tab I'm in too".

   So the glyph gets a tinted disc behind it. That is a shape, visible before
   you have read anything, and it sits in the gap the bar already had rather
   than making it taller. The label goes clay and heavy underneath it. */
.tabbar a.is-active { color: var(--clay); font-weight: 800; }
.tabbar a.is-active .tab-icon {
  background: color-mix(in srgb, var(--clay) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--clay) 32%, transparent);
}
.tab-icon {
  font-size: 1.2rem; line-height: 1;
  /* Sized and centred whether it is lit or not, so lighting it moves nothing
     — the row of glyphs has to stay on one baseline across all seven. */
  display: grid; place-items: center;
  width: 1.95rem; height: 1.95rem; border-radius: 999px;
  background: transparent;
}

/* A wrapped label would make the whole bar two lines taller, so labels are
   held on one line and ellipsised instead. */
.tab-label {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%; padding: 0 2px;
}

@media (min-width: 720px) {
  .topnav { display: flex; }
  .topbar-account { margin-left: 0; }
  .tabbar { display: none; }
  .page { padding-bottom: 3rem; }
}

/* Adding what you are standing outside: a coffee shop goes on the town's own
   list of things to see, so it becomes a row here, a pin on the map and
   something to tick off, with no special case for having been added late.
   It lives at the foot of the live bar and scrolls with it. */
.addplace { margin-top: .5rem; border-top: 1px dashed var(--line); padding-top: .5rem; }
.addplace[hidden] { display: none; }
.addplace-open {
  list-style: none; cursor: pointer;
  font-size: .82rem; font-weight: 700; color: var(--clay);
  padding: .25rem 0;
}
.addplace-open::-webkit-details-marker { display: none; }
.addplace-form { display: flex; flex-direction: column; gap: .4rem; margin-top: .4rem; }
.addplace-form input[type="text"] {
  /* 16px, like every other field on the site — anything smaller and iOS
     zooms the whole page in when it takes focus and does not zoom back out.
     That is the "when the keyboard goes up then back down the page is now
     much bigger and I can scroll left and right" bug: not the keyboard, the
     fourteen-and-a-half pixel text this box used to be set in. */
  font: inherit; font-size: 16px; padding: .45rem .55rem;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--paper); color: var(--ink);
}
/* Nothing in here may be wider than the phone.
 *
 * "When the keyboard goes up then back down the page is now much bigger and
 * I can scroll left and right." The live page sizes itself to its content —
 * that is why .page carries margin: 0 — and anything with a min-content
 * width larger than the screen drags the whole column out with it. A select
 * full of town names and a suggestion carrying a full Spanish address are
 * both exactly that, and a keyboard dismissal is just when the browser
 * re-measures and notices.
 */
.addplace, .addplace-form, .addplace-find { min-width: 0; max-width: 100%; }
.addplace-find { position: relative; }
.addplace-find input { width: 100%; box-sizing: border-box; }
.addplace-form input[type="text"],
.addplace-form select { width: 100%; max-width: 100%; box-sizing: border-box; }
.addplace-hits { max-width: 100%; }
.addplace-hit { min-width: 0; }
.addplace-hit strong, .addplace-hit span { min-width: 0; overflow-wrap: anywhere; }
.addplace-said { overflow-wrap: anywhere; }
.addplace-hits {
  list-style: none; margin: .2rem 0 0; padding: 0;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); overflow: hidden;
  max-height: 11rem; overflow-y: auto;
}
.addplace-hit {
  display: flex; flex-direction: column; gap: .1rem;
  padding: .4rem .55rem; cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.addplace-hit:last-child { border-bottom: 0; }
/* Not a result: the line that says there are none near you, or that the ones
   below are a long way off. Nothing to tap, so nothing that looks tappable. */
.addplace-note {
  padding: .45rem .55rem; cursor: default;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--saffron) 14%, transparent);
  line-height: 1.35;
}
.addplace-note:last-child { border-bottom: 0; }
.addplace-hit:hover, .addplace-hit:focus { background: color-mix(in srgb, var(--clay) 10%, transparent); }
.addplace-hit strong { font-size: .9rem; }
.addplace-form select {
  font: inherit; font-size: 16px; padding: .4rem .5rem;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--paper); color: var(--ink);
}
.addplace-undo { display: inline; margin-left: .4rem; }
.addplace-undo .as-link,
.ideas-drop .as-link {
  font: inherit; font-size: .82rem; font-weight: 700;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--clay); text-decoration: underline;
}
.ideas-drop { margin-left: auto; }
.ideas-drop form { display: inline; }
.addplace-at { margin: 0; }
.addplace-form .btn { align-self: flex-start; }
.addplace-said { margin: .5rem .8rem 0; }

/* The two hours that decide today: the room you have to be out of, and the
   room you cannot get into yet. Bold and green because they are the only
   lines on a booking with a clock on them, and each only ever shows on the
   day itself — "on checkout date I want you to bold and green the checkout
   info", and then "I want the check in time in green for the next hotel
   too". One rule, because they are the same kind of fact. */
.ev-checkout, .ev-checkin {
  margin-top: .2rem; font-weight: 800; font-size: .86rem;
  color: var(--green, #0f9d58);
}

/* The room card.

   What the desk hands you on a bit of card: the room number, the keypad on
   the room, the code on the street door, the wifi key. The values are set
   large and in the mono face, because the moment they matter is at a door at
   midnight with one hand free — "I want a way to add room info myself when I
   have my room number, room code, hotel door code."

   The form that sets them folds away under the values once there are any:
   typing them in happens once, reading them happens all week. */
.roomcard { border-left: 4px solid var(--line); }
.roomcard.is-set { border-left-color: var(--clay); }
.roomcard-said { margin: 0 0 .6rem; }
.roomcard-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: .55rem .9rem;
}
.roomcard-one { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.roomcard-what { font-size: .72rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; }
/* Big enough to read at arm's length in a dark doorway, and wrapping rather
   than running off the side of a phone. */
.roomcard-val {
  font-size: 1.5rem; font-weight: 750; line-height: 1.15;
  letter-spacing: .02em; overflow-wrap: anywhere;
}
.roomcard-note { margin: .6rem 0 0; }
.roomcard-when { margin: .5rem 0 0; }
.roomcard-edit { margin-top: .7rem; }
.roomcard-edit[open] { margin-top: .9rem; }
.roomcard-open {
  cursor: pointer; list-style: none; font-size: .86rem; font-weight: 650;
  color: var(--clay); display: flex; align-items: center; gap: .4rem;
}
.roomcard-open::-webkit-details-marker { display: none; }
.roomcard-form { margin-top: .7rem; display: grid; gap: .55rem; }
.roomcard-field { display: flex; flex-direction: column; gap: .2rem; }
.roomcard-label { font-size: .78rem; color: var(--ink-soft); }
.roomcard-field input {
  width: 100%; padding: .5rem .6rem; font-size: 1rem;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--paper); color: var(--ink);
}
.roomcard-why { margin: .1rem 0 .2rem; }

/* The same facts, one line, on a card that is about something else. */
.ev-room {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .6rem;
  margin-top: .35rem; font-size: .84rem;
}
.ev-room-one { white-space: nowrap; }
.day-stay-room {
  display: flex; flex-wrap: wrap; gap: .25rem .6rem;
  margin-top: .3rem; font-size: .84rem;
}

/* Where to leave the car, on a card rather than only on the stay's own page
   — "the 'drive to it' for parking near the hotel should be an available
   link on the hotel cards. In the itinerary and places etc." One line that
   wraps, because on a phone the name alone can take most of the width, and
   the link must never be the thing that falls off the end. */
.ev-park {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .45rem;
  margin-top: .35rem; font-size: .83rem; line-height: 1.35;
}
.ev-park-p { flex: 0 0 auto; }
.ev-park-name { font-weight: 700; }
.ev-park-walk { color: var(--ink-soft); }
/* Last in the line and first in weight: the whole point of the row. */
.ev-park-go {
  margin-left: auto; flex: 0 0 auto;
  font-weight: 700; white-space: nowrap;
}
.ev-park-why {
  margin: .2rem 0 0; font-size: .78rem; line-height: 1.35;
  color: var(--red, #c62828);
}

/* OPEN IN MAPS, on the row rather than two taps inside it — "In itinerary,
   these rows with large descriptions should have 'open in maps' link". Right
   where the car park's own link sits on the rows that have one, and styled
   the same, because they are the same kind of thing: the way out of this page
   and into the phone's map. Rendered only for a row that IS somewhere — see
   ev_map_url(), which stays quiet on a drive or a flight. */
.ev-go { margin-top: .3rem; text-align: right; line-height: 1.2; }
.ev-go > a {
  font-size: .78rem; font-weight: 700; white-space: nowrap;
  /* Padding rather than a taller line: it makes the thing you press about
     28 px high and gives it slack on the left, without putting that height
     into every row of a fourteen-day list. */
  display: inline-block; padding: .25rem 0 .25rem .6rem;
}

/* On the Places grid the cell is the grid child and the card fills it, so two
   cards in a row come out the same height. The card keeps its own name so the
   map's selectors still find it. */
.place-cell { display: flex; flex-direction: column; min-width: 0; }
.place-cell > .place-card { flex: 1 1 auto; }

/* A booking that is over folds to one line. The reference is why this tab
   exists, so nothing is thrown away — it is a <details>, and it opens. */
.card-done { padding: .55rem .7rem; }
.card-done:not([open]) { background: transparent; box-shadow: none; }
.card-done-head {
  display: flex; align-items: center; gap: .5rem;
  cursor: pointer; list-style: none;
  color: var(--ink-soft); font-size: .88rem;
}
.card-done-head::-webkit-details-marker { display: none; }
.card-done-head::after {
  content: '›'; margin-left: auto; flex: 0 0 auto;
  font-size: 1.1rem; line-height: 1; transition: transform .15s ease;
}
.card-done[open] .card-done-head::after { transform: rotate(90deg); }
.card-done-head .ev-icon { font-size: 1rem; filter: grayscale(.5); opacity: .75; }
.card-done-title {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 700;
}
.card-done-head .muted { flex: 0 0 auto; }
.card-done[open] .card-done-head { margin-bottom: .5rem; color: var(--ink); }
.card-done:not([open]) .ev,
.card-done:not([open]) .checks { display: none; }

/* The account, at the foot of Today, because the top bar that held it is
   gone on a phone. Shown there on a laptop too: one place for it beats two. */
.account-row {
  display: flex; align-items: center; gap: .7rem;
  margin-top: 1.5rem; padding: .7rem .85rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); color: var(--ink);
}
.account-row:hover { text-decoration: none; border-color: var(--clay); }
.account-row-badge {
  flex: 0 0 34px; width: 34px; height: 34px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--clay); color: #fff; font-weight: 700;
}
.account-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.account-row-body .muted { margin: 0; }
.account-row-go { color: var(--ink-soft); font-size: 1.3rem; line-height: 1; }

/* ---------- which trip this phone is reading ---------- */

.trippick { display: flex; flex-direction: column; gap: .5rem; }
.trippick-row {
  display: flex; align-items: center; gap: .7rem; width: 100%;
  padding: .6rem .75rem; text-align: left; font: inherit; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); color: var(--ink);
}
.trippick-row:hover:not([disabled]) { border-color: var(--clay); }
/* The one you are on is not a button you can press, and says so by not
   looking like one. */
.trippick-row.is-on { cursor: default; border-color: var(--clay); }
.trippick-row[disabled] { opacity: 1; }
.trippick-flag { font-size: 1.4rem; line-height: 1; }
.trippick-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.trippick-name { font-weight: 700; }
.trippick-mark {
  flex: 0 0 auto; font-size: .7rem; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.trippick-row.is-on .trippick-mark { color: var(--clay); }

/* ---------- a phone hands this app 762 px, so none of them go on a header ----

   Measured on the device rather than guessed at: screen 812, window 762,
   safe-area insets 50 top and 34 bottom. The chrome was taking 154 of those
   762 before any content — 50 of status bar we cannot have, 54 of top bar,
   50 of whatever the page puts first.

   The top bar carried three things: the trip name, a nav that is the tab bar
   written out again, and the account button. The first two say what the tab
   bar already says, and the account moved to the foot of Today. So on a phone
   there is no top bar at all, on any tab — the Live and Map tabs have worked
   that way for a while and are the reason this was asked for.

   The strip iOS draws the clock in keeps the brand colour, or the first
   daylight launch loses the clock into a white card. Those pixels were never
   ours: the strip is the inset and nothing more. */
@media (max-width: 719px) {
  .topbar { display: none; }
  body:not(.bare)::before {
    content: ''; position: fixed; left: 0; right: 0; top: 0; z-index: 70;
    height: env(safe-area-inset-top); background: var(--clay);
  }
  /* ONE NUMBER FOR THE TOP OF EVERY TAB.
     Measured on the phone, at its real 50px inset, the seven tabs started
     their first line at: Itinerary 8px under the status bar, Calendar 10,
     Map 10, Places 10, Today 28, Bookings 64. Nothing lined up with
     anything — "itinerary place booking too low a tad, the other tabs too
     high" — because three different rules were deciding it: the page's own
     padding, and two scroll-margins budgeting for the sticky bar at the top
     of the page, which on a phone is display:none.

     So: one value, used by all three. */
  /* Cut from 1.1rem now that iOS insets the window itself: this used to sit
     on top of a 50 px strip the app drew under the status bar, and with that
     strip gone it was the whole gap — "Places, move title up there's room",
     "Map. Empty space above too". It is also ten pixels the month was short
     of: "Calendar too long. Need to scroll." */
  :root { --gut-top: .5rem; }
  .page {
    padding-top: calc(var(--gut-top) + env(safe-area-inset-top));
    /* The tab bar's own height has to be cleared; a rem and a half of air
       under the last card on top of it was just more empty screen. */
    padding-bottom: calc(var(--tabbar-h) + var(--sab) + .6rem);
  }
  /* A tab that scrolls itself — Bookings to tonight's bed, the Itinerary to
     today — has to land in the same place as one that does not. */
  .card.is-now,
  .day-head {
    scroll-margin-top: calc(var(--gut-top) + env(safe-area-inset-top));
  }
  /* Live puts the inset on its own head, which is the first thing on it. */
  body.view-live .page { padding-top: 0; }

  /* The month is the page. There is no reason for it to stop two thirds of
     the way down and leave 166 px of nothing above the tabs, so the page
     becomes a column and the grid takes everything the month nav leaves.
     min-height, not height: a six-row month grows past the screen and
     scrolls, as it always did.

     margin: 0 for the reason .page carries it on the Live tab — auto margins
     in a flex container's cross axis turn stretch off, and the page would
     then size itself to its content instead of to the phone. */
  body.view-calendar {
    min-height: 100vh; min-height: 100dvh; min-height: 100%;
    display: flex; flex-direction: column;
  }
  body.view-calendar .page {
    flex: 1 1 auto; min-height: 0; margin: 0; max-width: none;
    display: flex; flex-direction: column;
  }
  /* flex alone, no height: 100%. A percentage height on a table whose parent
     is itself flex-sized is the kind of thing WebKit and Blink disagree
     about, and the way it goes wrong is the last week of the month sitting
     under the tab bar. Growing it with flex and nothing else is the same
     result where it works and a short grid, not a broken one, where it
     does not. */
  body.view-calendar .cal { flex: 1 1 auto; min-height: 0; }
  /* The month used to run under the tab bar, on the grounds that a date sits
     at the top of its square so only the empty bottom of the last week was
     covered. It reads as a month that does not fit — "Calendar too long.
     Need to scroll" — and anything in the lower half of that last week was
     simply behind the bar. It stops at the bar now and the five weeks share
     what is left. */
  body.view-calendar .page {
    padding-bottom: calc(var(--tabbar-h) + var(--sab));
  }
  /* Share the height out evenly — between the weeks that have something in
     them. Left to itself a table hands its spare height to the rows in
     proportion to what they already needed, which made the month stop
     looking like a grid; an equal share each fixed that but then October's
     two empty weeks, top and bottom, were holding 250 px while the days in
     the middle truncated at three events and said "+3 more".

     So a week with nothing in it is given a small fixed height and the rest
     share what is left, equally, as before. The view counts them, since only
     it knows which weeks those are. */
  body.view-calendar .cal {
    display: grid; grid-template-columns: repeat(7, 1fr);
    /* grid-template-rows comes from the view: one track per week. */
  }
  /* The table's own parts step out of the way and let their cells be the
     grid's children. The markup stays a table, which is what a month is. */
  body.view-calendar .cal thead,
  body.view-calendar .cal tbody,
  body.view-calendar .cal tr { display: contents; }
  /* Nothing collapses borders in a grid, so the squares overlap by the one
     pixel they share. */
  body.view-calendar .cal td { height: auto; margin: 0 -1px -1px 0; }
  body.view-calendar .cal th { padding-bottom: .3rem; }
}

/* ---------- building blocks ---------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem;
  margin-bottom: .85rem;
}

/* Where the Bookings tab lands: tonight's bed, or today's ticket until it is
   done. Marked, because a page that has scrolled itself has to say why it is
   not at the top. Where it lands is set per layout — see the two rules that
   follow .day-head. */
.card.is-now {
  border-color: var(--clay);
}

.section-title {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  margin: 1.5rem 0 .6rem;
}
.section-title:first-child { margin-top: 0; }
.section-title h2 { margin: 0; }
.section-title .muted { font-size: .85rem; }

.muted { color: var(--ink-soft); }
.small { font-size: .85rem; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

.pill {
  display: inline-block; padding: .15rem .55rem; border-radius: 999px;
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
  background: color-mix(in srgb, var(--saffron) 22%, transparent);
  color: color-mix(in srgb, var(--ink) 80%, var(--clay));
}
.pill-clay { background: color-mix(in srgb, var(--clay) 15%, transparent); color: var(--clay); }
.pill-sea  { background: color-mix(in srgb, var(--sea) 18%, transparent); color: var(--sea); }
.pill-draft {
  background: transparent; color: var(--ink-soft);
  border: 1px dashed var(--line); font-weight: 600;
}
.cal-dot-draft {
  background: transparent; border: 1px dashed var(--line); color: var(--ink-soft);
}

/* Things that still need reserving. */
.card-todo { border-left: 4px solid var(--saffron); }

.checks {
  list-style: none; margin: .7rem 0 0; padding: 0;
  border-top: 1px solid var(--line); padding-top: .6rem;
}
.checks li {
  position: relative; padding: .22rem 0 .22rem 1.35rem;
  font-size: .85rem; line-height: 1.45;
}
.checks li::before {
  content: '!'; position: absolute; left: 0; top: .38rem;
  width: 15px; height: 15px; border-radius: 50%;
  background: color-mix(in srgb, var(--saffron) 30%, transparent);
  color: color-mix(in srgb, var(--ink) 75%, var(--clay));
  font-size: 10px; font-weight: 800; line-height: 15px; text-align: center;
}

.empty {
  text-align: center; padding: 2.5rem 1rem; color: var(--ink-soft);
  border: 1px dashed var(--line); border-radius: var(--radius);
  background: var(--card);
}
.empty-icon { font-size: 2rem; display: block; margin-bottom: .5rem; }

/* Event rows */
.ev {
  display: flex; gap: .75rem; align-items: flex-start;
  padding: .7rem 0; border-bottom: 1px solid var(--line);
}
.ev:last-child { border-bottom: 0; padding-bottom: 0; }
.ev:first-child { padding-top: 0; }
.ev-icon { font-size: 1.25rem; line-height: 1.3; flex: 0 0 1.6rem; text-align: center; }
.ev-body { min-width: 0; flex: 1; }
.ev-title { font-weight: 600; }
.ev-meta { font-size: .85rem; color: var(--ink-soft); }
.ev-time {
  flex: 0 0 auto; font-size: .8rem; font-weight: 700; color: var(--sea);
  font-variant-numeric: tabular-nums; text-align: right; padding-top: .1rem;
}

/* A booking card's price, on the title line.
 *
 * It used to be .ev-time, a flex sibling of the whole body — which means it
 * reserved its own width for every line beneath it, not just the one it was
 * on. On a stay that is the dates, the provider, the reference, the room
 * number and codes and the car park, all wrapping inside two thirds of the
 * card with the last third blank the whole way down: "The table at the top
 * is narrow. Use the full width to leverage more estate."
 *
 * Inside the title it costs one line instead of a column. margin-left: auto
 * holds it right, and on a title too long to share the line it drops onto
 * its own, still right, which is where the eye looks for a price anyway. */
.ev-title-pay {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .5rem;
}
.ev-title-pay > a { min-width: 0; overflow-wrap: anywhere; }
.ev-cost {
  margin-left: auto; font-size: .8rem; font-weight: 700; color: var(--sea);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ev-cost .pill { font-weight: 600; }

/* The head of an event page.
 *
 * A card with a 1.8rem icon in it, the title at full size and the day on a
 * line of its own under that: a third of the screen for four facts, directly
 * above a map card whose own head repeats the title — "Remove big banner at
 * top on a place. Takes too much room."
 *
 * One line now, same shape as a booking card's title: the name, what kind of
 * thing it is, and the hours held to the right. No box, because a page title
 * is not a card. The title shrinks a little from the page default, since it
 * is now sharing its line rather than owning a panel. */
.ev-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .5rem;
  margin: 0 0 .9rem;
}
.ev-head h1 { margin: 0; font-size: 1.3rem; min-width: 0; overflow-wrap: anywhere; }
.ev-head-when {
  margin-left: auto; font-size: .82rem; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.day-head {
  display: flex; align-items: baseline; gap: .6rem;
  margin: 1.4rem 0 .5rem; padding-bottom: .35rem;
  border-bottom: 2px solid var(--line);
}

/* Where a tab that scrolls itself comes to rest.
 *
 * Both of these used to live on the rules above, unconditionally, and that
 * is a trap: the phone sets the same property inside a max-width block two
 * hundred lines earlier, and a later rule of equal specificity wins however
 * narrow the screen is. So the phone's version was dead from the day it was
 * written, and the Itinerary went on landing today's heading 57 px down the
 * screen and Bookings tonight's bed 64 px down — "the default is a little
 * too low". Measured in a 375x762 window, which is what this phone gives.
 *
 * Both numbers were making room for the sticky bar at the top of the page,
 * and on a phone that bar has been display:none since the tabs took over.
 * So the allowance belongs here, with the layout that still has one, and the
 * phone keeps the rule it always meant to have: land where the tabs that do
 * not scroll themselves land, against the top of the page. */
@media (min-width: 720px) {
  .day-head   { scroll-margin-top: 3.6rem; }
  .card.is-now { scroll-margin-top: 4rem; }
}
.day-head .dnum { font-weight: 700; font-size: 1.05rem; }
.day-head.is-today { border-bottom-color: var(--clay); }
.day-head.is-today .dnum { color: var(--clay); }

/* ---------- forms ---------- */

label { display: block; font-size: .85rem; font-weight: 600; margin-bottom: .3rem; }

input[type=text], input[type=password], input[type=date], input[type=number], select, textarea {
  width: 100%; padding: .65rem .75rem;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink);
  font: inherit; font-size: 16px; /* 16px stops iOS zoom-on-focus */
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--clay) 55%, transparent);
  outline-offset: 1px; border-color: transparent;
}
.field { margin-bottom: .9rem; }

.btn {
  display: inline-block; width: 100%; padding: .7rem 1rem;
  background: var(--clay); color: #fff; border: 0; border-radius: 10px;
  font: inherit; font-weight: 650; cursor: pointer; text-align: center;
}
.btn:hover { background: var(--clay-soft); text-decoration: none; }
.btn-quiet { background: transparent; color: var(--clay); border: 1px solid var(--line); }
.btn-quiet:hover { background: color-mix(in srgb, var(--clay) 8%, transparent); }

.notice, .error {
  padding: .7rem .85rem; border-radius: 10px; margin-bottom: 1rem; font-size: .9rem;
}
.notice { background: color-mix(in srgb, var(--olive) 18%, transparent); color: var(--ink); }
.error  { background: color-mix(in srgb, #d1453b 15%, transparent); color: #b3392f; font-weight: 500; }
@media (prefers-color-scheme: dark) { .error { color: #f09a92; } }

/* ---------- login / setup ---------- */

body.bare {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; padding: 1.25rem;
  background: linear-gradient(165deg, #8c3b21 0%, #b8532e 45%, #e0a12a 100%);
}
.bare-main { width: 100%; max-width: 24rem; }
.auth-card {
  background: var(--card); border-radius: 20px; padding: 1.75rem 1.5rem;
  box-shadow: 0 20px 60px rgba(60,25,10,.3);
}
.auth-head { text-align: center; margin-bottom: 1.4rem; }
.auth-flag { font-size: 2.2rem; display: block; }
.auth-head h1 { font-size: 1.3rem; margin: .4rem 0 .2rem; }

/* ---------- map ---------- */

/* Map and its detail panel. One column on a phone (panel under the map),
   side by side once there is room. */
.map-layout { display: grid; grid-template-columns: 1fr; gap: .8rem; }

@media (min-width: 900px) {
  /* stretch, so the panel is exactly as tall as the map beside it and scrolls
     its own content rather than running past the bottom of the map */
  .map-layout { grid-template-columns: minmax(0, 1fr) 320px; align-items: stretch; }
  .map-panel { height: 65vh; min-height: 340px; }
}

.map-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .9rem 1rem;
  overflow-y: auto;
}
.map-panel-empty {
  display: grid; place-items: center; text-align: center;
  min-height: 6rem; padding: 1rem;
}
@media (min-width: 900px) {
  /* the panel is a full 65vh now, so centre the prompt in it */
  .map-panel-empty { min-height: 100%; }
}

.panel-head { border-bottom: 1px solid var(--line); padding-bottom: .6rem; margin-bottom: .2rem; }
.panel-kind {
  font-size: .62rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft);
}
.panel-head h3 { margin: .2rem 0 .3rem; font-size: 1rem; line-height: 1.3; }
.panel-icon { font-size: 1.05rem; }
.panel-route { font-size: .84rem; color: var(--ink); }
.panel-meta {
  font-size: .78rem; color: var(--ink-soft); margin-top: .15rem;
  font-variant-numeric: tabular-nums;
}
.panel-photo {
  display: block; width: 100%; margin: .7rem 0 0;
  border-radius: 10px; border: 1px solid var(--line);
}
.panel-notes { list-style: none; margin: .7rem 0 0; padding: 0; }
.panel-notes li {
  position: relative; padding: .28rem 0 .28rem .95rem;
  font-size: .84rem; line-height: 1.45;
}
.panel-notes li::before {
  content: ''; position: absolute; left: 0; top: .62rem;
  width: 5px; height: 5px; border-radius: 50%; background: var(--saffron);
}
.panel-ideas { margin-top: .8rem; border-top: 1px solid var(--line); padding-top: .6rem; }
.panel-ideas b {
  font-size: .64rem; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-soft);
}
.panel-ideas ul { list-style: none; margin: .35rem 0 0; padding: 0; }
.panel-ideas li { padding: .28rem 0; border-bottom: 1px solid var(--line); font-size: .84rem; }
.panel-ideas li:last-child { border-bottom: 0; }
.panel-ideas li small { display: block; color: var(--ink-soft); font-size: .75rem; margin-top: .05rem; }
.panel-link { margin: .9rem 0 0; font-size: .84rem; }

/* On a phone the panel has nowhere to go and the popups already carry the
   same detail, so it is dropped rather than stacked under the map. */
@media (max-width: 899px) {
  .map-panel { display: none; }
}

/* The map page is the one screen that wants every pixel: without the top bar
   the stepper and its legs sit above the fold instead of below it. */
@media (max-width: 719px) {
  body.view-map .section-title { margin-bottom: .45rem; }
  body.view-map .section-title h1 { font-size: 1.15rem; }
  /* The map used to be "the window less 250 px", an allowance for the title,
     both stepper bars and the tab bar. Every one of those is a thing with a
     height of its own, and the number went stale the moment the top bar came
     off this page: the map came up short, the page ended above the tab bar,
     and that is the gap that turned up here. So the page is a column, the
     map takes what the others leave, and nothing has to be counted twice.

     A floor, not min-height: 0, for the reason the Live map has one — the
     height comes down a flex chain and a map with no floor is nought pixels
     tall on any browser where a link in that chain comes out at nothing. */
  body.view-map {
    min-height: 100vh; min-height: 100dvh; min-height: 100%;
    display: flex; flex-direction: column;
  }
  body.view-map .page {
    flex: 1 1 auto; min-height: 0; margin: 0; max-width: none;
    display: flex; flex-direction: column;
  }
  /* .page > .map-layout > .map-wrap > #map: the height has to be handed down
     every step of that, or it stops at the first link that does not pass it
     on — which is what happened, and the map fell back to its floor. */
  body.view-map .map-layout {
    flex: 1 1 auto; min-height: 0;
    grid-template-rows: minmax(0, 1fr);
  }
  /* A column, not a row. The wrap holds the map and, under it, the line
     that says what the red means — and a row stood that line up the map's
     right-hand side and squeezed the map itself into 129 px of the 375 the
     phone has. The key takes the one line it needs; the map takes the rest. */
  body.view-map .map-wrap { min-height: 0; display: flex; flex-direction: column; }
  body.view-map #map { flex: 1 1 auto; height: auto; min-height: 240px; }
}

.map-wrap { position: relative; }
/* The Today / whole-trip switch already owns the top right corner here, so
   the crosshair sits under it rather than through it. */
.map-wrap .map-tools { top: 56px; right: 12px; }

#map {
  height: 65vh; min-height: 340px;
  border-radius: var(--radius);
  border: 3px solid var(--card);
  box-shadow: var(--shadow);
  z-index: 1;
}
.leaflet-container { font: inherit; background: #aad3df; }

/* Pins drawn as stickers: white disc, thick edge, soft drop shadow.
   The wrapper is a fixed 60x60 box and the sticker hangs off its bottom
   centre, so the tail tip lands exactly on the wrapper's bottom-centre —
   which is what Leaflet is told the anchor is. Keeping that fixed means the
   larger focused sticker grows upward instead of shifting the pin off its
   coordinate. */
.pin-wrap { background: none; border: 0; pointer-events: none; }
.pin-sticker {
  position: absolute; left: 50%; bottom: 6px;
  transform: translateX(-50%);
  pointer-events: auto;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  background: var(--card);
  border: 3px solid var(--clay);
  border-radius: 50%;
  box-shadow: 0 3px 6px rgba(60,30,15,.35);
  font-size: 17px; line-height: 1;
  transition: width .12s ease, height .12s ease;
}
/* little tail: 6px tall, tip resting on the wrapper's bottom edge */
.pin-sticker::after {
  content: ''; position: absolute; bottom: -6px; left: 50%;
  margin-left: -5px;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--clay);
}
.pin-sticker:hover { transform: translateX(-50%) scale(1.12); }

/* Marked off. A numbered disc takes a tick or a cross in place of its number;
   a sticker has a hotel emoji in it and nowhere to put one, so it fades back
   instead — enough to say "this is why its row left the Live list", not enough
   to lose the bed on a map you are reading in the sun. */
.pin-sticker.is-done {
  opacity: .45;
  border-color: var(--ink-soft);
  box-shadow: 0 1px 3px rgba(60,30,15,.2);
}
.pin-sticker.is-done::after { border-top-color: var(--ink-soft); }

/* There is something behind this pin: the photo itself, or a hint of a list. */
.pin-thumb {
  position: absolute; right: -7px; bottom: -5px;
  width: 20px; height: 20px; object-fit: cover;
  border-radius: 5px; border: 2px solid var(--card);
  box-shadow: 0 1px 3px rgba(60,30,15,.4);
  background: var(--line);
}
.pin-more {
  position: absolute; right: -7px; bottom: -3px;
  padding: 0 3px; height: 14px;
  display: grid; place-items: center;
  border-radius: 999px; border: 2px solid var(--card);
  background: var(--sea); color: #fff;
  font-style: normal; font-size: 9px; font-weight: 800; line-height: 1;
  letter-spacing: .5px;
}

.pin-focus {
  width: 44px; height: 44px; font-size: 23px;
  border-color: var(--saffron);
  border-width: 4px;
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--saffron) 35%, transparent),
              0 4px 10px rgba(60,30,15,.4);
  animation: pin-bob 1.9s ease-in-out infinite;
}
.pin-focus::after { border-top-color: var(--saffron); }

@keyframes pin-bob {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) {
  .pin-focus { animation: none; }
}

/* --- popup contents --- */

.pin-count-note {
  font-size: .72rem; color: var(--ink-soft);
  margin-top: .1rem; padding-bottom: .3rem;
  border-bottom: 1px solid var(--line);
}
.pin-list { list-style: none; margin: .4rem 0 0; padding: 0; }
.pin-list li { padding: .28rem 0; line-height: 1.35; }
.pin-list li + li { border-top: 1px solid var(--line); }
.pin-list small { display: block; color: var(--ink-soft); font-size: .72rem; padding-left: 1.3rem; }

.pin-photo {
  display: block; width: 100%; max-width: 220px;
  margin: .4rem 0 .1rem 1.3rem;
  border-radius: 8px; border: 1px solid var(--line);
}
.event-photo {
  display: block; width: 100%; margin: .8rem 0 0;
  border-radius: var(--radius); border: 1px solid var(--line);
}

/* The practical points for whatever happens at this pin. */
.pin-notes { list-style: none; margin: .3rem 0 .1rem 1.3rem; padding: 0; }
.pin-notes li {
  position: relative; padding: .16rem 0 .16rem .7rem;
  border: 0 !important; font-size: .74rem; line-height: 1.4;
  color: var(--ink);
}
.pin-notes li::before {
  content: ''; position: absolute; left: 0; top: .55rem;
  width: 4px; height: 4px; border-radius: 50%; background: var(--saffron);
}

/* Things to see, listed on a city's pin. */
.pin-ideas { margin: .4rem 0 .1rem 1.3rem; }
.pin-ideas b {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-soft);
}
.pin-ideas ul { list-style: none; margin: .2rem 0 0; padding: 0; }
.pin-ideas li { padding: .14rem 0; border: 0 !important; font-size: .78rem; }
.pin-ideas li span { display: block; color: var(--ink-soft); font-size: .7rem; }
/* Who suggested this. Unmarked means it came from your friend, whose list is
   most of these; the tag names anyone else, so the two are never confused.
   "mine" was the old word and it was ambiguous — see idea_credit(). */
.is-mine, .pill-mine, .is-me, .pill-me {
  font-style: normal; font-size: .6rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  background: transparent; color: var(--ink-soft);
  border: 1px dashed var(--line); border-radius: 999px; padding: .02rem .36rem;
}

/* Your own pins. The same shape as the rest, solid rather than dashed,
   because "me" is the one row on the list you already know about. */
.is-me, .pill-me { border-style: solid; color: var(--ink); }

/* The glyph a thing to see carries, in the map's own lists. */
.pin-ideas .idea-glyph, .panel-ideas .idea-glyph { font-style: normal; margin-right: .15rem; }

.must-go {
  font-style: normal; font-size: .6rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em;
  background: var(--clay); color: #fff;
  border-radius: 999px; padding: .02rem .36rem;
}
.pill-must { background: var(--clay); color: #fff; }

.needs-book {
  font-style: normal; font-size: .6rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em;
  background: color-mix(in srgb, var(--clay) 15%, transparent);
  color: var(--clay); border-radius: 999px; padding: .02rem .32rem;
}


/* --- distance pinned on whichever arc is active --- */

.leg-label-wrap { background: none; border: 0; }
.leg-label {
  transform: translate(-50%, -50%);
  display: inline-block; white-space: nowrap;
  background: var(--clay); color: #fff;
  border: 2px solid var(--card);
  border-radius: 999px;
  padding: .12rem .5rem;
  font-size: .68rem; font-weight: 800;
  box-shadow: 0 2px 6px rgba(60,30,15,.35);
}
.leaflet-tooltip.leg-tip {
  background: var(--card); color: var(--ink);
  border: 1px solid var(--clay); border-radius: 8px;
  font-size: .74rem; font-weight: 600;
  box-shadow: 0 2px 8px rgba(60,30,15,.25);
}
.leaflet-tooltip.leg-tip::before { border-top-color: var(--clay); }

/* --- things to do, on an event page --- */

.ideas { list-style: none; margin: .5rem 0 0; padding: 0; }
.ideas li { padding: .45rem 0; border-bottom: 1px solid var(--line); }
.ideas li:last-child { border-bottom: 0; }
.ideas-name { font-weight: 600; }
.ideas-note { display: block; font-size: .85rem; color: var(--ink-soft); margin-top: .1rem; }
/* Where it is, above what we think of it — "add another field for notes,
   separate from address". Quieter than the note: an address is a thing to
   check, not a thing to read. */
.ideas-addr {
  display: block; font-size: .8rem; color: var(--ink-soft);
  margin-top: .1rem; font-variant-numeric: tabular-nums;
}

/* Chunky rounded popups to match. */
.leaflet-popup-content-wrapper {
  border-radius: 14px;
  border: 2px solid var(--clay);
  box-shadow: 0 8px 24px rgba(60,30,15,.3);
  background: var(--card); color: var(--ink);
}
.leaflet-popup-tip { background: var(--card); border: 2px solid var(--clay); }
.leaflet-popup-content { margin: .7rem .9rem; font-size: .88rem; line-height: 1.45; }
.pin-pop strong { color: var(--clay); }
/* The title is a link to the thing's own page, and has to look like one —
   it is the biggest thing on the card and the obvious thing to press. */
.leaflet-container a.pin-title {
  color: var(--clay); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.leaflet-container a.pin-title:hover { color: var(--clay-soft); }
.leaflet-container a.leaflet-popup-close-button {
  color: var(--ink-soft);
  /* a bigger tap target, and clear of the rounded corner */
  width: 26px; height: 26px; font-size: 18px; line-height: 24px;
  padding: 0; top: 4px; right: 4px;
}
/* the distance label matches whichever route is active */
.leg-label { background: #ff5a1f; }

/* Today / whole-trip switch, floating over the map. */
.map-toggle {
  position: absolute; top: 12px; right: 12px; z-index: 500;
  display: flex; gap: 2px; padding: 3px;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(8px);
  border: 2px solid var(--clay);
  border-radius: 999px;
  box-shadow: 0 3px 10px rgba(60,30,15,.25);
}
.map-toggle button {
  border: 0; background: none; cursor: pointer;
  font: inherit; font-size: .76rem; font-weight: 700;
  color: var(--ink-soft);
  padding: .3rem .7rem; border-radius: 999px;
}
.map-toggle button.is-on { background: var(--clay); color: #fff; }
.map-toggle button:disabled { opacity: .4; cursor: default; }

/* Step-through-the-trip bar under the map. */
.stepper {
  display: flex; align-items: center; gap: .6rem;
  margin-top: .7rem; padding: .55rem .6rem;
  background: var(--card);
  border: 2px solid var(--clay);
  border-radius: 999px;
  box-shadow: var(--shadow);
}
.stepper > button {
  flex: 0 0 38px; width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: var(--clay); color: #fff;
  font-size: 1.35rem; line-height: 1; font-weight: 700;
  cursor: pointer; padding: 0 0 3px;
}
.stepper > button:hover:not(:disabled) { background: var(--clay-soft); }
.stepper > button:disabled { background: var(--line); color: var(--ink-soft); cursor: default; }
.stepper-body { flex: 1; min-width: 0; text-align: center; }
.stepper-title {
  font-weight: 650; font-size: .92rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stepper-meta {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.step-icon { font-size: 1rem; }

/* Jumping in part way, rather than clicking from day one. Sits above the
   stepper and reads as a label with two menus, not a form. */
.stepjump {
  display: flex; align-items: center; flex-wrap: wrap; gap: .4rem .5rem;
  margin: .9rem 0 -.1rem; padding: 0 .35rem;
}
.stepjump-label { flex: 0 0 auto; }
.stepjump-pick { display: inline-flex; min-width: 0; }
.stepjump select {
  font: inherit; font-size: 16px;
  max-width: 11.5rem;
  padding: .3rem 1.5rem .3rem .55rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  /* own chevron, since the native one differs per platform */
  -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23999' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .55rem center;
  background-size: .6rem;
}
.stepjump select:hover { border-color: var(--clay); }
.stepjump select:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }

/* Visible to a screen reader, not on the page. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* Legs within a stop — getting to the Caminito, say. Sits under the main
   stepper, visually subordinate to it. */
.substepper {
  display: flex; align-items: center; gap: .5rem;
  /* sits above the stop bar, inset from it, and rises into place */
  margin: .7rem 2.2rem .35rem 2.2rem;
  padding: .35rem .45rem;
  background: var(--card);
  border: 1px dashed var(--line);
  border-radius: 999px;
  animation: sub-rise .28s cubic-bezier(.2,.7,.3,1);
}

@keyframes sub-rise {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .substepper { animation: none; }
}
.substepper > button {
  flex: 0 0 28px; width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: var(--sea); color: #fff;
  font-size: 1rem; line-height: 1; cursor: pointer; padding: 0 0 2px;
}
.substepper > button:disabled { background: var(--line); color: var(--ink-soft); cursor: default; }
.substepper-body { flex: 1; min-width: 0; text-align: center; }
.substepper-title {
  font-size: .82rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.substepper-meta {
  font-size: .7rem; color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.substepper-tag {
  flex: 0 0 auto; font-size: .6rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sea);
  border: 1px solid var(--line); border-radius: 999px; padding: .1rem .4rem;
}

@media (max-width: 420px) {
  .substepper { margin-left: 1rem; margin-right: 1rem; }
  .substepper-tag { display: none; }
}

@media (max-width: 420px) {
  .stepper-title { font-size: .85rem; }
  .stepper-meta { font-size: .72rem; }
}

.leaflet-control-zoom a {
  border-radius: 8px !important;
  color: var(--clay) !important;
  font-weight: 700;
}
.leaflet-control-attribution { font-size: .62rem; border-radius: 6px 0 0 0; }

/* ---------- calendar ---------- */

.cal-nav { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .75rem; }
.cal-nav h2 { margin: 0; }
.cal-nav a { padding: .35rem .7rem; border: 1px solid var(--line); border-radius: 8px; background: var(--card); }

/* One day, opened in full. */
.day-nav {
  display: flex; align-items: center; gap: .75rem;
  margin-bottom: .9rem;
}
.day-nav > a {
  flex: 0 0 40px; width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: 50%; background: var(--clay); color: #fff;
  font-size: 1.4rem; line-height: 1; padding-bottom: 3px;
}
.day-nav > a:hover { background: var(--clay-soft); text-decoration: none; }
.day-nav-body { flex: 1; min-width: 0; text-align: center; }
.day-nav-body h1 { font-size: 1.15rem; margin: 0; }

.day-stay {
  background: color-mix(in srgb, var(--saffron) 16%, transparent);
  border-radius: var(--radius); padding: .6rem .85rem; margin-bottom: .9rem;
  font-size: .9rem;
}

.cal { width: 100%; border-collapse: collapse; table-layout: fixed; }
.cal th {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-soft); padding: .35rem 0; font-weight: 700;
}
.cal { --cal-quiet-h: 64px; }
.cal td {
  border: 1px solid var(--line); vertical-align: top;
  /* Room for the "sleeping in" strip, the date and what fits after them —
     the view works out how many that is from the height its weeks are
     getting. Clipped rather than spilling, so a mis-estimate costs the
     bottom of one chip and not the shape of the grid. */
  height: 5.4rem; padding: .2rem; background: var(--card);
  position: relative; overflow: hidden;
}
/* The whole square opens that day. It sits under the chips, so a tap on one
   of those still goes where the chip points. */
.cal-open {
  position: absolute; inset: 0; z-index: 0;
  border-radius: 2px;
}
.cal-open:hover { background: color-mix(in srgb, var(--clay) 7%, transparent); }
.cal td > .cal-stay,
.cal td > .cal-date,
.cal td > .cal-dot,
.cal td > .cal-more { position: relative; z-index: 1; }
.cal td.out { background: transparent; border-color: transparent; }
.cal td.today { outline: 2px solid var(--clay); outline-offset: -2px; }
.cal .cal-date {
  display: block; font-size: .75rem; font-weight: 700; color: var(--ink-soft);
  padding: .05rem .15rem; border-radius: 4px;
}
.cal .cal-date:hover { background: var(--clay); color: #fff; text-decoration: none; }
.cal td.today .cal-date { color: var(--clay); }
.cal-dot {
  display: block; font-size: .62rem; line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-radius: 4px; padding: 0 .2rem; margin-top: 1px;
  background: color-mix(in srgb, var(--saffron) 25%, transparent);
  color: var(--ink);
}
.cal-more { display: block; font-size: .6rem; color: var(--ink-soft); padding-left: .2rem; }
.cal-more:hover { color: var(--clay); }

/* Where you sleep, heading the square. It frames everything else in the day,
   so it sits above the date number rather than in the list of events. */
.cal-stay {
  display: flex; align-items: center; gap: .15rem;
  font-size: .6rem; font-weight: 700; line-height: 1.3;
  padding: .1rem .25rem; margin: -.05rem -.05rem .1rem;
  border-radius: 4px;
  background: color-mix(in srgb, var(--sea) 16%, transparent);
  color: var(--sea);
}
.cal-stay:hover { text-decoration: none; background: var(--sea); color: #fff; }
.cal-stay-icon { flex: 0 0 auto; font-size: .62rem; }
/* "Setenil de las Bodegas" has nowhere near the room; the full name is on
   the link's title. */
.cal-stay-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Nothing is booked yet, so the dashed outline the draft event dots use
   applies here too — it should not look settled when it isn't. */
.cal-stay.is-draft {
  background: transparent;
  border: 1px dashed color-mix(in srgb, var(--sea) 45%, var(--line));
  padding: calc(.1rem - 1px) calc(.25rem - 1px);
}
.cal-stay.is-draft:hover { background: var(--sea); color: #fff; border-color: var(--sea); }
/* A night in the air rather than in a bed. */
.cal-stay.is-moving {
  background: transparent; color: var(--ink-soft);
  border: 1px dashed var(--line);
  padding: calc(.1rem - 1px) calc(.25rem - 1px);
  font-weight: 600;
}
.cal-stay.is-moving:hover { background: var(--ink-soft); color: var(--paper); }

@media (max-width: 560px) {
  .cal td { height: 4.2rem; }
  .cal-dot { font-size: .55rem; }
  .cal-stay { font-size: .54rem; gap: .1rem; }
  /* On a phone the icon is most of the width the name needs. */
  .cal-stay-icon { display: none; }
}

/* ---------- misc ---------- */

.legs { list-style: none; counter-reset: leg; margin: .5rem 0 0; padding: 0; }
.legs li {
  position: relative; padding: .35rem 0 .35rem 1.9rem;
  border-bottom: 1px solid var(--line); font-size: .9rem;
}
.legs li:last-child { border-bottom: 0; }
.legs li::before {
  counter-increment: leg; content: counter(leg);
  position: absolute; left: 0; top: .45rem;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--sea); color: #fff;
  font-size: .68rem; font-weight: 800; line-height: 20px; text-align: center;
}
.legs-when { display: block; font-size: .72rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.legs-what { display: block; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: .4rem 1rem; font-size: .92rem; }
.kv dt { color: var(--ink-soft); font-weight: 600; }
.kv dd { margin: 0; word-break: break-word; }

.hero {
  background: linear-gradient(135deg, var(--clay), var(--clay-soft));
  color: #fff; border-radius: var(--radius); padding: 1.15rem; margin-bottom: 1rem;
  box-shadow: var(--shadow);
}
.hero h1 { margin: 0 0 .25rem; font-size: 1.35rem; }
.hero .muted { color: rgba(255,255,255,.85); }
.hero-count { font-size: 2.4rem; font-weight: 750; line-height: 1; letter-spacing: -.02em; }

/* ---------- places ---------- */

.place-grid { display: grid; grid-template-columns: 1fr; gap: .6rem; }
@media (min-width: 720px) {
  .place-grid { grid-template-columns: 1fr 1fr; }
}

.place-card {
  display: flex; align-items: center; gap: .75rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: .8rem .9rem; color: inherit;
}
.place-card:hover {
  text-decoration: none; border-color: var(--clay);
  transform: translateY(-1px);
}
.place-card.is-here { border-left: 4px solid var(--clay); }
.place-icon { font-size: 1.5rem; line-height: 1; flex: 0 0 1.8rem; text-align: center; }
.place-body { min-width: 0; flex: 1; display: block; }
.place-name { display: block; font-weight: 700; font-size: 1.02rem; }
.place-when { display: block; font-size: .82rem; color: var(--ink); margin-top: .1rem; }
.place-meta { display: block; font-size: .78rem; color: var(--ink-soft); margin-top: .05rem; }
.place-go { color: var(--ink-soft); font-size: 1.3rem; line-height: 1; }

/* ---------- a place's own map, beside its entry ---------- */

/* One column on a phone with the map lifted above the detail, two columns
   once there is room for it to sit alongside. */
.place-layout { display: grid; grid-template-columns: 1fr; gap: .8rem; }
.place-layout.is-plain { display: block; }
/* A grid child's default min-width is auto, so a long unbroken string in the
   notes would push the column wider than its track and squeeze the map. */
.place-main { min-width: 0; overflow-wrap: break-word; }

@media (max-width: 899px) {
  /* The map is the point of opening a city, so it goes above the notes
     rather than below a screenful of them. */
  .place-aside { order: -1; }
}

@media (min-width: 900px) {
  .place-layout { grid-template-columns: minmax(0, 1fr) 360px; align-items: start; }
  .place-layout.is-plain { display: block; }
  /* Follows you down a long set of notes, which is where you want it. */
  .place-aside { position: sticky; top: .8rem; }
}

.place-mapcard {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  overflow: hidden;
}
.place-maphead {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .6rem .8rem .5rem; border-bottom: 1px solid var(--line);
}
.place-maphead strong { font-size: .95rem; }
.place-maphead .muted { margin-left: auto; }

#placemap {
  height: 46vh; min-height: 260px;
  z-index: 1;
}

/* Never a blank rectangle while Leaflet is still on its way.
   Leaflet puts .leaflet-container on the div it takes over, so this says its
   piece and then gets out of the way with no script of its own — and on the
   Live tab, where the map IS the page, that blank rectangle was the whole
   screen. */
#map:not(.leaflet-container),
#placemap:not(.leaflet-container) {
  display: grid; place-items: center;
}
#map:not(.leaflet-container)::before,
#placemap:not(.leaflet-container)::before {
  content: 'Loading the map…';
  font-size: .82rem; color: var(--ink-soft);
}
@media (min-width: 900px) {
  #placemap { height: 420px; }
}

/* Numbered things-to-see pins. Smaller and quieter than the trip map's
   stickers, because a town centre holds fifteen of them. */
.dot-wrap { background: none; border: 0; }
.dot {
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--card); color: var(--clay);
  border: 2px solid var(--clay);
  font-size: .76rem; font-weight: 800; line-height: 1;
  box-shadow: 0 1px 4px rgba(70,45,30,.35);
  cursor: pointer;
  transition: transform .12s ease;
}
.dot:hover { transform: scale(1.18); }
.dot-must { background: var(--clay); color: #fff; border-color: var(--card); }

.pin-tags { margin: .25rem 0 .1rem; }
.pin-note { margin: .35rem 0 0; font-size: .8rem; line-height: 1.45; }
/* The address on a pin's card, above the note for the same reason: the first
   question a card is asked is whether this is the right door. */
.pin-addr {
  margin: .3rem 0 0; font-size: .78rem; line-height: 1.4; color: var(--ink-soft);
}
.pin-link { margin: .45rem 0 0; font-size: .78rem; }

/* The number against the name, so the list and the map read together. */
.ideas-no {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.3rem; height: 1.3rem; padding: 0 .2rem;
  border-radius: 50%; border: 1.5px solid var(--clay);
  color: var(--clay); font-size: .7rem; font-weight: 800;
  margin-right: .3rem; vertical-align: .05rem;
}
/* The row is the hit area, so it needs side padding for its hover tint to
   look like a row rather than a stripe. The negative margin keeps the text
   aligned with the unpinned rows above and below it. */
.ideas li.is-pinned {
  cursor: pointer; border-radius: 8px;
  padding-left: .45rem; padding-right: .45rem;
  margin-left: -.45rem; margin-right: -.45rem;
}
.ideas li.is-pinned:hover,
.ideas li.is-pinned:focus-visible {
  background: color-mix(in srgb, var(--clay) 7%, transparent);
  outline: none;
}
.place-card { transition: transform .12s ease, border-color .12s ease; }

/* ---------- the Places index map ---------- */

.place-indexmap { margin-bottom: .9rem; }
.place-indexmap #placemap { height: 44vh; min-height: 260px; }
@media (min-width: 900px) {
  .place-indexmap #placemap { height: 460px; }
}

/* A stop on the route: numbered dot with its name alongside. Twelve emoji
   stickers at the scale of Andalusia would sit on top of one another. */
.dot-place { background: var(--clay); color: #fff; border-color: var(--card); }

.dot-label {
  position: absolute; left: 32px; top: 50%; transform: translateY(-50%);
  white-space: nowrap; pointer-events: none;
  font-size: .72rem; font-weight: 700; color: var(--ink);
  background: color-mix(in srgb, var(--card) 86%, transparent);
  padding: 1px 5px; border-radius: 5px;
  text-shadow: 0 1px 0 var(--card);
}
/* Names are reference, not decoration — they must never swallow a tap meant
   for the dot underneath them. */
.dot-wrap { overflow: visible; }

/* Lit by hovering its card in the list. */
.dot.is-lit {
  transform: scale(1.3);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--saffron) 65%, transparent),
              0 2px 6px rgba(70,45,30,.4);
}

/* The number on the card, tucked against the place's own icon. */
.place-icon { position: relative; }
.place-no {
  position: absolute; top: -.35rem; left: -.4rem;
  display: flex; align-items: center; justify-content: center;
  min-width: 1.05rem; height: 1.05rem; padding: 0 .15rem;
  border-radius: 50%; background: var(--clay); color: #fff;
  font-size: .6rem; font-weight: 800; line-height: 1;
  border: 1.5px solid var(--card);
}

/* ---------- a section of a page, folded away ----------

   Two of them on a place's page. The shortlist of beds, because choosing one
   is a job you do at a desk and not while standing in the town — "places to
   stay short list should be collapsed". And a booking's notes when they run
   long, because door codes and parking tariffs are reference rather than
   reading, and left open they buried the list of things to see.

   The chevron is the last flex item and claims the right-hand end of the
   first line; anything given a row of its own — the triage tally — is
   ordered after it, so it wraps underneath instead of pushing it down. */
.fold { margin: .3rem 0 0; }
.fold-head {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  cursor: pointer; list-style: none;
}
.fold-head::-webkit-details-marker { display: none; }
.fold-title { font-weight: 700; font-size: 1rem; }
.fold-head::after {
  content: '\203A'; order: 5; margin-left: auto; flex: 0 0 auto;
  font-size: 1.1rem; line-height: 1; color: var(--ink-soft);
  transition: transform .15s ease;
}
.fold[open] > .fold-head::after { transform: rotate(90deg); }
.fold[open] > .fold-head { margin-bottom: .4rem; }
.fold-head > .tally { order: 9; flex: 1 1 100%; margin: .15rem 0 0; }
/* Shut, the tally is a label on the fold, and a count of nothing is not worth
   a pill: "1 Yes · 1 Maybe" rather than six, four of them zero. Open, they
   come back — a row of zeros is how you see that nothing has been ruled out
   yet. */
.fold:not([open]) > .fold-head .tally-bit.is-zero { display: none; }

/* ---------- a shortlist of places to stay ---------- */

/* Lettered rather than numbered, so hotels and things-to-see never read as
   one sequence. */
.dot-option {
  background: var(--sea); color: #fff; border-color: var(--card);
  font-size: .7rem;
}

.place-filter {
  position: static; margin: .5rem .8rem 0; justify-content: flex-start;
}

.shortlist { list-style: none; margin: .4rem 0 0; padding: 0; }
.shortlist li {
  padding: .45rem 0; border-bottom: 1px solid var(--line);
}
.shortlist li:last-child { border-bottom: 0; }
.shortlist li.is-pinned {
  cursor: pointer; border-radius: 8px;
  padding-left: .45rem; padding-right: .45rem;
  margin-left: -.45rem; margin-right: -.45rem;
}
.shortlist li.is-pinned:hover,
.shortlist li.is-pinned:focus-visible {
  background: color-mix(in srgb, var(--sea) 8%, transparent);
  outline: none;
}
.shortlist-head { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.shortlist-no {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.3rem; height: 1.3rem; flex: 0 0 1.3rem;
  border-radius: 50%; background: var(--sea); color: #fff;
  font-size: .68rem; font-weight: 800;
}
.shortlist-name { font-weight: 600; }
.shortlist-meta {
  display: block; font-size: .8rem; color: var(--ink); margin-top: .1rem;
  padding-left: 1.65rem;
}
.shortlist-note {
  display: block; font-size: .78rem; color: var(--ink-soft); margin-top: .05rem;
  padding-left: 1.65rem;
}

/* ---------- a map that fills the screen ---------- */

.place-mapcard { position: relative; }

/* Three buttons now, so they stack rather than each claiming a corner. */
.map-tools {
  /* 1000 and not 500: on the .place-mapcard maps this sits inside the Leaflet
     container now, and Leaflet's marker pane is 600 and its popup pane 700, so
     at 500 a pin in the top corner covered the buttons. Same band as
     Leaflet's own controls. */
  position: absolute; top: .5rem; right: .5rem; z-index: 1000;
  display: flex; flex-direction: column; align-items: flex-end; gap: .35rem;
}
/* The two that go together, across rather than down — "Move the expand icon
   left to the recenter icon on all maps." Expand is first in the row, so it
   lands on the left of the crosshair; Live and the trail switch carry on down
   the column underneath. */
.map-toolrow {
  display: flex; flex-direction: row; align-items: center; gap: .35rem;
}

.map-expand, .map-locate, .map-live, .map-trail {
  position: static; margin: 0; padding: 0;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 9px;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: saturate(1.4) blur(6px);
  color: var(--ink); font-size: 1rem; line-height: 1; cursor: pointer;
  box-shadow: 0 1px 4px rgba(70,45,30,.18);
}
.map-expand:hover, .map-locate:hover { background: var(--clay); color: #fff; border-color: var(--clay); }

/* The trail switch wears the trail's own green, so there is no guessing which
   line it governs. Off is the plain button: the trail is on by default. */
.map-trail { font-size: 1.15rem; }
.map-trail.is-on { background: #0f9d58; color: #fff; border-color: #0f9d58; }
.map-trail:not(.is-on) { color: var(--ink-soft); }

/* Showing your position is a state, not an action, so the button holds it. */
.map-locate.is-on {
  background: var(--gps); color: #fff; border-color: var(--gps);
}

.map-live {
  width: auto; padding: 0 .55rem; gap: .3rem;
  grid-auto-flow: column; align-items: center;
  font-size: .76rem; font-weight: 700; letter-spacing: .02em; white-space: nowrap;
}
.map-live .live-blip { font-size: .66rem; color: var(--gps); }
.map-live:hover { border-color: var(--clay); }
.map-live.is-on { background: var(--gps); color: #fff; border-color: var(--gps); }
.map-live.is-on .live-blip { color: #fff; animation: live-pulse 1.6s ease-in-out infinite; }
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .2; } }

/* Full-bleed and above the top bar (z 50) — but it stops at the top of the
   tab bar (z 60) rather than over it. A map that hides the way out of itself
   is worse than a map 62 px shorter: the only other way out is a 34 px x in
   the corner of a screen you are holding one-handed in the street, and if
   that goes unnoticed the whole app has disappeared. Above 720 px there is no
   tab bar, so there it really is the whole window. */
.place-mapcard.is-expanded {
  position: fixed; left: 0; right: 0; top: 0;
  bottom: calc(var(--tabbar-h) + var(--sab));
  z-index: 200;
  display: flex; flex-direction: column;
  border-radius: 0; border: 0; box-shadow: none;
  margin: 0; max-width: none;
  padding-top: env(safe-area-inset-top);
  background: var(--card);
}
@media (min-width: 720px) {
  .place-mapcard.is-expanded {
    bottom: 0; padding-bottom: var(--sab);
  }
}
/* The map takes whatever the head and the filter leave. A flex child's
   default min-height is auto, so without min-height:0 it refuses to shrink
   and pushes its own controls off the bottom. */
.place-mapcard.is-expanded #placemap {
  flex: 1 1 auto; height: auto; min-height: 0;
  border-radius: 0; border: 0;
}
.place-mapcard.is-expanded .map-tools { top: calc(.5rem + env(safe-area-inset-top)); }
.place-mapcard.is-expanded .place-maphead { padding-right: 3rem; }

/* Stop the page behind it scrolling under the map. */
body.has-expanded-map { overflow: hidden; }
.shortlist-price {
  display: block; font-size: .82rem; font-weight: 700; color: var(--clay);
  margin-top: .15rem; padding-left: 1.65rem; font-variant-numeric: tabular-nums;
}
.shortlist-link { display: block; font-size: .78rem; margin-top: .15rem; padding-left: 1.65rem; }

/* ---------- marking a shortlist off ---------- */

:root {
  --v-yes:    #4f7a3a;
  --v-maybe:  #c08a1e;
  --v-pricey: #a4552b;
  --v-sold:   #b03a30;
  --v-no:     #8a8178;
}
@media (prefers-color-scheme: dark) {
  :root {
    --v-yes: #8fbd6e; --v-maybe: #e0b354; --v-pricey: #d98f63;
    --v-sold: #e0756a; --v-no: #9a9188;
  }
}

.tally { display: flex; flex-wrap: wrap; gap: .3rem; margin: .1rem 0 .7rem; }
.tally-bit {
  font-size: .7rem; font-weight: 600; padding: .15rem .4rem;
  border-radius: 999px; border: 1px solid var(--line); color: var(--ink-soft);
}
.tally-bit b { font-variant-numeric: tabular-nums; }
.tally-bit.is-zero { opacity: .4; }
.tally-bit.tone-yes    { color: var(--v-yes);    border-color: color-mix(in srgb, var(--v-yes) 45%, var(--line)); }
.tally-bit.tone-maybe  { color: var(--v-maybe);  border-color: color-mix(in srgb, var(--v-maybe) 45%, var(--line)); }
.tally-bit.tone-pricey { color: var(--v-pricey); border-color: color-mix(in srgb, var(--v-pricey) 45%, var(--line)); }
.tally-bit.tone-sold   { color: var(--v-sold);   border-color: color-mix(in srgb, var(--v-sold) 45%, var(--line)); }
.tally-bit.tone-no     { color: var(--v-no);     border-color: color-mix(in srgb, var(--v-no) 45%, var(--line)); }
/* Still to check: the one that should draw the eye while there is work left. */
.tally-bit.tone-left   { color: var(--clay); border-color: var(--clay); font-weight: 700; }

.verdict {
  display: flex; flex-wrap: wrap; align-items: center; gap: .25rem;
  margin: .4rem 0 .1rem; padding-left: 1.65rem;
}
.vbtn {
  font: inherit; font-size: .7rem; font-weight: 700;
  padding: .2rem .5rem; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--ink-soft);
}
.vbtn:hover { border-color: currentColor; }
.vbtn.tone-yes:hover,    .vbtn.tone-yes.is-on    { color: #fff; background: var(--v-yes);    border-color: var(--v-yes); }
.vbtn.tone-maybe:hover,  .vbtn.tone-maybe.is-on  { color: #fff; background: var(--v-maybe);  border-color: var(--v-maybe); }
.vbtn.tone-pricey:hover, .vbtn.tone-pricey.is-on { color: #fff; background: var(--v-pricey); border-color: var(--v-pricey); }
.vbtn.tone-sold:hover,   .vbtn.tone-sold.is-on   { color: #fff; background: var(--v-sold);   border-color: var(--v-sold); }
.vbtn.tone-no:hover,     .vbtn.tone-no.is-on     { color: #fff; background: var(--v-no);     border-color: var(--v-no); }
.verdict-when { font-size: .68rem; margin-left: .2rem; }
.verdict-failed { outline: 2px solid var(--v-sold); outline-offset: 2px; border-radius: 6px; }

/* A ruled-out row recedes but stays readable — you still want to know you
   looked at it, and why. */
/* A bed you have ruled out folds to its name, the same way a thing you have
   seen does — "Collapse places to stay too." The walk, the parking, the
   price and the link are all for deciding, and that is decided; the row
   stays so you can change your mind, and changing it brings the detail back.
   Yes and maybe keep theirs: one is the bed you are taking and the other is
   still a question. */
.shortlist li.verdict-no,
.shortlist li.verdict-sold,
.shortlist li.verdict-pricey { opacity: .55; }
.shortlist li.verdict-no > .shortlist-meta,
.shortlist li.verdict-no > .shortlist-note,
.shortlist li.verdict-no > .shortlist-price,
.shortlist li.verdict-no > .shortlist-link,
.shortlist li.verdict-sold > .shortlist-meta,
.shortlist li.verdict-sold > .shortlist-note,
.shortlist li.verdict-sold > .shortlist-price,
.shortlist li.verdict-sold > .shortlist-link,
.shortlist li.verdict-pricey > .shortlist-meta,
.shortlist li.verdict-pricey > .shortlist-note,
.shortlist li.verdict-pricey > .shortlist-price,
.shortlist li.verdict-pricey > .shortlist-link { display: none; }
.shortlist li.verdict-yes { background: color-mix(in srgb, var(--v-yes) 9%, transparent); }
.shortlist li.verdict-yes .shortlist-no { background: var(--v-yes); }

/* The name is a button now, so it must not look like one. */
.shortlist-name.as-link {
  font: inherit; font-weight: 600; color: var(--ink);
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
}
.shortlist-name.as-link:hover { color: var(--clay); text-decoration: underline; }

/* Same marks on the map. */
.dot-option.v-yes    { background: var(--v-yes);    }
.dot-option.v-maybe  { background: var(--v-maybe);  }
.dot-option.v-pricey { background: var(--v-pricey); }
.dot-option.v-sold   { background: var(--v-sold);   opacity: .5; }
.dot-option.v-no     { background: var(--v-no);     opacity: .5; }

/* Pinned at a quarter rather than a door: hollow and dashed, so it never
   reads as exact as the pins beside it. */
.dot-approx {
  background: var(--card); color: var(--sea);
  border-style: dashed; border-width: 2px;
}
.dot-approx.v-yes, .dot-approx.v-maybe, .dot-approx.v-pricey,
.dot-approx.v-sold, .dot-approx.v-no { color: #fff; }

/* Check-out morning. A stay stops covering the day you leave it, so this is
   the only thing that says when you have to be out. */
.day-checkout {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  background: color-mix(in srgb, var(--clay) 13%, transparent);
  border-left: 4px solid var(--clay);
  border-radius: var(--radius); padding: .6rem .85rem; margin-bottom: .6rem;
  font-size: .9rem;
}
.day-checkout-time {
  font-weight: 800; color: var(--clay); font-variant-numeric: tabular-nums;
}
.day-checkout a { margin-left: auto; }
.hero-out {
  margin-top: .3rem; font-size: .9rem;
  color: color-mix(in srgb, #fff 82%, var(--clay));
}
/* The times you are looking for when you open a stay in a hurry. */
.big-time { font-size: 1.05rem; font-variant-numeric: tabular-nums; color: var(--clay); }

/* ---------- ticking things off ---------- */

.things-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.things-head h3 { margin: 0; }
.seen-tally {
  margin-left: auto; font-size: .72rem; font-weight: 700; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 999px; padding: .12rem .45rem;
}
.seen-tally b { font-variant-numeric: tabular-nums; color: var(--olive); }
.seen-tally.is-done { border-color: var(--olive); color: var(--olive); }
/* Only there when something has actually been ruled out. */
.seen-skip { color: var(--v-no); }
.seen-skip b { color: var(--v-no); }

/* The row becomes a flex line so the boxes sit left of the name. */
.ideas li { display: flex; align-items: flex-start; gap: .5rem; flex-wrap: wrap; }
.ideas li > .ideas-name { flex: 1; min-width: 0; }
.ideas li > .ideas-note,
.ideas li > .ideas-addr { flex: 0 0 100%; }
.ideas li > form.seen {
  flex: 0 0 auto; order: -1; margin-top: .1rem;
  display: flex; gap: .3rem;
}

.seen-box {
  width: 24px; height: 24px; padding: 0; cursor: pointer;
  display: grid; place-items: center;
  border: 2px solid var(--line); border-radius: 7px;
  background: transparent; color: transparent;
  font-size: .85rem; font-weight: 900; line-height: 1;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
  /* Not something anybody double-taps, and saying so is what takes Safari's
     wait-and-see delay off the press. See the note by .livebar-list. */
  touch-action: manipulation;
}
@media (hover: hover) and (pointer: fine) {
  .seen-box:hover { border-color: var(--olive); color: color-mix(in srgb, var(--olive) 45%, transparent); }
}
.seen-box.is-on { background: var(--olive); border-color: var(--olive); color: #fff; }
.seen-box:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* Won't do: the same box, the colour the shortlist already uses for no. The
   two sit side by side and only one can be on, which is why pressing the one
   that is on clears it rather than there being a third button for that. */
.skip-box {
  width: 24px; height: 24px; padding: 0; cursor: pointer;
  display: grid; place-items: center;
  border: 2px solid var(--line); border-radius: 7px;
  background: transparent; color: transparent;
  font-size: .8rem; font-weight: 900; line-height: 1;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
  touch-action: manipulation;
}
@media (hover: hover) and (pointer: fine) {
  .skip-box:hover { border-color: var(--v-no); color: color-mix(in srgb, var(--v-no) 55%, transparent); }
}
.skip-box.is-on { background: var(--v-no); border-color: var(--v-no); color: #fff; }
.skip-box:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* The same two answers on a map card. Labelled, not two bare boxes: there is
   room here, and on a phone the card is already under your thumb. */
.pin-marks { display: flex; gap: .4rem; margin: .5rem 0 .1rem; }
.pin-mark {
  flex: 1 1 auto; padding: .34rem .4rem; cursor: pointer;
  border: 2px solid var(--line); border-radius: 8px;
  background: var(--card); color: var(--ink-soft);
  font: inherit; font-size: .82rem; font-weight: 700; line-height: 1.1;
  white-space: nowrap;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
  touch-action: manipulation;
}
@media (hover: hover) and (pointer: fine) {
  .pin-mark:hover { border-color: var(--olive); color: var(--ink); }
  .pin-mark-no:hover { border-color: var(--v-no); }
}
.pin-mark:active { border-color: var(--olive); color: var(--ink); }
.pin-mark.is-on { background: var(--olive); border-color: var(--olive); color: #fff; }
.pin-mark-no.is-on { background: var(--v-no); border-color: var(--v-no); }
.pin-mark:focus-visible { outline: 2px solid var(--clay); outline-offset: 2px; }

/* Seen already: still readable, visibly behind you — and folded down to the
   one line that still matters. "In places, when a place is seen, collapse the
   row." A list of twenty with a paragraph under each is most of a screen
   spent on the ones you are done with; the note comes back the moment the
   tick does, which is also how you change your mind about it. Ruled out gets
   the same treatment: both are decided. */
.ideas li.is-seen > .ideas-name { color: var(--ink-soft); }
.ideas li.is-seen .ideas-no { border-color: var(--olive); color: var(--olive); }
.ideas li.is-seen > .ideas-note,
.ideas li.is-seen > .ideas-addr,
.ideas li.is-seen > .ideas-link,
.ideas li.is-skipped > .ideas-note,
.ideas li.is-skipped > .ideas-addr,
.ideas li.is-skipped > .ideas-link { display: none; }

/* Tapping a row opens it again — "maybe the delete can be when I click on the
   row in city and then it opens full details there." A folded row gives its
   note back, and any row that was added from the street shows the one control
   that can take it off the list. Hidden until then, because a Remove sitting
   permanently beside a tick box is a mis-tap waiting to happen. */
.ideas li { cursor: default; }
.ideas li.is-seen, .ideas li.is-skipped, .ideas li.is-added { cursor: pointer; }
.ideas li.is-open > .ideas-note,
.ideas li.is-open > .ideas-addr,
.ideas li.is-open > .ideas-link { display: block; }
/* Specific enough to beat .ideas-link further down the file, which sets
   display: block on every link-ish span in a row. */
.ideas li > .ideas-drop { display: none; }
.ideas li.is-open > .ideas-drop { display: block; }

/* Ruled out: further back still, and struck through, because the reason it is
   still on the page at all is so you can change your mind. */
.ideas li.is-skipped > .ideas-name { color: var(--ink-soft); text-decoration: line-through; }
.ideas li.is-skipped .pill { text-decoration: none; }
.ideas li.is-skipped .ideas-no { border-color: var(--v-no); color: var(--v-no); }
.ideas li.is-skipped .idea-name.as-link { color: var(--ink-soft); }

.idea-name.as-link {
  font: inherit; font-weight: 600; color: var(--ink);
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
}
.idea-name.as-link:hover { color: var(--clay); text-decoration: underline; }
.ideas li.is-seen .idea-name.as-link { color: var(--ink-soft); }

/* The trail: everywhere we have actually been. Thin, so a fortnight of it
   over a town is still a map and not a scribble. */
.me-track { filter: drop-shadow(0 0 1px rgba(255,255,255,.7)); }
@media (prefers-color-scheme: dark) {
  .me-track { filter: none; opacity: .9; }
}
/* A stop: one fix with a long silence either side of it, so there is no line
   to draw. Small enough not to read as one of the trip's own pins. */
.me-stop { filter: none; }

/* When the logger last posted: a banner that slides up from the foot of the
   map and goes away again. Inside the map container, so "the bottom" is the
   bottom of the map and not of whatever is stacked under it.

   Out of the flow entirely until it is wanted — visibility rather than
   display, so there is something to transition from — and it never takes a
   tap: the map underneath is the thing you are using. */
.map-fixnote {
  position: absolute; z-index: 530;
  left: .6rem; right: .6rem; bottom: .6rem; margin: 0;
  padding: .42rem .6rem; border-radius: 10px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--card) 94%, transparent);
  backdrop-filter: saturate(1.4) blur(6px);
  font-size: .78rem; line-height: 1.3; color: var(--ink); text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 10px rgba(70,45,30,.22);
  pointer-events: none; visibility: hidden;
  transform: translateY(130%); opacity: 0;
  transition: transform .3s ease, opacity .3s ease;
}
.map-fixnote.is-in { visibility: visible; }
.map-fixnote.is-up { transform: translateY(0); opacity: 1; }
/* Gone quiet. Clay, and heavier, but still a nudge rather than an alarm: an
   hour at a lunch table is a perfectly good reason for silence. */
.map-fixnote.is-stale {
  border-color: var(--clay); color: var(--clay); font-weight: 600;
}
@media (prefers-reduced-motion: reduce) {
  .map-fixnote { transition: opacity .2s ease; transform: none; }
}

/* A jump: two fixes too far apart to be two steps, and the line between them
   is not the route. Dotted to say so — but it keeps .me-track's white outline
   rather than turning it off, because without it a dotted weight-2 line over
   a pale tile is not a cautious line, it is no line: "I seem to be missing
   lots of green routes."

   The ground in front of the line — your last dot to where you are standing —
   wears the same dots, because it is the same claim, and a shade heavier
   because it is the one still happening. It redraws on every fix and goes
   when Live does. */
.me-lead { pointer-events: none; }

/* The line you are drawing with your feet, right now — "build my green line
   as I'm walking live". The same green as the trail, because it is the same
   kind of fact; heavier and under a white halo, because this is the one being
   made and it has to read against whatever tile it crosses. The halo carries
   the drop shadow for both, so the green itself stays a clean colour.

   Both are ground, never a target: nothing here takes a tap. */
.me-walk, .me-walk-halo { pointer-events: none; }
.me-walk-halo { filter: drop-shadow(0 0 1px rgba(0,0,0,.25)); }
@media (prefers-color-scheme: dark) {
  /* A white halo on a dark map is a glow, which is right, but it needs less
     of itself to stop the green reading as mint. */
  .me-walk-halo { opacity: .35; filter: none; }
}

/* A ticked-off pin shows the tick rather than its number. */
.dot-seen { background: var(--olive); color: #fff; border-color: var(--card); }
/* And one you have ruled out: the same "nothing left to do here", in the
   colour the shortlist already uses for no. After .dot-must deliberately, so
   a must-go you have settled stops shouting. */
.dot-skip {
  background: var(--v-no); color: #fff; border-color: var(--card); opacity: .65;
}

/* ---------- a pin dropped by hand ----------

   "I long pressed on map and it didn't offer to create a place there. It
   wants me to fill in the form... I want a pin at those coordinates."

   So the pin is on the map before anything is decided, and it says so: a
   dashed ring in the clay the app uses for "this is yours", not a numbered
   dot like the things that are really on the list. The offer is the card on
   it, with the name already looked up off the map. */
.dot-new {
  background: var(--card); color: var(--clay);
  border: 2px dashed var(--clay); font-weight: 800;
}
/* The same dashed circle, for a place the search has found and you have not
   yet added — "show where the pin would be before I decide to press the
   add". Blue rather than clay, because it is the map answering the form
   rather than a spot you pressed yourself, and it is the colour the route
   line to it is drawn in. */
.dot-ask { color: #1f6feb; border-color: #1f6feb; }
.pin-new strong { display: block; }
.pin-new-at { color: var(--ink-soft); }
.pin-new .pin-new-name {
  display: block; width: 100%; box-sizing: border-box; margin: .45rem 0 0;
  padding: .4rem .5rem; border: 1px solid var(--line); border-radius: 8px;
  background: var(--paper); color: var(--ink);
  /* 16px or iOS zooms the whole page in on focus and never zooms back out.
     Every focusable field on this site is 16px for that reason, and a suite
     walks the stylesheet to keep it that way. */
  font: inherit; font-size: 16px;
}
.pin-new .pin-mark { font-size: .82rem; }

/* Visually hidden, still read aloud. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.ideas-link { display: block; font-size: .78rem; margin-top: .15rem; padding-left: 1.65rem; }

/* An idea with its own glyph — a paddle for pickleball — in place of its
   number, on the pin and in the list alike. */
.dot-glyph { font-size: .8rem; font-weight: 400; letter-spacing: 0; }
.ideas-no.is-glyph {
  border-color: transparent; background: transparent;
  font-size: .95rem; font-weight: 400;
}


/* ---------- where you are, and Live mode ---------- */

.me-wrap { background: none; border: 0; }
.me-dot {
  position: relative;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--gps); border: 3px solid #fff;
  box-shadow: 0 1px 6px rgba(0,0,0,.45);
}
/* The ring that says this is a live fix rather than a dropped pin. */
.me-dot i {
  position: absolute; inset: -8px; border-radius: 50%;
  border: 2px solid var(--gps);
  animation: me-ping 2.2s ease-out infinite;
}
@keyframes me-ping {
  0%   { transform: scale(.55); opacity: .7; }
  100% { transform: scale(1.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .me-dot i, .map-live.is-on .live-blip { animation: none; }
  .me-dot i { opacity: .4; }
}

/* What is closest, while you walk. Only ever shown full screen: in a 46vh
   box there is no room for both a map and a list. */
.livebar {
  /* 0 1: it may not grow, and it MUST be able to shrink. Unshrinkable, it
     kept its full height against a map that would not go below its own floor,
     and the sum came to more than the screen: 38vh of map + 42vh of bar + the
     head is 720 px where a 390x844 phone in standalone has 647 px to give
     once the top bar (~100 px with the inset) and the tab bar (62 + 34) are
     out. The overflow was the bottom of the list, below the window, and the
     bar's own scrollbar could not reach it because the box itself was off the
     screen. Shrinking turns that into a scroll inside the bar instead. */
  flex: 0 1 auto;
  border-top: 1px solid var(--line); background: var(--card);
  /* The tab bar sits below it and owns the bottom inset; only the full-window
     map above 720 px has to pay for it itself. */
  padding: .55rem .8rem calc(.6rem + var(--tabbar-h) + var(--sab));
  max-height: 42vh; overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  min-width: 0;
}
@media (min-width: 720px) {
  .livebar { padding-bottom: calc(.6rem + var(--sab)); }
}
.place-mapcard:not(.is-expanded) .livebar { display: none; }

/* A floor under the head, so the Recentre button appearing inside it does
   not nudge everything below by a few pixels. */
.livebar-head { display: flex; align-items: baseline; gap: .5rem; min-height: 30px; }
/* One line, always. A long name wrapping to two would grow the bar, shrink
   the map and shift the whole card — on every single GPS fix. */
.livebar-now {
  font-size: 1rem; line-height: 1.3; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.livebar-dist {
  color: var(--clay); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.livebar-acc {
  margin-left: auto; flex: 0 0 auto; font-variant-numeric: tabular-nums;
}
.livebar-recentre {
  flex: 0 0 auto; padding: .22rem .5rem; cursor: pointer;
  border: 1px solid var(--gps); border-radius: 7px;
  background: var(--gps); color: #fff;
  font-size: .72rem; font-weight: 700;
}

.livebar-list { list-style: none; margin: .5rem 0 0; padding: 0; }
/* A fixed row height, for the same reason: the name ellipsises rather than
   wrapping, so a row is always exactly one row tall however long the name. */
.livebar-list li {
  display: flex; align-items: center; gap: .55rem;
  height: 40px; padding: 0 .25rem; border-radius: 8px; cursor: pointer;
}
.livebar-list li + li { border-top: 1px solid var(--line); }
@media (hover: hover) and (pointer: fine) {
  .livebar-list li:hover { background: color-mix(in srgb, var(--clay) 9%, transparent); }
}

/* The page is zoomable — initial-scale=1 and nothing forbidding it — so Safari
   holds every tap for about a third of a second to see whether a second one
   is coming and it should zoom instead. On a row that marks something off,
   that third of a second is the whole interaction: you press the tick, the
   row sits there, and it only goes when you give up and press the next one —
   "when I mark as seen it doesn't disappear right away". These rows and their
   buttons are not things you double-tap, and saying so takes the wait away.
   The page stays pinch-zoomable, which is the part that matters. */
.livebar-list, .livebar-list li, .livebar-tick, .livebar-skip,
.livebar-routeoff, .livebar-routego, .livebar-recentre {
  touch-action: manipulation;
}
.livebar-mark {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--line); color: var(--ink);
  font-size: .72rem; font-weight: 700;
}
.livebar-mark.is-seen { background: var(--olive); color: #fff; }
.livebar-mark.is-must { background: var(--clay); color: #fff; }
.livebar-name {
  flex: 1 1 auto; min-width: 0; font-size: .86rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* JUST ADDED. A row added across the river sorts twenty places down the list,
   which reads exactly like an add that did not take — "when I add it show it
   at the top of the list for about 30 seconds so I can see the row was
   added". So it is lifted to the top and tinted, and says the word: the top
   of the list is where the nearest thing sits anyway, so position alone is
   not an answer. It falls back into distance order half a minute later — see
   justNew in placemap.js. */
.livebar-list li.is-new {
  background: color-mix(in srgb, var(--olive) 14%, transparent);
}
.livebar-new {
  flex: 0 0 auto; font-size: .66rem; font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase; color: var(--olive);
  border: 1px solid color-mix(in srgb, var(--olive) 45%, transparent);
  border-radius: 999px; padding: .08rem .3rem; white-space: nowrap;
}
/* Narrow phones: the name, the distance and the marks are the row. The tint
   and the lift still say it was added. */
@media (max-width: 360px) {
  .livebar-new { display: none; }
}

.livebar-far {
  flex: 0 0 auto; text-align: right;
  font-size: .82rem; font-weight: 700; color: var(--clay);
  font-variant-numeric: tabular-nums;
}
.livebar-far small {
  display: block; font-size: .68rem; font-weight: 400; color: var(--ink-soft);
}
/* The one row whose distance was measured along the line drawn on the map,
   rather than straight through the buildings and the river. Blue, because
   that is the colour of the line it was measured along. */
.livebar-far.is-routed { color: #1f6feb; }
.livebar-far.is-routed small { color: #1f6feb; opacity: .8; }
.livebar-msg { margin: .55rem 0 0; }
.livebar-msg.is-bad { color: var(--clay); font-weight: 600; }

/* Anything geolocation needs to say outside Live mode, where there is no bar. */
.map-geonote {
  position: absolute; z-index: 520;
  left: .6rem; right: 3.6rem; top: .5rem; margin: 0;
  padding: .45rem .6rem; border-radius: 9px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--card) 95%, transparent);
  backdrop-filter: blur(6px);
  font-size: .78rem; line-height: 1.35; color: var(--ink);
  box-shadow: 0 2px 8px rgba(70,45,30,.2);
}
.place-mapcard.is-expanded .map-geonote { top: calc(.5rem + env(safe-area-inset-top)); }

/* Ticking a thing off from the live bar. It presses the page's own box, so
   it is made to look like the one in the list below the map. */
.livebar-tick {
  flex: 0 0 auto; width: 30px; height: 30px; padding: 0; margin-left: .15rem;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--card); color: var(--ink-soft);
  font-size: .8rem; line-height: 1;
}
/* A phone has no pointer to hover with, and Safari leaves :hover applied to
   whatever you last tapped until you tap something else. So the button you
   pressed stayed green, and when the list re-sorted under your finger the
   green landed on whichever row had moved into that spot — "showed green
   checkmark on another row instead". Hover is for a mouse; a finger gets the
   pressed state below, which lasts exactly as long as the press. */
@media (hover: hover) and (pointer: fine) {
  .livebar-tick:hover {
    background: var(--olive); border-color: var(--olive); color: #fff;
  }
}
.livebar-tick:active {
  background: var(--olive); border-color: var(--olive); color: #fff;
}

/* Ruling one out from the live bar: the same button, the other answer. Both
   press the real boxes in the list this page keeps for them. */
.livebar-skip {
  flex: 0 0 auto; width: 30px; height: 30px; padding: 0; margin-left: .15rem;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--card); color: var(--ink-soft);
  font-size: .78rem; line-height: 1;
}
@media (hover: hover) and (pointer: fine) {
  .livebar-skip:hover {
    background: var(--v-no); border-color: var(--v-no); color: #fff;
  }
}
.livebar-skip:active {
  background: var(--v-no); border-color: var(--v-no); color: #fff;
}
/* Two buttons on the row now, so the name gives up the space rather than the
   distance wrapping. A narrow phone shrinks the gaps, not the targets. */
@media (max-width: 389px) {
  .livebar-list li { gap: .35rem; }
  .livebar-tick, .livebar-skip { width: 28px; height: 28px; margin-left: 0; }
}

/* ---------- which way you are facing ---------- */

/* A beam narrow at you and widening away, the way every map app draws it.
   Rotated about the dot's centre; the map is north-up, so 0deg is north.
   Hidden until a heading actually arrives — a cone pointing nowhere in
   particular is worse than no cone. */
.me-cone {
  display: none;
  position: absolute; left: 50%; top: 50%;
  width: 0; height: 0; margin: -40px 0 0 -14px;
  border-left: 14px solid transparent;
  border-right: 14px solid transparent;
  border-top: 28px solid var(--gps);
  opacity: .4;
  transform-origin: 50% 40px;
  pointer-events: none;
}

/* ---------- the way to the place you picked ---------- */

.livebar-route {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .45rem; padding: .32rem .25rem .32rem .5rem;
  border-radius: 8px; font-size: .82rem;
  background: color-mix(in srgb, var(--gps) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--gps) 32%, transparent);
}
.livebar-routetext {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 🚷 rather than ✕ — "replace the x in row with Cancel walk path icon". An ✕
   on this map already means "won't do": it is the mark on a pin's sticker and
   in the live bar's rows, so an ✕ on the route row was asking to be read as
   skipping the place rather than putting the line away. */
.livebar-routeoff {
  flex: 0 0 auto; width: 26px; height: 26px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--card); color: var(--ink-soft);
  /* Bigger than the ✕ it replaced: a sign emoji carries its own colours and
     a little detail, and at .68rem it was a smudge. */
  font-size: .95rem; line-height: 1;
}
.livebar-routeoff:hover { background: var(--clay); border-color: var(--clay); color: #fff; }

/* The way out of this map and into the one that talks. "I want icon in live
   rows to take a walk path into Google Maps app" — the route row is that
   walk path, so the hand-off sits on it.
   Filled in the GPS blue rather than outlined like its neighbour: these two
   buttons are a pair of opposites — go, and put the line away — and the one
   you reach for should not have to be read first. */
.livebar-routego {
  flex: 0 0 auto; height: 26px; padding: 0 .4rem;
  display: grid; grid-auto-flow: column; place-items: center;
  border-radius: 13px; border: 1px solid var(--gps);
  background: var(--gps); color: #fff;
  /* A pill, not a circle: the icon is two glyphs — the way you are going and
     the map it is going into — and a 26 px circle would have cut it in half.
     Wider is also easier to hit, which suits the one button on this row you
     actually mean to press. */
  font-size: .78rem; line-height: 1; letter-spacing: -.04em;
  text-decoration: none; white-space: nowrap;
}
.livebar-routego:hover, .livebar-routego:focus { text-decoration: none; color: #fff; }
.livebar-routego:active { transform: scale(.9); }
.livebar-routego:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* A dashed line is the straight one — no walking route came back, so it is
   not a way to go, only a direction and a lower bound. */
.leaflet-pane path.me-route.is-crow { opacity: .55; }

/* ---------- the Live tab ---------- */

/* The Live tab is the one you hold in your hand in the street, so it gets the
   same deal as the map page: no top bar. The bar carried the trip name and
   the account button, neither of which helps you find a street, and it cost
   54 px on a 812 px screen on top of the 50 px the status bar already takes.
   The picker below it already says which town this is, so it becomes the
   header and wears the status bar's inset itself.

   The strip below the window is the tab bar's own skirt now, in the bar's
   colour, so the page keeps the paper every other tab has. */
@media (max-width: 719px) {
  body.view-live .place-livemap .live-head {
    padding-top: calc(.4rem + env(safe-area-inset-top));
    padding-bottom: .4rem;
  }
}


/* Live on a city's own page is a mode you switch the map into, and there it
   goes fixed and full-screen over everything. The Live TAB cannot do that:
   full-screen covers the tab bar, which would leave a tab you could get into
   and not out of. So the page itself is the full screen instead — it does not
   scroll, and the map takes whatever the header and the tab bar leave. */
body.view-live {
  /* In this order on purpose: each is a fallback for the one after it, and
     the percentage — the window the tab bar is actually pinned to — wins
     wherever html has a height to resolve it against. */
  height: 100vh; height: 100dvh; height: 100%;
  overflow: hidden;
  display: flex; flex-direction: column;
}
body.view-live .page {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  max-width: none;
  /* margin: 0, and it matters more than it looks. .page carries
     `margin: 0 auto` to centre itself on a desktop, and auto margins in a
     flex container's cross axis turn stretch off: the page then sizes itself
     to fit its content, and fit-content never goes below min-content. One
     long nowrap line in the live bar — "Coeo Santa Maria Signature
     Apartments 10 m N" is 354 px — made the page 420 px wide inside a 375 px
     phone, and everything pinned to its right edge went off the screen with
     it: the crosshair, the trail switch, the expand button and Live. */
  margin: 0;
  /* Nothing reserved at the foot: the map and the list run under the bar,
     which is translucent, so the screen ends in content rather than in a
     band. The list keeps its own room to scroll the last row clear. */
  padding: 0;
}
/* body is the column, so the sticky header stays in flow and the map below it
   needs no height arithmetic that could go stale. */

.place-mapcard.place-livemap {
  flex: 1 1 auto; min-height: 0; min-width: 0; max-width: 100%;
  display: flex; flex-direction: column;
  border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0;
  box-shadow: none; margin: 0;
}
.place-mapcard.place-livemap #placemap {
  flex: 1 1 auto; height: auto;
  /* A floor, not min-height: 0. The height here comes from the flex chain —
     body 100dvh, the page, the card, this — and if any link in that chain
     comes out at nothing on some browser, a map with no floor is nought
     pixels tall: it paints once and is gone. */
  min-height: 38vh;
  border-radius: 0; border: 0;
}

/* A short screen — landscape, or a small old phone — has no 38vh of map to
   spare: there the floor comes down, so the list keeps more than its own
   head. */
@media (max-height: 560px) {
  .place-mapcard.place-livemap #placemap { min-height: 120px; }
  .livebar { max-height: 55vh; }
  /* The tool column is 113px of buttons and the map here is down to 120. The
     column lives inside the Leaflet container now, and that clips, so in
     landscape the trail switch lost its bottom edge. No vertical room means
     go across: a row of four is 34px tall and the map is the full width. */
  .place-livemap .map-tools { flex-direction: row; align-items: flex-start; }
}

/* The bar is the point of this page, not a thing that appears when the map is
   blown up, so it shows at the tab's ordinary size too. */
.place-mapcard.place-livemap:not(.is-expanded) .livebar { display: block; }
/* Stretched, on this tab, means the map takes the list's room.

   The card already fills the page here, so the expand button had nothing to
   expand into and pressing it changed nothing at all — it set position:fixed
   over the same rectangle. The only space left to give is the list's, so
   that is what it gives, and the button's label says so. Pressing it again
   brings the list back. */
.place-mapcard.place-livemap.is-expanded .livebar { display: none; }
/* What the map's floor leaves, up to 42vh, and it scrolls — see .livebar on
   why it has to be able to give ground. The map keeps its 38vh whatever the
   list wants, because a walking map smaller than that is not a map. */

.live-head { align-items: center; gap: .6rem; }
.live-pick { margin: 0; display: flex; align-items: center; gap: .4rem; }
.live-pick select {
  /* 16px for the same reason: a select that takes focus at 15.2px zooms the
     page too, and this one is on the tab you live in. */
  font: inherit; font-weight: 700; font-size: 16px;
  color: var(--ink); background: transparent;
  border: 1px solid var(--line); border-radius: 8px;
  padding: .2rem 1.6rem .2rem .45rem;
  max-width: 55vw;
  /* The arrow, drawn rather than left to the platform, which otherwise gives
     a full-height grey slab on a head this shallow. */
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.live-why { margin-left: auto; white-space: nowrap; }

/* The tick boxes the live bar ticks through. Present, deliberately not laid
   out: the live bar IS this list, sorted by distance and with what you have
   seen dropped out. See the note in views/live.php. */
.live-ticks { display: none; }

/* ---------- seven tabs ---------- */

/* "Itinerary" in a seventh of a 390 px phone needs the smaller type, so the
   breakpoint that used to be for narrow phones now covers ordinary ones. */
@media (max-width: 429px) {
  .tabbar a { font-size: .6rem; letter-spacing: -.01em; }
  .tab-icon { font-size: 1.1rem; }
}
@media (max-width: 359px) {
  .tabbar a { font-size: .55rem; }
  .tab-icon { font-size: 1rem; }
}

/* The trail endpoint's own log, on the account page. Two columns, because
   the question it answers is only ever "did anything arrive, and when". */
.gpslog { width: 100%; border-collapse: collapse; margin-top: .4rem; }
.gpslog td { padding: .25rem .4rem; border-top: 1px solid var(--line); vertical-align: top; }
.gpslog tr:first-child td { border-top: 0; }
.gpslog td:first-child { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--ink-soft); width: 1%; }

/* ---------- no signal ---------- */

/* A strip that sits on top of the tab bar.
 *
 * It has to be here rather than at the top of the page, and the reason is
 * the whole design of the offline copy: a saved page looks exactly like a
 * live one, which is the point, so nothing about the page itself can tell
 * you the network is gone. The one moment that difference matters is when
 * you tap something and it does not take — and your thumb is already at the
 * bottom of the screen, on the bar, which is where this is.
 *
 * Above the bar and not inside it: the bar's own geometry took days to get
 * right on this phone (see the notes on --sab) and a message that appears
 * and disappears must not be able to move it.
 */
.netbar {
  position: fixed; z-index: 70;
  left: 0; right: 0; bottom: calc(var(--tabbar-h) + var(--sab));
  padding: .5rem max(.9rem, env(safe-area-inset-left)) .55rem
                 max(.9rem, env(safe-area-inset-right));
  font-size: .78rem; font-weight: 600; line-height: 1.35;
  text-align: center;
  color: #fff; background: var(--sea);
  border-top: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 -6px 18px rgba(70, 45, 30, .10);
}
/* Offline, sitting on the saved copy: slate, because this is a statement of
   fact and not a problem to be solved. */
.netbar.is-off  { background: var(--sea); }
/* Something was just lost: the one colour on this site that means stop. */
.netbar.is-warn { background: #9a2b1e; }
/* The copy was just refreshed. Olive, and it takes itself away. */
.netbar.is-ok   { background: var(--olive); }
.netbar[hidden] { display: none; }

/* With no network there is nothing behind a write button but disappointment,
   so they are dimmed — still pressable, because the strip explaining why is
   what a press produces, and a button that cannot be pressed explains
   nothing. */
body.is-offline form button[type="submit"],
body.is-offline .seen-box,
body.is-offline .skip-box,
body.is-offline .verdict button {
  opacity: .5;
}

/* The offline panel on the account page. */
.offline-stat { font-variant-numeric: tabular-nums; }
.offline-bar {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--line); margin: .5rem 0 .25rem;
}
.offline-bar span { display: block; height: 100%; background: var(--olive); }


/* ---------- where you must not drive ----------
   "Order in red the traffic restricted center in each city I'm in or going",
   and then the part that matters on the road: "you should pin me outside the
   restricted zone since you don't want me driving in there. Closest spot to
   walk to hotel and start looking for parking."

   So this box sits above everything else on a stay, says which zone the
   hotel is in, and leads with a car park outside the line and the walk from
   it. See zone_for_event() and data/zones.php. */
.zonebox {
  border-left: 4px solid var(--red);
  background: color-mix(in srgb, var(--red) 9%, transparent);
}
.zonebox-head {
  display: flex; align-items: baseline; gap: .5rem;
  font-size: .98rem; line-height: 1.35;
}
.zonebox-dot {
  flex: 0 0 auto; width: .6rem; height: .6rem; border-radius: 50%;
  background: var(--red); align-self: center;
}
.zonebox-where {
  margin: .35rem 0 0; font-size: .86rem; font-weight: 700; color: var(--red);
}
.zonebox-park {
  margin-top: .6rem; padding: .55rem .7rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.zonebox-park-head {
  display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap;
}
.zonebox-walk {
  margin-left: auto; flex: 0 0 auto;
  font-size: .8rem; font-weight: 800; color: var(--olive);
  font-variant-numeric: tabular-nums;
}
.zonebox-park p { margin: .3rem 0 .35rem; }
.zonebox-more { margin-top: .5rem; }
.zonebox-more > summary {
  cursor: pointer; font-size: .84rem; font-weight: 700; color: var(--ink-soft);
}
.zonebox-more p { margin: .4rem 0; }
.zonebox-list { margin: .4rem 0 0; padding-left: 1.1rem; }
.zonebox-list li { margin-bottom: .45rem; }

/* And the same thing on a map: the ring itself, the gate that watches it,
   and the car park to aim for instead. */
.zone-pin, .park-pin {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  font-size: .72rem; line-height: 1;
  border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}
.zone-pin { background: var(--red); color: #fff; }
.park-pin { background: var(--olive); color: #fff; font-weight: 800; }
.zone-tip strong { color: var(--red); }
.zone-key {
  display: flex; align-items: center; gap: .35rem;
  /* Under the map, close to it: on a phone this sits inside the box the map
     is measured out of, so every millimetre here is a millimetre of map. */
  margin: .35rem 0 0;
  font-size: .76rem; color: var(--ink-soft);
}
.zone-key::before {
  content: ''; width: 1.1rem; height: 0;
  border-top: 3px solid var(--red);
}


/* ---------- the recap: what the trip came to ----------

   Not a tab, and the one page meant to be handed to somebody who was not
   there — so it is allowed to be the widest thing on the site and to lead
   with a map rather than with a list. See views/recap.php. */

.recap-head { margin: .4rem 0 1rem; }
.recap-head h1 { margin: 0 0 .2rem; }
.recap-dates { margin: 0; color: var(--ink-soft); }
.recap-live {
  display: inline-block; margin-left: .4rem; padding: .1rem .45rem;
  border-radius: 999px; font-size: .74rem; font-weight: 700;
  background: color-mix(in srgb, var(--clay) 14%, transparent); color: var(--clay);
}

.recap-mapcard { padding: 0; overflow: hidden; }
.recap-map-wrap { position: relative; }
#recapmap { height: 58vh; min-height: 320px; width: 100%; background: var(--line); }

/* The clock sits on the map rather than under it: it is the one thing that
   has to be readable while you are watching the line move. */
.recap-clock {
  position: absolute; z-index: 420; top: .5rem; left: .5rem;
  display: flex; flex-direction: column; gap: .05rem;
  padding: .35rem .6rem; border-radius: 12px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  box-shadow: 0 1px 6px rgba(0, 0, 0, .18);
  pointer-events: none;
}
.recap-clock-day { font-weight: 800; font-size: .92rem; letter-spacing: -.01em; }
.recap-clock-at { font-size: .76rem; color: var(--ink-soft); min-height: .9em; }

/* Arriving somewhere. Across the middle of the map for a couple of seconds,
   over everything, catching nothing: the clock in the corner says where we
   are the whole time and is therefore easy to stop reading, while arriving
   is an event. Hidden by default, and hidden from assistive tech with it —
   aria-live announces it when it is given something to say. */
.recap-arrive {
  position: absolute; z-index: 430; left: 0; right: 0; top: 38%;
  display: flex; flex-direction: column; align-items: center; gap: .1rem;
  padding: 0 1rem; text-align: center; pointer-events: none;
  opacity: 0; transition: opacity .45s ease, transform .45s ease;
  transform: translateY(6px);
}
.recap-arrive.is-on { opacity: 1; transform: translateY(0); }
.recap-arrive-name {
  font-size: clamp(1.6rem, 9vw, 2.9rem); font-weight: 800;
  letter-spacing: -.02em; line-height: 1.05; color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .55), 0 0 2px rgba(0, 0, 0, .8);
}
.recap-arrive-sub {
  font-size: .8rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: #fff;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .6), 0 0 2px rgba(0, 0, 0, .9);
}
@media (prefers-reduced-motion: reduce) {
  .recap-arrive { transition: none; transform: none; }
}

.recap-expand {
  position: absolute; z-index: 420; top: .5rem; right: .5rem;
  width: 34px; height: 34px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font-size: 1rem; line-height: 1;
}

.recap-controls {
  display: flex; align-items: center; gap: .6rem;
  padding: .6rem .7rem; border-top: 1px solid var(--line);
}
.recap-play {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  border: none; cursor: pointer; background: var(--clay); color: #fff;
  font-size: .9rem; line-height: 1;
}
.recap-scrub { flex: 1; min-width: 0; accent-color: var(--clay); }
/* The camera is on by default and the map can be dragged off it, so this is
   a state and not an action: lit while it is following. */
.recap-follow {
  flex: 0 0 auto; width: 32px; height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
  font-size: .95rem; line-height: 1;
}
.recap-follow.is-on { background: var(--clay); border-color: var(--clay); color: #fff; }
.recap-speeds { display: flex; gap: .15rem; flex: 0 0 auto; }
.recap-speeds button {
  padding: .25rem .4rem; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-soft);
  font-size: .74rem; font-weight: 700;
}
.recap-speeds button.is-on { background: var(--clay); border-color: var(--clay); color: #fff; }
.recap-mapnote { padding: 0 .8rem .8rem; margin: 0; }

/* Full-bleed, above the bars, the same trick the other maps use. */
.recap-mapcard.is-wide {
  position: fixed; inset: 0; z-index: 900; margin: 0;
  border-radius: 0; border: none; display: flex; flex-direction: column;
}
.recap-mapcard.is-wide .recap-map-wrap { flex: 1; min-height: 0; }
.recap-mapcard.is-wide #recapmap { height: 100%; }
.recap-mapcard.is-wide .recap-mapnote { display: none; }
body.recap-wide { overflow: hidden; }

/* ---------- the figures ---------- */

.recap-grid {
  display: grid; gap: .6rem; margin: 0 0 .3rem;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
}
.recap-stat {
  padding: .7rem .8rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--card);
  display: flex; flex-direction: column; gap: .1rem;
}
.recap-stat b {
  font-size: 1.5rem; line-height: 1.1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--clay);
}
.recap-stat span { font-size: .78rem; color: var(--ink-soft); }
/* A figure that is context rather than headline. */
.recap-stat.is-soft b { font-size: 1.15rem; color: var(--ink); }
.recap-caveat { margin: .5rem 0 0; }

/* ---------- where we stopped ---------- */

.recap-stops { list-style: none; margin: .4rem 0 0; padding: 0; }
.recap-stop {
  display: flex; gap: .7rem; align-items: baseline;
  padding: .5rem 0; border-top: 1px solid var(--line);
}
.recap-stop:first-child { border-top: none; }
.recap-stop-when {
  flex: 0 0 5.4rem; display: flex; flex-direction: column;
  font-size: .78rem; font-variant-numeric: tabular-nums;
}
.recap-stop-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .1rem; }
.recap-stop-name { font-weight: 600; }
.recap-stop-blank { color: var(--ink-soft); font-weight: 500; }
.recap-stop.is-meal .recap-stop-name { font-weight: 700; }
.recap-tick { color: #0f9d58; font-weight: 700; }
.recap-skip { color: var(--ink-soft); }
/* The find: pinned, sat at, and nobody wrote it down. */
.recap-new { color: var(--clay); font-weight: 700; }
.recap-missed { border-left: 3px solid var(--clay); }
.recap-missed h3 { margin: 0 0 .2rem; font-size: 1rem; }

/* ---------- the standouts ---------- */

.recap-tops { list-style: none; margin: 0; padding: 0; }
.recap-tops li {
  display: flex; align-items: center; gap: .7rem;
  padding: .55rem 0; border-top: 1px solid var(--line);
}
.recap-tops li:first-child { border-top: none; }
.recap-top-icon { font-size: 1.3rem; line-height: 1; }
.recap-top-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.recap-top-what { font-weight: 600; }
.recap-top-day { flex: 0 0 auto; font-size: .8rem; font-weight: 700; }

/* ---------- what we saw ---------- */

.recap-town + .recap-town { margin-top: .9rem; }
.recap-town h4 {
  margin: 0 0 .25rem; font-size: .92rem;
  display: flex; align-items: baseline; gap: .4rem;
}
.recap-saw { margin: 0; padding-left: 1.1rem; }
.recap-saw li { padding: .1rem 0; }
.recap-must {
  display: inline-block; margin-left: .3rem; padding: 0 .35rem;
  border-radius: 999px; font-size: .66rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .04em;
  background: color-mix(in srgb, var(--clay) 14%, transparent); color: var(--clay);
}
.recap-who { margin-left: .3rem; }

/* ---------- day by day ---------- */

.recap-daystable-wrap { overflow-x: auto; }
.recap-days { width: 100%; border-collapse: collapse; font-size: .84rem; }
.recap-days th {
  text-align: left; padding: .3rem .5rem .4rem; font-size: .7rem;
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
}
.recap-days td { padding: .4rem .5rem; border-bottom: 1px solid var(--line); }
.recap-days tr:last-child td { border-bottom: none; }
.recap-days .recap-n { text-align: right; font-variant-numeric: tabular-nums; }
.recap-days .recap-sleep { max-width: 10rem; }
/* A day the trip has not reached yet. */
.recap-days tr.is-future td { opacity: .45; }
.recap-foot { margin: 1.2rem 0 0; }

@media (min-width: 900px) {
  #recapmap { height: 62vh; }
  .recap-stop-when { flex-basis: 6.2rem; }
}

/* The way in to the recap. Afterwards it is a row like the account one;
   while the trip is still on it is one quiet line, because Today is for
   today. See views/today.php. */
.recap-way { margin-top: .5rem; }
.recap-peek { margin: 1.1rem 0 0; }

/* The one figure on the recap that nobody estimated: the car's own clock.
   Marked, because next to two estimates a counted number deserves to be
   told apart. See recap_stats(). */
.recap-stat.is-counted { border-color: var(--clay); }
.recap-stat.is-counted b { font-variant-numeric: tabular-nums; }
