// buff.jsx — skintick.io BUFF163 marketplace API page

const { useState, useEffect, useRef } = React;

// ─── BUFF-specific copy ────────────────────────────────────────────────────

const COVERAGE = [
  { label: "tracked listings",   value: 1_420_000, suffix: "" },
  { label: "unique SKUs",        value: 184_000,   suffix: "" },
  { label: "poll interval",      value: 30,        suffix: "s" },
  { label: "median p50 latency", value: 180,       suffix: "ms" },
];

const FEATURES = [
  {
    title: "Full BUFF orderbook",
    body: "Every active listing — ask price, float, paint seed, sticker combo, owner reputation tier — refreshed every 30 seconds. We see what BUFF sees, not just the top-of-book.",
  },
  {
    title: "CNY → USD normalized",
    body: "Native ¥ prices preserved. USD conversions use BUFF's own displayed rate, refreshed every 4 hours so your arbitrage math matches what a CN trader actually pays.",
  },
  {
    title: "Sold-history archive",
    body: "Every BUFF sale since Aug 2023 — float, pattern, sticker premium, final ¥, time-of-sale. Query the firehose or pull aggregated candles (5m, 1h, 1d).",
  },
  {
    title: "Buy-order book",
    body: "BUFF's bid side, fully indexed. See real buy-side demand for any SKU, by float bucket, by sticker combo, by seller tier.",
  },
  {
    title: "Variant-aware SKUs",
    body: "Doppler P1–P4, Ruby, Sapphire, Black Pearl, Marble Fade FFI/FIF, Case Hardened blue %, fade % — each is its own queryable SKU on BUFF.",
  },
  {
    title: "Anti-throttle infrastructure",
    body: "We run BUFF polling from a rotating CN-region edge pool. You never see 429s; you never lose listings to rate limits. That's our problem, not yours.",
  },
];

const USE_CASES = [
  { tag: "arbitrage",  title: "BUFF ↔ Steam spreads",   body: "Live BUFF ask vs. Steam Community Market quote with realized fees. Surface trades with >12% margin in <500ms." },
  { tag: "inventory",  title: "Inventory valuation",     body: "Mark-to-market a 50,000-item book against BUFF's mid every minute. Power dashboards and PnL." },
  { tag: "trading",    title: "Float & pattern signals", body: "Hunt rare floats (Tier 1 patterns, low-float StatTrak) the moment they hit BUFF, route them to your buyers." },
  { tag: "analytics",  title: "Liquidity reports",       body: "Depth, sell-through velocity, ¥ volume by SKU. The data you need to make a market on the long tail." },
];

const ENDPOINTS = [
  { method: "GET",  path: "/v1/markets/buff/listings/:sku",    blurb: "live orderbook for a SKU" },
  { method: "GET",  path: "/v1/markets/buff/quote/:sku",       blurb: "best ask + best bid, single number" },
  { method: "GET",  path: "/v1/markets/buff/history/:sku",     blurb: "sold-history, paginated or candled" },
  { method: "GET",  path: "/v1/markets/buff/search",           blurb: "filter by float/pattern/stickers" },
  { method: "WS",   path: "wss://stream.skintick.io/buff",     blurb: "push every new listing in real time" },
];

const FAQS = [
  { q: "Is the BUFF data official or scraped?",
    a: "Aggregated from BUFF's public endpoints, with the same rate-limit and ToS discipline that lets us keep delivering it. We do not redistribute account-protected pages." },
  { q: "How fresh is a BUFF price?",
    a: "30-second poll on every active SKU, push-streamed on top 20k SKUs. Median end-to-end (listing event → API) is 180ms. The slowest 1% is under 1.2s." },
  { q: "Do you handle BUFF's CNY pricing?",
    a: "Yes. We store native ¥ and emit USD at BUFF's displayed rate. You can also request raw ¥ + your own FX." },
  { q: "Sticker craft and rare patterns?",
    a: "Sticker SKU on every listing, including position, wear, and applied-value. Pattern indexes are returned and you can filter by Tier 1/2/3 known patterns." },
  { q: "Can I get a sample BUFF dump?",
    a: "Yes — drop your email below and we'll send a 24-hour CSV of liquid SKUs (AK Redline, AWP Asiimov, AK Vulcan, etc.) within 5 minutes." },
];

const SISTER_MARKETS = [
  { id: "csfloat",   name: "CSFloat",   href: "#",    p50: "140ms" },
  { id: "skinport",  name: "Skinport",  href: "skinport-api/", p50: "API page" },
  { id: "youpin",    name: "Youpin",    href: "#",    p50: "210ms" },
  { id: "csmoney",   name: "CS.MONEY",  href: "#",    p50: "200ms" },
  { id: "steam",     name: "Steam",     href: "#",    p50: "320ms" },
  { id: "dmarket",   name: "DMarket",   href: "#",    p50: "230ms" },
];

