/* ============================================================
   LOKO app
     HOME     : full-screen map — every local-route spot pinned.
                Tap a pin → its route draws → card → open it.
     DISCOVER : search (routes, creators, areas, spot names).
     SAVED / ME
     PLANNER  : the per-trip feature (Overview / Schedule / Docs).
   English UI · Naver directions · Leaflet + OSM/CARTO (key in data.js).
   ============================================================ */
:root{
  /* ---- the tokens — design system v14 (2026-09-26) ----
     Every value comes from design/v14/system/tokens.json, the code copy of
     the artifact. The code's names are kept (NEXT.md 8, step 1), so this is
     which name is which. A value that is not in that file is not a value:
     it goes into the design system first, then here, never the other way.

       code                                v14
       --brand-navy --accent --ink-btn     navy
       --focus                             focus (= navy)
       --accent-hover                      navy-hover
       --fill-2 --accent-wash              navy-soft
       --accent-wash-strong --select-bg    navy-soft-strong
       --on-fill                           on-navy, on-now
       --brand-red --now                   now
       --now-strong                        now-strong
       --now-ink --danger                  now-ink
       --warn-bg / --warn-ink              caution-bg / caution-ink
       --bg                                bg
       --card --chrome --pin-ring          surface
       --surface --map-ground --track      surface-sunken
       --surface-hover --hair2             surface-hover
       --ink --route-line                  ink
       --soft                              ink-muted
       --faint                             ink-subtle
       --select-edge (at --select-w 1.5px) select-edge
       --hair                              hairline
       --line --frame                      border
       --line-strong                       border-strong
       --scrim                             overlay
       --fs-screen / --fs-title            display 25 / title, title-display 21
       --fs-body                           body, body-strong, title-sm 17
       --fs-meta                           body-sm, label 15
       --fs-micro                          caption, label-sm, overline 13
       --fs-nano                           micro 11
       --fw-regular / -medium / -bold      400 / 600 / 700
       --tr-title / --tr-caps              title / overline letter-spacing
       --tr-display                        display, display-lg letter-spacing
       --font / --font-display             families.sans / families.display (Fredoka)
       --s-1 ... --s-8, --rail             space-1 ... space-8, space-9
       --gutter --card-pad / --stack       space-4 / space-3
       --section                           space-6
       --rad-2 / --rad-1                   radius-lg / radius-sm
       --rad-pill / --rad-round            radius-pill / radius-full
       --e-1 --e-2 --e-3 --e-4             shadow-sm -md -lg -sheet
       --tap --btn-sm-h --btn-lg-h         tap, btn-sm, btn-lg
       --nav-h                             nav
       --ic-1 --ic-2 --ic-3 --ic-stroke    icon-1 -2 -3, icon-stroke
       --koko-row / --koko-now             koko-row 32 / koko-now 40
       --koko-empty / --koko-ride          koko-empty 72 / koko-ride 80
       --dur-press / --dur-state           dur-press 80 / dur-state 150
       --dur-release / --dur-enter         dur-release 180 / dur-enter 220
       --dur-sheet                         dur-sheet 300
       --dur-loop-march / --dur-loop-flow  dur-loop-march 1300 / dur-loop-flow 1100 (map only)
       --dur-spin                          dur-spin 800 (the Loading ring, v14.9)
       --ease-out / --ease-sheet           ease-out / ease-sheet
       --ease-pop, --t-pop                 ease-pop, and its 320ms

     Not in v14, and derived from what is: --on-fill-soft (on-navy at .8),
     --chrome-line (the edge of map chrome
     until step 4b gives each component its own). In v14 with no user here
     yet: display-lg 34 (the cover, or an empty-state hero at 4b), koko-blush
     and koko-heart (the 2D Koko are images), space-10. The line heights
     stay ratios: v14 writes a px height per style and NEXT.md's steps do
     not move them. */
  --brand-navy:#0247A0; --brand-red:#CB2E3A;

  /* ---- surface + ink ----
     The previous palette was warm beige (#E5DFD4) on espresso near-black
     (#1A1815). That family — cream ground, warm near-black text — is the
     single most over-reached palette in generated interfaces, and it is what
     made the app read as generic rather than as LOKO. It is gone.

     Cool slate ink on a ground a shade off white. Measured against the
     card (#FFFFFF) / the page (#F7F8FB) / a chip (#EDF1F6):
     ink 16.82 / 15.84 / 14.83, soft 4.97 / 4.68 / 4.39,
     faint 5.42 / 5.10 / 4.78.

     Two things these numbers are load-bearing for. --soft has almost no
     headroom left at v10 (#475569 was 7.58 on white; #667085 is 4.97) and
     it FAILS on a chip at 4.39 — so chip labels are set in --ink, and
     putting --soft on a sunken ground is the way to break that quietly.
     --faint is darker than --soft on purpose — see its own note below. */
  /* Pure White is the CARD. The page behind it is a shade cooler — #FFFFFF
     edge to edge is glare, and it also left the card sitting on nothing at
     1.00:1. Measured on the v10 ground #F7F8FB — card separates at 1.06,
     ink 15.84, soft 4.68, faint 5.10. */
  --bg:#F7F8FB; --card:#FFFFFF;
  --ink:#151D2F; --soft:#667085;
  /* ink-subtle. v10 published #7C8494, which measures 3.76 on surface and
     3.54 on bg — it failed as text on both, and in this app every --faint
     use is real words, not decoration (the same lesson that set the value
     before it: #64748B fell to 4.48 on bg and 4.20 on a chip). This app kept
     #5D6B7F — 5.42 / 5.10 / 4.78 on surface / bg / sunken — and flagged it
     back; v14 adopted it (2026-09-26), so the divergence is closed. */
  --faint:#5D6B7F;
  /* The brand hairline. 1.23:1 on white by design — it delineates, it does not
     separate. On a near-white page, hierarchy is carried by type and space;
     a border that fought for attention would be the wrong instrument. */
  --hair:#E4E8F0;
  /* One step stronger, for a divider that genuinely has to be found: a table
     rule, the edge of a control. 1.39:1 on white, against --hair's 1.23. */
  --line:#D5DBE6;
  --hair2:var(--surface-hover);
  --surface:#EDF1F6;  /* the only tinted ground — map, troughs, placeholders */
  /* v10's tonal secondary. This was a cool grey; navy-soft reads as more
     clearly tappable. The most visible change in this pass — check the Follow
     bar, where several secondaries sit side by side. */
  --fill-2:#EEF5FF;

  /* Red carries three jobs at three values, because one red cannot do all
     three on one ground. Measured on surface / bg:
       --now        marks only: map pins, the progress bar, a dot (5.26 / 4.95)
       --now-strong fills that carry white text (white on it is 5.82)
       --now-ink    red as text (7.08 / 6.67)

     READ THIS BEFORE YOU LETTER IN --now. The old ban had a number behind
     it: #E53935 measured 4.23 on white and simply failed, so the rule
     enforced itself. v10's red does not fail. #CB2E3A clears 4.5 on every
     ground in the system — 5.26 surface, 4.95 bg, 4.64 sunken, 4.61 on the
     caution band. Check it and it passes, which means from here the rule is
     held up by this comment and nothing else.

     It still holds, for the reason it always actually had: red is the
     signal for NOW, one or two marks a screen, and a word is not a mark.
     Red stops being a signal the moment it becomes language. That is why
     --now-ink exists separately and is scoped to the rare live label — not
     as a licence to set sentences in red. */
  --now:var(--brand-red); --now-strong:#C0283A; --now-ink:#A9232E;
  /* --now-ring, the red pin's halo, went with the red pin (v14 step 4):
     the stop you are on is Koko now, and Koko has no ring. */

  /* A navy tint, not a new hue. */
  --accent-wash:var(--fill-2);

  /* ---- interactive ----
     Deep Navy is the base and carries every action. LOKO Red is the point,
     and it has one meaning: **now** — the stop you are on, and the button
     that takes you there. One or two marks a screen is what makes a point
     read as a point rather than as a second theme.

     Navy on white is 8.71; white on navy is the same. */
  /* --accent-hover is the one the app genuinely lacked: navy presses were
     being done with opacity. --line-strong is border-strong: v10 published
     #8B93A3, 2.91 on bg — under the 3:1 a control edge needs, and an
     outlined pill on the page background is exactly its case. This app took
     #868EA0, the nearest value holding on both grounds (3.29 surface / 3.09
     bg); v14 adopted it, which closes the second divergence. */
  --accent-hover:#01377D; --accent-wash-strong:#DCEAFF;
  --line-strong:#868EA0;  --surface-hover:#F0F3F8;

  --accent:var(--brand-navy); --ink-btn:var(--brand-navy);
  --danger:var(--now-ink); --focus:var(--brand-navy);
  --on-fill:#FFFFFF; --on-fill-soft:rgba(255,255,255,.80);
  --select-bg:var(--accent-wash-strong);
  /* Selected is ink, never navy (D13, 2026-09-23). Navy is what you DO; a
     selected chip, day, view, tab or stop is where you ARE. One edge, one
     width, wherever a selection is drawn. v14 made it the system's rule
     (select-edge) on 2026-09-26. */
  --select-edge:var(--ink); --select-w:1.5px;

  /* ---- map + chrome over map ----
     What sits on the map is opaque surface: no glass, no blur (v14). The
     chips and labels were white at .96 and the nav was white at .5 over a
     22px blur; measured against the map on 2026-09-24 the blur smeared the
     tiles under the bar, and v14 took glass out of the system. --glass and
     --glass-line are gone with it. */
  --frame:var(--line); --map-ground:var(--surface); --track:var(--surface);
  --chrome:var(--card); --chrome-line:rgba(21,29,47,.10);
  /* Shadows are tinted to the ground they fall on, which is now slate rather
     than warm brown. They appear only where something genuinely floats — the
     Follow bar over the map, the sheet, chrome sitting on tiles. A card on a
     white page is delineated by its hairline, not lifted off the paper. */
  --chrome-shadow:0 2px 6px rgba(15,31,56,.08), 0 6px 16px rgba(15,31,56,.10);
  --lift-shadow:0 4px 10px rgba(15,31,56,.08), 0 18px 36px rgba(15,31,56,.14);
  --scrim:rgba(21,29,47,.55);
  --pin-ring:var(--card); --route-line:var(--ink);

  /* ---- caution — the paid signal, not a category ---- */
  --warn-ink:#8A4B08; --warn-bg:#FCEFD2;

  /* One family for everything. Pretendard is drawn so Latin, Hangul and
     figures share a voice — this app sets an English name beside a Korean one
     constantly, and two files setting one line means two vertical metrics and
     a baseline that wanders. Its Latin is derived from Inter, so nothing is
     given up by not reaching for Inter directly.
     Figures align through tabular-nums, not a monospace face. */
  --font:'Pretendard Variable',Pretendard,system-ui,-apple-system,'Apple SD Gothic Neo',sans-serif;
  /* Fredoka 600 is the display face (v14, 2026-09-26 — over Baloo 2 and
     Nunito; Fraunces retired, it read as a bank on the detail header). Three
     places and no more: a screen title (25), an English route name (21),
     and the cover (34, not in the app). It is LATIN-ONLY, so a line with a
     Korean character in it stays entirely in Pretendard — enforced in
     app.js (displayCls), not here. The stack falls back to Pretendard, so a
     slow load shows the sans, never a system serif. Loaded from Google
     Fonts with display=swap, as the system asks; a Capacitor build will want
     it self-hosted, the same move Pretendard makes at packaging. */
  --font-display:Fredoka,'Pretendard Variable',Pretendard,system-ui,-apple-system,sans-serif;

  /* ---- type — v14's five roles, on the scale that shipped ---- */
  /* micro and meta were holding real sentences — a transit hint, a gate
     promise — at caption size. Both go up a step; body and above are
     unchanged, so the hierarchy keeps its shape and only the floor rises.
     13 is the floor for anything a traveller reads. --fs-nano (v14's
     `micro`, 11) sits under it on purpose and has three users and no
     more: the map attribution, the leg row's minute unit, the tab labels
     — none of them something a traveller has to read. */
  --fs-nano:11px;
  --fs-micro:13px; --fs-meta:15px; --fs-body:17px; --fs-title:21px; --fs-screen:25px;
  --fw-regular:400; --fw-medium:600; --fw-bold:700;
  --lh-tight:1.2; --lh-snug:1.35; --lh-text:1.55; --lh-flat:1;
  --lh-small:1.5;   /* 13-15px — small type needs more air, not less */
  --tr-title:-.02em; --tr-normal:0; --tr-caps:.06em;
  --tr-display:-.01em;   /* v14 display and display-lg — Fredoka at 25 and 34 */
  --ic-1:16px; --ic-2:20px; --ic-3:24px;
  /* Absolute, not proportional. stroke-width inside a 24-unit viewBox scales
     with the box, so one value gave a 1.15px line at 11px and a 2.5px line at
     24px — the same icon in six different weights, which is what made the set
     look muddy at small sizes and blunt at large ones. vector-effect pins the
     stroke to screen pixels, so every icon everywhere is one weight. */
  --ic-stroke:1.9px;
  /* Koko is not an icon (v14.6): its sizes are its own. A leg row and the
     Follow bar 32, the map 40, an empty state 72, the 3D traveller 80.
     --koko-ride has no reader since 2026-09-27: on the map Koko rides at
     the one scale it stands at (--koko-now), so it never changes size on
     arrival (Yoon). Kept to mirror tokens.json; flagged to the manager. */
  --koko-row:32px; --koko-now:40px; --koko-empty:72px; --koko-ride:80px;

  /* ---- space — 4px base ---- */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
  --s-5:20px; --s-6:24px; --s-7:32px; --s-8:40px;
  --gutter:var(--s-4); --card-pad:var(--s-4); --stack:var(--s-3); --section:var(--s-6);
  --rail:48px;

  /* ---- shape ---- */
  /* Shape lock. The brand's default corner is 16px (rounded-2xl) and that is
     the card and the button. The rule, followed everywhere:
       16px  cards, buttons, inputs, sheets, tiles   (the default)
        8px  small things set INSIDE a 16px container, so the two radii nest
             rather than fight — a chip on a card, a warn row, a badge
        999  pills: filter chips, the map's floating chrome, the grab handle
       50%   avatars, map pins, the tracker
     Nothing else. A round button in a square layout is broken design. */
  /* The shape lock is four values, and since v14 literally so. --rad-4 was
     20px, used in exactly one place — the page's corners while it is
     receded behind the sheet. Measured at 16px against 20px on a 446px
     screen: the page is scaled to .94 and its top corners sit behind the
     sheet, so the 4px never became visible — gone rather than documented.
     --rad-3 was the same 16px as --rad-2 under a second name; v14 removed
     it outright rather than let two names drift apart later. The two
     sheets were 22px; v14 puts every top-level surface, both sheets
     included, on 16. The one corner outside the lock is the desktop frame
     (.app at 26px): it is not the app, it is the window the app sits in. */
  --rad-1:8px; --rad-2:16px;
  --rad-pill:999px; --rad-round:50%;

  /* ---- elevation — set by what a thing floats over ---- */
  --e-1:0 1px 2px rgba(15,31,56,.06), 0 1px 3px rgba(15,31,56,.05);
  --e-2:var(--chrome-shadow);
  --e-3:var(--lift-shadow);
  --e-4:0 -1px 3px rgba(15,31,56,.05), 0 -14px 34px rgba(15,31,56,.16);

  --tap:44px;
  /* The bottom nav floats. Its height and the gap under it live here
     because the peek, the card row, the trip chip, the map attribution
     and the lists' bottom padding all have to clear it: --nav-top is
     where its top edge sits above the screen's bottom, before the safe
     area. Change the bar here and everything above it moves with it. */
  --nav-h:60px; --nav-gap:12px; --nav-top:calc(var(--nav-h) + var(--nav-gap));

  /* ---- buttons ----
     Three sizes and four roles. md is the default and equals the 44px touch
     minimum; sm is only for a control sitting inside a card's own padding,
     where the row it lives in already gives the finger room. */
  --btn-sm-h:36px;  --btn-sm-px:var(--s-3); --btn-sm-fs:var(--fs-micro);
  /* v14 Toggle (components/Toggle/preview.html): a 44px track with 3px
     inside it, each half 38px at 0 14px with a 6px icon gap. */
  --toggle-pad:3px; --toggle-px:14px; --toggle-gap:6px;
  --btn-md-h:var(--tap); --btn-md-px:var(--s-4); --btn-md-fs:var(--fs-meta);
  --btn-lg-h:52px;  --btn-lg-px:var(--s-5); --btn-lg-fs:var(--fs-body);

  /* ---- motion ---- */
  --dur-press:80ms; --dur-state:150ms; --dur-enter:220ms; --dur-sheet:300ms;
  --ease-out:cubic-bezier(.2,.8,.3,1);
  /* Character moments only — the GO LOKO bubble, a save confirmation, the
     mascot. This curve passes 1 on the way (1.56) and that is the point; it
     is why it must never touch a control or anything the user is waiting on.
     Disabled outright under prefers-reduced-motion, below: a bouncing bubble
     is a vestibular trigger, not a nicety to soften. */
  --ease-pop:cubic-bezier(.34,1.56,.64,1); --t-pop:320ms;
  /* No overshoot anywhere. A control that springs back past its resting size
     reads as a toy; the reference's own controls settle flat, and so do ours.
     The press is still acknowledged — it just stops when it arrives. */
  --dur-release:180ms;
  --ease-sheet:cubic-bezier(.32,.72,.3,1);
  /* The two loops, and the only motion on the map besides Koko: the walking
     leg marching along the active route and the active line flowing while
     you follow. v14 said the map is still; Yoon's 9/21 call kept these, and
     v14.5 gave them tokens rather than an exception (NEXT.md 2c). Map only;
     paused under reduced motion, below. */
  --dur-loop-march:1300ms; --dur-loop-flow:1100ms;
  --dur-spin:800ms;   /* one turn of a Loading ring (v14.9) */

  --r:var(--rad-2);
}

