/* global React, ReactDOM, useTweaks, TweaksPanel, TweakSection, TweakColor, TweakRadio, TweakToggle */
const { useState, useEffect, useRef, useMemo } = React;

// ─── Data ─────────────────────────────────────────────────────────────────
const PROFILE = {
  name: "Ritesh Singh",
  role: "AI / ML Engineer",
  location: "San Jose, CA",
  email: "riteshsingh30200@gmail.com",
  linkedin: "linkedin.com/in/ritesh-rakesh-singh",
  linkedinUrl: "https://linkedin.com/in/ritesh-rakesh-singh",
  github: "github.com/Ritesh-sudo",
  githubUrl: "https://github.com/Ritesh-sudo",
  tagline: "AI agents, data pipelines, and the simulation worlds that test them \u2014 built with care.",
  about: [
    "I'm a Master's student at San José State studying Artificial Intelligence & Data Science, currently shipping autonomy work at a UAV startup. My focus sits at the seam between research and production — agent training, evaluation harnesses, and the data plumbing that makes both reliable.",
    "Before grad school I spent a summer architecting AWS-backed cloud infrastructure for building-information workflows, and an undergrad at CMR Engineering College in Hyderabad. I like problems where a clean abstraction unlocks a measurable lift, and I like writing software that other people can extend.",
  ],
  now: [
    { when: "Now", title: "AI Agent Developer Intern", detail: "Boundary RSS — converting UAV protocol requirements into agent tasks, curating ~10k training samples, tuning end-to-end test reliability." },
    { when: "Studying", title: "Deep Learning, Recommender Systems, Threat Intel", detail: "SJSU MS-AIDS, graduating May 2026." },
    { when: "Tinkering", title: "Agent eval harnesses + sim environments", detail: "Mostly in CARLA, AirSim, and small fine-tuning runs over Mistral-class models." },
  ],
};

const EXPERIENCE = [
  {
    range: "Dec 2025 — Present",
    role: "AI Agent Developer Intern",
    company: "Boundary RSS · San Jose, CA",
    bullets: [
      "Reduced UAV autonomy integration defects ~25% by translating routing, data-input and operating protocol requirements into testable acceptance criteria with engineering leads.",
      "Cut onboard backend & AI bring-up time 20% by integrating data pipelines across hardware modules and ground-station software interfaces.",
      "Curated ~10,000 samples mapped to mission protocols across train / val / test, improving training stability and evaluation repeatability.",
      "Raised integrated system-test pass rate ~15% by triaging end-to-end failures, tuning performance, and iterating the agent against new test results.",
    ],
  },
  {
    range: "Mar 2023 — Jul 2023",
    role: "Cloud Intern",
    company: "AICTE — EduSkill · Hyderabad, India",
    bullets: [
      "Designed and deployed AWS infrastructure for BIM workflows, dropping processing latency 35%.",
      "Integrated ML models into pipelines running on EC2 & S3 with CloudWatch observability.",
      "Led a small team to simulate and scale 3D building modeling — 100% on-time delivery.",
    ],
  },
];

const PROJECTS = [
  {
    name: "Patrol.sim",
    full: "CARLA-Based Smart Security Patrol Simulation",
    period: "Jan — May 2025",
    role: "Sim Engineer · Full-stack",
    repo: "github.com/Ritesh-sudo/CARLA-Based-Smart-Security-Patrol-Simulation",
    repoUrl: "https://github.com/Ritesh-sudo/CARLA-Based-Smart-Security-Patrol-Simulation",
    viz: "lidar",
    tag: "lidar_stream / live",
    stack: ["CARLA", "Python", "OpenCV", "JavaScript", "WebSockets", "HTML/CSS"],
    summary: "A multi-agent patrol system running inside CARLA, fronted by a responsive web dashboard. Operators see live LIDAR, GPS and RGB streams plus anomaly overlays, with WebSocket-driven telemetry feeding into a custom situational-awareness UI.",
    detail: "Designed the dashboard around real operator workflows — anomaly cards collapse into a timeline, OpenCV overlays composite onto live frames, and a small widget framework lets the team add new visualizations without touching the transport layer.",
    metrics: [
      { big: "−15%", lbl: "simulated breaches" },
      { big: "−60ms", lbl: "telemetry latency" },
      { big: "Multi", lbl: "agent coordination" },
    ],
  },
  {
    name: "Triage.md",
    full: "LLM Medical AI Bot",
    period: "Jan — Mar 2025",
    role: "ML + Backend",
    repo: "github.com/Ritesh-sudo/LLM-MedicalAI-Bot",
    repoUrl: "https://github.com/Ritesh-sudo/LLM-MedicalAI-Bot",
    viz: "attention",
    tag: "attention / 8 heads",
    stack: ["Mistral 7B", "PEFT", "LoRA", "Flask", "Python", "HTML/CSS"],
    summary: "A medical chatbot built on a PEFT/LoRA fine-tune of Mistral 7B for symptom triage and information retrieval — wrapped in a Flask API that handles automated appointment booking end-to-end.",
    detail: "The booking flow was the hidden win — it folded the slot-search, calendar, and confirmation steps into the same conversational turn, which is where most patient frustration actually lives.",
    metrics: [
      { big: "−40%", lbl: "scheduling bottleneck" },
      { big: "7B", lbl: "param fine-tune" },
      { big: "LoRA", lbl: "adapter approach" },
    ],
  },
  {
    name: "CreditLens",
    full: "Credit Card Score Prediction",
    period: "Jul — Nov 2024",
    role: "ML + Web",
    repo: "github.com/Ritesh-sudo/CreditCardScorePred",
    repoUrl: "https://github.com/Ritesh-sudo/CreditCardScorePred",
    viz: "scatter",
    tag: "random_forest / 0.87",
    stack: ["Random Forest", "Python", "Scikit-learn", "JavaScript", "HTML/CSS"],
    summary: "A credit-risk classifier paired with a responsive web app for institutional users. The Random Forest sits behind a clean visualization surface that explains a score rather than just emitting one.",
    detail: "Focused on legibility for non-ML reviewers — feature importance, score deltas, and a per-applicant 'why' panel that compares against the cohort.",
    metrics: [
      { big: "RF", lbl: "ensemble classifier" },
      { big: "Web", lbl: "responsive UI" },
      { big: "Risk", lbl: "domain" },
    ],
  },
  {
    name: "CryptoPulse",
    full: "Real-time Crypto Price Anomaly Monitor",
    period: "Aug 2025 — Now",
    role: "Full-stack · ML",
    repo: "github.com/Ritesh-sudo/ritesh-crypto-pipeline",
    repoUrl: "https://github.com/Ritesh-sudo/ritesh-crypto-pipeline",
    viz: "volatility",
    tag: "isolation_forest / live ticks",
    stack: ["Python", "FastAPI", "WebSocket", "AsyncIO", "Scikit-learn", "React", "D3", "Docker", "AWS"],
    summary: "A real-time crypto price tracker that ingests multi-exchange tick data over WebSocket + REST, runs anomaly detection on a rolling window, and pushes alerts to a live React dashboard and notification channels.",
    detail: "Anomaly engine layers statistical thresholding (Z-Score, IQR) on top of Isolation Forest and One-Class SVM to catch both pointwise spikes and regime shifts. Async ingestion keeps the pipeline responsive at high tick volumes; alerts fan out via webhooks, SMTP and push.",
    metrics: [
      { big: "Multi", lbl: "exchange feeds" },
      { big: "<1s", lbl: "detect-to-alert" },
      { big: "3+", lbl: "anomaly methods" },
    ],
  },
];

