// src/store.jsx — Store menu (the hero screen).
// Supports 4 category-nav patterns via tweak: 'tabs' | 'rail' | 'sheet' | 'aisle'.
// 'aisle' drops the nav chrome entirely for a long-scroll walk-the-store feel.
// Always: sticky storefront header, delivery/pickup toggle, category sections, density-aware product grid.

// Store-specific mock content — distinct flavor per store so the editorial lede
// and orders-near ticker don't feel generic. Keyed by store.id.
const STORE_VOICE = {
  kalymnos: {
    lede: {
      pull: "The same 8 AM pastry shipment for thirty years. That's not tradition, it's a handshake.",
      body: "Maria's been running Kalymnos since her father shoved a knife into her hand in '92. If it's on the shelf, it sold last week.",
      author: "Staff of Kalymnos",
    },
    orders: { count: 142, window: "in the last hour", recent: ["Modelo 12pk", "BEC on a roll", "Topo Chico"] },
  },
  liu: {
    lede: {
      pull: "The sign says 24hr. The sign has always said 24hr.",
      body: "Liu's is the block's power grid. If the lights are out somewhere else, they're on here.",
      author: "Liu's 24hr",
    },
    orders: { count: 58, window: "in the last hour", recent: ["Liquid Death", "Red Bull 12oz", "Advil"] },
  },
  arbor: {
    lede: {
      pull: "Real hammers. Real level. No fruit.",
      body: "Arbor has been the hardware answer for three generations of Greenpoint renters. They know what drywall you have before you do.",
      author: "Arbor Hardware",
    },
    orders: { count: 17, window: "this morning", recent: ["Picture hooks", "Drywall anchors", "Painter's tape"] },
  },
  fig: {
    lede: {
      pull: "A wall of under-$22 bottles worth fighting about.",
      body: "Cobble Hill's wine counter where the staff will argue with you about the Pinot you just pulled off the shelf, and be right.",
      author: "Fig & Thistle",
    },
    orders: { count: 34, window: "in the last hour", recent: ["Meiomi Pinot", "Whispering Angel", "White Claw 12"] },
  },
  elmo: {
    lede: {
      pull: "We brined 200 thighs this morning. You are not going to eat them all.",
      body: "Elmo's Chicken & Fixins runs on a Tuesday dinner rush that has to be seen to be believed. The fryer doesn't stop until the thighs do.",
      author: "Elmo's",
    },
    orders: { count: 96, window: "in the last hour", recent: ["Fried chicken plate", "Collards", "Sweet tea"] },
  },
  verde: {
    lede: {
      pull: "Orchard to your bag in 14 hours. We timed it.",
      body: "Verde pulls from three Hudson Valley farms. The produce you pick today was on a branch yesterday.",
      author: "Verde Corner Produce",
    },
    orders: { count: 41, window: "in the last hour", recent: ["Honeycrisp apples", "Vine tomatoes", "Baby spinach"] },
  },
};

// Editorial pull-quote — distinct shape from product cards: no image, big serif text
// with a vertical accent rule and small attribution.
function EditorialLede({ voice }) {
  if (!voice) return null;
  return (
    <figure style={{
      margin: 0, display: "flex", gap: 18, alignItems: "flex-start",
      padding: "4px 0 4px 20px",
      borderLeft: "3px solid var(--accent)",
    }}>
      <div>
        <blockquote className="serif" style={{
          margin: 0, fontSize: 22, lineHeight: 1.25, letterSpacing: "-0.005em",
          color: "var(--ink)", textWrap: "balance", maxWidth: 640,
        }}>
          “{voice.pull}”
        </blockquote>
        <p style={{ margin: "10px 0 0", fontSize: 13.5, color: "var(--ink-2)",
                    lineHeight: 1.5, maxWidth: 560 }}>
          {voice.body}
        </p>
        <figcaption className="uppercase-mono" style={{ marginTop: 10, color: "var(--ink-3)" }}>
          — {voice.author}
        </figcaption>
      </div>
    </figure>
  );
}

// Horizontal "who's ordering right now" ticker — dots + recent item names.
// Distinct shape from the pill toggles and cards: a thin rail with pulsing dot.
function OrdersNearby({ voice }) {
  if (!voice) return null;
  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      padding: "10px 16px",
      background: "var(--surface-2)", border: "1px solid var(--line)",
      borderRadius: 999,
    }}>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 12.5, color: "var(--ink)" }}>
        <span style={{ position: "relative", width: 8, height: 8 }}>
          <span style={{ position: "absolute", inset: 0, borderRadius: 99, background: "var(--ok)" }} />
          <span style={{ position: "absolute", inset: -2, borderRadius: 99, background: "var(--ok)", opacity: 0.4,
                         animation: "pulseRing 1.8s ease-out infinite" }} />
        </span>
        <strong style={{ fontWeight: 600 }}>{voice.count} orders</strong>
        <span style={{ color: "var(--ink-3)" }}>{voice.window}</span>
      </span>
      <span style={{ color: "var(--ink-4)" }}>·</span>
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
        {voice.recent.map(item => (
          <span key={item} style={{
            fontSize: 11.5, padding: "4px 10px", borderRadius: 999,
            background: "var(--surface)", border: "1px solid var(--line)",
            color: "var(--ink-2)",
          }}>{item}</span>
        ))}
      </div>
    </div>
  );
}