@media (prefers-reduced-motion: reduce){
  .legline.leg-walk{animation:none}
  .legline.line-fade{animation:none;opacity:1}
  /* The draw is skipped in JS as well (drawActive passes draw:false), so
     this is the belt to that braces: a line must never be left invisible. */
  .legline.line-draw{stroke-dasharray:none;stroke-dashoffset:0;animation:none}
  :root{ --dur-press:1ms; --dur-state:1ms; --dur-enter:1ms; --dur-sheet:1ms;
         --t-pop:1ms; --ease-pop:linear; }
  *,*::before,*::after{
    animation-duration:1ms!important; animation-iteration-count:1!important;
    transition-duration:1ms!important; scroll-behavior:auto!important;
  }
}
*{margin:0;box-sizing:border-box}
html,body{height:100%}
[hidden]{display:none!important}
body{background:var(--frame);font-family:var(--font);font-weight:var(--fw-regular);font-size:var(--fs-body);line-height:var(--lh-snug);color:var(--ink);-webkit-font-smoothing:antialiased;display:flex;align-items:center;justify-content:center}
button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
a{color:inherit;text-decoration:none}
/* Press acknowledgement. Down is quick, up is a touch slower, both flat. */
button,[data-act],.route,.spotcard,.tripchip{transition:transform var(--dur-release) var(--ease-out)}
button:active,[data-act]:active,.route:active,.spotcard:active,.tripchip:active{
  transform:scale(.96); transition-duration:var(--dur-press); transition-timing-function:var(--ease-out);
}
/* A whole card has more mass than a button, so it moves less. */
.route:active,.spotcard:active,.tripchip:active,.stopcard:active{transform:scale(.985)}
.btn:active{transform:scale(.95)}
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
[role="button"][tabindex]{cursor:pointer}
::selection{background:var(--select-bg)}
input{caret-color:var(--accent)}
/* Lucide draws on a 24 grid at stroke 2. Heavier than that reads friendlier —
   the line stops being a diagram and starts being a shape. Past ~2.6 the
   detailed ones (bus, train, footprints) fill in and go muddy at 16px. */
.ic{width:1em;height:1em;stroke:currentColor;fill:none;stroke-width:var(--ic-stroke);vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round;display:inline-block;vertical-align:-.14em}
.ic *{vector-effect:non-scaling-stroke}

.app{width:100%;max-width:430px;height:100dvh;background:var(--bg);position:relative;overflow:hidden}
/* The desktop frame. It is not the app but the window the app sits in on
   a wide screen, so its corner, its shadow and the padding round it are
   outside the shape lock and the shadow tokens — the one listed exception
   (NEXT.md 8, step 1). A phone never sees it. */
@media(min-width:500px){
  body{padding:18px}
  .app{height:min(900px,96dvh);border-radius:26px;box-shadow:0 30px 70px -28px rgba(0,0,0,.55);overflow:hidden}
}
.screen{position:absolute;inset:0;display:flex;flex-direction:column}

/* ============================================================
   HOME — full-screen map
   ============================================================ */
.mapscreen{position:absolute;inset:0}
#homeMap{position:absolute;inset:0;background:var(--map-ground)}
/* keep the attribution clear of the floating nav */
#homeMap .leaflet-bottom{bottom:calc(var(--nav-top) + 2px + env(safe-area-inset-bottom))}
.leaflet-container{background:transparent!important;font:inherit!important}
.leaflet-control-attribution{font-size:var(--fs-nano)!important;padding:0 var(--s-1)!important;background:var(--chrome)!important;color:var(--faint)!important}
.mapfallback{position:absolute;inset:0;z-index:700;background:var(--bg);display:none;align-items:center;justify-content:center;text-align:center;padding:var(--s-6);color:var(--soft);font-size:var(--fs-meta);line-height:var(--lh-text)}
.mapfallback[data-on="1"]{display:flex}

/* floating top bar over the map */
.ov-top{position:absolute;left:var(--gutter);right:var(--gutter);top:calc(var(--gutter) + env(safe-area-inset-top));z-index:800;display:flex;align-items:center;justify-content:space-between;gap:var(--stack);pointer-events:none}
.ov-top > *{pointer-events:auto}
/* The wordmark is the logo artwork itself — height is fixed, width follows
   its 2.84:1 ratio so the pill never distorts it. */
.brandcap{background:var(--chrome);border:1px solid var(--chrome-line);border-radius:var(--rad-pill);padding:var(--s-2) var(--s-4);display:flex;align-items:center;box-shadow:var(--e-2)}
.brandcap img{height:22px;width:auto;display:block}

/* floating category chips over the map */
.ov-chips{position:absolute;left:0;right:0;top:calc(72px + env(safe-area-inset-top));z-index:800;display:flex;gap:var(--s-2);overflow-x:auto;overscroll-behavior-x:contain;padding:0 var(--gutter);scrollbar-width:none;scroll-snap-type:x proximity;scroll-padding:0 var(--gutter);
  -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 28px),transparent 100%);
          mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 28px),transparent 100%)}
.ov-chips>*{scroll-snap-align:start}
.ov-chips::-webkit-scrollbar{display:none}
.mchip{flex:none;display:flex;align-items:center;min-height:var(--tap);padding:var(--s-3) var(--s-4);border-radius:var(--rad-pill);background:var(--chrome);border:1px solid var(--line-strong);font-size:var(--fs-meta);font-weight:var(--fw-medium);color:var(--ink);white-space:nowrap;transition:background var(--dur-state) var(--ease-out),color var(--dur-state) var(--ease-out),border-color var(--dur-state) var(--ease-out),box-shadow var(--dur-state) var(--ease-out)}
/* Selected keeps the white chrome — it floats on tiles, and a filled chip
   was a navy slab over the map. The 1px border turns ink and an inset
   makes up the rest of --select-w, so selecting moves nothing. */
.mchip[aria-selected="true"]{border-color:var(--select-edge);box-shadow:inset 0 0 0 calc(var(--select-w) - 1px) var(--select-edge)}

/* 7c. The wrapper positions; the button keeps the kit's own size and
   fill, per the one-button-kit rule. `bottom` is written by app.js from
   the card row's measured height, because that height depends on what is
   in the row and a pill overlapping the cards is worse than no pill.

   On navy: the design system says navy carries every action, and this is
   the only action of its moment. At rest the map screen already has navy
   marks — the route dots and the peek's one button (the selected chip is
   ink since D13) — but this pill is never at rest: it exists only between
   a real pan and the tap that answers it, and it leaves on that tap. It is
   not a third permanent fill. */
/* Named `.areapill`, not `.searchwrap`, because `.searchwrap` was already
   taken by Discover's search box — a bare `background:var(--card)` and a
   gutter margin landed on this and drew a white slab behind the button.
   Check the name exists nowhere before adding a rule; the cascade gives
   no warning, it just merges. */
.areapill{position:absolute;left:0;right:0;z-index:880;display:flex;justify-content:center;pointer-events:none}
.areapill>*{pointer-events:auto;box-shadow:var(--e-3);animation:cardIn var(--dur-enter) var(--ease-out)}

/* Nothing here after a search. The row keeps its shape so the map does
   not jump, and it carries the way back out — a dead end with no exit is
   how a filter traps someone.

   It needs its own ground: `.spotcard.is-row` drops the card fill because
   the cards inside carry their own, and without one this rendered as bare
   words lying on the tiles. It floats over the map, so it earns the
   shadow. */
.rrow .rempty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-3);text-align:center;
  background:var(--card);border-radius:var(--rad-2);box-shadow:var(--e-3)}
.rrow .rempty span{font-size:var(--fs-meta);color:var(--soft)}

/* spot pins */
.spinwrap{width:var(--tap);height:var(--tap);display:flex;align-items:center;justify-content:center;cursor:pointer}
/* A place on the main map that is not on the route in view: a dot, no
   glyph (v14 MapMarks, Yoon 2026-09-27 — "stops are numbers, not category
   icons; Koko is the only picture on the map"). The category is one tap
   away, in the spot card. The 44px wrap above is the tap target. */
.spin{position:relative;width:14px;height:14px;box-sizing:content-box;border-radius:var(--rad-round);background:var(--soft);border:2.5px solid var(--card);box-shadow:var(--e-1)}
/* In your trip: ink, because it is the thing you are actually doing. */
.spin.trip{background:var(--ink)}
/* Kept: the same pin with a bookmark on its shoulder. "Saved" is a mark on a
   place, not a different kind of place. */
.spin.kept .spin-k{position:absolute;top:-9px;right:-9px;width:16px;height:16px;border-radius:var(--rad-round);background:var(--ink);color:var(--on-fill);border:2px solid var(--card);display:flex;align-items:center;justify-content:center}
.spin.kept .spin-k .ic{width:9px;height:9px}
/* A place the traveller added: hollow, because it carries nobody's pick. */
.spin.own{background:var(--card);border-color:var(--faint);border-style:dashed}
/* Round, like the reference's. The teardrop was rotated 45deg, which meant
   its contents had to be counter-rotated and only a single digit ever fitted.
   A circle holds the category glyph at full size and lets the number sit
   outside it as a badge. */
/* A stop on any map — the route's own map, the main map's route view, a
   spot's route (stopPinIcon): its NUMBER, v14 MapMarks. 28px NAVY (v14.8,
   Yoon 2026-09-28: navy is LOKO's and reads as something you can tap; red
   would say now, and six red dots on a card would spend it), a 2.5px
   surface casing, the number in label-sm bold. The Spots list numbers its
   cards with the same mark (.snum) and a card's picture dots its stops
   with it (.rend), so map, list and card cross-read. The line stays ink:
   a navy line would be a directions app's. */
.pin{position:relative;display:flex;align-items:center;justify-content:center;width:28px;height:28px;box-sizing:content-box;background:var(--accent);color:var(--on-fill);border-radius:var(--rad-round);border:2.5px solid var(--pin-ring);box-shadow:var(--e-1);font-size:var(--fs-micro);font-weight:var(--fw-bold);font-variant-numeric:tabular-nums;line-height:1;cursor:pointer}
/* Visited (Follow, behind you): a surface disc, the strong edge, a check. */
.pin.is-done{background:var(--card);border:2px solid var(--line-strong);color:var(--soft);box-shadow:none}
/* Tapped: the ink ring (below) and the stop's name beside it, until the
   next tap. */
.pinchip{position:absolute;left:calc(100% + var(--s-1));top:50%;transform:translateY(-50%);pointer-events:none;
  white-space:nowrap;max-width:min(220px,56vw);overflow:hidden;text-overflow:ellipsis;
  background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-pill);padding:var(--s-1) var(--s-3);
  font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--ink);box-shadow:var(--e-2)}
/* Koko fades across a hop it cannot ride, and between laps of its walk. */
.koko-marker{transition:opacity var(--dur-enter) var(--ease-out)}

/* spot card (bottom of map) */
.spotcard{position:absolute;left:var(--gutter);right:var(--gutter);bottom:calc(var(--nav-top) + 14px + env(safe-area-inset-bottom));z-index:900;background:var(--card);border-radius:var(--rad-2);padding:var(--card-pad);box-shadow:var(--e-3);cursor:pointer;animation:cardIn var(--dur-enter) var(--ease-out)}
@keyframes cardIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.spotcard .sx{position:absolute;right:var(--s-2);top:var(--s-2);background:var(--hair2);border-color:transparent}

/* Whose pick this is. On the map card it leads, because on a map of picks
   the author is the only thing distinguishing one pin from any other pin. */
.pick{display:inline-block;font-size:var(--fs-micro);font-weight:var(--fw-bold);letter-spacing:var(--tr-caps);color:var(--soft);margin-bottom:var(--s-1)}
.pick.team{color:var(--accent)}
.spotcard .snm{font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--tr-title);line-height:var(--lh-tight);padding-right:var(--s-8)}
.spotcard .smeta{display:flex;flex-wrap:wrap;gap:var(--s-2);align-items:center;margin-top:var(--s-2)}
.spotcard .tag{display:inline-flex;align-items:center;gap:var(--s-1);color:var(--soft);background:var(--bg);border:1px solid var(--hair)}
.spotcard .tp{font-size:var(--fs-meta);color:var(--soft)}
.spotcard .from,.smore .from{display:flex;align-items:center;gap:var(--stack);margin-top:var(--stack);padding-top:var(--stack);border-top:1px solid var(--hair2)}
.spotcard .from .dot,.smore .from .dot{width:32px;height:32px;border-radius:var(--rad-2);background:var(--ink);color:var(--on-fill);display:flex;align-items:center;justify-content:center;font-size:var(--fs-micro);line-height:var(--lh-flat);font-weight:var(--fw-bold);flex:none}
.spotcard .from .ft,.smore .from .ft{flex:1;min-width:0}
.spotcard .from .ft b,.smore .from .ft b{display:block;font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:var(--lh-snug)}
.spotcard .from .ft span,.smore .from .ft span{font-size:var(--fs-meta);color:var(--soft)}
.spotcard .from .go,.smore .from .go{display:flex;align-items:center;min-height:var(--tap);font-size:var(--fs-meta);font-weight:var(--fw-bold);padding:var(--s-3) var(--s-4);border-radius:var(--rad-2);background:var(--accent);color:var(--on-fill);flex:none}

/* your-trip chip (bottom of map) */
.tripchip{position:absolute;left:var(--gutter);right:var(--gutter);bottom:calc(var(--nav-top) + 14px + env(safe-area-inset-bottom));z-index:850;background:var(--card);color:var(--ink);border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--card-pad);display:flex;align-items:center;gap:var(--stack);box-shadow:var(--e-3);cursor:pointer}
.tripchip .tc{flex:1;min-width:0}
.tripchip .tc .k{font-size:var(--fs-micro);font-weight:var(--fw-medium);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--accent)}
.tripchip .tc b{display:block;font-size:var(--fs-body);font-weight:var(--fw-bold);margin-top:var(--s-1)}
.tripchip .tc span{font-size:var(--fs-meta);color:var(--soft)}

/* ============================================================
   HOME — scroll tabs (Discover / Saved / Me)
   ============================================================ */
.home-hd{padding:var(--s-5) var(--gutter) var(--stack);display:flex;align-items:flex-start;justify-content:space-between;flex:none}
.home-hd .hi{font-size:var(--fs-meta);color:var(--soft);letter-spacing:var(--tr-normal)}
/* The screen title — Discover, Saved, Me, the list's "6 routes". Always an
   English UI word, so it is v14's display style outright: Fredoka 600 at 25. */
