// NekoWebFree — shared UI primitives + subscribe gate

const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

// ──────────────────────────────────────────────────────────────────
// NekoMark — circular logo using the uploaded asset
// ──────────────────────────────────────────────────────────────────
function NekoMark({ size = 28, style = {} }) {
  return (
    <span style={{
      display: "inline-block", width: size, height: size, borderRadius: "50%",
      overflow: "hidden", backgroundImage: "url(assets/logo.jpg)",
      backgroundSize: "cover", backgroundPosition: "center 22%",
      flexShrink: 0, boxShadow: "inset 0 0 0 1px oklch(0.92 0.005 60 / 0.4)",
      ...style,
    }} aria-label="NekoWebFree logo" role="img" />
  );
}

// ──────────────────────────────────────────────────────────────────
// Source pill — visual label of where the download lives
// ──────────────────────────────────────────────────────────────────
function SourcePill({ kind }) {
  const map = {
    "mediafire":     { label: "MediaFire", icon: <FileIcon /> },
    "roblox-create": { label: "Roblox Create Store", icon: <BoxIcon /> },
    "web":           { label: "Web App", icon: <GlobeIcon /> },
  };
  const m = map[kind] || { label: "Download", icon: <FileIcon /> };
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      fontFamily: "var(--mono)", fontSize: 10, letterSpacing: "0.08em",
      textTransform: "uppercase", color: "var(--muted)",
      padding: "3px 8px", border: "1px solid var(--border)", borderRadius: 999,
    }}>{m.icon} {m.label}</span>
  );
}
function FileIcon()   { return <svg width="10" height="10" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M2 1.5h5L10 4v6.5H2z"/><path d="M7 1.5V4h3"/></svg>; }
function BoxIcon()    { return <svg width="10" height="10" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M1.5 3.5L6 1l4.5 2.5v5L6 11l-4.5-2.5z"/><path d="M1.5 3.5L6 6m0 0l4.5-2.5M6 6v5"/></svg>; }
function GlobeIcon()  { return <svg width="10" height="10" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="6" cy="6" r="4.5"/><path d="M1.5 6h9M6 1.5c1.5 1.5 1.5 7.5 0 9M6 1.5c-1.5 1.5-1.5 7.5 0 9"/></svg>; }

// ──────────────────────────────────────────────────────────────────
// VideoPlayer — real YouTube embed (click-to-load thumb → iframe)
// ──────────────────────────────────────────────────────────────────
function VideoPlayer({ videoId, label = "Trailer", fallback }) {
  const [loaded, setLoaded] = useState(false);
  if (!videoId) return fallback || null;
  if (loaded) {
    return (
      <div style={{ position: "relative", width: "100%", aspectRatio: "16/9", borderRadius: 4, overflow: "hidden", background: "black" }}>
        <iframe
          src={`https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`}
          title={label}
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowFullScreen
          style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }}
        />
      </div>
    );
  }
  return (
    <div onClick={() => setLoaded(true)} style={{
      position: "relative", width: "100%", aspectRatio: "16/9", borderRadius: 4,
      overflow: "hidden", cursor: "pointer", background: "black",
    }}>
      <img src={`https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`} alt={label}
        style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}
        onError={(e) => { e.currentTarget.style.display = "none"; }} />
      <div style={{ position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.45), rgba(0,0,0,0.05))" }} />
      <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
        <div style={{
          width: 72, height: 72, borderRadius: "50%", background: "var(--accent)",
          display: "flex", alignItems: "center", justifyContent: "center",
          boxShadow: "0 12px 36px rgba(0,0,0,0.35)",
        }}>
          <svg width="24" height="24" viewBox="0 0 24 24"><polygon points="7,4 7,20 21,12" fill="white" /></svg>
        </div>
      </div>
      <div style={{
        position: "absolute", left: 14, bottom: 12, color: "white",
        fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.06em", textTransform: "uppercase",
        textShadow: "0 1px 4px rgba(0,0,0,0.6)",
      }}>
        ▶ Play preview · YouTube
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────────────
// Striped placeholder for missing thumbs
// ──────────────────────────────────────────────────────────────────
function StripedPlaceholder({ hue = 22, label = "preview", aspect = "16/9", small = false, badge }) {
  const bg = `oklch(0.96 0.018 ${hue})`;
  const stripe = `oklch(0.88 0.05 ${hue})`;
  const ink = `oklch(0.32 0.06 ${hue})`;
  return (
    <div style={{
      position: "relative", width: "100%", aspectRatio: aspect,
      backgroundColor: bg,
      backgroundImage: `repeating-linear-gradient(135deg, ${stripe} 0 1px, transparent 1px 14px)`,
      borderRadius: 4, overflow: "hidden",
      display: "flex", alignItems: "flex-end", padding: small ? 10 : 16,
    }}>
      <div style={{
        fontFamily: "var(--mono)", fontSize: small ? 10 : 11,
        color: ink, letterSpacing: "0.04em", textTransform: "uppercase",
      }}>{label}</div>
      {badge && (
        <div style={{
          position: "absolute", top: small ? 8 : 12, left: small ? 8 : 12,
          fontFamily: "var(--mono)", fontSize: 10, letterSpacing: "0.06em",
          textTransform: "uppercase", color: "var(--bg)", background: "var(--accent)",
          padding: "3px 7px", borderRadius: 2,
        }}>{badge}</div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────────────
// Router (hash-based) — home / browse / category / product / about
// ──────────────────────────────────────────────────────────────────
const RouterCtx = createContext({ route: { name: "home" }, go: () => {} });
function parseHash() {
  const h = (location.hash || "#/").slice(1);
  const parts = h.split("/").filter(Boolean);
  if (parts.length === 0) return { name: "home" };
  if (parts[0] === "c" && parts[1]) return { name: "category", id: parts[1] };
  if (parts[0] === "p" && parts[1]) return { name: "product", id: parts[1] };
  if (parts[0] === "browse") return { name: "browse" };
  if (parts[0] === "about")  return { name: "about" };
  return { name: "home" };
}
function RouterProvider({ children }) {
  const [route, setRoute] = useState(parseHash());
  useEffect(() => {
    const onHash = () => { setRoute(parseHash()); window.scrollTo({ top: 0, behavior: "instant" }); };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  const go = useCallback((path) => { location.hash = path; }, []);
  return <RouterCtx.Provider value={{ route, go }}>{children}</RouterCtx.Provider>;
}
function useRouter() { return useContext(RouterCtx); }

// ──────────────────────────────────────────────────────────────────
// Subscribe gate — locks the site until visitor confirms
// localStorage key: "nekofree.unlocked.v1"
// ──────────────────────────────────────────────────────────────────
const GateCtx = createContext({ unlocked: false, unlock: () => {}, reset: () => {} });
function GateProvider({ children }) {
  const [unlocked, setUnlocked] = useState(() => {
    try { return localStorage.getItem("nekofree.unlocked.v1") === "1"; } catch { return false; }
  });
  const unlock = useCallback(() => {
    try { localStorage.setItem("nekofree.unlocked.v1", "1"); } catch {}
    setUnlocked(true);
  }, []);
  const reset = useCallback(() => {
    try { localStorage.removeItem("nekofree.unlocked.v1"); } catch {}
    setUnlocked(false);
  }, []);
  return <GateCtx.Provider value={{ unlocked, unlock, reset }}>{children}</GateCtx.Provider>;
}
function useGate() { return useContext(GateCtx); }

function SubscribeGate() {
  const { unlock } = useGate();
  const cfg = window.NEKO_CONFIG;
  const [opened, setOpened] = useState(false);
  const [seconds, setSeconds] = useState(0);
  const [hidden, setHidden] = useState(() => {
    try { return sessionStorage.getItem("nekofree.gate.dismissed") === "1"; } catch { return false; }
  });

  // Once they click "Open YouTube" we start a tiny honor-system timer so
  // people don't immediately spam Confirm. 5 seconds.
  useEffect(() => {
    if (!opened) return;
    if (seconds >= 5) return;
    const t = setTimeout(() => setSeconds(s => s + 1), 1000);
    return () => clearTimeout(t);
  }, [opened, seconds]);

  const ready = opened && seconds >= 5;
  const dismiss = () => {
    try { sessionStorage.setItem("nekofree.gate.dismissed", "1"); } catch {}
    setHidden(true);
  };

  if (hidden) {
    // Minimized — small corner pill that reopens the prompt. Content stays fully visible.
    return (
      <button onClick={() => { try { sessionStorage.removeItem("nekofree.gate.dismissed"); } catch {} setHidden(false); }}
        style={{
          position: "fixed", right: 20, bottom: 20, zIndex: 1000,
          display: "inline-flex", alignItems: "center", gap: 10,
          padding: "12px 18px", borderRadius: 999,
          background: "var(--accent)", color: "white", border: "none",
          boxShadow: "0 12px 32px rgba(0,0,0,0.22)",
          fontFamily: "var(--sans)", fontSize: 14, fontWeight: 600, cursor: "pointer",
        }}>
        <YtGlyph /> Subscribe to unlock
      </button>
    );
  }

  return (
    <div onClick={dismiss} style={{
      position: "fixed", inset: 0, zIndex: 1000,
      background: "rgba(20,20,22,0.55)", backdropFilter: "blur(3px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: "32px 24px", overflowY: "auto",
    }}>
      <div onClick={(e)=>e.stopPropagation()} style={{
        maxWidth: 560, width: "100%", position: "relative",
        background: "var(--ink)", color: "var(--bg)",
        borderRadius: 12, padding: "40px 40px 32px",
        boxShadow: "0 40px 100px rgba(0,0,0,0.45)",
        overflow: "hidden",
      }}>
        {/* subtle backdrop inside the card */}
        <GateBackdrop />

        {/* Close */}
        <button onClick={dismiss} aria-label="Close" style={{
          position: "absolute", top: 16, right: 16, zIndex: 2,
          width: 34, height: 34, borderRadius: "50%",
          background: "oklch(0.26 0.005 60)", border: "1px solid oklch(0.4 0.005 60)",
          color: "var(--bg)", cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center", fontSize: 16,
        }}>×</button>

        <div style={{ position: "relative", zIndex: 1 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 24 }}>
            <NekoMark size={30} />
            <span style={{ fontWeight: 700, fontSize: 17, letterSpacing: "-0.01em" }}>NekoWebFree</span>
            <span style={{
              marginLeft: 2, padding: "3px 9px", borderRadius: 999,
              background: "var(--accent)", color: "white",
              fontFamily: "var(--mono)", fontSize: 10, letterSpacing: "0.08em",
            }}>FREE</span>
          </div>

          <h1 style={{
            fontSize: "clamp(28px, 4vw, 38px)", lineHeight: 1.08, letterSpacing: "-0.025em",
            margin: 0, fontWeight: 600,
          }}>
            Enjoying the free models?
            <br /><em style={{ fontStyle: "normal", color: "var(--accent)" }}>Subscribe</em> to support the drops.
          </h1>

          <p style={{ fontSize: 14, color: "oklch(0.82 0.005 60)", lineHeight: 1.55, marginTop: 16, marginBottom: 0 }}>
            Everything here is free. A sub to the channel is the only thing that keeps new systems coming. Browse freely — this just takes a second.
          </p>

          {/* Steps */}
          <ol style={{
            listStyle: "none", padding: 0, margin: "26px 0 0",
            display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10,
          }}>
            <GateStep n="01" title="Open the channel" body={
              <>Opens <strong style={{ fontFamily: "var(--mono)", color: "var(--bg)" }}>{cfg.youtubeHandle}</strong> in a new tab.</>
            } done={opened} active={!opened} />
            <GateStep n="02" title="Hit Subscribe" body="Click the red Subscribe button on YouTube." done={ready} active={opened && !ready} />
          </ol>

          {/* CTA row */}
          <div style={{ display: "flex", flexWrap: "wrap", gap: 10, alignItems: "center", marginTop: 24 }}>
            <a href={cfg.youtubeChannel} target="_blank" rel="noreferrer"
               onClick={() => setOpened(true)}
               style={{
                 display: "inline-flex", alignItems: "center", gap: 10,
                 padding: "13px 20px", borderRadius: 999,
                 background: opened ? "transparent" : "var(--accent)",
                 color: opened ? "var(--bg)" : "white",
                 border: "1px solid",
                 borderColor: opened ? "oklch(0.4 0.005 60)" : "var(--accent)",
                 textDecoration: "none",
                 fontFamily: "var(--sans)", fontSize: 14, fontWeight: 500,
               }}>
              <YtGlyph />
              {opened ? "Re-open YouTube" : "Open YouTube ↗"}
            </a>

            <button onClick={ready ? unlock : undefined}
                    disabled={!ready}
                    style={{
                      display: "inline-flex", alignItems: "center", gap: 10,
                      padding: "13px 22px", borderRadius: 999,
                      background: ready ? "var(--bg)" : "transparent",
                      color: ready ? "var(--ink)" : "oklch(0.55 0.005 60)",
                      border: "1px solid",
                      borderColor: ready ? "var(--bg)" : "oklch(0.4 0.005 60)",
                      fontFamily: "var(--sans)", fontSize: 14, fontWeight: 600,
                      cursor: ready ? "pointer" : "not-allowed",
                      transition: "all 200ms",
                    }}>
              {!opened
                ? "Subscribe first →"
                : !ready
                  ? `Confirming… ${5 - seconds}s`
                  : "✓ Done — thanks!"}
            </button>

            <button onClick={dismiss} style={{
              background: "transparent", border: "none", padding: "8px 6px", cursor: "pointer",
              color: "oklch(0.65 0.005 60)", fontFamily: "var(--sans)", fontSize: 13,
            }}>
              Maybe later
            </button>
          </div>

          {/* Honor note */}
          <div style={{
            marginTop: 24, paddingTop: 18, borderTop: "1px solid oklch(0.3 0.005 60)",
            display: "flex", gap: 12, alignItems: "flex-start",
          }}>
            <span style={{ color: "var(--accent)", fontFamily: "var(--mono)", fontSize: 16, lineHeight: 1 }}>♥</span>
            <div style={{ fontSize: 12, color: "oklch(0.78 0.005 60)", lineHeight: 1.55 }}>
              Honor system — every sub keeps the systems coming. <strong style={{ color: "var(--bg)" }}>Thank you.</strong>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function GateStep({ n, title, body, active, done }) {
  return (
    <li style={{
      padding: 18, borderRadius: 6,
      border: "1px solid",
      borderColor: done ? "var(--accent)" : active ? "oklch(0.4 0.005 60)" : "oklch(0.3 0.005 60)",
      background: done ? "oklch(0.22 0.04 22)" : "oklch(0.2 0.005 60)",
      transition: "all 200ms",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
        <span style={{
          width: 24, height: 24, borderRadius: "50%",
          background: done ? "var(--accent)" : active ? "var(--bg)" : "transparent",
          color: done ? "white" : active ? "var(--ink)" : "oklch(0.55 0.005 60)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "var(--mono)", fontSize: 10, fontWeight: 700,
          border: "1px solid", borderColor: done ? "var(--accent)" : active ? "var(--bg)" : "oklch(0.4 0.005 60)",
        }}>{done ? "✓" : n}</span>
        <span style={{ fontWeight: 600, fontSize: 14, color: "var(--bg)" }}>{title}</span>
      </div>
      <div style={{ fontSize: 12, color: "oklch(0.75 0.005 60)", lineHeight: 1.55 }}>{body}</div>
    </li>
  );
}

function GateBackdrop() {
  return (
    <div aria-hidden style={{
      position: "absolute", inset: 0,
      backgroundImage:
        "radial-gradient(circle at 18% 30%, oklch(0.62 0.20 22 / 0.18) 0, transparent 35%)," +
        "radial-gradient(circle at 82% 70%, oklch(0.62 0.20 22 / 0.10) 0, transparent 40%)",
      pointerEvents: "none",
    }}>
      <svg width="100%" height="100%" style={{ position: "absolute", inset: 0, opacity: 0.5 }}>
        <defs>
          <pattern id="dotgrid" width="40" height="40" patternUnits="userSpaceOnUse">
            <circle cx="1" cy="1" r="1" fill="oklch(0.4 0.005 60 / 0.4)" />
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill="url(#dotgrid)" />
      </svg>
    </div>
  );
}

function YtGlyph() {
  return (
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
      <rect x="2" y="6" width="20" height="12" rx="3" fill="white" />
      <polygon points="10,9 10,15 16,12" fill="var(--accent)" />
    </svg>
  );
}

// ──────────────────────────────────────────────────────────────────
// Top nav
// ──────────────────────────────────────────────────────────────────
function TopNav() {
  const { go, route } = useRouter();
  const data = window.NEKO_DATA;
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 8);
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <header style={{
      position: "sticky", top: 0, zIndex: 50, background: "var(--bg)",
      borderBottom: `1px solid ${scrolled ? "var(--border)" : "transparent"}`,
      transition: "border-color 200ms ease",
    }}>
      <div className="container" style={{ display: "flex", alignItems: "center", height: 64, gap: 32 }}>
        <a href="#/" onClick={(e)=>{e.preventDefault();go("/");}} style={{ display: "flex", alignItems: "center", gap: 10, textDecoration: "none", color: "var(--ink)" }}>
          <NekoMark size={28} />
          <span style={{ fontWeight: 700, fontSize: 17, letterSpacing: "-0.01em" }}>NekoWebFree</span>
          <span style={{
            padding: "2px 8px", borderRadius: 999,
            background: "var(--accent-soft)", color: "var(--accent)",
            fontFamily: "var(--mono)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em",
          }}>FREE</span>
        </a>
        <nav style={{ display: "flex", alignItems: "center", gap: 28, marginLeft: 16 }}>
          <NavLink active={route.name === "home"} onClick={() => go("/")}>Home</NavLink>
          <NavLink active={route.name === "browse" || route.name === "category"} onClick={() => go("/browse")}>All systems</NavLink>
          <NavLink active={route.name === "about"} onClick={() => go("/about")}>About</NavLink>
        </nav>
        <div style={{ flex: 1 }} />
        <a href={window.NEKO_CONFIG.youtubeChannel} target="_blank" rel="noreferrer" style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "7px 14px", borderRadius: 999,
          background: "var(--accent)", color: "white",
          textDecoration: "none", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 500,
        }}>
          <YtGlyph /> Subscribe ↗
        </a>
      </div>
    </header>
  );
}
function NavLink({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      background: "transparent", border: "none", padding: 0, cursor: "pointer",
      fontFamily: "var(--sans)", fontSize: 14, fontWeight: 500,
      color: active ? "var(--ink)" : "var(--muted)",
      position: "relative",
    }}>
      {children}
      {active && <span style={{ position: "absolute", left: 0, right: 0, bottom: -22, height: 2, background: "var(--accent)" }} />}
    </button>
  );
}

// ──────────────────────────────────────────────────────────────────
// Buttons
// ──────────────────────────────────────────────────────────────────
function Btn({ children, primary, onClick, full, type, disabled, style={}, href, external }) {
  const base = {
    fontFamily: "var(--sans)", fontWeight: 500, fontSize: 14,
    padding: "12px 22px", borderRadius: 999, cursor: disabled ? "not-allowed" : "pointer",
    transition: "background 140ms ease, color 140ms ease, opacity 140ms",
    width: full ? "100%" : "auto",
    display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
    opacity: disabled ? 0.5 : 1,
    border: "1px solid",
    textDecoration: "none",
  };
  const variant = primary
    ? { background: "var(--ink)", color: "var(--bg)", borderColor: "var(--ink)" }
    : { background: "var(--bg)", color: "var(--ink)", borderColor: "var(--border)" };
  if (href) {
    return (
      <a href={href} target={external ? "_blank" : undefined} rel={external ? "noreferrer" : undefined}
         style={{ ...base, ...variant, ...style }}>
        {children}
      </a>
    );
  }
  return (
    <button onClick={onClick} type={type} disabled={disabled} style={{ ...base, ...variant, ...style }}>
      {children}
    </button>
  );
}

// ──────────────────────────────────────────────────────────────────
// ProductCard — used in grids. Source pill + Download CTA inline.
// ──────────────────────────────────────────────────────────────────
function ProductCard({ product, onClick }) {
  const cat = window.NEKO_DATA.cats[product.cat];
  const hue = window.NEKO_DATA.hue[product.cat];
  return (
    <article style={{
      display: "flex", flexDirection: "column",
      border: "1px solid var(--border)", borderRadius: 6, overflow: "hidden",
      background: "var(--bg)",
      transition: "border-color 160ms ease",
    }}
    onMouseEnter={(e)=>{e.currentTarget.style.borderColor="var(--ink)";}}
    onMouseLeave={(e)=>{e.currentTarget.style.borderColor="var(--border)";}}>
      <div onClick={onClick} style={{ cursor: "pointer" }}>
        {product.video ? (
          <div style={{ position: "relative", width: "100%", aspectRatio: "16/9", background: "var(--ink)", overflow: "hidden" }}>
            <img src={`https://i.ytimg.com/vi/${product.video}/hqdefault.jpg`} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
            <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.18)" }}>
              <div style={{ width: 42, height: 42, borderRadius: "50%", background: "var(--bg)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 6px 20px rgba(0,0,0,0.3)" }}>
                <svg width="14" height="14" viewBox="0 0 22 22"><polygon points="6,3 6,19 19,11" fill="var(--accent)" /></svg>
              </div>
            </div>
            {product.badge && (
              <div style={{
                position: "absolute", top: 10, left: 10,
                fontFamily: "var(--mono)", fontSize: 10, letterSpacing: "0.06em",
                textTransform: "uppercase", color: "white", background: "var(--accent)",
                padding: "3px 7px", borderRadius: 2,
              }}>{product.badge}</div>
            )}
          </div>
        ) : (
          <StripedPlaceholder hue={hue} label={`${product.id}`} badge={product.badge} />
        )}
      </div>
      <div style={{ padding: "14px 14px 14px", display: "flex", flexDirection: "column", gap: 10, flex: 1 }}>
        <div>
          <div style={{ fontFamily: "var(--mono)", fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 6 }}>
            {cat.label}
          </div>
          <h3 onClick={onClick} style={{ fontSize: 15, fontWeight: 600, margin: 0, lineHeight: 1.3, letterSpacing: "-0.005em", cursor: "pointer" }}>
            {product.name}
          </h3>
        </div>
        <div style={{ flex: 1 }} />
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
          <SourcePill kind={product.link.kind} />
          <a href={product.link.url} target="_blank" rel="noreferrer"
             onClick={(e)=>e.stopPropagation()}
             style={{
               display: "inline-flex", alignItems: "center", gap: 6,
               padding: "8px 14px", borderRadius: 999,
               background: "var(--ink)", color: "var(--bg)",
               textDecoration: "none", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 600,
             }}>
            <DownloadIcon /> Get
          </a>
        </div>
      </div>
    </article>
  );
}
function DownloadIcon() {
  return (
    <svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      <path d="M6 1.5v7"/><path d="M3 6l3 3 3-3"/><path d="M2 10.5h8"/>
    </svg>
  );
}

// ──────────────────────────────────────────────────────────────────
// Footer
// ──────────────────────────────────────────────────────────────────
function FooterCol({ title, children }) {
  return (
    <div>
      <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 14 }}>{title}</div>
      <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 9 }}>
        {children}
      </ul>
    </div>
  );
}
function FooterLink({ href, onClick, external, children }) {
  const props = external
    ? { href, target: "_blank", rel: "noreferrer" }
    : { href, onClick: (e) => { if (onClick) { e.preventDefault(); onClick(); } } };
  return <li><a {...props} style={{ color: "var(--ink)", fontSize: 13, textDecoration: "none" }}>{children}</a></li>;
}

