/* CityCrumbs — product tier.
 *
 * This is the cream/ink interior half of the two-tier rule in site/CLAUDE.md.
 * site.css is the other half: the kraft/rust campaign exterior, for the
 * marketing pages. The boundary used to be a comment; it is now a file.
 *
 * Everything a person made — profiles, lists, crumbs — is rendered with this
 * sheet and should look like the app, the way Instagram on the web is the same
 * column in a browser. Marketing pages keep site.css.
 *
 * INVARIANT: no --c-* token and no import of campaign.css may ever appear
 * below. If one does, the tier boundary has been crossed. Class names are
 * deliberately disjoint from site.css (.col not .wrap, .hd not header.site) so
 * the two sheets could load together and collide over nothing.
 *
 * Served from /app.css by scripts/build-site.mjs, which rewrites the ../tokens
 * paths. functions/src/render.ts links it.
 */
@import "/tokens/fonts.css";
@import "/tokens/colors.css";
@import "/tokens/typography.css";
@import "/tokens/spacing.css";

:root { --cta-h: 66px; }

*{box-sizing:border-box}
html,body{margin:0}
body{background:var(--paper);color:var(--ink);
     font:16px/1.5 var(--font-sans);-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
img{display:block}
:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:4px}

/* Only pages that render the sticky bar reserve room for it — the 404 does
   not, which is why the reserve hangs off .hascta and not off body. */
body.hascta{padding-bottom:calc(var(--cta-h) + env(safe-area-inset-bottom) + 20px)}

/* ── frame ─────────────────────────────────────────────────────────────
   512px holds a 480px card. The card is a design tuned at the app's 343px
   (375 − 16×2); 480 is about as far as it stretches before 16px padding and
   16px body text start looking under-scaled inside it. */
.col{max-width:512px;margin:0 auto;
     padding-left:max(16px,env(safe-area-inset-left));
     padding-right:max(16px,env(safe-area-inset-right))}

/* ── header ────────────────────────────────────────────────────────────
   Sticky, like the app's, which sits outside the scroll view. The hairline is
   deliberate: without it, cards scrolling under a same-colour bar read as
   breakage. */
.hd{position:sticky;top:0;z-index:50;background:var(--paper);
    border-bottom:1px solid var(--line)}
.hd .in{max-width:512px;margin:0 auto;display:flex;align-items:center;
        padding:9px max(16px,env(safe-area-inset-left))}
.hd img{height:30px;width:auto}

/* ── profile identity block ────────────────────────────────────────────
   Sits on --paper with no panel, unlike the list hero. That difference exists
   in the app and is worth keeping. */
.id{padding:18px 0 16px}
.idrow{display:flex;align-items:center;gap:14px}
.av62{width:62px;height:62px;border-radius:999px;object-fit:cover;flex:none;
      border:1px solid var(--line2);background:var(--accent-tint)}
.id h1{margin:0;font:400 24px/27px var(--font-serif);color:var(--ink);
       overflow-wrap:anywhere}
.id .hl{margin-top:4px;font:10px/1 var(--font-mono);letter-spacing:.5px;
        text-transform:uppercase;color:var(--ink3)}
/* No left rule. The app's bio has none; the old web version invented a rust
   one, which was the campaign tier leaking in. */
.id .bio{margin:13px 0 0;font:italic 400 15.5px/22px var(--font-serif);color:var(--ink2)}

/* Two cells, not the app's four — followers and following are never published
   (see the doctrine at the top of functions/src/data.ts). They hug left rather
   than flex:1, which at two cells would strand the second in dead space. */
.stats{display:flex;gap:22px;margin-top:15px;padding-top:14px;
       border-top:1px solid var(--line)}
.stats .n{display:block;font:400 21px/1.1 var(--font-serif);color:var(--ink)}
.stats .l{display:block;margin-top:1px;font:8.5px/1 var(--font-mono);
          letter-spacing:.5px;text-transform:uppercase;color:var(--ink3)}

/* ── list card grid (the app's ListCard) ───────────────────────────────
   aspect-ratio 165/88 reproduces the app's 88px cover at its native cell
   width, (375−32−12)/2, and scales with the column. */
.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;
      padding-top:14px;padding-bottom:24px}
