
const { useMemo, useState, useEffect } = React;

const PINK = "#ff4fa3";
const BLUE = "#4f8bff";
const fav = (domain) => `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
const host = (url) => { try { return new URL(url).hostname; } catch { return url; } };

// ErrorBoundary to avoid blank screens
class ErrorBoundary extends React.Component{
  constructor(props){ super(props); this.state={hasError:false, error:null}; }
  static getDerivedStateFromError(error){ return {hasError:true, error}; }
  componentDidCatch(error, info){ console.error("CamCompare Error:", error, info); }
  render(){
    if(this.state.hasError){
      return (<div className="max-w-[1415px] mx-auto p-4 mt-4 rounded-xl border border-red-200 bg-red-50 text-red-800 text-sm">
        <div className="font-semibold mb-1">Something went wrong loading this section.</div>
        <div className="opacity-80">Please reload the page. If it persists, re-install the plugin zip v17.4.</div>
      </div>);
    }
    return this.props.children;
  }
}

// Data (includes customer metrics + deals)
const PLATFORMS = [
  { name: "Flirt4Free", site: "https://f4f.link/m/340677", ratePct: 60, payout: "Weekly", payoutMin: "$50", payoutMethods:["SEPA","SWIFT"], payoutFees:"Varies", estCustomersK: 3000, estModelsK: 150, features:{ vod:true, lovense:true, ppvClips:true, vr:false, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Established network with tipping, shows & VOD.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.1, modelDiversity:["Women","Men","Couples"], activeModelsK:12, privatePerMinUSD:2.99, groupPerMinUSD:1.49, videoQuality:"HD/4K", interactive:["Cam2Cam","Lovense","Games"], freeContent:"Medium", uiNavigation:4.4, privacySecurity:["SSL","2FA"], payments:["Cards","Crypto"], reviewsScore:4.3, support:"24/7 chat", perks:["Welcome credits"] }, deals:{ model:{ headline:"Signup boost", perks:["Faster verification","Weekly payouts"], code:null, link:"https://jayson-d.flirt4free.com" }, customer:{ signupBonus:"Welcome credits", welcomeCredits:20, creditBonusPct:10, bundles:["$20 → $22","$50 → $55"], finePrint:"Promos vary by region; limited time.", code:null, link:"https://jayson-d.flirt4free.com" } } },
  { name: "OnlyFans (Live)", site: "https://onlyfans.com?ref=517803888", ratePct: 80, payout: "Weekly", payoutMin: "$50", payoutMethods:["SEPA","SWIFT"], payoutFees:"Medium", estCustomersK: 12000, estModelsK: 1500, features:{ vod:true, lovense:true, ppvClips:true, vr:false, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Huge user base; live exists but discovery is limited vs cam sites.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.0, modelDiversity:["Women","Men","NB","Trans","Couples"], activeModelsK:40, privatePerMinUSD:3.99, groupPerMinUSD:1.99, videoQuality:"HD", interactive:["Cam2Cam","Lovense"], freeContent:"Low", uiNavigation:4.2, privacySecurity:["SSL","2FA"], payments:["Cards"], reviewsScore:4.2, support:"Email", perks:["Fan clubs","Clips"] }, deals:{ model:{ headline:"Creator promo", perks:["80% revenue share","Bonuses vary"], code:null, link:"https://onlyfans.com?ref=517803888" }, customer:{ signupBonus:"Occasional discounts", welcomeCredits:0, creditBonusPct:0, bundles:["Seasonal sales"], finePrint:"Offers vary by creator/site campaigns.", code:null, link:"https://onlyfans.com?ref=517803888" } } },
  { name: "BongaCams", site: "https://bongacash.com/model-ref?c=827213", ratePct: 60, payout: "Weekly", payoutMin: "$50", payoutMethods:["SEPA","SWIFT"], payoutFees:"Low", estCustomersK: 6500, estModelsK: 400, features:{ vod:true, lovense:true, ppvClips:false, vr:false, mobileApp:false, twoFA:true, geoBlocking:true }, notes:"High exposure in EU markets, weekly payouts.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.3, modelDiversity:["Women","Men","Trans"], activeModelsK:18, privatePerMinUSD:2.49, groupPerMinUSD:1.29, videoQuality:"HD", interactive:["Lovense","Games"], freeContent:"High", uiNavigation:4.0, privacySecurity:["SSL","2FA"], payments:["Cards","Crypto"], reviewsScore:4.1, support:"24/7 chat", perks:["Bonuses"] }, deals:{ model:{ headline:"Model signup perks", perks:["EU traffic boost","Weekly payouts"], code:null, link:"https://bongacash.com/model-ref?c=827213" }, customer:{ signupBonus:"Welcome credits", welcomeCredits:30, creditBonusPct:15, bundles:["$20 → $23","$50 → $57.5"], finePrint:"Bonus auto‑applies on selected packs.", code:null, link:"https://bongacams.com" } } },
  { name: "Fansly (Live)", site: "https://fansly.com/application/form?r=comparecamsites", ratePct: 80, payout: "Weekly", payoutMin: "$100", payoutMethods:["SEPA","SWIFT"], payoutFees:"Medium", estCustomersK: 1500, estModelsK: 90, features:{ vod:true, lovense:true, ppvClips:true, vr:false, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Sub/PPV focus with live add-on.", customer:{ pricePerTokenUSD:0.10, valueForMoney:3.9, modelDiversity:["Women","Men","NB","Trans"], activeModelsK:6, privatePerMinUSD:3.49, groupPerMinUSD:1.59, videoQuality:"HD", interactive:["Lovense"], freeContent:"Low", uiNavigation:4.0, privacySecurity:["SSL","2FA"], payments:["Cards"], reviewsScore:4.0, support:"Ticket", perks:["Clips"] }, deals:{ model:{ headline:"New creator ramp", perks:["80% share","Tier promos"], code:null, link:"https://fansly.com/application/form?r=comparecamsites" }, customer:{ signupBonus:"Seasonal promo", welcomeCredits:10, creditBonusPct:5, bundles:["Creator bundles"], finePrint:"Creator‑specific discounts may apply.", code:null, link:"https://fansly.com/comparewebcamsites?r=comparewebcamsites" } } },
  { name: "Chaturbate", site: "http://chaturbate.com/accounts/register/?studio=jjaysond", ratePct: 50, payout: "Weekly", payoutMin: "$50", payoutMethods:["SEPA","SWIFT","Crypto"], payoutFees:"Varies", estCustomersK: 8000, estModelsK: 500, features:{ vod:true, lovense:true, ppvClips:true, vr:false, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Token-based, strong traffic, robust Lovense support.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.4, modelDiversity:["Women","Men","Couples","Trans"], activeModelsK:22, privatePerMinUSD:2.99, groupPerMinUSD:1.49, videoQuality:"HD/4K", interactive:["Cam2Cam","Lovense","Games"], freeContent:"High", uiNavigation:4.5, privacySecurity:["SSL","2FA"], payments:["Cards","Crypto"], reviewsScore:4.5, support:"24/7 chat", perks:["Teasers"] }, deals:{ model:{ headline:"Fast-track start", perks:["Weekly payouts","High traffic"], code:null, link:"http://chaturbate.com/accounts/register/?studio=jjaysond" }, customer:{ signupBonus:"Token sales", welcomeCredits:0, creditBonusPct:10, bundles:["$20 → $22","$100 → $110"], finePrint:"Frequent rotating sales; check banner.", code:null, link:"https://chaturbate.com/in/?tour=grq0&campaign=tRJ0r&track=default" } } },
  { name: "MyFreeCams", site: "https://t.amyfc.link/388186/779/0?bo=2779,2778,2777,2776,2775&po=6533&aff_sub5=SF_006OG000004lmDN", ratePct: 60, payout: "Bi-weekly", payoutMin: "$100", payoutMethods:["SWIFT"], payoutFees:"Medium", estCustomersK: 4200, estModelsK: 220, features:{ vod:false, lovense:true, ppvClips:false, vr:false, mobileApp:false, twoFA:true, geoBlocking:true }, notes:"Established brand; member culture heavy.", customer:{ pricePerTokenUSD:0.10, valueForMoney:3.8, modelDiversity:["Women","Couples"], activeModelsK:10, privatePerMinUSD:2.49, groupPerMinUSD:1.19, videoQuality:"HD", interactive:["Lovense"], freeContent:"Medium", uiNavigation:3.9, privacySecurity:["SSL","2FA"], payments:["Cards"], reviewsScore:4.0, support:"Email", perks:["Clubs"] }, deals:{ model:{ headline:"Standard onboarding", perks:["Community-centric"], code:null, link:"https://t.amyfc.link/388186/779/0?bo=2779,2778,2777,2776,2775&po=6533&aff_sub5=SF_006OG000004lmDN" }, customer:{ signupBonus:"VIP promos", welcomeCredits:0, creditBonusPct:5, bundles:["VIP club perks"], finePrint:"Promos vary; see site.", code:null, link:"https://t.amyfc.link/388186/779/0?bo=2779,2778,2777,2776,2775&po=6533&aff_sub5=SF_006OG000004lmDN" } } },
  { name: "Stripchat", site: "https://go.rmhfrtnd.com?path=%2Fsignup%2Fuser&userId=28301efbaad60d0dce6cbe01d0ba21e5d5dbb61b85ad4b2a66eaa0c9d49dbd80", ratePct: 60, payout: "Weekly", payoutMin: "$100", payoutMethods:["SEPA","SWIFT","Crypto"], payoutFees:"Varies", estCustomersK: 7000, estModelsK: 350, features:{ vod:true, lovense:true, ppvClips:true, vr:true, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Modern UX, VR shows, strong discoverability.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.5, modelDiversity:["Women","Men","Trans","Couples"], activeModelsK:20, privatePerMinUSD:2.99, groupPerMinUSD:1.49, videoQuality:"HD/4K", interactive:["Cam2Cam","Lovense","VR"], freeContent:"Medium", uiNavigation:4.6, privacySecurity:["SSL","2FA"], payments:["Cards","Crypto"], reviewsScore:4.4, support:"24/7 chat", perks:["Welcome credits","Multilingual"] }, deals:{ model:{ headline:"Launch perks", perks:["VR support","Weekly payouts"], code:null, link:"https://go.rmhfrtnd.com?path=%2Fsignup%2Fuser&userId=28301efbaad60d0dce6cbe01d0ba21e5d5dbb61b85ad4b2a66eaa0c9d49dbd80" }, customer:{ signupBonus:"Welcome pack", welcomeCredits:25, creditBonusPct:10, bundles:["$20 → $22","$50 → $55"], finePrint:"Regional restrictions apply.", code:null, link:"https://go.rmhfrtnd.com?path=%2Fsignup%2Fuser&userId=28301efbaad60d0dce6cbe01d0ba21e5d5dbb61b85ad4b2a66eaa0c9d49dbd80" } } },
  { name: "CamSoda", site: "https://www.camsoda.com/models?id=comparecamsites", ratePct: 50, payout: "Weekly", payoutMin: "$50", payoutMethods:["SEPA","SWIFT","ACH"], payoutFees:"Low", estCustomersK: 2300, estModelsK: 120, features:{ vod:true, lovense:true, ppvClips:true, vr:true, mobileApp:true, twoFA:true, geoBlocking:true }, notes:"Tech-forward, interactive toys & VR.", customer:{ pricePerTokenUSD:0.10, valueForMoney:4.0, modelDiversity:["Women","Men","Trans"], activeModelsK:8, privatePerMinUSD:2.79, groupPerMinUSD:1.39, videoQuality:"HD/4K", interactive:["Lovense","VR"], freeContent:"Medium", uiNavigation:4.1, privacySecurity:["SSL","2FA"], payments:["Cards"], reviewsScore:4.1, support:"Chat", perks:["Clips"] }, deals:{ model:{ headline:"Model incentives", perks:["VR ready","Weekly payouts"], code:null, link:"https://www.camsoda.com/models?id=comparecamsites" }, customer:{ signupBonus:"Occasional credits", welcomeCredits:15, creditBonusPct:10, bundles:["Bundles & sales"], finePrint:"Rotating promos; check homepage.", code:null, link:"https://www.camsoda.com/?id=comparecamsites&type=REV" } } },
].map(p => ({ ...p, logo: fav(host(p.site)) }));

// Webcams & Toys — images + purchase links
const WEBCAMS = [
  { name: "Logitech Brio 4K", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/webcam_resized_with_border.png", buyUrl: "https://amzn.to/42gRti0" },
  { name: "Elgato Facecam", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Elgato-Facecam.png", buyUrl: "https://amzn.to/4niw00o" },
  { name: "Razer Kiyo Pro", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Razer-Kiyo-Pro.png", buyUrl: "https://amzn.to/4mNyu6Z" },
  { name: "Insta360 Link", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Insta360-Link.png", buyUrl: "https://amzn.to/3VCbJHb" },
  { name: "OBSBOT Tiny", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/OBSBOT-Tiny.png", buyUrl: "https://amzn.to/4m7g1kR" },
  { name: "Logitech StreamCam", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Logitech-StreamCam.png", buyUrl: "https://amzn.to/41FqkFt" },
];

const TOYS = [
  { name: "Lovense Lush 3", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Lush-3.png", buyUrl: "https://www.lovense.com/r/cjco0x" },
  { name: "Lovense Lush 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Lush-2.png", buyUrl: "https://www.lovense.com/r/qs7tch" },
  { name: "Lovense Edge 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Edge-2-.png", buyUrl: "https://www.lovense.com/r/oq5l4l" },
  { name: "Lovense Lush Mini", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Lush-Mini.png", buyUrl: "https://www.lovense.com/r/ojrz7e" },
  { name: "Lovense Nora", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Nora.png", buyUrl: "https://www.lovense.com/r/u2iy44" },
  { name: "Lovense Domi 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Domi-2.png", buyUrl: "https://www.lovense.com/r/3djzqa" },
  { name: "Lovense Gush", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Gush.png", buyUrl: "https://www.lovense.com/r/vuip60" },
  { name: "Lovense Gush 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Gush-2.png", buyUrl: "https://www.lovense.com/r/510u5l" },
  { name: "Lovense Mission 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Mission-2.png", buyUrl: "https://www.lovense.com/r/cel34c" },
  { name: "Lovense Gravity", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Gravity-Sex-Toy.png", buyUrl: "https://www.lovense.com/r/wmaufk" },
  { name: "Lovense Lush 4", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Lovense-Lush-4.png", buyUrl: "https://www.lovense.com/r/qs7tch" },
  { name: "Solace Pro", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Solace-Pro.png", buyUrl: "https://www.lovense.com/r/v1brkw" },
  { name: "Solace Max 2", img: "https://comparewebcamsites.com/wp-content/uploads/2025/09/Solace-Max-2-Toy.png", buyUrl: "https://www.lovense.com/r/ntajpt" },
];

function Tag({ children }){
  return <span className="px-2 py-1 rounded-full text-xs font-medium bg-gradient-to-r from-pink-100 to-blue-100 text-slate-700 border border-slate-200">{children}</span>;
}
function Pill({ active, label, onClick }){
  return <button onClick={onClick} className={`px-3 py-1 rounded-full text-sm border transition ${active?"bg-blue-50 border-blue-200 text-blue-700":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>{label}</button>;
}
function Range({ label, value, onChange, min, max, step=1, suffix="" }){
  return (<div className="flex flex-col gap-1">
    <div className="text-xs text-slate-600 flex justify-between"><span>{label}</span><span className="font-medium">{value}{suffix}</span></div>
    <input type="range" value={value} min={min} max={max} step={step} onChange={(e)=>onChange(Number(e.target.value))} className="w-full accent-blue-500" />
  </div>);
}
function NumberInput({ label, value, onChange, min, max, step=1 }){
  return (<div className="flex flex-col gap-1">
    <div className="text-xs text-slate-600">{label}</div>
    <input type="number" className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-200" value={value} min={min} max={max} step={step} onChange={(e)=>onChange(Number(e.target.value))} />
  </div>);
}
function Card({ children }){ return <div className="rounded-2xl border border-slate-200 bg-white shadow-sm p-5">{children}</div>; }

