/* ============================================================================
 * GRIP — "Know Before You Go"  ·  Bike-parking finder
 * Self-contained, embeddable React component.  No required props.
 *
 *   import FigmaProject from './FigmaProject.jsx';
 *   <FigmaProject />
 *
 * Faithful reproduction of the GRIP Figma file (page "Version-3").
 * All flows are wired with native React state transitions (no Figma embeds).
 *
 * ASSETS: raster/illustration assets live in ./assets/ (relative to this file).
 *   Override the location by passing  <FigmaProject assetBase="/my/path/" />
 *   Bundled assets: bike-rider.gif (animated), city.png, avatar.svg
 *
 * MAP: real Google-Maps-style raster tiles via Leaflet (loaded from CDN at
 *   runtime) + CARTO Voyager basemap. Needs an internet connection; carries the
 *   required OpenStreetMap/CARTO attribution. No API key.
 *   TODO: for the literal Google look + Google's data, swap the tile layer for the
 *   Google Maps JS API and read a `googleMapsApiKey` prop (needs your key + billing).
 *
 * FONTS: Poppins (SemiBold 600) is the product font — load it on the host page:
 *   <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,500;0,600;0,700;1,800;1,900&display=swap" rel="stylesheet">
 *   The GRIP wordmark in the file is a custom display glyph; it is approximated
 *   here with heavy oblique type (TODO: drop in the real wordmark SVG if needed).
 * ==========================================================================*/

const { useState, useEffect, useRef, useCallback } = React;

/* ---- palette (exact values from the Figma file) ------------------------- */
const C = {
  blue:   "rgb(21,96,243)",
  blueLt: "rgb(114,162,255)",
  green:  "rgb(19,159,12)",
  gray:   "rgb(128,128,128)",
  grayMk: "rgb(164,164,164)",
  stroke: "rgb(116,172,229)",
  red:    "rgb(255,101,101)",
  amber:  "rgb(240,158,11)",
  ink:    "rgb(0,0,0)",
  cardGrad: "linear-gradient(180deg, rgb(255,255,255) 0%, rgb(255,255,253) 24%, rgb(250,255,232) 100%)",
  skyGrad:  "linear-gradient(180deg, rgb(206,230,253) 0%, rgb(225,240,255) 100%)",
  water: "#BCDCF2", beach: "#EDE6CD", land: "#ECEAE4", park: "#CDE8A4",
  casing: "#E4E1D8", road: "#FCFBF8", lime: "rgb(169,255,140)",
};
const F = 'Poppins, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';

/* ---- data --------------------------------------------------------------- */
const NEAR_SPOTS = [
  { name: "Pinsker 62",     spots: 3, km: "0.7", min: "3 Minute",   ll: [32.07595, 34.77185], photo: "photo1", tags: ["covered", "transit"] },
  { name: "Ben Ami St 3",   spots: 3, km: "0.7", min: "3 Minutes",  ll: [32.08185, 34.7788], photo: "photo2", tags: ["secure"] },
  { name: "Bograshov 12",   spots: 3, km: "0.5", min: "2 Minute",   ll: [32.07775, 34.77405], photo: "photo3", tags: ["covered", "secure", "transit"] },
  { name: "Mapu St 22",     spots: 1, km: "0.8", min: "3.5 Minute", ll: [32.08435, 34.77065], tags: ["transit"] },
  { name: "Ruppin St 17",   spots: 2, km: "0.9", min: "4 Minute",   ll: [32.08015, 34.77555], tags: ["secure", "transit"] },
  { name: "Trumpeldor 4",   spots: 1, km: "1.1", min: "5 Minute",   ll: [32.07335, 34.76955], tags: ["covered"] },
  { name: "Dizengoff 99",   spots: 2, km: "0.4", min: "2 Minute",   ll: [32.08105, 34.77385], tags: ["covered", "transit"] },
  { name: "Frishman 38",    spots: 1, km: "0.6", min: "3 Minute",   ll: [32.08055, 34.77285], tags: ["secure"] },
  { name: "Gordon St 21",   spots: 3, km: "0.9", min: "4 Minute",   ll: [32.08245, 34.77155], tags: ["covered", "secure"] },
  { name: "King George 44", spots: 2, km: "1.0", min: "4.5 Minute", ll: [32.07485, 34.77565], tags: ["transit"] },
  { name: "Ben Yehuda 110", spots: 1, km: "1.2", min: "5 Minute",   ll: [32.08125, 34.76995], tags: ["secure", "transit"] },
  { name: "Sheinkin 18",    spots: 3, km: "1.3", min: "6 Minute",   ll: [32.07015, 34.77415], tags: ["covered"] },
  { name: "Florentin 24",   spots: 2, km: "2.4", min: "10 Minute",  ll: [32.05605, 34.76845], tags: ["secure", "transit"] },
  { name: "Shabazi 31",     spots: 1, km: "2.1", min: "9 Minute",   ll: [32.06255, 34.76605], tags: ["covered"] },
  { name: "Herzl St 16",    spots: 3, km: "2.0", min: "8.5 Minute", ll: [32.05905, 34.77205], tags: ["transit"] },
  { name: "Nahalat Binyamin 40", spots: 2, km: "1.7", min: "7 Minute", ll: [32.06605, 34.77055], tags: ["covered", "secure"] },
  { name: "Yefet St 23",    spots: 1, km: "3.1", min: "13 Minute",  ll: [32.05285, 34.75895], tags: ["transit"] },
];

const RECENTS = [
  "Maze 40, Tel Aviv-Yaffo",
  "Florentin 23, Tel Aviv-Yaffo",
  "Bilu 44, Tel Aviv-Yaffo",
  "Zrubavel 12, Tel Aviv-Yaffo",
  "Hisin 9, Tel Aviv-Yaffo",
  "Dizengof 101, Tel Aviv-Yaffo",
  "Lillenblum 28, Tel Aviv-Yaffo",
];

const buildResults = (dest) => ([
  { name: dest || "Maze 40, Tel Aviv-Yaffo", spots: 3, km: "0.2 KM", min: "1 Minute", photo: "Maze 27, Tel Aviv-Yaffo" },
  { name: "Sheinkin 53, Tel Aviv-Yaffo",     spots: 1, km: "0.4 KM", min: "2 Minutes", photo: "Sheinkin 53" },
  { name: "Derech Jaffa 9, Tel Aviv-Yaffo",  spots: 2, km: "0.7 KM", min: "3 Minutes", photo: "Derech Jaffa 9" },
  { name: "Zrubavel 16, Tel Aviv-Yaffo",     spots: 3, km: "0.9 KM", min: "4 Minutes", photo: "Zrubavel 16" },
]);

/* ---- real-world geography (central Tel Aviv) ---- */
const TLV_CENTER = [32.0786, 34.7741];   // Dizengoff Square
const USER_LL = [32.07655, 34.77465];
/* lat/lng for each NEAR_SPOTS entry (by index) */
const SPOTS_GEO = [
  [32.07595, 34.77185],  // 0 Pinsker 62
  [32.08185, 34.7788],   // 1 Ben Ami St 3
  [32.07775, 34.77405],  // 2 Bograshov 12
  [32.08435, 34.77065],  // 3 Mapu St 22
  [32.08015, 34.77555],  // 4 Ruppin St 17
  [32.07335, 34.76955],  // 5 Trumpeldor 4
  [32.08105, 34.77385],  // 6 Dizengoff 99
  [32.08055, 34.77285],  // 7 Frishman 38
  [32.08245, 34.77155],  // 8 Gordon St 21
  [32.07485, 34.77565],  // 9 King George 44
  [32.08125, 34.76995],  // 10 Ben Yehuda 110
  [32.07015, 34.77415],  // 11 Sheinkin 18
  [32.05605, 34.76845],  // 12 Florentin 24
  [32.06255, 34.76605],  // 13 Shabazi 31
  [32.05905, 34.77205],  // 14 Herzl St 16
  [32.06605, 34.77055],  // 15 Nahalat Binyamin 40
  [32.05285, 34.75895],  // 16 Yefet St 23
];
/* context racks that are full (taken) */
const FULL_GEO = [ [32.07905, 34.77235], [32.07515, 34.77665], [32.08265, 34.77515], [32.07695, 34.77625], [32.08355, 34.77345], [32.07215, 34.77245], [32.05905, 34.76705], [32.06105, 34.77105] ];
/* cluster groupings shown when zoomed out (member indices into NEAR_SPOTS) */
const CLUSTERS = [
  { members: [0, 2, 5, 11] },
  { members: [1, 3, 4, 8] },
  { members: [6, 7, 9, 10] },
  { members: [12, 13, 14, 15, 16] },
];
const centroid = (lls) => [lls.reduce((a, p) => a + p[0], 0) / lls.length, lls.reduce((a, p) => a + p[1], 0) / lls.length];

/* =========================================================================
 * tiny shared pieces
 * =======================================================================*/
function StatusBar({ dark }) {
  const col = dark ? "#fff" : "rgb(0,0,0)";
  return (
    <div style={{ position: "absolute", left: 0, top: 0, width: 402, height: 54, zIndex: 40,
      display: "flex", alignItems: "center", justifyContent: "space-between",
      padding: "0 27px 0 30px", boxSizing: "border-box", pointerEvents: "none" }}>
      <span style={{ fontFamily: '-apple-system, "SF Pro", system-ui', fontWeight: 600, fontSize: 17, color: col, letterSpacing: "-0.2px" }}>9:41</span>
      <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
        {/* cellular */}
        <svg width="19" height="13" viewBox="0 0 19 13" fill="none">
          {[3,6,9,12].map((h,i)=>(<rect key={i} x={i*5} y={12-h} width="3.2" height={h} rx="1" fill={col}/>))}
        </svg>
        {/* wifi */}
        <svg width="18" height="13" viewBox="0 0 18 13" fill="none">
          <path d="M9 11.4 11 9C10.4 8.4 9.7 8 9 8 8.3 8 7.6 8.4 7 9L9 11.4Z" fill={col}/>
          <path d="M9 3C12 3 14.7 4.2 16.7 6.2L15.3 7.6C13.6 5.9 11.4 5 9 5 6.6 5 4.4 5.9 2.7 7.6L1.3 6.2C3.3 4.2 6 3 9 3Z" fill={col} opacity="0.9"/>
        </svg>
        {/* battery */}
        <div style={{ display: "flex", alignItems: "center", gap: 1.5 }}>
          <div style={{ width: 25, height: 13, borderRadius: 4, border: `1px solid ${col}`, opacity: 0.9, position: "relative", boxSizing: "border-box" }}>
            <div style={{ position: "absolute", top: 2, bottom: 2, left: 2, width: 18, borderRadius: 1.5, background: col }} />
          </div>
          <div style={{ width: 1.5, height: 5, borderRadius: 2, background: col, opacity: 0.5 }} />
        </div>
      </div>
    </div>
  );
}

function Press({ onClick, children, style, scale = 0.96 }) {
  const [d, setD] = useState(false);
  return (
    <div onClick={onClick}
      onPointerDown={() => setD(true)}
      onPointerUp={() => setD(false)}
      onPointerLeave={() => setD(false)}
      style={{ cursor: "pointer", transform: d ? `scale(${scale})` : "scale(1)",
        transition: "transform .12s ease, filter .12s ease", filter: d ? "brightness(0.95)" : "none",
        WebkitTapHighlightColor: "transparent", ...style }}>
      {children}
    </div>
  );
}

function BottomNav({ active, go }) {
  const Item = ({ id, label, icon }) => {
    const on = active === id;
    const col = on ? C.blue : C.grayMk;
    return (
      <Press onClick={() => go(id)} scale={0.9}
        style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2, width: 70 }}>
        {icon(col, on)}
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: col }}>{label}</span>
      </Press>
    );
  };
  return (
    <div style={{ position: "absolute", left: 0, bottom: 0, width: 402, height: 92, zIndex: 30,
      background: "rgba(255,255,255,0.92)", backdropFilter: "blur(8px)",
      display: "flex", justifyContent: "space-between", alignItems: "flex-start",
      padding: "20px 60px 0", boxSizing: "border-box", borderTop: "0.5px solid rgba(0,0,0,0.06)" }}>
      <Item id="map" label="Map" icon={(c,on)=>(
        <svg width="28" height="24" viewBox="0 0 28 24" fill={on?c:"none"} fillOpacity={on?0.18:0}><path d="M10 2 2 5v17l8-3 8 3 8-3V2l-8 3-8-3Zm0 0v17m8-14v17" stroke={c} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/></svg>
      )}/>
      <Item id="favorites" label="Favorites" icon={(c,on)=>(
        <svg width="28" height="28" viewBox="0 0 24 24" fill={on?c:"none"}><path d="M12 21s-8-5.2-8-11a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 10c0 5.8-8 11-8 11Z" stroke={c} strokeWidth="2" strokeLinejoin="round"/></svg>
      )}/>
      <Item id="profile" label="Profile" icon={(c,on)=>(
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="4" stroke={c} strokeWidth="2" fill={on?c:"none"}/><path d="M4 21c0-4.4 3.6-7 8-7s8 2.6 8 7" stroke={c} strokeWidth="2" strokeLinecap="round" fill={on?c:"none"}/></svg>
      )}/>
    </div>
  );
}

/* distance + duration meta row (bike icon · km | hourglass · min) */
function MetaRow({ km, min }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 3px" }}>
        <svg width="18" height="16" viewBox="0 0 18 16" fill="none">
          <circle cx="3.6" cy="12.4" r="3" stroke={C.ink} strokeWidth="1.3"/><circle cx="14.4" cy="12.4" r="3" stroke={C.ink} strokeWidth="1.3"/>
          <path d="M3.6 12.4 7.5 5.5h4.3l2.6 6.9M7.5 5.5 9.5 9h3.5M6.2 4.2h2.2" stroke={C.ink} strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
        </svg>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.gray }}>{km}</span>
      </div>
      <div style={{ width: 0.5, height: 18, background: C.stroke }} />
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "5px 3px" }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="1.6"><circle cx="12" cy="12" r="9"/><polyline points="12 6 12 12 15 14" strokeLinecap="round"/></svg>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.gray }}>{min}</span>
      </div>
    </div>
  );
}