const SKILL_GROUPS = [
  { cat: "Languages", items: ["C++", "Python", "SQL", "JavaScript", "HTML", "CSS"] },
  { cat: "ML / DS",   items: ["Ensemble Methods", "Clustering", "Predictive Analytics", "Transformers"] },
  { cat: "Cloud",     items: ["AWS", "EC2", "S3", "Lambda", "CloudWatch", "Docker", "Kubernetes"] },
  { cat: "Frameworks",items: ["PyTorch", "Scikit-learn", "LangChain", "Apache Spark", "MLflow", "FastAPI", "Pandas", "NumPy", "XGBoost"] },
  { cat: "Viz",       items: ["Tableau", "PowerBI"] },
  { cat: "Other",     items: ["MATLAB", "Tesseract OCR", "AirSim", "Unreal Engine"] },
];

const EDUCATION = [
  {
    range: "Aug 2024 — May 2026",
    deg: "M.S. Artificial Intelligence & Data Science",
    school: "San José State University · San Jose, CA",
    courses: ["Data Engineering", "Data Mining", "Machine Learning", "Cloud Technology", "Recommender Systems", "Deep Learning", "Threat Intelligence"],
  },
  {
    range: "Aug 2020 — May 2024",
    deg: "B.Tech, Computer Science",
    school: "CMR Engineering College · Hyderabad, India",
    courses: ["Computer Organization & Architecture", "Artificial Intelligence", "Computer Networks", "Embedded Systems", "Data Structures", "DBMS", "Design & Analysis of Algorithms", "Cyber Security"],
  },
];

const PALETTES = {
  paper:     { bg: "#12110e", fg: "#e8e1d0", muted: "rgba(232,225,208,.50)", line: "rgba(232,225,208,.09)", card: "rgba(232,225,208,.025)", surface:"#191814" },
  ink:       { bg: "#0a0a0a", fg: "#d4d2cc", muted: "rgba(212,210,204,.50)", line: "rgba(212,210,204,.09)", card: "rgba(212,210,204,.025)", surface:"#141413" },
  clay:      { bg: "#1b1714", fg: "#e6dccb", muted: "rgba(230,220,203,.50)", line: "rgba(230,220,203,.09)", card: "rgba(230,220,203,.025)", surface:"#241f1b" },
  linen:     { bg: "#e8e3d4", fg: "#1a1714", muted: "rgba(26,23,20,.55)",    line: "rgba(26,23,20,.10)",    card: "rgba(26,23,20,.03)",    surface:"#f1ecdd" },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "paper",
  "accent": "brass",
  "serif": false
}/*EDITMODE-END*/;

const ACCENTS = {
  brass:    "oklch(0.72 0.09 65)",
  rust:     "oklch(0.58 0.13 35)",
  sage:     "oklch(0.62 0.06 145)",
  slate:    "oklch(0.62 0.04 235)",
  bone:     "oklch(0.86 0.03 80)",
};