// ---------------- Mode-aware filters ----------------
function useModelFilters(){ const [f,setF]=useState({ query:"", minRate:40, minCustomersK:0, maxCustomersK:20000, minModelsK:0, maxModelsK:2000, feat:{ vod:false, lovense:false, ppvClips:false, vr:false, mobileApp:false, twoFA:false, geoBlocking:false }, payoutFilters:[], sortBy:"score" }); return [f,setF]; }
function useCustomerFilters(){ const [f,setF]=useState({ query:"", tokenMin:0.05, tokenMax:0.20, privateMax:4.00, groupMax:2.00, minValue:0.0, payments:{ Cards:false, Crypto:false }, interactive:{ "Cam2Cam":false, "Lovense":false, "VR":false }, freeContent:{ Low:false, Medium:false, High:false }, sortBy:"value" }); return [f,setF]; }

function filterModelRows(filters){
  const q=(filters.query||"").toLowerCase().trim();
  let rows=PLATFORMS.filter(p=>{
    const matches=!q||p.name.toLowerCase().includes(q);
    const withinRate=p.ratePct>=filters.minRate;
    const withinCustomers=p.estCustomersK>=filters.minCustomersK&&p.estCustomersK<=filters.maxCustomersK;
    const withinModels=p.estModelsK>=filters.minModelsK&&p.estModelsK<=filters.maxModelsK;
    const featOK=Object.entries(filters.feat).every(([k,v])=> v? p.features[k]: true);
    const payoutOK=filters.payoutFilters.length===0 || filters.payoutFilters.includes(p.payout);
    return matches&&withinRate&&withinCustomers&&withinModels&&featOK&&payoutOK;
  }).map(p=>({...p, earningsPer100:(p.ratePct/100)*100, score:p.ratePct*0.5 + p.estCustomersK*0.004 + p.estModelsK*0.03 + (p.features.lovense?2:0) + (p.features.vr?1:0)}));
  const sorter={ score:(a,b)=>b.score-a.score, rate:(a,b)=>b.ratePct-a.ratePct, customers:(a,b)=>b.estCustomersK-a.estCustomersK, models:(a,b)=>b.estModelsK-a.estModelsK, name:(a,b)=>a.name.localeCompare(b.name) }[filters.sortBy] || ((a,b)=>0);
  rows.sort(sorter); return rows;
}