const PicIcon = ({ photo, onOpen }) => (
  <Press onClick={() => onOpen?.(photo)} scale={0.9} style={{ display: "inline-flex", width: 18, height: 15, borderRadius: 1, alignItems: "center", justifyContent: "center",
    boxShadow: "1px 1px 1.7px 0 rgba(0,0,0,0.25)", verticalAlign: "middle", cursor: "pointer" }}>
    <svg width="18" height="15" viewBox="0 0 18 15" fill={C.blue}><rect width="18" height="15" rx="1.5" fill="#fff" stroke={C.blue} strokeWidth="1.4"/><circle cx="5.5" cy="5" r="1.6" fill={C.blue}/><path d="M2 13 7 8l3 3 3-3 3 3v1.5H2V13Z" fill={C.blue}/></svg>
  </Press>
);

/* The big "Nearest Available" carousel card (file: Frame 52) */
function SpotCard({ s, onNavigate, onPhotoOpen, saved, onSave, eyebrow = "Nearest Available" }) {
  return (
    <div style={{ width: 342, borderRadius: 14, background: C.cardGrad, boxShadow: `inset 0 0 0 0.5px ${C.blue}`,
      padding: "14px 18px", boxSizing: "border-box", position: "relative" }}>
      {/* save heart */}
      <Press onClick={onSave} scale={0.82} style={{ position: "absolute", right: 14, top: 14, width: 38, height: 38, borderRadius: 19,
        background: saved ? "rgba(255,101,101,0.1)" : "rgba(0,0,0,0.04)", display: "flex", alignItems: "center", justifyContent: "center" }}>
        <svg width="21" height="21" viewBox="0 0 24 24" fill={saved ? C.red : "none"} stroke={saved ? C.red : C.gray} strokeWidth="2" style={{ transition: "all .2s" }}><path d="M12 21s-8-5.2-8-11a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 10c0 5.8-8 11-8 11Z" strokeLinejoin="round"/></svg>
      </Press>
      {eyebrow && <div style={{ fontFamily: F, fontWeight: 600, fontSize: 13, color: C.gray, marginBottom: 3 }}>{eyebrow}</div>}
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, paddingRight: 44 }}>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 25, color: C.ink, lineHeight: 1 }}>{s.name}</span>
        <PicIcon photo={s.photo || s.name} onOpen={onPhotoOpen} />
      </div>
      <div style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.green, margin: "6px 0 9px" }}>
        {s.spots} {s.spots === 1 ? "Spot" : "Spots"} Available
      </div>
      {/* amenity tags */}
      {s.tags && s.tags.length > 0 && (
        <div style={{ display: "flex", gap: 6, marginBottom: 9, flexWrap: "wrap" }}>
          {s.tags.map((t) => (
            <div key={t} style={{ display: "flex", alignItems: "center", gap: 4, padding: "3px 9px", borderRadius: 14, background: "rgba(21,96,243,0.07)" }}>
              <FilterIcon name={t === "covered" ? "umbrella" : t === "secure" ? "lock" : "transit"} color={C.blue} size={12} />
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 11, color: C.blue }}>{t === "covered" ? "Covered" : t === "secure" ? "Secure" : "Transit"}</span>
            </div>
          ))}
        </div>
      )}
      <MetaRow km={`${s.km}  KM`} min={s.min} />
      <Press onClick={onNavigate} style={{ marginTop: 12, height: 44, borderRadius: 14, background: C.blue,
        display: "flex", alignItems: "center", justifyContent: "center",
        boxShadow: "0 6px 16px rgba(21,96,243,0.28)" }}>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>Navigate</span>
      </Press>
    </div>
  );
}

/* amenity / filter glyphs */
function FilterIcon({ name, color, size = 16 }) {
  const p = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
  if (name === "umbrella") return <svg {...p}><path d="M12 12v8a2 2 0 0 0 4 0M12 3v1.5M3 12a9 9 0 0 1 18 0Z"/></svg>;
  if (name === "lock") return <svg {...p}><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>;
  if (name === "transit") return <svg {...p}><rect x="6" y="3" width="12" height="14" rx="2"/><path d="M6 11h12M9 21l1.5-3M15 21l-1.5-3"/><circle cx="9" cy="14" r="0.6" fill={color}/><circle cx="15" cy="14" r="0.6" fill={color}/></svg>;
  return null;
}

/* Search-result card with availability badge (file: Count=1/2/3) */
function ResultCard({ s, onNavigate, onPhotoOpen }) {
  const badge = s.spots === 0 ? C.grayMk : s.spots <= 2 ? C.amber : C.green;
  return (
    <div style={{ width: 342, borderRadius: 8, background: C.cardGrad, boxShadow: `inset 0 0 0 0.5px ${C.blue}`,
      padding: 30, boxSizing: "border-box" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10 }}>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 20, color: C.ink }}>{s.name}</span>
        <PicIcon photo={s.photo || s.name} onOpen={onPhotoOpen} />
      </div>
      <div style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.green, margin: "8px 0" }}>
        {s.spots} {s.spots === 1 ? "Spot" : "Spots"} Available
      </div>
      <div style={{ margin: "2px 0 8px" }}><MetaRow km={s.km} min={s.min} /></div>
      <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
        <div style={{ width: 48, height: 48, borderRadius: "50%", background: badge, flex: "none",
          display: "flex", alignItems: "center", justifyContent: "center", color: "#fff",
          boxShadow: "inset 0 0 0 0.7px #fff, 0 4px 4px rgba(0,0,0,0.09)",
          fontFamily: F, fontWeight: 600, fontSize: 26 }}>{s.spots}</div>
        <Press onClick={onNavigate} style={{ flex: 1, height: 46, borderRadius: 14, background: C.blue,
          display: "flex", alignItems: "center", justifyContent: "center" }}>
          <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>Navigate</span>
        </Press>
      </div>
    </div>
  );
}

/* =========================================================================
 * SCREENS
 * =======================================================================*/