const SAMPLE_JSON = `{
  "market": "buff",
  "sku": "ak47_redline_ft",
  "name": "AK-47 | Redline (Field-Tested)",
  "currency": "CNY",
  "currency_usd_rate": 7.24,
  "updated_at": "2026-05-14T17:42:08Z",
  "best_ask": {
    "cny": 133.50,
    "usd":  18.44,
    "float": 0.2418,
    "paint_seed": 387,
    "stickers": [],
    "seller_tier": "silver",
    "asset_id": "9821844721"
  },
  "best_bid": {
    "cny": 123.80,
    "usd":  17.10,
    "float_max": 0.30,
    "depth": 412
  },
  "depth": {
    "asks_under_140cny": 9,
    "asks_under_150cny": 18,
    "bids_over_120cny":  61
  },
  "sold_24h": {
    "trades": 1842,
    "volume_cny": 246340.20,
    "vwap_cny": 133.74,
    "vwap_usd":  18.47
  }
}`;

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

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();
}

// ─── Pieces ────────────────────────────────────────────────────────────────

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() {
  return (
    <nav className="nav">
      <a href="index.html" className="brand">
        <img src="/logos/favicon.svg" alt="" className="brand-mark" />
        <span>skintick.io</span>
      </a>
      <div className="nav-links">
        <a href="index.html#about">about</a>
        <a href="index.html#markets">markets</a>
        <a href="index.html#api">api</a>
        <a href="index.html#pricing">pricing</a>
        <a href="#" className="text-link">log in</a>
        <a href="#" className="btn btn-primary">sign up</a>
      </div>
    </nav>
  );
}

function Hero() {
  return (
    <header className="hero hero-market" data-screen-label="00 BUFF Hero">
      <PixelLandscape />
      <div className="hero-content">
        <div className="market-crumb">
          <a href="index.html#markets">markets</a>
          <span className="crumb-sep">/</span>
          <span>BUFF163</span>
        </div>
        <h1 className="hero-title">
          The BUFF163 price API<br/>
          for Counter-Strike 2.
        </h1>
        <p className="hero-sub">
          Real-time orderbook, sold-history, and buy-side depth from China's largest CS2 marketplace. 1.4M listings, 184k SKUs, 30-second polling, 180ms median latency. CNY native, USD normalized.
        </p>
        <div className="hero-cta">
          <a href="#start" className="btn btn-primary">get an API key</a>
          <a href="#endpoints" className="btn btn-secondary">view endpoints</a>
          <div className="hero-foot">
            <span className="hero-foot-dot" /> BUFF feed: <b>operational</b>
            <span className="hero-foot-sep">·</span>
            <span>14s ago</span>
          </div>
        </div>
      </div>
      <div className="hero-fade" aria-hidden="true" />
    </header>
  );
}

function Coverage() {
  const ref = useRef(null);
  const inView = useInView(ref);
  return (
    <section className="coverage" ref={ref}>
      {COVERAGE.map((c, i) => (
        <CoverageCell key={i} item={c} start={inView} />
      ))}
    </section>
  );
}
function CoverageCell({ item, start }) {
  const v = useCountUp(item.value, 1600, start);
  return (
    <div className="stat">
      <div className="stat-value">{v}<span className="stat-suffix">{item.suffix}</span></div>
      <div className="stat-label">{item.label}</div>
    </div>
  );
}

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 Overview() {
  return (
    <FrameBox label="overview" id="overview">
      <p>
        BUFF163 is the largest Counter-Strike 2 marketplace by volume — and the hardest to integrate against. It's a Chinese site with a Chinese rate-limit policy, native ¥ pricing, and a public API that politely declines to be a public API.
      </p>
      <p>
        <b>skintick.io's BUFF feed</b> is a fully-normalized, sub-second view of that marketplace, with the same SKU graph we use for the other 11 venues we cover. Plug in once, query like it's any other endpoint, ship the feature.
      </p>
    </FrameBox>
  );
}

function WhyBuff() {
  return (
    <FrameBox label="why this matters" id="why">
      <ul className="bullets">
        <li><b>~60% of global CS2 liquidity</b> trades on BUFF. Any cross-market arbitrage signal is incomplete without it.</li>
        <li><b>Price discovery happens on BUFF first.</b> Items routinely move 8–15% on BUFF before Steam Community Market catches up.</li>
        <li><b>The float and pattern market</b> — Tier-1 patterns, low-float trophies, rare sticker combos — is overwhelmingly a BUFF market.</li>
        <li><b>Integrating BUFF yourself is a project.</b> CN edge pool, rotating residential IPs, request shaping, CAPTCHA handling, FX, and Mandarin-language sticker normalization. We did it so you don't have to.</li>
      </ul>
    </FrameBox>
  );
}

