/* ═══════════════════════════════════════════════════════════════════════════
   quarter — Budapest.

   THE ONE IDEA: the photograph is the page, and the writing sits on paper
   underneath it. Every screen is one picture and then some prose — no cards
   inside cards, no chips over images, no gradient scrims doing the work that a
   crop should do.

   COLOUR — paper and ink, and nothing else. The photographs are the only
   colour in the app, which is why they have to be good and why the chrome
   gives them the whole width.

   TYPE — Kalice for anything that is a NAME (a district, a place, a year),
   Selecta for everything that is information. Two faces, one job each.
   ═══════════════════════════════════════════════════════════════════════════ */

@font-face{font-family:Selecta;src:url('fonts/Selecta-Regular.otf') format('opentype');font-weight:400;font-display:block}
@font-face{font-family:Selecta;src:url('fonts/Selecta-Medium.otf')  format('opentype');font-weight:500;font-display:block}
@font-face{font-family:Selecta;src:url('fonts/Selecta-Bold.otf')    format('opentype');font-weight:700;font-display:block}
@font-face{font-family:Kalice; src:url('fonts/Kalice-Regular.woff2') format('woff2');  font-weight:400;font-display:block}

:root{
  --paper:#FCFBFA;
  --shell:#EDE9E4;
  --ink:#2C2528;
  --ink-70:rgba(44,37,40,.70);
  --ink-45:rgba(44,37,40,.45);
  --ink-28:rgba(44,37,40,.28);
  --ink-12:rgba(44,37,40,.12);
  --ink-06:rgba(44,37,40,.06);

  /* the one warm accent, spent only on "this is shut" and on a saved mark */
  --flag:#A8442A;

  --glass-bg:rgba(252,250,249,.74);
  --glass-line:rgba(255,255,255,.72);
  --glass-blur:blur(26px) saturate(1.35);
  --glass-shadow:
    inset 0 1px 0 rgba(255,255,255,.60),
    0 1px 2px rgba(44,37,40,.06),
    0 14px 34px -12px rgba(44,37,40,.26);

  /* The display face, behind one variable so the desktop toggle is a single
     write. Kalice is the house serif; the sans alternative is Selecta, which
     the app is already loading, at a heavier weight and tighter tracking so it
     carries the same weight on the page that the serif does. */
  --face-display:Kalice,Georgia,serif;
  --face-w:400;
  --face-track:-.018em;

  --r-panel:16px; --r-card:22px; --r-pill:999px;
  --ease:cubic-bezier(.22,.9,.3,1);
  --ease-long:cubic-bezier(.32,.72,0,1);
  --pop:cubic-bezier(.34,1.42,.64,1);

  --safe-top:max(14px,env(safe-area-inset-top));
  --safe-bot:env(safe-area-inset-bottom);
  --pad:20px;
  --tabh:60px;            /* the pill itself */
  --tab-gap:14px;         /* how far it floats off the bottom */
  /* what any scroller has to clear so the last row is not under the glass */
  --tab-clear:calc(var(--tabh) + var(--tab-gap) * 2 + var(--safe-bot));
}

*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
[hidden]{display:none!important}