function OnboardingScreen({ done }) {
  const [step, setStep] = useState(0);
  const slides = [
    { icon: "radar", title: "Find a free rack, instantly", body: "GRIP shows you which bike racks are open right now — no more circling the block hunting for a spot." },
    { icon: "sensor", title: "Every rack has a sensor", body: "Live sensors report the moment a spot opens up or fills, so the map you see is the street as it really is." },
    { icon: "lock", title: "Hold it while you ride", body: "Reserve a spot for a few minutes so it's still there when you arrive. If it's taken, we'll find you another." },
  ];
  const last = slides.length;            // index === last → location-priming frame
  const isPrime = step === last;
  const cur = slides[Math.min(step, last - 1)];

  const Icon = ({ name }) => {
    const p = { width: 52, height: 52, viewBox: "0 0 24 24", fill: "none", stroke: "#fff", strokeWidth: 1.9, strokeLinecap: "round", strokeLinejoin: "round" };
    if (name === "radar") return <svg {...p}><path d="M12 22s-7-5.6-7-11a7 7 0 0 1 14 0c0 5.4-7 11-7 11Z"/><circle cx="12" cy="11" r="2.4"/></svg>;
    if (name === "sensor") return <svg {...p}><path d="M5 12.5a9 9 0 0 1 14 0M8 15.5a5 5 0 0 1 8 0"/><circle cx="12" cy="18.5" r="1.4" fill="#fff" stroke="none"/></svg>;
    if (name === "lock") return <svg {...p}><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>;
    return null;
  };

  return (
    <div style={{ position: "absolute", inset: 0, background: C.skyGrad, overflow: "hidden", display: "flex", flexDirection: "column" }}>
      <StatusBar />
      {/* skip */}
      <Press onClick={done} scale={0.9} style={{ position: "absolute", right: 22, top: 64, zIndex: 5, padding: "6px 10px", opacity: isPrime ? 0 : 1, pointerEvents: isPrime ? "none" : "auto" }}>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.gray }}>Skip</span>
      </Press>

      {!isPrime && (
        <div key={step} style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "0 40px", animation: "fIn .35s ease both" }}>
          <div style={{ position: "relative", width: 150, height: 150, display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 40 }}>
            <div style={{ position: "absolute", inset: 0, borderRadius: "50%", background: C.blue, opacity: 0.12, animation: "pulseRing 2.4s ease-out infinite" }} />
            <div style={{ width: 108, height: 108, borderRadius: "50%", background: C.blue, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 16px 36px rgba(21,96,243,0.32)" }}>
              <Icon name={cur.icon} />
            </div>
            <div style={{ position: "absolute", right: 18, top: 14, width: 30, height: 30, borderRadius: "50%", background: C.lime, boxShadow: "0 4px 10px rgba(0,0,0,0.12)" }} />
          </div>
          <div style={{ fontFamily: F, fontWeight: 700, fontSize: 26, color: C.ink, textAlign: "center", lineHeight: 1.15 }}>{cur.title}</div>
          <div style={{ fontFamily: F, fontWeight: 500, fontSize: 16, color: C.gray, textAlign: "center", lineHeight: 1.5, marginTop: 14, textWrap: "pretty" }}>{cur.body}</div>
        </div>
      )}

      {isPrime && (
        <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "0 40px", animation: "fIn .35s ease both" }}>
          <div style={{ position: "relative", width: 150, height: 150, display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 36 }}>
            <div style={{ position: "absolute", inset: 0, borderRadius: "50%", background: C.green, opacity: 0.14, animation: "pulseRing 2.2s ease-out infinite" }} />
            <div style={{ width: 108, height: 108, borderRadius: "50%", background: C.green, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 16px 36px rgba(19,159,12,0.3)" }}>
              <svg width="54" height="54" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/><circle cx="12" cy="12" r="9" strokeDasharray="3 3" opacity="0.6"/></svg>
            </div>
          </div>
          <div style={{ fontFamily: F, fontWeight: 700, fontSize: 25, color: C.ink, textAlign: "center" }}>Enable location</div>
          <div style={{ fontFamily: F, fontWeight: 500, fontSize: 16, color: C.gray, textAlign: "center", lineHeight: 1.5, marginTop: 12, textWrap: "pretty" }}>
            GRIP uses your location to show racks near you and guide you there. We only access it while you're using the app.
          </div>
        </div>
      )}

      {/* bottom controls */}
      <div style={{ position: "absolute", left: 0, bottom: 56, width: 402, display: "flex", flexDirection: "column", alignItems: "center", gap: 22 }}>
        {!isPrime && (
          <div style={{ display: "flex", gap: 8 }}>
            {slides.map((_, i) => (
              <div key={i} style={{ width: i === step ? 22 : 8, height: 8, borderRadius: 4, background: i === step ? C.blue : "rgba(21,96,243,0.25)", transition: "width .25s ease" }} />
            ))}
          </div>
        )}
        <div style={{ width: 282, display: "flex", flexDirection: "column", gap: 12 }}>
          {!isPrime ? (
            <Press onClick={() => setStep((s) => s + 1)} scale={0.97} style={{ height: 52, borderRadius: 14, background: C.blue,
              display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 8px 20px rgba(21,96,243,0.3)" }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>{step === last - 1 ? "Get started" : "Next"}</span>
            </Press>
          ) : (
            <>
              <Press onClick={done} scale={0.97} style={{ height: 52, borderRadius: 14, background: C.blue,
                display: "flex", alignItems: "center", justifyContent: "center", gap: 9, boxShadow: "0 8px 20px rgba(21,96,243,0.3)" }}>
                <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 21s-7-5.6-7-11a7 7 0 0 1 14 0c0 5.4-7 11-7 11Z"/><circle cx="12" cy="10" r="2.4"/></svg>
                <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>Allow location</span>
              </Press>
              <Press onClick={done} scale={0.97} style={{ height: 46, display: "flex", alignItems: "center", justifyContent: "center" }}>
                <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.gray }}>Not now</span>
              </Press>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function Welcome({ go, A }) {
  return (
    <div style={{ position: "absolute", inset: 0, background: C.skyGrad, overflow: "hidden" }}>
      <StatusBar />
      {/* skyline */}
      <div style={{ position: "absolute", left: -144, top: -56, width: 546, height: 760,
        opacity: 0.6, background: `url(${A}city.png) center top / cover no-repeat` }} />
      {/* neon sun — positioned to cover the skyline's painted sun */}
      <div style={{ position: "absolute", left: 284, top: 49, width: 66, height: 66, borderRadius: "50%", background: "rgb(224,255,124)" }} />
      {/* wordmark — custom display glyph approximated with heavy oblique type */}
      <div style={{ position: "absolute", left: 0, top: 128, width: 402, textAlign: "center", animation: "wmDrop .7s cubic-bezier(.22,.61,.36,1) .1s both" }}>
        <div style={{ fontFamily: F, fontWeight: 900, fontStyle: "italic", fontSize: 86, lineHeight: 0.95,
          color: "rgb(43,43,43)", letterSpacing: "-3px", transform: "scaleX(1.06)" }}>GRIP</div>
        <div style={{ fontFamily: F, fontWeight: 600, fontSize: 18, color: "rgb(43,43,43)", marginTop: 0, letterSpacing: "0.3px" }}>Know Before You Go.</div>
      </div>
      {/* ground band + shadow + animated rider */}
      <div style={{ position: "absolute", left: 0, top: 558, width: 402, height: 316, background: "rgb(206,230,253)" }} />
      <div style={{ position: "absolute", left: 47, top: 597, width: 308, height: 14, borderRadius: "50%", background: C.stroke }} />
      <img src={`${A}bike-rider.gif`} alt="Cyclist riding" style={{ position: "absolute", left: -209, top: 68, width: 820, height: "auto", pointerEvents: "none", animation: "riderRoll .8s cubic-bezier(.22,.61,.36,1) .3s both" }} />
      {/* buttons */}
      <Press onClick={() => go("map")} style={{ position: "absolute", left: 60, top: 645, width: 282, height: 46,
        borderRadius: 14, background: C.blue, display: "flex", alignItems: "center", justifyContent: "center",
        boxShadow: "0 8px 20px rgba(21,96,243,0.3)", animation: "riseUp .6s cubic-bezier(.22,.61,.36,1) .6s both" }}>
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>Log In</span>
      </Press>
      <Press onClick={() => go("map")} style={{ position: "absolute", left: 61, top: 704, width: 280, height: 46,
        borderRadius: 14, background: "#fff", boxShadow: `inset 0 0 0 0.5px ${C.blue}`,
        display: "flex", alignItems: "center", justifyContent: "center", animation: "riseUp .6s cubic-bezier(.22,.61,.36,1) .7s both" }}>
        {/* TODO: Sign Up screen was not prototyped in the file — routes to map */}
        <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.ink }}>Sign Up</span>
      </Press>
      <div style={{ position: "absolute", left: 0, top: 772, width: 402, display: "flex", flexDirection: "column", alignItems: "center", gap: 7, animation: "riseUp .6s cubic-bezier(.22,.61,.36,1) .8s both" }}>
        <span style={{ fontFamily: F, fontWeight: 500, fontSize: 11, color: C.gray, letterSpacing: "0.3px" }}>Follow us</span>
        <div style={{ display: "flex", justifyContent: "center", gap: 10 }}>
          {/* Instagram */}
          <Press scale={0.9} style={{ width: 34, height: 34, borderRadius: 17, background: "#fff", boxShadow: `inset 0 0 0 1px ${C.stroke}, 0 4px 10px rgba(21,96,243,0.08)`, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2"><rect x="3" y="3" width="18" height="18" rx="5.5"/><circle cx="12" cy="12" r="4"/><circle cx="17.4" cy="6.6" r="1.1" fill={C.blue} stroke="none"/></svg>
          </Press>
          {/* Facebook */}
          <Press scale={0.9} style={{ width: 34, height: 34, borderRadius: 17, background: "#fff", boxShadow: `inset 0 0 0 1px ${C.stroke}, 0 4px 10px rgba(21,96,243,0.08)`, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <svg width="17" height="17" viewBox="0 0 24 24" fill={C.blue}><path d="M14 8.5h2.2V5.4c-.4-.05-1.6-.16-3-.16-3 0-5 1.8-5 5.2v2.6H5.4v3.4h2.8V25h3.5v-8.6h2.9l.5-3.4h-3.4v-2.3c0-1 .3-1.7 1.8-1.7Z" transform="translate(0 -2)"/></svg>
          </Press>
          {/* X / Twitter */}
          <Press scale={0.9} style={{ width: 34, height: 34, borderRadius: 17, background: "#fff", boxShadow: `inset 0 0 0 1px ${C.stroke}, 0 4px 10px rgba(21,96,243,0.08)`, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill={C.blue}><path d="M18.2 2.5h3.3l-7.3 8.3 8.5 11.2h-6.6l-5.2-6.8-6 6.8H1.6l7.8-8.9L1.2 2.5h6.8l4.7 6.2 5.5-6.2Zm-1.2 17.6h1.8L7.1 4.3H5.2L17 20.1Z"/></svg>
          </Press>
        </div>
      </div>
    </div>
  );
}

/* ---------- Leaflet (real map tiles) loader + hook ----------
 * Loads Leaflet from CDN once, then gives each map screen a live map instance.
 * Basemap: CARTO Voyager (Google-Maps-like). Needs internet; no API key. */
const LEAFLET_CSS = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
const LEAFLET_JS  = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
const TILE_URL = "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png";
const TILE_ATTR = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/attributions">CARTO</a>';
let _leafletPromise = null;
function loadLeaflet() {
  if (typeof window !== "undefined" && window.L) return Promise.resolve(window.L);
  if (_leafletPromise) return _leafletPromise;
  _leafletPromise = new Promise((resolve, reject) => {
    if (!document.querySelector(`link[href="${LEAFLET_CSS}"]`)) {
      const link = document.createElement("link");
      link.rel = "stylesheet"; link.href = LEAFLET_CSS;
      document.head.appendChild(link);
    }
    const s = document.createElement("script");
    s.src = LEAFLET_JS; s.async = true;
    s.onload = () => resolve(window.L);
    s.onerror = () => reject(new Error("Leaflet failed to load"));
    document.head.appendChild(s);
  });
  return _leafletPromise;
}

/* init a Leaflet map on `ref`; returns { L, map, ready } */
function useLeaflet(ref, { center, zoom, interactive = true } = {}) {
  const [s, setS] = useState({ L: null, map: null, ready: false, error: false });
  useEffect(() => {
    let map, cancelled = false;
    const failTimer = setTimeout(() => { if (!cancelled) setS((p) => p.ready ? p : { ...p, error: true }); }, 12000);
    loadLeaflet().then((L) => {
      if (cancelled || !ref.current) return;
      map = L.map(ref.current, {
        center, zoom, zoomControl: false, attributionControl: false,
        dragging: interactive, scrollWheelZoom: interactive, doubleClickZoom: interactive,
        boxZoom: interactive, keyboard: interactive, tap: interactive, zoomSnap: 0.5,
      });
      L.tileLayer(TILE_URL, { maxZoom: 20, subdomains: "abcd", detectRetina: true }).addTo(map);
      setTimeout(() => map.invalidateSize(), 80);
      clearTimeout(failTimer);
      setS({ L, map, ready: true, error: false });
    }).catch(() => { clearTimeout(failTimer); setS((p) => ({ ...p, ready: false, error: true })); });
    return () => { cancelled = true; clearTimeout(failTimer); if (map) map.remove(); };
  }, []);   // eslint-disable-line react-hooks/exhaustive-deps
  return s;
}

/* ---- divIcon HTML builders (rendered inside Leaflet markers) ---- */
function pinHTML(count, selected, dimmed) {
  const col = count === 0 ? C.grayMk : count <= 2 ? C.amber : C.green;
  const ring = selected ? `<div style="position:absolute;left:50%;top:5px;width:30px;height:30px;margin-left:-15px;border-radius:50%;background:${col};animation:pulseRing 1.8s ease-out infinite"></div>` : "";
  return `<div style="position:relative;width:40px;height:52px;transform-origin:50% 100%;transform:scale(${selected ? 1.18 : 1});filter:drop-shadow(0 ${selected ? 9 : 3}px ${selected ? 10 : 4}px rgba(0,0,0,${selected ? 0.32 : 0.25}));opacity:${dimmed ? 0.28 : count === 0 ? 0.55 : 1};transition:opacity .25s">${ring}<svg width="40" height="52" viewBox="0 0 40 52" style="position:relative;display:block"><path d="M20 51 C8 33 1 27 1 19 A19 19 0 1 1 39 19 C39 27 32 33 20 51 Z" fill="${col}" stroke="#fff" stroke-width="${selected ? 3 : 2}"/><text x="20" y="26" fill="#fff" font-family="${F}" font-weight="700" font-size="19" text-anchor="middle">${count}</text></svg></div>`;
}
function clusterHTML(total) {
  return `<div style="min-width:56px;height:56px;padding:0 8px;border-radius:28px;background:${C.blue};box-sizing:border-box;box-shadow:inset 0 0 0 3px rgba(255,255,255,.85),0 6px 14px rgba(0,0,0,.3);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-family:${F};animation:gripPop .35s ease both"><span style="font-weight:700;font-size:21px;line-height:1">${total}</span><span style="font-weight:600;font-size:9px;opacity:.85;margin-top:1px">free</span></div>`;
}
function userHTML() {
  return `<div style="position:relative;width:28px;height:28px"><div style="position:absolute;inset:0;border-radius:50%;background:${C.blue};opacity:.3;animation:pulseRing 2s ease-out infinite"></div><div style="position:absolute;left:4px;top:4px;width:20px;height:20px;border-radius:50%;background:${C.blue};box-shadow:0 0 0 3px #fff,0 2px 5px rgba(0,0,0,.35)"></div></div>`;
}
const makeIcon = (L, html, w, h, anchor) => L.divIcon({ html, className: "", iconSize: [w, h], iconAnchor: anchor });

/* derive a realistic, deterministic turn-by-turn snippet per spot */
function navDirections(spot) {
  const raw = String(spot.name || "");
  let street = raw.replace(/,.*$/, "").replace(/\s*\d+\s*$/, "").trim() || "your route";
  const suffix = /\b(st\.?|street|blvd|boulevard|road|rd\.?)$/i.test(street) ? "" : " St.";
  const label = street + suffix;
  let h = 0; for (let i = 0; i < raw.length; i++) h = (h * 31 + raw.charCodeAt(i)) >>> 0;
  const primaries = [
    { verb: "Continue on", icon: "straight" },
    { verb: "Head down", icon: "straight" },
    { verb: "Turn right onto", icon: "right" },
    { verb: "Turn left onto", icon: "left" },
    { verb: "Stay on", icon: "straight" },
    { verb: "Bear left onto", icon: "slightLeft" },
  ];
  const nexts = [
    { label: "Then", icon: "left" },
    { label: "Then", icon: "right" },
    { label: "Then", icon: "roundabout" },
    { label: "In 200 m", icon: "left" },
    { label: "In 150 m", icon: "right" },
    { label: "Then", icon: "arrive" },
  ];
  const p = primaries[h % primaries.length] || primaries[0];
  const n = nexts[Math.abs(h >> 3) % nexts.length] || nexts[0];
  return { text: `${p.verb} ${label}`, icon: p.icon, nextLabel: n.label, nextIcon: n.icon };
}

function DirIcon({ type, size = 24 }) {
  const p = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "#fff", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round" };
  if (type === "straight") return <svg {...p}><path d="M12 20V5M6 11l6-6 6 6"/></svg>;
  if (type === "left") return <svg {...p}><path d="M16 20v-7a4 4 0 0 0-4-4H6"/><path d="M9 5 5 9l4 4"/></svg>;
  if (type === "right") return <svg {...p}><path d="M8 20v-7a4 4 0 0 1 4-4h6"/><path d="M15 5l4 4-4 4"/></svg>;
  if (type === "slightLeft") return <svg {...p}><path d="M14 20v-6a4 4 0 0 0-1.2-2.9L7 5.5"/><path d="M7 11V5h6"/></svg>;
  if (type === "roundabout") return <svg {...p}><circle cx="11" cy="12" r="5"/><path d="M11 20v-3M16 9l1.5-3 1.5 3"/></svg>;
  if (type === "arrive") return <svg {...p}><path d="M12 21s-7-5.6-7-11a7 7 0 0 1 14 0c0 5.4-7 11-7 11Z"/><circle cx="12" cy="10" r="2.2"/></svg>;
  return <svg {...p}><path d="M12 20V5M6 11l6-6 6 6"/></svg>;
}
function riderHTML() {
  return `<div style="position:relative;width:22px;height:22px"><div style="position:absolute;inset:-6px;border-radius:50%;background:${C.blue};opacity:.25;animation:pulseRing 1.8s ease-out infinite"></div><div style="position:absolute;left:2px;top:2px;width:18px;height:18px;border-radius:50%;background:${C.blue};box-shadow:0 0 0 3px #fff,0 2px 6px rgba(0,0,0,.3)"></div></div>`;
}

/* MapScreen + NavScreen render their markers as Leaflet divIcons (see builders above). */

function MapScreen({ go, startNav, onPhotoOpen }) {
  const elRef = useRef(null);
  const { L, map, ready, error } = useLeaflet(elRef, { center: USER_LL, zoom: 16 });
  const [idx, setIdx] = useState(0);
  const [clustered, setClustered] = useState(false);
  const [filters, setFilters] = useState([]);
  const [saved, setSaved] = useState([]);
  const [toast, setToast] = useState(null);
  const [freshSec, setFreshSec] = useState(0);
  // live-data freshness: ticks up, "refreshes" (resets) every ~30s with a pulse
  useEffect(() => {
    const id = setInterval(() => setFreshSec((s) => (s >= 29 ? 0 : s + 1)), 1000);
    return () => clearInterval(id);
  }, []);
  const matchSpot = (s) => filters.every((f) => (s.tags || []).includes(f));
  const shownList = NEAR_SPOTS.map((_, i) => i).filter((i) => matchSpot(NEAR_SPOTS[i]));
  const pos = Math.max(0, shownList.indexOf(idx));
  // keep selection valid when filters change
  useEffect(() => {
    if (!shownList.includes(idx)) setIdx(shownList[0] ?? 0);
    setSheetY(0);
  }, [filters]);   // eslint-disable-line react-hooks/exhaustive-deps
  const toggleFilter = (k) => setFilters((f) => f.includes(k) ? f.filter((x) => x !== k) : [...f, k]);
  const toggleSave = (name) => {
    setSaved((s) => {
      const has = s.includes(name);
      setToast(has ? null : `${name} saved to Favorites`);
      if (!has) setTimeout(() => setToast(null), 1900);
      return has ? s.filter((x) => x !== name) : [...s, name];
    });
  };
  const layers = useRef({ pins: [], pinLayer: null, clusterLayer: null });
  const cdrag = useRef({ x: 0, active: false });
  const [sheetY, setSheetY] = useState(0);
  const sheetDrag = useRef({ y: 0, base: 0, active: false });
  const SHEET_COLLAPSE = 300;
  const hDown = (e) => { e.currentTarget.setPointerCapture(e.pointerId); sheetDrag.current = { y: e.clientY, base: sheetY, active: true }; };
  const hMove = (e) => { if (!sheetDrag.current.active) return; const ny = Math.max(0, Math.min(SHEET_COLLAPSE, sheetDrag.current.base + (e.clientY - sheetDrag.current.y))); setSheetY(ny); };
  const hUp = (e) => { if (!sheetDrag.current.active) return; sheetDrag.current.active = false; const drag = Math.abs(e.clientY - sheetDrag.current.y); if (drag < 8) { setSheetY((y) => (y === 0 ? SHEET_COLLAPSE : 0)); } else { setSheetY((y) => (y > SHEET_COLLAPSE / 2 ? SHEET_COLLAPSE : 0)); } };
  const greet = "Good Morning";
  const clamp = (v, a, b) => Math.max(a, Math.min(b, v));

  const selectSpot = (i, center) => {
    i = clamp(i, 0, NEAR_SPOTS.length - 1);
    setIdx(i);
    setSheetY(0);   // re-open the drawer if it was dragged down
    if (center && map) map.panTo(SPOTS_GEO[i], { animate: true, duration: 0.5 });
  };

  /* build markers once the map is ready */
  useEffect(() => {
    if (!ready) return;
    const pinLayer = L.layerGroup().addTo(map);
    const clusterLayer = L.layerGroup();
    FULL_GEO.forEach((ll) => L.marker(ll, { icon: makeIcon(L, pinHTML(0, false), 40, 52, [20, 52]), interactive: false }).addTo(pinLayer));
    const pins = SPOTS_GEO.map((ll, i) => {
      const m = L.marker(ll, { icon: makeIcon(L, pinHTML(NEAR_SPOTS[i].spots, i === 0), 40, 52, [20, 52]), zIndexOffset: i === 0 ? 1000 : 0 });
      m.on("click", () => selectSpot(i, true));
      m.addTo(pinLayer);
      return m;
    });
    CLUSTERS.forEach((c) => {
      const lls = c.members.map((m) => SPOTS_GEO[m]);
      const ctr = centroid(lls);
      const total = c.members.reduce((a, m) => a + NEAR_SPOTS[m].spots, 0);
      const cm = L.marker(ctr, { icon: makeIcon(L, clusterHTML(total), 56, 56, [28, 28]) });
      cm.on("click", () => map.flyTo(ctr, 15, { duration: 0.6 }));
      cm.addTo(clusterLayer);
    });
    L.marker(USER_LL, { icon: makeIcon(L, userHTML(), 28, 28, [14, 14]), interactive: false, zIndexOffset: -500 }).addTo(map);
    layers.current = { pins, pinLayer, clusterLayer };
    const onZoom = () => setClustered(map.getZoom() < 14);
    map.on("zoomend", onZoom); onZoom();
    return () => { map.off("zoomend", onZoom); };
  }, [ready]);   // eslint-disable-line react-hooks/exhaustive-deps

  /* swap clusters ⇄ individual pins on zoom */
  useEffect(() => {
    if (!ready) return;
    const { pinLayer, clusterLayer } = layers.current;
    if (!pinLayer) return;
    if (clustered) { map.removeLayer(pinLayer); clusterLayer.addTo(map); }
    else { map.removeLayer(clusterLayer); pinLayer.addTo(map); }
  }, [clustered, ready]);   // eslint-disable-line react-hooks/exhaustive-deps

  /* restyle pins: highlight selected, dim non-matching when filters active */
  useEffect(() => {
    if (!ready) return;
    layers.current.pins.forEach((m, i) => m.setIcon(makeIcon(L, pinHTML(NEAR_SPOTS[i].spots, i === idx, filters.length > 0 && !matchSpot(NEAR_SPOTS[i])), 40, 52, [20, 52])));
  }, [idx, ready, filters]);   // eslint-disable-line react-hooks/exhaustive-deps

  const zoom = (d) => { if (map) map.setZoom(map.getZoom() + d); };
  /* center the user dot higher than dead-center, so it stays visible above the open sheet */
  const centerOnUser = (animate) => {
    if (!map) return;
    const z = 16;
    const pt = map.project(USER_LL, z);
    pt.y += 150;  // shift map center down → user dot renders ~150px higher on screen
    const c = map.unproject(pt, z);
    map.flyTo(c, z, { duration: animate ? 0.6 : 0, animate });
  };
  /* recenter map when sheet is fully open */
  useEffect(() => {
    if (ready && sheetY === 0) centerOnUser(true);
  }, [sheetY, ready]);   // eslint-disable-line react-hooks/exhaustive-deps

  const recenterMap = () => centerOnUser(true);

  /* carousel swipe — steps within the filtered list */
  const stepSpot = (delta) => {
    const p = shownList.indexOf(idx);
    const np = clamp(p + delta, 0, shownList.length - 1);
    const ni = shownList[np];
    if (ni == null) return;
    setIdx(ni); setSheetY(0);
    if (map) map.panTo(SPOTS_GEO[ni], { animate: true, duration: 0.5 });
  };
  const cDown = (e) => { cdrag.current = { x: e.clientX, active: true }; };
  const cUp = (e) => { if (!cdrag.current.active) return; const dx = e.clientX - cdrag.current.x; cdrag.current.active = false; if (dx < -40) stepSpot(1); else if (dx > 40) stepSpot(-1); };

  return (
    <div style={{ position: "absolute", inset: 0, background: C.land, overflow: "hidden" }}>
      <StatusBar />
      {/* real map tiles (Leaflet + CARTO) */}
      <div ref={elRef} className="gripmap-sheeted" style={{ position: "absolute", left: 0, top: 0, width: 402, height: 782, background: "#e8eef2", zIndex: 1 }} />
      {!ready && !error && (
        <div style={{ position: "absolute", left: 0, top: 0, width: 402, height: 782, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14, fontFamily: F }}>
          <div style={{ width: 30, height: 30, borderRadius: "50%", border: `3px solid rgba(21,96,243,0.2)`, borderTopColor: C.blue, animation: "spin .8s linear infinite" }} />
          <span style={{ fontWeight: 600, fontSize: 14, color: C.gray }}>Loading live map…</span>
        </div>
      )}
      {error && (
        <div style={{ position: "absolute", left: 0, top: 0, width: 402, height: 782, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14, padding: "0 50px", boxSizing: "border-box", textAlign: "center" }}>
          <div style={{ width: 72, height: 72, borderRadius: "50%", background: "rgba(255,101,101,0.1)", display: "flex", alignItems: "center", justifyContent: "center" }}>
            <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke={C.red} strokeWidth="2" strokeLinecap="round"><path d="M1 1l22 22M16.7 16.7A11 11 0 0 1 12 18M5 12.5a9 9 0 0 1 4-2.3M8 8.5A9 9 0 0 1 19 12.5M2 8.8a15 15 0 0 1 3.6-2.3"/><circle cx="12" cy="20" r="0.6" fill={C.red}/></svg>
          </div>
          <div style={{ fontFamily: F, fontWeight: 700, fontSize: 19, color: C.ink }}>Can't reach the live map</div>
          <div style={{ fontFamily: F, fontWeight: 500, fontSize: 14, color: C.gray, lineHeight: 1.45 }}>Check your connection — GRIP needs to be online to show real-time rack availability.</div>
          <Press onClick={() => window.location.reload()} scale={0.96} style={{ marginTop: 6, padding: "12px 24px", borderRadius: 14, background: C.blue, boxShadow: "0 8px 18px rgba(21,96,243,0.28)" }}>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: "#fff" }}>Try again</span>
          </Press>
        </div>
      )}

      {/* availability legend (top-left) */}
      <div style={{ position: "absolute", left: 18, top: 66, zIndex: 25, display: "flex", alignItems: "center", gap: 12, padding: "9px 14px", borderRadius: 22,
        background: "rgba(255,255,255,0.95)", boxShadow: "0 2px 8px rgba(0,0,0,0.15)" }}>
        {[["Free", C.green], ["Filling", C.amber], ["Full", C.grayMk]].map(([label, col]) => (
          <div key={label} style={{ display: "flex", alignItems: "center", gap: 5 }}>
            <div style={{ width: 9, height: 9, borderRadius: "50%", background: col }} />
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 12, color: C.ink }}>{label}</span>
          </div>
        ))}
      </div>

      {/* zoom + re-center controls (top-right) */}
      <div style={{ position: "absolute", left: 344, top: 66, display: "flex", flexDirection: "column", gap: 8, zIndex: 25 }}>
        <Press onClick={() => zoom(1)} scale={0.85} style={{ width: 42, height: 42, borderRadius: 12, background: "rgba(255,255,255,0.95)", boxShadow: "0 2px 8px rgba(0,0,0,0.18)", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: F, fontWeight: 600, fontSize: 24, color: C.ink }}>+</Press>
        <Press onClick={() => zoom(-1)} scale={0.85} style={{ width: 42, height: 42, borderRadius: 12, background: "rgba(255,255,255,0.95)", boxShadow: "0 2px 8px rgba(0,0,0,0.18)", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: F, fontWeight: 600, fontSize: 28, color: C.ink }}>−</Press>
        <Press onClick={recenterMap} scale={0.85} style={{ width: 42, height: 42, borderRadius: 21, background: "rgba(255,255,255,0.95)", boxShadow: "0 2px 8px rgba(0,0,0,0.18)", display: "flex", alignItems: "center", justifyContent: "center" }}>
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="rgb(30,30,30)" strokeWidth="1.6"><circle cx="12" cy="12" r="3.5"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3" strokeLinecap="round"/></svg>
        </Press>
      </div>

      {/* spots card — draggable bottom sheet */}
      <div style={{ position: "absolute", left: 0, bottom: 0, width: 402, height: 488, zIndex: 20,
        background: "rgba(255,255,255,0.94)", backdropFilter: "blur(10px)", borderRadius: "18px 18px 0 0",
        boxShadow: "0 -8px 30px rgba(0,0,0,0.10)", boxSizing: "border-box",
        transform: `translateY(${sheetY}px)`, transition: sheetDrag.current.active ? "none" : "transform .35s cubic-bezier(.4,0,.2,1)" }}>
        {/* drag handle */}
        <div onPointerDown={hDown} onPointerMove={hMove} onPointerUp={hUp} onPointerCancel={hUp}
          style={{ height: 24, display: "flex", alignItems: "center", justifyContent: "center", cursor: "grab", touchAction: "none" }}>
          <div style={{ width: 44, height: 5, borderRadius: 3, background: "rgba(0,0,0,0.18)" }} />
        </div>
        {/* greeting + live freshness (one row) */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, flexWrap: "wrap" }}>
          <span style={{ fontFamily: F, fontWeight: 600, fontSize: 19, color: C.ink }}>{greet}, Laura! 👋</span>
          <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "4px 10px", borderRadius: 14, background: "rgba(19,159,12,0.1)" }}>
            <span style={{ width: 7, height: 7, borderRadius: "50%", background: C.green, animation: "livePulse 1.8s ease-out infinite" }} />
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 12, color: C.green }}>Live{freshSec === 0 ? "" : ` · ${freshSec}s`}</span>
          </div>
        </div>

        {/* filter chips */}
        <div style={{ display: "flex", justifyContent: "center", gap: 8, marginTop: 8, padding: "0 24px" }}>
          {[["covered", "Covered", "umbrella"], ["secure", "Secure", "lock"], ["transit", "Near transit", "transit"]].map(([k, label, ic]) => {
            const on = filters.includes(k);
            return (
              <Press key={k} onClick={() => toggleFilter(k)} scale={0.94} style={{ display: "flex", alignItems: "center", gap: 6, padding: "8px 13px", borderRadius: 20,
                background: on ? C.blue : "#fff", boxShadow: on ? "none" : `inset 0 0 0 1.5px rgba(0,0,0,0.12)` }}>
                <FilterIcon name={ic} color={on ? "#fff" : C.gray} />
                <span style={{ fontFamily: F, fontWeight: 600, fontSize: 13, color: on ? "#fff" : C.ink }}>{label}</span>
              </Press>
            );
          })}
        </div>

        {shownList.length > 0 ? (
          <>
            {/* horizontal carousel */}
            <div onPointerDown={cDown} onPointerUp={cUp} onPointerLeave={cUp}
              style={{ marginTop: 8, height: 266, overflow: "hidden", touchAction: "pan-y" }}>
              <div style={{ display: "flex", gap: 30, paddingLeft: 30, paddingTop: 2, paddingBottom: 2,
                transform: `translateX(${-pos * 372}px)`, transition: "transform .42s cubic-bezier(.22,.61,.36,1)" }}>
                {shownList.map((i, n) => (
                  <div key={i} style={{ flex: "none", width: 342, opacity: i === idx ? 1 : 0.5,
                    transform: i === idx ? "scale(1)" : "scale(0.96)", transition: "opacity .3s, transform .3s" }}>
                    <SpotCard s={NEAR_SPOTS[i]} eyebrow={n === 0 ? "Nearest Available" : "Available Spot"} onNavigate={() => startNav(NEAR_SPOTS[i])} onPhotoOpen={onPhotoOpen}
                      saved={saved.includes(NEAR_SPOTS[i].name)} onSave={() => toggleSave(NEAR_SPOTS[i].name)} />
                  </div>
                ))}
              </div>
            </div>
            {/* crumbs progress — active crumb is wider */}
            <div style={{ display: "flex", justifyContent: "center", alignItems: "center", gap: 6, marginTop: 4, padding: "0 24px", flexWrap: "wrap" }}>
              {shownList.map((i) => (
                <div key={i} onClick={() => selectSpot(i, true)} style={{ cursor: "pointer",
                  width: i === idx ? 22 : 7, height: 7, borderRadius: 4,
                  background: i === idx ? C.blue : "rgba(0,0,0,0.16)", transition: "width .25s, background .25s" }} />
              ))}
            </div>
          </>
        ) : (
          /* empty state */
          <div style={{ marginTop: 30, padding: "0 36px", display: "flex", flexDirection: "column", alignItems: "center", gap: 12, textAlign: "center" }}>
            <div style={{ width: 64, height: 64, borderRadius: "50%", background: "rgba(21,96,243,0.08)", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5M8 11h6" strokeLinecap="round"/></svg>
            </div>
            <div style={{ fontFamily: F, fontWeight: 600, fontSize: 17, color: C.ink }}>No racks match those filters</div>
            <div style={{ fontFamily: F, fontWeight: 500, fontSize: 14, color: C.gray, lineHeight: 1.4 }}>Try removing a filter to see more available spots nearby.</div>
            <Press onClick={() => setFilters([])} scale={0.96} style={{ marginTop: 4, padding: "11px 20px", borderRadius: 12, background: C.blue, boxShadow: "0 6px 16px rgba(21,96,243,0.28)" }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: "#fff" }}>Clear filters</span>
            </Press>
          </div>
        )}
      </div>

      {/* save toast */}
      {toast && (
        <div style={{ position: "absolute", left: "50%", top: "50%", transform: "translate(-50%, -50%)", zIndex: 60,
          display: "flex", alignItems: "center", gap: 9, padding: "13px 20px", borderRadius: 24, background: "rgba(20,20,20,0.92)", boxShadow: "0 8px 24px rgba(0,0,0,0.3)", animation: "gripPop .3s ease both", maxWidth: 320 }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill={C.lime} stroke={C.lime} strokeWidth="1.5" style={{ flex: "none" }}><path d="M12 21s-8-5.2-8-11a4.5 4.5 0 0 1 8-2.8A4.5 4.5 0 0 1 20 10c0 5.8-8 11-8 11Z"/></svg>
          <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: "#fff" }}>{toast}</span>
        </div>
      )}
      <BottomNav active="map" go={go} />
    </div>
  );
}

