/* Retro-tactile design system, matching Style/homepage-mockup.html:
   Clash Display + Plus Jakarta Sans + JetBrains Mono, hard ink borders,
   offset drop-shadows, brand blue on --paper. Category colors are a
   separately validated palette (dataviz skill) and are NOT part of this
   system — never retouch the --cat- (or --cat- pastel) tokens for a re-skin. */
:root{
  --ink:#0E1726;
  --ink-soft:#334155;
  --muted:#7c8f93;
  --paper:#f0faff;
  --card:#FFFFFF;
  --line:#d8e6ea;
  /* A visible-but-not-solid-black border for guide-page chrome (hero, search,
     pills, dropdowns, modal, map) — landing keeps full ink borders for its
     bold retro identity, but that read as too heavy repeated across an
     entire guide page. Between --line (too pale to read as a real border at
     this scale) and --ink (the thing being toned down). */
  --border-soft:#a7b3bd;
  /* Matches the Reckon wordmark logo — used for headings/brand text so
     they read as one consistent identity with the logo itself. The one
     accent color in the system; there is no secondary accent hue. */
  --accent-dark:#2c5ed4;
  --brand-light:#DCECFE;
  --accent-soft:#dcedf1;
  --fav:#C9A227;
  /* hero overlay text color — the only surviving member of the old
     hero-* trio; --hero-accent/--hero-overlay were unused (dead, tied to
     an unreferenced background image) and have been dropped. */
  --hero-text:#F5F3EE;
  /* Category identity colors — validated categorical palette (dataviz skill),
     fixed hue order, always paired with a visible text label (never color alone). */
  --cat-eat:#2a78d6;
  --cat-cafe:#eb6834;
  --cat-drink:#1baf7a;
  --cat-see:#eda100;
  --cat-shop:#e87ba4;
  --cat-sport:#008300;
  --cat-stay:#4a3aa7;
  /* Pastel tints (same hues, mixed toward white) — used as the tile fill on
     photo-less cards, where the vivid versions above would be too loud. */
  --cat-eat-pastel:#d4e4f7;
  --cat-cafe-pastel:#fbe1d6;
  --cat-drink-pastel:#d1efe4;
  --cat-see-pastel:#fbeccc;
  --cat-shop-pastel:#fae5ed;
  --cat-sport-pastel:#cce6cc;
  --cat-stay-pastel:#dbd8ed;

  --radius-sm:8px;
  --radius-md:14px;
  --radius-lg:20px;

  --shadow-sm:3px 3px 0px var(--ink);
  --shadow-sm-brand:3px 3px 0px var(--accent-dark);
  --shadow-md:5px 5px 0px var(--ink);
  --shadow-md-brand:5px 5px 0px var(--accent-dark);
  --shadow-lg-brand:8px 8px 0px var(--accent-dark);

  --font-display:'Clash Display', sans-serif;
  --font-body:'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono:'JetBrains Mono', monospace;
}
*{box-sizing:border-box}
body{
  overflow-anchor:none; /* squareCards() re-measures + resets every card's
    pixel height on a post-load resize (mobile browser chrome collapsing the
    address bar); without this, scroll anchoring "compensates" for that
    shift and drops the user mid-page instead of at the top after navigating. */
  margin:0; font-family:var(--font-body);
  font-weight:400; background:var(--paper); color:var(--ink); line-height:1.4;
}
a{color:inherit; text-decoration:none}
/* A fixed height, not min-height — align-items:center centers the logo
   within whatever the row's TALLEST sibling makes the content box, and
   that varies by page: an empty #header-nav (home/guide pages) lets the
   logo's own 38px set it, while landing's search input is ~39.5px, so the
   row came out ~1.5px taller there and the centered logo sat visibly
   lower. min-height is only a floor — content taller than it still grows
   the row, so it didn't fix anything. A fixed height forces every page's
   content box to the same size regardless of what's in header-nav, so the
   logo (same 38px everywhere) centers identically everywhere. 64px leaves
   headroom above the tallest known sibling (~39.5px). */
header{
  max-width:900px; margin:0 auto; box-sizing:border-box;
  padding:22px 20px 0; display:flex; align-items:center; gap:10px;
  justify-content:space-between; height:64px;
}
/* Same max-width as main on guide pages (see below) so the header's edges
   land exactly above the card grid's edges instead of running wider. */
@media (min-width:641px){
  body.guide header, body.home header, body.landing header, body.dashboard header{max-width:1180px}
}
#site-title{
  height:38px; width:auto; display:block; cursor:pointer; justify-self:start;
  transform:rotate(-1deg); transition:transform .2s ease;
}
#site-title:hover{transform:rotate(1deg) scale(1.03)}
#header-nav{line-height:1}
#header-nav:empty{display:none}
/* The landing and dashboard headers are 3 true grid columns (not flex
   space-between, which
   only has 2 items to split) so the search bar can sit dead-center on the
   page regardless of how wide the logo or the actions happen to be, with
   the logo and the right-hand actions pinned to the outer edges.
   #header-nav itself contributes no box (display:contents) so its two
   children — search and actions — become direct grid items in columns 2
   and 3. */
body.landing header, body.dashboard header{display:grid; grid-template-columns:1fr auto 1fr; column-gap:16px}
body.landing #header-nav, body.dashboard #header-nav{display:contents}
/* min(570px, 40vw), not 100% — a percentage here is indeterminate for an
   "auto"-sized grid track's intrinsic sizing pass, so the box silently fell
   back to the input's default (~205px) no matter what px value was set.
   vw isn't subject to that circularity. */