// Deals strip — small horizontal ticket-style cards, visually distinct from the
// larger ProductCard grid that dominates the rest of the page. One deal per
// scroll-column. Container respects its parent column width (no negative
// bleed margins) so it can't push the page into horizontal scroll.
function DealsStrip({ deals, onOpen, onAdd, getQty }) {
  if (!deals.length) return null;
  return (
    <section style={{ minWidth: 0 }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 12 }}>
        <div className="uppercase-mono" style={{ color: "var(--ok)" }}>Deals this week</div>
        <span style={{ fontSize: 12, color: "var(--ink-4)" }}>{deals.length} on</span>
      </div>
      <div className="no-scrollbar" style={{
        display: "flex", gap: 10,
        overflowX: "auto", overflowY: "hidden",
        scrollSnapType: "x mandatory",
        padding: "2px 2px 4px",
        // Edge fade so last partial tile clearly signals "more to the right"
        WebkitMaskImage: "linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%)",
        maskImage: "linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%)",
      }}>
        {deals.map(p => (
          <div key={p.id} style={{
            flexShrink: 0, width: 248,
            scrollSnapAlign: "start",
            display: "flex", gap: 12, padding: 10,
            background: "linear-gradient(100deg, oklch(from var(--ok) l c h / 0.14), oklch(from var(--ok) l c h / 0.04))",
            border: "1px dashed oklch(from var(--ok) l c h / 0.35)",
            borderRadius: 14, alignItems: "center",
          }}>
            <button className="btn-reset" onClick={() => onOpen(p.id)} style={{ flexShrink: 0 }}>
              <Ph label={p.name.split(" ")[0]} aspect="1/1" img={productImage(p)} seed={p.id}
                  style={{ width: 58, height: 58, borderRadius: 10 }} />
            </button>
            <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 }}>
              <div style={{ fontSize: 10.5, letterSpacing: "0.06em", textTransform: "uppercase",
                            fontFamily: '"JetBrains Mono", monospace', color: "var(--ok)", fontWeight: 600 }}>
                {p.deal}
              </div>
              <div style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)",
                            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {p.name}
              </div>
              <span className="num" style={{ fontSize: 12.5, color: "var(--ink-2)", fontWeight: 600 }}>{money(p.price)}</span>
            </div>
            <button className="btn-reset" aria-label={`Add ${p.name}`}
              onClick={() => onAdd(p.id)}
              style={{ flexShrink: 0, width: 34, height: 34, borderRadius: 999,
                       background: "var(--ok)", color: "#fff",
                       display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
              <Icons.Plus size={15} stroke={2.4} />
            </button>
          </div>
        ))}
      </div>
    </section>
  );
}

// Large prominent pickup-ready banner — visible when mode is "pickup". Different
// shape from the PickupBanner on home: full-bleed green stripe with timer dot.
function PickupReadyChip({ store }) {
  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      padding: "12px 18px", borderRadius: 14,
      background: "var(--pickup-bg)", border: "1px solid var(--pickup-border)",
      color: "var(--ok)",
    }}>
      <span style={{
        width: 36, height: 36, borderRadius: 999, flexShrink: 0,
        background: "var(--pickup-icon-bg)", border: "1px solid var(--pickup-icon-border)",
        display: "grid", placeItems: "center", color: "var(--ok)",
      }}>
        <Icons.Pickup size={16} stroke={1.8} />
      </span>
      <div style={{ flex: 1, minWidth: 200, fontSize: 13.5 }}>
        <strong style={{ fontWeight: 600, color: "var(--ink)" }}>{store.pickupEta || "Ready in 8 min"}</strong>
        <span style={{ color: "var(--ink-2)", marginLeft: 8 }}>
          We'll text you when your bag's packed. Show your name at the counter.
        </span>
      </div>
    </div>
  );
}