const FAV = [
  { label: "Home",  addr: "Maze 40",        grad: "rgb(224,255,124)", ring: "rgb(150,190,40)" },
  { label: "Work",  addr: "Rothschild 22",  grad: "rgb(255,218,119)", ring: "rgb(220,170,30)" },
  { label: "Gym",   addr: "Dizengoff 101",  grad: "rgb(114,162,255)", ring: "rgb(21,96,243)" },
  { label: "Park",  addr: "Yarkon Park",    grad: "rgb(130,208,126)", ring: "rgb(19,159,12)" },
];
function FavIcon({ label }) {
  const co = "rgb(0,0,0)";
  if (label === "Home") return <svg width="34" height="33" viewBox="0 0 34 33" fill={co}><path d="M4.25 19.6V32.6h25.5V19.6L17 7.7 4.25 19.6Zm15.58 10.15h-5.66V21.25h5.66v8.5ZM34 15.8 32.07 17.9 17 3.87 1.93 17.85 0 15.77 17 0l17 15.8Z"/></svg>;
  if (label === "Work") return <svg width="34" height="29" viewBox="0 0 34 29" fill="none" stroke={co} strokeWidth="2.2"><rect x="1" y="7" width="32" height="20" rx="2.5"/><path d="M11 7V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3M1 15h32"/></svg>;
  if (label === "Gym") return <svg width="44" height="24" viewBox="0 0 44 24" fill="none" stroke={co} strokeWidth="2.4" strokeLinecap="round"><path d="M6 6v12M11 4v16M33 4v16M38 6v12M11 12h22"/></svg>;
  if (label === "Park") return <svg width="33" height="39" viewBox="0 0 33 39" fill={co}><path d="M16.5 0 4 16h6L2 27h11v12h7V27h11l-8-11h6L16.5 0Z"/></svg>;
  return <svg width="35" height="31" viewBox="0 0 24 22" fill="none" stroke={co} strokeWidth="2"><path d="M12 21S2 14.5 2 8a5 5 0 0 1 10-2 5 5 0 0 1 10 2c0 6.5-10 13-10 13Z"/></svg>;
}
function FavoritesScreen({ go, openSearch }) {
  const [editing, setEditing] = useState(false);
  const [favs, setFavs] = useState(FAV);
  const [recents, setRecents] = useState(RECENTS);

  // deterministic meta for a recent (spots + distance) so each row reads like a real place
  const metaFor = (name) => {
    let h = 0; for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
    const spots = h % 4;                          // 0..3 (0 = full)
    const km = (0.2 + (h % 18) / 10).toFixed(1);  // 0.2 .. 1.9
    return { spots, km };
  };

  const removeFav = (label) => setFavs((f) => f.filter((x) => x.label !== label));
  const removeRecent = (name) => setRecents((r) => r.filter((x) => x !== name));

  return (
    <div style={{ position: "absolute", inset: 0, background: "#fff", overflow: "hidden", display: "flex", flexDirection: "column" }}>
      <StatusBar />
      {/* fixed top: search + favorites + recents header */}
      <div style={{ flex: "none", padding: "62px 0 0" }}>
        {/* search bar */}
        <div style={{ padding: "0 26px" }}>
          <Press onClick={() => openSearch("")} scale={0.99}
            style={{ height: 56, borderRadius: 16, background: "#fff", boxShadow: `inset 0 0 0 1px ${C.stroke}, 0 2px 8px rgba(21,96,243,0.06)`,
              display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 16px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2.2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5" strokeLinecap="round"/></svg>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "rgba(0,0,0,0.45)" }}>Search available spot</span>
            </div>
            <svg width="16" height="22" viewBox="0 0 16 22" fill={C.blue}><rect x="5" y="1" width="6" height="12" rx="3"/><path d="M2 10a6 6 0 0 0 12 0M8 16v4M5 21h6" stroke={C.blue} strokeWidth="1.6" fill="none" strokeLinecap="round"/></svg>
          </Press>
        </div>

        {/* favorites header */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "30px 26px 14px" }}>
          <span style={{ fontFamily: F, fontWeight: 700, fontSize: 21, color: C.ink }}>Favorites</span>
          <Press onClick={() => setEditing((e) => !e)} scale={0.92}
            style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 12px", borderRadius: 20,
              background: editing ? C.blue : "rgba(21,96,243,0.08)" }}>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: editing ? "#fff" : C.blue }}>{editing ? "Done" : "Edit"}</span>
          </Press>
        </div>

        {/* favorites row — peek-cut last card hints horizontal scroll */}
        <div className="fav-scroll" style={{ display: "flex", gap: 14, overflowX: "auto", padding: "4px 26px 6px", WebkitOverflowScrolling: "touch" }}>
          {favs.map((f) => (
            <div key={f.label} style={{ flex: "none", width: 66, display: "flex", flexDirection: "column", alignItems: "center", gap: 7, position: "relative" }}>
              <Press onClick={() => editing ? removeFav(f.label) : openSearch(f.addr)} scale={0.92}
                style={{ width: 66, height: 66, borderRadius: 16, boxShadow: `inset 0 0 0 1.5px ${f.ring}`,
                  background: `linear-gradient(160deg, ${f.grad} 0%, #fff 115%)`,
                  display: "flex", alignItems: "center", justifyContent: "center", position: "relative", overflow: "visible" }}>
                <FavIcon label={f.label} />
                {editing && (
                  <div style={{ position: "absolute", right: 3, top: 3, width: 22, height: 22, borderRadius: 11, background: C.red,
                    display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 2px 6px rgba(0,0,0,0.25)" }}>
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3"><path d="M5 12h14" strokeLinecap="round"/></svg>
                  </div>
                )}
              </Press>
              <div style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: C.ink, textAlign: "center" }}>{f.label}</div>
            </div>
          ))}
          {/* add-favorite card */}
          <div style={{ flex: "none", width: 66, display: "flex", flexDirection: "column", alignItems: "center", gap: 7 }}>
            <Press onClick={() => openSearch("")} scale={0.92}
              style={{ width: 66, height: 66, borderRadius: 16, border: `1.5px dashed ${C.stroke}`, boxSizing: "border-box",
                background: "rgba(21,96,243,0.03)", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2.4"><path d="M12 5v14M5 12h14" strokeLinecap="round"/></svg>
            </Press>
            <div style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: C.blue }}>Add</div>
          </div>
        </div>

        {/* recents header */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "24px 26px 6px" }}>
          <span style={{ fontFamily: F, fontWeight: 700, fontSize: 21, color: C.ink }}>Recents</span>
          {recents.length > 0 && (
            <Press onClick={() => setRecents([])} scale={0.95} style={{ padding: "4px 8px" }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 13, color: C.gray }}>Clear all</span>
            </Press>
          )}
        </div>
      </div>

      {/* scrollable recents list */}
      <div style={{ flex: 1, overflowY: "auto", padding: "0 26px 96px" }}>
          {recents.length === 0 && (
            <div style={{ fontFamily: F, fontWeight: 500, fontSize: 14, color: C.gray, padding: "18px 0", textAlign: "center" }}>No recent searches</div>
          )}
          {recents.map((r, i) => {
            const m = metaFor(r);
            const dot = m.spots === 0 ? C.grayMk : m.spots <= 2 ? C.amber : C.green;
            return (
              <Press key={r} onClick={() => editing ? removeRecent(r) : openSearch(r)} scale={0.99}
                style={{ display: "flex", alignItems: "center", gap: 13, padding: "14px 0",
                  borderBottom: i < recents.length - 1 ? "1px solid rgba(0,0,0,0.07)" : "none" }}>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={C.gray} strokeWidth="1.8"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.ink, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r}</div>
                  <div style={{ display: "flex", alignItems: "center", gap: 7, marginTop: 3 }}>
                    <span style={{ width: 7, height: 7, borderRadius: 4, background: dot, flex: "none" }} />
                    <span style={{ fontFamily: F, fontWeight: 500, fontSize: 12, color: C.gray, whiteSpace: "nowrap" }}>
                      {m.spots === 0 ? "Full" : `${m.spots} spot${m.spots > 1 ? "s" : ""}`} · {m.km} km
                    </span>
                  </div>
                </div>
                {editing ? (
                  <div style={{ width: 26, height: 26, borderRadius: 13, background: "rgba(255,101,101,0.12)", display: "flex", alignItems: "center", justifyContent: "center", flex: "none" }}>
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={C.red} strokeWidth="2.6"><path d="M6 6l12 12M18 6 6 18" strokeLinecap="round"/></svg>
                  </div>
                ) : (
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={C.grayMk} strokeWidth="2.2" style={{ flex: "none" }}><path d="M9 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>
                )}
              </Press>
            );
          })}
        </div>
      <BottomNav active="favorites" go={go} />
    </div>
  );
}