.home-hd .wm{font-family:var(--font-display);font-size:var(--fs-screen);font-weight:var(--fw-medium);letter-spacing:var(--tr-display);line-height:var(--lh-tight);margin-top:var(--s-1)}
.home-scroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:0 0 calc(var(--nav-top) + var(--s-6) + env(safe-area-inset-bottom));scrollbar-width:none}
.home-scroll::-webkit-scrollbar{display:none}

/* search */
.searchwrap{margin:0 var(--gutter) var(--stack);display:flex;align-items:center;gap:var(--s-2);background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-pill);padding:0 var(--s-5);height:52px;box-shadow:var(--e-2)}
.searchwrap .ic{font-size:var(--ic-2);color:var(--faint);flex:none}
.searchwrap:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--select-bg)}
.searchwrap input{flex:1;min-width:0;border:none;outline:none;background:none;font:inherit;font-size:var(--fs-body);color:var(--ink);height:100%}
.searchwrap input::placeholder{color:var(--faint)}
.searchwrap .qx{flex:none;background:var(--hair2);border-color:transparent}

.chips{display:flex;gap:var(--s-2);overflow-x:auto;padding:0 var(--gutter) var(--stack);scrollbar-width:none}
.chips::-webkit-scrollbar{display:none}
/* A chip's edge is border-strong (NEXT.md 5a): the 10% ink and the
   hairline it had were under the 3:1 a control's edge needs. */
.chip{flex:none;display:flex;align-items:center;min-height:var(--tap);padding:var(--s-3) var(--s-4);border-radius:var(--rad-pill);background:var(--card);border:1px solid var(--line-strong);font-size:var(--fs-meta);font-weight:var(--fw-medium);color:var(--ink);white-space:nowrap;transition:background var(--dur-state) var(--ease-out),color var(--dur-state) var(--ease-out),border-color var(--dur-state) var(--ease-out),box-shadow var(--dur-state) var(--ease-out)}
.chip[aria-selected="true"]{border-color:var(--select-edge);box-shadow:inset 0 0 0 calc(var(--select-w) - 1px) var(--select-edge)}
.reslab{padding:0 var(--gutter) var(--stack);font-size:var(--fs-micro);font-weight:var(--fw-medium);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--faint)}

/* ---- browse tiles ----
   Sideways then down: a row per area, each card showing its picture first.
   The next card peeks so the row reads as scrollable without a control. */
.rowhead{display:flex;align-items:center;gap:var(--s-1);padding:var(--section) var(--gutter) var(--stack);font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--tr-title)}
.rowhead .ic{color:var(--soft)}
/* The browse grid: one card per row on a phone, two once there is room for
   two without shrinking the image below the size that sells it. */
.browse{display:grid;grid-template-columns:1fr;gap:var(--section) var(--stack);padding:0 var(--gutter)}
@media (min-width:600px){ .browse{grid-template-columns:1fr 1fr} }
.browse .tile{flex:none;max-width:none;width:100%}
.browse .tile-img{aspect-ratio:4/3}

.rail-h{display:flex;gap:var(--stack);overflow-x:auto;overscroll-behavior-x:contain;padding:0 var(--gutter) var(--s-1);scroll-snap-type:x mandatory;scrollbar-width:none}
.rail-h::-webkit-scrollbar{display:none}
.tile{flex:0 0 68%;max-width:260px;scroll-snap-align:start;cursor:pointer}
.tile-img{position:relative;aspect-ratio:1/1;border-radius:var(--rad-2);overflow:hidden;background:var(--card)}
.tile-img .photo{height:100%;border-radius:0}
/* A route with no photograph gets a deliberate surface, not a blank one. The
   old treatment — a faint dotted line on white inside a hairline — read as an
   unfinished wireframe sitting next to a real photograph. Now it is a tinted
   panel carrying the route's actual shape over its category mark: still
   honestly "no photo yet", but drawn as something rather than as nothing.
   This matters more than it sounds: most spots will have no photo for a long
   time, so designing as if photographs were guaranteed makes the common case
   look broken. */
/* B5 (Yoon, 2026-09-22): the PHOTOGRAPH is the card and the route is a
   small window in its bottom-right corner — Komoot's card. No photograph,
   no window: the whole thumbnail is the route, thick, on the flat ground.
   No basemap tiles on either; see routeThumb() in app.js for why. */

/* The line itself. `vector-effect:non-scaling-stroke` on the path means the
   stroke-width below is in SCREEN pixels, the same rule `.ic` follows — so
   a 200px tile and a 56px inset draw the same weight of line and neither
   goes to a hairline or a smear. Ink, because it is the route colour on
   every map (v14 MapPin, and the Route and Main boards draw it so); never
   red, which is "now", and never navy, which is what you tap. */
.rline{display:block;width:100%;height:100%;color:var(--route-line)}
.rline path{stroke-width:4px}
.rl-inset path{stroke-width:3px}

/* No photograph: the route on the flat ground. `--surface` because it is
   the token that already means "the tinted ground — map, troughs,
   placeholders"; a thumbnail with no photo is exactly that. */
.rflat{position:absolute;inset:0;background:var(--surface)}
/* The route's own map, captured at build time (NEXT.md 4c): the whole
   thumbnail when there is no photograph, the corner window when there is. */
.rmap{position:absolute;inset:0;background:var(--surface)}
.rmap .rcap,.rinset .rcap{display:block;width:100%;height:100%;object-fit:cover}
/* Its credit, on it: OpenStreetMap and CARTO are credited wherever their
   tiles show, and a picture of tiles is tiles. Laid on in CSS, never baked
   into the image, so it stays text. 11px is the map attribution's floor. */
.mapcredit{position:absolute;right:var(--s-1);bottom:var(--s-1);z-index:1;padding:0 var(--s-1);border-radius:var(--rad-1);
  background:var(--card);color:var(--soft);font-size:var(--fs-nano);line-height:var(--lh-flat);white-space:nowrap;pointer-events:none}
/* Toast (v14): one line about what just happened, an ink pill 12px over
   the bottom bar, gone in three seconds or on a tap. The error is the one
   outlined variant — never a red fill. Replaces every window.alert. */
.toast{position:absolute;left:50%;z-index:1300;display:flex;align-items:center;gap:var(--s-2);max-width:calc(100% - 2 * var(--gutter));
  min-height:var(--tap);padding:0 var(--s-4);border-radius:var(--rad-pill);background:var(--ink);color:var(--card);border:1px solid var(--ink);
  font-size:var(--fs-meta);font-weight:var(--fw-medium);box-shadow:var(--e-3);opacity:0;transform:translate(-50%, var(--s-2));pointer-events:none;
  transition:opacity var(--dur-enter) var(--ease-out), transform var(--dur-enter) var(--ease-out)}
.toast.is-on{opacity:1;transform:translate(-50%, 0);pointer-events:auto}
.toast.is-error{background:var(--card);color:var(--now-ink);border-color:var(--now-ink)}
.toast .ic{font-size:var(--ic-2);flex:none}
.toast span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ?thumb=<id>: the page is the map and nothing else (tools/render-thumbs.js). */
html.thumb-mode .app{display:none}
#thumbMap{position:fixed;inset:0;background:var(--surface)}
.tile-img>.ph-cat{position:absolute;left:var(--s-3);bottom:var(--s-3);z-index:1;display:inline-flex;align-items:center;gap:var(--s-1);padding:var(--s-1) var(--s-2);border-radius:var(--rad-pill);background:var(--chrome);border:1px solid var(--chrome-line);font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--ink)}

/* The window. Bottom-right, square, a third of the card: big enough that a
   six-stop route still has a readable shape, small enough to leave the
   photograph its subject. 8px corners — a thing set inside a 16px container,
   by the shape lock. It floats over the photograph, so it earns the chrome
   shadow; the white edge is what separates it from a dark photo. */
.rinset{position:absolute;right:var(--s-2);bottom:var(--s-2);z-index:1;width:34%;aspect-ratio:1/1;
  border-radius:var(--rad-1);background:var(--surface);border:2px solid var(--card);box-shadow:var(--e-2);overflow:hidden}
.route .cover .rinset{width:44%;right:6px;bottom:6px;border-width:1.5px}

/* The credit leaves the thumbnail (C8). At 200px it was a quarter of the
   width in 11px type nobody read, and it now sits exactly where the route
   window goes. Photographs are credited once, at the foot of the list —
   photoCredits() — and in full on the large photographs, which keep theirs. */
/* Three classes deep, not two. `.photo .credit` sets display:block further
   down this file at the same specificity (0,2,0), so the first version of
   this rule lost on source order and the credit stayed on every photo
   tile — the check that caught it read the COMPUTED display, not the rule. */
.tile-img .photo .credit,.route .cover .photo .credit,.rc-thumb .photo .credit{display:none}
.thumbcredits{margin:var(--s-5) 0 0;text-align:center;font-size:var(--fs-micro);line-height:var(--lh-text);color:var(--faint)}
.thumbcredits a{color:var(--faint);text-decoration:underline;text-underline-offset:2px}
.tile-pick{position:absolute;left:var(--s-2);top:var(--s-2);z-index:2;background:var(--card);border-radius:var(--rad-pill);padding:var(--s-1) var(--s-2);font-size:var(--fs-micro);font-weight:var(--fw-bold);letter-spacing:.04em;color:var(--soft);box-shadow:var(--e-2)}
.tile-pick.team{color:var(--accent)}
/* "LOKO" as a word is the wordmark (v14): badges, "by LOKO", the Discover
   line, the GO LOKO chip. 11px tall, at its own colours, sitting on the
   baseline of the words beside it. */
.wm-in{display:inline-block;height:var(--fs-nano);width:auto;vertical-align:baseline}
/* The save control carries its own ground. A white stroke icon is legible
   over a dark subject and invisible over a bright sky, and this one sat over
   the Han River in daylight. Both rules are written at :has() specificity
   deliberately: .btn.ghost is declared later in the button kit and would
   otherwise win the background back. */
.tile-save{position:absolute;right:var(--s-2);top:var(--s-2);z-index:2}
.tile-img:has(.photo) .tile-save{border-radius:var(--rad-round);color:#fff;background:rgba(15,31,56,.5);border-color:transparent;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);box-shadow:0 1px 4px rgba(15,31,56,.3)}
.tile-img:has(.photo) .tile-save:hover{background:rgba(15,31,56,.66)}
.tile-img:has(.photo) .tile-save[aria-pressed="true"]{color:#fff;background:var(--accent);border-color:transparent}
/* A generated cover has no photograph to sit on, so it takes the page's own
   controls instead of a scrim. */
.tile-img:has(.rflat) .tile-save{border-radius:var(--rad-round);color:var(--accent);background:var(--card);border-color:transparent;box-shadow:var(--e-1)}
.tile-img:has(.rflat) .tile-save[aria-pressed="true"]{color:var(--on-fill);background:var(--accent);border-color:transparent}
.tile-t{margin-top:var(--s-2);font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:var(--lh-snug);display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
.tile-m{margin-top:2px;font-size:var(--fs-meta);color:var(--soft);line-height:var(--lh-small)}
.tile-p{margin-top:2px;font-size:var(--fs-meta);font-weight:var(--fw-bold)}
.tile-p.free{color:var(--soft);font-weight:var(--fw-medium)}

/* route cards */
.grid{padding:0 var(--gutter)}
.route{background:var(--card);border:1px solid var(--hair);border-radius:var(--r);overflow:hidden;cursor:pointer;margin-bottom:var(--stack);display:flex}
/* Saving is the funnel's whole ask, so the control sits on the card itself. */
.route{position:relative}
.route-save{position:absolute;right:var(--s-2);top:var(--s-2);z-index:1;color:var(--card);filter:drop-shadow(0 1px 3px rgba(15,31,56,.45))}

/* Saving a single spot — the one action a free user leaves behind. */
.spot-save{position:absolute;right:var(--s-1);top:var(--s-1);z-index:1}

.spotcard .spot-save{right:var(--s-8)}
/* ---- adding your own place ---- */
/* Your own spots read differently from a local's pick — they carry no judgement. */
.savedrow.mine .sdot{color:var(--faint)}
.savedrow{display:flex;align-items:center;gap:var(--stack);background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--card-pad);margin-bottom:var(--s-2)}
.savedrow .sdot{flex:none;display:flex;color:var(--soft)}
.savedrow .sinfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--s-1)}
.savedrow .sinfo b{font-size:var(--fs-body);font-weight:var(--fw-bold)}
.savedrow .sko{font-size:var(--fs-meta);color:var(--soft)}
.savedrow .smeta2{font-size:var(--fs-micro);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:var(--lh-small)}
.savedrow .srm{flex:none}
.gate .gate-cta + .gate-cta{margin-top:var(--s-2)}
.hdr-save{flex:none}
.hdr-save[aria-pressed="true"]{border-color:var(--accent)}
/* ---- photography ----
   The credit is not optional under CC BY-SA, so it is part of the component
   rather than something a later layout can drop. */
.photo{position:relative;overflow:hidden;border-radius:var(--rad-2);background:var(--bg);line-height:0}
.photo img{width:100%;height:100%;object-fit:cover;display:block}
/* The credit sits on a photograph, so its contrast is whatever the photo
   happens to be — a bright sky under it can take this to ~1.1:1. The gradient
   starts sooner and darker, and the shadow carries the worst case. */
.photo .credit{position:absolute;left:0;right:0;bottom:0;padding:var(--s-1) var(--s-2);font-size:var(--fs-micro);line-height:1.4;color:#fff;background:linear-gradient(rgba(15,31,56,0) 0%,rgba(15,31,56,.55) 55%,rgba(15,31,56,.85) 100%);text-shadow:0 1px 2px rgba(0,0,0,.8);text-align:right;letter-spacing:.01em;display:block;text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.photo a.credit:hover{text-decoration:underline}
.card-photo{margin:var(--s-2) 0 0 var(--s-6);height:150px}
.sheet-photo{height:180px;margin-bottom:var(--stack)}
.route .cover.has-photo{padding:0;border-right:0}
.route .cover.has-photo .photo{width:100%;height:100%;border-radius:0}
.route .cover{width:104px;flex:none;min-height:120px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-2);padding:var(--s-3);background:var(--bg);border-right:1px solid var(--hair);color:var(--soft)}
.route .cover .tag{background:transparent;border:0;padding:0;color:var(--soft)}
.route .cover.has-line{padding:0;border-right:0;overflow:hidden;background:var(--surface)}
.route .body{flex:1;padding:var(--card-pad)}
/* Applied by displayCls() in app.js, never hardcoded in markup, and
   WITHHELD from any string containing Hangul. Fredoka has no Korean glyphs,
   so half a bilingual line would fall through to another face and the
   baseline would shift mid-line — the exact problem Pretendard was chosen
   to avoid. Opt-in rather than opt-out, so the failure mode is a route name
   in the sans, not a broken one. 600 is Fredoka's one weight here (v14
   title-display); the 25px header takes display's -.01em below. */
.rt-display{font-family:var(--font-display);font-weight:var(--fw-medium);letter-spacing:var(--tr-normal)}
/* The rules the class has to out-specify. .route .body h3 and .hdr h1 both
   set --fw-bold and their own tracking, and a bare .rt-display loses to
   either — which is how Fraunces once rendered at 700 where the system
   asked for 500. Named explicitly rather than fought with !important. */
.route .body h3.rt-display{font-weight:var(--fw-medium);letter-spacing:var(--tr-normal)}
.hdr h1.rt-display{font-weight:var(--fw-medium);letter-spacing:var(--tr-display)}
/* A name with Hangul, Hanja or Kana in it — judged by displayCls() when it
   is drawn (NEXT.md 3b) — takes v14's `title`: Pretendard 700 at 21. That
   is the display style's own rule: it is Latin only, and a title that
   cannot be Latin drops to the sans title rather than staying at 25. */
.rt-title{font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--tr-title)}
.hdr h1.rt-title{font-size:var(--fs-title)}
/* The title keeps clear of the save button, which sits over the card's top
   right at --s-2 from the edge and is --tap wide. The box always ran under
   it; Fredoka's wider 600 (v14 step 3) is what put the letters on the icon. */