function StoreHeader({ store, mode, setMode, onNav }) {
  const isMobile = useMedia("(max-width: 720px)");
  return (
    <div style={{ position: "relative" }}>
      {/* Hero */}
      <div style={{ position: "relative", height: isMobile ? 180 : 260, overflow: "hidden",
                    borderRadius: isMobile ? 0 : "var(--radius-lg)" }}>
        <div className="ph" style={{ position: "absolute", inset: 0, borderRadius: 0 }}>
          {(() => {
            const si = storeImage(store);
            if (!si) return <span className="ph-lbl">{store.hero}</span>;
            const src = si.url || `https://loremflickr.com/1600/600/${encodeURIComponent(si.keyword)}/all?lock=${encodeURIComponent(store.id)}`;
            return (
              <>
                <img className="ph-img loaded" src={src}
                  alt={store.hero}
                  onError={(e) => { e.currentTarget.style.display = 'none'; }} />
                <div className="ph-scrim" aria-hidden />
              </>
            );
          })()}
        </div>
        <div aria-hidden style={{
          position: "absolute", inset: 0,
          background: "linear-gradient(180deg, rgba(10,31,68,0.0) 40%, rgba(10,31,68,0.7) 100%)",
        }} />
        {/* back button */}
        <button className="btn-reset" onClick={() => onNav({ screen: "home" })}
          style={{ position: "absolute", top: 16, left: 16,
                   width: 40, height: 40, borderRadius: 999,
                   background: "var(--chrome-bubble)", backdropFilter: "blur(10px)",
                   color: "var(--ink)",
                   border: "1px solid var(--chrome-bubble-border)",
                   display: "inline-flex", alignItems: "center", justifyContent: "center",
                   boxShadow: "var(--shadow-sm)" }}>
          <Icons.ArrowLeft size={18} stroke={2} />
        </button>
        <button className="btn-reset" aria-label="Save"
          style={{ position: "absolute", top: 16, right: 16,
                   width: 40, height: 40, borderRadius: 999,
                   background: "var(--chrome-bubble)", backdropFilter: "blur(10px)",
                   color: "var(--ink)",
                   border: "1px solid var(--chrome-bubble-border)",
                   display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
          <Icons.Heart size={18} stroke={2} />
        </button>
      </div>

      {/* Store info card — overlapping */}
      <div style={{
        margin: isMobile ? "-24px 16px 0" : "-40px 28px 0",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: "var(--radius-lg)", padding: isMobile ? 18 : 22,
        boxShadow: "var(--shadow-md)", position: "relative", zIndex: 2,
      }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 20, flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 300px", minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap", rowGap: 6 }}>
              <h1 className="serif" style={{
                fontSize: isMobile ? 26 : 34,
                margin: 0,
                letterSpacing: "-0.015em",
                lineHeight: 1.2,
                paddingBottom: 4,
                marginBottom: 4,
                textWrap: "balance",
                wordBreak: "break-word",
              }}>{store.name}</h1>
              <Pill tone="ok" icon={<Icons.Dot size={8} stroke={0} style={{ fill: "currentColor" }} />}>Open · til 11p</Pill>
            </div>
            <div style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 4 }}>{store.tagline}</div>
            <div style={{ display: "flex", alignItems: "center", gap: 14, marginTop: 12, flexWrap: "wrap", fontSize: 13, color: "var(--ink-2)" }}>
              <Rating value={store.rating} reviews={store.reviews} />
              <span style={{ color: "var(--ink-4)" }}>·</span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap" }}>
                <Icons.Pin size={13} stroke={1.8} /> {store.distance}
              </span>
              <span style={{ color: "var(--ink-4)" }}>·</span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap" }}>
                <Icons.ShieldCheck size={13} stroke={1.8} /> {store.minOrder}
              </span>
            </div>
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 14 }}>
              {store.tags.map(t => <Pill key={t} tone="default" style={{ fontSize: 11.5, padding: "5px 11px", letterSpacing: "0.01em" }}>{t}</Pill>)}
            </div>
          </div>

          {/* Delivery/pickup toggle */}
          <div style={{ display: "flex", flexDirection: "column", gap: 10, minWidth: 280 }}>
            <div style={{ display: "inline-flex", padding: 3, borderRadius: 999,
                          background: "var(--surface-2)", border: "1px solid var(--line)" }}>
              {[
                { id: "delivery", label: "Delivery", icon: "Truck", eta: store.eta },
                { id: "pickup",   label: "Pickup",   icon: "Store", eta: store.pickupEta || "Ready in 8 min" },
              ].map(m => {
                const Icon = Icons[m.icon];
                const active = mode === m.id;
                return (
                  <button key={m.id} className="btn-reset" onClick={() => setMode(m.id)}
                    style={{
                      display: "inline-flex", alignItems: "center", gap: 8,
                      padding: "8px 16px", borderRadius: 999,
                      background: active ? "var(--navy-fill)" : "transparent",
                      color: active ? "#fff" : "var(--ink-2)",
                      fontSize: 13, fontWeight: 600,
                      transition: "background 0.15s",
                    }}>
                    <Icon size={15} stroke={1.8} /> {m.label}
                    <span style={{ fontSize: 11, opacity: 0.7, fontWeight: 500, whiteSpace: "nowrap" }}>· {m.eta}</span>
                  </button>
                );
              })}
            </div>
            <div style={{ fontSize: 12, color: "var(--ink-4)", display: "flex", alignItems: "center", gap: 6 }}>
              <Icons.Truck size={13} stroke={1.8} /> {store.fee} · {store.hours}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