html,body{height:100%}
body{
  background:var(--shell); color:var(--ink);
  font:400 15px/1.45 Selecta,ui-sans-serif,system-ui,-apple-system,sans-serif;
  letter-spacing:-.008em; -webkit-font-smoothing:antialiased;
  overscroll-behavior:none;
  display:grid; place-items:center;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
img{display:block;max-width:100%}

/* ── the frame ───────────────────────────────────────────────────────────── */

.device{display:grid;place-items:center;width:100%;height:100%}
.phone{
  position:relative;
  width:402px; height:874px;             /* iPhone 17 Pro points */
  border-radius:54px; overflow:hidden;
  background:var(--paper);
  box-shadow:
    0 0 0 1px rgba(9,9,9,.10),
    0 1px 2px rgba(9,9,9,.06),
    0 44px 90px -24px rgba(9,9,9,.30);
}
body.bare .phone{
  width:100%;height:100%;border-radius:0;box-shadow:none;
}
.hint{
  position:fixed;left:0;right:0;bottom:16px;text-align:center;
  font-size:11.5px;color:var(--ink-28);letter-spacing:.02em;pointer-events:none;
  transition:opacity .4s var(--ease);
}
.hint kbd{font:inherit;border:1px solid var(--ink-12);border-radius:4px;padding:0 4px}
.hint.gone{opacity:0}

/* Below the frame's own size there IS no frame — full bleed, real insets. */
@media (max-width:480px),(max-height:900px){
  .phone{width:100%;height:100%;border-radius:0;box-shadow:none}
  .hint{display:none}
}

/* container queries, not media queries: the framed desktop view and a real
   phone then share ONE layout and the frame is a preview, not a second design */
.app{
  container-type:inline-size;
  position:relative; width:100%; height:100%;
  background:var(--paper); overflow:hidden;
}
body.bare .app{--safe-top:max(14px,env(safe-area-inset-top))}

/* ── the stack ───────────────────────────────────────────────────────────── */

.stage{position:absolute;inset:0}

.screen{
  position:absolute; inset:0;
  overflow-y:auto; overflow-x:hidden;
  -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
  background:var(--paper);
  padding-bottom:calc(var(--tab-clear) + 10px);
}
.screen::-webkit-scrollbar{width:0}

/* a pushed screen comes in from the right over the one below; the one below
   drifts a little and dims, so the stack has depth without a shadow */
.screen.pushing{animation:pushIn .42s var(--ease-long) both}
.screen.popping{animation:pushIn .34s var(--ease) reverse both}
@keyframes pushIn{from{opacity:0}to{opacity:1}}
.screen.under{transform:translateX(-14px);filter:brightness(.965);transition:transform .42s var(--ease-long),filter .42s var(--ease-long)}

/* a screen the morph is flying INTO holds its content back for a beat, so the
   photograph arrives first and the words land on it */
.screen.arriving .sheet{opacity:0;transform:translateY(10px)}
.screen .sheet{transition:opacity .34s var(--ease) .1s,transform .34s var(--ease) .1s}

/* ── chrome ──────────────────────────────────────────────────────────────── */

/* ── the home hero ─────────────────────────────────────────────────────────
   The photograph is the first thing, full-bleed to the TRUE top — under the
   status bar, under whatever chrome floats over the app — and the title sits
   ON it. It is deliberately not the whole viewport: at 66cqh the about
   paragraph's first line shows underneath, so the screen reads as the top of
   something rather than as a title card you have to dismiss.
   ────────────────────────────────────────────────────────────────────────── */
.hero-top{
  position:relative; z-index:1;
  touch-action:pan-y;                   /* horizontal is the slideshow's */
  height:66cqh; min-height:380px;
  margin-bottom:4px;
  background:var(--ink-12);
  overflow:hidden;
  isolation:isolate;                    /* keeps the scrim off the page below */
}
.hero-top .slides{position:absolute;inset:0}
.hero-top .sl{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover;
  -webkit-user-drag:none; user-select:none;
  opacity:0; transition:opacity 1.4s var(--ease);
}
.hero-top .sl.on{opacity:1}

/* Two stops, not one: a short wash at the top so a light sky cannot swallow
   the floating Wabi chrome, and a long one at the foot carrying the title.
   The foot stop has to be generous because the slides run from a night river
   to a white-tiled pool and the type is the same colour over both. */
.hero-top .scrim{
  position:absolute; inset:0; pointer-events:none;
  background:
    linear-gradient(rgba(18,15,16,.34), rgba(18,15,16,0) 22%),
    linear-gradient(rgba(18,15,16,0) 34%, rgba(18,15,16,.28) 58%, rgba(18,15,16,.78));
}
.hero-top .titling{
  position:absolute; left:var(--pad); right:var(--pad);
  bottom:calc(var(--pad) + 4px);
  color:#FCFBFA;
}
.hero-top h1{
  font:var(--face-w) 44px/.98 var(--face-display); letter-spacing:var(--face-track);
  text-shadow:0 1px 26px rgba(18,15,16,.34);
}
/* the subheadline carries weight, so it reads as a second voice rather than as
   a caption of the title */
.hero-top .sub{
  margin-top:11px; max-width:23em;
  font:500 15px/1.42 Selecta,sans-serif; letter-spacing:-.006em;
  color:rgba(252,251,250,.94);
  text-shadow:0 1px 18px rgba(18,15,16,.45);
  text-wrap:pretty;
}
.hero-top .meta{
  margin-top:7px; font-size:12px; letter-spacing:.01em;
  color:rgba(252,251,250,.72);
}

/* jump links — the three things people arrive wanting, straight off the hero */
.jump{
  display:flex; gap:7px; flex-wrap:wrap;
  padding:22px var(--pad) 20px;
}
.jump button{
  font:500 12.5px/1 Selecta,sans-serif; letter-spacing:-.004em;
  padding:0 13px; height:33px; border-radius:var(--r-pill);
  background:var(--ink-06); color:var(--ink-70);
  transition:background .16s var(--ease), color .16s var(--ease);
}
.jump button:active{background:var(--ink-12);color:var(--ink)}
.hero-top .dots{
  position:absolute; top:calc(var(--safe-top) + 12px); right:var(--pad);
  display:flex; gap:5px;
}
.hero-top .dots i{
  width:5px;height:5px;border-radius:var(--r-pill);
  background:rgba(252,251,250,.42);
  transition:background .5s var(--ease);
}
.hero-top .dots i.on{background:rgba(252,251,250,.95)}

@media (prefers-reduced-motion: reduce){
  .hero-top .sl{transition:none}
  .hero-top .dots{display:none}   /* nothing is advancing, so nothing to count */
}

/* the orienting paragraph, on paper, directly under the photograph */
.about{
  margin:0 var(--pad) 22px;
  font-size:14.5px; line-height:1.62; color:var(--ink-70);
  text-wrap:pretty;
}

.top{
  position:relative; z-index:6;
  padding:calc(var(--safe-top) + 26px) var(--pad) 14px;
  background:var(--paper);
}
.top h1{
  font:var(--face-w) 30px/1 var(--face-display); letter-spacing:var(--face-track);
}
.top .meta{margin-top:7px;font-size:12px;color:var(--ink-45);letter-spacing:.01em}

/* the back bar floats over a hero photograph, so it is glass and it is dark */
.back{
  position:absolute; z-index:8;
  top:calc(var(--safe-top) + 4px); left:14px;
  width:38px;height:38px;border-radius:var(--r-pill);
  display:grid;place-items:center;
  background:rgba(20,18,20,.34);
  backdrop-filter:blur(14px) saturate(1.3); -webkit-backdrop-filter:blur(14px) saturate(1.3);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18);
  color:#fff;
}
.back:active{transform:scale(.94)}
.back svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}