.route .body h3{font-size:var(--fs-title);font-weight:var(--fw-bold);line-height:1.3;letter-spacing:var(--tr-normal);padding-right:calc(var(--tap) - var(--s-2));display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.route .body .by{display:flex;align-items:center;gap:var(--s-2);margin-top:var(--s-2)}
.route .body .by .dot{width:22px;height:22px;border-radius:var(--rad-round);background:var(--ink);color:var(--on-fill);display:flex;align-items:center;justify-content:center;font-size:var(--fs-micro);line-height:var(--lh-flat);font-weight:var(--fw-bold);flex:none}
.route .body .by .nm{font-size:var(--fs-meta);color:var(--soft)}
.route .body .foot{display:flex;align-items:center;justify-content:space-between;margin-top:var(--stack)}
.route .body .foot .dur{font-size:var(--fs-micro);color:var(--faint)}
.route .body .foot .price{font-size:var(--fs-body);font-weight:var(--fw-bold)}
.route .body .foot .price.free{padding:var(--s-1) var(--s-2);border-radius:var(--rad-1);background:var(--bg);border:1px solid var(--hair);font-size:var(--fs-meta);letter-spacing:var(--tr-caps);text-transform:uppercase}
.route .body .foot .like{font-variant-numeric:tabular-nums;font-size:var(--fs-micro);color:var(--faint);display:flex;align-items:center;gap:var(--s-1)}

.pad{padding:0 var(--gutter) var(--s-5)}
/* A claimed set in Saved (NEXT.md 12, B8): its name and who picked it over
   its own spots, its one action under the name — beside it, "Add all to my
   trip" left the name 200px at 375 and the wordmark wrapped onto a line of
   its own. A preview says it is not saved yet, in the muted ink. */
.sethead{display:flex;flex-direction:column;align-items:flex-start;gap:var(--s-2);padding:0 var(--gutter) var(--s-3)}
.setname{font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:var(--lh-tight);color:var(--ink)}
.setname .wm-in{height:var(--fs-micro)}
.setnote{padding:0 var(--gutter) var(--s-2);font-size:var(--fs-micro);color:var(--soft)}
/* EmptyState (v14): Koko, one line that says what to do, one action —
   no box. It was a dashed card, which v14 names as the thing not to draw. */
.empty{padding:var(--s-7) var(--card-pad);text-align:center;color:var(--ink);font-size:var(--fs-body);line-height:var(--lh-text);margin:0 var(--gutter)}
.empty > .btn{margin-top:var(--stack)}
.profile{background:var(--card);border:1px solid var(--hair);border-radius:var(--r);padding:var(--card-pad);text-align:center}
.profile .big{width:64px;height:64px;border-radius:var(--rad-round);background:var(--ink);color:var(--on-fill);display:flex;align-items:center;justify-content:center;font-size:var(--fs-title);line-height:var(--lh-flat);font-weight:var(--fw-bold);margin:0 auto}
.profile h3{font-size:var(--fs-title);font-weight:var(--fw-bold);margin-top:var(--stack)}
.profile .em{font-size:var(--fs-meta);color:var(--soft);margin-top:var(--s-1)}
.profile .stats{display:flex;margin-top:var(--s-4);border-top:1px solid var(--hair2)}
.profile .stats div{flex:1;padding:var(--s-3) 0}
.profile .stats b{display:block;font-variant-numeric:tabular-nums;font-size:var(--fs-title);font-weight:var(--fw-bold)}
.profile .stats span{font-size:var(--fs-micro);color:var(--faint)}
.creator{margin-top:var(--stack);background:var(--accent);color:var(--on-fill);border-radius:var(--r);padding:var(--card-pad);display:flex;align-items:center;gap:var(--stack);cursor:pointer}
.creator .txt{flex:1}
.creator .txt .k{font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--on-fill-soft);letter-spacing:var(--tr-caps);text-transform:uppercase}
.creator .txt h4{font-size:var(--fs-body);font-weight:var(--fw-bold);margin-top:var(--s-1)}

/* bottom nav — a floating bar, opaque.

   It sits ABOVE every other layer of the home screen (z over the browse
   sheet and its scrim) rather than beside them, so the content runs
   under it instead of stopping at it. Two things follow. Nothing has to
   stop at the nav any more, which is how Discover, Saved and Me can no
   longer cover it: they cannot reach it. And everything that used to sit
   on its top edge — the row, the peek, the chip, the map attribution, the
   lists' bottom padding — clears --nav-top instead, the one place the
   bar's size is written.

   Pill, not 16px. It floats over the map, and pill is the shape this app
   gives floating map chrome (the chips, the area pill, the brand cap).

   It was glass from 2026-09-21 — white at .5 over a 22px blur, labels
   kept legible by a white halo — so a list scrolling under it showed
   through, as in the reference. v14 took glass out (2026-09-26): over the
   map the blur smeared the tiles, and what sits on the map is opaque
   surface with shadow-md. The halo went with it; on an opaque bar it was
   only blur on the type. */
/* Capped and centred: on a tablet or a desktop pane a bar stretched to the
   gutters is a 700px pill with four labels lost in it. 480px is wide
   enough for four 44px targets with room to breathe and narrow enough to
   still read as one object under the thumb. */
.nav{position:absolute;left:0;right:0;margin:0 auto;width:min(calc(100% - 2 * var(--s-3)), 480px);bottom:calc(var(--nav-gap) + env(safe-area-inset-bottom));height:var(--nav-h);
  display:flex;z-index:1100;border-radius:var(--rad-pill);
  background:var(--card);box-shadow:var(--e-2)}
.nav button{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-1);color:var(--soft);font-size:var(--fs-nano);font-weight:var(--fw-medium);border-radius:var(--rad-pill);transition:color var(--dur-state) var(--ease-out)}
.nav button svg{font-size:var(--ic-3)}
/* The tab you are on is where you are, so it is ink like every other
   selection. No ring: a tab has no surface of its own to outline, so the
   glyph and the label carry it — ink against the others' --faint. */
.nav button[aria-selected="true"]{color:var(--ink)}
.nav button[aria-selected="true"] svg{stroke:var(--ink)}

/* ============================================================
   PLANNER
   ============================================================ */
.hdr{padding:var(--s-4) var(--gutter) var(--stack);display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2)}
.hdr .back{flex:none;order:0}
.hdr #tripSave{order:0;margin-left:auto;display:flex}
.hdr .htxt{order:2;flex:1 0 100%;min-width:0;margin-top:var(--s-2)}
.hdr .htxt h1{overflow-wrap:anywhere}
/* Share copies the deep link to this route — the Instagram-comment handoff. */
.hdr .share{flex:none;order:1}
/* Three controls plus a title do not fit a 375px header. Below 400px the
   share label drops to its icon; the copied state still swaps the icon's
   colour, so the confirmation survives. */


.hdr .share[data-copied="1"]{background:var(--ink-btn);border-color:var(--ink-btn);color:var(--on-fill)}
/* Back, Save and Share are one set of round buttons (NEXT.md 5a: Share and
   Save both, round, top right). Surface with a hairline, not a shadow: the
   header sits on the page, not over the map. */
#btnBack,#btnShare,#tripSave .btn{border-radius:var(--rad-round);background:var(--card);border-color:var(--hair)}
/* The kicker names what this is; nothing in it can be tapped, so it is
   ink-muted, not navy (navy is every action and nothing else — the step-5
   comparison found it navy, against the Route and Trip boards). */
.hdr .kick{font-size:var(--fs-micro);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--soft);font-weight:var(--fw-medium)}
.hdr h1{font-size:var(--fs-screen);font-weight:var(--fw-bold);letter-spacing:var(--tr-title);line-height:var(--lh-tight);margin-top:var(--s-1)}
/* Whose route this is, and its price, on the route itself. */
.hdr .by{display:flex;align-items:center;gap:var(--s-2);margin-top:var(--s-2);font-size:var(--fs-meta);color:var(--soft)}
.hdr .by:empty{display:none}   /* a seed, or the demo: no author, and no empty row holding its margin */
.hdr .by .dot{width:22px;height:22px;border-radius:var(--rad-round);background:var(--ink);color:var(--on-fill);display:flex;align-items:center;justify-content:center;font-size:var(--fs-micro);line-height:var(--lh-flat);font-weight:var(--fw-bold);flex:none}
.hdr .by .price{margin-left:auto;font-weight:var(--fw-bold);color:var(--ink)}
.hdr .by .price.free{padding:var(--s-1) var(--s-2);border-radius:var(--rad-1);background:var(--bg);border:1px solid var(--hair);font-size:var(--fs-micro);letter-spacing:var(--tr-caps);text-transform:uppercase}
/* Hours and rating on the map card — the map is where "where next" is decided. */
.spotcard .sfacts{display:flex;flex-wrap:wrap;gap:var(--stack);align-items:center;margin-top:var(--s-2);font-size:var(--fs-meta);color:var(--soft)}
.dist{font-variant-numeric:tabular-nums;color:var(--faint)}
.spotcard .sfacts .rt{font-variant-numeric:tabular-nums;font-weight:var(--fw-medium);color:var(--ink)}
.spotcard .sfacts .rc{font-family:var(--font);font-weight:var(--fw-regular);color:var(--faint)}
.hdr .dates{font-variant-numeric:tabular-nums;font-size:var(--fs-meta);color:var(--soft);margin-top:var(--s-1);letter-spacing:var(--tr-normal)}
/* On the list view the strip sheds its numbers and keeps only the exit, so
   a route that is not an Exit to Local has nothing left to say there. Its
   height is already 0; the margin is not, and 4px of unexplained air under
   the title on some routes and not others reads as a bug. */