function SearchScreen({ dest, back, startNav, onPhotoOpen }) {
  const [query, setQuery] = useState(dest || "");
  const [kbOpen, setKbOpen] = useState(true);
  const [caps, setCaps] = useState(true);
  const inputRef = useRef(null);
  // open the keyboard on mount
  useEffect(() => {
    const t = setTimeout(() => setKbOpen(true), 340);
    return () => clearTimeout(t);
  }, []);
  const type = (ch) => { setQuery((q) => q + (caps ? ch.toUpperCase() : ch)); if (caps) setCaps(false); };
  const backspace = () => setQuery((q) => q.slice(0, -1));
  const space = () => setQuery((q) => q + " ");
  const results = buildResults(query || dest);
  const q = query.trim().toLowerCase();
  const shown = q ? results.filter((s) => s.name.toLowerCase().includes(q)) : results;
  const KB_H = 300;
  return (
    <div style={{ position: "absolute", inset: 0, background: "#fff", overflow: "hidden" }}>
      <StatusBar />
      {/* Fixed header with live search input */}
      <div style={{ position: "absolute", left: 0, top: 62, right: 0, background: "#fff", padding: "16px 22px 14px", boxSizing: "border-box", borderBottom: `0.5px solid ${C.stroke}`, zIndex: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <Press onClick={back} scale={0.85} style={{ flex: "none", width: 38, height: 38, borderRadius: 19, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: `inset 0 0 0 0.5px ${C.stroke}` }}>
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="2.2"><path d="M15 5l-7 7 7 7" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </Press>
          <Press onClick={() => setKbOpen(true)} scale={0.995} style={{ flex: 1, height: 48, borderRadius: 14, background: "#fff", boxShadow: `inset 0 0 0 1px ${kbOpen ? C.blue : C.stroke}`, display: "flex", alignItems: "center", gap: 10, padding: "0 14px" }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2.2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5" strokeLinecap="round"/></svg>
            <span style={{ flex: 1, fontFamily: F, fontWeight: 600, fontSize: 16, color: query ? C.ink : "rgba(0,0,0,0.42)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textAlign: "left" }}>
              {query || "Search available spot"}
              {kbOpen && <span style={{ display: "inline-block", width: 2, height: 18, background: C.blue, marginLeft: 1, verticalAlign: "middle", animation: "gripCaret 1s steps(1) infinite" }} />}
            </span>
            {query && (
              <Press onClick={() => setQuery("")} scale={0.85} style={{ flex: "none", width: 22, height: 22, borderRadius: 11, background: "rgba(0,0,0,0.08)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={C.gray} strokeWidth="3"><path d="M18 6 6 18M6 6l12 12" strokeLinecap="round"/></svg>
              </Press>
            )}
          </Press>
        </div>
      </div>
      {/* Scrollable cards container */}
      <div style={{ position: "absolute", left: 0, top: 140, right: 0, bottom: kbOpen ? KB_H : 0, overflowY: "auto", padding: "16px 30px 40px", transition: "bottom .28s cubic-bezier(.32,.72,0,1)" }}>
        <div style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: C.gray, padding: "0 0 12px" }}>
          {q ? `Results for "${query}"` : "Spots to Grip Near"}
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 15 }}>
          {shown.length === 0 && (
            <div style={{ fontFamily: F, fontWeight: 500, fontSize: 14, color: C.gray, padding: "24px 0", textAlign: "center" }}>No spots match “{query}”</div>
          )}
          {shown.map((s, i) => <ResultCard key={i} s={s} onNavigate={() => startNav(s)} onPhotoOpen={onPhotoOpen} />)}
        </div>
      </div>
      {/* On-screen keyboard */}
      <Keyboard open={kbOpen} caps={caps} onKey={type} onBackspace={backspace} onSpace={space}
        onShift={() => setCaps((c) => !c)} onReturn={() => setKbOpen(false)} height={KB_H} />
    </div>
  );
}

/* iOS-style on-screen keyboard */
function Keyboard({ open, caps, onKey, onBackspace, onSpace, onShift, onReturn, height }) {
  const rows = [["q","w","e","r","t","y","u","i","o","p"], ["a","s","d","f","g","h","j","k","l"], ["z","x","c","v","b","n","m"]];
  const keyStyle = { flex: 1, height: 42, borderRadius: 6, background: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
    fontFamily: F, fontWeight: 500, fontSize: 22, color: C.ink, boxShadow: "0 1px 0 rgba(0,0,0,0.28)", cursor: "pointer", userSelect: "none" };
  const wideStyle = { ...keyStyle, background: "#aab0bb", flex: "none", width: 42 };
  return (
    <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, height, background: "#d1d5db", padding: "8px 4px 24px", boxSizing: "border-box",
      display: "flex", flexDirection: "column", gap: 11, zIndex: 40,
      transform: open ? "translateY(0)" : "translateY(110%)", transition: "transform .28s cubic-bezier(.32,.72,0,1)", boxShadow: "0 -1px 0 rgba(0,0,0,0.1)" }}>
      <div style={{ display: "flex", gap: 6, padding: "0 4px" }}>
        {rows[0].map((k) => <Press key={k} onClick={() => onKey(k)} scale={0.9} style={keyStyle}>{caps ? k.toUpperCase() : k}</Press>)}
      </div>
      <div style={{ display: "flex", gap: 6, padding: "0 22px" }}>
        {rows[1].map((k) => <Press key={k} onClick={() => onKey(k)} scale={0.9} style={keyStyle}>{caps ? k.toUpperCase() : k}</Press>)}
      </div>
      <div style={{ display: "flex", gap: 6, padding: "0 4px", alignItems: "center" }}>
        <Press onClick={onShift} scale={0.9} style={{ ...wideStyle, background: caps ? "#fff" : "#aab0bb" }}>
          <svg width="22" height="22" viewBox="0 0 24 24" fill={caps ? C.ink : "#fff"}><path d="M12 4l8 9h-5v6h-6v-6H4z"/></svg>
        </Press>
        <div style={{ flex: 1, display: "flex", gap: 6 }}>
          {rows[2].map((k) => <Press key={k} onClick={() => onKey(k)} scale={0.9} style={keyStyle}>{caps ? k.toUpperCase() : k}</Press>)}
        </div>
        <Press onClick={onBackspace} scale={0.9} style={wideStyle}>
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2"><path d="M21 5H8l-5 7 5 7h13a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1z" fill="#fff" stroke="none"/><path d="M16 9l-5 6M11 9l5 6" stroke="#aab0bb" strokeLinecap="round"/></svg>
        </Press>
      </div>
      <div style={{ display: "flex", gap: 6, padding: "0 4px" }}>
        <div style={{ ...wideStyle, flex: "none", width: 76, fontSize: 15, fontWeight: 600, color: "#fff" }}>123</div>
        <Press onClick={onSpace} scale={0.97} style={{ ...keyStyle, flex: 1, fontSize: 15, fontWeight: 500 }}>space</Press>
        <Press onClick={onReturn} scale={0.93} style={{ ...keyStyle, flex: "none", width: 92, background: C.blue, color: "#fff", fontSize: 16, fontWeight: 600, boxShadow: "0 1px 0 rgba(0,0,0,0.2)" }}>search</Press>
      </div>
    </div>
  );
}

function NavScreen({ spot, exit, spotTaken, dismissTaken, onTakenNavigate, onSeeOptions, onPhotoOpen, onHold, onArrive, A }) {
  const elRef = useRef(null);
  const { L, map, ready } = useLeaflet(elRef, { center: spot.ll || TLV_CENTER, zoom: 16, interactive: true });
  const [held, setHeld] = useState(false);
  const [holdLeft, setHoldLeft] = useState(300);   // 5:00 hold window
  useEffect(() => {
    if (!held) return;
    const id = setInterval(() => setHoldLeft((s) => Math.max(0, s - 1)), 1000);
    return () => clearInterval(id);
  }, [held]);
  // a fresh spot resets the hold
  useEffect(() => { setHeld(false); setHoldLeft(300); }, [spot]);
  const mmss = `${Math.floor(holdLeft / 60)}:${String(holdLeft % 60).padStart(2, "0")}`;
  const doHold = () => { if (held) return; setHeld(true); onHold && onHold(); };
  const dir = navDirections(spot);

  /* draw the route + animate the rider along it */
  useEffect(() => {
    if (!ready) return;
    const a = USER_LL, dest = spot.ll || TLV_CENTER;
    const mid1 = [a[0] + (dest[0] - a[0]) * 0.18, a[1] + (dest[1] - a[1]) * 0.55];
    const mid2 = [a[0] + (dest[0] - a[0]) * 0.62, a[1] + (dest[1] - a[1]) * 0.86];
    const route = [a, mid1, mid2, dest];
    const casing = L.polyline(route, { color: "#1560F3", weight: 12, opacity: 0.22, lineCap: "round", lineJoin: "round" }).addTo(map);
    const line = L.polyline(route, { color: "#1560F3", weight: 7, lineCap: "round", lineJoin: "round" }).addTo(map);
    const destM = L.marker(dest, { icon: makeIcon(L, pinHTML(spot.spots ?? 1, true), 40, 52, [20, 52]) }).addTo(map);
    const rider = L.marker(a, { icon: makeIcon(L, riderHTML(), 22, 22, [11, 11]), interactive: false, zIndexOffset: 900 }).addTo(map);
    map.fitBounds(L.latLngBounds(route).pad(0.45), { animate: false });
    const seg = []; let tot = 0;
    for (let i = 0; i < route.length - 1; i++) { const d = map.distance(route[i], route[i + 1]); seg.push(d); tot += d; }
    
    // Parse the ETA (e.g., "2 Minute" or "3 Minutes") and convert to milliseconds
    const etaMatch = String(spot.min).match(/(\d+)/);
    const etaMinutes = etaMatch ? parseInt(etaMatch[1]) : 5;
    const dur = etaMinutes * 60 * 1000;  // Convert minutes to milliseconds
    
    let raf, start;
    const step = (t) => {
      if (!start) start = t;
      let dist = (((t - start) % dur) / dur) * tot, i = 0;
      while (i < seg.length - 1 && dist > seg[i]) { dist -= seg[i]; i++; }
      const f = seg[i] ? dist / seg[i] : 0, A2 = route[i], B2 = route[i + 1];
      rider.setLatLng([A2[0] + (B2[0] - A2[0]) * f, A2[1] + (B2[1] - A2[1]) * f]);
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => { cancelAnimationFrame(raf); [casing, line, destM, rider].forEach((o) => map.removeLayer(o)); };
  }, [ready, spot]);   // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <div style={{ position: "absolute", inset: 0, background: C.land, overflow: "hidden" }}>
      <StatusBar />
      {/* full-screen map */}
      <div ref={elRef} style={{ position: "absolute", inset: 0, background: "#e8eef2", zIndex: 1, pointerEvents: "auto" }} />
      {!ready && (
        <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: F, fontWeight: 600, fontSize: 14, color: C.gray }}>Loading map…</div>
      )}

      {/* exit button (top-right) */}
      <Press onClick={exit} scale={0.85} style={{ position: "absolute", right: "22px", top: "69px", width: 46, height: 46, borderRadius: 12,
        background: "#fff", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 100, animation: "fadeDown .5s ease both" }}>
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(43,43,43)" strokeWidth="2.4"><path d="M18 6L6 18M6 6l12 12" strokeLinecap="round"/></svg>
      </Press>

      {/* direction banners (top-left) */}
      <div style={{ position: "absolute", left: "22px", top: "69px", zIndex: 100, display: "flex", flexDirection: "column", gap: 10, pointerEvents: "none" }}>
        {/* primary direction */}
        <div style={{ minWidth: 280, maxWidth: 358, height: 50, borderRadius: 16, background: C.blue, padding: "0 20px", boxSizing: "border-box",
          display: "inline-flex", alignItems: "center", justifyContent: "flex-start", gap: 12, boxShadow: "0 8px 20px rgba(21,96,243,0.35)", animation: "fadeDown .5s ease both" }}>
          <DirIcon type={dir.icon} />
          <span style={{ fontFamily: F, fontWeight: 600, fontSize: 17, color: "#fff", whiteSpace: "nowrap" }}>{dir.text}</span>
        </div>
        {/* next turn hint */}
        <div style={{ minWidth: 130, height: 50, borderRadius: 16, background: C.blue, padding: "0 18px", boxSizing: "border-box", alignSelf: "flex-start",
          display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 10, boxShadow: "0 8px 20px rgba(21,96,243,0.35)", animation: "fadeDown .5s ease .1s both" }}>
          <span style={{ fontFamily: F, fontWeight: 600, fontSize: 17, color: "#fff", whiteSpace: "nowrap" }}>{dir.nextLabel}</span>
          <DirIcon type={dir.nextIcon} size={22} />
        </div>
      </div>

      {/* bottom destination card */}
      <div style={{ position: "absolute", left: 0, bottom: 0, width: 402, zIndex: 100,
        background: "#fff", borderRadius: "18px 18px 0 0", boxShadow: "0 -8px 24px rgba(0,0,0,0.12)", padding: "20px 24px 30px", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: 14, animation: "fadeUp .5s ease .15s both", pointerEvents: "auto" }}>
        {/* reservation banner (when held) */}
        {held && (
          <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderRadius: 12, background: "rgb(236,255,209)", boxShadow: `inset 0 0 0 1px ${C.green}`, animation: "fadeDown .35s ease both" }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={C.green} strokeWidth="2.2"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: C.green }}>Spot reserved for you</span>
            <span style={{ marginLeft: "auto", fontFamily: F, fontWeight: 700, fontSize: 16, color: C.green, fontVariantNumeric: "tabular-nums" }}>{mmss}</span>
          </div>
        )}
        {/* destination info */}
        <div style={{ textAlign: "left" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
            <div style={{ width: 32, height: 32, borderRadius: 8, background: (spot.spots || 3) === 0 ? C.grayMk : (spot.spots || 3) <= 2 ? C.amber : C.green, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: F, fontWeight: 700, fontSize: 16, color: "#fff" }}>{spot.spots || 3}</div>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: (spot.spots || 3) === 0 ? C.grayMk : (spot.spots || 3) <= 2 ? C.amber : C.green }}>Spots Available</span>
          </div>
          <div style={{ fontFamily: F, fontWeight: 600, fontSize: 18, color: C.ink, marginBottom: 8 }}>{spot.name}</div>
          <div style={{ display: "flex", gap: 16, alignItems: "center" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
              <svg width="17" height="15" viewBox="0 0 18 16" fill="none">
                <circle cx="3.6" cy="12.4" r="3" stroke="rgb(100,100,100)" strokeWidth="1.3"/><circle cx="14.4" cy="12.4" r="3" stroke="rgb(100,100,100)" strokeWidth="1.3"/>
                <path d="M3.6 12.4 7.5 5.5h4.3l2.6 6.9M7.5 5.5 9.5 9h3.5M6.2 4.2h2.2" stroke="rgb(100,100,100)" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: "rgb(100,100,100)" }}>{String(spot.km).includes("KM") ? spot.km : `${spot.km} KM`}</span>
            </div>
            <div style={{ width: 1, height: 18, background: "rgb(200,200,200)" }} />
            <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="rgb(100,100,100)" strokeWidth="1.6"><circle cx="12" cy="12" r="9"/><polyline points="12 6 12 12 15 14" strokeLinecap="round"/></svg>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: "rgb(100,100,100)" }}>{spot.min}</span>
            </div>
          </div>
        </div>
        {/* action buttons */}
        <div style={{ display: "flex", gap: 12 }}>
          <Press onClick={doHold} scale={held ? 1 : 0.96} style={{ flex: 1, height: 50, borderRadius: 14,
            background: held ? "rgb(236,255,209)" : "#fff", boxShadow: held ? `inset 0 0 0 1.5px ${C.green}` : `inset 0 0 0 1.5px ${C.blue}`,
            display: "flex", alignItems: "center", justifyContent: "center", gap: 8, cursor: held ? "default" : "pointer" }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={held ? C.green : C.blue} strokeWidth="2.2"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: held ? C.green : C.blue, fontVariantNumeric: "tabular-nums" }}>{held ? `Held · ${mmss}` : "Hold spot"}</span>
          </Press>
          <Press onClick={onArrive} scale={0.96} style={{ flex: 1, height: 50, borderRadius: 14, background: C.blue,
            display: "flex", alignItems: "center", justifyContent: "center", gap: 8, boxShadow: "0 8px 18px rgba(21,96,243,0.28)" }}>
            <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4"><path d="M20 6 9 17l-5-5" strokeLinecap="round" strokeLinejoin="round"/></svg>
            <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: "#fff" }}>I've arrived</span>
          </Press>
        </div>
      </div>

      {/* spot-taken modal */}
      {spotTaken && (
        <div style={{ position: "absolute", inset: 0, zIndex: 1000, background: "rgba(0,0,0,0.35)",
          display: "flex", alignItems: "center", justifyContent: "center", animation: "gripFade .25s ease both" }}>
          <div style={{ width: 367, borderRadius: 20, background: "#fff", padding: "24px 22px 26px", boxSizing: "border-box",
            position: "relative", animation: "gripSlide .3s cubic-bezier(.22,.61,.36,1) both", boxShadow: "0 20px 60px rgba(0,0,0,0.25)" }}>
            <Press onClick={dismissTaken} scale={0.8} style={{ position: "absolute", right: 18, top: 16, width: 28, height: 28, display: "flex", alignItems: "center", justifyContent: "center" }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={C.ink} strokeWidth="2.4"><path d="M6 6l12 12M18 6 6 18" strokeLinecap="round"/></svg>
            </Press>
            <div style={{ textAlign: "center", fontSize: 26, marginTop: 14 }}>💔</div>
            <div style={{ textAlign: "center", fontFamily: F, fontWeight: 600, fontSize: 20, color: C.ink, marginTop: 4 }}>All Spots Are Taken</div>
            <div style={{ textAlign: "center", fontFamily: F, fontWeight: 600, fontSize: 16, color: C.gray, marginTop: 14, lineHeight: 1.35 }}>No Worries,<br/>We found another spot nearby</div>
            <div style={{ marginTop: 22, borderRadius: 8, background: "linear-gradient(180deg,#fff,rgb(250,255,232))", boxShadow: `inset 0 0 0 0.5px ${C.stroke}`, padding: 24, boxSizing: "border-box", position: "relative" }}>
              <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, marginBottom: 16 }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontFamily: F, fontWeight: 600, fontSize: 20, color: C.ink }}>Maze 27,</div>
                  <div style={{ fontFamily: F, fontWeight: 600, fontSize: 20, color: C.ink }}>Tel Aviv-Yaffo</div>
                </div>
                <PicIcon photo="Maze 27" onOpen={onPhotoOpen} />
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 14, margin: "0 0 14px 0" }}>
                <div style={{ width: 48, height: 48, borderRadius: "50%", background: C.amber, color: "#fff", flex: "none",
                  display: "flex", alignItems: "center", justifyContent: "center", fontFamily: F, fontWeight: 600, fontSize: 26,
                  boxShadow: "inset 0 0 0 0.7px #fff, 0 4px 4px rgba(0,0,0,0.09)" }}>1</div>
                <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.amber }}>1 Spot Available</span>
              </div>
              <div style={{ marginBottom: 14 }}><MetaRow km="0.2 KM" min="1 Minute" /></div>
              <Press onClick={() => onTakenNavigate({ name: "Maze 27, Tel Aviv-Yaffo", spots: 1, km: "0.2", min: "1 Minute", ll: [32.079, 34.7726] })}
                style={{ height: 46, borderRadius: 14, background: C.blue, display: "flex", alignItems: "center", justifyContent: "center" }}>
                <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: "#fff" }}>Navigate</span>
              </Press>
            </div>
            <Press onClick={onSeeOptions} scale={0.97} style={{ textAlign: "center", marginTop: 18 }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.blue }}>See Other Options</span>
            </Press>
          </div>
        </div>
      )}
    </div>
  );
}