function Features() {
  return (
    <FrameBox label="what's in the feed" id="features" wide>
      <div className="feat-grid">
        {FEATURES.map((f) => (
          <div key={f.title} className="feat">
            <div className="feat-title">{f.title}</div>
            <div className="feat-body">{f.body}</div>
          </div>
        ))}
      </div>
    </FrameBox>
  );
}

function Endpoints() {
  return (
    <FrameBox label="endpoints" id="endpoints" wide>
      <div className="endpoint-table">
        <div className="endpoint-row endpoint-head">
          <span>method</span><span>path</span><span>description</span>
        </div>
        {ENDPOINTS.map((e) => (
          <div key={e.path} className="endpoint-row">
            <span className={"endpoint-method " + (e.method === "WS" ? "method-ws" : "method-get")}>{e.method}</span>
            <code className="endpoint-path">{e.path}</code>
            <span className="endpoint-blurb">{e.blurb}</span>
          </div>
        ))}
      </div>
    </FrameBox>
  );
}

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 = () => {
      i = Math.min(total, i + 22);
      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="sample response" id="sample" wide>
      <div className="api-grid">
        <div className="api-left">
          <p className="kicker">GET</p>
          <code className="endpoint">/v1/markets/buff/listings/ak47_redline_ft</code>
          <p className="api-desc">
            One call returns BUFF's full quote for a SKU — best ask with its full provenance (float, seed, stickers, seller tier), best bid, depth at common price tiers, and 24h volume in both ¥ and USD.
          </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/markets/buff/listings/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">buff.response.json</span>
          </div>
          <code className="code-pane-body">
            {typed}
            {!done && <span className="caret">█</span>}
          </code>
        </pre>
      </div>
    </FrameBox>
  );
}

function UseCases() {
  return (
    <FrameBox label="what people build" id="use-cases" wide>
      <div className="usecase-grid">
        {USE_CASES.map((u) => (
          <div key={u.title} className="usecase">
            <div className="usecase-tag">{u.tag}</div>
            <div className="usecase-title">{u.title}</div>
            <div className="usecase-body">{u.body}</div>
          </div>
        ))}
      </div>
    </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 SisterMarkets() {
  return (
    <FrameBox label="other marketplace APIs" id="other-markets" wide>
      <p style={{ margin: "0 0 14px" }}>
        BUFF is one of <b>12 marketplaces</b> covered by skintick.io. Same shape, same auth, same SKU graph:
      </p>
      <div className="sisters">
        {SISTER_MARKETS.map((m) => (
          <a key={m.id} href={m.href} className="sister">
            <span className="sister-name">{m.name}</span>
            <span className="sister-p50">p50 {m.p50}</span>
            <span className="sister-arrow">→</span>
          </a>
        ))}
      </div>
    </FrameBox>
  );
}

function StartCTA() {
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);
  const submit = (e) => { e.preventDefault(); if (email) setSubmitted(true); };
  return (
    <section className="start-cta" id="start" data-screen-label="99 CTA">
      <div className="start-inner">
        <h2 className="start-title">Start querying BUFF in 5 minutes.</h2>
        <p className="start-sub">
          14-day full-access trial, no card. Drop your email, get a key and a 24h CSV sample of liquid BUFF SKUs in your inbox within 5 minutes.
        </p>
        {!submitted ? (
          <form className="start-form" onSubmit={submit}>
            <input
              type="email"
              required
              placeholder="you@yourdomain.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              className="start-input"
            />
            <button className="btn btn-primary start-btn" type="submit">get key + sample</button>
          </form>
        ) : (
          <div className="start-thanks">
            <span className="start-check">✓</span> sent. check <b>{email}</b> in the next 5 minutes.
          </div>
        )}
        <div className="start-foot">no card · cancel anytime · 14-day trial on all plans</div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="footer">
      <div className="footer-grid">
        <div className="footer-col">
          <div className="footer-brand">
            <img src="/logos/favicon.svg" alt="" className="footer-brand-mark" />
            <span>skintick.io</span>
          </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="index.html#api">API reference</a>
          <a href="#">WebSocket streams</a>
          <a href="#">Bulk exports</a>
          <a href="#">Changelog</a>
        </div>
        <div className="footer-col">
          <div className="footer-h">markets</div>
          <a href="buff.html">BUFF163</a>
          <a href="#">CSFloat</a>
          <a href="skinport-api/">Skinport</a>
          <a href="#">Steam</a>
        </div>
        <div className="footer-col">
          <div className="footer-h">company</div>
          <a href="index.html#about">About</a>
          <a href="index.html#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",
  "density": 4
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  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 />
      <main className="main">
        <Overview />
        <WhyBuff />
        <Coverage />
        <Features />
        <Endpoints />
        <CodeBlock />
        <UseCases />
        <FAQ />
        <SisterMarkets />
      </main>
      <StartCTA />
      <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)}
        />
      </TweaksPanel>
    </div>
  );
}

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