.hdr .dates:empty{display:none}
/* Following a route is a map task, so the map gets the screen: in Follow the
   header collapses to one line and the toggle shrinks to a control rather than
   a banner. In All stops the page scrolls, so the full header can stay.

   NOT ON A BUNDLE. `data-kind` already separates them, and the two maps are
   two jobs: a plan's overview is Follow — you are walking it, the header is
   chrome taken from the thing you are reading — while a bundle's Map view is
   the landing screen of a shared link, where a traveller is deciding whether
   to walk it at all. The collapse squeezes the title to one nowrap line and
   hides the strip, which is how the map view ended up with none of the
   route's three numbers on it; letting the strip back in ALONE then wrapped
   it under a title built for one line. So the whole collapse is scoped, not
   one rule of it. A bundle's map is shorter by a header and that is the
   right trade. */
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr{padding-bottom:var(--s-2)}
/* The collapse belongs to FOLLOWING, not to every map. `data-kind` already
   says which this is: a plan's overview is the Follow task, where the
   header is chrome taken from the map; a bundle's Map view is where a
   traveller decides whether to walk it at all, and `6 stops · 2.8 km ·
   4–6 hours` is the decision.

   This is the regression the one-place-per-view rule was written to stop,
   and it shipped anyway: the numbers were moved into the header for the
   map view while this rule was hiding the header on the map view, so the
   map view had none. It passed my check because I measured `textContent`
   — which a display:none element answers perfectly well. Measure what is
   on screen, not what is in the DOM. */
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr .dates,
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr .by{display:none}
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr h1{font-size:var(--fs-title);margin-top:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr .kick{display:none}
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .hdr .htxt{order:0;flex:1;margin-top:0}
/* Follow is a map task, so every pixel of chrome above the map is taken from
   the thing the screen is for. Measured: the header, toggle, day strip and
   day line were 269px of 812 — a third of the screen spent on furniture. The
   strip loses its vertical padding and the header its breathing room; both
   keep a 44px tap target. */
#screenTrip[data-view="overview"] .days{padding-top:var(--s-1);padding-bottom:var(--s-1);gap:var(--s-1)}
#screenTrip[data-view="overview"] .day{padding:var(--s-1) var(--s-3);min-height:0}
#screenTrip[data-view="overview"] .day .dnum{margin-top:0}
#screenTrip[data-view="overview"] .toggle{margin-bottom:0}
/* The toggle keeps its full tap target; the pixels come from elsewhere. */
#screenTrip[data-view="overview"] .hdr{padding-top:var(--s-3)}

/* Reference shape: two standalone pills, not a segmented track. The selected
   one is outlined and the other is bare, so which view you are in is legible
   from the edge alone rather than from which half of a capsule is lighter.

   Ink, not navy (D13, 2026-09-23 — the rule is at --select-edge). The
   toggle sits right under "Add to my trip", the screen's one primary
   action, and a navy fill here made three navy slabs in the first
   screenful (Add, the toggle, Directions). The edge is an inset shadow,
   not a border, so selecting moves nothing and `outline` stays the focus
   ring's. */
/* v14 Toggle: the halves sit in a sunken pill track — without it the half
   not chosen floated free, and the row read as starting 16px in from
   everything above it (Yoon, 2026-09-29: "My trip looks messy"). */
.toggle{margin:0 var(--gutter);display:inline-flex;align-self:flex-start;gap:0;box-sizing:border-box;min-height:var(--tap);padding:var(--toggle-pad);border-radius:var(--rad-pill);background:var(--track)}
.toggle button{display:inline-flex;align-items:center;gap:var(--toggle-gap);min-height:calc(var(--tap) - 2 * var(--toggle-pad));padding:0 var(--toggle-px);font-size:var(--fs-meta)}
.toggle button{border-radius:var(--rad-pill);font-weight:var(--fw-medium);color:var(--soft);background:transparent;transition:background var(--dur-state) var(--ease-out),color var(--dur-state) var(--ease-out),box-shadow var(--dur-state) var(--ease-out)}
.toggle button .ic{width:18px;height:18px;flex:none}
.toggle button[aria-selected="false"]:hover{background:var(--hair2);color:var(--ink)}
.toggle button[aria-selected="true"]{background:var(--card);color:var(--ink);box-shadow:inset 0 0 0 var(--select-w) var(--select-edge)}
.days{display:flex;gap:var(--s-2);overflow-x:auto;overscroll-behavior-x:contain;padding:var(--stack) var(--gutter);scrollbar-width:none}
.days::-webkit-scrollbar{display:none}
/* A chip, so the chip's edge (NEXT.md 5b): border-strong, 3:1. */
.day{flex:none;min-width:56px;min-height:var(--tap);text-align:center;padding:var(--s-2) var(--s-3);border-radius:var(--rad-2);background:var(--card);border:1px solid var(--line-strong);transition:border-color var(--dur-state) var(--ease-out),box-shadow var(--dur-state) var(--ease-out)}
.day .dow{font-size:var(--fs-micro);color:var(--faint);letter-spacing:var(--tr-normal)}
.day .dnum{font-size:var(--fs-body);font-weight:var(--fw-bold);margin-top:var(--s-1)}
.day .dcity{font-size:var(--fs-micro);color:var(--faint);margin-top:1px;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.day[aria-selected="true"]{border-color:var(--select-edge);box-shadow:inset 0 0 0 calc(var(--select-w) - 1px) var(--select-edge)}
/* A Lottie mount. It starts life holding the static icon and is replaced in
   place once the player and the file arrive, so the row is legible from the
   first paint and stays legible if the CDN never answers. The colour override
   is what lets one JSON serve every context — the files ship with a neutral
   stroke and take the surrounding ink here. */
.lot{display:inline-flex;align-items:center;justify-content:center;flex:none;line-height:0;color:inherit}
.lot>svg{width:100%;height:100%;display:block}
/* Only the files we authored take the surrounding ink. A file from elsewhere
   was coloured by whoever drew it, and overriding its stroke would flatten a
   designed illustration into a silhouette. */
.lot[data-authored]>svg *{stroke:currentColor!important;fill:none!important}
.lot>svg.ic{width:1em;height:1em}

/* The play control, opposite the zoom control so the two thumbs do not
   compete for the same corner. */
.mapplay{position:absolute;right:var(--s-3);top:var(--s-3);z-index:800;width:40px;height:40px;background:var(--card);border:1px solid var(--hair);box-shadow:var(--e-2);color:var(--accent)}
.mapplay[aria-pressed="true"]{background:var(--now);border-color:var(--now);color:var(--on-fill)}

/* Walking legs march (v14 MapMarks: dotted, the march loop). The dash is
   1 on, 9 off, so one period is 10 — shifting by exactly that puts the
   pattern back where it started and the loop has no seam. Slow enough to
   read as direction rather than as a flicker. */
.legline.leg-walk{animation:march var(--dur-loop-march) linear infinite}
/* A dotted walk cannot be drawn in by a dash offset — the dots are its
   dashes — so on the focused route's entrance it fades in on the draw's beat. */
.legline.line-fade{animation:lineFade var(--dur-enter) var(--ease-out) both}
@keyframes lineFade{from{opacity:0}}
@keyframes march{to{stroke-dashoffset:-10}}
/* The dot the camera follows during playback. */
/* The thing riding the route is the mode of transport, in a disc that reads
   at any zoom. The animation inside inherits the disc's colour. */
/* The marker that rides the route sits directly on the map, the way the
   reference's does — no disc, no ring, no pulsing halo. A badge around a
   moving illustration reads as chrome bolted onto it; the drawing is the
   marker. A drop shadow is all the separation it needs from a pale basemap,
   and it follows the artwork's own silhouette rather than boxing it. */
.tracker{position:relative;display:flex;align-items:center;justify-content:center;
  filter:drop-shadow(0 0 2px var(--card));
  color:var(--accent)}
.tracker .lot{max-width:100%;max-height:100%}

@keyframes ping{0%{transform:scale(.55);opacity:.30}70%{transform:scale(1);opacity:0}100%{opacity:0}}

/* Leaflet's zoom control, redrawn in our own language: Leaflet ships a grey
   box with a 1px hairline and its own font. Left alone it is the one control
   on the screen that belongs to a different product. */
.leaflet-control-zoom{border:none!important;box-shadow:var(--e-2)!important;border-radius:var(--rad-2)!important;overflow:hidden;margin:var(--s-3)!important}
.leaflet-control-zoom a{width:44px!important;height:44px!important;line-height:42px!important;background:var(--card)!important;color:var(--ink)!important;border:none!important;font-size:var(--fs-title)!important;font-weight:var(--fw-medium)!important;font-family:var(--font)!important}
.leaflet-control-zoom a:first-child{border-bottom:1px solid var(--hair)!important}
.leaflet-control-zoom a:hover{background:var(--hair2)!important}
.leaflet-control-zoom a.leaflet-disabled{background:var(--card)!important;color:var(--hair)!important}

/* The day-progress line, Follow only. Reference shape: place on top, the
   count under it with the current number carrying the now colour, and a bar
   that fills across the day. Sits directly on the map's top edge so the two
   read as one block rather than as a header floating above a picture. */
#screenTrip[data-view="schedule"] .dayline{display:none}
.dayline{position:relative;padding:var(--s-1) var(--gutter) 0;background:var(--bg)}
.dayline .dl-where{font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--soft);letter-spacing:var(--tr-normal);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dayline .dl-count{margin-top:1px;font-size:var(--fs-meta);color:var(--soft);font-variant-numeric:tabular-nums;line-height:var(--lh-small)}
.dayline .dl-count b{color:var(--now-ink);font-weight:var(--fw-bold)}
.dayline .dl-bar{margin-top:var(--s-2);height:3px;border-radius:var(--rad-pill);background:var(--track);overflow:hidden}
/* scaleX, not width. This was the only transition in the file that animated
   a layout property: `width` makes the browser re-lay-out the bar on every
   frame of the fill, and it is the one piece of motion that runs while the
   traveller is walking and the map is redrawing beside it. A transform is
   composited and costs nothing. The span is full width and scaled down from
   its left edge instead. */
.dayline .dl-bar span{display:block;width:100%;height:100%;border-radius:var(--rad-pill);background:var(--now);
  transform-origin:left center;transition:transform var(--dur-enter) var(--ease-out)}

/* Follow: the map takes what is left of the screen, edge to edge, because
   following a route is a spatial task and a 200px window is not one.
   All stops: the map is gone entirely and the cards take the screen. */
.mapwrap{position:relative;flex:1;min-height:0;background:var(--map-ground);border-top:1px solid var(--hair)}
/* Attribution clears the Follow bar. The bar's height changes with the stop
   (a long name wraps), so the offset is measured at render rather than
   guessed — a fixed number here drifted the moment the bar grew a line. */
#screenTrip[data-view="overview"] #map .leaflet-bottom{bottom:var(--nowbar-h,132px)}
#screenTrip[data-view="schedule"] .mapwrap{display:none}
#screenTrip[data-view="overview"] .panel{display:none}
#screenTrip[data-view="overview"] .nowbar{display:block}

/* The current stop and the stepper, pinned above the safe area. */
.nowbar{position:absolute;left:0;right:0;bottom:0;z-index:900;background:var(--card);border-top:1px solid var(--hair);padding:var(--stack) var(--gutter) calc(var(--stack) + env(safe-area-inset-bottom));box-shadow:var(--e-4)}
/* The now-box is one tap target, so it is drawn as one object: a bordered
   card, not a run of loose lines above some buttons. */
.nowbar .nb-box{position:relative;border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--s-3) var(--s-8) var(--s-3) var(--s-3);background:var(--card);cursor:pointer;transition:border-color var(--dur-state) var(--ease-out),background var(--dur-state) var(--ease-out)}
.nowbar .nb-box:hover{border-color:var(--accent);background:var(--hair2)}
/* The head wraps rather than truncates. A stop's name is the one thing on
   this bar you cannot guess from context, so it never gets cut to make room
   for the leg — the leg wraps to its own line instead. */
.nowbar .nb-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 var(--s-2);min-width:0}
.nowbar .nb-go{flex:none;align-self:center;display:flex;color:var(--now)}
.nowbar .nb-time{font-variant-numeric:tabular-nums;font-size:var(--fs-meta);font-weight:var(--fw-bold);color:var(--accent);flex:none}
.nowbar .nb-name{font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:var(--lh-snug);min-width:0}
.nowbar .nb-tp{font-size:var(--fs-meta);color:var(--soft);line-height:var(--lh-small)}
.nowbar .nb-meta{margin-top:var(--s-1);font-size:var(--fs-meta);color:var(--soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:var(--lh-small)}
.nowbar .nb-dir{position:absolute;top:50%;right:var(--s-3);transform:translateY(-50%);display:flex;color:var(--accent)}
/* Reference proportion: the two travel buttons carry the row and Now sits
   between them at its own width, because it is one short word. */
.nowbar .nb-steps{display:flex;gap:var(--s-2);margin-top:var(--s-2)}
.nowbar .nb-steps .btn{min-height:var(--btn-md-h);font-size:var(--btn-md-fs)}
.nowbar .nb-steps .btn{flex:1;min-width:0}
.nowbar .nb-steps .btn.is-now{flex:none;padding:0 var(--s-4)}
/* Red means now: the button that jumps to the current stop, and that stop's
   own pin and label on the map. Nothing else. */
.nowbar .btn.is-now{background:var(--now-strong);border-color:var(--now-strong);color:var(--on-fill)}
/* --soft on --track is 4.39, and this is LEFT that way on purpose. WCAG
   1.4.3 exempts disabled controls, and here the exemption is the point:
   lowered contrast IS how the button says it cannot be pressed. Darkening
   it to pass would make a dead control look live, which costs more than it
   buys. Noted because this button is unusual — CLAUDE.md keeps Now on
   screen disabled rather than hiding it, so unlike most disabled controls
   it is visible most of the day, and an audit will find it again. */
.nowbar .btn.is-now[disabled]{background:var(--track);border-color:var(--line);color:var(--soft)}
/* The time-and-name labels over each pin went with MapMarks (2026-09-27):
   a stop is its number, the tapped one says its name (.pinchip), and the
   time is in the Follow bar and the list. */
#map{position:absolute;inset:0}
/* Two states that used to be one class. `.on` is NOW — the stop the Follow
   bar is on, the one thing on that map that is red by definition. `.sel`
   is a PICK — the spot chosen from a spot's route on the home map — and a
   pick is where you are looking, not where you are: the selection edge,
   ink, outside the pin's white ring (A3, finished 2026-09-23; 0ce939f had
   only moved the red glow onto a token). */
/* Koko on the map is real-time 3D (Yoon, 2026-09-27): one canvas, centred
   on the point Koko stands on, turning to any heading and standing at the
   stop, still 3D — src/koko3d.js. The flat Koko stands in while three.js
   loads, and wherever WebGL is not there. It never takes a tap: the map
   under it stays the map. In Follow the stop you are on has no pin — Koko
   is its mark. The red .pin.on is gone. */
.koko-mark{position:relative;width:100%;height:100%;pointer-events:none}
.koko3d{display:block;pointer-events:none}
/* The flat Koko (KOKO3D off, or no WebGL) is sized by its height, so the
   wider ride drawings keep their shape; feet on the point, centred, and
   mirrored when a ride heads left. */
.koko-flat{position:absolute;left:50%;bottom:0;display:block;height:100%;width:auto;max-width:none;transform:translateX(-50%)}
.koko-flat.is-left{transform:translateX(-50%) scaleX(-1)}
.pin.sel{box-shadow:0 0 0 var(--select-w) var(--select-edge), var(--e-1)}
.stationdot{display:block;width:12px;height:12px;border-radius:var(--rad-round);background:var(--card);border:3px solid var(--route-line);box-shadow:var(--e-1)}
.pinwrap{position:relative;display:flex;align-items:center;justify-content:center}
.panel{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:var(--stack) var(--gutter) var(--section);scrollbar-width:none;display:flex;flex-direction:column}
.panel::-webkit-scrollbar{display:none}
.stepper{order:2;display:flex;align-items:center;gap:var(--stack);margin:var(--stack) 0 0}
.stepper .btn{flex:none}

.stepper .prog{flex:1;text-align:center;font-variant-numeric:tabular-nums;font-size:var(--fs-meta);color:var(--soft);letter-spacing:var(--tr-normal)}
.now{order:1;background:var(--card);border:1px solid var(--hair);border-radius:var(--r);padding:var(--card-pad)}
.now .t{font-variant-numeric:tabular-nums;font-size:var(--fs-meta);color:var(--accent);font-weight:var(--fw-medium)}
.now .nm{font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--tr-title);line-height:var(--lh-tight);margin-top:var(--s-1)}
.now .meta{display:flex;flex-wrap:wrap;gap:var(--s-2);align-items:center;margin-top:var(--s-2)}
.now .note{font-size:var(--fs-meta);color:var(--soft);margin-top:var(--stack);line-height:var(--lh-text)}
.now .go{margin-top:var(--stack)}
.tag{display:inline-flex;align-items:center;gap:var(--s-1);font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--soft);background:var(--bg);border:1px solid var(--hair);border-radius:var(--rad-1);padding:var(--s-1) var(--s-2);letter-spacing:var(--tr-normal)}
.tp{font-size:var(--fs-meta);color:var(--soft);display:inline-flex;align-items:center;gap:var(--s-1)}
.sec-label{font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--soft);letter-spacing:var(--tr-caps);text-transform:uppercase;margin:var(--section) 0 var(--stack);display:flex;align-items:center;gap:var(--s-2)}
.sec-label::after{content:"";flex:1;height:1px;background:var(--hair)}
.docbox{background:var(--card);border:1px solid var(--hair);border-radius:var(--r);padding:var(--s-1) var(--card-pad) var(--s-2);margin-bottom:var(--section)}
.docbox .head{display:flex;align-items:center;gap:var(--s-2);padding:var(--s-3) 0 var(--s-2);border-bottom:1px dashed var(--hair)}
.docbox .head .t{font-size:var(--fs-body);font-weight:var(--fw-bold)}
.docbox .head .s{font-size:var(--fs-micro);color:var(--faint);margin-left:auto}
.doc{display:flex;align-items:center;gap:var(--stack);padding:var(--s-3) 0;border-bottom:1px solid var(--hair2)}
.doc:last-child{border-bottom:none}
.doc .dt{width:38px;height:38px;border-radius:var(--rad-2);display:flex;align-items:center;justify-content:center;flex:none;background:var(--bg);border:1px solid var(--hair);color:var(--soft)}
.doc .info{flex:1;min-width:0}
.doc .info .dn{display:block;font-size:var(--fs-body);font-weight:var(--fw-medium);line-height:var(--lh-snug)}
.doc .info .dm{display:block;font-size:var(--fs-meta);color:var(--soft);margin-top:var(--s-1)}
.doc .open{flex:none}
/* ---- stop card (reference layout) ----
   Flat, full width, no rail. The time sits with the name behind a category
   pin; the leg chip between cards carries the sequence the rail used to. */
/* Elevation, not just an outline. The warm split shadow is what separates a
   white card from a warm page at 1.33 — a hairline alone could not. */
/* No shadows on cards. On a white page a card is delineated by its hairline;
   elevation is reserved for things that genuinely float above something else
   — the Follow bar over the map, the sheet, chrome sitting on tiles. Stacking
   a shadow under every card is what makes an interface look heavy. */

/* Visible to a screen reader, not to the eye. A search field still needs a
   label even when the magnifier makes its purpose obvious to sighted users. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* Headings balance their line breaks rather than leaving a widow. */
h1,.wm,.dayhead .dh-t,.rowhead,.tile-t,.snm{text-wrap:balance}

/* The day heading. Reference sets this at reading size with a pin, not as an
   uppercase caption, because it is the answer to "which day am I looking at"
   rather than a section divider. */
.dayhead{display:flex;align-items:center;gap:var(--s-2);margin:var(--section) 0 var(--stack)}
.dayhead .ic{flex:none;color:var(--soft)}   /* not red: a heading is not "now" */
.dayhead .dh-t{font-size:var(--fs-body);font-weight:var(--fw-bold);letter-spacing:var(--tr-title);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dayhead .dh-n{margin-left:auto;flex:none;font-size:var(--fs-micro);color:var(--faint);font-variant-numeric:tabular-nums}

/* Two columns: a category tile, then everything else. The old card indented
   its own children by a magic 24px to fake this; a real column means the
   indent can never drift out of step with the icon that sets it. */
.stopcard{position:relative;display:flex;gap:var(--s-3);background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--card-pad);margin-bottom:var(--stack);transition:border-color var(--dur-state) var(--ease-out),box-shadow var(--dur-state) var(--ease-out)}
/* A withheld stop: the same card, with everything that costs money removed.
   It is deliberately the same shape as the ones above it — that is what makes
   it read as "there is more day here" rather than as a different component. */
.stopcard.is-locked{background:var(--surface);border-style:dashed;border-color:var(--line)}
.stopcard.is-locked .sico{background:transparent;border:1px dashed var(--line);color:var(--faint)}
.stopcard.is-locked .sname{color:var(--faint)}   /* not --soft: see below */
/* --soft on --surface is 4.39 and fails, and a locked stop's NAME is the
   thing a person reads to decide whether to pay. The v10 migration caused
   this: --soft fell from #475569 (7.58 on white) to #667085 (4.97), which
   pushed it below --faint (#5D6B7F, 4.78 on this ground). The colour meant
   to be quieter now reads better than the one meant to be primary, so the
   two swapped places here. Anything on --surface wants --faint or --ink. */
.stopcard.is-locked .scat{color:var(--faint)}

/* The stop you are standing on in Follow is the stop outlined here, so the
   two views never disagree about where you are — and where you are is a
   selection, so it is ink (--select-edge). The tile loses its navy fill
   with it: a filled tile was a second navy slab on the same card as the
   navy Directions button. It keeps the ink glyph as its half of the cue. */
.stopcard.is-at{border-color:var(--select-edge);box-shadow:inset 0 0 0 calc(var(--select-w) - 1px) var(--select-edge)}
.stopcard .sico{flex:none;display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:var(--rad-2);background:var(--hair2);color:var(--soft)}
.stopcard.is-at .sico{color:var(--ink)}
/* The stop's number, the same mark as its pin on the map (v14 MapMarks:
   "the list uses the same numbers"). The category stays in the card, as a
   word — never on the pin. */
.stopcard .snum{flex:none;display:flex;align-items:center;justify-content:center;width:28px;height:28px;box-sizing:content-box;margin-top:var(--s-1);border:2.5px solid var(--pin-ring);border-radius:var(--rad-round);background:var(--accent);color:var(--on-fill);font-size:var(--fs-micro);font-weight:var(--fw-bold);font-variant-numeric:tabular-nums;line-height:1}
.stopcard .sbodycol{flex:1;min-width:0}
/* 24px, not 32: the save control is a 44px tap target around a ~20px glyph,
   so the title may run under its transparent edge without touching the mark.
   Measured — at 32px the title wrapped for want of 4px. */