.keep{
  position:absolute; z-index:8;
  top:calc(var(--safe-top) + 4px); right:14px;
  width:38px;height:38px;border-radius:var(--r-pill);
  display:grid;place-items:center;
  background:rgba(20,18,20,.34);
  backdrop-filter:blur(14px) saturate(1.3); -webkit-backdrop-filter:blur(14px) saturate(1.3);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18);
  color:#fff;
}
.keep svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linejoin:round}
.keep[aria-pressed="true"] svg{fill:#fff}
.keep:active{transform:scale(.94)}

/* ── search ──────────────────────────────────────────────────────────────── */

.find{
  margin:2px var(--pad) 0; position:relative;
  display:flex;align-items:center;gap:9px;
  height:40px;padding:0 14px;border-radius:var(--r-pill);
  background:var(--ink-06);
}
.find svg{width:15px;height:15px;flex:none;fill:none;stroke:var(--ink-45);stroke-width:1.9;
  stroke-linecap:round}
.find input{
  flex:1;min-width:0;align-self:stretch;border:0;background:none;outline:0;
  font:400 14.5px/1 Selecta,sans-serif;color:var(--ink);letter-spacing:-.005em;
}
.find input::placeholder{color:var(--ink-45)}
.find .clr{width:22px;height:22px;border-radius:var(--r-pill);display:grid;place-items:center;
  background:var(--ink-12);color:var(--ink-70);font-size:13px;line-height:1}

/* ── sections ────────────────────────────────────────────────────────────── */

.sec{padding:40px var(--pad) 0;scroll-margin-top:8px}

/* A section heading is a HEADING, not an eyebrow. These were 10.5px caps at 45%
   ink for a while, which fixed the label/lede/title collision by demoting the
   label out of the hierarchy altogether — "threads" ended up quieter than the
   card captions underneath it. Kalice at 26px is the same voice as the card
   titles one size up, so the page reads title → section → card. */
.sec > h2,
.sheet .sec > h2{
  font:var(--face-w) 26px/1.1 var(--face-display); letter-spacing:var(--face-track);
  text-transform:none; color:var(--ink); margin:0 0 8px;
}
.sec > .lede{
  font-size:13px;line-height:1.5;color:var(--ink-70);
  margin-bottom:18px;max-width:30em;text-wrap:pretty;
}

/* ── the cover ───────────────────────────────────────────────────────────── */

/* The epigraph, with no photograph of its own any more — the hero above is the
   picture, and the cover shot used to be 52% of the first screen AND the same
   file as the Lipótváros card 700px below it. Left alone on paper between two
   rules, the sentence is the loudest thing on the page, which is what it was
   written to be. */
.cover{
  padding:8px var(--pad) 0; margin:0 0 6px;
}
.cover .say{
  margin:0 2px;
  padding:22px 0 24px;
  border-top:1px solid var(--ink-12);
  border-bottom:1px solid var(--ink-12);
  font:400 22px/1.3 var(--face-display); letter-spacing:-.014em;
  text-wrap:pretty;
}

/* ── cards ───────────────────────────────────────────────────────────────── */

.card{display:block;width:100%;text-align:left;position:relative}
.card .shot{
  position:relative; width:100%; overflow:hidden;
  border-radius:var(--r-card); background:var(--ink-12);
}
.card .shot img{width:100%;height:100%;object-fit:cover;display:block}
.card:active .shot{transform:scale(.985)}
.card .shot{transition:transform .16s var(--ease)}

/* the quarters — a wide card with the number set INTO the picture, because a
   district's number is how it is spoken about */
.quarters{display:grid;gap:22px}
.q-card .shot{aspect-ratio:16/10}
/* the train, drawn the way the city draws it — a filled circle per line.
   Monochrome, because the photographs are still the only colour here. */
.num{display:flex;gap:5px;align-items:center}
.q-card .num{position:absolute; left:14px; bottom:12px; z-index:2}
.num .bul{
  display:grid;place-items:center;width:27px;height:27px;border-radius:var(--r-pill);
  background:var(--paper);color:var(--ink);
  font:700 14px/1 Selecta,sans-serif;letter-spacing:0;font-style:normal;
  box-shadow:0 1px 6px rgba(0,0,0,.30);
}
.num .wor{
  display:grid;place-items:center;height:27px;padding:0 12px;border-radius:var(--r-pill);
  background:var(--paper);color:var(--ink);
  font:500 12.5px/1 Selecta,sans-serif;font-style:normal;letter-spacing:.01em;
  box-shadow:0 1px 6px rgba(0,0,0,.30);
}
/* on a detail sheet it sits on paper, so it wants ink on a tint, not white */
.num.inline{margin:9px 0 2px}
.num.inline .bul,.num.inline .wor{
  background:var(--ink);color:var(--paper);box-shadow:none;
}
.q-card .shot::after{
  content:''; position:absolute; inset:auto 0 0 0; height:52%;
  background:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.42));
}
.q-card .say{display:flex;align-items:baseline;gap:9px;margin:10px 2px 0}
.q-card .say b{font:400 20px/1.15 var(--face-display);letter-spacing:-.014em}
.q-card .say i{font-style:normal;font-size:12px;color:var(--ink-45);letter-spacing:0}