function filterCustomerRows(filters){
  const q=(filters.query||"").toLowerCase().trim();
  let rows=PLATFORMS.filter(p=>{
    const c=p.customer||{};
    if(!(!q || p.name.toLowerCase().includes(q))) return false;
    if(!(c.pricePerTokenUSD>=filters.tokenMin && c.pricePerTokenUSD<=filters.tokenMax)) return false;
    if(!(c.privatePerMinUSD<=filters.privateMax)) return false;
    if(!(c.groupPerMinUSD<=filters.groupMax)) return false;
    if(!(c.valueForMoney>=filters.minValue)) return false;
    for(const k of Object.keys(filters.payments)){ if(filters.payments[k] && !(c.payments||[]).includes(k)) return false; }
    for(const k of Object.keys(filters.interactive)){ if(filters.interactive[k] && !(c.interactive||[]).includes(k)) return false; }
    const selectedFC = Object.keys(filters.freeContent).filter(k=>filters.freeContent[k]);
    if(selectedFC.length>0 && !selectedFC.includes(String(c.freeContent))) return false;
    return true;
  }).map(p=>({...p, ...p.customer}));
  const sorter={ value:(a,b)=> b.valueForMoney-a.valueForMoney || b.activeModelsK-a.activeModelsK, token:(a,b)=> a.pricePerTokenUSD-b.pricePerTokenUSD, private:(a,b)=> a.privatePerMinUSD-b.privatePerMinUSD, group:(a,b)=> a.groupPerMinUSD-b.groupPerMinUSD, active:(a,b)=> b.activeModelsK-a.activeModelsK, name:(a,b)=> a.name.localeCompare(b.name) }[filters.sortBy] || ((a,b)=>0);
  rows.sort(sorter); return rows;
}