.stopcard .shead{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 var(--s-2);padding-right:var(--s-6)}
.stopcard .stime{font-variant-numeric:tabular-nums;font-size:var(--fs-meta);font-weight:var(--fw-bold);color:var(--soft);flex:none}
.stopcard .sname{font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:var(--lh-snug);min-width:0}
.stopcard .kosm{margin-top:var(--s-1)}
.stopcard .smeta3{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2);margin-top:var(--s-2);font-size:var(--fs-meta);line-height:var(--lh-small)}
.stopcard .scat{font-weight:var(--fw-medium);color:var(--soft)}
.stopcard .note{font-size:var(--fs-meta);color:var(--soft);line-height:var(--lh-text);margin-top:var(--s-2)}
/* The destructive action is quiet: one row, no chrome, below a hairline. */
.acts.quiet{margin-top:var(--s-2);padding-top:var(--s-2);border-top:1px solid var(--hair2)}
.stop{display:flex;gap:var(--stack);margin-bottom:var(--stack)}
.rail{display:flex;flex-direction:column;align-items:center;width:var(--rail);flex:none}
.rail .tm{font-variant-numeric:tabular-nums;font-size:var(--fs-meta);font-weight:var(--fw-medium);color:var(--ink)}
.rail .node{width:13px;height:13px;border-radius:var(--rad-round);margin:var(--s-2) 0;border:2px solid var(--nc,var(--ink));background:var(--card)}
.rail .thread{width:2px;flex:1;background:var(--hair);min-height:var(--s-6)}
/* The warning row carries what the paid product actually sells — that a place
   is shut on Tuesdays, that it needs booking two weeks out. It must not read
   as another line of the note, so it gets its own colour and rule. */
.warn{display:flex;gap:var(--s-2);margin-top:var(--s-2);padding:var(--s-2) var(--s-3);border-radius:var(--rad-1);background:var(--warn-bg);color:var(--warn-ink);box-shadow:inset 3px 0 0 var(--warn-ink);font-size:var(--fs-meta);line-height:var(--lh-text)}
.warn .ic{flex:none;font-size:var(--ic-1);margin-top:1px}
/* LOCAL MOVE — advice, not caution. Same geometry as .warn so the two stack
   evenly, and three deliberate differences so they never read as the same
   thing: the neutral tinted ground instead of amber (amber is caution only,
   and a rooftop tip in a hazard band inverts its tone), no inset bar (a
   warning outranks advice and should look like it), and its own glyph.
   Telling them apart by SHAPE is the system's own rule for categories.
   PROVISIONAL: the design system has no tip row yet. Its navy-soft is
   defined as a tonal secondary or a selected row, i.e. "tappable", and this
   row is not — see moveRow() in app.js. */
/* An area stop's badge. Same weight as the category beside it — this is
   what KIND of stop it is, not a warning and not an action, so it takes no
   colour of its own. The glyph carries it, as with categories. */
.scat.is-area{display:inline-flex;align-items:center;gap:var(--s-1)}
.scat.is-area .ic{font-size:var(--ic-1)}
/* The way home, closing a route's list. A quiet full-width row: it is
   not a stop, it is what happens after the last one. */
.gohome{display:flex;align-items:center;gap:var(--s-2);margin:var(--stack) 0 0;
  padding:var(--s-3) var(--s-4);background:var(--surface);border-radius:var(--rad-2);
  font-size:var(--fs-meta);color:var(--ink)}
.gohome .ic{flex:none;color:var(--soft)}

/* The address, under the arrival line. Quieter than it, because how you
   get there is the useful half and this is the half you show a driver. */
.saddr{display:flex;align-items:flex-start;gap:var(--s-1);margin-top:var(--s-1);
  font-size:var(--fs-micro);color:var(--soft);line-height:var(--lh-small)}
.saddr .ic{font-size:var(--ic-1);flex:none;margin-top:1px}
.leg-n{font-style:normal;color:var(--soft);font-size:var(--fs-micro);margin-left:var(--s-2)}

/* A stop's own numbers. Quiet — they are facts about the stop, not the
   thing being sold, and they sit between the meta line and the note. */
.snums{display:flex;align-items:center;gap:var(--s-2);margin-top:var(--s-2);
  font-size:var(--fs-micro);color:var(--soft);font-variant-numeric:tabular-nums}
.snums .ic{font-size:var(--ic-1);margin-right:var(--s-1);vertical-align:-.15em}
.sn-d{width:3px;height:3px;border-radius:var(--rad-round);background:var(--line);flex:none}
.tip{display:flex;gap:var(--s-2);margin-top:var(--s-2);padding:var(--s-2) var(--s-3);border-radius:var(--rad-1);background:var(--surface);color:var(--ink);font-size:var(--fs-meta);line-height:var(--lh-text)}
.tip .ic{flex:none;font-size:var(--ic-1);margin-top:1px;color:var(--soft)}
/* The leg between two stops. It sits in the rail gutter so the eye reads
   stop -> how you get there -> stop, which is what makes a day feel planned. */
.leg{display:flex;align-items:center;gap:var(--s-2);margin:0 0 var(--stack) var(--s-4);padding:var(--s-1) 0;color:var(--soft);font-size:var(--fs-meta);line-height:var(--lh-small)}
.leg .ic{font-size:var(--ic-1);flex:none}
/* Koko carries the leg's mode (v14 LegRow), --koko-row tall (32 — 24 lost
   the face), in a slot as wide as the widest ride, the subway at 520:310,
   so the words start in one column whatever the mode. The Follow bar is
   the same row: same size, same slot. */
.koko-leg{flex:none;display:inline-flex;align-items:flex-end;justify-content:center;width:calc(var(--koko-row) * 1.7)}
.koko-leg img{display:block;height:var(--koko-row);width:auto}
.leg b{font-variant-numeric:tabular-nums;font-weight:var(--fw-medium);color:var(--ink)}
/* The traveller's own leg (NEXT.md 이동수단, item 10). Read on the left,
   chosen on the right: the picker is D11's segmented control, and the
   chosen mode wears the selection edge in ink (D13) — the kit's pressed
   state is navy, and a selection is not an action. Two fields open under
   it for a ride; they are inputs, so they take an input's shape (16px, the
   --line-strong edge a control standing on its own needs), and they are
   16px type or larger so iOS does not zoom the page when one is tapped. */
.leg.leg-own .legmode{margin-left:auto;display:inline-flex;align-items:center;flex:none;
  border:1px solid var(--line-strong);border-radius:var(--rad-1);background:var(--card);overflow:hidden}
.legmode .btn.ghost{color:var(--soft);border-radius:0}
.legmode .btn+.btn{border-left:1px solid var(--hair)}
.legmode .btn[aria-pressed="true"]{color:var(--ink);box-shadow:inset 0 0 0 var(--select-w) var(--select-edge)}
.legin{display:flex;align-items:center;gap:var(--s-2);margin:calc(-1 * var(--s-2)) 0 var(--stack) var(--s-4)}
.legin-t,.legin-m input{min-height:var(--btn-sm-h);border:1px solid var(--line-strong);border-radius:var(--rad-1);
  background:var(--card);font:inherit;font-size:var(--fs-body);color:var(--ink)}
.legin-t{flex:1;min-width:0;padding:0 var(--s-3)}
.legin-m{display:inline-flex;align-items:center;gap:var(--s-1);flex:none;font-size:var(--fs-meta);color:var(--soft)}
.legin-m input{width:60px;padding:0 var(--s-2);text-align:center;font-variant-numeric:tabular-nums;-moz-appearance:textfield}
.legin-m input::-webkit-inner-spin-button,.legin-m input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.legin-t:focus,.legin-m input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}
/* Primary actions sit apart from the destructive pair. */
.acts.secondary{margin-top:var(--s-2);padding-top:var(--s-2);border-top:1px solid var(--hair2)}

.stepper .now-btn{flex:none;color:var(--accent)}
.acts{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--stack)}
/* ============================================================
   BUTTONS — one kit
     size   .sm .lg            (md is the default)
     role   .primary .ghost .danger   (secondary is the default)
     shape  .icon .round .block
     state  [disabled] [aria-pressed] — hover and active are global
   ============================================================ */
.btn{
  display:inline-flex; align-items:center; justify-content:center;
  gap:var(--s-2); white-space:nowrap;
  min-height:var(--btn-md-h); padding:0 var(--btn-md-px);
  font-size:var(--btn-md-fs); font-weight:var(--fw-medium); font-family:inherit;
  border-radius:var(--rad-2); border:1px solid var(--line);
  background:var(--fill-2); color:var(--ink); cursor:pointer;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation;
  transition:background var(--dur-state) var(--ease-out),
             border-color var(--dur-state) var(--ease-out),
             color var(--dur-state) var(--ease-out);
}
.btn .ic{font-size:var(--ic-1);flex:none}

/* --- size --- */
.btn.sm{min-height:var(--btn-sm-h);padding:0 var(--btn-sm-px);font-size:var(--btn-sm-fs);border-radius:var(--rad-1)}   /* btn-sm is radius-sm: it sits inside a 16px card */
.btn.lg{min-height:var(--btn-lg-h);padding:0 var(--btn-lg-px);font-size:var(--btn-lg-fs)}
.btn.lg .ic{font-size:var(--ic-2)}

/* --- role --- */
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-fill)}
/* Ghost is ink on a border-strong edge, no fill (v14 Button): it was grey
   text with no edge, and v14 moved it off navy so a screen with several
   ghosts still has one navy action. */
.btn.ghost{background:transparent;border-color:var(--line-strong);color:var(--ink)}
/* ...a word's edge, not an icon's: a bookmark or a close in a card's
   corner is the glyph alone — boxed, it read as a second card. */
.btn.ghost.icon{border-color:transparent}
.btn.danger{background:transparent;border-color:transparent;color:var(--danger)}

/* --- shape --- */
.btn.icon{padding:0;width:var(--btn-md-h);min-width:var(--btn-md-h)}
.btn.icon.sm{width:var(--btn-sm-h);min-width:var(--btn-sm-h)}
.btn.icon.lg{width:var(--btn-lg-h);min-width:var(--btn-lg-h)}
.btn.icon .ic{font-size:var(--ic-2)}
.btn.round{border-radius:var(--rad-round)}
.btn.block{width:100%}

/* --- state --- */
@media (hover:hover){
  .btn:hover{background:var(--hair2)}
  .btn.primary:hover{background:var(--accent-hover)}
  .btn.ghost:hover,.btn.danger:hover{background:var(--hair2)}
}
/* Disabled (v14 Button): the sunken fill and ink-subtle label — a quiet
   control, not a faded copy of a live one. */
.btn[disabled]{background:var(--surface);border-color:var(--surface);color:var(--faint);cursor:default}
/* Loading: the label stays, a 16px ring in the label's colour takes the
   icon's place, and the width does not change. */
.btn.is-loading{pointer-events:none}
.btn.is-loading .ic{display:none}
.btn.is-loading::before{content:"";flex:none;width:16px;height:16px;box-sizing:border-box;border-radius:var(--rad-round);
  border:2px solid currentColor;border-right-color:transparent;animation:btnSpin var(--dur-spin) linear infinite}
@keyframes btnSpin{to{transform:rotate(360deg)}}
.btn[disabled]:active{transform:none}
/* A pressed toggle — saved, copied — fills with the accent it represents. */
.btn[aria-pressed="true"]{color:var(--accent)}
.btn.filled[aria-pressed="true"]{background:var(--ink-btn);border-color:var(--ink-btn);color:var(--on-fill)}
/* ---- the gate ---- */
.gate{margin-top:var(--section);background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--card-pad)}
.gate-k{font-size:var(--fs-micro);font-weight:var(--fw-medium);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--accent)}
.gate-list{margin:var(--stack) 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:var(--s-2)}
.gate-list li{position:relative;padding-left:var(--s-6);font-size:var(--fs-meta);line-height:var(--lh-text);color:var(--ink)}
.gate-list li::before{content:"";position:absolute;left:var(--s-2);top:.62em;width:6px;height:6px;border-radius:var(--rad-round);background:var(--accent)}
.gate-cta{margin-top:var(--card-pad)}
.gate-fine{margin-top:var(--s-2);text-align:center;font-size:var(--fs-micro);color:var(--faint);line-height:var(--lh-small)}
/* The way back to a purchase made elsewhere (NEXT.md 9 ③): quiet, under
   the one navy fill, centred on the fine print it follows. */
.gate-restore{display:flex;width:max-content;margin:var(--s-3) auto 0}
/* The restore sheet: the paste box's input and its one-line message, an
   email first, then the code the email carries. */
.rs-form{margin-top:var(--stack);display:flex;flex-direction:column;gap:var(--s-2)}
.rs-code{font-variant-numeric:tabular-nums;letter-spacing:var(--tr-caps)}
.rs-again{align-self:flex-start}
/* Locked stops are named, never placed — the shape of the day, not the day. */
.stop.locked .nm{color:var(--soft)}
.stop.locked .rail .tm{color:var(--faint);font-size:var(--ic-1)}
.stop.locked .node{border-color:var(--hair);background:var(--bg)}
/* The sheet rises and the page behind it steps back — the iOS/Airbnb idiom.
   The recede is what makes a sheet read as a layer rather than as a panel
   that slid over the top, and it is why the corner rounds at the same time:
   a page that has stepped back is no longer flush to the screen edge. */
.screen-body{flex:1;min-height:0;display:flex;flex-direction:column;
  transform-origin:50% 0;
  transition:transform var(--dur-sheet) var(--ease-sheet),
             border-radius var(--dur-sheet) var(--ease-sheet),
             filter var(--dur-sheet) var(--ease-out)}
#screenTrip[data-sheet="open"] .screen-body{
  transform:scale(.94) translateY(10px); border-radius:var(--rad-2); overflow:hidden;
  filter:brightness(.97)}

/* A route's marker on the home map. Navy because it is tappable, and a
   category SHAPE rather than a per-route hue — Komoot colours each route's
   marker and LOKO cannot spend a third meaning on identity. */
/* Orientation: the map's own names, in LOKO's English. Grey and not
   tappable — navy would read as one of LOKO's marks and invite a tap that
   does nothing. Set on the ground with a halo, the way a map labels a
   place, rather than boxed like a control. */
/* Sits BELOW its point, not on it. Five of these name the same places
   LOKO has routes in, so the route's dot lands on the same coordinate —
   centred, the dot covered the word.

   The transform here is only the fallback. app.js writes an inline one
   per label, because it also tries above / right / left when below is
   taken, and the collision box is computed from the very same offsets —
   one set of numbers, so the model cannot disagree with the render. */
.opin{position:absolute;left:0;top:0;transform:translate(-50%,10px);
  white-space:nowrap;font-size:var(--fs-micro);font-weight:var(--fw-medium);
  /* --ink, not --soft. Grey was chosen so these would not read as LOKO's
     navy marks, and it made them hard to read at all — which defeats the
     one job they have, telling a first-time visitor where they are
     looking. Ink is neutral: it is the map's own voice, distinct from
     navy without being faint. */
  color:var(--faint);letter-spacing:.02em;pointer-events:none;   /* ink-subtle since MapMarks: the route's chip reads first */
  text-shadow:0 0 3px var(--card),0 0 3px var(--card),0 0 6px var(--card)}

/* ---- the main map's full view (v14 MapMarks, Yoon 2026-09-27) ----
   A route is a line; where it starts is a dot; its name is one chip, on the
   route in focus only. The navy start discs with a format glyph (below,
   .rpin) are gone with the category pins: no picture on the map but Koko. */
.rdot{width:var(--tap);height:var(--tap);display:flex;align-items:center;justify-content:center;cursor:pointer}
/* Navy on the pin's white ring, as its line is navy on a white casing
   (Yoon, 2026-09-29 — the ink-muted dot and line read as routes switched
   off). The route in focus turns ink: selected is ink. */
.rdot i{width:10px;height:10px;box-sizing:content-box;border-radius:var(--rad-round);background:var(--accent);border:2.5px solid var(--pin-ring);box-shadow:var(--e-1)}
.rdot.is-active i{background:var(--ink)}
/* With every stop drawn, stop 1's pin is the start: the dot keeps its tap. */
.rdot.is-quiet i{display:none}
/* The full view's stops (Yoon, 2026-09-29): the stop pin at 20px — the
   same navy, ring and 13px number as the route view's 28px one — where it
   has room, a small navy dot where it would sit on its neighbour. */
.pin.is-sm{width:20px;height:20px;border-width:2px}
/* A LOKO PICK spot (NEXT.md 12, B9): a place of its own, on no line — the
   10px navy dot on the pin's white ring, in a 44px tap. */