// ─── AI flavor ────────────────────────────────────────────────────────────
function NeuralNet() {
  const layers = [4,7,9,9,7,4], W=1400, H=700, m=80;
  const cols = layers.map((_,i)=> m + i*(W-2*m)/(layers.length-1));
  const nodes = [];
  layers.forEach((n,ci)=>{ const s=(H-2*m)/(n-1||1); for(let i=0;i<n;i++) nodes.push({x:cols[ci],y:m+i*s,col:ci,row:i,id:nodes.length}); });
  const lines = [];
  for(let c=0;c<layers.length-1;c++){ const L=nodes.filter(n=>n.col===c), R=nodes.filter(n=>n.col===c+1);
    L.forEach(a=>R.forEach(b=>lines.push({a,b,w:Math.random(),neg:Math.random()<0.18}))); }
  const hotEdges = lines.map((e,i)=>({...e,i})).filter(e=>e.w>0.78).slice(0,32);
  const fireNodes = nodes.filter((_,i)=>i%5===0).slice(0,12);
  // moving "tokens" along a few highlighted paths
  const tokenEdges = lines.filter(e=>e.w>0.92).slice(0,10);
  return (
    <svg className="neuralnet" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid slice">
      <defs>
        <radialGradient id="nn-glow" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.55"/>
          <stop offset="60%" stopColor="var(--accent)" stopOpacity="0.08"/>
          <stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
        </radialGradient>
        <radialGradient id="nn-node" cx="50%" cy="50%" r="50%">
          <stop offset="0%"  stopColor="var(--accent)" stopOpacity="1"/>
          <stop offset="60%" stopColor="var(--accent)" stopOpacity=".7"/>
          <stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
        </radialGradient>
        <filter id="nn-blur" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="2.2"/>
        </filter>
        <filter id="nn-blur-soft" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="6"/>
        </filter>
      </defs>
      {/* deep ambient halos */}
      <ellipse cx={W*0.35} cy={H*0.45} rx={W*0.35} ry={H*0.5} fill="url(#nn-glow)" opacity=".35"/>
      <ellipse cx={W*0.7} cy={H*0.55} rx={W*0.3} ry={H*0.45} fill="url(#nn-glow)" opacity=".25"/>
      {/* faint grid */}
      {Array.from({length:14},(_,i)=>(
        <line key={'gv'+i} x1={i*W/14} y1="0" x2={i*W/14} y2={H} stroke="var(--accent)" strokeOpacity=".025" strokeWidth=".5"/>
      ))}
      {/* base edges — weight-driven width + opacity, hint of negative weights */}
      {lines.map((e,i)=>(
        <line key={i} x1={e.a.x} y1={e.a.y} x2={e.b.x} y2={e.b.y}
          strokeWidth={0.25 + e.w*1.0}
          strokeOpacity={0.05 + e.w*0.22}
          strokeDasharray={e.neg ? "3 4" : undefined}
          stroke="var(--accent)"/>
      ))}
      {/* signal pulses — sampled across all layers for performance */}
      {lines.filter((_,k)=> k % 5 === 0).map((e,k)=>{
        const len = Math.hypot(e.b.x-e.a.x, e.b.y-e.a.y);
        const dashLen = 7;
        const dur = (10.0 + (k % 17) * 0.9).toFixed(2);
        const begin = -((k * 1.7) % parseFloat(dur));
        return (
          <line key={'pulse'+k}
            x1={e.a.x} y1={e.a.y} x2={e.b.x} y2={e.b.y}
            stroke="var(--accent)"
            strokeOpacity={0.5 + e.w * 0.45}
            strokeWidth={0.8 + e.w * 0.7}
            strokeDasharray={`${dashLen} ${len * 3}`}
            strokeLinecap="round">
            <animate
              attributeName="stroke-dashoffset"
              from="0"
              to={`${-(len + dashLen)}`}
              dur={`${dur}s`}
              begin={`${begin.toFixed(2)}s`}
              repeatCount="indefinite"/>
          </line>
        );
      })}
      {/* moving tokens (small bright dots traveling along paths) */}
      {tokenEdges.map((e,k)=>{
        const dur = 2.2 + (k%4)*0.5;
        return (
          <g key={'t'+k}>
            <circle r="2.4" fill="var(--accent)" filter="url(#nn-blur)">
              <animate attributeName="cx" from={e.a.x} to={e.b.x} dur={`${dur}s`} begin={`${(k*0.31)%dur}s`} repeatCount="indefinite"/>
              <animate attributeName="cy" from={e.a.y} to={e.b.y} dur={`${dur}s`} begin={`${(k*0.31)%dur}s`} repeatCount="indefinite"/>
            </circle>
            <circle r="1.4" fill="var(--bg)" stroke="var(--accent)" strokeWidth=".8">
              <animate attributeName="cx" from={e.a.x} to={e.b.x} dur={`${dur}s`} begin={`${(k*0.31)%dur}s`} repeatCount="indefinite"/>
              <animate attributeName="cy" from={e.a.y} to={e.b.y} dur={`${dur}s`} begin={`${(k*0.31)%dur}s`} repeatCount="indefinite"/>
            </circle>
          </g>
        );
      })}
      {/* layer labels */}
      {layers.map((n,ci)=>(
        <g key={'L'+ci}>
          <text x={cols[ci]} y={H-32} textAnchor="middle"
            fill="var(--accent)" fillOpacity=".35" fontSize="11" fontFamily="monospace"
            letterSpacing="2">{`L${ci} · ${n}`}</text>
          <line x1={cols[ci]-14} x2={cols[ci]+14} y1={H-22} y2={H-22}
            stroke="var(--accent)" strokeOpacity=".2" strokeWidth=".5"/>
        </g>
      ))}
      {/* nodes — varied sizes, glow, firing rings */}
      {nodes.map((n,i)=>{
        const isHot = fireNodes.includes(n);
        const isEdge = n.col===0 || n.col===layers.length-1;
        const r = isEdge ? 5 : (isHot ? 4 : 2.4);
        return (
          <g key={n.id}>
            {isHot && (<>
              <circle cx={n.x} cy={n.y} r={r*5} fill="url(#nn-node)" opacity=".22"
                style={{animation:`nn-breathe 3.4s ease-in-out ${(i*0.21)%3.4}s infinite`}}/>
              {/* firing ripple */}
              <circle cx={n.x} cy={n.y} r="4" fill="none" stroke="var(--accent)" strokeOpacity=".6" strokeWidth="1"
                style={{animation:`nn-ripple ${4+i%3}s ease-out ${(i*0.7)%4}s infinite`, transformOrigin:`${n.x}px ${n.y}px`}}/>
            </>)}
            <circle cx={n.x} cy={n.y} r={r}
              fill={isHot || isEdge ? "var(--accent)" : "var(--fg)"}
              fillOpacity={isHot ? 0.98 : isEdge ? 0.9 : 0.32}/>
            {(isHot || isEdge) && (<circle cx={n.x} cy={n.y} r={r+2}
              fill="none" stroke="var(--accent)" strokeOpacity=".55" strokeWidth=".7"/>)}
            {isEdge && (<circle cx={n.x} cy={n.y} r={r+5} fill="none" stroke="var(--accent)" strokeOpacity=".22" strokeWidth=".5"/>)}
          </g>
        );
      })}
      {/* corner hud tags */}
      <g fontFamily="monospace" fontSize="10" fill="var(--accent)" fillOpacity=".4">
        <text x={m} y={m-28}>model · transformer-encoder</text>
        <text x={W-m} y={m-28} textAnchor="end">params · 14.2M</text>
        <text x={m} y={H-m+58}>loss · 0.041 ↓</text>
        <text x={W-m} y={H-m+58} textAnchor="end">step · 28,461</text>
      </g>
    </svg>
  );
}
function TypingPrompt() {
  const lines = ["training UAV agent… ok","curating 10k mission samples… ok","eval pass rate +15%… ok","ship checkpoint → ground-station… ok"];
  const [i,setI]=useState(0),[t,setT]=useState("");
  useEffect(()=>{ let c=false,j=0; setT(""); const tick=()=>{ if(c) return; const L=lines[i];
    if(j<=L.length){ setT(L.slice(0,j)); j++; setTimeout(tick,32); } else setTimeout(()=>!c&&setI((i+1)%lines.length),1500); }; tick(); return ()=>{c=true}; },[i]);
  return <div className="term reveal"><span className="p">$</span><span className="s">{t}<span className="cur"/></span></div>;
}
function ProjectViz({ kind }) {
  if (kind==='lidar') {
    return (<svg viewBox="0 0 400 190" preserveAspectRatio="xMidYMid meet">
      {[40,75,108].map(r=><circle key={r} cx="200" cy="95" r={r} fill="none" stroke="var(--accent)" strokeOpacity=".25" strokeWidth=".6" strokeDasharray="3 5"/>)}
      {Array.from({length:72},(_,i)=>{const a=i/72*Math.PI*2,r=38+Math.abs(Math.sin(i*0.7)*68)+(i%5)*5;
        return <circle key={i} cx={200+Math.cos(a)*r} cy={95+Math.sin(a)*r} r="1.6" fill="var(--accent)" fillOpacity=".85"/>;})}
      <circle cx="200" cy="95" r="3.5" fill="var(--fg)"/>
      <line x1="200" y1="95" x2="305" y2="25" stroke="var(--accent)" strokeWidth="1.2" strokeOpacity=".85"/>
    </svg>);
  }
  if (kind==='volatility') {
    const pts=Array.from({length:120},(_,i)=>{
      const base=95+Math.sin(i*0.18)*28+Math.cos(i*0.07)*14;
      const spike=(i===45||i===88)?(i===45?-48:55):0;
      return {x:30+i*(340/120), y:base+spike+(Math.random()-0.5)*5, anom:Math.abs(spike)>0};
    });
    const d=pts.map((p,i)=>(i?'L':'M')+p.x.toFixed(1)+' '+p.y.toFixed(1)).join(' ');
    return (<svg viewBox="0 0 400 190" preserveAspectRatio="xMidYMid meet">
      <line x1="30" y1="170" x2="370" y2="170" stroke="var(--line)"/>
      <path d={d} fill="none" stroke="var(--accent)" strokeOpacity=".85" strokeWidth="1.3"/>
      <path d={d+' L 370 170 L 30 170 Z'} fill="var(--accent)" fillOpacity=".07"/>
      {pts.filter(p=>p.anom).map((p,i)=>(<g key={i}>
        <circle cx={p.x} cy={p.y} r="10" fill="none" stroke="var(--accent)" strokeOpacity=".5"/>
        <circle cx={p.x} cy={p.y} r="3" fill="var(--accent)"/>
      </g>))}
      <text x="40" y="35" fill="var(--muted)" fontSize="10" fontFamily="monospace">btc_usd · 1m · anomaly +6.4%</text>
    </svg>);
  }
  if (kind==='attention') {
    const cells=[]; for(let y=0;y<10;y++) for(let x=0;x<10;x++) cells.push({x,y,v:Math.exp(-((x-y)**2)/8)+Math.random()*0.15});
    return (<svg viewBox="0 0 400 190" preserveAspectRatio="xMidYMid meet">
      <g transform="translate(140 15)">{cells.map((c,i)=><rect key={i} x={c.x*16} y={c.y*16} width="15" height="15"
        fill="var(--accent)" fillOpacity={Math.min(c.v,0.95)*0.9} rx="2"/>)}</g>
      <text x="22" y="100" fill="var(--muted)" fontSize="10" fontFamily="monospace">attention</text>
    </svg>);
  }
  const pts=Array.from({length:80},()=>({x:40+Math.random()*320,y:25+Math.random()*150,cls:Math.random()>0.5}));
  return (<svg viewBox="0 0 400 190" preserveAspectRatio="xMidYMid meet">
    <line x1="40" y1="170" x2="360" y2="170" stroke="var(--line)"/>
    <line x1="40" y1="25" x2="40" y2="170" stroke="var(--line)"/>
    <path d="M 40 155 Q 200 75, 360 28" fill="none" stroke="var(--accent)" strokeWidth="1.2" strokeDasharray="4 4"/>
    {pts.map((p,i)=><circle key={i} cx={p.x} cy={p.y} r="2.4" fill={p.cls?"var(--accent)":"var(--fg)"} fillOpacity={p.cls?0.85:0.35}/>)}
    <text x="50" y="40" fill="var(--muted)" fontSize="10" fontFamily="monospace">decision_boundary</text>
  </svg>);
}