.lc{background:var(--paper2);border:1px solid var(--line);border-radius:16px;
    overflow:hidden;display:block}
.lc .cov{aspect-ratio:165/88;display:grid;place-items:center;
         background:repeating-linear-gradient(135deg,var(--sand) 0 10px,var(--sand2) 10px 20px)}
.lc .cov img{width:100%;height:100%;object-fit:cover}
.lc .cov svg{width:22px;height:22px;color:var(--ink3);opacity:.55}
.lc .bd{padding:11px 12px 13px}
.lc h2{margin:0;font:600 17px/19px var(--font-serif);color:var(--ink);
       display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.lc .mt{display:flex;align-items:center;gap:6px;margin-top:6px;
        font:9px/1 var(--font-mono);letter-spacing:.4px;text-transform:uppercase;
        color:var(--ink3)}
.lc .mt i{width:3px;height:3px;border-radius:50%;background:var(--ink3);flex:none}

/* ── list hero ─────────────────────────────────────────────────────────
   Full-bleed panel with a .col-width inner, matching the app where the panel
   spans the screen. The app has no border on it; at 480px inside a 1440px
   window, paper2 on paper is a 2% delta that reads as nothing, so a hairline
   gives the panel an edge. */
.hero{background:var(--paper2);border-bottom:1px solid var(--line)}
.hero .in{max-width:512px;margin:0 auto;
          padding:20px max(16px,env(safe-area-inset-left))}
.hero .owner{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.av40{width:40px;height:40px;border-radius:999px;object-fit:cover;flex:none;
      background:var(--accent-tint);border:1px solid var(--line)}
.hero .nm{display:block;font:600 16px/1.2 var(--font-sans);color:var(--ink)}
.hero .sub{display:block;margin-top:2px;font:14px/1.3 var(--font-sans);color:var(--ink3)}
/* sans, not serif — this is the app's listTitle, the one place the product
   sets a title in Hanken Grotesk. */
.hero h1{margin:0 0 8px;font:600 28px/1.15 var(--font-sans);color:var(--ink);
         text-wrap:balance}
.hero .desc{margin:0;font:16px/22px var(--font-sans);color:var(--ink3)}
.hero .facts{margin-top:14px;font:10px/1.6 var(--font-mono);letter-spacing:.5px;
             text-transform:uppercase;color:var(--ink3)}

/* ── list map hero ─────────────────────────────────────────────────────
   The app's list screen opens with a 200px map of everywhere on the list. Same
   16/9 box as the place page's .pmap, and the image is absolutely positioned
   for the same reason — a percentage height against an aspect-ratio parent is
   not definite, so it falls back to its intrinsic size and blows the ratio. */
.lmap{position:relative;max-width:512px;margin:0 auto;aspect-ratio:16/9;
      overflow:hidden;background:var(--sand);border-bottom:1px solid var(--line)}
.lmap img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.lmap figcaption{position:absolute;left:12px;bottom:12px;
                 background:var(--paper2);border:1px solid var(--line);
                 border-radius:999px;padding:5px 11px;
                 font:600 11px/1 var(--font-sans);color:var(--ink)}

/* ── the crumb card ───────────────────────────────────────────────────
   The app's list card, stacked: author (only when it is not the list owner),
   photo, tinted location strip, note. */
.body-pad{padding-top:8px;padding-bottom:24px}
.cr{background:var(--paper2);border:1px solid var(--line);border-radius:16px;
    margin:0 0 16px;overflow:hidden}
.cr .au{display:flex;align-items:center;gap:12px;padding:16px}
.cr .au .nm{font:600 16px/1.2 var(--font-sans);color:var(--ink)}
/* aspect-ratio, not a fixed 300px: the app is height 300 at 343 wide, so a
   fixed height at a 480 column would be a different crop. */
.cr .ph{aspect-ratio:343/300;
        background:repeating-linear-gradient(135deg,var(--sand) 0 10px,var(--sand2) 10px 20px)}
.cr .ph img{width:100%;height:100%;object-fit:cover}
/* The strip IS the place-name heading. An app-identical card has no separate
   title, and a 40-item page with no headings is a real SEO regression — this
   gets both without inventing an element the app does not have. */
.cr .loc{display:flex;align-items:center;gap:8px;margin:0;
         background:var(--accent-tint);padding:10px 12px;
         border-bottom:1px solid var(--line);
         font:600 14px/1.35 var(--font-sans);color:var(--accent)}
.cr .loc svg{flex:none;width:14px;height:14px}
/* The whole strip is the tap target when the crumb has a place page. Inheriting
   the flex row rather than nesting another keeps a linked and an unlinked card
   pixel-identical apart from the chevron. */
.cr .loc a{display:flex;align-items:center;gap:8px;flex:1;min-width:0;color:inherit}
.cr .loc .go{margin-left:auto;flex:none;color:var(--ink3);font-size:16px;line-height:1}
.cr .loc a:hover .go{color:var(--ink)}
.cr.nocap .loc{border-bottom:0}
/* The tint is the app's strip that sits UNDER a photo. With no photo above it
   there is nothing for it to divide, and a full-bleed tinted block reads as a
   pill — four in a row look like a nav, not like crumbs. So a text-only crumb
   gets its place name as a plain heading on the card instead. */
.cr.nopic .loc{background:none;border-bottom:0;padding:16px 16px 0}
.cr.nopic .q{padding-top:9px}
/* The 0 above is the note's job — .q supplies the bottom padding. A crumb with
   no note has no .q, so nothing closes the card and the place name sits hard on
   the border. 12 of 38 crumbs on public lists have no note, so this is the
   common case, not an edge one. */
.cr.nopic.nocap .loc{padding:16px}
/* Body copy — see .interface-design/system.md, "User notes". Not clamped:
   crawlers do not tap "more", and truncated content is what makes a page thin. */
.cr .q{margin:0;padding:16px;font:400 16px/24px var(--font-sans);color:var(--ink)}

.empty{font:10px/1.6 var(--font-mono);letter-spacing:.5px;text-transform:uppercase;
       color:var(--ink3);padding:36px 0}

/* An unclaimed gift profile — a list built for someone who has not taken the
   page over yet. Deliberately quiet rather than a warning colour: it is an
   explanation for the person it was made for, not an error. Sits between the
   identity block and the list grid so it is read before the lists are, which
   is the whole point — nobody should mistake these for the subject's own
   words. See unclaimedNote() in functions/src/render.ts. */
.unclaimed{margin:0 0 18px;padding:12px 14px;border:1px solid var(--line);
           border-radius:10px;background:var(--bg2,transparent);
           font:400 13px/1.5 var(--font-sans);color:var(--ink2)}

/* ── place page ────────────────────────────────────────────────────────
   /place/<city>/<name-street>. The map is the square share-card image cropped
   to a band: the pin is dead centre for a single place, so a centre crop keeps
   it — which is NOT true of a list, whose pins spread across the whole frame. */
/* Column-width, not full-bleed. The app's map spans the phone screen, and the
   web equivalent of that is the width of the column the rest of the page lives
   in — a 1320px map above a 512px column is a different page, not the same one.

   The image is absolutely positioned rather than height:100%. A percentage
   height against an aspect-ratio parent is not definite, so the image falls
   back to its intrinsic 1200px and drags the box to 1200 tall, ignoring the
   ratio entirely. */
.pmap{position:relative;max-width:512px;margin:0 auto;aspect-ratio:16/9;
      overflow:hidden;background:var(--sand);border-bottom:1px solid var(--line)}
.pmap img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}

.ptitle{font:500 30px/1.15 var(--font-serif);letter-spacing:-.01em;
        color:var(--ink);margin:20px 0 0}
.pcity{font:400 15px/1.4 var(--font-sans);color:var(--ink2);margin:6px 0 0}

.sec{font:10px/1 var(--font-mono);letter-spacing:.11em;text-transform:uppercase;
     color:var(--ink3);margin:26px 0 10px}

/* The address is the only outbound link on the page and the reason it exists,
   so it is a full-width target rather than an inline link in a sentence. */
.addr{display:flex;align-items:center;gap:10px;padding:14px;
      background:var(--paper2);border:1px solid var(--line);border-radius:12px;
      font:400 15px/1.45 var(--font-sans);color:var(--ink)}
.addr svg{flex:none;width:16px;height:16px;color:var(--ink3)}
.addr .ext{margin-left:auto;flex:none;font:10px/1 var(--font-mono);
           letter-spacing:.08em;text-transform:uppercase;color:var(--ink3)}
.addr:hover{border-color:var(--line2)}

.pnote{background:var(--paper2);border:1px solid var(--line);border-radius:16px;
       overflow:hidden;margin:0 0 12px}
.pnote .who{display:flex;align-items:center;gap:9px;padding:14px 14px 0}
.pnote .who b{font:600 13px/1 var(--font-sans);color:var(--ink)}
.av24{width:24px;height:24px;border-radius:50%;flex:none;object-fit:cover;
      background:var(--sand);border:1px solid var(--line)}
.pnote .pph{width:100%;aspect-ratio:343/300;object-fit:cover;margin-top:14px;
            display:block}
.pnote p{margin:0;padding:14px;font:400 16px/24px var(--font-sans);color:var(--ink)}
.pnote .who + p{padding-top:10px}

.prow{display:flex;align-items:center;gap:12px;padding:12px 14px;
      background:var(--paper2);border:1px solid var(--line);border-radius:12px;
      margin:0 0 10px}
.prow .sq{width:38px;height:38px;border-radius:9px;flex:none;
          background:var(--sand);border:1px solid var(--line)}
.prow .tx{min-width:0}
.prow b{display:block;font:600 14px/1.3 var(--font-sans);color:var(--ink);
        overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prow small{font:400 12px/1.3 var(--font-sans);color:var(--ink3)}
.prow .chev{margin-left:auto;flex:none;color:var(--ink3);font-size:17px;line-height:1}
.prow:hover{border-color:var(--line2)}

/* ── embed snippet ─────────────────────────────────────────────────── */
.embedder{margin-top:8px;border-top:1px solid var(--line);padding-top:20px}
.embedder summary{font:10px/1 var(--font-mono);letter-spacing:.5px;
                  text-transform:uppercase;color:var(--ink3);cursor:pointer}
.embedder p{font:14px/1.5 var(--font-sans);color:var(--ink2);margin:12px 0 8px}
.embedder textarea{width:100%;font:11px/1.5 var(--font-mono);color:var(--ink2);
                   background:var(--paper2);border:1px solid var(--line);
                   border-radius:10px;padding:11px;resize:vertical}

/* ── sticky CTA ───────────────────────────────────────────────────────
   Height is deterministic — 11 + 44 + 11 = 66 — because both text lines are
   single-line with ellipsis, so the bar can never wrap and --cta-h can never
   be wrong. The button is the app's primary pill: accent fill, accent-ink
   label, chip radius. */
.cta{position:fixed;left:0;right:0;bottom:0;z-index:60;
     background:var(--paper2);border-top:1px solid var(--line)}
.cta .in{max-width:512px;margin:0 auto;display:flex;align-items:center;gap:14px;
         padding:11px max(16px,env(safe-area-inset-left));
         padding-bottom:max(11px,env(safe-area-inset-bottom))}
.cta .t{margin:0;min-width:0;font:14px/1.3 var(--font-sans);color:var(--ink2)}
.cta .t b,.cta .t span{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cta .t b{font:600 15px/1.35 var(--font-sans);color:var(--ink)}
.cta .b{margin-left:auto;flex:none;display:inline-flex;align-items:center;
        min-height:44px;padding:0 18px;border-radius:999px;
        background:var(--accent);color:var(--accent-ink);
        font:600 14px/1 var(--font-sans)}
.cta .b:hover{background:var(--ink2)}

/* ── 404 / 410 ────────────────────────────────────────────────────── */
.nf{padding:64px 0 80px;max-width:34ch}
.nf h1{margin:0;font:400 28px/1.15 var(--font-serif);color:var(--ink)}
.nf p{margin:12px 0 0;font:16px/24px var(--font-sans);color:var(--ink2)}
.nf .lnk{display:inline-block;margin-top:20px;font:10px/1 var(--font-mono);
         letter-spacing:.5px;text-transform:uppercase;color:var(--ink3)}

@media(max-width:400px){
  .hero h1{font-size:24px}
  .cta .t span{display:none}
}
@media(max-width:360px){
  .col,.hd .in,.hero .in,.cta .in{padding-left:12px;padding-right:12px}
}