.pickdot{width:var(--tap);height:var(--tap);display:flex;align-items:center;justify-content:center;cursor:pointer}
.pickdot i{width:10px;height:10px;box-sizing:content-box;border-radius:var(--rad-round);background:var(--accent);border:2.5px solid var(--pin-ring);box-shadow:var(--e-1)}
.stopdot{display:block;width:6px;height:6px;box-sizing:content-box;border-radius:var(--rad-round);background:var(--accent);border:1.5px solid var(--pin-ring)}
/* The focused route's two ends, and every stop on a card's picture
   (tools/render-thumbs.js) — navy, the stop's colour (v14.8). */
.rend{display:block;width:10px;height:10px;box-sizing:content-box;border-radius:var(--rad-round);background:var(--accent);border:2px solid var(--card)}
/* Its name, once, at the middle of the line: a pill with the ink edge. */
/* The 1.5px edge is a 1px border plus a half-pixel inset shadow, the way a
   selected stop card draws it: Chrome floors a 1.5px BORDER to 1px, which
   the step-5 comparison measured on this chip. */
.rchip{position:absolute;left:0;top:0;transform:translate(-50%,-50%);white-space:nowrap;pointer-events:none;
  background:var(--card);border:1px solid var(--select-edge);border-radius:var(--rad-pill);padding:var(--s-1) var(--s-3);
  font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--ink);
  box-shadow:inset 0 0 0 calc(var(--select-w) - 1px) var(--select-edge), var(--e-1)}

/* The route in focus draws itself, leg by leg, in walking order. pathLength=1 is
   set on the path in drawOn(), which is what lets one duration cover a 90m
   walk and a 3km ride — the line is normalised, so nothing here has to know
   how long the geometry is.

   `both`, and the class is removed on animationend as well. `backwards`
   alone is the trap: the stagger needs it (a later leg would otherwise be
   drawn whole while it waits its turn), but WITHOUT the forwards half the
   element snaps back to its own rule when the animation ends — and its own
   rule here is dashoffset:1, which is an invisible line. Caught by watching
   the frames: each leg drew and then vanished, one after another, and the
   finished route was a map with nothing on it. */
.legline.line-draw{stroke-dasharray:1;stroke-dashoffset:1;
  animation:lineDraw var(--dur-enter) var(--ease-out) both}
@keyframes lineDraw{to{stroke-dashoffset:0}}

/* The card row under the map. The neighbours peek at both edges on purpose:
   a single centred card teaches nobody that it swipes, and the capture
   shows the same overhang. Snap points do the paging. */
/* D17 (2026-09-23), measured rather than assumed. The row sat inside
   .spotcard's 16px gutters from the day it was built (22a0e0c), so it was
   343px wide, not 375 — and a percentage flex-basis resolves against the
   content box, inside the padding, on top of that. Every card came out
   243px, not the 283 or 319 the notes here were reasoning about: 143px
   left for the title, worse than the four-lines-deep layout 46px gutters
   had already been rejected for. Then .rc-m's nowrap set the card's
   minimum content width, so a route with a long strip (Classic Seongsu,
   379px) ran off the screen, lost its right side, and pushed the
   neighbour it was meant to show out of sight.

   So: the row spans the screen, the card IS the row's content box, and
   min-width:0 lets the strip ellipsize instead of widening its card. At
   375px that is a 319px card with 20px of each neighbour showing past the
   8px gap — the overhang's whole job — centred at every stop. (No cap for
   wide screens: .app is never wider than 430px.) */
.spotcard.is-row{left:0;right:0;padding:0;background:transparent;border:0;box-shadow:none}
.rrow{display:flex;gap:var(--s-2);overflow-x:auto;scroll-snap-type:x mandatory;
  scrollbar-width:none;padding:0 var(--s-7);scroll-padding:0 var(--s-7)}
.rrow::-webkit-scrollbar{display:none}
.rcard{flex:0 0 100%;min-width:0;scroll-snap-align:center;display:flex;gap:var(--s-3);
  background:var(--card);border-radius:var(--rad-2);padding:var(--s-3);
  box-shadow:var(--e-3);cursor:pointer;align-items:center;
  transition:box-shadow var(--dur-state) var(--ease-out)}
/* The neighbour steps BACK, it does not fade. `opacity:.72` on the whole
   card was the wrong instrument: it makes a floating white surface
   translucent, so the street grid shows through the card and through its
   thumbnail, and every ratio in the design system is computed on a card
   that is `--card`. Measured against the real Voyager pixels under the
   row, the five commonest tile colours on screen:

     --soft  (.rc-m meta, 13px)   4.97 -> 2.92    fails 4.5
     --accent(.rc-k kicker)       8.71 -> 4.41    fails 4.5
     --ink   (.rc-t title, 19px) 16.82 -> 6.79    holds

   Two of the three text roles drop under the floor, and they drop
   exactly when it costs most: at rest a neighbour is a 22px sliver
   nobody reads, so the fade only becomes legible MID-SWIPE — the moment
   the traveller is reading the incoming title and meta to decide whether
   to stop on it.

   Elevation says the same thing for nothing. "Shadow means floating over
   the map" is already this app's rule, so a shallower shadow is a card
   floating lower — further back — in the vocabulary the row already
   speaks. It leaves the ground opaque, so every ratio above is the solid
   -card column again.

   Not scale: `transform` would be safe here (no Leaflet container is
   inside `.rcard`), but scaling .97 from the centre pulls ~5px off each
   overhang, and the 22px peek above is a measured value with a job. */
.rcard:not(.on){box-shadow:var(--e-2)}
/* The trip's slot before there is a trip: a plus where the photo goes,
   dashed instead of filled, so it reads as "make one" rather than as a
   route you have not looked at. */
/* Flat in both states: it has no shadow to step down, and its dashed edge
   cannot lighten — `--line-strong` is the value CLAUDE.md pins at 3:1 for a
   control edge, and this whole card is `role="button"`. It needs no state of
   its own: at rest 22px of it shows, where no card reads as on or off, and
   anywhere wider the centred card's elevation already says which is live. */
.rcard.rc-new{background:var(--bg);box-shadow:none;border:1.5px dashed var(--line-strong)}
.rcard.rc-new .rc-thumb{background:var(--accent-wash);color:var(--accent)}   /* the app's own tonal navy; --navy-soft is the design system's name for it */
.rcard.rc-new .rc-t{color:var(--accent)}

/* Add to my trip — on the map card and in the planner sheet. One
   button, or the day row it opens into. The "in" state is a ghost with a
   check: it is a fact, not an action, and tapping it opens the day. */
.sadd{margin-top:var(--stack)}
.sadd .tripadd{width:100%;justify-content:center}
.sadd .tripadd.is-in{color:var(--soft)}
.daypick{display:flex;gap:var(--s-2);flex-wrap:wrap}
.daypick .btn{flex:1 1 auto}
/* Editing the own trip: the three small icon buttons sit at the end of
   the card's action row. Disabled at the ends of the day — WCAG exempts a
   disabled control from the contrast floor, and a button that cannot act
   should look like it cannot. */
/* D11 — the edit group. It was three borderless glyphs in --soft at the
   card's far edge, measured 4.97:1 and legible, and still read as
   decoration: nothing said they were one control. A hairline around the
   three and --ink on the glyphs makes a segmented control — the reorder is
   the planner's core action and should look like something you press.
   8px corners, because it sits inside a 16px card (the shape lock). It
   stays ghost-weight so Directions above it keeps the lead. Both renderers
   draw it (editControls() in app.js), so the list and the timed trip can
   no longer drift apart. */
.edit{margin-left:auto;display:inline-flex;align-items:center;border:1px solid var(--line-strong);border-radius:var(--rad-1);background:var(--card);overflow:hidden}
.edit .btn.ghost{color:var(--ink);border-radius:0;border-color:transparent}
.edit .btn+.btn{border-left:1px solid var(--hair)}
.edit .btn[disabled]{background:transparent;color:var(--faint)}
.edit .btn[data-act="rm"]{color:var(--soft)}
/* + Day, at the end of the strip. Dashed like the plan card: it makes one. */
.days .day-new{border-style:dashed;color:var(--accent)}
/* The view toggle and the page's one navy fill in one row (NEXT.md 5a):
   Add to my trip at btn-lg, filling what the toggle leaves. In Follow the
   Follow bar's Next is that navy action, so the add steps out there. */
.viewrow{display:flex;align-items:center;gap:var(--s-2);padding:0 var(--gutter)}
.viewrow .toggle{margin:0;flex:none}
.tripadd-slot{flex:1;min-width:0}
.tripadd-slot:empty{display:none}
.tripadd-slot .tripadd{width:100%;justify-content:center;overflow:hidden;padding:0 var(--s-2);gap:var(--s-1)}   /* beside the toggle there are 142px: "+ Add to my trip" at 17px, measured */
#screenTrip[data-view="overview"]:not([data-kind="bundle"]) .tripadd-slot{display:none}
.ttools-slot{margin-top:var(--s-2)}
.ttools-slot:empty{display:none}
/* The own trip's name is editable in place; a dashed underline on focus
   says so without a pencil that would be a fourth thing in the header. */
/* No radius: the 4px that was here was a fifth value outside the shape
   lock, rounding the ends of a focus underline nobody could see curve. */
#tripTitle[contenteditable="true"]{cursor:text}
/* D15. Title and pencil share one line; the title gives way (min-width:0)
   so the Follow header's ellipsis still works, and the button is pulled
   into the title's line box so the header does not grow by the 36px the
   kit gives it. */
.hdr .ttl{display:flex;align-items:center;gap:var(--s-1);min-width:0}
.hdr .ttl h1{min-width:0}
.hdr .ttl .btn{flex:none;margin:0 0 calc(-1 * var(--s-1))}
#tripTitle[contenteditable="true"]:focus{outline:none;box-shadow:0 2px 0 0 var(--accent)}

/* The own trip's tools: a start time, and the button that turns dwell and
   walks into a clock. Inline, quiet, under the byline. */
.ttools{display:flex;gap:var(--s-2);align-items:center;flex-wrap:wrap}
.tstart{display:inline-flex;align-items:center;gap:var(--s-1);font-size:var(--fs-meta);color:var(--soft)}
/* D12: the one control in the app that still looked like the browser's.
   An input, so an input's shape — 16px, the --line-strong edge a control
   standing on its own needs — at the kit's small height, with the app's
   chevron in place of the platform arrow. Empty reads as a placeholder. */
.tstart .tsel{position:relative;display:inline-flex;align-items:center}
.tstart select{-webkit-appearance:none;appearance:none;font:inherit;font-variant-numeric:tabular-nums;color:var(--ink);background:var(--card);border:1px solid var(--line-strong);border-radius:var(--rad-1);min-height:var(--btn-sm-h);padding:0 calc(var(--s-3) + var(--ic-1) + var(--s-1)) 0 var(--s-3);cursor:pointer;transition:background var(--dur-state) var(--ease-out)}
.tstart select:hover{background:var(--surface-hover)}
.tstart select.is-empty{color:var(--faint)}
.tstart select option{color:var(--ink)}   /* desktop lists inherit the placeholder grey otherwise */
.tstart .tsel .ic{position:absolute;right:var(--s-3);pointer-events:none;color:var(--soft)}
/* "about 14:20" in the numbers strip — computed, so it says so. */
.sn-at{color:var(--ink);font-weight:var(--fw-medium);font-variant-numeric:tabular-nums}
/* A generated caution reads exactly like an authored one: same band, same
   glyph. The fact is the same kind of fact. */
.warn.is-gen b{font-weight:var(--fw-bold)}
.warn .lnk{background:none;border:0;padding:0;font:inherit;color:var(--accent);text-decoration:underline;text-underline-offset:2px;cursor:pointer}
/* The Now line in the Follow bar. */
.nb-now{font-size:var(--fs-meta);color:var(--ink);margin-top:2px;font-variant-numeric:tabular-nums;display:flex;align-items:center;gap:var(--s-2)}
/* An empty own day offers LOKO's days, after saying it is empty. */
/* The empty state's Koko, waving, 72px (v14 EmptyState). The block around
   it is still the old dashed card; the component itself is step 4b. */
.koko-empty{display:block;height:auto;width:var(--koko-empty);margin:0 auto var(--s-3)}
/* The empty day's LOKO days: one under another, as wide as the paste box
   above them — they were centred chips of three widths. */
.empty .offer{margin-top:var(--section);display:flex;flex-direction:column;align-items:stretch;gap:var(--s-2)}
.empty .offer span{font-size:var(--fs-meta);color:var(--soft)}
.empty .offer .btn{width:100%}
/* In the trip, the panel already holds the gutter: the empty day spans the
   same 343 as the cards and the account under it, not a box inset twice. */
#panel .empty{margin:0;padding-left:0;padding-right:0}

/* Discover's collapsed edge on the city map: a grabber and the count,
   the reference's "57 hikes". It floats over the map, so it carries the
   shadow; the row takes its place from the neighbourhood zoom up. */
.peek{position:absolute;left:var(--gutter);right:var(--gutter);bottom:calc(var(--nav-top) + 14px + env(safe-area-inset-bottom));z-index:900;
  background:var(--card);border-radius:var(--rad-2);box-shadow:var(--e-3);padding:var(--s-2) var(--card-pad) var(--s-3);cursor:pointer;
  animation:cardIn var(--dur-enter) var(--ease-out)}
.peek .grab{margin:0 auto var(--s-2)}
.pk-row{display:flex;align-items:center;justify-content:space-between;gap:var(--s-2)}
.pk-t{display:inline-flex;align-items:center;gap:var(--s-2);font-weight:var(--fw-medium);color:var(--ink)}
.pk-t .ic{color:var(--faint)}
/* The spot card steps sideways. */
.sstep{display:flex;align-items:center;justify-content:center;gap:var(--s-2);margin-top:var(--stack);font-size:var(--fs-meta);color:var(--soft);font-variant-numeric:tabular-nums}
/* GO LOKO — the character moment. The one spring in the app. */
.golo{position:absolute;left:50%;top:38%;z-index:1200;display:flex;align-items:center;gap:var(--s-2);padding:var(--s-3) var(--s-5);background:var(--card);border-radius:var(--rad-pill);box-shadow:var(--e-3);font-weight:var(--fw-bold);font-size:var(--fs-title);letter-spacing:.02em;color:var(--ink);pointer-events:none;white-space:nowrap;
  transform:translate(-50%,-50%) scale(.6);opacity:0;transition:transform var(--t-pop) var(--ease-pop),opacity var(--dur-state) var(--ease-out)}
.golo img{height:22px;width:auto;display:block}
.golo.in{transform:translate(-50%,-50%) scale(1);opacity:1}
.golo.out{opacity:0;transform:translate(-50%,-56%) scale(1);transition:opacity var(--dur-sheet) var(--ease-out),transform var(--dur-sheet) var(--ease-out)}

/* The route's intro: its paragraph, clamped to three lines with a Show
   more, and the gate's line as plain information. Flat on the page, a
   hairline below — not a card. */
.intro{padding:0 0 var(--stack);margin-bottom:var(--stack);border-bottom:1px solid var(--hair)}
.intro .desc{margin:0;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--ink)}
.intro .desc.is-long:not(.is-open){display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.intro .more{background:none;border:0;padding:var(--s-1) 0 0;font:inherit;font-size:var(--fs-meta);color:var(--accent);cursor:pointer}
/* The route's numbers, three across. Not the reference's 2x2 grid: that
   wants four values and LOKO has three, and the fourth would have to be
   invented. Flat, because this sits on the page and nothing here floats —
   a hairline box, the same vocabulary as .mestats, which is the same idea
   one screen over. tabular-nums so 6 / 2.8 km / 4-6 do not jitter when the
   day strip changes underneath. */
.rstats{display:flex;gap:var(--s-2);margin-top:var(--stack)}
.rstats>div{flex:1;min-width:0;background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-2);
  padding:var(--s-3) var(--s-2);text-align:center}