// ---------------- Drawer with vertical tab ----------------
function FilterDrawer({ open, setOpen, mode, modelFilters, setModelFilters, customerFilters, setCustomerFilters }){
  const setFeat=(k,v)=> setModelFilters(f=>({...f, feat:{...f.feat,[k]:v}}));
  const togglePayout=(val)=> setModelFilters(f=>({...f, payoutFilters: f.payoutFilters.includes(val)? f.payoutFilters.filter(x=>x!==val) : [...f.payoutFilters,val]}));
  const PAYOUT_OPTIONS = Array.from(new Set(PLATFORMS.map(p=>p.payout)));

  const Tab = (
    <button
      aria-label="Open filters"
      onClick={()=>setOpen(true)}
      className={`fixed left-0 top-1/2 -translate-y-1/2 z-[10000] bg-gradient-to-b from-pink-600 to-blue-600 text-white shadow-lg rounded-r-xl px-2 py-3 border border-white/20
                 [writing-mode:vertical-rl] tracking-wide ${open?'opacity-0 pointer-events-none':'opacity-100'}`}
      style={{ transform: 'translateY(-50%)' }}
    >
      Filters
    </button>
  );

  return (<>
    {Tab}
    {open && <div className="fixed inset-0 z-[9998] bg-black/30" onClick={()=>setOpen(false)} />}
    <div className={`fixed z-[9999] top-24 left-0 w-[250px] max-w-[85vw] h-[calc(100vh-6rem)] bg-white border-r border-slate-200 shadow-xl transition-transform ${open?"translate-x-0":"-translate-x-full"}`}>
      <div className="p-4 flex items-center justify-between border-b border-slate-200">
        <div className="flex items-center gap-2"><span className="w-2 h-2 rounded-full" style={{background:PINK}} /> <h3 className="font-semibold text-slate-800">Filters — {mode==="model"?"For Models":"For Customers"}</h3></div>
        <button onClick={()=>setOpen(false)} className="px-2 py-1 text-sm rounded-md border border-slate-200 hover:bg-slate-50">Close</button>
      </div>
      <div className="p-4 space-y-4 overflow-y-auto">
        {mode==="model" ? (<>
          <div><div className="text-xs text-slate-600 mb-1">Search</div><input type="text" placeholder="Find a platform..." className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-pink-200" value={modelFilters.query} onChange={(e)=>setModelFilters(f=>({...f, query:e.target.value}))} /></div>
          <Range label="Minimum revenue share (%)" value={modelFilters.minRate} min={0} max={100} onChange={(v)=>setModelFilters(f=>({...f,minRate:v}))} />
          <div className="grid grid-cols-2 gap-3">
            <NumberInput label="Min customers (K)" value={modelFilters.minCustomersK} onChange={(v)=>setModelFilters(f=>({...f,minCustomersK:v}))} min={0} max={20000} step={100} />
            <NumberInput label="Max customers (K)" value={modelFilters.maxCustomersK} onChange={(v)=>setModelFilters(f=>({...f,maxCustomersK:v}))} min={0} max={20000} step={100} />
          </div>
          <div className="grid grid-cols-2 gap-3">
            <NumberInput label="Min models (K)" value={modelFilters.minModelsK} onChange={(v)=>setModelFilters(f=>({...f,minModelsK:v}))} min={0} max={2000} step={10} />
            <NumberInput label="Max models (K)" value={modelFilters.maxModelsK} onChange={(v)=>setModelFilters(f=>({...f,maxModelsK:v}))} min={0} max={2000} step={10} />
          </div>
          <div><div className="text-xs text-slate-600 mb-2">Features</div><div className="flex flex-wrap gap-2">
            {Object.keys(modelFilters.feat).map(k=> <Pill key={k} label={k} active={modelFilters.feat[k]} onClick={()=>setModelFilters(f=>({...f, feat:{...f.feat, [k]:!f.feat[k]}}))} />)}
          </div></div>
          <div><div className="text-xs text-slate-600 mb-2">Payout timeframe</div><div className="flex flex-wrap gap-2">
            {PAYOUT_OPTIONS.map(opt=> <Pill key={opt} label={opt} active={modelFilters.payoutFilters.includes(opt)} onClick={()=>togglePayout(opt)} />)}
          </div></div>
          <div className="text-xs text-slate-600">Sort by</div>
          <select className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={modelFilters.sortBy} onChange={(e)=>setModelFilters(f=>({...f, sortBy:e.target.value}))}>
            <option value="score">Recommended</option>
            <option value="rate">Revenue share</option>
            <option value="customers">Estimated customers</option>
            <option value="models">Estimated models</option>
            <option value="name">Name (A–Z)</option>
          </select>
        </>) : (<>
          <div><div className="text-xs text-slate-600 mb-1">Search</div><input type="text" placeholder="Find a site..." className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-200" value={customerFilters.query} onChange={(e)=>setCustomerFilters(f=>({...f, query:e.target.value}))} /></div>
          <Range label="Max token price (USD)" value={customerFilters.tokenMax} min={0.05} max={0.5} step={0.01} onChange={(v)=>setCustomerFilters(f=>({...f, tokenMax:Number(v.toFixed(2))}))} />
          <Range label="Max private show $/min" value={customerFilters.privateMax} min={0.5} max={6} step={0.1} onChange={(v)=>setCustomerFilters(f=>({...f, privateMax:Number(v.toFixed(1))}))} />
          <Range label="Max group show $/min" value={customerFilters.groupMax} min={0.25} max={4} step={0.05} onChange={(v)=>setCustomerFilters(f=>({...f, groupMax:Number(v.toFixed(2))}))} />
          <Range label="Min value for money (0–5)" value={customerFilters.minValue} min={0} max={5} step={0.1} onChange={(v)=>setCustomerFilters(f=>({...f, minValue:Number(v.toFixed(1))}))} />
          <div><div className="text-xs text-slate-600 mb-2">Payments</div><div className="flex flex-wrap gap-2">{["Cards","Crypto"].map(k=> <Pill key={k} label={k} active={customerFilters.payments[k]} onClick={()=>setCustomerFilters(f=>({...f, payments:{...f.payments, [k]:!f.payments[k]}}))} />)}</div></div>
          <div><div className="text-xs text-slate-600 mb-2">Interactive</div><div className="flex flex-wrap gap-2">{["Cam2Cam","Lovense","VR"].map(k=> <Pill key={k} label={k} active={customerFilters.interactive[k]} onClick={()=>setCustomerFilters(f=>({...f, interactive:{...f.interactive, [k]:!f.interactive[k]}}))} />)}</div></div>
          <div><div className="text-xs text-slate-600 mb-2">Free content</div><div className="flex flex-wrap gap-2">{["Low","Medium","High"].map(k=> <Pill key={k} label={k} active={customerFilters.freeContent[k]} onClick={()=>setCustomerFilters(f=>({...f, freeContent:{...f.freeContent, [k]:!f.freeContent[k]}}))} />)}</div></div>
          <div className="text-xs text-slate-600">Sort by</div>
          <select className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={customerFilters.sortBy} onChange={(e)=>setCustomerFilters(f=>({...f, sortBy:e.target.value}))}>
            <option value="value">Best value</option>
            <option value="token">Lowest token price</option>
            <option value="private">Lowest private $/min</option>
            <option value="group">Lowest group $/min</option>
            <option value="active">Most active models</option>
            <option value="name">Name (A–Z)</option>
          </select>
        </>)}
      </div>
    </div>
  </>);
}

// ---------------- Tables ----------------
function ModelTable({ rows, onToggleSave, onToggleCompare, saved, compare }){
  return (<div className="rounded-2xl border border-slate-200 overflow-hidden bg-white shadow-sm">
    <div className="max-w-full overflow-x-auto">
      <table className="w-full text-[13px]">
        <thead className="sticky top-0 border-b border-slate-200 bg-gradient-to-r from-pink-500 via-pink-600 to-blue-600 text-white">
          <tr>
            <th className="text-left px-3 py-2 font-semibold w-[150px] max-w-[150px]">Platform</th>
            <th className="text-right px-3 py-2 font-semibold">Rev %</th>
            <th className="text-right px-3 py-2 font-semibold">Per $100</th>
            <th className="text-center px-3 py-2 font-semibold">Payout</th>
            <th className="text-right px-3 py-2 font-semibold">Cust (K)</th>
            <th className="text-right px-3 py-2 font-semibold">Models (K)</th>
            <th className="text-center px-3 py-2 font-semibold">Lovense</th>
            <th className="text-center px-3 py-2 font-semibold">VOD</th>
            <th className="text-center px-3 py-2 font-semibold">PPV</th>
            <th className="text-center px-3 py-2 font-semibold">VR</th>
            <th className="text-center px-3 py-2 font-semibold">App</th>
            <th className="text-center px-3 py-2 font-semibold">2FA</th>
            <th className="text-center px-3 py-2 font-semibold">Geo</th>
            <th className="text-center px-3 py-2 font-semibold">Actions</th>
            <th className="text-right px-3 py-2 font-semibold">Link</th>
          </tr>
        </thead>
        <tbody>
        {rows.map((p,idx)=> (<tr key={p.name} className={idx%2?"bg-slate-50/50":"bg-white"}>
          <td className="px-3 py-2 whitespace-nowrap align-top w-[150px] max-w-[150px]"><div className="flex items-start gap-2">
            <div className="w-7 h-7 rounded-md border border-slate-200 bg-white overflow-hidden grid place-items-center"><img src={p.logo} alt={`${p.name} logo`} className="w-full h-full object-contain" /></div>
            <div className="min-w-0"><div className="font-semibold text-slate-900 truncate">{p.name}</div><div className="text-[11px] text-slate-500 hidden xl:block truncate">{p.notes}</div></div>
          </div></td>
          <td className="px-3 py-2 text-right text-slate-900 font-medium">{p.ratePct}%</td>
          <td className="px-3 py-2 text-right text-slate-900 font-medium">${p.earningsPer100.toFixed(0)}</td>
          <td className="px-3 py-2 text-center">{p.payout}</td>
          <td className="px-3 py-2 text-right">{p.estCustomersK.toLocaleString()}K</td>
          <td className="px-3 py-2 text-right">{p.estModelsK.toLocaleString()}K</td>
          <td className="px-3 py-2 text-center">{p.features.lovense?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.vod?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.ppvClips?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.vr?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.mobileApp?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.twoFA?"✓":"—"}</td>
          <td className="px-3 py-2 text-center">{p.features.geoBlocking?"✓":"—"}</td>
          <td className="px-3 py-2 text-center whitespace-nowrap"><button onClick={()=>onToggleSave(p.name)} className={`px-2 py-1 rounded-full text-xs border mr-2 ${saved.includes(p.name)?"bg-pink-500 border-pink-600 text-white":"bg-pink-50 border-pink-200 text-pink-700 hover:bg-pink-100"}`}>Save</button><button onClick={()=>onToggleCompare(p.name)} className={`px-2 py-1 rounded-full text-xs border ${compare.includes(p.name)?"bg-blue-600 border-blue-700 text-white":"bg-blue-50 border-blue-200 text-blue-700 hover:bg-blue-100"}`}>Compare</button></td>
          <td className="px-3 py-2 text-right"><a href={p.site} target="_blank" rel="noreferrer" className="inline-block px-3 py-1 rounded-full border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100">Visit</a></td>
        </tr>))}
        </tbody>
      </table>
    </div>
  </div>);
}