/* threads — two columns of portrait cards. This was a bleeding horizontal rail
   and the negative margin only holds at scrollLeft 0; snapped or nudged, the
   first card sat hard against the screen edge while every other element on the
   page began at --pad. A grid cannot lose its margin. Portrait against the
   full-width quarter cards above is also the rhythm change the rail was for. */
/* ⚠️ `1fr` is `minmax(auto, 1fr)`, so a track can REFUSE to shrink below its
   content's min-content width. The column whose caption happened to fit on one
   line grew wider than its neighbour, which made its photograph wider, which
   made it taller — two cards in one row with two different pictures sizes.
   `minmax(0, 1fr)` is what actually means "half each". */
.rail{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:20px 12px;align-items:start}
.t-card .shot{aspect-ratio:4/5}
.t-card .say{display:block;margin:10px 2px 0}
.t-card .say b{display:block;font:400 17px/1.2 var(--face-display);letter-spacing:-.01em}
.t-card .say i{font-style:normal;display:block;margin-top:3px;font-size:12px;color:var(--ink-45);
  line-height:1.35;text-wrap:pretty}

/* stay — a list, because the answer is a comparison */
.stays{display:grid;gap:18px}
.s-card{display:grid;grid-template-columns:96px 1fr;gap:13px;align-items:start;text-align:left;width:100%}
.s-card .shot{aspect-ratio:1;border-radius:var(--r-panel)}
.s-card b{font:400 16px/1.2 var(--face-display);display:block}
.s-card i{font-style:normal;display:block;margin-top:3px;font-size:12.5px;color:var(--ink-45)}
.s-card p{margin-top:6px;font-size:13px;line-height:1.45;color:var(--ink-70);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.s-card.no b::after{
  content:'avoid'; margin-left:7px; vertical-align:2px;
  font:500 9.5px/1 Selecta,sans-serif;letter-spacing:.09em;text-transform:uppercase;
  color:var(--flag); border:1px solid currentColor;border-radius:3px;padding:3px 4px;
}

/* ── place rows ──────────────────────────────────────────────────────────── */

.rows{display:grid}
.row{
  display:grid;grid-template-columns:1fr auto;gap:12px;align-items:baseline;
  width:100%;text-align:left;padding:13px 0;
  box-shadow:inset 0 -1px 0 var(--ink-12);
}
.rows .row:last-child{box-shadow:none}
.row b{font:400 16px/1.25 var(--face-display);letter-spacing:-.005em}
.row .k{font-size:11.5px;color:var(--ink-45);letter-spacing:.02em;white-space:nowrap}
.row p{grid-column:1/-1;margin-top:4px;font-size:13px;line-height:1.46;color:var(--ink-70);
  text-wrap:pretty;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.row.shut b{color:var(--ink-45)}
.row.shut b::after{
  content:'closed'; margin-left:8px;vertical-align:2px;
  font:500 9.5px/1 Selecta,sans-serif;letter-spacing:.09em;text-transform:uppercase;
  color:var(--flag);border:1px solid currentColor;border-radius:3px;padding:3px 4px;
}
.row .n{
  font:400 12px/1 Selecta,sans-serif;color:var(--ink-28);
  font-variant-numeric:tabular-nums;
}

/* ── a detail screen ─────────────────────────────────────────────────────── */

.hero{
  position:relative; width:100%; aspect-ratio:4/5; overflow:hidden;
  background:var(--ink-12);
  touch-action:pan-y;
}
.hero img{width:100%;height:100%;object-fit:cover}

/* a detail hero that is a slideshow: same mechanism as the home hero, and the
   dots go top-LEFT-of-right, clear of the back button at the other corner */
.hero .slides{position:absolute;inset:0}
.hero .sl{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  -webkit-user-drag:none; user-select:none;
  opacity:0; transition:opacity 1.4s var(--ease);
}
.hero .sl.on{opacity:1}
.hero .dots{
  position:absolute; z-index:3;
  top:calc(var(--safe-top) + 17px); right:var(--pad);
  display:flex; gap:5px;
}
.hero .dots i{
  width:5px;height:5px;border-radius:var(--r-pill);
  background:rgba(252,251,250,.42);
  box-shadow:0 0 6px rgba(18,15,16,.34);
  transition:background .5s var(--ease);
}
.hero .dots i.on{background:rgba(252,251,250,.95)}
@media (prefers-reduced-motion: reduce){
  .hero .sl{transition:none}
  .hero .dots{display:none}
}
.hero::after{
  content:''; position:absolute; inset:0;
  background:linear-gradient(rgba(0,0,0,.30) 0 14%,rgba(0,0,0,0) 42%);
  pointer-events:none;
}

/* the sheet is paper pulled up over the bottom of the photograph */
.sheet{
  position:relative; z-index:3; margin-top:-34px;
  border-radius:26px 26px 0 0; background:var(--paper);
  padding:22px var(--pad) 0; min-height:52cqh;
}
/* the sheet already pays the page margin — a .sec inside it must not pay it
   again, or the heading indents 20px further than the name above it */
.sheet .sec{padding-left:0;padding-right:0}
.sheet .credit{margin-left:0;margin-right:0;padding-left:0;padding-right:0}
.sheet .kind{font-size:11.5px;letter-spacing:.10em;text-transform:uppercase;color:var(--ink-45)}
.sheet h2{margin-top:6px;font:400 30px/1.08 var(--face-display);letter-spacing:-.018em;text-wrap:balance}
.sheet h2 .sub{display:block;margin-top:6px;font-size:14px;color:var(--ink-45);letter-spacing:0}
.sheet .why{margin-top:15px;font-size:15px;line-height:1.55;text-wrap:pretty}
.sheet .why + .why{margin-top:11px}

.note{
  margin-top:16px;padding:13px 14px;border-radius:var(--r-panel);
  background:var(--ink-06);font-size:13.2px;line-height:1.5;color:var(--ink-70);text-wrap:pretty;
}
.note.warn{background:rgba(168,68,42,.07);color:#7C3520}
.note b{font-weight:500;color:var(--ink)}
.note.warn b{color:#7C3520}

.facts{margin-top:16px;display:grid;gap:0}
.fact{display:grid;grid-template-columns:76px 1fr;gap:12px;padding:9px 0;
  box-shadow:inset 0 -1px 0 var(--ink-12);font-size:13px}
.facts .fact:last-child{box-shadow:none}
.fact dt{color:var(--ink-45)}
.fact dd{color:var(--ink)}

.acts{display:flex;gap:8px;margin-top:18px;flex-wrap:wrap}
.btn{
  height:38px;padding:0 15px;border-radius:var(--r-pill);
  display:inline-flex;align-items:center;gap:7px;
  background:var(--ink-06); font-size:13.5px;
}
.btn:active{transform:scale(.97)}
.btn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linejoin:round}
.btn.on{background:var(--ink);color:var(--paper)}
.btn.on svg{fill:currentColor}

.credit{
  margin:26px 0 0;padding:14px var(--pad) 20px;
  border-top:1px solid var(--ink-12);
  font-size:11.5px;line-height:1.5;color:var(--ink-45);
}
.credit a{color:inherit}

/* ── know ────────────────────────────────────────────────────────────────── */

/* a segmented control, and the options FLEX to fill rather than scroll — if
   they do not fit, there are too many options */
.seg{
  display:flex; gap:3px; margin:14px var(--pad) 0;
  padding:3px; border-radius:var(--r-pill); background:var(--ink-06);
}
.seg button{
  flex:1; height:34px; border-radius:var(--r-pill);
  font:500 13px/1 Selecta,sans-serif; letter-spacing:-.004em;
  color:var(--ink-45);
  transition:background .18s var(--ease), color .18s var(--ease);
}
.seg button[aria-selected="true"]{
  background:var(--paper); color:var(--ink);
  box-shadow:0 1px 2px rgba(44,37,40,.10), 0 0 0 .5px rgba(44,37,40,.05);
}

/* ── language ──────────────────────────────────────────────────────────────
   Two tables that are not <table>s, because both are two-or-three columns of
   very short strings and a real table cannot keep a column steady when one
   cell wraps. The grids below fix the columns instead, so every row's
   pronunciation starts at the same x down the whole list. */
.sounds{display:grid;gap:0}
.snd{
  display:grid; grid-template-columns:58px 116px minmax(0, 1fr);
  gap:10px; align-items:baseline;
  padding:11px 0; border-top:1px solid var(--ink-12);
}
.snd:last-child{border-bottom:1px solid var(--ink-12)}
.snd b{font:400 16px/1.2 var(--face-display);letter-spacing:-.01em}
.snd i{font-style:normal;font-size:12.5px;color:var(--ink-70)}
.snd span{font-size:12.5px;color:var(--ink-45);text-wrap:pretty}

.words{display:grid;gap:0}
.wd{
  display:grid; grid-template-columns:minmax(0, 1fr);
  gap:2px; padding:13px 0; border-top:1px solid var(--ink-12);
}
.wd:last-child{border-bottom:1px solid var(--ink-12)}
.wd b{
  font:400 18px/1.25 var(--face-display); letter-spacing:-.012em;
  text-wrap:pretty;
}
.wd i{font-style:normal;font-size:12.5px;color:var(--ink-45);letter-spacing:.01em}
.wd p{margin-top:3px;font-size:13.5px;line-height:1.45;color:var(--ink-70);text-wrap:pretty}

.body{font-size:14.5px;line-height:1.6;color:var(--ink-70);text-wrap:pretty}


.know-g{display:grid;gap:0}
.kn{padding:15px 0;box-shadow:inset 0 -1px 0 var(--ink-12)}
.kn:last-child{box-shadow:none}
.kn h3{font:400 17px/1.2 var(--face-display);margin-bottom:6px}
.kn p{font-size:13.6px;line-height:1.55;color:var(--ink-70);text-wrap:pretty}

/* history — the year is the spine */
.hist{display:grid;gap:0}
.hx{display:grid;grid-template-columns:58px 1fr;gap:14px;padding:16px 0;
  box-shadow:inset 0 -1px 0 var(--ink-12)}
.hist .hx:last-child{box-shadow:none}
.hx .yr{font:400 14px/1.3 var(--face-display);color:var(--ink-45);font-variant-numeric:tabular-nums;
  padding-top:1px}
.hx h3{font:400 17.5px/1.2 var(--face-display);margin-bottom:6px}
.hx p{font-size:13.6px;line-height:1.55;color:var(--ink-70);text-wrap:pretty}
.hx .shot{margin-top:11px;border-radius:var(--r-panel);overflow:hidden;aspect-ratio:16/10;
  background:var(--ink-12)}
.hx .shot img{width:100%;height:100%;object-fit:cover}

/* ── saved ───────────────────────────────────────────────────────────────── */

/* Most people will open this with nothing in it, so it is a designed screen
   rather than a line of text: the guide's own photographs, faded to the paper,
   as the shape the list will take. */
.empty{padding:0 var(--pad)}
.empty .ghost{
  display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:6px;
  opacity:.20; filter:grayscale(.5); pointer-events:none;
  -webkit-mask-image:linear-gradient(#000 0 34%,rgba(0,0,0,0) 96%);
          mask-image:linear-gradient(#000 0 34%,rgba(0,0,0,0) 96%);
}
.empty .ghost div{aspect-ratio:4/5;border-radius:var(--r-panel);overflow:hidden;background:var(--ink-12)}
.empty .ghost img{width:100%;height:100%;object-fit:cover}
.empty .say{margin-top:-186px;position:relative;z-index:2;font:400 19px/1.35 var(--face-display);
  max-width:19em;text-wrap:pretty}
.empty .sub{position:relative;z-index:2;margin-top:9px;font-size:13.4px;line-height:1.5;
  color:var(--ink-70);max-width:26em}

/* ── map ─────────────────────────────────────────────────────────────────── */

.mapwrap{position:absolute;inset:0}
#map{position:absolute;inset:0}
/* The tint is now mostly done in the style itself (see map.js) — this is only
   the last few per cent of warmth, because a filter strong enough to make the
   map look like paper is also strong enough to erase the river.
   The markers are DOM, not canvas, so they stay untinted either way. */
#map .mapboxgl-canvas{filter:sepia(.10) saturate(.94) contrast(1.05)}
.mapboxgl-ctrl-logo{opacity:.5}
.mapboxgl-ctrl-attrib{font:11px/1.3 Selecta,sans-serif}
.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-bottom-left{
  margin-bottom:calc(var(--tab-clear) - 14px)}

/* ⚠️ THE MARKER ELEMENT CARRIES NO TRANSITION. Mapbox positions a Marker by
   writing `transform` on it on every frame of a pan, so a transition on
   `transform` here animates each of those writes and the pins visibly TRAIL
   the map — which is exactly what it looked like. The dot inside does the
   scaling; the element Mapbox drives does nothing but sit where it is put. */
.pin{
  position:relative; cursor:pointer;
  width:15px; height:15px;
  display:flex; align-items:center; justify-content:center;
}
.pin .dot{
  width:15px;height:15px;border-radius:var(--r-pill);flex:none;
  background:var(--ink); border:2.5px solid var(--paper);
  box-shadow:0 1px 5px rgba(0,0,0,.32);
  transition:transform .18s var(--pop);
}
.pin:hover .dot{transform:scale(1.28)}
.pin.shut .dot{background:var(--flag)}
.pin.kept .dot{background:var(--paper);border-color:var(--ink);
  box-shadow:0 0 0 2.5px var(--ink) inset,0 1px 5px rgba(0,0,0,.32)}
.pin.shut.kept .dot{background:var(--flag)}  /* closed is the fact, kept the note */

/* the name rides beside the dot. Forty-nine of these at city zoom is soup, so
   they arrive at the zoom where you have picked a district and the names start
   being the thing you are reading (see NAME_FROM in map.js). */
.pin .lab{
  position:absolute; left:19px; top:50%; transform:translateY(-50%);
  font:500 11px/1.1 Selecta,sans-serif; letter-spacing:-.004em;
  white-space:nowrap; color:#241E20;
  text-shadow:
     0 0 3px var(--paper), 0 0 3px var(--paper),
     1px 1px 0 rgba(252,251,250,.95), -1px 1px 0 rgba(252,251,250,.95),
     1px -1px 0 rgba(252,251,250,.95), -1px -1px 0 rgba(252,251,250,.95);
  opacity:0; pointer-events:none;
  transition:opacity .2s var(--ease);
}
.show-names .pin .lab{opacity:1}
.pin.shut .lab{color:var(--flag)}
.pin.big .lab, .pin:hover .lab{opacity:1}

.pin.big .dot{transform:scale(1.5)}

.mapbar{
  position:absolute;z-index:5;left:0;right:0;
  top:calc(var(--safe-top) + 4px);
  display:flex;gap:7px;overflow-x:auto;padding:0 14px;scrollbar-width:none;
}
.mapbar::-webkit-scrollbar{height:0}
.chip{
  flex:none;height:32px;padding:0 13px;border-radius:var(--r-pill);
  font-size:12.5px;white-space:nowrap;
  background:var(--glass-bg);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-line); box-shadow:var(--glass-shadow);
}
.chip[aria-pressed="true"]{background:var(--ink);color:var(--paper);border-color:transparent}

/* the little sheet that comes up when a pin is tapped */
.peek{
  position:absolute;z-index:6;left:10px;right:10px;
  bottom:calc(var(--tab-clear) + 6px);
  display:grid;grid-template-columns:64px 1fr auto;gap:12px;align-items:center;
  padding:10px;border-radius:20px;text-align:left;
  background:var(--glass-bg);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-line); box-shadow:var(--glass-shadow);
  animation:peekUp .3s var(--pop) both;
}
@keyframes peekUp{from{transform:translateY(14px);opacity:0}to{transform:none;opacity:1}}
.peek .shot{width:64px;height:64px;border-radius:13px;overflow:hidden;background:var(--ink-12)}
.peek .shot img{width:100%;height:100%;object-fit:cover}
.peek b{font:400 16px/1.2 var(--face-display);display:block}
.peek i{font-style:normal;display:block;margin-top:3px;font-size:12px;color:var(--ink-45)}
.peek .go{width:30px;height:30px;border-radius:var(--r-pill);display:grid;place-items:center;
  background:var(--ink-06);margin-right:4px}
.peek .go svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}

.mapmsg{
  position:absolute;inset:0;display:grid;place-content:center;gap:10px;
  padding:0 34px;text-align:center;background:var(--shell);
}
.mapmsg h3{font:400 20px/1.25 var(--face-display)}
.mapmsg p{font-size:13.4px;line-height:1.55;color:var(--ink-70)}
.mapmsg code{font:12px ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--ink-06);
  padding:2px 5px;border-radius:5px}

/* ── the morph ───────────────────────────────────────────────────────────── */

/* ONE node. It is given the source picture's rect, then the destination's, and
   a single transition carries it. No clone of the card, no FLIP library — the
   thing that moves is a real element with a real photograph in it. */
.morph{
  position:absolute; z-index:20; overflow:hidden; pointer-events:none;
  background:var(--ink-12); display:none;
}
.morph img{width:100%;height:100%;object-fit:cover}
.morph.go{
  transition:top .46s var(--ease-long),left .46s var(--ease-long),
             width .46s var(--ease-long),height .46s var(--ease-long),
             border-radius .46s var(--ease-long);
}

/* ── tabs ────────────────────────────────────────────────────────────────── */

/* A floating glass pill, not a bar welded to the bottom edge. The content runs
   underneath it and is read THROUGH it, which is the whole point of the
   surface — a bar with an opaque edge is just a footer with a blur on it. */
.tabs{
  position:absolute; z-index:10;
  left:var(--tab-gap); right:var(--tab-gap);
  bottom:calc(var(--tab-gap) + var(--safe-bot));
  height:var(--tabh);
  display:grid; grid-template-columns:repeat(4,1fr);
  border-radius:var(--r-pill);
  background:var(--glass-bg);
  backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-line);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.62),
    0 1px 2px rgba(44,37,40,.06),
    0 10px 30px -8px rgba(44,37,40,.24),
    0 30px 60px -22px rgba(44,37,40,.20);
}
.tab{
  display:grid;place-items:center;gap:3px;align-content:center;
  color:var(--ink-28);position:relative;border-radius:var(--r-pill);
  transition:color .18s var(--ease),transform .14s var(--ease);
}
.tab svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linejoin:round;stroke-linecap:round}
.tab span{font-size:10px;letter-spacing:.02em}
.tab[aria-current="true"]{color:var(--ink)}
.tab:active{transform:scale(.93)}
.tab .pip{
  position:absolute;top:11px;left:50%;margin-left:8px;
  width:5px;height:5px;border-radius:var(--r-pill);background:var(--flag);
}