// ─── Custom cursor ────────────────────────────────────────────────────────
function Cursor() {
  const dotRef = useRef(null);
  useEffect(() => {
    let raf;
    let x = window.innerWidth/2, y = window.innerHeight/2;
    let tx = x, ty = y;
    const move = (e) => { tx = e.clientX; ty = e.clientY; };
    const enter = (e) => {
      if (e.target.closest && e.target.closest('a, button, .proj, .skill-filters button')) {
        dotRef.current && dotRef.current.classList.add('lg');
      }
    };
    const leave = (e) => {
      if (e.target.closest && e.target.closest('a, button, .proj, .skill-filters button')) {
        dotRef.current && dotRef.current.classList.remove('lg');
      }
    };
    const out = () => dotRef.current && dotRef.current.classList.add('hide');
    const on = () => dotRef.current && dotRef.current.classList.remove('hide');

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseover', enter);
    document.addEventListener('mouseout', leave);
    document.addEventListener('mouseleave', out);
    document.addEventListener('mouseenter', on);

    const loop = () => {
      x += (tx - x) * 0.22;
      y += (ty - y) * 0.22;
      if (dotRef.current) dotRef.current.style.transform = `translate(${x}px,${y}px) translate(-50%,-50%)`;
      raf = requestAnimationFrame(loop);
    };
    loop();
    return () => {
      cancelAnimationFrame(raf);
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseover', enter);
      document.removeEventListener('mouseout', leave);
      document.removeEventListener('mouseleave', out);
      document.removeEventListener('mouseenter', on);
    };
  }, []);
  return <div className="rs-cursor" ref={dotRef} />;
}

// ─── Scroll reveal ────────────────────────────────────────────────────────
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll('.reveal');
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) e.target.classList.add('in'); });
    }, { threshold: 0.12 });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, []);
}

// ─── Active section spy ───────────────────────────────────────────────────
function useActiveSection(ids) {
  const [active, setActive] = useState(ids[0]);
  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY + 120;
      let cur = ids[0];
      for (const id of ids) {
        const el = document.getElementById(id);
        if (el && el.offsetTop <= y) cur = id;
      }
      setActive(cur);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return active;
}