function Footer() {
  const cfg = window.NEKO_CONFIG;
  const { go } = useRouter();
  const { reset } = useGate();
  const s = cfg.socials;
  return (
    <footer style={{ borderTop: "1px solid var(--border)", marginTop: 96, paddingTop: 48, paddingBottom: 56 }}>
      <div className="container" style={{ display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1.2fr", gap: 40 }}>
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16 }}>
            <NekoMark size={26} />
            <span style={{ fontWeight: 700, fontSize: 16 }}>NekoWebFree</span>
          </div>
          <div style={{ color: "var(--muted)", fontSize: 13, lineHeight: 1.6, maxWidth: 340 }}>
            All NoMen Free systems for Roblox. No payment, no whitelist, no catch — just hit subscribe on the channel and you're in.
          </div>
          <div style={{ marginTop: 18, display: "flex", gap: 8, flexWrap: "wrap" }}>
            <SocialPill href={s.youtubeMain.url} label="YouTube" />
            <SocialPill href={s.tiktokMain.url}  label="TikTok" />
            <SocialPill href={cfg.discordInvite} label="Discord" />
          </div>
        </div>
        <FooterCol title="Browse">
          <FooterLink href="#/browse" onClick={() => go("/browse")}>All systems</FooterLink>
          <FooterLink href="#/c/kits" onClick={() => go("/c/kits")}>Base kits</FooterLink>
          <FooterLink href="#/c/chat" onClick={() => go("/c/chat")}>Chat &amp; tags</FooterLink>
          <FooterLink href="#/c/avatar" onClick={() => go("/c/avatar")}>Avatars &amp; NPCs</FooterLink>
          <FooterLink href="#/c/admin" onClick={() => go("/c/admin")}>Admin &amp; server</FooterLink>
        </FooterCol>
        <FooterCol title="Info">
          <FooterLink href="#/about" onClick={() => go("/about")}>About this site</FooterLink>
          <FooterLink href={cfg.discordInvite} external>Discord — nomenfams ↗</FooterLink>
          <li>
            <button onClick={reset} style={{ background: "transparent", border: "none", padding: 0, color: "var(--muted)", fontSize: 12, cursor: "pointer", fontFamily: "var(--mono)", letterSpacing: "0.04em", textTransform: "uppercase" }}>
              Reset subscribe gate
            </button>
          </li>
        </FooterCol>
        <FooterCol title="Follow">
          <FooterLink href={s.youtubeMain.url} external>{s.youtubeMain.label} ↗</FooterLink>
          <FooterLink href={s.youtubeAlt.url}  external>{s.youtubeAlt.label} ↗</FooterLink>
          <FooterLink href={s.tiktokMain.url}  external>{s.tiktokMain.label} ↗</FooterLink>
          <FooterLink href={s.tiktokAlt.url}   external>{s.tiktokAlt.label} ↗</FooterLink>
          <li><span style={{ color: "var(--ink)", fontSize: 13 }}>{s.discord.label}</span></li>
        </FooterCol>
      </div>
      <div className="container" style={{ marginTop: 48, paddingTop: 24, borderTop: "1px solid var(--border)", display: "flex", justifyContent: "space-between", color: "var(--muted)", fontSize: 12, fontFamily: "var(--mono)" }}>
        <span>© 2026 NEKOWEBFREE — by {cfg.creator.toUpperCase()}</span>
        <span>FREE FOREVER · NOMEN SYSTEMS</span>
      </div>
    </footer>
  );
}
function SocialPill({ href, label }) {
  return (
    <a href={href} target="_blank" rel="noreferrer" style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "6px 12px", borderRadius: 999,
      border: "1px solid var(--border)", background: "var(--bg)",
      textDecoration: "none", color: "var(--ink)",
      fontSize: 12, fontWeight: 500, fontFamily: "var(--sans)",
    }}>
      {label} ↗
    </a>
  );
}