function CustomerTable({ items }){
  const cols=[
    { key:"name", label:"Site", align:"left" },
    { key:"pricePerTokenUSD", label:"Token $", align:"right", fmt:v=>"$"+v.toFixed(2) },
    { key:"privatePerMinUSD", label:"Private/min", align:"right", fmt:v=>"$"+v.toFixed(2) },
    { key:"groupPerMinUSD", label:"Group/min", align:"right", fmt:v=>"$"+v.toFixed(2) },
    { key:"freeContent", label:"Free Content", align:"center" },
    { key:"videoQuality", label:"Quality", align:"center" },
    { key:"interactive", label:"Interactive", align:"center", fmt:v=>Array.isArray(v)? v.slice(0,3).join(" · "):v },
    { key:"payments", label:"Payments", align:"center", fmt:v=>Array.isArray(v)? v.join(", "):v },
    { key:"activeModelsK", label:"Active (K)", align:"right", fmt:v=>v+"K" },
    { key:"valueForMoney", label:"Value/5", align:"right", fmt:v=>v.toFixed(1) },
    { key:"link", label:"Visit", align:"right", action:true },
  ];
  const itemsSorted = items.slice();
  return (<div className="rounded-2xl border border-slate-200 overflow-hidden bg-white shadow-sm">
    <div className="max-w-full overflow-x-auto">
      <table className="w-full text-sm">
        <thead className="sticky top-0 border-b border-slate-200 bg-gradient-to-r from-blue-600 via-pink-600 to-pink-500 text-white">
          <tr>{cols.map(c=> <th key={c.key} className={`${c.align==="right"?"text-right":c.align==="center"?"text-center":"text-left"} px-4 py-3 font-semibold`}>{c.label}</th>)}</tr>
        </thead>
        <tbody>{itemsSorted.map((p,i)=> (<tr key={p.name} className={i%2?"bg-slate-50/50":"bg-white"}>{
          cols.map(c=>{
            let content=null; const cls=`${c.align==="right"?"text-right":c.align==="center"?"text-center":"text-left"} px-4 py-3`;
            if(c.key==="name") content=<div className="flex items-center gap-2"><img src={p.logo} className="w-6 h-6 rounded" alt="logo"/><span className="font-medium text-slate-900">{p.name}</span></div>;
            else if(c.action) content=<a href={p.site} target="_blank" rel="noreferrer" className="inline-block px-3 py-1 rounded-full border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100">Visit</a>;
            else { const v=p[c.key]; content=c.fmt? c.fmt(v) : (Array.isArray(v) ? v.join(", ") : v); }
            return <td key={c.key} className={cls}>{content}</td>;
          })
        }</tr>))}</tbody>
      </table>
    </div>
    <p className="text-[11px] text-slate-400 px-4 pb-4">All figures are accurate at the time of updating (07/09/2025).</p>
  </div>);
}

// ---------------- Calculators ----------------
function ModelCalculator(){
  const [platform, setPlatform] = useState(PLATFORMS[0].name);
  const [viewers, setViewers] = useState(200);
  const [tippersPct, setTippersPct] = useState(5);
  const [avgTip, setAvgTip] = useState(10);
  const [hoursPerDay, setHoursPerDay] = useState(4);
  const [daysPerWeek, setDaysPerWeek] = useState(5);
  const ratePct = PLATFORMS.find(p=>p.name===platform)?.ratePct ?? 60;
  const tipsPerHour = viewers*(tippersPct/100)*avgTip;
  const takeHomePerHour = tipsPerHour*(ratePct/100);
  const daily = takeHomePerHour*hoursPerDay;
  const weekly = daily*daysPerWeek;
  return (<Card>
    <h3 className="text-slate-900 font-semibold mb-3">Earnings Calculator (Models)</h3>
    <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
      <div className="flex flex-col gap-4">
        <div><div className="text-xs text-slate-600 mb-1">Platform</div><select className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={platform} onChange={(e)=>setPlatform(e.target.value)}>{PLATFORMS.map(p=> <option key={p.name} value={p.name}>{p.name} ({p.ratePct}% share)</option>)}</select></div>
        <Range label="Avg live viewers" value={viewers} min={0} max={2000} step={10} onChange={setViewers} />
        <Range label="Tippers % of viewers" value={tippersPct} min={0} max={20} step={1} onChange={setTippersPct} suffix="%" />
        <Range label="Average tip (USD)" value={avgTip} min={1} max={100} step={1} onChange={setAvgTip} />
        <Range label="Hours streamed per day" value={hoursPerDay} min={0} max={12} step={1} onChange={setHoursPerDay} />
        <Range label="Days per week" value={daysPerWeek} min={1} max={7} step={1} onChange={setDaysPerWeek} />
      </div>
      <div className="grid grid-cols-1 gap-3">
        <div className="rounded-xl border border-blue-200 bg-blue-50 p-4"><div className="text-xs text-blue-800">You keep per $100 tips</div><div className="text-2xl font-bold text-blue-900">${((ratePct/100)*100).toFixed(0)}</div></div>
        <div className="rounded-xl border border-pink-200 bg-pink-50 p-4"><div className="text-xs text-pink-800">Take-home per hour</div><div className="text-2xl font-bold text-pink-900">${takeHomePerHour.toFixed(2)}</div></div>
        <div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs text-slate-600">Estimated daily (net)</div><div className="text-xl font-semibold text-slate-900">${daily.toFixed(2)}</div></div>
        <div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs text-slate-600">Estimated weekly (net)</div><div className="text-xl font-semibold text-slate-900">${weekly.toFixed(2)}</div></div>
      </div>
    </div>
  </Card>);
}