function CountUp({ value }) {
  const m = String(value).match(/^([\d.]+)(.*)$/);
  const target = m ? parseFloat(m[1]) : 0;
  const suffix = m ? m[2] : "";
  const dec = (m && m[1].includes(".")) ? 1 : 0;
  const [n, setN] = useState(0);
  useEffect(() => {
    let raf, start; const dur = 950;
    const step = (t) => { if (!start) start = t; const p = Math.min(1, (t - start) / dur); const e = 1 - Math.pow(1 - p, 3); setN(target * e); if (p < 1) raf = requestAnimationFrame(step); };
    raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf);
  }, [target]);
  return <span>{n.toFixed(dec)}{suffix}</span>;
}

function ProfileScreen({ go, A }) {
  const stats = [
    { v: "18", l: "Rides", icon: "bike" }, { v: "8.7", l: "KM\nCycled", icon: "route" },
    { v: "13", l: "Searches", icon: "search" }, { v: "89%", l: "Success\nRate", green: true, icon: "check" },
  ];
  const menu = [
    { t: "My Riding Profile", icon: "rider" },
    { t: "Activity History", icon: "history" },
    { t: "Preferences and Settings", icon: "settings" },
    { t: "Report a Problem", icon: "report" },
  ];
  return (
    <div style={{ position: "absolute", inset: 0, background: "#fff", overflow: "hidden" }}>
      <StatusBar />
      {/* fixed header */}
      <div style={{ position: "absolute", left: 0, top: 64, right: 0, padding: "0 23px", background: "#fff", zIndex: 5 }}>
        <div style={{ textAlign: "center", fontFamily: F, fontWeight: 600, fontSize: 20, color: C.ink, marginTop: 6 }}>Hello, Laura! 👋</div>
        <div style={{ display: "flex", justifyContent: "center", marginTop: 18 }}>
          <div style={{ position: "relative", width: 177, height: 177 }}>
            <div style={{ width: 177, height: 177, borderRadius: "50%", overflow: "hidden", position: "relative", background: "linear-gradient(180deg,#eaf1ff,#fff)", display: "flex", alignItems: "center", justifyContent: "center", animation: "gripPop .5s cubic-bezier(.34,1.56,.64,1) both" }}>
              <span style={{ position: "absolute", fontFamily: F, fontWeight: 700, fontSize: 64, color: C.blue, opacity: 0.5 }}>L</span>
              <img src={`${A}avatar.png`} alt="Laura" style={{ width: "150%", height: "150%", objectFit: "cover", objectPosition: "center 32%", position: "relative" }}
                onError={(e)=>{ e.target.style.display='none'; }} />
            </div>
            {/* edit / camera badge */}
            <Press scale={0.85} style={{ position: "absolute", right: 18, bottom: 18, width: 44, height: 44, borderRadius: 22, background: C.blue,
              display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 4px 12px rgba(21,96,243,0.4), 0 0 0 3px #fff" }}>
              <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
            </Press>
          </div>
        </div>
        {/* name + handle */}
        <div style={{ textAlign: "center", marginTop: 4, paddingBottom: 18 }}>
          <div style={{ fontFamily: F, fontWeight: 700, fontSize: 24, color: C.ink }}>Laura Leezy</div>
          <div style={{ fontFamily: F, fontWeight: 500, fontSize: 15, color: C.gray, marginTop: 2 }}>laura.leezy@gmail.com</div>
        </div>
      </div>

      {/* scrollable body */}
      <div style={{ position: "absolute", left: 0, top: 318, right: 0, bottom: 92, overflowY: "auto", padding: "6px 23px 24px" }}>
        <div style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: C.gray, margin: "26px 0 12px" }}>This Month</div>
        <div style={{ display: "flex", gap: 14 }}>
          {stats.map((s, i) => (
            <div key={i} style={{ flex: 1, height: 104, borderRadius: 12, background: C.cardGrad, boxShadow: `inset 0 0 0 0.5px ${C.stroke}`,
              display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 5, padding: 4, boxSizing: "border-box" }}>
              <StatIcon name={s.icon} color={s.green ? C.green : C.blue} />
              <span style={{ fontFamily: F, fontWeight: 700, fontSize: 22, color: s.green ? C.green : C.ink }}><CountUp value={s.v} /></span>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 12, color: C.gray, textAlign: "center", whiteSpace: "pre-line", lineHeight: 1.1 }}>{s.l}</span>
            </div>
          ))}
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 24 }}>
          {menu.map((m, i) => (
            <Press key={i} scale={0.99} style={{ height: 56, borderRadius: 12, boxShadow: `inset 0 0 0 1px rgba(0,0,0,0.12)`,
              display: "flex", alignItems: "center", gap: 14, padding: "0 16px" }}>
              <div style={{ flex: "none", width: 38, height: 38, borderRadius: 10, background: "rgba(21,96,243,0.08)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <MenuIcon name={m.icon} />
              </div>
              <span style={{ flex: 1, fontFamily: F, fontWeight: 600, fontSize: 16, color: C.ink }}>{m.t}</span>
              <svg width="9" height="16" viewBox="0 0 9 16" fill="none" stroke={C.grayMk} strokeWidth="2"><path d="M1 1l7 7-7 7" strokeLinecap="round" strokeLinejoin="round"/></svg>
            </Press>
          ))}
        </div>

        <Press onClick={() => go("welcome")} scale={0.97} style={{ height: 54, borderRadius: 12, marginTop: 24, boxShadow: `inset 0 0 0 1.5px ${C.red}`,
          display: "flex", alignItems: "center", justifyContent: "center", gap: 10 }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={C.red} strokeWidth="2.2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" strokeLinecap="round" strokeLinejoin="round"/></svg>
          <span style={{ fontFamily: F, fontWeight: 700, fontSize: 16, color: C.red }}>Log Out</span>
        </Press>
      </div>
      <BottomNav active="profile" go={go} />
    </div>
  );
}

/* small stat-card icons */
function StatIcon({ name, color }) {
  const p = { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
  if (name === "bike") return <svg {...p}><circle cx="6" cy="17" r="3.2"/><circle cx="18" cy="17" r="3.2"/><path d="M6 17l4-7h5M10 10l3.5 7M14 7h3"/></svg>;
  if (name === "route") return <svg {...p}><circle cx="6" cy="19" r="2"/><circle cx="18" cy="5" r="2"/><path d="M8 19h6a3 3 0 0 0 0-6H10a3 3 0 0 1 0-6h6"/></svg>;
  if (name === "search") return <svg {...p}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>;
  if (name === "check") return <svg {...p}><path d="M20 6 9 17l-5-5"/></svg>;
  return null;
}

/* leading icons for menu rows */
function MenuIcon({ name }) {
  const p = { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: C.blue, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
  if (name === "rider") return <svg {...p}><circle cx="12" cy="7" r="4"/><path d="M5 21c0-3.9 3.1-7 7-7s7 3.1 7 7"/></svg>;
  if (name === "history") return <svg {...p}><path d="M3 3v6h6"/><path d="M3.5 9a9 9 0 1 0 2.1-3.4L3 9"/><path d="M12 8v4l3 2"/></svg>;
  if (name === "settings") return <svg {...p}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.2A1.6 1.6 0 0 0 7 19.4a1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0-1.1-2.7H1a2 2 0 0 1 0-4h.2A1.6 1.6 0 0 0 2.6 7a1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.6 1.6 0 0 0 7 2.6h.1A1.6 1.6 0 0 0 9 1.2V1a2 2 0 0 1 4 0v.2A1.6 1.6 0 0 0 15 2.6a1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8V7a1.6 1.6 0 0 0 1.4 1H23a2 2 0 0 1 0 4h-.2a1.6 1.6 0 0 0-1.4 1z"/></svg>;
  if (name === "report") return <svg {...p}><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>;
  return null;
}

/* =========================================================================
 * ARRIVAL + REPORT-RACK END STATE
 * =======================================================================*/
function ArriveScreen({ spot, reserved, photoUrl, onDone, onFindAnother }) {
  const [stage, setStage] = useState("confirm");   // confirm | report | done
  const [tags, setTags] = useState([]);
  const [imgOk, setImgOk] = useState(true);
  const now = (() => { const d = new Date(); return `${d.getHours()}:${String(d.getMinutes()).padStart(2, "0")}`; })();
  const reportChips = [
    { k: "free", label: "Free & easy", good: true },
    { k: "tight", label: "Tight fit" },
    { k: "hard", label: "Hard to find" },
    { k: "full", label: "Was full" },
    { k: "broken", label: "Rack broken" },
    { k: "blocked", label: "Blocked / obstructed" },
  ];
  const toggle = (k) => setTags((t) => t.includes(k) ? t.filter((x) => x !== k) : [...t, k]);

  return (
    <div style={{ position: "absolute", inset: 0, background: C.skyGrad, overflow: "hidden" }}>
      <StatusBar />

      {/* hero */}
      <div style={{ position: "absolute", left: 0, top: 84, width: 402, display: "flex", flexDirection: "column", alignItems: "center" }}>
        <div style={{ position: "relative", width: 132, height: 132, display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div style={{ position: "absolute", width: 132, height: 132, borderRadius: "50%", background: C.green, opacity: 0.18, animation: "pulseRing 2s ease-out infinite" }} />
          <div style={{ width: 96, height: 96, borderRadius: "50%", background: C.green, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 12px 30px rgba(19,159,12,0.35)", animation: "gripPop .5s cubic-bezier(.34,1.56,.64,1) both" }}>
            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.6"><path d="M20 6 9 17l-5-5" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </div>
        </div>
        <div style={{ fontFamily: F, fontWeight: 700, fontSize: 26, color: C.ink, marginTop: 18 }}>You've arrived!</div>
        <div style={{ fontFamily: F, fontWeight: 600, fontSize: 16, color: C.gray, marginTop: 4 }}>{spot.name}</div>
      </div>

      {/* sheet */}
      <div style={{ position: "absolute", left: 0, bottom: 0, width: 402, background: "#fff", borderRadius: "22px 22px 0 0",
        boxShadow: "0 -10px 30px rgba(0,0,0,0.12)", padding: "26px 24px 34px", boxSizing: "border-box", minHeight: 360, animation: "fadeUp .45s ease both" }}>

        {stage === "confirm" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
            {/* rack preview */}
            <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
              <div style={{ flex: "none", width: 64, height: 64, borderRadius: 12, overflow: "hidden", background: "#e8eef2", boxShadow: `inset 0 0 0 1px ${C.stroke}` }}>
                {imgOk ? <img src={photoUrl} alt="Rack" onError={() => setImgOk(false)} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                  : <div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }}><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke={C.blue} strokeWidth="2"><circle cx="6" cy="17" r="3"/><circle cx="18" cy="17" r="3"/><path d="M6 17l4-7h5M10 10l3.5 7"/></svg></div>}
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontFamily: F, fontWeight: 700, fontSize: 17, color: C.ink }}>{spot.name}</div>
                <div style={{ fontFamily: F, fontWeight: 500, fontSize: 13, color: C.gray, marginTop: 2 }}>{reserved ? "Reserved · arrived " : "Arrived "}{now}</div>
              </div>
              {reserved && (
                <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 6, padding: "6px 10px", borderRadius: 20, background: "rgb(236,255,209)", boxShadow: `inset 0 0 0 1px ${C.green}` }}>
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.green} strokeWidth="2.4"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
                  <span style={{ fontFamily: F, fontWeight: 600, fontSize: 12, color: C.green }}>Held</span>
                </div>
              )}
            </div>
            <div style={{ fontFamily: F, fontWeight: 600, fontSize: 18, color: C.ink, textAlign: "center", marginTop: 4 }}>Did you park here?</div>
            <Press onClick={() => setStage("report")} scale={0.97} style={{ height: 54, borderRadius: 14, background: C.green,
              display: "flex", alignItems: "center", justifyContent: "center", gap: 10, boxShadow: "0 8px 18px rgba(19,159,12,0.3)" }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4"><path d="M20 6 9 17l-5-5" strokeLinecap="round" strokeLinejoin="round"/></svg>
              <span style={{ fontFamily: F, fontWeight: 700, fontSize: 16, color: "#fff" }}>Yes, I'm parked</span>
            </Press>
            <Press onClick={onFindAnother} scale={0.97} style={{ height: 50, borderRadius: 14, boxShadow: `inset 0 0 0 1.5px ${C.stroke}`,
              display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.ink }}>Spot was taken, find another</span>
            </Press>
          </div>
        )}

        {stage === "report" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <div>
              <div style={{ fontFamily: F, fontWeight: 700, fontSize: 19, color: C.ink }}>How was this rack?</div>
              <div style={{ fontFamily: F, fontWeight: 500, fontSize: 14, color: C.gray, marginTop: 3 }}>Your feedback keeps GRIP's map accurate for other riders.</div>
            </div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 10 }}>
              {reportChips.map((c) => {
                const on = tags.includes(c.k);
                const accent = c.good ? C.green : C.blue;
                return (
                  <Press key={c.k} onClick={() => toggle(c.k)} scale={0.95} style={{ padding: "10px 16px", borderRadius: 22,
                    background: on ? (c.good ? "rgb(236,255,209)" : "rgba(21,96,243,0.08)") : "#fff",
                    boxShadow: `inset 0 0 0 1.5px ${on ? accent : "rgba(0,0,0,0.12)"}`, display: "flex", alignItems: "center", gap: 7 }}>
                    {on && <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={accent} strokeWidth="3"><path d="M20 6 9 17l-5-5" strokeLinecap="round" strokeLinejoin="round"/></svg>}
                    <span style={{ fontFamily: F, fontWeight: 600, fontSize: 14, color: on ? accent : C.ink }}>{c.label}</span>
                  </Press>
                );
              })}
            </div>
            <Press onClick={() => setStage("done")} scale={0.97} style={{ height: 54, borderRadius: 14, background: C.blue, marginTop: 4,
              display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 8px 18px rgba(21,96,243,0.28)" }}>
              <span style={{ fontFamily: F, fontWeight: 700, fontSize: 16, color: "#fff" }}>{tags.length ? "Submit feedback" : "Done"}</span>
            </Press>
            <Press onClick={() => setStage("done")} scale={0.97} style={{ textAlign: "center" }}>
              <span style={{ fontFamily: F, fontWeight: 600, fontSize: 15, color: C.gray }}>Skip</span>
            </Press>
          </div>
        )}

        {stage === "done" && (
          <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 14, paddingTop: 6 }}>
            <div style={{ fontSize: 40 }}>🎉</div>
            <div style={{ fontFamily: F, fontWeight: 700, fontSize: 22, color: C.ink, textAlign: "center" }}>Parked &amp; saved!</div>
            <div style={{ fontFamily: F, fontWeight: 500, fontSize: 15, color: C.gray, textAlign: "center", lineHeight: 1.4 }}>
              This ride to <span style={{ color: C.ink, fontWeight: 600 }}>{spot.name}</span> was added to your history. Thanks for keeping the map fresh!
            </div>
            <div style={{ width: "100%", borderRadius: 12, background: "linear-gradient(180deg,#fff,rgb(250,255,232))", boxShadow: `inset 0 0 0 0.5px ${C.stroke}`, padding: "16px 18px", boxSizing: "border-box", display: "flex", justifyContent: "space-around", marginTop: 4 }}>
              <div style={{ textAlign: "center" }}>
                <div style={{ fontFamily: F, fontWeight: 700, fontSize: 18, color: C.ink }}>{now}</div>
                <div style={{ fontFamily: F, fontWeight: 500, fontSize: 12, color: C.gray }}>Parked at</div>
              </div>
              <div style={{ width: 1, background: "rgba(0,0,0,0.08)" }} />
              <div style={{ textAlign: "center" }}>
                <div style={{ fontFamily: F, fontWeight: 700, fontSize: 18, color: C.ink }}>{String(spot.km).replace("KM", "").trim()} km</div>
                <div style={{ fontFamily: F, fontWeight: 500, fontSize: 12, color: C.gray }}>Cycled</div>
              </div>
            </div>
            <Press onClick={onDone} scale={0.97} style={{ width: "100%", height: 54, borderRadius: 14, background: C.blue, marginTop: 8,
              display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 8px 18px rgba(21,96,243,0.28)" }}>
              <span style={{ fontFamily: F, fontWeight: 700, fontSize: 16, color: "#fff" }}>Done</span>
            </Press>
          </div>
        )}
      </div>
    </div>
  );
}

