// app.jsx — skintick.io landing page

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

// ─── Data ──────────────────────────────────────────────────────────────────
const MARKETPLACES = [
  { id: "buff",       name: "BUFF",      glyph: "B",   color: "#1f2937", ink: "#fff",     latency: "180ms", items: "1.4M" },
  { id: "youpin",     name: "Youpin",    glyph: "悠",  color: "#facc15", ink: "#1f1408",  latency: "210ms", items: "920k" },
  { id: "csfloat",    name: "CSFloat",   glyph: "ƒ",   color: "#0f172a", ink: "#a5b4fc",  latency: "140ms", items: "680k" },
  { id: "steam",      name: "Steam",     glyph: "⌬",   color: "#101820", ink: "#66c0f4",  latency: "320ms", items: "1.1M" },
  { id: "skinport",   name: "Skinport",  glyph: "◇",   color: "#1a1d29", ink: "#fa490a",  latency: "160ms", items: "540k", href: "skinport-api/" },
  { id: "tradeit",    name: "Tradeit",   glyph: "↔",   color: "#0b0f1a", ink: "#22c55e",  latency: "190ms", items: "410k" },
  { id: "csmoney",    name: "CS.MONEY",  glyph: "$",   color: "#1b1d22", ink: "#f5b400",  latency: "200ms", items: "750k" },
  { id: "dmarket",    name: "DMarket",   glyph: "D",   color: "#0f1115", ink: "#39ff14",  latency: "230ms", items: "380k" },
  { id: "skinbaron",  name: "Skinbaron", glyph: "S",   color: "#111827", ink: "#fde047",  latency: "240ms", items: "210k" },
  { id: "waxpeer",    name: "Waxpeer",   glyph: "W",   color: "#13131a", ink: "#a78bfa",  latency: "170ms", items: "330k" },
  { id: "gamerpay",   name: "Gamerpay",  glyph: "G",   color: "#0c1422", ink: "#60a5fa",  latency: "260ms", items: "120k" },
  { id: "lisskins",   name: "LIS-SKINS", glyph: "L",   color: "#161616", ink: "#f97316",  latency: "220ms", items: "440k" },
];

const TICKER_ITEMS = [
  { name: "AK-47 | Redline (FT)",            price: 18.42,  delta: +0.21 },
  { name: "AWP | Asiimov (FT)",              price: 92.18,  delta: -1.47 },
  { name: "★ Karambit | Doppler P2 (FN)",   price: 1842.50, delta: +18.20 },
  { name: "M4A4 | Howl (MW)",                price: 5240.00, delta: +44.10 },
  { name: "Glock-18 | Fade (FN)",            price: 412.30, delta: +2.85 },
  { name: "USP-S | Kill Confirmed (MW)",     price: 168.40, delta: -0.92 },
  { name: "★ Butterfly | Sapphire (FN)",    price: 9820.00, delta: +120.00 },
  { name: "AK-47 | Wild Lotus (FT)",         price: 3210.00, delta: -22.40 },
  { name: "Desert Eagle | Blaze (FN)",       price: 720.20, delta: +4.10 },
  { name: "★ M9 Bayonet | Tiger Tooth (FN)", price: 1280.00, delta: +6.50 },
  { name: "AWP | Dragon Lore (MW)",          price: 11820.00, delta: +85.00 },
  { name: "P250 | Mehndi (FT)",              price: 22.10,  delta: -0.05 },
];

const STATS = [
  { value: 47,   suffix: "B+",  label: "data points indexed" },
  { value: 12,   suffix: "",    label: "marketplaces" },
  { value: 99.98, suffix: "%",  label: "uptime, 12-month",  decimals: 2 },
  { value: 140,  suffix: "ms",  label: "median p50 latency" },
];