function CustomerCalculator(){
  const [platform, setPlatform] = useState(PLATFORMS[0].name);
  const p = PLATFORMS.find(x=>x.name===platform) || {};
  const c = p.customer || {};
  const [budget, setBudget] = useState(50);
  const [tipSize, setTipSize] = useState(5);
  const tokenPrice = c.pricePerTokenUSD ?? 0.10;
  const privatePerMin = c.privatePerMinUSD ?? 2.99;
  const groupPerMin = c.groupPerMinUSD ?? 1.49;
  const tokens = Math.floor(budget / tokenPrice);
  const privateMins = privatePerMin? (budget/privatePerMin): 0;
  const groupMins = groupPerMin? (budget/groupPerMin): 0;
  const tipsCount = tipSize? Math.floor(budget/tipSize): 0;
  return (<Card>
    <h3 className="text-slate-900 font-semibold mb-3">Viewer Budget Planner (Customers)</h3>
    <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
      <div className="flex flex-col gap-4">
        <div><div className="text-xs text-slate-600 mb-1">Site</div><select className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={platform} onChange={(e)=>setPlatform(e.target.value)}>{PLATFORMS.map(pp=> <option key={pp.name} value={pp.name}>{pp.name}</option>)}</select></div>
        <div><div className="text-xs text-slate-600 mb-1">Budget (USD)</div><input type="number" className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" value={budget} min={0} step={1} onChange={(e)=>setBudget(Number(e.target.value)||0)} /></div>
        <div><div className="text-xs text-slate-600 mb-1">Average tip size (USD)</div><input type="number" className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" value={tipSize} min={1} step={1} onChange={(e)=>setTipSize(Number(e.target.value)||1)} /></div>
        <div className="text-xs text-slate-600 mt-2"><div>Token price: ${tokenPrice.toFixed(2)}</div><div>Private/min: ${privatePerMin.toFixed(2)}</div><div>Group/min: ${groupPerMin.toFixed(2)}</div></div>
      </div>
      <div className="grid grid-cols-1 gap-3">
        <div className="rounded-xl border border-blue-200 bg-blue-50 p-4"><div className="text-xs text-blue-800">Tokens you can buy</div><div className="text-2xl font-bold text-blue-900">{tokens.toLocaleString()}</div></div>
        <div className="rounded-xl border border-pink-200 bg-pink-50 p-4"><div className="text-xs text-pink-800">Approx. private minutes</div><div className="text-2xl font-bold text-pink-900">{privateMins.toFixed(1)} min</div></div>
        <div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs text-slate-600">Approx. group minutes</div><div className="text-xl font-semibold text-slate-900">{groupMins.toFixed(1)} min</div></div>
        <div className="rounded-xl border border-slate-200 bg-white p-4"><div className="text-xs text-slate-600">Number of tips (avg)</div><div className="text-xl font-semibold text-slate-900">{tipsCount.toLocaleString()}</div></div>
      </div>
    </div>
    <p className="text-[11px] text-slate-400 mt-3">Estimates assume constant prices; sites vary by room/model.</p>
  </Card>);
}

// ---------------- Views ----------------
function PlatformsView({ mode, saved, compare, onToggleSave, onToggleCompare }){
  const [modelFilters,setModelFilters] = useModelFilters();
  const [customerFilters,setCustomerFilters] = useCustomerFilters();
  const [open,setOpen] = useState(false);

  const rowsModel = filterModelRows(modelFilters);
  const rowsCustomer = filterCustomerRows(customerFilters);

  return (<div className="relative">
    <FilterDrawer open={open} setOpen={setOpen} mode={mode} modelFilters={modelFilters} setModelFilters={setModelFilters} customerFilters={customerFilters} setCustomerFilters={setCustomerFilters} />
    <div className="flex items-center justify-between mb-3">
      <h3 className="text-slate-800 font-semibold">{(mode==="model"? rowsModel.length : rowsCustomer.length)} platform{(mode==="model"? rowsModel : rowsCustomer).length===1?"":"s"} match</h3>
      {mode==="model" ? (
        <div className="flex items-center gap-2">
          <span className="text-xs text-slate-600">Sort by</span>
          <select className="rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={modelFilters.sortBy} onChange={(e)=>setModelFilters(f=>({...f,sortBy:e.target.value}))}>
            <option value="score">Recommended</option>
            <option value="rate">Revenue share</option>
            <option value="customers">Estimated customers</option>
            <option value="models">Estimated models</option>
            <option value="name">Name (A–Z)</option>
          </select>
        </div>
      ) : (
        <div className="flex items-center gap-2">
          <span className="text-xs text-slate-600">Sort by</span>
          <select className="rounded-lg border border-slate-200 px-3 py-2 text-sm bg-white" value={customerFilters.sortBy} onChange={(e)=>setCustomerFilters(f=>({...f,sortBy:e.target.value}))}>
            <option value="value">Best value</option>
            <option value="token">Lowest token price</option>
            <option value="private">Lowest private $/min</option>
            <option value="group">Lowest group $/min</option>
            <option value="active">Most active models</option>
            <option value="name">Name (A–Z)</option>
          </select>
        </div>
      )}
    </div>

    {mode==="model"? (
      <ModelTable rows={rowsModel} onToggleSave={onToggleSave} onToggleCompare={onToggleCompare} saved={saved} compare={compare} />
    ) : (
      <CustomerTable items={rowsCustomer} />
    )}
  </div>);
}

function PayoutsView(){
  return (<Card>
    <h3 className="text-slate-900 font-semibold mb-3">Payouts & Methods</h3>
    <div className="rounded-2xl border border-slate-200 overflow-hidden">
      <table className="w-full text-sm">
        <thead className="sticky top-0 border-b border-slate-200 bg-gradient-to-r from-pink-500 via-pink-600 to-blue-600 text-white"><tr>
          <th className="text-left px-4 py-3 font-semibold">Platform</th>
          <th className="text-center px-4 py-3 font-semibold">Timeframe</th>
          <th className="text-right px-4 py-3 font-semibold">Min Payout</th>
          <th className="text-left px-4 py-3 font-semibold">Methods</th>
          <th className="text-left px-4 py-3 font-semibold">Fees</th>
          <th className="text-right px-4 py-3 font-semibold">Link</th>
        </tr></thead>
        <tbody>{PLATFORMS.map((p,i)=> (<tr key={p.name} className={i%2?"bg-slate-50/50":"bg-white"}>
          <td className="px-4 py-3"><div className="flex items-center gap-2"><img src={p.logo} className="w-6 h-6 rounded" alt="logo" /><span className="font-medium text-slate-900">{p.name}</span></div></td>
          <td className="px-4 py-3 text-center">{p.payout}</td>
          <td className="px-4 py-3 text-right">{p.payoutMin||"—"}</td>
          <td className="px-4 py-3">{(p.payoutMethods||[]).join(", ")}</td>
          <td className="px-4 py-3">{p.payoutFees||"—"}</td>
          <td className="px-4 py-3 text-right"><a className="text-blue-700 hover:underline" href={p.site} target="_blank" rel="noreferrer">Visit</a></td>
        </tr>))}</tbody>
      </table>
    </div>
    <p className="text-[11px] text-slate-400 mt-3">All figures are accurate at the time of updating (07/09/2025).</p>
  </Card>);
}