.nav-search-box{position:relative; grid-column:2; justify-self:center; width:min(570px, 40vw); display:none}
@media (min-width:768px){.nav-search-box{display:block}}
.nav-search-box input{
  width:100%; font:inherit; font-size:.88rem; color:var(--ink);
  background:var(--card); border:2px solid var(--ink); border-radius:999px;
  padding:9px 16px 9px 38px; outline:none; box-shadow:var(--shadow-sm-brand);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.nav-search-box input:focus{border-color:var(--accent-dark); box-shadow:3px 3px 0px var(--accent-dark)}
.nav-search-box input::placeholder{color:var(--muted)}
.nav-search-icon{
  position:absolute; left:14px; top:50%; transform:translateY(-50%);
  color:var(--accent-dark); font-size:.9rem; pointer-events:none;
}
.nav-actions-box{grid-column:3; justify-self:end; display:flex; align-items:center; gap:16px}
.nav-login-btn{
  font-family:var(--font-display); font-weight:700; font-size:.85rem;
  color:#fff; background:var(--accent-dark); border:2px solid var(--ink);
  border-radius:999px; padding:8px 24px; box-shadow:var(--shadow-sm);
  transition:transform .15s ease, box-shadow .15s ease;
}
.nav-login-btn:hover{transform:translate(1px,1px); box-shadow:2px 2px 0px var(--ink)}
main{max-width:900px; margin:0 auto; padding:20px}

/* Wider than the default 900px specifically so 3 comfortably-sized
   columns fit (square rec tiles on a guide page, continent columns on
   the home page) instead of being squeezed to 2. */
@media (min-width:641px){
  body.guide main, body.home main, body.landing main, body.dashboard main{max-width:1180px}
}

/* ---- home: "James's books" — a Spotify/Pinterest-style library of
   destination tiles, grouped by continent. Three fixed columns (grid, not
   auto-balancing) so every column's first heading lands in the same row —
   a bucket with more than one continent just stacks them internally. */
.home-page{padding:min(4vh, 40px) 0 40px}
.home-title-row{display:flex; align-items:center; gap:18px; margin-bottom:40px}
.home-avatar{
  flex:0 0 auto; width:96px; height:96px; border-radius:50%; overflow:hidden;
  background:var(--accent-soft); border:1.5px solid var(--border-soft);
  transition:border-color .15s ease;
}
.home-avatar:hover{border-color:var(--accent-dark)}
.home-avatar img{width:100%; height:100%; object-fit:cover; display:block}
.home-title{
  margin:0 0 4px; font-family:var(--font-display); font-weight:700;
  font-size:clamp(32px,5vw,44px); color:var(--accent-dark);
}
.home-subtitle{margin:0; color:var(--muted)}
.continent-jump{
  display:none; width:100%; margin-bottom:24px; padding:10px 12px;
  border:2px solid var(--ink); border-radius:var(--radius-sm); background:var(--card);
  color:var(--ink); font:inherit; font-size:.9rem;
}
@media (max-width:640px){.continent-jump{display:block}}
.continent-columns{display:grid; grid-template-columns:1fr; gap:0 28px; align-items:start}
@media (min-width:641px){.continent-columns{grid-template-columns:repeat(3, 1fr)}}
.continent-group{margin-bottom:32px}
.continent-heading{
  margin:0 0 14px; padding-bottom:10px; border-bottom:1px solid var(--accent-dark);
  font-family:var(--font-body); font-size:.78rem; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; color:var(--accent-dark);
}
.dest-tile-grid{display:flex; flex-direction:column; gap:12px}
/* An inset box-shadow, not a real border — a real `border` carves its
   width out of the padding box that the photo <img> (position:absolute;
   inset:0) is positioned against, so the rotated/scale-compensated photo
   (see rotateTransform()) came up a few px short of the tile's own edge on
   any rotated cover photo, leaving a sliver of background visible on two
   sides. box-shadow is paint-only — it doesn't touch the box model — so
   inset:0 now reaches the tile's true edge and the compensation math (which
   assumes it does) is exact again. Matches the destination-page border
   language (thin, --border-soft) instead of the old 2px ink one. */
.dest-tile{
  position:relative; display:block; aspect-ratio:16/10; border-radius:var(--radius-lg); overflow:hidden;
  background:linear-gradient(135deg, var(--accent-soft), var(--card));
}
/* The border line lives on a generated overlay, not directly on .dest-tile
   — a box-shadow painted on the tile itself sits BELOW its own children in
   normal stacking order, so the full-bleed <img> (inset:0, see the note
   above) painted right over it, hiding it completely except for a flash
   during the opacity crossfade between photos. ::after is generated last,
   so it paints on top of every .tile-photo regardless of z-index; pointer-
   events:none keeps it from stealing clicks meant for the arrows/tile link. */
.dest-tile::after{
  content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
  box-shadow:inset 0 0 0 1.5px var(--border-soft); transition:box-shadow .15s ease;
}
.dest-tile-info{
  position:absolute; left:0; right:0; bottom:0; padding:12px 14px;
}
.dest-tile.has-photo .dest-tile-info{
  background:linear-gradient(0deg, rgba(10,16,18,.75) 0%, rgba(10,16,18,0) 75%);
}
.dest-tile-name{
  margin:0; font-family:var(--font-display); font-size:1.5rem; font-weight:700;
  color:var(--ink);
}
.dest-tile.has-photo .dest-tile-name{color:var(--hero-text)}
.dest-tile.coming-soon{opacity:.55; cursor:default}
.dest-tile-soon{
  display:inline-block; margin-top:2px; font-family:var(--font-mono); font-size:.68rem;
  text-transform:uppercase; letter-spacing:.06em; color:var(--muted);
}
.dest-tile:not(.coming-soon):hover::after{box-shadow:inset 0 0 0 1.5px var(--accent-dark)}

/* ---- dashboard: the logged-in "/" (Stage 2 of the homepage plan in
   BACKLOG.md). Mirrors Style/homepage-mockup.html's "COMMAND CENTER" view
   section for section: greeting banner, Upcoming Trip Boards, My Published
   Books, Your Circle's Destinations, Fresh From Your Circle, footer.
   Books are real; the other three have no tables yet and render the
   mockup's chrome around a .dash-preview-panel instead of invented people
   (see the header comment on renderDashboard in app.js). */
.dashboard-page{padding:min(4vh,40px) 0 40px}
.dash-hero{
  display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:20px;
  padding:28px 32px; margin-bottom:40px;
  background:var(--card); border:2px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md-brand);
}
.dash-title{
  margin:0 0 8px; font-family:var(--font-display); font-weight:700;
  font-size:clamp(28px,4.5vw,40px); color:var(--ink);
}
.dash-title span{color:var(--accent-dark)}
.dash-meta{margin:0; color:var(--ink-soft)}
.dash-meta strong{color:var(--ink)}
.dash-hero-actions{flex:0 0 auto; display:flex; gap:10px; flex-wrap:wrap}
@media (max-width:640px){.dash-hero{padding:22px}}

/* ---- logged-in header (mockup's #navLoggedIn) ---- */
/* The outline "Add spot" action and the "@handle" + avatar badge. Both sit
   in #header-nav's .nav-actions-box alongside the shared centred search. */
.dash-nav-outline{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  font-weight:700; font-size:.8rem; padding:9px 16px; border-radius:999px;
  background:var(--card); color:var(--ink); border:2px solid var(--ink);
  box-shadow:2px 2px 0px var(--ink); transition:transform .15s ease, box-shadow .15s ease;
}
.dash-nav-outline:hover{transform:translate(1px,1px); box-shadow:1px 1px 0px var(--ink)}
.dash-user-badge{
  display:inline-flex; align-items:center; gap:8px; cursor:pointer;
  padding:4px 6px 4px 12px; font:inherit; background:var(--card);
  border:2px solid var(--ink); border-radius:999px; box-shadow:2px 2px 0px var(--accent-dark);
  transition:transform .15s ease, box-shadow .15s ease;
}
/* Presses IN on hover, the same gesture the page's buttons use (.dash-btn-demo,
   .dash-btn-primary): move along the shadow's direction and drop the shadow, so
   the element reads as pushed flat against the page. The distance matches the
   resting shadow exactly (2px here, where those buttons offset 2px against a
   3px shadow), which is what makes it land flush rather than half-pressed. */
.dash-user-badge:hover{transform:translate(2px,2px); box-shadow:none}
.dash-user-handle{font-family:var(--font-mono); font-size:.78rem; font-weight:700; color:var(--ink)}
/* Always a circle, photo or not — an empty badge with no avatar set would
   otherwise collapse and pull the handle off-centre inside the pill. */
.dash-user-avatar{
  width:30px; height:30px; flex:0 0 auto; border-radius:50%; overflow:hidden;
  background:var(--accent-soft); border:1.5px solid var(--ink);
}
.dash-user-avatar img{width:100%; height:100%; object-fit:cover; display:block}
@media (max-width:640px){
  /* The header is tight on a phone; the badge alone still opens the menu
     that "Add spot" duplicates (it links to /admin either way). */
  .dash-nav-outline{display:none}
  .dash-user-handle{display:none}
  .dash-user-badge{padding:4px}
}