const FAQS = [
  {
    q: "How is skintick.io faster than cs2.sh?",
    a: "We poll most marketplaces at 30s intervals and stream pushes for Steam/CSFloat/Skinport. Median end-to-end latency from listing event → API response is 140ms (p50), 380ms (p99). cs2.sh quotes 5-minute granularity on cold reads.",
  },
  {
    q: "Do you support Doppler & Gem phases?",
    a: "Yes. Every variant — Doppler P1-P4, Ruby/Sapphire/Black Pearl, Marble Fade FFI/FIF, Case Hardened blue %, fade % — is its own SKU with independent price history.",
  },
  {
    q: "What about float values and pattern indexes?",
    a: "Float and paint seed are returned on every listing. You can also query historical sale data filtered by float ranges, pattern index, or sticker combos.",
  },
  {
    q: "How long is the historical record?",
    a: "3+ years of 5-minute candles for liquid items, daily candles back to CS:GO 2015 for the long tail. Backfills are continuous as new marketplaces come online.",
  },
  {
    q: "How do I authenticate?",
    a: "Bearer token in the Authorization header. Rotate keys from the dashboard; we support up to 8 active keys per workspace with per-key scopes and rate limits.",
  },
  {
    q: "Is there a free tier or trial?",
    a: "14-day full-access trial on any plan, no card required. After that the Developer tier ($47/mo) is the entry point.",
  },
];

// ─── Hooks & utilities ────────────────────────────────────────────────────
function useInView(ref, opts = { threshold: 0.2 }) {
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); io.disconnect(); }
    }, opts);
    io.observe(ref.current);
    return () => io.disconnect();
  }, []);
  return inView;
}

function useCountUp(target, durationMs, start, decimals = 0) {
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!start) return;
    let raf;
    const t0 = performance.now();
    const tick = (now) => {
      const p = Math.min(1, (now - t0) / durationMs);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(target * eased);
      if (p < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [start, target, durationMs]);
  return decimals > 0 ? val.toFixed(decimals) : Math.round(val).toLocaleString();
}

function fmtUSD(n) {
  return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

// ─── Components ────────────────────────────────────────────────────────────

function PixelLandscape() {
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current || !window.LandscapeAPI) return;
    window.LandscapeAPI.mount(ref.current);
  }, []);
  return <canvas ref={ref} className="landscape-canvas" />;
}

function Nav({ headline }) {
  return (
    <nav className="nav">
      <a href="#" className="brand">skintick.io</a>
      <div className="nav-links">
        <a href="#about">about</a>
        <a href="#markets">markets</a>
        <a href="#api">api</a>
        <a href="#pricing">pricing</a>
        <a href="#" className="text-link">log in</a>
        <a href="#" className="btn btn-primary">sign up</a>
      </div>
    </nav>
  );
}

function Hero({ headline, subhead }) {
  return (
    <header className="hero" data-screen-label="00 Hero">
      <PixelLandscape />
      <div className="hero-content">
        <h1 className="hero-title">{headline}</h1>
        <p className="hero-sub">
          {subhead}
        </p>
        <div className="hero-cta">
          <a href="#" className="btn btn-primary">sign up</a>
          <a href="#api" className="btn btn-secondary">see docs</a>
        </div>
      </div>
      <div className="hero-fade" aria-hidden="true" />
    </header>
  );
}

function Ticker() {
  // Duplicate items so the loop seams cleanly.
  const items = [...TICKER_ITEMS, ...TICKER_ITEMS];
  return (
    <section className="ticker" aria-label="live prices">
      <div className="ticker-tag">
        <span className="ticker-dot" /> LIVE
      </div>
      <div className="ticker-track">
        <div className="ticker-row">
          {items.map((it, i) => (
            <span key={i} className="ticker-item">
              <span className="ticker-name">{it.name}</span>
              <span className="ticker-price">{fmtUSD(it.price)}</span>
              <span className={"ticker-delta " + (it.delta >= 0 ? "up" : "down")}>
                {it.delta >= 0 ? "▲" : "▼"} {Math.abs(it.delta).toFixed(2)}
              </span>
            </span>
          ))}
        </div>
      </div>
    </section>
  );
}

function FrameBox({ label, children, id, wide }) {
  return (
    <section className={"framebox " + (wide ? "framebox-wide" : "")} id={id}>
      <div className="framebox-label">{label}</div>
      <div className="framebox-body">{children}</div>
    </section>
  );
}

