﻿/* ── Reset & base ── */
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0;height:100%;overflow:hidden}
body{
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  font-size:15px;line-height:1.4;color:#1a1a2e;background:#f5f5f7;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  display:flex;flex-direction:column;height:100vh;height:100dvh;width:100%;
}
:root{
  --muted:#888;--bg:#f5f5f7;--card:#fff;--border:#e8e8ed;
  --primary:#f59e0b;--green:#22c55e;--red:#ef4444;--blue:#3b82f6;
}
button{font-family:inherit}

/* Compacted 2026-10-05: 45px -> 39px, padding only. Font sizes unchanged, so the
   bar shortens without the type shrinking. Applies to every page. */
#topbar{
  flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
  padding:5px 12px;background:#fff;border-bottom:1px solid var(--border);
  z-index:100;position:sticky;top:0;
}
.tb-left{display:flex;align-items:center;gap:4px}
.tb-right{display:flex;align-items:center;gap:2px}
.tb-btn{
  background:none;border:none;font-size:15px;cursor:pointer;color:#1a1a2e;
  padding:4px 8px;border-radius:8px;line-height:1;white-space:nowrap;
}
.tb-btn:active{background:#f0f0f5}
.tb-title{font-size:20px;font-weight:800;color:#1a1a2e;letter-spacing:-.5px}
.tb-sub{font-size:11px;color:var(--muted);margin-left:2px;font-weight:500}

/* ── Search overlay ── */
.search-overlay{
  position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:200;
  display:flex;flex-direction:column;
}
.search-bar{
  display:flex;align-items:center;gap:8px;padding:12px;
  background:#fff;border-bottom:1px solid var(--border);
}
.search-bar #searchInput{
  flex:1;border:1.5px solid var(--border);border-radius:10px;
  padding:10px 14px;font-size:15px;outline:none;background:#fafafa;
}
.search-bar #searchClose{background:none;border:none;font-size:20px;cursor:pointer;color:#555;padding:4px 8px;border-radius:8px}
.search-bar #searchClose:active{background:#f0f0f5}
.search-results{flex:1;overflow-y:auto;padding:0 12px}
.search-hit{
  display:flex;align-items:center;gap:10px;padding:10px 4px;border-bottom:1px solid #f0f0f0;
  cursor:pointer;
}
.search-hit:active{background:#f5f5f7}
.search-hit-name{font-size:14px;font-weight:600}
.search-hit-meta{font-size:11px;color:var(--muted)}
.search-hit-close{background:none;border:none;color:var(--muted);cursor:pointer;font-size:13px;padding:2px 6px}

/* ── City menu ── */
.city-menu{
  /* 3000 clears the topbar (100), Leaflet's map pane (400) and the map dialog
     (1200). At the old 190 the map painted over this on the Map page, so the
     options were visible in the DOM but untappable. */
  position:fixed;top:56px;right:12px;z-index:3000;background:#fff;
  border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,.15);
  overflow:hidden;min-width:140px;
}
.city-opt{
  display:block;width:100%;text-align:left;background:none;border:none;
  padding:12px 16px;font-size:14px;font-weight:600;cursor:pointer;color:#1a1a2e;
}
.city-opt:active{background:#f5f5f7}

/* ── Main content area ── */
#main{
  flex:1;min-height:0;overflow:hidden;position:relative;
}
.view{display:flex;flex-direction:column;height:100%;min-height:0}
/* Me view scrolls: plain .view has no internal scroller, so taller content
   (profile card + rules + checkins + buttons) was clipped and unreachable. */
.view-me-scroll{
  height:100%;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;
}

/* Compacted 2026-10-05: 62px -> 52px, padding only (tab icons unchanged). The
   safe-area inset stays, just 4px smaller, so the bar still clears the iPhone
   home indicator — zero would put icons under the gesture line. */
#tabbar{
  position:fixed;left:0;right:0;bottom:0;z-index:100;
  display:flex;align-items:stretch;justify-content:space-around;
  background:#fff;border-top:1px solid var(--border);padding:2px 0 4px;
  padding-bottom:calc(env(safe-area-inset-bottom,4px) + 4px);
}
/* Photo page only: the user asked for the 5-tab bar to be gone there. Hidden
   via a class toggled in render(), and body padding removed with it so the
   footer is not left floating above a phantom gap. */
#tabbar.hidden{display:none}
body.photo-view-open{padding-bottom:0}
/* The app's own #topbar (search / language / city / mode) is position:sticky
   with z-index:100, which sat ON TOP of the photo page and made its own back
   and share buttons unclickable. The photo page carries its own top bar, so the
   global one is hidden there — same scope as the tab bar. */
body.photo-view-open #topbar{display:none}
/* Reserves room for the fixed #tabbar. MUST equal its MEASURED height, so re-read
   `getBoundingClientRect().height` on #tabbar whenever its padding changes:
   too large and the reclaimed space never reaches the content, too small and
   content hides behind the bar. */