// Category nav — three variants
function CategoryTabs({ categories, active, onSelect, sticky = true }) {
  const isMobile = useMedia("(max-width: 720px)");
  return (
    <div style={{
      position: sticky ? "sticky" : "static",
      top: 72, zIndex: 10,
      // Fully opaque background — translucent + backdrop-blur was letting the
      // scrolled content (editorial lede, product cards) bleed through and
      // read mid-word, which killed legibility on mobile.
      background: "var(--bg)",
      borderBottom: "1px solid var(--line)",
      padding: isMobile ? "10px 0" : "14px 0",
      margin: isMobile ? "0 -16px" : "0 -28px",
    }}>
      <div className="no-scrollbar" style={{
        display: "flex", gap: 6, overflow: "auto",
        padding: isMobile ? "0 16px" : "0 28px",
      }}>
        {categories.map(c => {
          const Icon = Icons[c.icon];
          const isActive = active === c.id;
          return (
            <button key={c.id} className="btn-reset" onClick={() => onSelect(c.id)}
              style={{
                display: "inline-flex", alignItems: "center", gap: 8,
                padding: "9px 16px", borderRadius: 999,
                background: isActive ? "var(--navy-fill)" : "var(--surface)",
                color: isActive ? "#fff" : "var(--ink-2)",
                border: "1px solid " + (isActive ? "transparent" : "var(--line)"),
                fontSize: 13, fontWeight: 500, whiteSpace: "nowrap",
                transition: "background 0.15s",
              }}>
              <Icon size={14} stroke={1.8} />
              {c.label}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function CategoryRail({ categories, active, onSelect }) {
  const [collapsed, setCollapsed] = React.useState(false);
  return (
    <aside style={{
      position: "sticky", top: 88, alignSelf: "flex-start",
      width: collapsed ? 64 : 264, flexShrink: 0,
      padding: "8px 0",
      transition: "width 0.18s ease",
    }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 12px 8px" }}>
        {!collapsed && <div className="uppercase-mono">Aisles</div>}
        <button className="btn-reset" onClick={() => setCollapsed(v => !v)}
          title={collapsed ? "Expand aisles" : "Collapse aisles"}
          style={{
            width: 26, height: 26, borderRadius: 6,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            color: "var(--ink-3)",
            marginLeft: "auto",
          }}>
          <Icons.Chevron size={14} stroke={2} style={{ transform: collapsed ? "rotate(0deg)" : "rotate(180deg)", transition: "transform 0.18s" }} />
        </button>
      </div>
      <nav style={{ display: "flex", flexDirection: "column", gap: 1 }}>
        {categories.map(c => {
          const Icon = Icons[c.icon];
          const isActive = active === c.id;
          return (
            <button key={c.id} className="btn-reset" onClick={() => onSelect(c.id)}
              title={collapsed ? c.label : undefined}
              style={{
                display: "flex", alignItems: "center", gap: 12,
                padding: collapsed ? "12px 0" : "10px 12px", borderRadius: 10,
                justifyContent: collapsed ? "center" : "flex-start",
                background: isActive ? "var(--navy-tint)" : "transparent",
                color: isActive ? "var(--navy)" : "var(--ink-2)",
                fontSize: 14, fontWeight: isActive ? 600 : 500, textAlign: "left",
                transition: "background 0.12s",
              }}>
              <Icon size={collapsed ? 18 : 16} stroke={1.8} style={{ flexShrink: 0 }} />
              {!collapsed && <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.label}</span>}
              {!collapsed && isActive && <Icons.Chevron size={14} stroke={2} style={{ marginLeft: "auto", flexShrink: 0 }} />}
            </button>
          );
        })}
      </nav>
    </aside>
  );
}

// Product card — larger version with "customize" badge if relevant
function ProductCard({ p, qty, onOpen, onAdd, onRemove, pattern = "morph", density = "regular" }) {
  const compact = density === "compact";
  return (
    <div style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: "var(--radius-lg)", padding: compact ? 10 : 12,
      display: "flex", flexDirection: "column", gap: compact ? 8 : 10,
      transition: "transform 0.1s, box-shadow 0.12s",
      position: "relative",
    }}
    onMouseEnter={(e) => { e.currentTarget.style.boxShadow = "var(--shadow-md)"; }}
    onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "none"; }}>
      <button className="btn-reset" onClick={onOpen} style={{ display: "block", position: "relative" }}>
        <Ph label={p.name.split(" ").slice(0,3).join(" ")} aspect="1/1" radius={compact ? 8 : 10} img={productImage(p)} seed={p.id} />
        {p.popular && (
          <span style={{ position: "absolute", top: 10, left: 10,
                         background: "var(--navy-fill)", color: "#fff",
                         fontSize: 11, fontWeight: 600, padding: "4px 10px 4px 8px",
                         borderRadius: 999, letterSpacing: "0.01em",
                         display: "inline-flex", alignItems: "center", gap: 5,
                         boxShadow: "0 2px 8px rgba(0,0,0,0.18)" }}>
            <span style={{ display: "inline-flex",
                           filter: "drop-shadow(0 0 4px oklch(from var(--accent) l c h / 0.6))" }}>
              <Icons.Flame size={13} stroke={2.2} />
            </span>
            Popular
          </span>
        )}
        {p.trending && !p.popular && (
          <span style={{ position: "absolute", top: 10, left: 10,
                         background: "var(--accent)", color: "#fff",
                         fontSize: 11, fontWeight: 600, padding: "4px 10px 4px 8px",
                         borderRadius: 999, display: "inline-flex", alignItems: "center", gap: 5,
                         boxShadow: "0 2px 8px rgba(0,0,0,0.18)" }}>
            <Icons.TrendingUp size={13} stroke={2.2} />
            Trending
          </span>
        )}
        {p.deal && (
          <span style={{ position: "absolute", bottom: 8, left: 8,
                         background: "var(--ok)", color: "#fff",
                         fontSize: 10.5, fontWeight: 600, padding: "3px 8px",
                         borderRadius: 999, letterSpacing: "0.01em",
                         display: "inline-flex", alignItems: "center", gap: 4,
                         boxShadow: "0 2px 8px rgba(0,0,0,0.22)" }}>
            <Icons.Percent size={11} stroke={2.4} />
            {p.deal}
          </span>
        )}
      </button>
      <div style={{ padding: "0 2px", display: "flex", flexDirection: "column", gap: 4, flex: 1 }}>
        <div style={{ fontSize: compact ? 13 : 14, fontWeight: 500, color: "var(--ink)", lineHeight: 1.3,
                      display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden",
                      minHeight: compact ? 32 : 36 }}>{p.name}</div>
        {/* Meta row — always reserves one line so customizable / stock / unit
            variations can't push the card taller than its neighbors. */}
        <div style={{ fontSize: 11.5, color: "var(--ink-4)", minHeight: 16,
                      display: "flex", alignItems: "center", gap: 6,
                      overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }}>
          {p.stock ? (
            <span style={{ color: "var(--accent)", fontWeight: 600,
                           display: "inline-flex", alignItems: "center", gap: 4 }}>
              <span style={{ width: 6, height: 6, borderRadius: 99, background: "var(--accent)",
                             animation: "pulseDot 1.6s ease-in-out infinite" }} />
              {p.stock}
            </span>
          ) : (
            <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{p.unit}</span>
          )}
        </div>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8,
                      marginTop: "auto", paddingTop: 6 }}>
          <span className="num" style={{ fontSize: compact ? 14 : 15, fontWeight: 600, color: "var(--ink)" }}>{money(p.price)}</span>
          {p.customizable ? (
            <button className="btn-reset" onClick={onAdd} aria-label={`Customize ${p.name}`}
              style={{ height: compact ? 28 : 32, padding: compact ? "0 10px" : "0 12px",
                       borderRadius: 999,
                       background: "var(--navy-fill)", color: "#fff",
                       display: "inline-flex", alignItems: "center", gap: 5,
                       fontSize: compact ? 11.5 : 12.5, fontWeight: 600,
                       boxShadow: "var(--shadow-sm)", flexShrink: 0 }}>
              <Icons.Sliders size={compact ? 11 : 12} stroke={2} />
              Customize
            </button>
          ) : (
            <AddStepper qty={qty} onAdd={onAdd} onRemove={onRemove} size={compact ? "sm" : "md"} pattern={pattern} />
          )}
        </div>
      </div>
    </div>
  );
}