/* "@handle" header menu — reuses the existing .filter-dd/.nav-dd dropdown
   (same one the guide page's "All destinations" trigger uses), so no new
   dropdown CSS is needed beyond making its one non-link item (the Log out
   button) look like the <a> beside it. */
/* `font:inherit` is the shorthand, so it resets font-size too — and since it
   inherits from the trigger rather than from `.nav-dd-item`'s own .85rem, the
   Log out button rendered at the body's 16px next to 13.6px links. Restating
   the size after the shorthand is what keeps every item in the menu identical;
   the shorthand still has to come first, to pick up the font family. */
button.nav-dd-item{
  display:block; width:100%; text-align:left; background:none; border:none;
  font:inherit; font-size:.85rem; cursor:pointer;
}
/* nav-dd-panel's width:100%/min-width:0 (below, `.nav-dd .filter-dd-panel`)
   otherwise inherits the "@handle" trigger's own narrow content width —
   fine for the guide page's long destination-name list (which wraps on
   purpose), but "My profile"/"Log out" wrapped mid-word at that width.
   Three classes, not two, so this wins on specificity regardless of
   source order against that later same-specificity rule. The panel is
   right-aligned because the badge sits at the header's right edge, where a
   left-aligned panel would overflow the viewport. */
.filter-dd.nav-dd.dash-profile-dd .filter-dd-panel{
  min-width:160px; width:auto; white-space:nowrap; left:auto; right:0;
}

/* A small general-purpose button for in-page dashboard actions. */
.dash-btn{
  font-family:var(--font-display); font-weight:700; font-size:.9rem;
  padding:10px 20px; border-radius:var(--radius-md); border:2px solid var(--ink);
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  cursor:pointer; transition:all .15s ease;
}
.dash-btn-primary{background:var(--accent-dark); color:#fff; box-shadow:var(--shadow-sm)}
.dash-btn-primary:hover{transform:translate(2px,2px); box-shadow:none}
/* The mockup's second, lighter hero CTA (.btn-demo-retro) — white fill,
   brand shadow. Text swaps to "Link copied" for ~2s after a copy, so a
   fixed width would jump; it's allowed to size to content instead. */
.dash-btn-demo{background:var(--card); color:var(--ink); box-shadow:var(--shadow-sm-brand)}
.dash-btn-demo:hover{transform:translate(2px,2px); box-shadow:none}

.dash-section{margin-bottom:40px}
.dash-section-header{display:flex; align-items:baseline; justify-content:space-between; gap:16px; margin-bottom:18px; flex-wrap:wrap}
.dash-section-title{
  margin:0; font-family:var(--font-display); font-weight:700; font-size:1.4rem; color:var(--ink);
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
}
.dash-section-badge{
  font-family:var(--font-mono); font-size:.68rem; font-weight:700; letter-spacing:.04em;
  color:var(--accent-dark); background:var(--accent-soft); border-radius:999px; padding:3px 10px;
}
.dash-badge-muted{color:var(--muted); background:var(--paper)}
.dash-section-link{font-family:var(--font-body); font-weight:700; font-size:.82rem; color:var(--accent-dark)}
.dash-section-link:hover{text-decoration:underline}
/* Not a real link (no href) — trip boards have nowhere to go yet. Styled
   visibly inert rather than a dead click; the title attribute explains why. */
.dash-link-disabled{color:var(--muted); cursor:default}
.dash-link-disabled:hover{text-decoration:none}
/* The mockup's small right-hand mono caption on a section header
   ("NETWORK GRAPH", "REAL-TIME ADDITIONS"). */
.dash-section-note{
  font-family:var(--font-mono); font-size:.68rem; font-weight:700; letter-spacing:.04em;
  color:var(--accent-dark);
}

/* The stand-in for the three sections whose tables don't exist yet (trip
   boards, circle, feed). Sized like the content it replaces so the page
   keeps the mockup's rhythm, and dashed rather than solid so it never reads
   as a real card with real rows in it. */
.dash-preview-panel{
  position:relative; padding:36px 28px; text-align:center;
  border:2px dashed var(--border-soft); border-radius:var(--radius-lg);
  background:var(--card);
}
.dash-preview-title{margin:0 0 8px; font-family:var(--font-display); font-weight:700; font-size:1.05rem; color:var(--ink)}
.dash-preview-body{margin:0 auto; max-width:52ch; font-size:.88rem; color:var(--ink-soft); line-height:1.5}
.dash-preview-tag{
  position:absolute; top:12px; right:14px;
  font-family:var(--font-mono); font-size:.62rem; font-weight:700; letter-spacing:.08em;
  color:var(--muted);
}

/* ---- My published books (mockup's .books-grid-retro) ---- */
/* A flat cover image per book with the name and spot count over a scrim —
   the mockup's launcher grid, deliberately not the public browse page's
   photo-cycling .dest-tile.
   Capped at TWO ROWS and paged sideways rather than wrapping down the page:
   the mockup drew 3 books, but at 25 the section alone would be taller than
   the screen and push the rest of the dashboard out of sight. `grid-auto-flow:
   column` fills top-to-bottom then starts a new column, so reading order stays
   sane and the "New destination" tile lands last wherever that falls.
   Columns are sized as a fraction of the VISIBLE box (not 1fr, which would
   divide the whole scrollable width) so exactly four, two or one show at a
   time and a page-scroll lands cleanly. */
.dash-books-wrap{display:flex; align-items:center; gap:10px}
.dash-books-grid{
  display:grid; grid-auto-flow:column;
  grid-template-rows:repeat(2, 220px);
  grid-auto-columns:calc((100% - 3 * 18px) / 4);
  gap:18px; min-width:0; flex:1;
  overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;
  /* Room for the tiles' 3px offset shadow, which sits outside the border box
     and would otherwise be clipped by the scroll container on all four sides. */
  padding:4px 4px 14px;
  scrollbar-width:thin; scrollbar-color:var(--border-soft) transparent;
}
.dash-books-grid::-webkit-scrollbar{height:6px}
.dash-books-grid::-webkit-scrollbar-thumb{background:var(--border-soft); border-radius:3px}
.dash-books-grid > *{scroll-snap-align:start}
/* One row once only one or two columns fit — two rows of one tile is a column,
   not a grid, and it wastes half the viewport height on a phone. */
@media (max-width:900px){
  .dash-books-grid{grid-auto-columns:calc((100% - 18px) / 2)}
}
@media (max-width:540px){
  .dash-books-grid{grid-template-rows:220px; grid-auto-columns:100%}
}
.dash-books-arrow{
  flex:0 0 auto; width:36px; height:36px; border-radius:50%; border:2px solid var(--ink);
  background:var(--card); color:var(--ink); font-size:1.2rem; line-height:1; cursor:pointer;
  display:flex; align-items:center; justify-content:center; box-shadow:var(--shadow-sm);
  transition:transform .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.dash-books-arrow:hover{transform:translate(1px,1px); box-shadow:none}
/* Dimmed, not hidden — removing one would reflow the row and shift every tile
   sideways mid-scroll. */
.dash-books-arrow.at-end{opacity:.3; pointer-events:none}
@media (max-width:640px){
  /* Touch scroll is the natural interaction at this width (same reasoning as
     .continent-jump's mobile fallback) — arrows are redundant. */
  .dash-books-arrow{display:none}
}
.dash-book-tile{
  position:relative; overflow:hidden; display:flex; align-items:flex-end;
  padding:16px; color:#fff; border:2px solid var(--ink); border-radius:var(--radius-md);
  box-shadow:var(--shadow-sm); transition:transform .2s ease, box-shadow .2s ease;
}
.dash-book-tile:hover{transform:translateY(-4px); box-shadow:var(--shadow-md)}
.dash-book-img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block}
.dash-book-noimg{background:var(--accent-soft)}
/* Strong at the bottom, gone by 60% — the name and count sit in the bottom
   band and have to stay legible over a bright photo. */
.dash-book-scrim{
  position:absolute; inset:0;
  background:linear-gradient(to top, rgba(14,23,38,.92) 0%, rgba(14,23,38,.3) 60%, transparent 100%);
}
.dash-book-text{position:relative; display:block}
.dash-book-name{
  display:block; font-family:var(--font-display); font-weight:700; font-size:1.35rem;
  line-height:1.1; margin-bottom:4px; text-shadow:0 2px 6px rgba(0,0,0,.6);
}
.dash-book-count{
  display:block; font-family:var(--font-mono); font-size:.68rem; font-weight:700;
  letter-spacing:.04em; color:#E2E8F0;
}
/* Same box as a book tile so it sits flush in the grid, dashed and brand-
   coloured so it reads as an action rather than an empty book. */
.dash-add-tile{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  background:var(--card); color:var(--accent-dark);
  border:2px dashed var(--accent-dark); border-radius:var(--radius-md);
  box-shadow:var(--shadow-sm); transition:all .15s ease;
}
.dash-add-tile:hover{background:var(--accent-soft); transform:translateY(-2px); box-shadow:var(--shadow-md-brand)}
.dash-add-icon{font-size:1.7rem; font-weight:700; line-height:1}
.dash-add-text{font-family:var(--font-display); font-weight:700; font-size:1rem}

/* ---- Your Circle's Destinations / Fresh From Your Circle ---- */
/* Both are the mockup's big bordered box; the header sits inside the box
   for the trust graph and above it for the feed, exactly as in the mockup. */
.dash-trust-box, .dash-feed-box{
  background:var(--card); border:2px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md); padding:24px;
}
.dash-box-header{margin-bottom:8px}
.dash-box-sub{margin:0 0 16px; font-size:.88rem; color:var(--ink-soft)}
/* Inside a box the panel is already on card white and inside a border, so
   it drops its own fill and softens its edge rather than double-framing. */