.rstats b{display:block;font-size:var(--fs-title);font-weight:var(--fw-bold);font-variant-numeric:tabular-nums;
  line-height:var(--lh-tight);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rstats span{display:block;margin-top:2px;font-size:var(--fs-micro);color:var(--soft);letter-spacing:.04em;text-transform:uppercase}
.intro .known{display:flex;gap:var(--s-2);align-items:flex-start;margin-top:var(--stack);font-size:var(--fs-meta);color:var(--soft)}
.intro .known .ic{flex:none;color:var(--faint);margin-top:1px}
.intro .known b{color:var(--ink);font-weight:var(--fw-bold)}

/* ---- review mode. No new colour: an unreviewed route IS a caution
   state, so it takes the caution band the design system already has, and
   the collisions inside it are the same amber rows a traveller would
   see. Nothing here ships to a traveller — REVIEW is localhost only. */
.revbar{margin-bottom:var(--stack);padding:var(--s-3);border-radius:var(--rad-2);
  background:var(--warn-bg);color:var(--warn-ink);box-shadow:inset 3px 0 0 var(--warn-ink);
  font-size:var(--fs-meta);line-height:var(--lh-text)}
.revbar .rv-hd{display:flex;align-items:center;gap:var(--s-2)}
.revbar .rv-hd b{letter-spacing:var(--tr-caps)}
.revbar .rv-hd span{color:var(--warn-ink);opacity:.75;font-size:var(--fs-micro)}
.revbar .rv-hd .ic{flex:none}
/* DRAFT: the one state in review mode a route cannot ship in. Caution
   colours, because review mode is the caution band's own surface; filled,
   so it outranks the REVIEW label it sits beside. */
.revbar .rv-hd .rv-draft{background:var(--warn-ink);color:var(--warn-bg);padding:0 var(--s-2);border-radius:var(--rad-1);font-size:var(--fs-micro)}
.pick.is-draft,.tile-pick.is-draft,.hdr .kick.is-draft{color:var(--warn-ink)}
.tile-pick.is-draft{background:var(--warn-bg)}
.revbar .rv-n{display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-3);margin-top:var(--s-2);
  font-size:var(--fs-micro);font-variant-numeric:tabular-nums}
.revbar .rv-n .is-off{font-weight:var(--fw-bold)}
.revbar .rv-note{margin:var(--s-2) 0 0}
.revbar .rv-note.is-late b{text-decoration:underline;text-underline-offset:2px}
.revbar .rv-flags{margin:var(--s-2) 0 0;padding-left:var(--s-4)}
.revbar .rv-flags li{margin-top:2px}
.revbar .rv-fine{margin:var(--s-3) 0 0;font-size:var(--fs-micro);opacity:.8}
.revbar code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em}
/* Under each stop: signed or not, and the sources one tap away. Quiet —
   it is scaffolding for the reviewer, not part of the day. */
.revrow{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-2);
  padding-top:var(--s-2);border-top:1px dashed var(--line);
  font-size:var(--fs-micro);color:var(--soft)}
.revrow .ic{flex:none;font-size:var(--ic-1)}
.revrow b{color:var(--warn-ink)}
.revrow .revsrc{margin-left:auto;display:flex;gap:var(--s-2)}
.revrow .revsrc a{color:var(--accent)}
.revrow .revsrc.is-none{color:var(--faint)}

/* Me: the trip card in a column, three true numbers, one honest line. */
.me-trip{margin-bottom:var(--stack)}
.mefine{font-size:var(--fs-meta);color:var(--soft);margin:var(--stack) 0 0;max-width:40ch}
/* Your own trip is the My trip tab (2026-09-28): the bar floats over it as
   it does over the map, so what sits at the bottom clears it — the list,
   the Follow bar, the place sheet — and there is no back button: you leave
   through another door. */
#screenTrip.with-nav .panel{padding-bottom:calc(var(--section) + var(--nav-top))}
#screenTrip.with-nav .nowbar{bottom:var(--nav-top)}
#screenTrip.with-nav #btnBack{display:none}
/* The own trip's note, edited where it is read. A placeholder in the
   muted ink until there is one; the dashed underline says "yours". */
.note.is-edit{cursor:text;min-height:1.4em}
/* F19 — the paste row at the foot of each day of the traveller's own trip.
   Closed it is one quiet ghost row, because it sits under every day and must
   not outrank the stops above it; open it is an input the size of the
   other inputs in the app (16px, the shape lock's input radius). */
.addplace-btn{margin-top:var(--stack);width:100%;justify-content:flex-start;color:var(--accent)}
.addplace{margin-top:var(--stack);display:flex;flex-direction:column;gap:var(--s-2)}
.addplace-in,.addplace-name input{width:100%;min-height:var(--tap);padding:0 var(--s-4);
  border:1px solid var(--line-strong);border-radius:var(--rad-2);background:var(--card);
  font:inherit;font-size:var(--fs-body);color:var(--ink)}
.addplace-in:focus,.addplace-name input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}
.addplace-name{display:flex;gap:var(--s-2)}
.addplace-name input{flex:1}
/* What the paste box says (v14 Input): an error is one caption line in
   now-ink under a now-ink edge — never a fill; it was an amber block, and
   amber is caution, not "that link will not read". A note on the way
   ("Opening that short link…") is the same line in ink-muted. */
.addplace-msg{font-size:var(--fs-micro);line-height:var(--lh-text);color:var(--now-ink)}
.addplace-msg.is-note{color:var(--soft)}
.addplace-in.is-error{border-color:var(--now-ink)}
.empty .addplace{text-align:left}
/* A stop's own name, renamed in place. Same treatment as its note: nothing
   until focused, then the accent underline — the card already says "yours"
   by carrying the edit controls, so it does not need a box around the name. */
.nm.is-edit,.sname.is-edit{cursor:text}
.nm.is-edit:focus,.sname.is-edit:focus{outline:none;box-shadow:0 2px 0 0 var(--accent)}
.note.is-edit:empty::before{content:attr(data-ph);color:var(--faint)}
.note.is-edit:focus{outline:none;box-shadow:0 2px 0 0 var(--accent)}
.rc-thumb{flex:none;position:relative;width:64px;height:64px;border-radius:var(--rad-1);overflow:hidden;
  background:var(--surface);display:flex;align-items:center;justify-content:center;color:var(--soft)}
.rc-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.rc-body{min-width:0}
/* No font-family here. displayCls() gives a Latin route name .rt-display
   (Fredoka at 21, title-display) and one with Hangul, Hanja or Kana in it
   .rt-title (Pretendard 700 at 21, title) — both 21, so a row of routes is
   one size whatever each is called. Only the card that makes a trip, which
   is not a route's name, stays at 17. */
.rc-t{font-size:var(--fs-body);font-weight:var(--fw-bold);line-height:1.3;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.rt-display.rc-t,.rt-title.rc-t{font-size:var(--fs-title)}
/* .rc-t sets --fw-bold after .rt-display in this file, so on the card row
   Fredoka was asked for at 700 — a weight the page never loads. The same
   trap the header and the .route card name explicitly, missed here in
   step 3 and caught by reading the computed weight, not the size. */
.rt-display.rc-t{font-weight:var(--fw-medium)}
.rc-k{font-size:var(--fs-micro);font-weight:var(--fw-bold);color:var(--accent);
  letter-spacing:.06em;margin-bottom:2px}
/* One line. A meta strip that wraps is not a strip. */
.rc-m{margin-top:2px;font-size:var(--fs-micro);color:var(--soft);
  font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The browse sheet on the home screen. Same shape language as .sheet — the
   radius, the handle, the shadow — and two deliberate differences, both
   forced by what is underneath it.

   It does NOT step the page back. .screen-body scales to .94 when the
   planner's sheet opens, which is right for a page of cards and fatal for
   a map: a transform on a container holding live Leaflet tiles resamples
   them. The map stays flat, sharp and live.

   And its scrim is lighter, because the map is not a backdrop to be dimmed
   out of the way — it is still the thing being browsed. */
/* The sheet runs to the screen's bottom edge and the nav floats over it:
   the nav is above it in z, so the sheet can never cover
   the one control that leaves it — a screen whose way out is covered has
   no way out, and this one cannot reach its way out. Its list pads its own
   bottom by --nav-top so the last card scrolls out from under the bar. */
.homesheet{position:absolute;left:0;right:0;bottom:0;background:var(--card);
  border-radius:var(--rad-2) var(--rad-2) 0 0;z-index:1001;transform:translateY(101%);
  transition:transform var(--dur-sheet) var(--ease-sheet);
  height:calc(72% + var(--nav-top));display:flex;flex-direction:column;box-shadow:var(--e-4);
  will-change:transform;touch-action:none}
.homesheet.is-dragging{transition:none}
.homesheet[data-open="true"]{transform:translateY(0)}
/* Discover, Saved and Me are PAGES, not sheets. Same element so nothing
   moves again, but square, from the top down, no handle — and they do not
   slide up. The nav switches them, so they switch the way tabs beside each
   other do: the incoming page slides in from the side it sits on
   (.page-in, below), nothing vertical. The sheet — handle, rounded top,
   floating over the map — is the map's route list and nothing else. */
.homesheet[data-full="true"]{top:0;height:auto;border-radius:0;box-shadow:none;
  transform:none;opacity:0;pointer-events:none;transition:opacity var(--dur-state) var(--ease-out)}
.homesheet[data-full="true"][data-open="true"]{transform:none;opacity:1;pointer-events:auto}
.homesheet[data-full="true"] .grabwrap{display:none}
/* Sideways, 24px, ease-out, no overshoot. --page-dx is set per switch from
   the tab order. Safe to transform: #homeScroll holds no Leaflet map. */
.home-scroll.page-in{animation:pageIn var(--dur-release) var(--ease-out)}
@keyframes pageIn{from{opacity:0;transform:translateX(var(--page-dx,24px))}to{opacity:1;transform:none}}
/* #homeScroll and #homeHd were MOVED into the sheet rather than rebuilt, so
   renderScrollTab() and everything it renders is untouched — that is what
   made 7a a move instead of a build. The scroll container keeps its own
   rule; it only needs the sheet's gutter now that it is not the page. */
.homesheet .home-scroll{padding-left:var(--gutter);padding-right:var(--gutter)}
.homesheet .home-hd{padding-top:var(--s-3)}
.hs-scrim{position:absolute;inset:0;background:var(--scrim);opacity:0;
  pointer-events:none;transition:opacity var(--dur-sheet) var(--ease-out);z-index:1000}
.hs-scrim[data-open="true"]{opacity:.55;pointer-events:auto}
.scrim{position:absolute;inset:0;background:var(--scrim);opacity:0;pointer-events:none;transition:opacity var(--dur-sheet) var(--ease-out);z-index:1000}
.scrim[data-open="true"]{opacity:1;pointer-events:auto}
.sheet{position:absolute;left:0;right:0;bottom:0;background:var(--card);border-radius:var(--rad-2) var(--rad-2) 0 0;z-index:1001;transform:translateY(101%);transition:transform var(--dur-sheet) var(--ease-sheet);max-height:88%;display:flex;flex-direction:column;box-shadow:var(--e-4);will-change:transform;touch-action:none}
/* While a finger is on it the sheet answers the finger, not a curve. */
.sheet.is-dragging{transition:none}
.sheet[data-open="true"]{transform:translateY(0)}
/* The handle is a 32px-tall grab area around a 5px mark — a 5px target is
   not one. */
.grabwrap{padding:var(--s-3) 0 var(--s-1);display:flex;justify-content:center;cursor:grab;flex:none}
.grabwrap:active{cursor:grabbing}
.grab{width:38px;height:5px;border-radius:var(--rad-pill);background:var(--line)}
/* The sheet's only dismissal used to be the sliver of scrim above it. */
.sheetx{position:absolute;right:var(--gutter);top:var(--s-3);z-index:2;background:var(--chrome);border-color:var(--chrome-line);box-shadow:var(--e-2)}
.sheet .hero{height:auto;margin:var(--stack) var(--gutter) 0;display:flex;align-items:center;color:var(--soft)}
.sheet .hero .cat{display:inline-flex;align-items:center;gap:var(--s-1);font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--soft);background:var(--bg);border:1px solid var(--hair);padding:var(--s-1) var(--s-2);border-radius:var(--rad-1)}
.sheet .sbody{overscroll-behavior:contain;padding:var(--card-pad) var(--gutter);overflow-y:auto}
.sheet .snm{font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--tr-title);line-height:var(--lh-tight)}
/* The Korean name, big enough to hold up to a taxi driver. */
.konm{display:flex;align-items:center;gap:var(--s-2);width:100%;margin-top:var(--stack);padding:var(--s-3) var(--card-pad);border-radius:var(--rad-2);background:var(--bg);border:1px solid var(--hair);font-size:var(--fs-title);font-weight:var(--fw-bold);color:var(--ink);text-align:left}
.konm .ic{font-size:var(--ic-1);color:var(--soft);flex:none;margin-left:auto}
.konm .kohint{font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--soft);flex:none}
.spotcard .kosm{margin-top:var(--s-1);font-size:var(--fs-body);font-weight:var(--fw-medium);color:var(--soft)}
.sheet .srt{font-size:var(--fs-meta);color:var(--soft);margin-top:var(--s-1)}
/* ---- the one place sheet (NEXT.md 5a; the Spot board) ----
   From a list or from a map pin, the same: the photo with its Save on it,
   the kicker (what, which stop, which route), the name and the Korean to
   show, one line of facts, the note, caution then tip, and Add to my trip
   (52 navy) with Directions (52 outline). Opened from a pin it adds the
   stepper and the route's row under the actions. */
.sheet.over-nav{padding-bottom:var(--nav-top)}            /* the tab bar floats over it */
.sheet .sp-photo{position:relative;margin-bottom:var(--stack)}
.sheet .sp-photo .sheet-photo{margin-bottom:0;border-radius:var(--rad-2);overflow:hidden}
.sheet .sp-photo .btn.spot-save{right:var(--s-2);top:var(--s-2);width:var(--tap);min-width:var(--tap);min-height:var(--tap);border-radius:var(--rad-round);background:var(--accent);color:var(--on-fill)}
.sheet .sp-photo .btn.spot-save[aria-pressed="true"]{color:var(--on-fill)}
.sheet .sp-head{position:relative;padding-right:var(--tap)}
.sheet .sp-photo + .sp-head{padding-right:0}
.sheet .sp-k{font-size:var(--fs-micro);font-weight:var(--fw-bold);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--soft)}
.sheet .sp-head .snm{margin-top:var(--s-1)}
.sheet .sp-facts{margin-top:var(--s-2);font-size:var(--fs-micro);line-height:var(--lh-text);color:var(--soft);font-variant-numeric:tabular-nums}
.sheet .sp-note{margin-top:var(--stack);font-size:var(--fs-meta);line-height:var(--lh-text);color:var(--ink)}
.smore .sp-build{margin-top:var(--stack)}
/* The account, at the end of My trip (NEXT.md 4j): a flat page card, its
   kicker muted — nothing in it is navy but the actions' own ghosts. */
.acct{margin-top:var(--section);background:var(--card);border:1px solid var(--hair);border-radius:var(--rad-2);padding:var(--card-pad)}
.acct-k{font-size:var(--fs-micro);font-weight:var(--fw-medium);letter-spacing:var(--tr-caps);text-transform:uppercase;color:var(--soft)}
.acct-t{margin:var(--s-2) 0 0;font-size:var(--fs-meta);line-height:var(--lh-text);color:var(--ink)}
.acct-row{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--stack)}
.acct-fine{margin-top:var(--stack);font-size:var(--fs-micro);line-height:var(--lh-small);color:var(--soft)}
.acct-fine a{color:inherit;text-decoration:underline}
.sheet .sp-note + .warn,.sheet .sp-note + .tip,.sheet .sp-facts + .warn,.sheet .sp-facts + .tip{margin-top:var(--stack)}
.cta .sp-dir{flex:none}
.cta .daypick{flex:1;display:flex;flex-wrap:wrap;gap:var(--s-2)}
.smore{padding:0 var(--gutter) var(--stack)}
.smore:empty{display:none}
.kv{display:flex;gap:var(--stack);padding:var(--s-3) 0;border-bottom:1px solid var(--hair2)}
.kv:last-child{border-bottom:none}
.kv .l{font-size:var(--fs-micro);font-weight:var(--fw-medium);color:var(--faint);width:64px;flex:none;text-transform:uppercase;letter-spacing:var(--tr-caps)}
.kv .v{font-size:var(--fs-body);line-height:var(--lh-snug)}
.cta{display:flex;gap:var(--s-2);padding:var(--stack) var(--gutter) calc(var(--s-4) + env(safe-area-inset-bottom));border-top:1px solid var(--hair)}
.cta .btn{flex:1}