// ─── CatalogSlider ─────────────────────────────────────────────────────────
// Netflix-style horizontal row of ProductCards for a single category. Each
// category on the store page renders one of these. Desktop gets chevron
// controls; mobile relies on swipe + snap.
function CatalogSlider({ products, cardWidth, isMobile, getQty, onOpen, onAdd, onRemove, pattern, density }) {
  const ref = React.useRef(null);
  const [canLeft, setCanLeft] = React.useState(false);
  const [canRight, setCanRight] = React.useState(true);

  const update = React.useCallback(() => {
    const el = ref.current; if (!el) return;
    setCanLeft(el.scrollLeft > 4);
    setCanRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 4);
  }, []);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    update();
    el.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
    return () => {
      el.removeEventListener("scroll", update);
      window.removeEventListener("resize", update);
    };
  }, [update]);
  const step = (dir) => {
    const el = ref.current; if (!el) return;
    el.scrollBy({ left: dir * el.clientWidth * 0.85, behavior: "smooth" });
  };

  return (
    <div style={{ position: "relative" }}>
      <div ref={ref} className="no-scrollbar" style={{
        display: "grid", gridAutoFlow: "column",
        gridAutoColumns: `${cardWidth}px`,
        gap: "var(--grid-gap)",
        overflowX: "auto", overflowY: "hidden",
        scrollSnapType: "x mandatory",
        paddingBottom: 4,
        // Keep the scroller inside the parent's padding so the first card
        // has a predictable gap from the viewport edge. Right-side mask fade
        // signals there's more to scroll without needing a negative bleed.
        paddingRight: isMobile ? 12 : 0,
        WebkitMaskImage: "linear-gradient(90deg, #000 0, #000 calc(100% - 24px), transparent 100%)",
        maskImage: "linear-gradient(90deg, #000 0, #000 calc(100% - 24px), transparent 100%)",
      }}>
        {products.map(p => (
          <div key={p.id} style={{ scrollSnapAlign: "start" }}>
            <ProductCard p={p}
              qty={getQty(p.id)}
              onOpen={() => onOpen(p.id)}
              onAdd={() => onAdd(p)}
              onRemove={() => onRemove(p.id)}
              pattern={pattern}
              density={density} />
          </div>
        ))}
      </div>
      {!isMobile && (canLeft || canRight) && (
        <>
          {canLeft && (
            <button className="btn-reset" onClick={() => step(-1)} aria-label="Scroll left"
              style={{ position: "absolute", top: "50%", left: 6, transform: "translateY(-50%)",
                       width: 36, height: 36, borderRadius: 99,
                       background: "var(--surface)", border: "1px solid var(--line)",
                       boxShadow: "var(--shadow-md)",
                       display: "grid", placeItems: "center",
                       color: "var(--ink-2)", zIndex: 2 }}>
              <Icons.ArrowLeft size={15} stroke={2} />
            </button>
          )}
          {canRight && (
            <button className="btn-reset" onClick={() => step(1)} aria-label="Scroll right"
              style={{ position: "absolute", top: "50%", right: 6, transform: "translateY(-50%)",
                       width: 36, height: 36, borderRadius: 99,
                       background: "var(--surface)", border: "1px solid var(--line)",
                       boxShadow: "var(--shadow-md)",
                       display: "grid", placeItems: "center",
                       color: "var(--ink-2)", zIndex: 2 }}>
              <Icons.ArrowRight size={15} stroke={2} />
            </button>
          )}
        </>
      )}
    </div>
  );
}