.dash-trust-box .dash-preview-panel,
.dash-feed-box .dash-preview-panel{background:transparent; padding:28px 20px}

/* ---- guide ---- */
.guide-header{margin-bottom:18px; padding-top:6px}
.guide-header h1{margin:0 0 2px; font-family:var(--font-display); font-size:2.3rem; font-weight:700; color:var(--accent-dark)}
/* Toggle sits inline next to the subtitle instead of on its own row —
   saves a full row of header height. */
.connection-row{display:flex; align-items:center; gap:8px; margin-bottom:12px}
.connection-line{color:var(--muted); font-size:.85rem; font-weight:400}

/* Cover-photo banner — name/subtitle overlaid at the bottom instead of
   sitting as plain text above the page, same overlay language as the
   rec cards/dest tiles (gradient + light text over a photo). Only
   rendered at all when the guide actually has a cover photo; no photo
   falls back to the original plain-text header untouched. */
.guide-hero{
  position:relative; aspect-ratio:3/1; border-radius:var(--radius-lg); overflow:hidden;
  margin-bottom:10px; background:var(--accent-soft); border:1.5px solid var(--border-soft);
  transition:border-color .15s ease;
}
.guide-hero:hover{border-color:var(--accent-dark)}
@media (max-width:640px){.guide-hero{aspect-ratio:12/7}}
.guide-hero-overlay{
  position:absolute; left:0; right:0; bottom:0; padding:28px 24px 16px; z-index:1;
  background:linear-gradient(0deg, rgba(10,16,18,.82) 0%, rgba(10,16,18,0) 80%);
}
.guide-hero-overlay h1{color:var(--hero-text)}
.guide-hero-overlay .connection-line{color:var(--hero-text); opacity:.9}
/* The intro/tips content itself — no more collapsible box: with a hero
   photo it lives inside the destination modal (see destModalHtml()); with
   no hero photo it renders inline, plainly, right under the title. */
.lead-text{font-size:1rem; color:var(--ink-soft); margin:0 0 10px}
.tips{margin:0; padding-left:18px; color:var(--ink-soft); font-size:.9rem}
.tips li{margin-bottom:4px}

/* Not sticky — search was taking up too much of the pinned header's height.
   Only the compact dropdown row + view/clear row stay stuck. */
.search-wrap{margin:0 0 12px}
.search-input{
  /* Matches every other control in this row (.82rem) — was .95rem, the one
     thing actually breaking the "shares one font-size" rule described below. */
  width:100%; padding:11px 14px; font-size:.82rem; border:1.5px solid var(--border-soft);
  border-radius:var(--radius-lg); background:var(--card); color:var(--ink); font-family:inherit; outline:none;
  transition:border-color .15s ease;
}
.search-input:focus{border-color:var(--accent-dark)}
.search-input::placeholder{color:var(--muted)}

.filter-label-row{display:flex; align-items:center; justify-content:space-between; margin-bottom:6px}
.filter-label{
  font-family:var(--font-mono); font-size:.7rem; text-transform:uppercase; letter-spacing:.06em; color:var(--muted);
}
.tabs{display:flex; gap:5px; flex-wrap:wrap; margin:0 0 14px; row-gap:5px}
/* Chips, dropdown triggers, and fav/visited toggles all share one
   font-size (.82rem desktop / .74rem mobile) so the sticky bar and the
   full chip row read as one consistent control language, not three
   differently-sized ones. */