/* =========================================================================
 * ROOT
 * =======================================================================*/
/* which transition between two screens */
function dirFor(from, to) {
  if (to === "nav") return "up";
  if (from === "nav") return "down";
  if (to === "arrive") return "up";
  if (from === "arrive") return "down";
  if (to === "search") return "forward";
  if (from === "search") return "back";
  if (to === "welcome") return "fade";
  if (from === "welcome") return "up";
  if (to === "onboarding") return "fade";
  if (from === "onboarding") return "forward";
  return "fade";   // tab switches
}
const TRANS = {
  forward: { enter: "slInR",  exit: "slOutL",    top: true  },
  back:    { enter: "slInL",  exit: "slOutR",    top: false },
  up:      { enter: "slInUp", exit: "dimOut",    top: true  },
  down:    { enter: "fIn",    exit: "slOutDown", top: false },
  fade:    { enter: "fIn",    exit: "fOut",      top: true  },
};

function FigmaProject({ assetBase = "./assets/", initialScreen = "onboarding" } = {}) {
  const A = assetBase.endsWith("/") ? assetBase : assetBase + "/";
  const [screen, setScreen] = useState(initialScreen);      // onboarding|welcome|map|favorites|search|nav|profile
  const [dest, setDest] = useState("");
  const [navSpot, setNavSpot] = useState(NEAR_SPOTS[0]);
  const [spotTaken, setSpotTaken] = useState(false);
  const [reserved, setReserved] = useState(false);
  const [photoModal, setPhotoModal] = useState(null);
  
  // Real bike-rack photos, mapped per spot (cycles the 3 uploads across all spots)
  const RACK_PHOTOS = [A + "rack-1.jpg", A + "rack-2.jpg", A + "rack-3.jpg"];
  const PHOTO_MAP = {
    "photo1": RACK_PHOTOS[0], "photo2": RACK_PHOTOS[1], "photo3": RACK_PHOTOS[2],
    "Pinsker 62": RACK_PHOTOS[0], "Ben Ami St 3": RACK_PHOTOS[1], "Bograshov 12": RACK_PHOTOS[2],
    "Maze 27": RACK_PHOTOS[0], "Maze 27, Tel Aviv-Yaffo": RACK_PHOTOS[0],
    "Sheinkin 53": RACK_PHOTOS[1], "Derech Jaffa 9": RACK_PHOTOS[2], "Zrubavel 16": RACK_PHOTOS[0],
  };
  const photoFor = (key) => {
    if (PHOTO_MAP[key]) return PHOTO_MAP[key];
    // deterministic fallback: hash the key to one of the 3 photos
    const str = String(key || "");
    let h = 0; for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) >>> 0;
    return RACK_PHOTOS[h % RACK_PHOTOS.length];
  };
  const screenRef = useRef(initialScreen);
  const viewRef = useRef(null);
  const [exiting, setExiting] = useState(null);           // { el, dir }
  const timer = useRef(0);

  const navigate = useCallback((to) => {
    const from = screenRef.current;
    if (from === to) return;
    setExiting({ el: viewRef.current, dir: dirFor(from, to) });
    screenRef.current = to;
    setScreen(to);
    clearTimeout(timer.current);
    timer.current = setTimeout(() => setExiting(null), 470);
  }, []);

  const go = navigate;
  const openSearch = (d) => { setDest(d); navigate("search"); };
  const startNav = (spot) => { setNavSpot(spot); setSpotTaken(false); setReserved(false); navigate("nav"); };
  const onPhotoOpen = (photoKey) => { setPhotoModal(photoKey); };

  // simulate a live sensor update: the chosen spot gets taken mid-ride — unless reserved
  useEffect(() => {
    if (screen !== "nav" || reserved) return;
    const t = setTimeout(() => setSpotTaken(true), 5000);
    return () => clearTimeout(t);
  }, [screen, reserved]);

  let view;
  if (screen === "onboarding") view = <OnboardingScreen done={() => go("welcome")} />;
  else if (screen === "welcome") view = <Welcome go={go} A={A} />;
  else if (screen === "map") view = <MapScreen go={go} startNav={startNav} onPhotoOpen={onPhotoOpen} A={A} />;
  else if (screen === "favorites") view = <FavoritesScreen go={go} openSearch={openSearch} />;
  else if (screen === "search") view = <SearchScreen dest={dest} back={() => go("favorites")} startNav={startNav} onPhotoOpen={onPhotoOpen} />;
  else if (screen === "nav") view = (
    <NavScreen spot={navSpot} A={A} spotTaken={spotTaken} onPhotoOpen={onPhotoOpen}
      exit={() => go("map")}
      onHold={() => setReserved(true)}
      onArrive={() => go("arrive")}
      dismissTaken={() => setSpotTaken(false)}
      onTakenNavigate={(alt) => { setNavSpot(alt); setSpotTaken(false); setReserved(false); }}
      onSeeOptions={() => { setSpotTaken(false); setDest(navSpot.name); go("search"); }} />
  );
  else if (screen === "arrive") view = (
    <ArriveScreen spot={navSpot} reserved={reserved} photoUrl={photoFor(navSpot.photo || navSpot.name)}
      onDone={() => go("map")} onFindAnother={() => { setDest(navSpot.name); go("search"); }} />
  );
  else if (screen === "profile") view = <ProfileScreen go={go} A={A} />;
  viewRef.current = view;

  const t = exiting ? (TRANS[exiting.dir] || TRANS.fade) : null;
  const incTop = t ? t.top : true;
  const D = ".46s", E = "cubic-bezier(.4,0,.2,1)";

  return (
    <div style={{ width: 402, height: 874, position: "relative", overflow: "visible",
      borderRadius: 0, background: "#fff", fontFamily: F, userSelect: "none" }}>
      <style>{`
        .leaflet-container{font-family:${F};background:#e8eef2;z-index:1!important}
        .gripmap-sheeted .leaflet-bottom.leaflet-right{bottom:110px}
        @keyframes gripPop{0%{transform:scale(.4);opacity:0}60%{transform:scale(1.12)}100%{transform:scale(1);opacity:1}}
        @keyframes gripFade{from{opacity:0}to{opacity:1}}
        @keyframes gripSlide{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes fIn{from{opacity:0}to{opacity:1}}
        @keyframes fOut{from{opacity:1}to{opacity:0}}
        @keyframes slInR{from{transform:translateX(100%)}to{transform:translateX(0)}}
        @keyframes slOutL{from{transform:translateX(0);opacity:1}to{transform:translateX(-24%);opacity:.55}}
        @keyframes slInL{from{transform:translateX(-100%)}to{transform:translateX(0)}}
        @keyframes slOutR{from{transform:translateX(0)}to{transform:translateX(100%)}}
        @keyframes slInUp{from{transform:translateY(100%)}to{transform:translateY(0)}}
        @keyframes slOutDown{from{transform:translateY(0)}to{transform:translateY(100%)}}
        @keyframes dimOut{from{opacity:1}to{opacity:.65}}
        @keyframes pulseRing{0%{transform:scale(.55);opacity:.5}70%{opacity:0}100%{transform:scale(2.4);opacity:0}}
        @keyframes fadeUp{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes fadeDown{from{transform:translateY(-14px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes sheetUp{from{transform:translateY(46px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes panelDown{from{transform:translateY(-46px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes routeMove{0%{transform:translate(0,0)}45%{transform:translate(105px,62px)}100%{transform:translate(170px,2px)}}
        @keyframes gripDraw{to{stroke-dashoffset:0}}
        @keyframes gripCaret{0%,50%{opacity:1}50.01%,100%{opacity:0}}
        @keyframes wmDrop{from{transform:translateY(-24px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes riderRoll{from{transform:translateX(-90px);opacity:0}to{transform:translateX(0);opacity:1}}
        @keyframes riseUp{from{transform:translateY(22px);opacity:0}to{transform:translateY(0);opacity:1}}
        @keyframes livePulse{0%{box-shadow:0 0 0 0 rgba(19,159,12,0.5)}70%{box-shadow:0 0 0 6px rgba(19,159,12,0)}100%{box-shadow:0 0 0 0 rgba(19,159,12,0)}}
        @keyframes spin{to{transform:rotate(360deg)}}
        .fav-scroll::-webkit-scrollbar{display:none}
        ::-webkit-scrollbar{width:4px;height:4px}
        ::-webkit-scrollbar-track{background:transparent}
        ::-webkit-scrollbar-thumb{background:rgba(114,162,255,0.45);border-radius:4px}
        ::-webkit-scrollbar-thumb:hover{background:rgba(114,162,255,0.7)}
        *{scrollbar-width:thin;scrollbar-color:rgba(114,162,255,0.45) transparent}
        .fav-scroll{scrollbar-width:none;-ms-overflow-style:none}
      `}</style>
      {exiting && (
        <div style={{ position: "absolute", inset: 0, zIndex: incTop ? 1 : 2,
          animation: `${t.exit} ${D} ${E} both`, willChange: "transform,opacity" }}>{exiting.el}</div>
      )}
      <div key={screen} style={{ position: "absolute", inset: 0, zIndex: incTop ? 2 : 1,
        animation: exiting ? `${t.enter} ${D} ${E} both` : "none", willChange: "transform,opacity" }}>{view}</div>
      
      {/* Photo modal */}
      {photoModal && (
        <div style={{ position: "absolute", inset: 0, zIndex: 1000, background: "rgba(0,0,0,0.6)",
          display: "flex", alignItems: "center", justifyContent: "center", animation: "gripFade .25s ease both", overflow: "auto" }}>
          <Press onClick={() => setPhotoModal(null)} scale={0.95} style={{ position: "absolute", inset: 0, zIndex: 0 }} />
          <div style={{ position: "relative", zIndex: 1, borderRadius: 16, overflow: "hidden", boxShadow: "0 20px 60px rgba(0,0,0,0.4)", maxWidth: "90%", maxHeight: "90vh", margin: "auto" }}>
            <img src={photoFor(photoModal)} alt="Bike rack" style={{ width: "100%", height: "auto", display: "block", maxHeight: "80vh", objectFit: "contain" }} />
            <Press onClick={() => setPhotoModal(null)} scale={0.8} style={{ position: "absolute", right: 12, top: 12, width: 40, height: 40, borderRadius: 20,
              background: "rgba(255,255,255,0.9)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 4px 12px rgba(0,0,0,0.2)", zIndex: 10 }}>
              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(43,43,43)" strokeWidth="2.4"><path d="M18 6L6 18M6 6l12 12" strokeLinecap="round"/></svg>
            </Press>
          </div>
        </div>
      )}
    </div>
  );
}

if (typeof window !== "undefined") window.FigmaProject = FigmaProject;
if (typeof module !== "undefined") module.exports = { FigmaProject, default: FigmaProject };