function GoesWithStrip({ anchorId, addToCart, onOpen }) {
  const ids = GOES_WITH[anchorId];
  if (!ids) return null;
  const items = ids.map(i => PRODUCT_INDEX[i]).filter(Boolean);
  const anchor = PRODUCT_INDEX[anchorId];
  return (
    <div style={{
      background: "var(--navy-tint)", borderRadius: "var(--radius-lg)",
      padding: 16, display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap",
      border: "1px solid oklch(from var(--navy) l c h / 0.1)",
    }}>
      <div style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--navy)", fontWeight: 600 }}>
        <Icons.Sparkle size={14} stroke={2} />
        Goes with <span style={{ fontWeight: 500 }}>{anchor.name}</span>
      </div>
      <div style={{ display: "flex", gap: 8, flex: 1, flexWrap: "wrap" }}>
        {items.map(p => (
          <button key={p.id} className="btn-reset" onClick={() => onOpen(p.id)}
            style={{ display: "inline-flex", alignItems: "center", gap: 8,
                     padding: "6px 14px 6px 6px", borderRadius: 999,
                     background: "var(--surface)", border: "1px solid var(--line)" }}>
            <Ph label="" aspect="1/1" radius={999} img={productImage(p)} seed={p.id} style={{ width: 28, height: 28 }} />
            <span style={{ fontSize: 12.5, fontWeight: 500 }}>{p.name.split(" ").slice(0,3).join(" ")}</span>
            <span className="num" style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{money(p.price)}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function Store({ storeId, onNav, cart, addToCart, tweaks, initialCategory }) {
  const store = STORES.find(s => s.id === storeId) || STORES[0];
  const voice = STORE_VOICE[store.id];
  const [mode, setMode] = React.useState("delivery");
  const [active, setActive] = React.useState(initialCategory || "featured");
  const isMobile = useMedia("(max-width: 720px)");
  const sectionRefs = React.useRef({});
  const aisleMode = tweaks.categoryNav === "aisle";

  // Group products by category
  const byCategory = React.useMemo(() => {
    const g = {};
    PRODUCTS.forEach(p => {
      (g[p.category] = g[p.category] || []).push(p);
    });
    return g;
  }, []);

  const featured = PRODUCTS.filter(p => p.popular || p.trending).slice(0, 8);
  const deals = PRODUCTS.filter(p => p.deal).slice(0, 8);

  const cols = isMobile ? (tweaks.density === "compact" ? 3 : 2) : (tweaks.density === "compact" ? 6 : tweaks.density === "comfy" ? 4 : 5);
  // Card width for horizontal category sliders. Scales with density on desktop,
  // shrinks on mobile so ~2.2 cards are visible and the next peeks in.
  const cardWidth = isMobile
    ? (tweaks.density === "compact" ? 132 : tweaks.density === "comfy" ? 180 : 160)
    : (tweaks.density === "compact" ? 168 : tweaks.density === "comfy" ? 252 : 210);

  // Scroll-spy: when user scrolls, update the active category
  React.useEffect(() => {
    const onScroll = () => {
      const scrollY = window.scrollY + 200;
      let current = active;
      for (const c of CATEGORIES) {
        const el = sectionRefs.current[c.id];
        if (el && el.offsetTop <= scrollY) current = c.id;
      }
      if (current !== active) setActive(current);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [active]);

  const goCategory = (id) => {
    setActive(id);
    const el = sectionRefs.current[id];
    if (el) window.scrollTo({ top: el.offsetTop - 160, behavior: "smooth" });
  };

  const getQty = (pid) => cart.items.find(i => i.productId === pid)?.qty || 0;

  const renderSection = (cat, idx) => {
    const list = cat.id === "featured" ? featured : (byCategory[cat.id] || []);
    if (list.length === 0) return null;
    const Icon = Icons[cat.icon];
    // In "aisle" mode, render each category as a walk-the-store banner with a big
    // aisle number instead of a tight pill header.
    return (
      <section key={cat.id} ref={el => sectionRefs.current[cat.id] = el}
               style={{ scrollMarginTop: 160, paddingTop: 12 }} data-screen-label={`Aisle ${cat.label}`}>
        {aisleMode ? (
          <div style={{
            display: "flex", alignItems: "center", gap: 16, marginBottom: 18,
            padding: "18px 0",
            borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line-soft)",
            flexWrap: "wrap",
          }}>
            <span className="uppercase-mono" style={{ color: "var(--ink-4)", fontSize: 11 }}>
              Aisle {String(idx + 1).padStart(2, "0")}
            </span>
            <h2 className="serif" style={{ margin: 0, fontSize: isMobile ? 28 : 36, letterSpacing: "-0.01em", flex: 1 }}>
              {cat.label}
            </h2>
            <span style={{ fontSize: 12, color: "var(--ink-4)", fontFamily: '"JetBrains Mono", monospace' }}>
              {list.length} items
            </span>
            {cat.id !== "featured" && (
              <button className="btn-reset" onClick={() => onNav({ screen: "category", storeId: store.id, category: cat.id })}
                style={{ fontSize: 13, color: "var(--navy)", fontWeight: 500,
                         display: "inline-flex", alignItems: "center", gap: 4 }}>
                See all
                <Icons.Chevron size={13} stroke={2} />
              </button>
            )}
          </div>
        ) : (
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14, marginTop: 8 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <span style={{ width: 34, height: 34, borderRadius: 999, background: "var(--surface-2)",
                           display: "inline-flex", alignItems: "center", justifyContent: "center", color: "var(--navy)" }}>
              <Icon size={16} stroke={1.8} />
            </span>
            <h2 className="serif" style={{ margin: 0, fontSize: 24, letterSpacing: "-0.01em" }}>{cat.label}</h2>
            <span style={{ fontSize: 12, color: "var(--ink-4)", fontFamily: '"JetBrains Mono", monospace' }}>{list.length}</span>
          </div>
          {cat.id !== "featured" && (
            <button className="btn-reset" onClick={() => onNav({ screen: "category", storeId: store.id, category: cat.id })}
              style={{ fontSize: 13, color: "var(--navy)", fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 4 }}>
              See all {list.length}
              <Icons.Chevron size={13} stroke={2} />
            </button>
          )}
        </div>
        )}
        <CatalogSlider
          products={list}
          cardWidth={cardWidth}
          isMobile={isMobile}
          getQty={getQty}
          onOpen={(pid) => onNav({ screen: "product", storeId: store.id, productId: pid })}
          onAdd={(p) => p.customizable
            ? onNav({ screen: "product", storeId: store.id, productId: p.id })
            : addToCart(store.id, p.id, 1)}
          onRemove={(pid) => addToCart(store.id, pid, -1)}
          pattern={tweaks.addPattern}
          density={tweaks.density}
        />
        {/* Contextual "goes with" strip after beer category — only for desktop and when something's in cart */}
        {cat.id === "beer" && cart.items.some(i => i.productId === "bw-modelo") && (
          <div style={{ marginTop: 16 }}>
            <GoesWithStrip anchorId="bw-modelo"
              addToCart={(pid) => addToCart(store.id, pid, 1)}
              onOpen={(pid) => onNav({ screen: "product", storeId: store.id, productId: pid })} />
          </div>
        )}
      </section>
    );
  };

  return (
    <div>
      <StoreHeader store={store} mode={mode} setMode={setMode} onNav={onNav} />
      <div style={{ maxWidth: 1440, margin: "0 auto", padding: isMobile ? "24px 16px 120px" : "32px 28px 80px" }}>

        {tweaks.categoryNav === "tabs" && (
          <CategoryTabs categories={CATEGORIES} active={active} onSelect={goCategory} />
        )}

        <div style={{ display: "flex", gap: 28, marginTop: 16 }}>
          {tweaks.categoryNav === "rail" && !isMobile && (
            <CategoryRail categories={CATEGORIES} active={active} onSelect={goCategory} />
          )}

          <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: "var(--section-gap)" }}>
            {/* Orders ticker — social proof that this store is alive right now */}
            {voice && <OrdersNearby voice={voice.orders} />}

            {/* Prominent pickup-ready chip when user has switched to pickup */}
            {mode === "pickup" && <PickupReadyChip store={store} />}

            {/* Lede + Deals — stacked on mobile, 40/60 side-by-side on desktop.
                Both cells get min-width:0 so the deals scroller can't push the
                lede column out past the container on wide screens. */}
            <div style={{
              display: "grid",
              gridTemplateColumns: isMobile ? "1fr" : "2fr 3fr",
              gap: isMobile ? 20 : 32,
              alignItems: "start",
            }}>
              <div style={{ minWidth: 0 }}>
                {voice && <EditorialLede voice={voice.lede} />}
              </div>
              <div style={{ minWidth: 0 }}>
                <DealsStrip deals={deals}
                  getQty={getQty}
                  onOpen={(pid) => onNav({ screen: "product", storeId: store.id, productId: pid })}
                  onAdd={(pid) => addToCart(store.id, pid, 1)} />
              </div>
            </div>

            {CATEGORIES.map((c, i) => renderSection(c, i))}

            {/* End-of-store warm close — distinct shape from HomeFarewell:
                this one's a warm postcard/note style with the store owner's
                voice, not the navy gradient hero the home page uses. */}
            <StoreFarewell store={store} voice={voice} onNav={onNav} isMobile={isMobile} />
          </div>
        </div>
      </div>

      {/* Mobile bottom-sheet nav */}
      {tweaks.categoryNav === "sheet" && isMobile && (
        <BottomSheetNav categories={CATEGORIES} active={active} onSelect={goCategory} />
      )}
    </div>
  );
}

function BottomSheetNav({ categories, active, onSelect }) {
  const [open, setOpen] = React.useState(false);
  const activeCat = categories.find(c => c.id === active) || categories[0];
  const ActiveIcon = Icons[activeCat.icon];
  return (
    <>
      <button className="btn-reset" onClick={() => setOpen(true)}
        style={{ position: "fixed", bottom: 86, left: 16, zIndex: 20,
                 display: "inline-flex", alignItems: "center", gap: 10,
                 padding: "12px 18px", borderRadius: 999,
                 background: "var(--navy-fill)", color: "#fff", fontSize: 14, fontWeight: 600,
                 boxShadow: "var(--shadow-lg)" }}>
        <ActiveIcon size={16} stroke={1.8} /> {activeCat.label}
        <Icons.ChevronDown size={14} stroke={2} style={{ transform: "rotate(180deg)" }} />
      </button>
      {open && (
        <div style={{ position: "fixed", inset: 0, zIndex: 50, background: "rgba(10,31,68,0.4)" }} onClick={() => setOpen(false)}>
          <div className="slide-up" onClick={(e) => e.stopPropagation()}
            style={{ position: "absolute", bottom: 0, left: 0, right: 0,
                     background: "var(--surface)", borderRadius: "20px 20px 0 0",
                     padding: "14px 16px calc(20px + env(safe-area-inset-bottom))" }}>
            <div style={{ width: 36, height: 4, background: "var(--line)", borderRadius: 99, margin: "0 auto 14px" }} />
            <div className="uppercase-mono" style={{ marginBottom: 10 }}>Jump to aisle</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 8 }}>
              {categories.map(c => {
                const Icon = Icons[c.icon];
                const a = c.id === active;
                return (
                  <button key={c.id} className="btn-reset"
                    onClick={() => { onSelect(c.id); setOpen(false); }}
                    style={{ display: "inline-flex", alignItems: "center", gap: 10,
                             padding: "12px 14px", borderRadius: 12,
                             background: a ? "var(--navy-tint)" : "var(--surface-2)",
                             color: a ? "var(--navy)" : "var(--ink-2)",
                             fontSize: 14, fontWeight: 500, textAlign: "left" }}>
                    <Icon size={16} stroke={1.8} />
                    {c.label}
                  </button>
                );
              })}
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// ─── StoreFarewell ─────────────────────────────────────────────────────────
// End-of-store close. Warm postcard/hand-drawn feel with a tiny store map
// pin glyph, the store's own voice, and two CTAs: Ask Opa and Try nearby.
// Intentionally quieter than the HomeFarewell navy gradient — this is a
// pause, not a pitch.
function StoreFarewell({ store, voice, onNav, isMobile }) {
  const nearby = STORES.filter(s => s.id !== store.id).slice(0, 3);
  const firstName = (voice?.lede?.author || store.name).split(/[,\s]/)[0];
  return (
    <section style={{
      marginTop: 8,
      borderRadius: "var(--radius-xl)",
      background: "var(--surface)",
      border: "1px solid var(--line)",
      overflow: "hidden",
      display: "grid",
      gridTemplateColumns: isMobile ? "1fr" : "auto 1fr",
    }}>
      {/* Left: tactile "aisle end" stamp band */}
      <aside style={{
        background: "linear-gradient(160deg, var(--surface-2), oklch(from var(--navy) calc(l + 0.1) c h / 0.1))",
        padding: isMobile ? "22px 24px 16px" : "32px 36px",
        borderRight: isMobile ? "none" : "1px solid var(--line)",
        borderBottom: isMobile ? "1px solid var(--line)" : "none",
        display: "flex", flexDirection: "column", gap: 8,
        minWidth: isMobile ? 0 : 280, alignItems: "flex-start",
      }}>
        <span style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "5px 11px", borderRadius: 999,
          background: "var(--surface)", border: "1px solid var(--line)",
          fontSize: 10.5, letterSpacing: "0.08em", textTransform: "uppercase",
          color: "var(--ink-3)", fontFamily: '"JetBrains Mono", monospace',
        }}>
          <span style={{ width: 6, height: 6, borderRadius: 99, background: "var(--accent)" }} />
          End of aisle walk
        </span>
        <div className="serif" style={{
          marginTop: 4, fontSize: isMobile ? 24 : 30, lineHeight: 1.15,
          letterSpacing: "-0.015em", color: "var(--ink)",
        }}>
          That's the whole shop.
        </div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", fontStyle: "italic", maxWidth: 280 }}>
          {store.name} · {store.distance} · since {store.since || "the block remembers"}
        </div>
      </aside>

      {/* Right: store voice + CTAs */}
      <div style={{ padding: isMobile ? "20px 24px 24px" : "32px 36px" }}>
        <p style={{ margin: 0, fontSize: isMobile ? 15 : 16, lineHeight: 1.55,
                    color: "var(--ink-2)", maxWidth: 560 }}>
          Didn't see what you came for? {firstName} keeps more behind the counter than what's on the shelves.
          Tell Opa what you need — or hop to a nearby store that stocks it.
        </p>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 18 }}>
          <button className="btn-reset" onClick={() => onNav({ screen: "home" })}
            style={{ height: 42, padding: "0 18px", borderRadius: 999,
                     background: "var(--accent)", color: "#fff",
                     fontSize: 14, fontWeight: 600,
                     display: "inline-flex", alignItems: "center", gap: 8 }}>
            <Icons.Sparkle size={14} stroke={2.2} /> Ask Opa
          </button>
          <button className="btn-reset" onClick={() => onNav({ screen: "search" })}
            style={{ height: 42, padding: "0 16px", borderRadius: 999,
                     background: "var(--surface-2)", border: "1px solid var(--line)",
                     color: "var(--ink-2)", fontSize: 14, fontWeight: 500,
                     display: "inline-flex", alignItems: "center", gap: 8 }}>
            <Icons.Search size={14} stroke={1.9} /> Search across stores
          </button>
        </div>

        {nearby.length > 0 && (
          <div style={{ marginTop: 22, borderTop: "1px dashed var(--line)", paddingTop: 16 }}>
            <div className="uppercase-mono" style={{ marginBottom: 10 }}>
              Try nearby
            </div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
              {nearby.map(s => (
                <button key={s.id} className="btn-reset"
                  onClick={() => onNav({ screen: "store", storeId: s.id })}
                  style={{
                    display: "inline-flex", alignItems: "center", gap: 8,
                    padding: "8px 14px 8px 8px", borderRadius: 999,
                    background: "var(--surface-2)", border: "1px solid var(--line)",
                    fontSize: 12.5, color: "var(--ink-2)",
                  }}>
                  <span style={{ width: 22, height: 22, borderRadius: 99, overflow: "hidden",
                                 background: "var(--navy-tint)", flexShrink: 0 }}>
                    <Ph label="" aspect="1/1" radius={99} img={storeImage(s)} seed={s.id} />
                  </span>
                  <span style={{ fontWeight: 500 }}>{s.name}</span>
                  <span style={{ color: "var(--ink-4)" }}>· {s.eta}</span>
                </button>
              ))}
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

window.Store = Store;
window.ProductCard = ProductCard;
window.CatalogSlider = CatalogSlider;
window.StoreFarewell = StoreFarewell;