// ─── Live clock (PT) ──────────────────────────────────────────────────────
function Clock() {
  const [t, setT] = useState(() => new Date());
  useEffect(() => {
    const i = setInterval(() => setT(new Date()), 1000);
    return () => clearInterval(i);
  }, []);
  const fmt = t.toLocaleTimeString('en-US', { timeZone: 'America/Los_Angeles', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
  return (
    <span style={{fontFamily:'var(--mono)', fontSize:12, color:'var(--muted)', letterSpacing:'.02em'}}>
      {fmt} <span style={{opacity:.6}}>PT</span>
    </span>
  );
}

// ─── Nav ──────────────────────────────────────────────────────────────────
const SECTIONS = [
  { id: "about", label: "Index", num: "01" },
  { id: "experience", label: "Work", num: "02" },
  { id: "projects", label: "Projects", num: "03" },
  { id: "skills", label: "Stack", num: "04" },
  { id: "education", label: "Schools", num: "05" },
  { id: "contact", label: "Contact", num: "06" },
];

function OpenToWorkBar() {
  return (
    <div className="otw">
      <span className="pulse" />
      <span>Open to full-time AI / ML roles</span>
      <span className="sep">·</span>
      <span className="otw-grad">Graduating May 2026</span>
      <span className="sep">·</span>
      <a href={`mailto:${PROFILE.email}?subject=Full-time%20opportunity`}>Let's talk →</a>
    </div>
  );
}

function Nav() {
  const active = useActiveSection(SECTIONS.map(s => s.id));
  const dockRef = React.useRef(null);
  const [pill, setPill] = useState({ x: 0, w: 0, on: false });
  const [progress, setProgress] = useState(0);
  const [mobileOpen, setMobileOpen] = useState(false);

  // scroll progress + body class
  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement.scrollHeight - window.innerHeight;
      setProgress(h > 0 ? window.scrollY / h : 0);
      document.body.classList.toggle('scrolled', window.scrollY > 40);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  // lock body scroll when mobile menu is open
  useEffect(() => {
    document.body.style.overflow = mobileOpen ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [mobileOpen]);

  // position the sliding pill behind active link
  useEffect(() => {
    if (!dockRef.current) return;
    const el = dockRef.current.querySelector(`a[data-id="${active}"]`);
    if (!el) return;
    const dockRect = dockRef.current.getBoundingClientRect();
    const r = el.getBoundingClientRect();
    setPill({ x: r.left - dockRect.left - 5, w: r.width, on: true });
  }, [active]);

  const jump = (e, id) => {
    e.preventDefault();
    const el = document.getElementById(id);
    if (el) window.scrollTo({ top: el.offsetTop - 40, behavior: 'smooth' });
  };

  const jumpMobile = (e, id) => {
    jump(e, id);
    setMobileOpen(false);
  };

  // SVG ring stroke math
  const R = 16, C = 2 * Math.PI * R;
  const dash = C * progress;

  return (
    <>
      <div className="navwrap">
        <div className={`nav-dock ${pill.on ? 'active' : ''}`} ref={dockRef}>
          <div className="pill" style={{ transform: `translateX(${pill.x}px)`, width: pill.w }} />
          {SECTIONS.map(s => (
            <a key={s.id} data-id={s.id} href={`#${s.id}`}
               className={active === s.id ? 'active' : ''}
               onClick={(e)=>jump(e,s.id)}>
              <span className="num">{s.num}</span>
              <span>{s.label}</span>
            </a>
          ))}
        </div>

        <button className="nav-hamburger" aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
                onClick={() => setMobileOpen(o => !o)}>
          {mobileOpen ? '✕' : '☰'}
        </button>

        <div className="nav-right">
          <span className="status" title="Open to work" />
          <span style={{fontFamily:'var(--sans)', fontWeight:500, fontSize:13, letterSpacing:'-0.012em', color:'var(--fg)'}}>Available</span>
          <span style={{width:1, height:18, background:'var(--line)'}}/>
          <span className="clock"><Clock /></span>
        </div>
      </div>

      {mobileOpen && (
        <div className="nav-mobile-overlay" onClick={() => setMobileOpen(false)}>
          {SECTIONS.map(s => (
            <a key={s.id} href={`#${s.id}`}
               className={active === s.id ? 'active' : ''}
               onClick={(e) => jumpMobile(e, s.id)}>
              <span className="num">{s.num}</span>
              <span>{s.label}</span>
            </a>
          ))}
        </div>
      )}
    </>
  );
}

// ─── Hero ─────────────────────────────────────────────────────────────────
function Hero() {
  return (
    <section className="hero" id="top">
      <NeuralNet />
      <div className="container">
        <div className="hero-grid">
          <div>
            <div className="eyebrow reveal"><span className="bar"/>Portfolio · 2026 · v1</div>
            <h1 className="name reveal">
              Ritesh <span className="it serif">Singh</span>
            </h1>
            <div className="tagline reveal serif">
              {PROFILE.tagline}
            </div>
            <TypingPrompt />
            <div className="ctas reveal">
              <a className="cta" href="#projects" onClick={(e)=>{e.preventDefault(); document.getElementById('projects').scrollIntoView({behavior:'smooth', block:'start'});}}>
                See the work <span className="arr">→</span>
              </a>
              <a className="cta" href={`mailto:${PROFILE.email}`}>
                Get in touch <span className="arr">↗</span>
              </a>
            </div>
          </div>
          <div className="hero-side reveal">
            <div className="meta-row">
              <div className="k">Currently</div>
              <div className="v"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><span style={{width:8, height:8, borderRadius:'50%', background:'var(--accent)', boxShadow:'0 0 10px var(--accent)', display:'inline-block'}}></span>Open to work</span></div>
            </div>
            <div className="meta-row">
              <div className="k">Based</div>
              <div className="v">San Jose, California</div>
            </div>
            <div className="meta-row">
              <div className="k">Studying</div>
              <div className="v">MS · AI & Data Science · SJSU '26</div>
            </div>
            <div className="meta-row">
              <div className="k">Available</div>
              <div className="v">Open to full-time roles · May 2026</div>
            </div>
            <div className="meta-row">
              <div className="k">Reach</div>
              <div className="v">
                <a href={`mailto:${PROFILE.email}`}>{PROFILE.email}</a><br/>
                <a href={PROFILE.linkedinUrl} target="_blank" rel="noreferrer">{PROFILE.linkedin}</a><br/>
                <a href={PROFILE.githubUrl} target="_blank" rel="noreferrer">{PROFILE.github}</a>
              </div>
            </div>
          </div>
        </div>
        <div className="scroll-hint">
          <span>scroll</span><span className="arrow">↓</span>
        </div>
      </div>
    </section>
  );
}

// ─── Marquee strip ────────────────────────────────────────────────────────
function Strip() {
  const items = ["AI Agents", "UAV Autonomy", "Data Pipelines", "LLM Fine-tuning", "Sim Worlds", "Cloud Infra", "Evaluation"];
  return (
    <div className="strip">
      <div className="strip-track">
        {[0,1].map(i => (
          <span key={i}>
            {items.map((it, j) => (
              <React.Fragment key={j}>
                <span>{it}</span>
                <span className="star">✦</span>
              </React.Fragment>
            ))}
          </span>
        ))}
      </div>
    </div>
  );
}

// ─── About ────────────────────────────────────────────────────────────────
// ─── Gradient Descent Visualizer ─────────────────────────────────────────
function GradientDescent() {
  const canvasRef = useRef(null);
  const stRef = useRef({
    rx: -0.45, ry: 0.45, zoom: 1.0,
    drag: false, didDrag: false, lx: 0, ly: 0,
    mx: 0, my: 0, hovering: false,
    pinchDist: 0,
    bx: 2.4, bz: -1.9,
    vx: 0, vz: 0,
    trail: [], step: 0,
  });

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let raf;

    const N = 34, RANGE = 3.0, LR = 0.006, FRICTION = 0.90, MAX_V = 0.05;

    const f = (x, z) =>
      0.12*(x*x + z*z) +
      0.5*Math.sin(1.6*x)*Math.cos(1.6*z)*Math.exp(-0.15*(x*x+z*z));

    const EPS = 0.01;
    const grad = (x, z) => [
      (f(x+EPS,z) - f(x-EPS,z))/(2*EPS),
      (f(x,z+EPS) - f(x,z-EPS))/(2*EPS),
    ];

    // Pre-compute grid
    const grid = [];
    let gMin = Infinity, gMax = -Infinity;
    for (let i = 0; i <= N; i++) {
      grid[i] = [];
      for (let j = 0; j <= N; j++) {
        const x = -RANGE + 2*RANGE*i/N;
        const z = -RANGE + 2*RANGE*j/N;
        const y = f(x, z);
        if (y < gMin) gMin = y;
        if (y > gMax) gMax = y;
        grid[i][j] = { x, y, z };
      }
    }
    const yRange = gMax - gMin;

    const project = (x, y, z, W, H) => {
      const { rx, ry } = stRef.current;
      const scale = Math.min(W, H) * 0.36 * stRef.current.zoom;
      const x1 = x*Math.cos(ry) - z*Math.sin(ry);
      const z1 = x*Math.sin(ry) + z*Math.cos(ry);
      const y2 = y*Math.cos(rx) - z1*Math.sin(rx);
      const z2 = y*Math.sin(rx) + z1*Math.cos(rx);
      const fov = 5;
      const d = Math.max(0.1, fov + z2);
      return [W*0.5 + (x1*fov/d)*scale, H*0.48 - (y2*fov/d)*scale, z2];
    };

    const lossColor = (y, a=1) => {
      const t = Math.max(0, Math.min(1, (y - gMin) / yRange));
      return `rgba(${Math.round(200-t*170)},${Math.round(165-t*140)},${Math.round(90-t*68)},${a})`;
    };

    const render = () => {
      const W = canvas.offsetWidth, H = canvas.offsetHeight;
      if (!W || !H) { raf = requestAnimationFrame(render); return; }
      if (canvas.width !== W || canvas.height !== H) { canvas.width = W; canvas.height = H; }
      ctx.clearRect(0, 0, W, H);

      const st = stRef.current;

      // Gradient descent step
      const [gx, gz] = grad(st.bx, st.bz);
      st.vx = FRICTION*st.vx - LR*gx;
      st.vz = FRICTION*st.vz - LR*gz;
      // Clamp speed so steep slopes don't cause runaway
      const speed = Math.sqrt(st.vx*st.vx + st.vz*st.vz);
      if (speed > MAX_V) { st.vx *= MAX_V/speed; st.vz *= MAX_V/speed; }
      st.bx = Math.max(-RANGE+0.1, Math.min(RANGE-0.1, st.bx + st.vx));
      st.bz = Math.max(-RANGE+0.1, Math.min(RANGE-0.1, st.bz + st.vz));
      st.step++;
      // Record trail every 3 frames
      if (st.step % 3 === 0) {
        st.trail.push([st.bx, f(st.bx, st.bz), st.bz]);
        if (st.trail.length > 60) st.trail.shift();
      }
      // Reset once truly converged (speed checked after update, step guard avoids instant reset)
      if (speed < 0.0008 && st.step > 150) {
        const ang = Math.random()*Math.PI*2;
        const r = 1.8 + Math.random()*1.0;
        st.bx = Math.max(-RANGE+0.1, Math.min(RANGE-0.1, Math.cos(ang)*r));
        st.bz = Math.max(-RANGE+0.1, Math.min(RANGE-0.1, Math.sin(ang)*r));
        st.vx = 0; st.vz = 0; st.trail = []; st.step = 0;
      }

      // Build + sort faces (painter's algorithm)
      const faces = [];
      for (let i = 0; i < N; i++) {
        for (let j = 0; j < N; j++) {
          const a=grid[i][j], b=grid[i+1][j], c=grid[i+1][j+1], d=grid[i][j+1];
          const cy = (a.y+b.y+c.y+d.y)*0.25;
          const [,,cz] = project((a.x+c.x)*0.5, cy, (a.z+c.z)*0.5, W, H);
          faces.push({pts:[a,b,c,d], cy, cz});
        }
      }
      faces.sort((a,b)=>b.cz-a.cz);

      for (const {pts, cy} of faces) {
        const [p0,p1,p2,p3] = pts.map(p=>project(p.x,p.y,p.z,W,H));
        ctx.beginPath();
        ctx.moveTo(p0[0],p0[1]);
        ctx.lineTo(p1[0],p1[1]);
        ctx.lineTo(p2[0],p2[1]);
        ctx.lineTo(p3[0],p3[1]);
        ctx.closePath();
        ctx.fillStyle = lossColor(cy, 0.92);
        ctx.fill();
        ctx.strokeStyle = 'rgba(18,17,14,0.22)';
        ctx.lineWidth = 0.3;
        ctx.stroke();
      }

      // Trail
      for (let i = 1; i < st.trail.length; i++) {
        const [ax,ay,az] = st.trail[i-1], [bx,by,bz] = st.trail[i];
        const [p0x,p0y] = project(ax, ay+0.03, az, W, H);
        const [p1x,p1y] = project(bx, by+0.03, bz, W, H);
        ctx.beginPath();
        ctx.moveTo(p0x,p0y); ctx.lineTo(p1x,p1y);
        ctx.strokeStyle = `rgba(200,165,90,${(i/st.trail.length)*0.85})`;
        ctx.lineWidth = 1.6; ctx.stroke();
      }

      // Ball glow + ball
      const by = f(st.bx, st.bz);
      const [bsx,bsy] = project(st.bx, by+0.05, st.bz, W, H);
      const grd = ctx.createRadialGradient(bsx,bsy,1,bsx,bsy,20);
      grd.addColorStop(0,'rgba(200,165,90,0.55)');
      grd.addColorStop(1,'rgba(200,165,90,0)');
      ctx.beginPath(); ctx.arc(bsx,bsy,20,0,Math.PI*2);
      ctx.fillStyle = grd; ctx.fill();
      ctx.beginPath(); ctx.arc(bsx,bsy,5.5,0,Math.PI*2);
      ctx.fillStyle = 'rgba(220,185,100,1)'; ctx.fill();
      ctx.strokeStyle = 'rgba(255,245,200,0.75)'; ctx.lineWidth = 1; ctx.stroke();

      // Hover indicator — find nearest surface point to cursor
      if (st.hovering) {
        let best = Infinity, hx = 0, hy = 0, hz = 0;
        for (let i = 0; i <= N; i++) {
          for (let j = 0; j <= N; j++) {
            const {x, y, z} = grid[i][j];
            const [px, py] = project(x, y, z, W, H);
            const d = (px-st.mx)**2 + (py-st.my)**2;
            if (d < best) { best=d; hx=x; hy=y; hz=z; }
          }
        }
        if (best < 3000) {
          const [hsx, hsy] = project(hx, hy+0.04, hz, W, H);
          ctx.save();
          ctx.setLineDash([2,3]);
          ctx.beginPath(); ctx.arc(hsx,hsy,11,0,Math.PI*2);
          ctx.strokeStyle='rgba(200,165,90,0.45)'; ctx.lineWidth=1; ctx.stroke();
          ctx.setLineDash([]);
          ctx.beginPath(); ctx.arc(hsx,hsy,3.5,0,Math.PI*2);
          ctx.strokeStyle='rgba(200,165,90,0.8)'; ctx.lineWidth=1.2; ctx.stroke();
          ctx.restore();
        }
      }

      // HUD
      ctx.font = '10px JetBrains Mono, monospace';
      ctx.fillStyle = 'rgba(200,165,90,0.7)';
      ctx.fillText('loss landscape · click to drop · drag to rotate', 14, 20);
      ctx.fillStyle = 'rgba(200,165,90,0.55)';
      ctx.fillText(`loss: ${f(st.bx,st.bz).toFixed(5)}   step: ${String(st.step).padStart(4,'0')}`, 14, H - 14);

      raf = requestAnimationFrame(render);
    };

    const placeBall = (mx, my) => {
      const W = canvas.offsetWidth, H = canvas.offsetHeight;
      let best = Infinity, bestX = 0, bestZ = 0;
      for (let i = 0; i <= N; i++) {
        for (let j = 0; j <= N; j++) {
          const {x, y, z} = grid[i][j];
          const [px, py] = project(x, y, z, W, H);
          const d = (px-mx)**2 + (py-my)**2;
          if (d < best) { best=d; bestX=x; bestZ=z; }
        }
      }
      Object.assign(stRef.current, { bx:bestX, bz:bestZ, vx:0, vz:0, trail:[[bestX, f(bestX,bestZ), bestZ]], step:0 });
    };

    const down = (e) => {
      if (e.touches) e.preventDefault();
      const {clientX,clientY} = e.touches ? e.touches[0] : e;
      stRef.current.drag = true;
      stRef.current.didDrag = false;
      stRef.current.lx = clientX;
      stRef.current.ly = clientY;
    };
    const move = (e) => {
      if (e.touches) e.preventDefault();
      const {clientX,clientY} = e.touches ? e.touches[0] : e;
      const rect = canvas.getBoundingClientRect();
      stRef.current.mx = clientX - rect.left;
      stRef.current.my = clientY - rect.top;
      stRef.current.hovering = true;
      if (!stRef.current.drag) return;
      const dx = clientX - stRef.current.lx;
      const dy = clientY - stRef.current.ly;
      if (Math.abs(dx) > 2 || Math.abs(dy) > 2) stRef.current.didDrag = true;
      stRef.current.ry += dx * 0.008;
      stRef.current.rx += dy * 0.008;
      stRef.current.rx = Math.max(-1.2, Math.min(1.2, stRef.current.rx));
      stRef.current.lx = clientX; stRef.current.ly = clientY;
    };
    const up = () => { stRef.current.drag = false; };
    const onClick = (e) => {
      if (stRef.current.didDrag) return;
      const rect = canvas.getBoundingClientRect();
      placeBall(e.clientX - rect.left, e.clientY - rect.top);
    };
    const leave = () => { stRef.current.hovering = false; };
    const MIN_ZOOM = 0.5, MAX_ZOOM = 2.5;
    const clampZoom = z => Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, z));
    const onWheel = (e) => {
      e.preventDefault();
      stRef.current.zoom = clampZoom(stRef.current.zoom - e.deltaY * 0.001);
    };
    const onTouchStart = (e) => {
      if (e.touches.length === 2) {
        stRef.current.pinchDist = Math.hypot(
          e.touches[0].clientX - e.touches[1].clientX,
          e.touches[0].clientY - e.touches[1].clientY
        );
      }
    };
    const onTouchMoveZoom = (e) => {
      if (e.touches.length === 2) {
        const dist = Math.hypot(
          e.touches[0].clientX - e.touches[1].clientX,
          e.touches[0].clientY - e.touches[1].clientY
        );
        stRef.current.zoom = clampZoom(stRef.current.zoom * (dist / stRef.current.pinchDist));
        stRef.current.pinchDist = dist;
      }
    };

    canvas.addEventListener('mousedown', down);
    canvas.addEventListener('touchstart', down, {passive:false});
    canvas.addEventListener('touchstart', onTouchStart, {passive:false});
    canvas.addEventListener('click', onClick);
    canvas.addEventListener('mouseleave', leave);
    canvas.addEventListener('wheel', onWheel, {passive:false});
    window.addEventListener('mousemove', move);
    canvas.addEventListener('touchmove', move, {passive:false});
    canvas.addEventListener('touchmove', onTouchMoveZoom, {passive:false});
    window.addEventListener('mouseup', up);
    window.addEventListener('touchend', up);
    raf = requestAnimationFrame(render);

    return () => {
      cancelAnimationFrame(raf);
      canvas.removeEventListener('mousedown', down);
      canvas.removeEventListener('touchstart', down);
      canvas.removeEventListener('touchstart', onTouchStart);
      canvas.removeEventListener('click', onClick);
      canvas.removeEventListener('mouseleave', leave);
      canvas.removeEventListener('wheel', onWheel);
      window.removeEventListener('mousemove', move);
      canvas.removeEventListener('touchmove', move);
      canvas.removeEventListener('touchmove', onTouchMoveZoom);
      window.removeEventListener('mouseup', up);
      window.removeEventListener('touchend', up);
    };
  }, []);

  return (
    <div className="gd-canvas" style={{
      position:'relative', height:320, borderRadius:'var(--radius)',
      border:'1px solid var(--line)', overflow:'hidden',
      background:'var(--surface)', boxShadow:'var(--shadow-sm)',
      cursor:'grab', userSelect:'none', touchAction:'none',
    }}>
      <canvas ref={canvasRef} style={{width:'100%',height:'100%',display:'block',touchAction:'none'}} />
    </div>
  );
}