function About() {
  return (
    <FrameBox label="about" id="about">
      <p>
        <b>skintick.io</b> is a pricing API for the Counter-Strike 2 skin market. We aggregate <b>real-time</b> and <b>historical</b> price data across every marketplace that matters, normalize it onto a single SKU graph, and serve it back to you with median 140ms p50 latency.
      </p>
      <p>What makes skintick different:</p>
      <ul className="bullets">
        <li><b>Faster.</b> 30-second polling on 9/12 venues + streaming push on Steam, CSFloat, and Skinport. cs2.sh markets 5-minute granularity.</li>
        <li><b>Wider coverage.</b> 12 marketplaces, including DMarket, Gamerpay, LIS-SKINS, and Waxpeer that aren't on most aggregators.</li>
        <li><b>Variant-aware.</b> Every Doppler phase, Gem tier, Fade %, and pattern index is a first-class SKU with its own price history.</li>
        <li><b>Fairer pricing.</b> Unlimited tier at <b>$197/mo</b> — not metered, not throttled.</li>
      </ul>
    </FrameBox>
  );
}

function Stats() {
  const ref = useRef(null);
  const inView = useInView(ref);
  return (
    <section className="stats" ref={ref}>
      {STATS.map((s, i) => (
        <StatCell key={i} stat={s} start={inView} />
      ))}
    </section>
  );
}

function StatCell({ stat, start }) {
  const v = useCountUp(stat.value, 1600, start, stat.decimals || 0);
  return (
    <div className="stat">
      <div className="stat-value">
        {v}<span className="stat-suffix">{stat.suffix}</span>
      </div>
      <div className="stat-label">{stat.label}</div>
    </div>
  );
}

function Marketplaces() {
  const [active, setActive] = useState(null);
  return (
    <FrameBox label="supported marketplaces" id="markets" wide>
      <div className="markets-grid">
        {MARKETPLACES.map((m) => {
          const className = "market " + (active === m.id ? "active" : "");
          const content = (
            <>
              <div className="market-icon" style={{ background: m.color, color: m.ink }}>
                <span className="market-glyph">{m.glyph}</span>
                <span className="market-pixel-overlay" aria-hidden="true" />
              </div>
              <div className="market-meta">
                <span className="market-bullet" /> {m.name}
              </div>
              <div className="market-stats" aria-hidden={active !== m.id}>
                <span>p50 {m.latency}</span>
                <span>{m.items} SKUs</span>
              </div>
            </>
          );

          if (m.href) {
            return (
              <a
                key={m.id}
                href={m.href}
                className={className}
                onMouseEnter={() => setActive(m.id)}
                onMouseLeave={() => setActive(null)}
              >
                {content}
              </a>
            );
          }

          return (
            <button
              key={m.id}
              className={className}
              onClick={() => setActive(active === m.id ? null : m.id)}
              onMouseEnter={() => setActive(m.id)}
              onMouseLeave={() => setActive(null)}
            >
              {content}
            </button>
          );
        })}
      </div>
    </FrameBox>
  );
}

const SAMPLE_JSON = `{
  "sku": "ak47_redline_ft",
  "name": "AK-47 | Redline (Field-Tested)",
  "wear": { "min": 0.15, "max": 0.38 },
  "currency": "USD",
  "updated_at": "2026-05-14T17:42:08Z",
  "markets": {
    "buff":     { "ask": 18.42, "bid": 17.10, "depth": 412, "p50_ms": 180 },
    "csfloat":  { "ask": 19.05, "bid": 18.20, "depth":  87, "p50_ms": 140 },
    "skinport": { "ask": 18.74, "bid": 17.60, "depth": 153, "p50_ms": 160 },
    "steam":    { "ask": 21.30, "bid": null,  "depth": 940, "p50_ms": 320 },
    "dmarket":  { "ask": 19.88, "bid": 18.40, "depth":  62, "p50_ms": 230 }
  },
  "history_24h": {
    "open": 18.10, "high": 18.92, "low": 17.84, "close": 18.42,
    "volume_usd": 184220.55, "trades": 1842
  },
  "variants": [
    { "id": "stattrak",  "ask": 47.20, "premium_pct": 156.2 },
    { "id": "souvenir",  "ask": null,  "premium_pct": null  }
  ]
}`;