// ──────────────────────────────────────────────────────────────────
// Filter chip — used by browse
// ──────────────────────────────────────────────────────────────────
function FilterChip({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      padding: "7px 14px", border: "1px solid",
      borderColor: active ? "var(--ink)" : "var(--border)",
      borderRadius: 999, background: active ? "var(--ink)" : "var(--bg)",
      color: active ? "var(--bg)" : "var(--ink)",
      fontFamily: "var(--sans)", fontSize: 13, fontWeight: 500, cursor: "pointer",
      transition: "all 120ms",
    }}>{children}</button>
  );
}

// ──────────────────────────────────────────────────────────────────
// Section heading
// ──────────────────────────────────────────────────────────────────
function SectionHeading({ kicker, title, action }) {
  return (
    <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 28, gap: 24, flexWrap: "wrap" }}>
      <div>
        <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 12 }}>{kicker}</div>
        <h2 style={{ fontSize: 30, fontWeight: 600, letterSpacing: "-0.02em", margin: 0 }}>{title}</h2>
      </div>
      {action}
    </div>
  );
}

// Expose
Object.assign(window, {
  NekoMark, StripedPlaceholder, SourcePill, VideoPlayer,
  RouterProvider, useRouter,
  GateProvider, useGate, SubscribeGate,
  TopNav, Footer, Btn, ProductCard, FilterChip, SectionHeading,
  DownloadIcon, YtGlyph,
});