function About() {
  return (
    <section className="block" id="about">
      <div className="container">
        <div className="section-head reveal">
          <span className="num">01 / Index</span>
          <h2>About<span className="it"> & now</span></h2>
        </div>
        <div className="about-grid">
          <div className="about reveal">
            {PROFILE.about.map((p,i) => <p key={i}>{p}</p>)}
          </div>
          <div className="now reveal">
            {PROFILE.now.map((n,i) => (
              <div className="now-item" key={i}>
                <div className="when">{n.when}</div>
                <div className="what">
                  <em>{n.title}</em>
                  {n.detail}
                </div>
              </div>
            ))}
          </div>
        </div>
        <div className="reveal gd-reveal" style={{marginTop:48}}>
          <GradientDescent />
        </div>
      </div>
    </section>
  );
}

// ─── Experience ───────────────────────────────────────────────────────────
function Experience() {
  return (
    <section className="block" id="experience">
      <div className="container">
        <div className="section-head reveal">
          <span className="num">02 / Work</span>
          <h2>Where I've<span className="it"> shipped</span></h2>
        </div>
        <div>
          {EXPERIENCE.map((x, i) => (
            <div className="xp-row reveal" key={i}>
              <div className="yr">{x.range}</div>
              <div className="ttl serif">
                {x.role}
                <span className="co">{x.company}</span>
              </div>
              <ul className="bullets">
                {x.bullets.map((b, j) => <li key={j}>{b}</li>)}
              </ul>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

// ─── Projects ─────────────────────────────────────────────────────────────
function Projects() {
  const [open, setOpen] = useState(-1); // all collapsed by default
  return (
    <section className="block" id="projects">
      <div className="container">
        <div className="section-head reveal">
          <span className="num">03 / Projects</span>
          <h2>Selected<span className="it"> projects</span></h2>
        </div>
        <div className="proj-grid">
          {PROJECTS.map((p, i) => {
            const isOpen = open === i;
            return (
              <div className={`proj ${isOpen ? 'open' : ''}`} key={p.name}>
                <div className="proj-head" onClick={() => setOpen(isOpen ? -1 : i)} style={{cursor:'none'}}>
                  <span className="idx">P / 0{i+1}</span>
                  <div className="nm serif">{p.name}</div>
                  <div className="toggle">+</div>
                </div>
                <div className="proj-body">
                  <div>
                    <div className="proj-inner">
                      <div className="spacer" />
                      <div className="meta">
                        <div>
                          <div className="k">Project</div>
                          <div className="v">{p.full}</div>
                        </div>
                        <div>
                          <div className="k">Period</div>
                          <div className="v">{p.period}</div>
                        </div>
                        <div>
                          <div className="k">Role</div>
                          <div className="v">{p.role}</div>
                        </div>
                        {p.repoUrl && (
                          <div>
                            <div className="k">Repo</div>
                            <div className="v"><a href={p.repoUrl} target="_blank" rel="noreferrer" onClick={e=>e.stopPropagation()} style={{color:'var(--accent)', textDecoration:'none', borderBottom:'1px dashed currentColor', paddingBottom:'1px'}}>{p.repo} ↗</a></div>
                          </div>
                        )}
                        <div>
                          <div className="k">Stack</div>
                          <div className="stack">
                            {p.stack.map(s => <span key={s}>{s}</span>)}
                          </div>
                        </div>
                      </div>
                      <div className="desc">
                        <p>{p.summary}</p>
                        <p style={{opacity:.78}}>{p.detail}</p>
                        <div className="metrics">
                          {p.metrics.map((m, j) => (
                            <div className="m" key={j}>
                              <div className="big serif">{m.big}</div>
                              <div className="lbl">{m.lbl}</div>
                            </div>
                          ))}
                        </div>
                        <div className="proj-preview" onClick={e=>e.stopPropagation()}>
                          <ProjectViz kind={p.viz || (i===0?'lidar':i===1?'attention':'scatter')} />
                          <span className="tag"><span className="ok">●</span>{p.tag || (i===0?'lidar_stream / live':i===1?'attention / 8 heads':'random_forest / 0.87')}</span>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

// ─── Skills ───────────────────────────────────────────────────────────────
function Skills() {
  const cats = ["All", ...SKILL_GROUPS.map(g => g.cat)];
  const [filter, setFilter] = useState("All");
  const all = useMemo(() => SKILL_GROUPS.flatMap(g => g.items.map(it => ({ cat: g.cat, name: it }))), []);
  return (
    <section className="block" id="skills">
      <div className="container">
        <div className="section-head reveal">
          <span className="num">04 / Stack</span>
          <h2>Things I<span className="it"> reach for</span></h2>
        </div>
        <div className="reveal">
          <div className="skill-filters">
            {cats.map(c => (
              <button key={c} className={filter === c ? 'on' : ''} onClick={() => setFilter(c)}>
                {c}
              </button>
            ))}
          </div>
          <div className="skill-cloud">
            {all.map((s, i) => {
              const active = filter === "All" || filter === s.cat;
              return (
                <div key={i} className={`skill ${active ? 'hi' : 'dim'}`}>
                  <span>{s.name}</span>
                  <span className="cat">/ {s.cat}</span>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </section>
  );
}

// ─── Education ────────────────────────────────────────────────────────────
function Education() {
  return (
    <section className="block" id="education">
      <div className="container">
        <div className="section-head reveal">
          <span className="num">05 / Schools</span>
          <h2>Education<span className="it"> & studies</span></h2>
        </div>
        <div className="edu-grid">
          {EDUCATION.map((e, i) => (
            <div className="edu reveal" key={i}>
              <div className="yr">{e.range}</div>
              <div className="deg serif">
                {e.deg}
                <span className="sch">{e.school}</span>
              </div>
              <div className="courses">
                <span className="ck">Coursework</span>
                {e.courses.join(" · ")}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

// ─── Contact ──────────────────────────────────────────────────────────────
function Contact() {
  return (
    <section className="contact" id="contact">
      <div className="container">
        <div className="section-head reveal" style={{borderTop:'none', paddingTop:0}}>
          <span className="num">06 / Contact</span>
          <h2>Let's<span className="it"> build</span></h2>
        </div>
        <h3 className="lead reveal">
          Hiring for a <span className="it">full-time</span><br/>AI / ML role?
        </h3>
        <p className="reveal" style={{fontFamily:'var(--serif)', fontStyle:'italic', fontSize:'clamp(20px,2vw,28px)', maxWidth:'42ch', marginTop:'28px', opacity:.85, lineHeight:1.35}}>
          I graduate from SJSU in May 2026 and I'm actively interviewing. If you're building agents, evaluation tooling, or the infrastructure underneath them — I'd love to talk.
        </p>
        <div className="ctas reveal" style={{marginTop:'36px'}}>
          <a className="cta" href={`mailto:${PROFILE.email}?subject=Full-time%20opportunity`}>
            Email me <span className="arr">↗</span>
          </a>
          <a className="cta" href={PROFILE.linkedinUrl} target="_blank" rel="noreferrer">
            Connect on LinkedIn <span className="arr">↗</span>
          </a>
        </div>
        <div className="contact-grid reveal">
          <div className="contact-cell">
            <span className="k">Email</span>
            <span className="v"><a href={`mailto:${PROFILE.email}`}>{PROFILE.email}</a></span>
          </div>
          <div className="contact-cell">
            <span className="k">LinkedIn</span>
            <span className="v"><a href={PROFILE.linkedinUrl} target="_blank" rel="noreferrer">{PROFILE.linkedin}</a></span>
          </div>
          <div className="contact-cell">
            <span className="k">GitHub</span>
            <span className="v"><a href={PROFILE.githubUrl} target="_blank" rel="noreferrer">{PROFILE.github}</a></span>
          </div>
          <div className="contact-cell">
            <span className="k">Location</span>
            <span className="v">San Jose, CA<br/><span style={{color:'var(--muted)'}}>open to relocation</span></span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─── Tweaks ───────────────────────────────────────────────────────────────
function Tweaks({ t, setTweak }) {
  return (
    <TweaksPanel>
      <TweakSection label="Palette" />
      <TweakRadio label="Mode" value={t.palette} options={["paper","ink","clay","linen"]}
                  onChange={(v)=>setTweak('palette', v)} />
      <TweakSection label="Accent" />
      <TweakRadio label="Hue" value={t.accent} options={["brass","rust","sage","slate","bone"]}
                  onChange={(v)=>setTweak('accent', v)} />
      <TweakSection label="Type" />
      <TweakToggle label="Editorial serif" value={t.serif} onChange={(v)=>setTweak('serif', v)} />
    </TweaksPanel>
  );
}

// ─── Root ─────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useReveal();

  // apply palette + accent + serif
  useEffect(() => {
    const p = PALETTES[t.palette] || PALETTES.paper;
    const root = document.documentElement;
    Object.entries(p).forEach(([k,v]) => root.style.setProperty('--' + k, v));
    root.style.setProperty('--accent', ACCENTS[t.accent] || ACCENTS.brass);
    root.style.setProperty('--accent-ink', t.palette === 'linen' ? '#f1ecdd' : '#12110e');
    document.body.classList.toggle('serif-on', !!t.serif);
  }, [t.palette, t.accent, t.serif]);

  return (
    <>
      <Cursor />
      <OpenToWorkBar />
      <Nav />
      <Hero />
      <Strip />
      <About />
      <Experience />
      <Projects />
      <Skills />
      <Education />
      <Contact />
      <footer>
        <div className="left">
          <span>© 2026 Ritesh Singh</span>
          <span>·</span>
          <span>Designed & built with care</span>
        </div>
        <div>v 1.0 · San Jose, CA</div>
      </footer>
      <Tweaks t={t} setTweak={setTweak} />
    </>
  );
}

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