function CodeBlock() {
  const ref = useRef(null);
  const inView = useInView(ref, { threshold: 0.1 });
  const [typed, setTyped] = useState("");
  const [done, setDone] = useState(false);

  useEffect(() => {
    if (!inView) return;
    let i = 0;
    const total = SAMPLE_JSON.length;
    let raf;
    const tick = () => {
      // Type ~25 chars per frame for ~1.6s total
      i = Math.min(total, i + 18);
      setTyped(SAMPLE_JSON.slice(0, i));
      if (i < total) raf = requestAnimationFrame(tick);
      else setDone(true);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [inView]);

  return (
    <FrameBox label="api" id="api" wide>
      <div className="api-grid">
        <div className="api-left">
          <p className="kicker">GET</p>
          <code className="endpoint">/v1/items/ak47_redline_ft</code>
          <p className="api-desc">
            One endpoint, every marketplace. Bid/ask depth, 24h OHLCV, variant premiums, all normalized onto a stable SKU. Stream the same payload over WebSockets with <code>wss://stream.skintick.io</code>.
          </p>
          <div className="curl">
            <span className="curl-prompt">$</span> curl -H "Authorization: Bearer sk_live_…" \\<br/>
            <span className="curl-cont">  </span>https://api.skintick.io/v1/items/ak47_redline_ft
          </div>
        </div>
        <pre className="code-pane" ref={ref}>
          <div className="code-pane-bar">
            <span className="code-dot" style={{ background: "#ff5f56" }} />
            <span className="code-dot" style={{ background: "#ffbd2e" }} />
            <span className="code-dot" style={{ background: "#27c93f" }} />
            <span className="code-pane-title">response.json</span>
          </div>
          <code className="code-pane-body">
            {typed}
            {!done && <span className="caret">█</span>}
          </code>
        </pre>
      </div>
    </FrameBox>
  );
}

const TIERS = [
  {
    name: "Developer",
    price: 47,
    blurb: "for one-off projects & weekend builds",
    features: [
      "10k requests / day",
      "All 12 marketplaces",
      "Real-time + 90d history",
      "1 API key",
      "Community Discord",
    ],
    cta: "start 14-day trial",
  },
  {
    name: "Growth",
    price: 97,
    blurb: "for shipped products with paying users",
    features: [
      "50k requests / day",
      "All 12 marketplaces",
      "Real-time + 3y history",
      "WebSocket streams",
      "5 API keys + scopes",
      "Email support, 24h SLA",
    ],
    cta: "start 14-day trial",
    featured: true,
  },
  {
    name: "Pro",
    price: 197,
    blurb: "for trading desks & inventory platforms",
    features: [
      "Unlimited requests",
      "All marketplaces + private feeds",
      "Full historical archive",
      "Streaming + bulk dumps",
      "Unlimited API keys",
      "Shared Slack channel, 2h SLA",
    ],
    cta: "start 14-day trial",
  },
];

function Pricing() {
  return (
    <FrameBox label="pricing" id="pricing" wide>
      <div className="tiers">
        {TIERS.map((t) => (
          <div key={t.name} className={"tier " + (t.featured ? "tier-featured" : "")}>
            {t.featured && <div className="tier-badge">most popular</div>}
            <div className="tier-name">{t.name}</div>
            <div className="tier-price">
              <span className="tier-price-num">${t.price}</span>
              <span className="tier-price-period">/ month</span>
            </div>
            <div className="tier-blurb">{t.blurb}</div>
            <ul className="tier-features">
              {t.features.map((f, i) => (
                <li key={i}><span className="check">+</span> {f}</li>
              ))}
            </ul>
            <a href="#" className={"btn " + (t.featured ? "btn-primary" : "btn-secondary") + " tier-cta"}>{t.cta}</a>
          </div>
        ))}
      </div>
      <p className="tiers-note">
        all plans include the full marketplace set. annual billing: <b>2 months free</b>. self-hosted enterprise on request.
      </p>
    </FrameBox>
  );
}

function FAQ() {
  const [open, setOpen] = useState(0);
  return (
    <FrameBox label="faq" id="faq" wide>
      <div className="faq-list">
        {FAQS.map((f, i) => (
          <div key={i} className={"faq-item " + (open === i ? "open" : "")}>
            <button className="faq-q" onClick={() => setOpen(open === i ? -1 : i)}>
              <span className="faq-marker">{open === i ? "[-]" : "[+]"}</span>
              {f.q}
            </button>
            {open === i && <div className="faq-a">{f.a}</div>}
          </div>
        ))}
      </div>
    </FrameBox>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <div className="footer-grid">
        <div className="footer-col">
          <div className="footer-brand">skintick.io</div>
          <div className="footer-tag">price intelligence for the CS2 skin economy.</div>
        </div>
        <div className="footer-col">
          <div className="footer-h">product</div>
          <a href="#api">API reference</a>
          <a href="skinport-api/">Skinport API</a>
          <a href="#">WebSocket streams</a>
          <a href="#">Bulk exports</a>
          <a href="#">Changelog</a>
        </div>
        <div className="footer-col">
          <div className="footer-h">resources</div>
          <a href="#">Docs</a>
          <a href="status/status.html">Status</a>
          <a href="#">Latency report</a>
          <a href="#">Discord</a>
        </div>
        <div className="footer-col">
          <div className="footer-h">company</div>
          <a href="#about">About</a>
          <a href="#pricing">Pricing</a>
          <a href="terms/">Terms</a>
          <a href="privacy/">Privacy</a>
          <a href="refund/">Refund Policy</a>
          <a href="mailto:support@skintick.io">Contact</a>
        </div>
      </div>
      <p className="footer-disclaimer">
        Skintick is not affiliated with Valve Corporation, Steam, Counter-Strike, Skinport, CSFloat, Tradeit, DMarket, or any other marketplace. All trademarks belong to their respective owners. Data is provided as-is and may be delayed or inaccurate.
      </p>
      <div className="footer-bottom">
        <span>© 2026 skintick.io</span>
        <span>data is provided as-is.</span>
      </div>
    </footer>
  );
}

// ─── Tweaks ────────────────────────────────────────────────────────────────

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "midnight",
  "headline": "The price API for Counter-Strike 2.",
  "subhead": "Real-time and historical prices from 12 marketplaces. Faster, wider coverage, fairer pricing. 3+ years of history with full Doppler & Gem variants.",
  "density": 4
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // Apply theme + density to landscape API
  useEffect(() => {
    if (window.LandscapeAPI) window.LandscapeAPI.setTheme(t.theme);
  }, [t.theme]);
  useEffect(() => {
    if (window.LandscapeAPI) window.LandscapeAPI.setDensity(t.density);
  }, [t.density]);

  return (
    <div className="page">
      <Nav />
      <Hero headline={t.headline} subhead={t.subhead} />
      <Ticker />
      <main className="main">
        <About />
        <Stats />
        <Marketplaces />
        <CodeBlock />
        <Pricing />
        <FAQ />
      </main>
      <Footer />

      <TweaksPanel>
        <TweakSection label="Scene" />
        <TweakRadio
          label="Theme"
          value={t.theme}
          options={[
            { value: "midnight", label: "midnight" },
            { value: "sunset",   label: "sunset"   },
            { value: "matrix",   label: "matrix"   },
            { value: "dusk",     label: "dusk"     },
          ]}
          onChange={(v) => setTweak('theme', v)}
        />
        <TweakSlider
          label="Pixel size"
          value={t.density}
          min={2} max={8} step={1}
          onChange={(v) => setTweak('density', v)}
        />
        <TweakSection label="Copy" />
        <TweakText
          label="Headline"
          value={t.headline}
          onChange={(v) => setTweak('headline', v)}
        />
        <TweakText
          label="Subhead"
          value={t.subhead}
          onChange={(v) => setTweak('subhead', v)}
        />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