.tab{
  padding:6px 14px; border-radius:999px; border:1.5px solid var(--border-soft); background:var(--card);
  cursor:pointer; font-size:.82rem; white-space:nowrap; transition:border-color .15s ease;
}
.tab:hover{border-color:var(--accent-dark)}
.tab.active{background:var(--accent-dark); color:#fff; border-color:var(--accent-dark)}
.cat-dot{display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:5px; flex:0 0 auto; background:var(--muted)}
.tab .cat-dot{margin-right:6px}
/* Without the "All" chip, 6 categories already fit two rows on mobile
   without shrinking the text — just trim the padding/gap a little. Every
   chip/dropdown/toggle (category, region, neighborhood, favourites, been
   here, list/map) shares the SAME padding here, sized to whatever the
   most space-constrained one needs (fitting 3 dropdowns on one row) — a
   mismatch in padding read as a text-size mismatch even though the actual
   font-size was already identical everywhere. */
@media (max-width:480px){
  .tabs{gap:4px; row-gap:4px}
  .tab, .filter-dd-trigger, .fav-toggle, .view-switch-opt{padding:5px 8px}
  .tab .cat-dot{width:6px; height:6px; margin-right:4px}
  /* Category/Region/Neighborhood stay on one row in the sticky bar even on
     narrow phones — Favourites/Been-here wrap to their own row below
     instead (filter-dd-row itself still wraps), rather than letting the
     three dropdowns break apart across two lines. */
  #cat-region-group{flex-wrap:nowrap; gap:5px}
}

/* Sticks under the page top as the user scrolls, so filters stay reachable
   without scrolling back up. Opaque background + bottom border so grid
   content scrolling underneath doesn't show through. Deliberately compact
   (dropdowns, not the full tab/chip row) so it doesn't eat the screen on
   mobile — the full category chips and region/neighborhood pickers below
   are the primary controls; this is just what stays reachable once stuck. */
.filter-bar{
  position:sticky; top:0; z-index:20; background:var(--paper);
  padding-top:10px;
}
/* Only the pinned (stuck) state needs breathing room below the pills —
   .filter-bar's own painted background box ends right at .filter-dd-row's
   content edge (margin doesn't extend a background), so with no
   padding-bottom here the pinned bar's edge sat flush against the pills,
   and cards scrolled in right underneath with no gap at all. The unstuck
   two-row layout already has its own margin-bottom on .meta-row and didn't
   need this. */
/* var(--border-soft), not var(--line) — --line (#d8e6ea) sits too close in
   tone to --paper (#f0faff), the bar's own background, to actually read as
   a line at 1px; --border-soft has real contrast against it.
   The line (and the bar's own background) needs to run edge-to-edge across
   the viewport, not just main's centered content column — .filter-bar
   normally sits inside main's max-width + padding, so a plain border would
   stop wherever main's content happens to end. margin-left/right:
   calc(50% - 50vw) bleeds the box out to the full viewport width (the "%"
   here resolves against main's content width, i.e. .filter-bar's own
   un-bled width, which is what makes this correct regardless of main's own
   max-width/padding); the matching padding-left/right:calc(50vw - 50%)
   then pushes the actual pills back in by the exact same amount, so they
   land right back where they were — only the painted background and this
   border extend the rest of the way to the screen edges. */
.filter-bar.is-stuck{
  padding-bottom:5px; border-bottom:1px solid var(--border-soft);
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  padding-left:calc(50vw - 50%); padding-right:calc(50vw - 50%);
}
#sticky-sentinel{height:1px}
/* Category dropdown only appears once the bar is actually pinned (an
   IntersectionObserver on #sticky-sentinel toggles .is-stuck in app.js) —
   the full chip row above is the primary picker while scrolled to the top. */
.cat-dd-sticky-only{display:none}
.filter-bar.is-stuck .cat-dd-sticky-only{display:inline-block}

/* Region/area (and, compactly, category) filters live as dropdowns here —
   the full-size category chips above are the primary picker; this trio
   is what stays visible and reachable once the bar is stuck to the top. */
.filter-dd-row{display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-bottom:14px}
.filter-dd-group{display:flex; flex-wrap:wrap; gap:8px}
/* Two different right-anchors depending on state, both via margin-left:auto
   directly on the element that should hug the row's right edge — not
   justify-content:space-between, which silently falls back to flex-start
   when the pushed item is alone on a wrapped line (bit us on a real phone
   already). Unstuck: #fav-group (Favourite/Visited) is the one pinned
   right, sitting in the same row as Region. Stuck: #fav-group instead
   clusters left with #cat-region-group (Category/Region compact
   dropdowns), and the sticky-only List/Map — now a plain sibling of both,
   not nested inside either — takes over the right-hand pin instead. */
#fav-group{margin-left:auto}
.filter-bar.is-stuck #fav-group{margin-left:0}
.filter-bar.is-stuck .view-switch.view-switch-sticky-only{margin-left:auto}
.filter-dd{position:relative}
.filter-dd-trigger{
  display:inline-flex; align-items:center; gap:6px; padding:7px 14px; border-radius:999px;
  border:1.5px solid var(--border-soft); background:var(--card); cursor:pointer; font-size:.82rem; color:var(--ink-soft);
  transition:border-color .15s ease;
}
.filter-dd-trigger:hover{border-color:var(--accent-dark)}
.filter-dd-count{
  background:var(--accent-dark); color:#fff; border-radius:999px; padding:1px 7px;
  font-family:var(--font-mono); font-size:.7rem; line-height:1.4;
}
.dd-chevron{font-size:.95rem; line-height:1; color:var(--muted)}
.filter-dd-panel{
  position:absolute; top:calc(100% + 6px); left:0; z-index:30; min-width:230px; max-height:280px;
  overflow-y:auto; background:var(--card); border:1.5px solid var(--border-soft); border-radius:var(--radius-md);
  padding:8px;
}
.filter-dd-item{
  display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:6px; font-size:.85rem;
  color:var(--ink-soft); cursor:pointer;
}
.filter-dd-item:hover{background:var(--paper)}
.filter-dd-item input{flex:0 0 auto; margin:0}
.filter-dd-item .chip-n{margin-left:auto; color:var(--muted); font-size:.75rem}

.nav-dd .filter-dd-panel{left:auto; right:0; width:100%; min-width:0}
/* "All" leads the destinations menu and goes up a level rather than sideways to
   another destination, so it reads as a heading with a rule under it. */
.nav-dd-all{
  font-weight:700; color:var(--accent-dark);
  border-bottom:1.5px solid var(--line); margin-bottom:4px; padding-bottom:8px;
}
/* The guide header carries two controls now — the destinations dropdown and the
   account badge — where every other page's #header-nav holds one thing. */
body.guide #header-nav{display:flex; align-items:center; gap:12px}
.nav-dd .filter-dd-trigger{color:var(--accent-dark); border-color:var(--accent-dark); font-weight:700}
.nav-dd .dd-chevron{color:var(--accent-dark)}
.nav-dd-item{display:block; padding:7px 10px; border-radius:6px; font-size:.85rem; color:var(--ink-soft); white-space:normal}
.nav-dd-item:hover{background:var(--paper)}
.nav-dd-item.active{color:var(--accent-dark); font-weight:700}

.meta-row{display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:10px; margin-bottom:14px}
.rec-count{color:var(--muted); font-size:.85rem}
/* The stuck-bar copy of List/Map (see the comment above its markup in
   app.js) — hidden in normal flow, since .meta-row's own copy is already
   showing there; swaps once the bar is pinned, when .meta-row (rec count
   included) hides instead. */
/* Chained classes (not just .view-switch-sticky-only) so this reliably
   outranks the plain .view-switch{display:flex} rule below regardless of
   source order — two equal-specificity, same-property rules would
   otherwise resolve by declaration order, not intent, which is exactly
   what silently showed both List/Map copies at once here before. */
.view-switch.view-switch-sticky-only{display:none}
.filter-bar.is-stuck .view-switch.view-switch-sticky-only{display:flex}
.filter-bar.is-stuck .meta-row{display:none}

.view-switch{
  display:flex; border:1.5px solid var(--border-soft); border-radius:999px; overflow:hidden; background:var(--card);
}
.view-switch-opt{padding:7px 16px; font-size:.82rem; color:var(--ink-soft); cursor:pointer}
.view-switch-opt.active{background:var(--accent-dark); color:#fff}
.fav-toggle{
  display:inline-flex; align-items:center; gap:6px; padding:7px 14px; border-radius:999px;
  border:1.5px solid var(--border-soft); background:var(--card); cursor:pointer; font-size:.82rem; color:var(--ink-soft);
  transition:border-color .15s ease;
}
.fav-toggle:hover{border-color:var(--fav)}
/* Gold by default — same identity as the star/check badge on a card that's
   actually favourited/visited — so the icon previews the color the whole
   pill turns once you enable it, instead of a plain grey stand-in. Flips to
   white only once active, for contrast against the now gold-filled pill. */
.fav-toggle svg{width:12px; height:12px; color:var(--fav)}
.fav-toggle.active{background:var(--fav); border-color:var(--fav); color:#fff}
.fav-toggle.active svg{color:#fff}
/* Matches .filter-label's exact type (mono, .7rem, uppercase, letter-
   size (.7rem), not its own larger .82rem — so Clear appearing/
   disappearing next to CATEGORY doesn't change the row's line-height and
   nudge everything below it down a couple pixels. Normal body font/case/
   spacing though — the mono-uppercase treatment is for the CATEGORY *label*
   specifically, not for actual clickable copy like this. */
.clear-filters{
  font-family:var(--font-body); font-size:.7rem;
  color:var(--accent-dark); cursor:pointer; font-weight:700;
}

/* align-items:start overrides grid's default stretch — without it, any row
   whose auto height comes out slightly taller (rounding, a longer name
   wrapping, etc.) stretches every card in that row to match, which is what
   was showing up as dead space inside shorter cards. */
.rec-grid{
  display:grid; grid-template-columns:repeat(3, 1fr);
  gap:14px; align-items:start;
}
@media (max-width:640px){
  .rec-grid{grid-template-columns:repeat(2, 1fr); gap:8px}
}
/* Every card opens the rec detail modal (click/tap) to reveal its note —
   front face stays minimal (name + category + area, bottom-left), whether
   or not it has a photo. Category rides two ways: a thin border outline
   (--cat-color) on every tile, and — when there's no photo — a pastel fill
   (--cat-pastel) standing in for the missing image. Fixed aspect-ratio (not
   a fixed height) so the tile shape is identical on desktop and mobile,
   just scaled. */
.rec-card{
  /* Height is set in JS (squareCards() in app.js) to exactly match the
     rendered width in pixels. Both aspect-ratio and the padding-bottom
     trick turned out to conflict with Grid's own row-auto-sizing pass —
     the box itself measured a correct square via getBoundingClientRect,
     but the grid row track sized short, so the square overflowed into
     the next row and got painted over. An explicit pixel height is the
     one thing Grid's row sizing can't misjudge. */
  position:relative; width:100%; cursor:pointer;
}
.tile-face{
  /* Per-card category-color border is load-bearing — never replace the
     var(--cat-color, ...) fallback mechanism with a flat --ink border. */
  position:absolute; inset:0;
  border-radius:var(--radius-md); overflow:hidden; border:1.5px solid var(--cat-color, var(--line));
  background:var(--card); display:flex; flex-direction:column;
}
.tile-front{
  justify-content:flex-end;
  background-color:var(--cat-pastel, var(--accent-soft));
}
.tile-photo{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  background-size:cover; background-repeat:no-repeat;
  z-index:0; display:block; opacity:0; transition:opacity .2s ease;
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
}
.tile-photo.active{opacity:1}

/* Multi-photo nav — arrows + dots on the card front, no flip needed to
   browse. Arrows get their own -webkit-backface-visibility (not just
   inherited from .tile-face) for the same reason .badge-row needs it:
   Safari doesn't reliably cascade backface-visibility to position:absolute
   descendants. */
/* Arrows only ever appear over a photo (multi-photo nav), so they get the
   same translucent-white ring the star/check/maps badges use when they're
   sitting on a photo — never the plain ink border those badges use on a
   photo-less pastel tile. */
.photo-arrow{
  position:absolute; top:50%; transform:translateY(-50%) translateX(0); z-index:2;
  width:24px; height:24px; border-radius:50%; border:1.5px solid rgba(255,255,255,.5); cursor:pointer;
  background:rgba(0,0,0,.4); color:#fff; font-size:16px; line-height:1;
  display:flex; align-items:center; justify-content:center; opacity:.6;
  transition:opacity .15s ease;
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
}
.photo-arrow:hover{opacity:1}
.photo-prev{left:6px}
.photo-next{right:6px}
/* A small "N/M" corner badge replaced a dot row — dots ate too much of a
   small mobile tile and read as sitting mid-photo instead of at an edge.
   Top-center, same spot on every card regardless of screen size — clear
   of the left star/check and right maps-pin badges, and clear of the
   variable-height name block at the bottom (bottom-right got squeezed
   out by that name block on small mobile tiles, so it's not used at any
   size — one consistent placement everywhere beats it fitting sometimes). */
.photo-counter{
  position:absolute; top:8px; left:50%; transform:translateX(-50%); z-index:1;
  font-family:var(--font-mono); font-size:.68rem; font-weight:400; color:rgba(255,255,255,.65); background:rgba(0,0,0,.5);
  border:1.5px solid rgba(255,255,255,.4);
  padding:2px 7px; border-radius:999px; pointer-events:none;
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
}

/* Star/visited top-left, maps-link top-right — same circular "pill" size
   and treatment everywhere (photo or pastel fill), so the three read as one
   consistent icon language instead of two different styles. -webkit-
   backface-visibility here specifically (not just on the parent .tile-face)
   is the actual fix for the badges bleeding through mirrored in Safari. */
.badge-row{
  position:absolute; top:8px; display:flex; align-items:center; gap:6px; z-index:1;
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
}
.badge-row.left{left:8px}
.badge-row.right{right:8px}
.badge, .maps-btn{
  width:24px; height:24px; border-radius:50%; display:inline-flex; align-items:center;
  justify-content:center; flex:0 0 auto; background:rgba(255,255,255,.8); color:var(--ink-soft);
  border:1.5px solid var(--ink); transition:all .15s ease;
}
.rec-card.has-photo .badge, .rec-card.has-photo .maps-btn{
  background:rgba(20,28,30,.55); color:#fff; border-color:rgba(255,255,255,.5);
}
.badge-star, .badge-check{color:var(--fav) !important}
.badge svg, .maps-btn svg{width:13px; height:13px}
.maps-btn:hover{background:var(--accent-dark); color:#fff; border-color:var(--accent-dark)}
/* The has-photo override two rules up is 3 classes deep (.rec-card.has-photo
   .maps-btn) vs. plain .maps-btn:hover's 2 — higher specificity wins
   regardless of hover, so without this the pin never actually turned blue
   on a photo card. Same specificity bump the modal's photo-context override
   needs below. */
.rec-card.has-photo .maps-btn:hover{background:var(--accent-dark); color:#fff; border-color:var(--accent-dark)}

.tile-name-block{
  position:relative; z-index:1; padding:22px 12px 10px;
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
}
.rec-card.has-photo .tile-name-block{
  background:linear-gradient(0deg, rgba(10,16,18,.8) 0%, rgba(10,16,18,0) 65%);
}
.rec-card.has-photo .tile-name-block .card-name,
.rec-card.has-photo .tile-name-block .card-hood{color:var(--hero-text)}

.card-name{
  font-family:var(--font-display); font-size:1.05rem; font-weight:700; margin:0;
  line-height:1.25; color:var(--ink);
}
.card-hood{
  font-family:var(--font-mono); font-size:.7rem; color:var(--muted); text-transform:uppercase;
  letter-spacing:.03em; margin-top:3px;
}

.tag-row{margin-top:8px; display:flex; flex-wrap:wrap; gap:5px}
.tag{
  font-family:var(--font-mono); font-size:.68rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--accent-dark); background:var(--brand-light);
  padding:2px 8px; border-radius:6px;
}

/* Desktop tiles have real room now (3 columns, wider guide container) —
   nudge the type up a step from the mobile-first sizes above. */
@media (min-width:641px){
  .card-name{font-size:1.15rem}
  .card-hood{font-size:.72rem}
  .tag{font-size:.72rem}
}

/* ---- rec detail modal — opened by tapping a card ---- */
.rec-modal-backdrop{
  position:fixed; inset:0; background:rgba(10,16,18,0); z-index:100;
  display:flex; align-items:flex-end; justify-content:center;
  transition:background .28s ease; pointer-events:none;
}
.rec-modal-backdrop.open{background:rgba(10,16,18,.6); pointer-events:auto}
@media (min-width:641px){.rec-modal-backdrop{align-items:center; padding:24px}}
/* Fixed height (not max-height/shrink-to-fit) and flex-column, so
   shrinking the photo never moves the sheet's own top edge or its
   anchored bottom — only the internal split between photo and text
   changes, with the text sliding up into the space the photo gives up.
   The photo is a plain flex item (no position:sticky needed); the body
   is the one part that scrolls, in its own overflow region. */
.rec-modal{
  position:relative; background:var(--card); width:100%; max-width:600px;
  height:min(80vh, 640px); display:flex; flex-direction:column; overflow:hidden;
  border-radius:var(--radius-lg) var(--radius-lg) 0 0;
  border-top:1.5px solid var(--cat-color, var(--border-soft));
  transform:translateY(100%); transition:transform .28s cubic-bezier(.32,.72,0,1);
}
.rec-modal-backdrop.open .rec-modal{transform:translateY(0)}
@media (min-width:641px){
  .rec-modal{
    border-radius:var(--radius-lg); height:min(78vh, 640px); border:1.5px solid var(--cat-color, var(--border-soft));
    transform:translateY(16px) scale(.97); opacity:0;
    transition:transform .22s ease, opacity .22s ease;
  }
  .rec-modal-backdrop.open .rec-modal{transform:translateY(0) scale(1); opacity:1}
  /* Destination hero modal only — matches main's own guide-page max-width
     (the hero's actual rendered width), not the rec-detail modal's
     deliberately narrower 600px reading width. */
  .rec-modal.dest-modal{max-width:1180px}
}
/* Same circular size as the star/check/pin badges (24px) — sits in the
   right badge-row slot the maps-pin badge used to occupy, since the
   modal moves that link down next to the title instead. */
.rec-modal-close{
  border:1.5px solid var(--ink); cursor:pointer; background:rgba(255,255,255,.8); color:var(--ink-soft);
  font-size:16px; line-height:1; padding:0;
}
.rec-modal-photos:not(.no-photo) .rec-modal-close{background:rgba(20,28,30,.55); color:#fff; border-color:rgba(255,255,255,.5)}
/* Height is set in JS (openRecModal) as a plain pixel value, not
   aspect-ratio — aspect-ratio left on this element fought the JS-driven
   shrink once height became a definite JS value: with width stretched by
   the flex column parent, the browser resolved the aspect-ratio by also
   pulling width along to keep 4:3, so the shrink-to-scroll gesture
   shrank the photo's width along with its height instead of just height. */
.rec-modal-photos{
  position:relative; flex:0 0 auto; overflow:hidden;
  background:var(--cat-pastel, var(--accent-soft));
}
.rec-modal-photos:not(.no-photo) .badge, .rec-modal-photos:not(.no-photo) .maps-btn{
  background:rgba(20,28,30,.55); color:#fff; border-color:rgba(255,255,255,.5);
}
.rec-modal-photos:not(.no-photo) .maps-btn:hover{background:var(--accent-dark); color:#fff; border-color:var(--accent-dark)}
.rec-modal-body{flex:1 1 auto; overflow-y:auto; padding:20px}
.rec-modal-title-row{display:flex; align-items:baseline; justify-content:space-between; gap:12px}
.rec-modal-name{
  margin:0; font-family:var(--font-display); font-weight:700;
  font-size:1.5rem; color:var(--accent-dark);
}
.rec-modal-maps{
  flex:0 0 auto; font-size:.78rem; font-weight:700; color:var(--accent-dark);
  white-space:nowrap;
}
.rec-modal-maps:hover{text-decoration:underline}
.rec-modal-hood{
  font-family:var(--font-mono); font-size:.75rem; color:var(--muted); text-transform:uppercase; letter-spacing:.03em;
  margin:4px 0 14px;
}
.rec-modal-note{margin:0; font-size:1rem; line-height:1.55; color:var(--ink-soft)}
/* Only relevant in the destination hero modal, where a .rec-modal-note
   (the lead paragraph) can be followed by .tips — .rec-modal-note has no
   bottom margin normally (a rec's own note is always the last thing in its
   modal), so without this the two would sit flush together. */
.rec-modal-note + .tips{margin-top:10px}

.pager{display:flex; align-items:center; justify-content:center; gap:16px; margin:20px 0 8px}
.pager-btn{
  padding:7px 16px; border-radius:999px; border:1.5px solid var(--border-soft); background:var(--card);
  font-size:.85rem; color:var(--ink-soft); cursor:pointer;
}
.pager-btn:hover:not(:disabled){background:var(--accent-dark); color:#fff; border-color:var(--accent-dark)}
.pager-btn:disabled{opacity:.35; cursor:default}
.pager-status{font-size:.8rem; color:var(--muted)}

.empty, .loading{color:var(--muted); padding:40px 0; text-align:center}

.map-view{
  display:none; height:560px; border-radius:var(--radius-lg); overflow:hidden; border:1.5px solid var(--border-soft);
}
.map-popup{font-family:var(--font-body); min-width:180px}
.map-popup strong{font-family:var(--font-display); font-weight:700; font-size:1rem; color:var(--ink)}
.map-popup-meta{font-family:var(--font-mono); font-size:.7rem; color:var(--muted); text-transform:uppercase; letter-spacing:.03em; margin-top:2px}
.map-popup p{font-size:.82rem; color:var(--ink-soft); margin:6px 0}
.map-popup a{color:var(--accent-dark); font-size:.8rem}

/* ---- landing: logged-out marketing page at "/" (Stage 1 of the homepage
   plan in BACKLOG.md). Static content — no live data, no auth. Classes are
   port of Style/homepage-mockup.html's logged-out view, renamed to this
   codebase's convention and re-based on the tokens above instead of the
   mockup's own standalone :root block. ---- */
.landing-page{padding:min(2vh,20px) 0 20px}

.landing-hero{text-align:center; margin:0 auto 40px}
.landing-hero-title{
  font-family:var(--font-display); font-weight:700; letter-spacing:-.02em;
  font-size:clamp(34px,6vw,58px); line-height:1.05; color:var(--ink); margin:0 0 22px;
}
.landing-hero-highlight{color:var(--accent-dark)}
/* The underline highlight lives on this inner span (just "reckon"), not on
   .landing-hero-highlight itself (the whole "Share what you reckon."
   phrase) — the phrase stays blue throughout, only the one word gets the
   marker-stroke treatment underneath it. */
.landing-hero-highlight-word{position:relative; display:inline-block}
.landing-hero-highlight-word::after{
  content:""; position:absolute; left:0; bottom:4px; width:100%; height:8px;
  background:#9CC5F2; z-index:-1; transform:rotate(-1deg);
}
.landing-hero-sub{
  font-size:1.05rem; line-height:1.5; color:var(--ink-soft); max-width:640px;
  margin:0 auto 36px; font-weight:500;
}
.landing-hero-ctas{display:flex; justify-content:center; align-items:center; gap:16px; flex-wrap:wrap}
.landing-btn-primary, .landing-btn-demo{
  font-family:var(--font-display); font-weight:700; font-size:1.05rem; letter-spacing:.01em;
  padding:15px 30px; border-radius:var(--radius-md); border:2px solid var(--ink);
  display:inline-flex; align-items:center; justify-content:center; gap:8px; cursor:pointer;
  transition:all .15s ease; min-width:290px; box-sizing:border-box;
}
.landing-btn-primary{background:var(--accent-dark); color:#fff; box-shadow:var(--shadow-md)}
.landing-btn-primary:hover{transform:translate(2px,2px); box-shadow:3px 3px 0px var(--ink); background:#1B3E96}
.landing-btn-demo{background:var(--card); color:var(--accent-dark); border-color:var(--accent-dark); box-shadow:var(--shadow-md-brand)}
.landing-btn-demo:hover{transform:translate(2px,2px); box-shadow:3px 3px 0px var(--accent-dark)}

.landing-pillars{
  background:var(--card); border:2px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md-brand); padding:32px; margin-bottom:48px;
  display:grid; grid-template-columns:repeat(3,1fr); gap:28px;
}
@media (max-width:860px){.landing-pillars{grid-template-columns:1fr; gap:20px; padding:24px}}
.landing-pillar{
  padding-left:16px; border-left:3px solid var(--line);
  transition:border-color .15s ease;
}
.landing-pillar:hover{border-left-color:var(--accent-dark)}
.landing-pillar-tag{
  font-family:var(--font-mono); font-size:.7rem; font-weight:700; letter-spacing:.08em;
  color:var(--accent-dark); margin-bottom:8px;
}
.landing-pillar-title{font-family:var(--font-display); font-weight:700; font-size:1.25rem; color:var(--ink); margin:0 0 8px}
.landing-pillar-desc{font-size:.9rem; line-height:1.5; color:var(--ink-soft); margin:0}

.landing-demo{
  background:var(--card); border:2px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lg-brand); overflow:hidden; margin-bottom:40px;
}
.landing-demo-bar{
  background:#F8FAFC; border-bottom:2px solid var(--ink); padding:14px 20px;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
}
.landing-demo-lights{display:flex; gap:8px}
.landing-demo-light{width:12px; height:12px; border-radius:50%; border:1.5px solid var(--ink)}
.landing-demo-url{
  font-family:var(--font-mono); font-size:.75rem; font-weight:700; color:var(--accent-dark);
  background:var(--card); border:1.5px solid var(--ink); border-radius:999px; padding:4px 16px;
}
.landing-demo-count{font-family:var(--font-mono); font-size:.7rem; font-weight:700; color:var(--accent-dark)}
.landing-demo-body{display:grid; grid-template-columns:1.1fr .9fr; min-height:340px}
@media (max-width:768px){.landing-demo-body{grid-template-columns:1fr} .landing-demo-map{display:none}}
.landing-demo-cards{padding:22px; display:flex; flex-direction:column; gap:14px; background:#FAFCFF}
.landing-demo-rec{
  background:var(--card); border:1.5px solid var(--ink); border-radius:var(--radius-sm);
  padding:14px 16px; box-shadow:3px 3px 0px var(--brand-light); transition:transform .15s ease;
}
.landing-demo-rec:hover{transform:translateX(4px); border-color:var(--accent-dark)}
.landing-demo-rec-head{display:flex; justify-content:space-between; align-items:baseline; gap:8px; margin-bottom:4px; flex-wrap:wrap}
.landing-demo-rec-name{font-family:var(--font-display); font-weight:700; font-size:.95rem; color:var(--ink)}
.landing-demo-rec-cat{
  font-family:var(--font-mono); font-size:.68rem; font-weight:700; letter-spacing:.04em;
  color:var(--accent-dark); background:var(--brand-light); padding:2px 8px; border-radius:4px; white-space:nowrap;
}
.landing-demo-rec-note{font-size:.82rem; line-height:1.45; color:var(--ink-soft); margin:0}
/* Same gold as --fav / badge-star elsewhere on the site. */
.landing-demo-star{color:var(--fav); display:inline-block; vertical-align:-2px}
.landing-demo-star svg{display:block}
/* A real Leaflet map — same CARTO Voyager (no-labels variant, see the
   tileLayer comment in renderLanding()) tile source and gold favourite-star
   markers as the actual guide pages — not a drawn stand-in, so the real
   coastline shape shows through. Non-interactive (see the init options in
   renderLanding()): it's decorative chrome in the demo window, not
   something a visitor should be able to pan/zoom away from. */
.landing-demo-map{border-left:2px solid var(--ink)}
.landing-demo-map .leaflet-control-attribution{font-size:.62rem; background:rgba(255,255,255,.75)}
/* A Leaflet divIcon (see renderLanding()), positioned by real lat/lng. A
   custom `className` passed to L.divIcon REPLACES Leaflet's default
   'leaflet-div-icon' class rather than adding to it — confirmed via the
   rendered element's actual classList — so this marker never had that
   class (or its baked-in white background/border) to fight in the first
   place; it was just falling back to the page's inherited ink-black
   color. Single-class selector, no leaflet-div-icon needed. */
.landing-demo-star-marker{color:var(--fav); display:flex; align-items:center; justify-content:center}
.landing-demo-star-marker svg{filter:drop-shadow(0 1px 1px rgba(14,23,38,.4))}
.landing-demo-tooltip{
  background:var(--card); border:1.5px solid var(--ink); box-shadow:2px 2px 0px var(--accent-dark);
  border-radius:6px; padding:3px 9px; font-family:var(--font-mono); font-size:.68rem; font-weight:700;
  color:var(--ink); white-space:nowrap;
}
.landing-demo-tooltip::before{display:none}

/* Full-bleed: main has its own max-width + padding for the rest of the
   page, but the footer needs to run edge-to-edge regardless of viewport
   size. Negative margins (not width:100vw, which double-counts the
   scrollbar) pull it to the viewport edges; its own padding keeps the
   text off the screen edge on mobile. */
.site-footer{
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  border-top:2px solid var(--line); padding:18px 20px 25px; text-align:center; background:var(--card);
}
.site-footer-word{font-family:var(--font-display); font-weight:700; letter-spacing:.02em; color:var(--accent-dark)}

@media (max-width:640px){
  .landing-hero-title{font-size:clamp(30px,9vw,40px)}
  .landing-hero-sub{font-size:.95rem}
}