/* ── the way in ──────────────────────────────────────────────────────────── */

/* No title card. The cover photograph is already there and the writing lands
   on it — the app is running when you arrive. */
.in-1{animation:up .5s var(--ease) both}
.in-2{animation:up .5s var(--ease) .07s both}
.in-3{animation:up .5s var(--ease) .13s both}
@keyframes up{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

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


/* ── the display face, and the switch for it ───────────────────────────────
   A real open question on this design: the serif gives the guide its editorial
   voice, the sans makes it read as an app. The only way to decide is to look
   at both, so the switch lives on the desktop rail — outside the phone,
   because it is a design control and not part of the product.
   ────────────────────────────────────────────────────────────────────────── */
html[data-face="sans"]{
  --face-display:Selecta,ui-sans-serif,system-ui,sans-serif;
  --face-w:500;
  --face-track:-.022em;
}

.rail{
  position:fixed; right:calc(50% - 201px - 40px - 150px); top:50%;
  transform:translateY(-50%);
  width:150px; display:grid; gap:9px;
  font-size:12px; color:var(--ink-45);
}
.rail-lab{letter-spacing:.02em}
.seg-rail{margin:0}
.seg-rail button{height:30px;font-size:12px}

/* below the frame's own size there is no rail, because there is no outside */
@media (max-width:900px),(max-height:900px){ .rail{display:none} }