// ---------------- Deals Page ----------------
function DealsView({ mode }){
  const colsModel = [
    { key:"headline", label:"Offer" },
    { key:"perks", label:"Incentives" },
    { key:"link", label:"Sign‑up" },
  ];

  const colsCustomer = [
    { key:"signupBonus", label:"Sign‑up bonus" },
    { key:"welcomeCredits", label:"Welcome credits" },
    { key:"creditBonusPct", label:"Credit bonus %" },
    { key:"bundles", label:"Popular bundles" },
    { key:"finePrint", label:"Fine print" },
    { key:"link", label:"Open" },
  ];

  const rows = PLATFORMS.map(p=> ({
    name:p.name, logo:p.logo,
    model: p.deals?.model || {},
    customer: p.deals?.customer || {}
  }));
  const cols = mode==="model"? colsModel : colsCustomer;

  return (<Card>
    <div className="flex items-center justify-between mb-3">
      <h3 className="text-slate-900 font-semibold">Latest deals & sign‑up incentives — <span className="text-pink-600">{mode==="model"?"For Models":"For Customers"}</span></h3>
      <div className="text-xs text-slate-500">Switch mode at the top (For Models / For Customers)</div>
    </div>
    <div className="rounded-2xl border border-slate-200 overflow-hidden">
      <table className="w-full text-sm">
        <thead className="sticky top-0 border-b border-slate-200 bg-gradient-to-r from-pink-500 via-pink-600 to-blue-600 text-white">
          <tr>
            <th className="text-left px-4 py-3 font-semibold">Platform</th>
            {cols.map(c=> <th key={c.key} className="text-left px-4 py-3 font-semibold">{c.label}</th>)}
          </tr>
        </thead>
        <tbody>
          {rows.map((r,i)=> {
            const data = mode==="model"? r.model : r.customer;
            return (
              <tr key={r.name} className={i%2?"bg-slate-50/50":"bg-white"}>
                <td className="px-4 py-3">
                  <div className="flex items-center gap-2">
                    <img src={r.logo} className="w-6 h-6 rounded" alt="logo" />
                    <span className="font-medium text-slate-900">{r.name}</span>
                  </div>
                </td>
                {cols.map(c=>{
                  let content=null;
                  const v = data[c.key];
                  if(c.key==="perks"){
                    content = (v||[]).length? <div className="flex flex-wrap gap-1">{v.map((p,idx)=><span key={idx} className="px-2 py-0.5 rounded-full bg-blue-50 border border-blue-200 text-blue-700 text-xs">{p}</span>)}</div> : "—";
                  } else if(c.key==="bundles"){
                    content = (v||[]).length? v.join(", ") : "—";
                  } else if(c.key==="creditBonusPct"){
                    content = (v||0) ? `${v}%` : "—";
                  } else if(c.key==="welcomeCredits"){
                    content = (v||0) ? `${v} credits` : "—";
                  } else if(c.key==="link"){
                    const link = data.link || "#";
                    content = <a href={link} target="_blank" rel="noreferrer" className="inline-block px-3 py-1 rounded-full border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100">{mode==="model"?"Sign‑up":"Open"}</a>;
                  } else {
                    content = v || "—";
                  }
                  return <td key={c.key} className="px-4 py-3">{content}</td>;
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
    <p className="text-[11px] text-slate-400 mt-3">Deals placeholders — update with your live promos. Last updated 07/09/2025.</p>
  </Card>);
}

// Saved & Compare
function SavedView({ saved = [], onToggleSave }) {
  const items = PLATFORMS.filter((p) => saved.includes(p.name));
  if (!items.length) return <Card><div className="text-slate-600 text-sm">No saved platforms yet. On the Platforms tab, tap <b>Save</b> to add some.</div></Card>;
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
      {items.map((p) => (
        <Card key={p.name}>
          <div className="flex items-center gap-3">
            <img src={p.logo} className="w-10 h-10 rounded-lg border" alt="logo" />
            <div>
              <div className="font-semibold text-slate-900">{p.name}</div>
              <div className="text-xs text-slate-500">{p.ratePct}% share · {p.payout}</div>
            </div>
          </div>
          <div className="mt-4 flex gap-2">
            <a className="px-3 py-1 rounded-full border border-blue-200 bg-blue-50 text-blue-700" href={p.site} target="_blank" rel="noreferrer">Visit</a>
            <button onClick={() => onToggleSave(p.name)} className="px-3 py-1 rounded-full border border-pink-200 bg-pink-50 text-pink-700">Remove</button>
          </div>
        </Card>
      ))}
    </div>
  );
}

function CompareView({ compare = [], setCompare }) {
  const items = PLATFORMS.filter((p) => compare.includes(p.name));
  if (!items.length) return <Card><div className="text-slate-600 text-sm">No platforms selected to compare. Go to <b>Platforms</b> and press <b>Compare</b> on up to 3.</div></Card>;
  const featCols = ["lovense", "vod", "ppvClips", "vr", "mobileApp", "twoFA", "geoBlocking"];
  const remove = (name)=> setCompare(prev=> prev.filter(n=>n!==name));
  const clear = ()=> setCompare([]);
  return (
    <div className="space-y-3">
      <div className="flex items-center justify-end">
        <button onClick={clear} className="px-3 py-1 rounded-full border border-slate-200 text-slate-700 hover:bg-slate-50 text-sm">Clear all</button>
      </div>
      <div className="rounded-2xl border border-slate-200 overflow-auto bg-white">
        <table className="w-full text-sm min-w-[720px]">
          <thead className="sticky top-0 border-b border-slate-200 bg-gradient-to-r from-pink-500 via-pink-600 to-blue-600 text-white">
            <tr>
              <th className="text-left px-4 py-3 font-semibold">Metric</th>
              {items.map((p) => (
                <th key={p.name} className="text-left px-4 py-3 font-semibold">
                  <div className="flex items-center gap-2"><img src={p.logo} className="w-6 h-6 rounded" alt="logo"/> {p.name}
                    <button onClick={()=>remove(p.name)} className="ml-2 px-2 py-0.5 rounded-full text-[11px] border border-pink-200 bg-pink-50 text-pink-700">Remove</button>
                  </div>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            <tr className="bg-white">
              <td className="px-4 py-3 text-slate-700">Revenue share</td>
              {items.map((p) => <td key={p.name+"rate"} className="px-4 py-3">{p.ratePct}%</td>)}
            </tr>
            <tr className="bg-slate-50/50">
              <td className="px-4 py-3 text-slate-700">Earn per $100</td>
              {items.map((p) => <td key={p.name+"earn"} className="px-4 py-3">${((p.ratePct/100)*100).toFixed(0)}</td>)}
            </tr>
            <tr className="bg-white">
              <td className="px-4 py-3 text-slate-700">Payout</td>
              {items.map((p) => <td key={p.name+"payout"} className="px-4 py-3">{p.payout}</td>)}
            </tr>
            <tr className="bg-slate-50/50">
              <td className="px-4 py-3 text-slate-700">Customers (K)</td>
              {items.map((p) => <td key={p.name+"cust"} className="px-4 py-3">{p.estCustomersK.toLocaleString()}K</td>)}
            </tr>
            <tr className="bg-white">
              <td className="px-4 py-3 text-slate-700">Models (K)</td>
              {items.map((p) => <td key={p.name+"models"} className="px-4 py-3">{p.estModelsK.toLocaleString()}K</td>)}
            </tr>
            <tr className="bg-slate-50/50">
              <td className="px-4 py-3 text-slate-700">Features</td>
              {items.map((p) => (
                <td key={p.name+"feat"} className="px-4 py-3">
                  <div className="flex flex-wrap gap-1 text-xs">
                    {featCols.map((k) => p.features[k] && <span key={k} className="px-2 py-0.5 rounded-full bg-blue-50 border border-blue-200 text-blue-700">{k}</span>)}
                  </div>
                </td>
              ))}
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

// Webcams & Toys
function WebcamsView() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
      {WEBCAMS.map((w) => (
        <Card key={w.name}>
          <img src={w.img} alt={`${w.name} image`} className="w-full h-40 object-cover rounded-xl border border-slate-200" />
          <h4 className="mt-3 text-slate-900 font-semibold">{w.name}</h4>
          <ul className="mt-2 text-sm text-slate-600 list-disc pl-5">
            <li>Great for streaming</li>
            <li>Autofocus, low-light friendly</li>
            <li>USB plug-and-play</li>
          </ul>
          <div className="mt-3 flex gap-2">
            <Tag>1080p+</Tag>
            <Tag>Autofocus</Tag>
            <Tag>Streamer-ready</Tag>
          </div>
          <div className="mt-4 flex gap-2">
            <a href={w.buyUrl} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center px-4 py-2 rounded-full text-sm font-semibold text-white bg-gradient-to-r from-pink-500 to-blue-600 shadow hover:opacity-90">Buy now</a>
            <a href={w.buyUrl} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center px-4 py-2 rounded-full text-sm font-semibold border border-slate-200 text-slate-700 hover:bg-slate-50">Learn more</a>
          </div>
        </Card>
      ))}
    </div>
  );
}

function ToysView() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
      {TOYS.map((t) => (
        <Card key={t.name}>
          <img src={t.img} alt={`${t.name} image`} className="w-full h-40 object-cover rounded-xl border border-slate-200" />
          <h4 className="mt-3 text-slate-900 font-semibold">{t.name}</h4>
          <ul className="mt-2 text-sm text-slate-600 list-disc pl-5">
            <li>App + interactive cam support</li>
            <li>Good battery life</li>
            <li>Model & audience control modes</li>
          </ul>
          <div className="mt-3 flex gap-2">
            <Tag>Interactive</Tag>
            <Tag>App</Tag>
            <Tag>USB-charge</Tag>
          </div>
          <div className="mt-4 flex gap-2">
            <a href={t.buyUrl} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center px-4 py-2 rounded-full text-sm font-semibold text-white bg-gradient-to-r from-pink-500 to-blue-600 shadow hover:opacity-90">Buy now</a>
            <a href={t.buyUrl} target="_blank" rel="noreferrer" className="inline-flex items-center justify-center px-4 py-2 rounded-full text-sm font-semibold border border-slate-200 text-slate-700 hover:bg-slate-50">Learn more</a>
          </div>
        </Card>
      ))}
    </div>
  );
}

// ---------------- App Shell ----------------
function AppShell(){
  const [active,setActive] = useState("platforms");
  const [mode,setMode] = useState("model");
  const [saved,setSaved] = useState([]);
  const [compare,setCompare] = useState([]);

  useEffect(()=>{ try{ setSaved(JSON.parse(localStorage.getItem("cc_saved")||"[]")); setCompare(JSON.parse(localStorage.getItem("cc_compare")||"[]")); }catch(e){} },[]);
  useEffect(()=>{ try{ localStorage.setItem("cc_saved", JSON.stringify(saved)); }catch(e){} },[saved]);
  useEffect(()=>{ try{ localStorage.setItem("cc_compare", JSON.stringify(compare)); }catch(e){} },[compare]);

  const toggle=(setList,name,max=99)=> setList(prev=> prev.includes(name)? prev.filter(n=>n!==name) : [...prev,name].slice(0,max));

  return (
    <ErrorBoundary>
    <div className="min-h-screen bg-gradient-to-b from-white via-pink-50/40 to-blue-50/40">
      <header className="sticky top-0 z-50 backdrop-blur supports-[backdrop-filter]:bg-white/70 bg-white/60 border-b border-slate-200">
        <div className="max-w-[1415px] mx-auto px-4 py-3 flex items-center justify-between">
          <div className="flex items-center gap-3">
            <div className="w-9 h-9 rounded-2xl grid place-items-center" style={{background:PINK}}><span className="text-white font-bold">CC</span></div>
            <div><div className="font-semibold text-slate-900 leading-tight">Compare Webcam Sites</div><div className="text-[11px] text-slate-500 -mt-0.5">Find the right platform & kit</div></div>
          </div>
          <div className="flex gap-2 items-center flex-wrap">
            <button onClick={()=>setActive("platforms")} className={`px-4 py-2 rounded-full text-sm border ${active==="platforms"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Platforms</button>
            <button onClick={()=>setActive("payouts")} className={`px-4 py-2 rounded-full text-sm border ${active==="payouts"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Payouts</button>
            <button onClick={()=>setActive("deals")} className={`px-4 py-2 rounded-full text-sm border ${active==="deals"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Deals</button>
            <button onClick={()=>setActive("calculator")} className={`px-4 py-2 rounded-full text-sm border ${active==="calculator"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Calculator</button>
            <button onClick={()=>setActive("compare")} className={`px-4 py-2 rounded-full text-sm border ${active==="compare"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Compare</button>
            <button onClick={()=>setActive("saved")} className={`px-4 py-2 rounded-full text-sm border ${active==="saved"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Saved</button>
            <button onClick={()=>setActive("webcams")} className={`px-4 py-2 rounded-full text-sm border ${active==="webcams"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Webcams</button>
            <button onClick={()=>setActive("toys")} className={`px-4 py-2 rounded-full text-sm border ${active==="toys"?"bg-pink-200 border-pink-400 text-pink-900":"bg-white border-slate-200 text-slate-700 hover:bg-slate-50"}`}>Toys</button>
            <div className="inline-flex rounded-full border border-slate-200 overflow-hidden bg-white ml-2">
              <button onClick={()=>setMode("model")} className={`px-3 py-1.5 text-sm ${mode==="model"?"bg-pink-100 text-pink-900":"text-slate-700 hover:bg-slate-50"}`}>For Models</button>
              <button onClick={()=>setMode("customer")} className={`px-3 py-1.5 text-sm border-l border-slate-200 ${mode==="customer"?"bg-blue-100 text-blue-900":"text-slate-700 hover:bg-slate-50"}`}>For Customers</button>
            </div>
          </div>
        </div>
      </header>

      <section className="max-w-[1415px] mx-auto px-4 pt-8 pb-4">
        <div className="rounded-3xl border border-slate-200 p-6 md:p-8 bg-white shadow-sm">
          <div className="grid grid-cols-1 md:grid-cols-3 gap-6 items-center">
            <div className="md:col-span-2">
              <h1 className="text-2xl md:text-3xl font-bold text-slate-900">Compare cam platforms by <span className="text-pink-600">earnings</span>, <span className="text-blue-600">audience</span> & <span className="text-pink-600">features</span></h1>
              <p className="mt-2 text-slate-600">Switch between <b>For Models</b> and <b>For Customers</b> to see the table, filters, calculator <i>and deals page</i> change. The left filter tab hides while open and the drawer content changes with the mode.</p>
              <div className="mt-3 flex flex-wrap gap-2"><Tag>VOD</Tag><Tag>Lovense</Tag><Tag>VR</Tag><Tag>PPV</Tag><Tag>App</Tag></div>
            </div>
            <div className="md:col-span-1"><div className="rounded-2xl border border-pink-200 bg-pink-50/60 p-4"><div className="text-sm text-slate-700"><div className="font-semibold">Quick tips</div><ul className="mt-2 list-disc pl-5 space-y-1"><li>Higher revenue share ≠ higher income — traffic matters.</li><li>Lovense support boosts interactivity and tips.</li><li>Test 2–4 platforms for 2–4 weeks.</li></ul></div></div></div>
          </div>
        </div>
      </section>

      <main className="max-w-[1415px] mx-auto px-4 py-6 space-y-6">
        {active==="platforms" && <PlatformsView mode={mode} saved={saved} compare={compare} onToggleSave={(n)=>setSaved(s=> s.includes(n)? s.filter(x=>x!==n): [...s,n])} onToggleCompare={(n)=>setCompare(s=> s.includes(n)? s.filter(x=>x!==n): [...s,n].slice(0,3))} />}
        {active==="payouts" && <PayoutsView />}
        {active==="deals" && <DealsView mode={mode} />}
        {active==="calculator" && (mode==="model"? <ModelCalculator /> : <CustomerCalculator />)}
        {active==="compare" && <CompareView compare={compare} setCompare={setCompare} />}
        {active==="saved" && <SavedView saved={saved} onToggleSave={(n)=>setSaved(s=> s.includes(n)? s.filter(x=>x!==n): [...s,n])} />}
        {active==="webcams" && <WebcamsView />}
        {active==="toys" && <ToysView />}
      </main>

      <footer className="max-w-[1415px] mx-auto px-4 pb-10 text-center text-[12px] text-slate-400">
        © 2025 Compare Webcam Sites. All Rights Reserved.
        <div>All figures are accurate at the time of updating (07/09/2025).</div>
      </footer>
    </div>
    </ErrorBoundary>
  );
}

// attach to window for the shortcode boot script
window.CamCompareApp = AppShell;