body{
  padding-bottom:52px;
}
.tab{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;background:none;border:none;cursor:pointer;color:var(--muted);
  /* 4px -> 2px vertical padding only; icon and label sizes unchanged. */
  padding:2px 0;border-radius:8px;border-bottom:2px solid transparent;
}
.tab .tab-icon{font-size:22px;line-height:1}
.tab .tab-label{font-size:11px;font-weight:600}
.tab.active{color:#1a1a2e;border-bottom-color:var(--primary)}
.tab:active{background:#f5f5f7}

/* ── Cards ── */
.login-card,.profile-card,.points-card{
  background:var(--card);margin:12px 14px;border-radius:14px;
  padding:14px 16px;box-shadow:0 1px 4px rgba(0,0,0,.06);
}
.login-card h3{margin:0 0 8px;font-size:17px}
.login-card p{margin:0 0 14px;font-size:13px;color:var(--muted);line-height:1.4}
.vs{font-size:11px;color:var(--muted);text-align:center;margin:6px 0 0;
  padding:6px;background:#f5f5f7;border-radius:6px}

/* ── Points card ── */
.pc-bar{height:8px;background:#eee;border-radius:4px;overflow:hidden;margin:10px 0 6px}
.pc-fill{height:100%;background:linear-gradient(90deg,#f59e0b,#fb923c);border-radius:4px;transition:width .4s}
.pc-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.pc-pts{font-size:36px;font-weight:800;color:#1a1a2e;line-height:1;letter-spacing:-1px}
.pc-sub{font-size:12px;color:var(--muted);font-weight:500;margin-top:2px}
.pc-next{font-size:12px;color:#555;margin-top:4px}
.points-rules{font-size:12px;border-top:1px solid var(--border);margin-top:8px;padding-top:8px}
.pr-row{display:flex;justify-content:space-between;padding:3px 0;font-size:12px}
.pr-row span{color:var(--muted)}
.pr-row b{color:#1a1a2e;font-weight:700}
/* A category no feature awards yet: dimmed, and labelled rather than shown as
   "+0" so it reads as unavailable instead of as a zero the user failed to earn. */
.pr-row-off{opacity:.55}
.pr-row-off b{color:var(--muted);font-weight:600;font-size:11px}
.pr-row-survey{margin-top:6px;padding-top:6px;border-top:1px dashed var(--border)}

/* Total + the rules icon beside it. The button is 30px (past the 24px minimum
   comfortable tap size) with a 26px visible circle, so the hit area is larger
   than it looks without pushing the number off-centre. */
.pc-pts-row{display:flex;align-items:center;gap:6px}
.pc-rules-btn{
  width:30px;height:30px;flex:0 0 auto;border-radius:50%;
  border:1.5px solid var(--border);background:#f5f5f7;
  display:flex;align-items:center;justify-content:center;padding:0;
}
.pc-rules-btn:active{background:#e8e8ec}
.pc-rules-icon{font-size:15px;font-weight:800;color:var(--muted);line-height:1}

/* Rules popup internals. */
.rules-title{font-size:14px;font-weight:800;color:#1a1a2e;margin-bottom:2px}
.rules-note{font-size:11px;color:var(--muted);margin-top:8px;padding-top:6px;border-top:1px dashed var(--border)}

/* Ranking page: rules button pinned at the bottom. */
.view-rank{overflow:hidden}
.rank-rules-footer{
  flex:0 0 auto;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));
  border-top:1px solid var(--border);background:#fff;
}
.rank-rules-btn{width:100%}

/* ── Profile card ── */
.nm{font-size:14px}
.provider-badge{
  font-size:11px;color:var(--muted);background:#f5f5f7;border-radius:6px;
  padding:3px 8px;display:inline-block;margin-top:4px;
}
.logout-btn{
  margin-top:8px;background:none;border:1px solid var(--border);border-radius:8px;
  padding:6px 16px;font-size:13px;cursor:pointer;color:#555;
}
.logout-btn:active{background:#f5f5f7}

/* ── Section headers ── */
.section-h{
  font-size:13px;font-weight:600;color:var(--muted);
  padding:6px 14px 4px;letter-spacing:.3px;
}

/* ── Action row ── */
.action-row{display:flex;gap:8px;padding:8px 14px 14px;flex-wrap:wrap}
.btn{
  border-radius:10px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer;
  border:1.5px solid transparent;line-height:1.2;
}
.btn-primary{background:var(--primary);color:#fff;border-color:var(--primary)}
.btn-primary:active{background:#d97706}
.btn-ghost{background:none;border-color:var(--border);color:#1a1a2e}
.btn-ghost:active{background:#f5f5f7}
.btn:disabled{opacity:.5;cursor:default}

/* ── Modal ── */
.modal-bg{
  position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9999;
  display:flex;align-items:center;justify-content:center;padding:20px;
  opacity:0;transition:opacity .15s ease;
}
.modal-bg.show{opacity:1}
.modal-bg>div{
  position:relative;
  background:#fff;border-radius:16px;padding:20px;max-width:360px;width:100%;
  box-shadow:0 8px 30px rgba(0,0,0,.2);
}
.modal-close{
  position:absolute;top:12px;right:16px;background:none;border:none;
  font-size:22px;cursor:pointer;color:#888;line-height:1;
}
.modal-close:active{color:#333}

/* ── Empty state ── */
.empty{padding:20px;text-align:center;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px}
.empty-icon{font-size:40px;opacity:.5}
.empty-text{font-size:13px;color:var(--muted)}
.empty-small{font-size:12px}

/* ── Map section ── */
/* 25% of viewport height, per spec. No min-height: one silently floored the map
   to 140px on short screens (320x480 wanted 120), so the ratio only held above
   ~560px tall. */
.map-section{
  flex:0 0 25vh;width:100%;position:relative;
  background:#e8e8e8;border-radius:12px;overflow:hidden;margin:0;
  border-top-left-radius:0;border-top-right-radius:0;
}

/* ── Routes view heading ── */
.routes-view{display:flex;flex-direction:column;height:100%;min-height:0;background:#f5f5f7}
/* Compacted 2026-10-05: 65px -> 45px. Padding 10px -> 5px plus the select's own
   height 44 -> 34; text sizes unchanged so it stays a comfortable target. */
.routes-heading{
  flex:0 0 auto;padding:5px 14px;background:#fff;border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:10px;
}
.route-select{
  /* flex-basis 0 + min-width:0 so the dropdown yields space to the rating bar
     instead of claiming its max-width and crushing the stars. */
  flex:1 1 0;min-width:0;max-width:none;height:34px;padding:5px 30px 5px 12px;
  font-size:14px;font-weight:600;
  border:1.5px solid var(--border);border-radius:10px;background:#fff;color:#1a1a2e;
  appearance:none;-webkit-appearance:none;outline:none;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center;
}
.route-select:focus{border-color:var(--green);box-shadow:0 0 0 3px rgba(34,197,94,.15)}
.route-select option{padding:8px;font-weight:400}
/* Average beside the dropdown: number on top, stars below. Matches the select's
   height; no background, no border. */
.routes-heading-rate{
  flex:0 0 auto;height:34px;display:inline-flex;align-items:center;
  padding:0 2px;white-space:nowrap;
}
.rh-rate{display:inline-flex;flex-direction:column;align-items:center;gap:1px}
.rh-num{font-size:15px;font-weight:800;color:#b45309;line-height:1.05}
.rh-stars{display:inline-flex;gap:0;line-height:1}
.rh-star{font-size:15px;line-height:1;color:#dcdce4;margin:0 -1px}
/* The rating-star yellow, named so the map can use the EXACT same value for a
   number on a white marker instead of guessing a similar-looking amber. */
.rh-star.on{color:var(--star-yellow,#f5a623)}

/* ── Stops section ── */
.stops-section{flex:1;min-height:0;overflow-y:auto;padding:0 0 14px}
/* Compacted 2026-10-05: 55px -> 49px, padding only. The row's real height is the
   32px star touch target below, so padding was the only slack to take. */
.stops-head{padding:5px 14px 3px;border-bottom:1px solid var(--border)}
.stops-route-name-row{display:flex;align-items:center;gap:8px}
.stops-head-spacer{flex:1 1 auto}
.stops-meta{
  font-size:11px;color:var(--muted);
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.stops-list{padding:6px 14px 14px}

/* ── Stop row ── */
.stoprow{display:flex;align-items:center;gap:8px;padding:10px 0;border-bottom:1px solid #f0f0f0}
.stoprow:last-child{border-bottom:none}
.stop-no{
  flex:0 0 24px;height:24px;min-width:24px;border-radius:50%;
  background:var(--primary);color:#fff;font-size:11px;font-weight:700;border:1.5px solid #1a1a1a;
  display:flex;align-items:center;justify-content:center;
}
.stop-no.checked-today{background:var(--green)}
.stop-no.checked-past{background:#eab308;color:#1a1a2e}
.stop-no.red-state{background:var(--red)}
/* Thumbnail of the point's big photo, in the route-page stop row */
.stop-thumb{
  flex:0 0 auto;width:40px;height:40px;border-radius:8px;overflow:hidden;
  background:#eee;display:block;
}
.stop-thumb-img{width:100%;height:100%;object-fit:cover;display:block}
/* Point name. Owns the row's remaining width (flex:1) and is capped at 2 lines.
   Because the average and movie tags moved out, the widest name in the dataset
   fits on one line; the 2-line clamp is a safety net, not the normal case. */
/* The name cell holds the point name AND, on HKG01, its film line. One cell
   rather than two siblings so the columns to the right (average, button) keep
   their x positions and gaps — the film line must not push them. */
.stop-name-cell{
  flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px;
}
.stop-name{
  flex:0 1 auto;min-width:0;font-size:14px;font-weight:600;color:#1a1a2e;line-height:1.3;
  padding-top:1px;
  display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:2;line-clamp:2;overflow:hidden;
}
/* HKG01 film line. font-size 10px is the largest that keeps a two-film line on
   one row inside the name column at 390px; .stop-name is 14px. Single line,
   ellipsised — it must never wrap, because a wrap grows the row and moves every
   row below it, which is the layout drift the user asked to avoid. */
.stop-movies{
  flex:0 0 auto;display:flex;flex-wrap:nowrap;gap:4px;min-width:0;overflow:hidden;
  font-size:10px;line-height:13px;color:var(--muted);
}
.stop-movie{
  display:inline-flex;align-items:baseline;gap:2px;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.stop-movie-year{font-size:9px;opacity:.75}
/* Thumbnail + movie tags as one column. The tags stack UNDER the thumbnail in
   a single row (flex-wrap:nowrap), so HKG01 rows gain height, not width, and the
   name keeps its space. */
.stop-media{
  flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-start;gap:3px;
}
/* Point average, immediately left of the check-in button.
   Sized to FIT the 30px check-in button height while making the number as
   large as that allows: number 18px (18x1.05=18.9) + star row 11px = ~29.9px.

   The width is driven by the 5-star row, not the number — at 15px stars the
   badge measured 36.9px wide even scaled down to .72. Dropping the stars to
   11px keeps the badge at the same ~37px width while letting the number grow
   from an effective 10.8px to a full 18px. That is what buys the name its room.
   Real font-size, NOT transform:scale(): a transform paints larger but still
   reserves the original layout box, so the badge would keep stealing width. */
.stop-avg{display:inline-flex;flex:0 0 auto;pointer-events:none}
.stop-avg .rh-num{font-size:18px;line-height:1.05}
.stop-avg .rh-star{font-size:11px}
.stop-avg .rh-rate{gap:0}
.stop-coord{flex:0 0 auto;font-size:11px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.stop-checkin-btn{
  flex:0 0 auto;background:#fff;border:1px solid var(--border);border-radius:8px;
  padding:6px 12px;font-size:12px;font-weight:600;color:var(--primary);cursor:pointer;
  white-space:nowrap;font-family:inherit;
}
.stop-checkin-btn:active{background:#f5f5f7}
/* ── Check-in button: ORANGE fill + WHITE text (2026-10-05) ──
   Applied to the BASE rule, so it covers the never-checked (white) state on
   both the route stop row and the point page — the two states that were
   white-on-white and read as "not a button". The state modifiers below
   (.checked green, .yellow amber, .red red, .private purple) are unchanged and
   still win, because they come later and are equally specific.
   --primary (#f59e0b) is the project's orange; #fff is white. */
.stop-checkin-btn,
.point-checkin-btn{
  background:var(--primary);border-color:var(--primary);color:#fff;
}
.stop-checkin-btn:active,
.point-checkin-btn:active{background:#d97706;border-color:#d97706}
.stop-checkin-btn.checked{background:var(--green);color:#fff;border-color:var(--green)}
.stop-checkin-btn.yellow{background:#eab308;border-color:#eab308;color:#1a1a2e}
.stop-checkin-btn.red{background:var(--red);color:#fff;border-color:var(--red)}
/* Private mode: any check-in reads as done in PURPLE, matching the map marker
   colour (#a855f7 in _routesRenderMarkers). Applies to BOTH the route row and
   the point page button — they share checkinBtnStyle(). */
.stop-checkin-btn.private,
.point-checkin-btn.private{background:#a855f7;color:#fff;border-color:#a855f7}
.stop-checkin-btn.private:active,
.point-checkin-btn.private:active{background:#9333ea}
.stop-checkin-btn.has-photo{color:#f59e0b}

/* ── Map popup buttons ── */
.point-popup-view,.point-popup-checkin{margin-top:4px}
.point-popup-checkin:disabled{opacity:.5;cursor:default}
/* never-checked (white) popup checkin button: white fill needs outline so it's visible on white popup bg */
.point-popup-checkin-outline{background:#fff;border:1.5px solid #1a1a1a;color:#555}
.point-popup-checkin-past{background:#eab308;border-color:#eab308;color:#1a1a2e}
/* point page checkin button: red state (public mode, unchecked stop on checked route) */
.point-checkin-btn-red{background:var(--red);border-color:var(--red);color:#fff}

/* point page checkin button: never-checked state (white fill + outline) */
.point-checkin-btn-outline{background:#fff;border:1.5px solid #1a1a1a;color:#555}

/* ── Map circle marker legend ── */
.marker-legend{
  position:absolute;bottom:10px;left:10px;z-index:1000;background:rgba(255,255,255,.9);
  border-radius:8px;padding:6px 10px;font-size:11px;color:#555;border:1px solid var(--border);
  box-shadow:0 1px 4px rgba(0,0,0,.1);display:flex;gap:10px;align-items:center;
}
.marker-legend-item{display:flex;align-items:center;gap:4px}
.marker-dot{width:10px;height:10px;border-radius:50%;border:1.5px solid #333}
.marker-dot-white{background:#fff}
.marker-dot-green{background:var(--green)}
.marker-dot-red{background:var(--red)}

/* ── Point detail page ── */
.point-view{display:flex;flex-direction:column;height:100%;min-height:0;background:#f5f5f7}
/* Compacted 2026-10-05: 49px -> 37px, padding only. */
.point-topbar{
  display:flex;align-items:center;justify-content:space-between;
  padding:6px 12px;background:#fff;border-bottom:1px solid var(--border);
  position:sticky;top:0;z-index:5;
}
.point-back{
  background:none;border:none;font-size:15px;color:var(--primary);cursor:pointer;
  font-family:inherit;padding:2px 8px;border-radius:8px;
}
.point-back:active{background:#f5f5f7}
/* Full route name in the topbar, beside the back button */
.point-route-name{
  font-size:13px;font-weight:700;color:#1a1a2e;text-align:right;
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.point-main{flex:1;min-height:0;overflow-y:auto;padding:0}
/* 25% of viewport height, per spec. No min-height floor: it would override the
   ratio on short screens. */
.point-bigphoto{
  position:relative;width:100%;height:25vh;background:#e8e8e8;
  overflow:hidden;border-radius:0 0 12px 12px;
}
.point-bigimg{width:100%;height:100%;object-fit:cover;display:block}
.point-bigphoto-empty-wrap{background:#e8e8e8}
.point-bigphoto-empty{
  position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;color:#aaa;font-size:14px;
}
.point-bigphoto-icon{font-size:40px;margin-bottom:8px}

/* Small photo in the top-right corner of the big photo. The button overlays
   the whole thumb so the tap target is thumb-sized, not the 1px image. */
.point-corner{
  position:absolute;top:8px;right:8px;width:64px;height:64px;
  border-radius:8px;overflow:hidden;z-index:2;
  border:2px solid rgba(255,255,255,.9);box-shadow:0 1px 6px rgba(0,0,0,.3);
  background:#e8e8e8;
}
.point-corner-img{width:100%;height:100%;object-fit:cover;display:block}
.point-corner-btn{
  position:absolute;inset:0;width:100%;height:100%;
  background:none;border:none;padding:0;cursor:pointer;
}
.point-corner-btn:active{background:rgba(0,0,0,.15)}

/* HKG01 movie tags, overlaid on the TOP-LEFT of the big photo. They used to sit
   in the name row, where they squeezed the point name into a 2-line column.
   Sits below the corner thumbnail's z-index band and above the photo itself, so
   it never blocks the top-right thumbnail or the bottom credit gradient. */
.point-movies-overlay{
  position:absolute;top:8px;left:8px;z-index:2;
  display:flex;flex-direction:column;align-items:flex-start;gap:4px;
  max-width:min(58%,calc(100% - 88px));  /* leave room for the 64px corner thumb */
  pointer-events:none;
}
.point-movies-overlay .point-movie-tag{
  background:rgba(0,0,0,.62);border-color:rgba(255,255,255,.35);color:#fff;
  text-shadow:0 1px 2px rgba(0,0,0,.5);
  max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* The year must inherit the overlay's white text. .point-movie-year sets
   var(--muted) for the light-background inline tag, which on the dark photo
   badge rendered low-contrast grey and looked like it sat outside the pill. */
.point-movies-overlay .point-movie-year{color:#fff;opacity:.85}

/* Credits for BOTH photos, overlaid at the bottom of the big photo. */
.point-photo-credits{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:16px 10px 7px;
  background:linear-gradient(to top,rgba(0,0,0,.62),rgba(0,0,0,.28) 55%,rgba(0,0,0,0));
}
.point-credit{
  font-size:10.5px;line-height:1.35;color:#fff;
  text-shadow:0 1px 2px rgba(0,0,0,.5);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.point-credit a{color:#fff;text-decoration:underline}
.point-credit-src{opacity:.9}

.point-info{padding:12px 14px}

/* ── 電影取景 / Movies shot here ── */
/* HKG01 movie tag. Base style is for the light background; the overlay on the
   big photo darkens it (see .point-movies-overlay below). */
.point-movie-tag{
  display:inline-block;font-size:12px;font-weight:600;line-height:1.4;color:#8a5a12;
  background:#fff7e6;border:1px solid #ffe0a3;border-radius:9px;padding:1px 7px;
  white-space:nowrap;
}
.point-movie-year{color:var(--muted);font-weight:400;font-size:11px}
/* Name row: prev, no, name(+English under it), next, avg | share, upload.
   (1)-(5) hard left, (6)-(7) hard right — the spacer eats the slack. A real
   element, not ::after: an empty pseudo-element gets no flex base here and left
   30px unused. `margin-left:auto` on #pointShareBtn would strand the "+" instead. */
.point-name-row{display:flex;align-items:center;gap:4px}
.point-row-spacer{flex:1 1 auto;min-width:0;align-self:stretch}
/* Name block. Never grows — that space belongs to the spacer — but still shrinks,
   so a long name wraps instead of shoving the trailing controls off the row.
   Keep `auto` basis: flex-basis:0 collapsed this wrapping block to 0px wide. */
.point-head{
  flex:0 1 auto;min-width:0;max-width:100%;
  --point-name-lh:1.3;
  --point-name-fs:18px;
  /* 3 lines: up to 2 for a wrapped Chinese name + 1 for the English name below. */
  --point-name-3lh:calc(var(--point-name-fs) * var(--point-name-lh) * 3); /* 70.2px */
  display:flex;flex-wrap:wrap;align-items:center;
  gap:2px 8px;
}
/* English name now lives INSIDE the name block, directly under the Chinese name,
   so it costs no extra row. As a flex item it shares the block's line rather than
   sitting on a line of its own, which is what saves the vertical space. */
.point-head .point-name-en{
  flex:1 0 100%;            /* force onto its own line inside the block */
  font-size:12px;            /* 13 -> 12: it is secondary text, and this row is tight */
  color:var(--muted);font-weight:400;line-height:1.25;
  margin-top:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* Number badge before the name: same circle as the route page's .stop-no. */
.point-no{
  flex:0 0 auto;width:24px;height:24px;min-width:24px;border-radius:50%;
  background:var(--primary);color:#fff;font-size:11px;font-weight:700;
  border:1.5px solid #1a1a1a;
  display:flex;align-items:center;justify-content:center;
}
.point-no.checked-today{background:var(--green)}
.point-no.checked-past{background:#eab308;color:#1a1a2e}
.point-no.red-state{background:var(--red)}
.point-name{
  font-size:var(--point-name-fs);font-weight:700;color:#1a1a2e;
  line-height:var(--point-name-lh);
  /* Names show all their words; the cap stays only as a safety net against a
     pathological single token that could never wrap. Raised to the 3-line budget
     so a 2-line name plus the English line underneath still both fit. */
  max-height:var(--point-name-3lh);
  display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:2;line-clamp:2;
  overflow:hidden;
}
.point-name-en{font-size:13px;color:var(--muted);font-weight:400;margin-top:2px}
.point-addr{font-size:13px;color:#555;margin-top:6px}
.point-desc{font-size:14px;color:#444;line-height:1.5;margin-top:8px}
.point-credits{
  margin-top:10px;padding:8px 10px;background:#f9f9fb;border-radius:6px;
  border:1px solid var(--border);font-size:10px;color:var(--muted);line-height:1.5;
}
.point-credit{margin-bottom:2px}
.point-credit:last-child{margin-bottom:0}
/* 50/50 split: 5-star user rating on the left half, check-in on the right.
   GRID, not flex. With `display:flex` + `flex:1 1 0` the two halves came out
   174px / 178px — a button's automatic minimum size is its min-CONTENT width,
   so the intrinsic widths of the star row and the label leak into the
   distribution and the split lands a few px off. `grid-template-columns:1fr 1fr`
   divides the track space exactly and ignores content, which is what "half the
   bar" means. flex-basis, min-width and padding trims were all tried first and
   none of them made it exact. */
.point-bottom{
  display:grid;grid-template-columns:1fr 1fr;align-items:center;
  padding:12px 14px;background:#fff;border-top:1px solid var(--border);gap:10px;
}
.point-bottom > .point-rank-btn{
  justify-content:center;
  min-width:0;max-width:none;
  padding-left:4px;padding-right:4px;
  overflow:visible;
}
.point-bottom > .point-checkin-btn{
  min-width:0;max-width:none;
  padding-left:6px;padding-right:6px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* the nested star row is a flex item; keep its min-content from adding to the
   grid track (harmless with 1fr, but it centres wrongly if it overflows) */
.point-bottom .stop-rank-stars{min-width:0;flex:0 1 auto}
.point-coords{
  font-size:11px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  flex:1;text-align:right;
}

/* Point-page rating button (wraps starBarHtml) */
.stop-rank-btn{
  flex:0 0 auto;background:#fff;border:1px solid var(--border);
  padding:5px 10px;font-size:12px;font-weight:600;color:var(--primary);cursor:pointer;
  white-space:nowrap;font-family:inherit;display:flex;align-items:center;gap:4px;
}
.stop-rank-btn:active{background:#f5f5f7}
/* Point average — now sits immediately after the name, inside the row's natural
   flow. `margin-left:auto` is REMOVED: that was what pushed it to the far right
   of the row. Size is unchanged (17px number, 12px star) so it reads exactly as
   before, just in the new position.

   Sized down from the earlier 26px number: at 26px this badge measured 63px
   wide and starved the name column — "終審法院（舊立法局大樓）" needs 216px on one
   line but only 137px was left, so long names still wrapped to 2 lines. At 17px
   the badge is ~41px, returning ~22px to the name, and a 2-line name becomes
   rare rather than routine. Real font-size (NOT transform:scale, which paints
   bigger but reserves the original layout box). */
.point-avg{
  display:inline-flex;flex:0 0 auto;
  pointer-events:none;
}
.point-avg .rh-num{font-size:17px;line-height:1.05}
.point-avg .rh-star{font-size:12px}
.point-avg .rh-rate{gap:0}
.stop-rank-route-btn{
  flex:0 0 auto;background:none;border:none;padding:4px 8px;cursor:pointer;
  display:flex;align-items:center;gap:4px;border-radius:6px;
}
.stop-rank-route-btn:active{background:#f0f0f5}
/* Inline 5-star bar: the user's own whole-star score. */
.stop-rank-stars{display:inline-flex;gap:1px;align-items:center}
.stop-rank-stars .rs{
  font-size:16px;line-height:1;color:#dcdce4;transition:color .1s,transform .1s;
  font-family:inherit;background:none;border:none;padding:0;margin:0;
}
.stop-rank-stars .rs.on{color:#f5a623}
/* Route header bar: 26px stars, tappable in place. The ★ glyph is ~21px, so the
   box hugs it (min-width:0, -2px margin, gap 0) — padding it out shows gaps.
   Height stays 32px for the touch target. */
.stop-rank-route-btn.tappable{padding:4px 6px;cursor:default;gap:0}
.stop-rank-route-btn .stop-rank-stars{gap:0}
.stop-rank-route-btn .stop-rank-stars .rs{
  font-size:26px;min-width:0;min-height:32px;margin:0 -2px;
  display:inline-flex;align-items:center;justify-content:center;
}
.stop-rank-route-btn .stop-rank-stars.tappable .rs{cursor:pointer;-webkit-tap-highlight-color:transparent}
.stop-rank-route-btn .stop-rank-stars.tappable .rs:hover{transform:scale(1.14)}
.stop-rank-route-btn .stop-rank-stars.tappable .rs:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:3px}
.stop-rank-route-btn .stop-rank-stars.tappable .rs.hover{color:#f5a623}

/* ── Point page inline star bar ──
   The point page rates inline with no popup, so it reuses the route header's
   packed/hugged star box but larger: 26px stars look small beside the ~39px
   check-in button, so this runs 34px with min-height 40px to match the button.
   MUST stay after the .stop-rank-route-btn rules above — they have identical
   specificity (0,3,0) and later-in-file wins, so placing this earlier
   silently leaves the stars at 26px.
   Packing: the ★ glyph is narrower than its font-size, so each star box is
   min-width:0 with a small negative horizontal margin and gap 0. Padding the
   box out instead renders as visible gaps between stars. Touch area is kept
   generous via min-height rather than by widening the glyph. */
.point-rank-btn{padding:0;cursor:default;gap:0}
.point-rank-btn .stop-rank-stars{gap:0}
.point-rank-btn .stop-rank-stars .rs{
  font-size:34px;min-width:0;min-height:40px;margin:0 -3px;
  display:inline-flex;align-items:center;justify-content:center;
}
.point-rank-btn .stop-rank-stars.tappable .rs{cursor:pointer;-webkit-tap-highlight-color:transparent}
.point-rank-btn .stop-rank-stars.tappable .rs:hover{transform:scale(1.12)}
.point-rank-btn .stop-rank-stars.tappable .rs:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:3px}
.point-rank-btn .stop-rank-stars.tappable .rs.hover{color:#f5a623}
.stop-share-btn{
  flex:0 0 auto;background:none;border:none;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  padding:7px;border-radius:8px;color:var(--muted);
}
.stop-share-btn:active{background:#f0f0f5;color:var(--primary)}
.stop-share-btn svg{display:block;line-height:0}
/* Prev/next point buttons, flanking the share button. Enlarged from 22px/2px 9px
   to 28px/4px 12px so they match the 34px star bar's presence in the bottom
   row and are comfortable thumb targets. */
.point-step{
  flex:0 0 auto;background:none;border:none;cursor:pointer;
  font-size:28px;line-height:1;color:var(--muted);
  padding:4px 12px;border-radius:8px;font-family:inherit;
}
.point-step:active{background:#f0f0f5;color:var(--primary)}
.point-step:disabled{opacity:.28;cursor:default}
.point-step:disabled:active{background:none;color:var(--muted)}

/* "+" in the point name row. Format/colour come from .point-checkin-btn plus the
   same inline background/border-color/color checkinBtnStyle() emits, so it tracks
   every check-in state (white/green/amber/red/private-purple) for free. Only SIZE
   is overridden: .btn's padding around a ＋ glyph made it far wider than the
   check-in button and broke the row's one-line fit. Don't set border-radius or
   border-width here either — doing so desynced it from .point-checkin-btn
   (8px/1.5px vs 10px/1px) and failed the colour-parity assertion. */
.point-add-btn{
  flex:0 0 auto;
  width:34px;height:34px;min-width:34px;
  padding:0;margin:0;
  display:inline-flex;align-items:center;justify-content:center;
  line-height:1;
}
.point-add-glyph{
  font-size:19px;font-weight:700;line-height:1;
  display:block;transform:translateY(-.5px);
}

/* ── Ranking popup ── */
.rank-modal .modal-card{
  max-width:420px;
}
/* ── 5-star rating bar (Google Maps style: whole stars only) ── */
.rank-stars{
  display:flex;gap:4px;justify-content:center;margin:4px 0 2px;
}
.rank-star{
  background:none;border:none;padding:2px;cursor:pointer;line-height:1;
  font-size:34px;color:#dcdce4;font-family:inherit;
  transition:color .08s,transform .08s;
  -webkit-tap-highlight-color:transparent;
}
.rank-star.on{color:#f5a623}
.rank-star.hover{color:#f5a623;transform:scale(1.12)}
.rank-star:active{transform:scale(.9)}
.rank-score{
  text-align:center;font-size:38px;font-weight:800;color:var(--primary);
  line-height:1;letter-spacing:-1px;margin:6px 0 2px;
}
.rank-score-unit{
  display:block;font-size:13px;color:var(--muted);font-weight:500;margin-top:2px;
}
.rank-hint{
  font-size:12px;color:var(--muted);text-align:center;margin-bottom:10px;
}

/* ── Route item (map popups) ── */
.route-item{display:flex;align-items:center;gap:8px;padding:6px 0}

/* ── Login card extras ── */
.login-card .google-btn,.login-card .apple-btn{
  display:block;width:100%;margin:6px 0;padding:12px;border-radius:12px;
  font-size:14px;font-weight:600;cursor:pointer;border:1.5px solid transparent;
}
.login-card .google-btn{background:#fff;color:#1a1a2e;border-color:#ddd}
.login-card .apple-btn{background:#fff;color:#1a1a2e;border-color:#ddd}
.login-card .google-btn:active,.login-card .apple-btn:active{background:#f5f5f7}
.login-card .email-field-wrap{margin-top:10px}
.login-card input[type=text]{
  width:100%;padding:10px 14px;border:1.5px solid var(--border);border-radius:10px;
  font-size:14px;outline:none;background:#fafafa;
}
.login-card input[type=text]:focus{border-color:var(--primary)}
.login-card .btn-primary{margin-top:10px;width:100%}
.login-card .field-alert{font-size:11px;color:#f59e0b;margin-top:4px;min-height:16px}

/* ── Search hit in results ── */
.search-hit{cursor:pointer;padding:10px 4px;border-bottom:1px solid #f0f0f0}
.search-hit:active{background:#f5f5f7}

/* ── Responsive: desktop wider screens ── */
@media (min-width:768px){
  body{font-size:16px}
  #topbar{padding:10px 20px}
  .tb-title{font-size:22px}
  .login-card,.profile-card,.points-card{margin:20px 40px}
  #tabbar{padding:6px 0 12px}
  .tab .tab-icon{font-size:24px}
  .tab .tab-label{font-size:12px}
}

/* v20260929-1600 */

/* ── Profile card (Me view) ── */
.profile-card-me{padding:16px 16px 14px}
.profile-header{display:flex;align-items:center;gap:12px}
.profile-avatar{
  flex:0 0 64px;width:64px;height:64px;border-radius:50%;overflow:hidden;
  background:var(--border);display:flex;align-items:center;justify-content:center;
  border:2px solid var(--card);box-shadow:0 2px 6px rgba(0,0,0,.1);
}
.profile-avatar img{width:100%;height:100%;object-fit:cover}
.profile-avatar-default{
  background:linear-gradient(135deg,#e0e0e0 0%,#c8c8c8 50%,#b0b0b0 100%);
  color:#555;font-size:26px;font-weight:700;border:2px solid var(--card);
  position:relative;cursor:pointer;
}
.profile-avatar-default .avatar-initial{display:none}
.profile-avatar-default::before{
  content:"";
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='32' r='20' fill='gray'/%3E%3Cpath d='M18 100 L18 74 Q18 60 50 60 Q82 60 Q82 74 L82 100 Z' fill='gray'/%3E%3C/svg%3E");
  background-size:68%;background-repeat:no-repeat;background-position:center;
  pointer-events:none;
}
.avatar-initial{line-height:1;transform:translateY(-2px)}
.profile-info{flex:1;min-width:0}
.profile-name-display{
  font-size:17px;font-weight:800;color:#1a1a2e;line-height:1.2;word-break:break-word;
}
.profile-name-row{display:flex;align-items:center;gap:10px;width:100%}
.profile-name-display{flex:1 1 auto;min-width:0}
.profile-account-row{display:flex;align-items:center;gap:6px;margin-top:4px;flex-wrap:wrap}
.profile-account-detail{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}
/* Upload button sits directly under the avatar, same 64px width as the avatar.
   Icon-only (title/aria-label carry the text) so it stays compact. */
.profile-avatar-col{
  flex:0 0 64px;width:64px;display:flex;flex-direction:column;align-items:center;gap:6px;
}
.profile-avatar-col .profile-avatar{flex:0 0 64px;width:64px;height:64px}
.profile-pic-btn{
  flex:none;width:64px;height:32px;padding:0;margin:0;
  background:#fff;border:1.5px dashed var(--border);border-radius:8px;
  color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.profile-pic-btn:active{background:#f5f5f7;border-color:var(--muted)}
.profile-pic-icon{font-size:16px;line-height:1}
.profile-edit-btn{margin-top:0;flex:0 0 auto}

/* ── Completed survey answer cells (tappable to re-edit) ── */
.survey-cells{margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.survey-cell{
  display:inline-flex;align-items:center;gap:5px;max-width:100%;text-align:left;
  font:600 13px/1 inherit;color:#1a1a2e;cursor:pointer;appearance:none;-webkit-appearance:none;
  padding:6px 12px;background:#fff;border:1.5px solid #e8e8ed;border-radius:20px;
}
.survey-cell-icon{font-size:16px;line-height:1;flex:none}
.survey-cell-label{line-height:1.25;min-width:0;overflow-wrap:anywhere}
.survey-cell:active{background:#fffbeb;border-color:#e8a040}

/* ── Me view logout button ── */
/* Logout button. It read `var(--brand)`, and --brand is defined NOWHERE in the
   project, so the declaration was invalid and the button fell back to .btn-ghost
   / .btn-primary with no visible fill — the "no background colour" report. Fixed
   by using the real project yellow (#f59e0b = --primary) and making it explicit,
   so this cannot silently break again. */
.me-logout-btn{
  width:calc(100% - 28px);margin:16px 14px 0;
  background:#f59e0b;border-color:#f59e0b;color:#fff;
}
.me-logout-btn:active{background:#d97706;border-color:#d97706}

/* ── Survey button (inline) ── */
.survey-start-btn{margin-top:2px;font-size:14px;flex:0 0 auto;min-width:180px}

/* ── Survey popup buttons ── */
.survey-opt-btn:hover{border-color:#e8a040;background:#fffbeb;color:#1a1a2e}
.survey-opt-btn:active{background:#f5f5f7;border-color:#d4a030;transform:scale(0.98)}

/* ── Points card margins for Me view ── */
.profile-card-me .points-card{margin:0 14px 10px}
.profile-card-me .section-h{margin:0 14px 6px}
.profile-card-me .empty{margin:0 14px 10px}
.profile-card-me .action-row{margin:0 14px 14px}

/* ── Modal tweaks ── */
.modal-bg .modal-card{border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.18)}

/* ── Map page ── */
/* The map is the whole page: no route list, no colour legend. */
.view-map{padding:0}
.map-fill{position:relative;width:100%;height:100%;min-height:340px;background:#e8e8e8}
.map-loading{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:#f5f5f7;color:#888;font-size:14px;z-index:1;
}
.map-loading-icon{margin-right:6px}
/* divIcon marker: coloured disc with the ROUTE number inside, so every point of a
   route carries the same digit (HKG07 -> "7", MAC01 -> "1"). A point's order is in
   the tap dialog, not on the map. */
.map-point-icon{background:none;border:none}
.map-point-dot{
  position:absolute;left:0;top:0;width:22px;height:22px;border-radius:50%;
  /* Black outline on EVERY point colour, so a disc never blends into the map
     tiles or the polyline. Was #fff, which only read against dark tiles. */
  border:2px solid #000;box-shadow:0 1px 3px rgba(0,0,0,.35);
}
.map-point-num{
  position:absolute;left:0;top:0;width:22px;height:22px;
  display:flex;align-items:center;justify-content:center;
  font-size:10px;font-weight:800;color:#fff;line-height:1;letter-spacing:-0.3px;
  text-shadow:0 1px 2px rgba(0,0,0,.45);
}
/* A white marker needs a deep amber for its number — white on white was
   unreadable (2026-10-05). #b45309 is the EXACT .rh-num colour used for the
   rating number on the route page, so a point number and a rating number are
   literally the same yellow rather than two similar ambers. */
.map-point-num.on-white{color:#b45309}

/* Point dialog: a real dialog element over the map, not a Leaflet popup. A
   popup is anchored to the marker, clips at the map edge and cannot carry a
   header/footer, so it cannot show route id + route name reliably. */
.map-pd-bg{
  position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.45);opacity:0;transition:opacity .18s ease;
  padding:20px;
}
.map-pd-bg.show{opacity:1}
.map-pd-card{
  width:100%;max-width:320px;background:#fff;border-radius:14px;overflow:hidden;
  box-shadow:0 12px 40px rgba(0,0,0,.3);transform:scale(.96);transition:transform .18s ease;
}
.map-pd-bg.show .map-pd-card{transform:scale(1)}
.map-pd-head{display:flex;align-items:center;gap:8px;padding:10px 14px;position:relative}
.map-pd-badge{font-size:13px;font-weight:800;color:#fff}
/* Beside the route number, not pushed right: the pair reads as one identity. */
.map-pd-num{
  font-size:11px;font-weight:700;color:#fff;
  background:rgba(0,0,0,.22);border-radius:999px;padding:2px 8px;
}
/* Close sits in the top-right corner and takes the ×, so the footer needs only
   the two action buttons. Absolute rather than a flex sibling so it does not
   disturb the badge/number spacing. 28px is the minimum comfortable tap size. */
.map-pd-x{
  position:absolute;top:6px;right:6px;width:28px;height:28px;
  border:none;background:rgba(0,0,0,.18);color:#fff;
  font-size:19px;line-height:1;border-radius:50%;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  font-family:inherit;padding:0;
}
.map-pd-x:active{background:rgba(0,0,0,.32)}
.map-pd-body{padding:12px 14px 4px}
.map-pd-route{font-size:15px;font-weight:800;color:#1a1a2e;line-height:1.3}
.map-pd-route-en{font-size:11px;color:#888;margin-top:2px}
.map-pd-stop{
  font-size:13px;color:#1a1a2e;margin-top:10px;padding-top:10px;
  border-top:1px solid var(--border);
}
.map-pd-stop-en{font-size:11px;color:#888;margin-top:2px}
.map-pd-foot{display:flex;gap:8px;padding:12px 14px 14px}
/* Two action buttons in the card's 320px: 28px padding + 8px gap leaves 142px
   each, so a 4-glyph CJK label at 13px (~52px) sits comfortably centred.
   Both are yellow with white text per the user's request, so neither is a
   "primary" and .btn/.btn-primary are deliberately not used here. */
.map-pd-foot-2 .map-pd-btn{flex:1;font-size:13px;padding:10px 0}
/* #eab308 is the project's amber (stop-checkin-btn.yellow), but it is far too
   light for white text: white on #eab308 is 1.92:1, and even a darkened #b88505
   is only 3.28:1. #937005 is the same hue (45 deg) scaled to 63% so white text
   reaches 4.60:1, which clears WCAG AA for normal text. */
.map-pd-btn-yellow{background:#937005;color:#fff;border:none;font-weight:600}
.map-pd-btn-yellow:active{background:#7d5f04}


/* ── Point page: user photos + comments ──
   Appended after .point-desc, so these blocks extend the page downward only.
   Nothing above them changes size, so the big photo, name row and description
   keep their positions. */
.point-userblock{margin-top:14px}
.point-userblock-head{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  font-size:12px;font-weight:700;color:var(--muted);margin-bottom:6px;
}
/* A <label for> rather than a <button>: it opens the native file picker without
   a script, and the input it targets is hidden but still keyboard-reachable via
   the label. */
.point-upload-btn{
  display:inline-flex;align-items:center;gap:4px;
  padding:5px 10px;border:1px solid var(--border);border-radius:8px;background:#fff;
  font-size:12px;font-weight:600;color:var(--primary);cursor:pointer;white-space:nowrap;
}
.point-upload-btn:active{background:#f5f5f7}
.point-upload-icon{font-size:13px;line-height:1}
/* Hidden, NOT display:none — a display:none input is skipped by some assistive
   tech and by label activation in older engines. Clip instead. */
.point-file-input{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
/* ── Point page photo feed: 2 columns, Xiaohongshu style ──
   Each card is photo on top, caption clamped to 2 rows, then author + heart.
   grid-template-columns uses an explicit 1fr 1fr (NOT auto-fit auto-fill): the
   feed must stay exactly 2 columns at every width, because 3+ columns has been
   shown to squeeze the cards so narrow the clamp drops to one word per line. */
.pp-grid{
  display:grid;grid-template-columns:1fr 1fr;gap:10px;align-items:start;
}
.pp-card{
  display:flex;flex-direction:column;min-width:0;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
.pp-card:focus-visible{outline:2px solid rgba(230,57,70,.45);outline-offset:2px;border-radius:8px}
.pp-imgwrap{
  width:100%;aspect-ratio:1/1;border-radius:10px;overflow:hidden;background:#eee;
}
.pp-img{width:100%;height:100%;object-fit:cover;display:block}
/* "+N" badge: how many photos ONE upload contributed. Only rendered when >1,
   so a single-photo post stays clean. */
.pp-count{
  position:absolute;top:6px;right:6px;z-index:2;
  min-width:20px;height:20px;padding:0 6px;border-radius:999px;
  background:rgba(0,0,0,.55);color:#fff;
  font-size:11px;font-weight:700;line-height:20px;text-align:center;
  font-variant-numeric:tabular-nums;
}
/* Two rows only, with an ellipsis when it overflows. min-height is NOT set:
   that would keep a 1-line caption's box the same height as a 2-line one and
   leave a ragged gap in the grid. */
.pp-caption{
  margin:6px 0 0;font-size:12.5px;line-height:1.4;color:#1a1a2e;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;word-break:break-word;
}
/* Author name: shrinks and ellipses rather than pushing the heart off the row. */
.pp-meta{
  display:flex;align-items:center;justify-content:space-between;
  gap:6px;margin-top:4px;min-width:0;
}
.pp-author{
  font-size:11.5px;color:var(--muted);min-width:0;flex:1 1 auto;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* Outline heart: no fill, black stroke. The liked state changes COLOUR and
   WEIGHT only — the interior stays transparent as the user asked. */
.pp-like{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:3px;
  background:none;border:none;padding:2px;cursor:pointer;color:#111;
  font-family:inherit;
}
.pp-like .pp-heart-svg{width:16px;height:16px;display:block;fill:none}
.pp-like.is-liked{color:#e63946}
.pp-like-n{font-size:11.5px;font-weight:600;color:var(--muted)}
.pp-like.is-liked .pp-like-n{color:#e63946}
.pp-empty{
  grid-column:1 / -1;padding:14px 0;text-align:center;
  font-size:13px;color:var(--muted);
}
/* Caption box for the next upload, sitting above the upload button */
/* Caption box for the next upload. Sits in its OWN block above the photo grid
   so it is unmistakably the photo description, not a comment box. */
.pp-upload-block{margin-top:14px;padding:10px 12px;background:#fff;
  border:1px solid var(--border);border-radius:10px}
.pp-upload-block .point-userblock-head{margin:0 0 6px}
.pp-caption-add{position:relative;display:flex;align-items:center;gap:6px;width:100%;margin-top:0}
.pp-caption-input{
  flex:1 1 auto;min-width:0;padding:8px 52px 8px 10px;border:1px solid var(--border);
  border-radius:8px;font-size:13px;font-family:inherit;background:#fff;color:#1a1a2e;
}
.pp-caption-input:focus{outline:2px solid rgba(230,57,70,.25);outline-offset:0}
.pp-caption-count{
  position:absolute;right:10px;font-size:11px;color:var(--muted);
  font-variant-numeric:tabular-nums;pointer-events:none;
}
.point-comments{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.point-comment{
  padding:8px 10px;border-radius:8px;background:#fff;border:1px solid var(--border);
  font-size:13px;line-height:1.45;color:#1a1a2e;word-break:break-word;
}
.point-comment-empty{color:var(--muted);font-style:italic}
.point-comment-add{display:flex;align-items:flex-end;gap:6px;margin-top:8px}
.point-comment-input{
  flex:1 1 auto;min-width:0;padding:8px 10px;border:1px solid var(--border);
  border-radius:8px;font-size:13px;font-family:inherit;resize:vertical;
}
.point-comment-input:focus{outline:2px solid rgba(230,57,70,.25);outline-offset:0}
.point-comment-send{
  flex:0 0 auto;padding:8px 14px;border:0;border-radius:8px;
  background:var(--primary);color:#fff;font-size:13px;font-weight:700;
  cursor:pointer;white-space:nowrap;font-family:inherit;
}
.point-comment-send:active{background:#c1121f}

/* The upload button and the comment button share one actions row, so both
   "add something to this point" controls are in the same place. */
.point-comment-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto}
.point-userblock-count{
  margin-left:6px;font-size:11px;font-weight:600;color:var(--muted);
  background:#f0f0f3;border-radius:9px;padding:1px 7px;
}
/* Me page uploads — ONE unified 2-column grid of the user's own posts, photos and
   comment-only posts together, in the point page's card language. Each card:
   visual on top, a top-right "×" delete, and a footer with the route label on
   the left and the like count on the RIGHT. */
.myuploads-grid{
  display:grid;grid-template-columns:1fr 1fr;gap:8px;
}
.myuploads-count{
  margin-left:6px;font-size:11px;font-weight:600;color:var(--muted);
}
.myuploads-empty{grid-column:1/-1;font-size:13px;color:var(--muted);padding:6px 2px}
.myuploads-bar{margin-top:10px}
.myuploads-bar .pp-postbtn{width:100%}
.myupload-card{
  position:relative;border-radius:10px;overflow:hidden;background:#fff;
  border:1px solid var(--border);
  display:flex;flex-direction:column;
}
/* 1:1 visual cell, same shape as the point page's feed cards. */
.myupload-visual{
  position:relative;width:100%;aspect-ratio:1/1;background:#eee;overflow:hidden;
}
.myupload-img{width:100%;height:100%;object-fit:cover;display:block}
.myupload-visual-text{
  display:flex;align-items:center;padding:10px;
  background:#f7f7fa;
}
.myupload-text{
  margin:0;font-size:13px;line-height:1.45;color:#1a1a2e;
  display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:4;line-clamp:4;overflow:hidden;
}
/* Top-right delete. Big enough to hit, subtle enough not to shout. */
.myupload-x{
  position:absolute;top:4px;right:4px;z-index:2;
  width:24px;height:24px;padding:0;border:none;border-radius:50%;
  background:rgba(0,0,0,.55);color:#fff;font-size:15px;line-height:1;
  cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.myupload-x:active{background:rgba(0,0,0,.8)}
.myupload-foot{
  display:flex;align-items:center;gap:6px;padding:6px 8px;
  border-top:1px solid var(--border);
}
.myupload-where{
  flex:1 1 auto;min-width:0;font-size:10px;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* Like count in the BOTTOM-RIGHT corner. Outline heart, matching the point page;
   it is a readout here, not a control — the owner does not like their own post,
   so there is no tap target and no fill. */
.myupload-like{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:3px;
  color:var(--muted);
}
.myupload-like .pp-heart-svg{width:13px;height:13px}
.myupload-like-n{font-size:11px;font-weight:600;color:var(--muted)}

/* ── Propose trail: button with the reward badge inside it ──
   The badge sits to the RIGHT of the label, inside the SAME button element, so
   the entire row is one tap target and tapping the badge opens the sheet. An
   earlier attempt put the badge in a sibling span with pointer-events:none —
   that made the tap fall through to the wrapper div, which has no click handler,
   so the badge became a dead zone. Colour is the project green (#15803d on a
   pale green pill) because this is a reward, matching the green "+N" figures
   used elsewhere on the Me page. */
.trailprop-row{
  display:flex;align-items:stretch;flex:1 1 auto;min-width:0;
}
.trailprop-btn{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  width:100%;min-width:0;
}
.trailprop-btn .trailprop-reward{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;
  padding:4px 9px;border-radius:999px;
  background:#ecfdf5;border:1px solid #86efac;
  font-size:11px;font-weight:700;color:#15803d;white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
.trailprop-btn .trailprop-reward::before{
  content:"";width:12px;height:12px;flex:0 0 auto;background:var(--green);
  /* A star drawn with clip-path, so no image or font is needed. Decorative. */
  clip-path:polygon(50% 0%,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);
}
/* Label above the proposal's description box. */
.pick-label{
  display:block;margin:10px 0 5px;font-size:12px;font-weight:700;color:var(--muted);
}
/* Point picker for the "Propose trail" sheet. The route dropdown was removed
   (2026-10-05), so .pick-card / .pick-select are gone; .pick-desc and the
   .pick-desc-count word counter remain. */
.pick-desc{
  display:block;width:100%;min-height:88px;margin:8px 0 10px;padding:10px 12px;
  font-size:14px;line-height:1.5;font-family:inherit;color:#1a1a2e;
  border:1.5px solid var(--border);border-radius:8px;background:#fff;resize:vertical;
}
.pick-desc:focus{outline:2px solid rgba(245,158,11,.25);outline-offset:0}
.pick-desc-count{
  display:block;margin:-4px 0 10px;font-size:11.5px;color:var(--muted);
  font-variant-numeric:tabular-nums;
}
.pick-desc-count.over{color:var(--red);font-weight:700}


/* ═══════════════════════════════════════════════════════════════════════════
   PHOTO PAGE — full-screen single photo, no tab bar
   ═══════════════════════════════════════════════════════════════════════════ */
.photo-view{
  /* Own scroll container: the footer is fixed to it, the body scrolls under.
     z-index is above the app's own topbar (100) / tabbar (100) so nothing can
     intercept a tap on the back, share, heart or comment controls. */
  position:fixed;inset:0;z-index:200;
  display:flex;flex-direction:column;background:#fff;
  padding-bottom:0;
}
/* Top bar: back on the left, author centred, share on the right. */
.pp-topbar{
  flex:0 0 auto;display:flex;align-items:center;gap:8px;
  padding:calc(env(safe-area-inset-top,0px) + 10px) 12px 10px;
  border-bottom:1px solid var(--border);background:#fff;
}
.pp-back,.pp-share{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;padding:0;border:1px solid var(--border);
  border-radius:999px;background:#fff;color:#111;cursor:pointer;
}
.pp-author-wrap{
  flex:1 1 auto;display:flex;align-items:center;gap:8px;min-width:0;
}
.pp-author-avatar{
  width:26px;height:26px;border-radius:999px;object-fit:cover;flex:0 0 auto;
  background:#eee;
}
.pp-author-avatar-blank{
  display:inline-flex;align-items:center;justify-content:center;
  font-size:12px;font-weight:700;color:var(--muted);background:#f0f0f3;
}
.pp-author-name{
  font-size:13px;font-weight:600;color:#1a1a2e;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* The photo stage: 60% of the viewport (was 70% — user asked for 60%).
   Letterboxed and never cropped, because cropping a user's own photo would
   hide what they framed. */
.pp-stage{
  position:relative;flex:0 0 auto;height:60vh;background:#0b0b0e;overflow:hidden;
}
/* Native scroll-snap track instead of a JS slider: momentum, rubber-banding and
   snap thresholds come from the platform, so the swipe feels right on a real
   phone without re-implementing touch physics. overflow-x is the scroller;
   overflow-y is hidden so a swipe can't drift the page. */
.pp-track{
  display:flex;height:100%;width:100%;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x mandatory;scrollbar-width:none;
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;
}
.pp-track::-webkit-scrollbar{display:none}
.pp-slide{
  flex:0 0 100%;width:100%;height:100%;
  scroll-snap-align:center;scroll-snap-stop:always;
  display:flex;align-items:center;justify-content:center;
}
.pp-stage-img{
  max-width:100%;max-height:100%;width:auto;height:auto;
  object-fit:contain;display:block;
}
/* Dot indicators, overlaid at the bottom of the stage. Red = the photo on
   screen; grey = the others. Sized 6px so they read as dots, not buttons. */
/* A gradient scrim under the dots. Without it a pale photo bottom made the
   grey dots collide visually with the image content (seen on a screenshot
   where dots landed on the photo's own blue artwork). */
.pp-stage::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:56px;
  background:linear-gradient(to top,rgba(0,0,0,.55),rgba(0,0,0,0));
  pointer-events:none;z-index:1;
}
.pp-dots{
  position:absolute;left:0;right:0;bottom:10px;
  display:flex;align-items:center;justify-content:center;gap:5px;z-index:2;
}
.pp-dot{
  width:6px;height:6px;padding:0;border:none;border-radius:999px;
  background:rgba(255,255,255,.55);cursor:pointer;
  transition:background .15s ease,width .15s ease;
}
.pp-dot.is-on{background:#e63946;width:16px;border-radius:999px}
/* Caption in full (no clamp here — this page has room for all of it) plus the
   comment thread. */
.pp-body{
  flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:12px;
}
.pp-authorline{font-size:13px;font-weight:700;color:#1a1a2e;margin-bottom:6px}
.pp-fullcaption{
  margin:0 0 12px;font-size:14px;line-height:1.5;color:#1a1a2e;word-break:break-word;
}
.pp-comments{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.ppc-item{font-size:13px;line-height:1.45;color:#1a1a2e;word-break:break-word}
.ppc-name{font-weight:700;margin-right:6px}
.ppc-empty{color:var(--muted);font-style:italic}
/* Brief highlight when the bottom-right comment icon reveals the thread, so the
   tap has a visible consequence even when the list was already on screen.
   Background only — no layout shift, no reflow of the surrounding page. */
.pp-comments.ppc-flash{animation:ppcFlash 1.4s ease-out}
@keyframes ppcFlash{
  0%{background:#fff4d6}
  55%{background:#fff4d6}
  100%{background:transparent}
}
@media (prefers-reduced-motion:reduce){
  .pp-comments.ppc-flash{animation:none;background:#fff4d6}
}
/* Foot: comment field + Post (left) | heart+count | comment+count (right) */
.pp-foot{
  flex:0 0 auto;display:flex;align-items:center;gap:8px;
  padding:8px 12px calc(env(safe-area-inset-bottom,0px) + 10px);
  border-top:1px solid var(--border);background:#fff;
}
/* The field and its Post button are ONE unit, so they shrink together instead
   of the button being squeezed off the row on a narrow phone. */
.pp-comment-row{
  flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;
}
.pp-comment-input{
  flex:1 1 auto;min-width:0;padding:9px 12px;border:1px solid var(--border);
  border-radius:999px;font-size:13px;font-family:inherit;background:#fff;color:#1a1a2e;
}
.pp-comment-input:focus{outline:2px solid rgba(230,57,70,.25);outline-offset:0}
/* A real, visible send control. Before this the ONLY way to post a reply was
   pressing Enter, which is a hidden gesture on a phone keyboard — the field
   looked inert. 34px tall keeps it at the same touch-target floor as the heart
   and comment icon beside it. The disabled state is driven by JS (an empty box
   has nothing to post), not by a CSS sibling trick that a re-render could break. */
.pp-send{
  flex:0 0 auto;min-width:52px;height:34px;padding:0 12px;
  border:none;border-radius:999px;background:var(--red);color:#fff;
  font-family:inherit;font-size:13px;font-weight:700;line-height:1;
  cursor:pointer;white-space:nowrap;
}
.pp-send:active{opacity:.8}
.pp-send:disabled{background:#d8d8de;color:#fff;cursor:default}
.pp-foot-like,.pp-foot-cmt{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;
  background:none;border:none;padding:4px;cursor:pointer;color:#111;
  font-family:inherit;
}
.pp-foot-like .pp-heart-svg{width:22px;height:22px;display:block;fill:none}
.pp-foot-like.is-liked{color:#e63946}
.pp-foot-cmt .pp-like-n{font-size:12px}
.pp-foot-like .pp-like-n{font-size:12.5px;font-weight:700}
.pp-foot-like.is-liked .pp-like-n{color:#e63946}

/* Login-only popup. Centred (not a bottom sheet) because it is a short notice,
   not a task. Reuses .pfd-overlay for the backdrop so z-index and dismissal
   behave identically to the photo draft sheet. */
.login-only-overlay{align-items:center;justify-content:center;padding:16px}
.login-only-card{
  position:relative;width:100%;max-width:340px;background:#fff;
  border-radius:14px;padding:22px 18px 16px;text-align:center;
  box-shadow:0 8px 28px rgba(0,0,0,.22);
}
.login-only-x{
  position:absolute;top:6px;right:6px;width:32px;height:32px;
  display:flex;align-items:center;justify-content:center;
  font-size:22px;line-height:1;color:var(--muted);background:none;
  border:none;border-radius:8px;cursor:pointer;
}
.login-only-x:active{background:#f0f0f3}
.login-only-title{margin:0 24px 8px;font-size:17px;font-weight:800;color:#1a1a2e}
.login-only-body{margin:0 0 16px;font-size:14px;line-height:1.5;color:var(--muted)}
.login-only-actions{display:flex;gap:8px}
.login-only-actions .btn{flex:1;padding:11px 0;font-size:14px}

/* ── Photo draft sheet: choose photos -> describe them -> post ──
   This is the step the user asked for. It is a bottom sheet so the point page
   stays visible behind it, and the description box is the LAST thing before the
   post button, which is the order the user described. */
.pfd-overlay{
  position:fixed;inset:0;z-index:3000;
  background:rgba(0,0,0,.5);
  display:flex;align-items:flex-end;justify-content:center;
}
.pfd-sheet{
  width:100%;max-width:520px;
  background:#fff;border-radius:16px 16px 0 0;
  padding:14px 14px calc(env(safe-area-inset-bottom,0px) + 14px);
  display:flex;flex-direction:column;gap:10px;
  max-height:92vh;overflow-y:auto;
}
.pfd-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.pfd-count{font-size:13px;font-weight:700;color:#1a1a2e}
.pfd-x{
  border:none;background:none;font-size:18px;line-height:1;
  padding:4px 8px;color:var(--muted);cursor:pointer;
}
.pfd-thumbs{display:flex;gap:8px;overflow-x:auto;padding-bottom:4px}
/* A tile WRAPS the thumbnail so the per-photo ✕ can sit in its top-right
   corner without covering the image content itself. */
.pfd-tile{position:relative;flex:0 0 auto;width:72px;height:72px}
.pfd-thumb{
  position:relative;
  width:72px;height:72px;border-radius:8px;object-fit:cover;
  background:#eee;display:block;
}
/* Per-photo cancel. Sits top-right, black on translucent white so it reads on
   any photo. pointer-events only on the button, not the tile, so the tile is
   not covered. */
.pfd-rm{
  position:absolute;top:-5px;right:-5px;z-index:3;
  width:20px;height:20px;padding:0;border:none;border-radius:999px;
  background:rgba(0,0,0,.6);color:#fff;
  font-size:11px;line-height:20px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
}
.pfd-rm:active{background:rgba(0,0,0,.85)}
/* Add-more tile: SAME size as a thumbnail, so it reads as another slot in the
   row rather than a separate button. Hidden entirely at the 6-photo cap. */
.pfd-add{
  background:#f4f4f7;border:1px dashed #c9c9d2;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px;cursor:pointer;padding:0;font-family:inherit;
}
.pfd-add-plus{font-size:20px;line-height:1;color:var(--muted)}
.pfd-add-txt{font-size:9px;color:var(--muted);line-height:1.1;text-align:center}
.pfd-add-full{border-style:solid;opacity:.6;cursor:default}
.pfd-first{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  background:rgba(0,0,0,.55);color:#fff;
  font-size:10px;font-weight:700;text-align:center;line-height:16px;
  border-radius:0 0 8px 8px;
}
.pfd-caprow{position:relative;display:flex;align-items:center;gap:6px;width:100%}
.pfd-confirm{
  width:100%;padding:12px;border:none;border-radius:10px;
  background:var(--primary);color:#fff;
  font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;
}
.pfd-confirm:disabled{opacity:.55;cursor:default}
.pfd-confirm:active:not(:disabled){background:#c1121f}

/* ── 分享及評論: one grid for photo posts AND comment-only posts ──
   A text post MUST occupy the same cell as a photo post (1:1, same type scale),
   so a comment is a first-class post rather than a footnote. Only the visual
   weight differs. */
/* A text post must occupy the SAME grid cell as a photo post. A photo card is
   img(1:1) + caption + meta; this is box(1:1) + meta. The missing caption row
   is why text cards came out 28px shorter and broke the grid rhythm, so the box
   is stretched by the same amount a 1-line caption would take. */
.pp-card-text{justify-content:flex-start}
.pp-card-text .pp-text-box{
  width:100%;aspect-ratio:1/1;border-radius:10px;overflow:hidden;
  background:#f6f6fa;border:1px solid var(--border);
  padding:12px;display:flex;align-items:flex-start;
  /* matches .pp-caption's line box + its 6px top margin */
  margin-bottom:calc(1.4em + 6px);
}
.pp-text-body{
  margin:0;font-size:13.5px;line-height:1.5;color:#1a1a2e;
  word-break:break-word;
  /* A long comment is clamped to the same visual box the photo fills, so the
     grid stays even. The FULL text is always available on its own page. */
  display:-webkit-box;-webkit-line-clamp:6;-webkit-box-orient:vertical;overflow:hidden;
}
.pp-meta-spacer{flex:0 0 auto;min-width:22px}

/* The single share button, under the grid. */
.pp-postbar{margin-top:12px}
.pp-postbtn{
  width:100%;display:flex;align-items:center;justify-content:center;gap:6px;
  padding:12px 14px;border:1px solid var(--primary);border-radius:10px;
  background:#fff;color:var(--primary);
  font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;
}
.pp-postbtn:active{background:#fdecee}
.pp-postbtn-icon{font-size:17px;line-height:1}

/* "Comment only" tile — TOP-LEFT of the draft row, same size as a thumbnail. */
.pfd-textonly{
  background:#fff8f0;border:1px dashed var(--primary);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;padding:4px;cursor:pointer;text-align:center;
}
.pfd-textonly-mark{font-size:18px;line-height:1;color:var(--primary)}
.pfd-textonly-txt{font-size:9px;line-height:1.15;color:var(--primary);font-weight:700}
.pfd-textonly-note{
  width:100%;padding:14px 12px;border-radius:8px;
  background:#fff8f0;border:1px dashed var(--primary);
  font-size:12.5px;line-height:1.4;color:#8a5a00;text-align:center;
}
/* Once the remove-button is added, keep the caption row's counter clear. */
.pp-postbar .point-file-input{margin:0}

/* ── TEXT-POST PAGE: a comment gets the same page as a photo post ──
   Reuses .photo-view chrome (top bar, body, foot) so the two pages feel
   identical; only the stage differs — words instead of an image. */
.textpost-view{background:#fff}
.tp-stage{
  flex:0 0 auto;height:60vh;              /* same 60% band as the photo page */
  padding:24px 20px;background:#fff;
  overflow-y:auto;-webkit-overflow-scrolling:touch;
}
.tp-body{
  margin:0;font-size:17px;line-height:1.6;color:#1a1a2e;
  word-break:break-word;white-space:pre-wrap;
  /* NOT clamped — the whole post must be readable on its own page. */
}
