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

function rand(s){ const x=Math.sin(s+1)*10000; return x-Math.floor(x); }

function occType(occ) {
  const o = (occ||"").toLowerCase();
  if (o.includes("birthday")||o.includes("baby")) return "birthday";
  if (o.includes("anniversary")||o.includes("first met")||o.includes("first date")||o.includes("first kiss")) return "anniversary";
  if (o.includes("wedding")) return "wedding";
  if (o.includes("graduation")) return "graduation";
  return "default";
}

/* ===== 1. Orrery — real solar system + occasion-shaped arc ===== */
function Orrery({ size = 200, seed = 1, date = null, occasion = "", stroke = "currentColor" }) {
  const cx = size/2, cy = size/2;
  const PLANETS = [
    { name:"Mercury", r:0.16, dot:1.1 },
    { name:"Venus",   r:0.24, dot:1.5 },
    { name:"Earth",   r:0.31, dot:1.8 },
    { name:"Mars",    r:0.39, dot:1.4 },
    { name:"Jupiter", r:0.47, dot:2.4 },
  ];
  const angles = PLANETS.map(({ name }, i) => {
    if (date && typeof Astronomy !== "undefined") {
      try { const v = Astronomy.HelioVector(name, date); return Math.atan2(v.y, v.x); } catch(e) {}
    }
    return rand(seed + i * 2.618) * Math.PI * 2;
  });
  const pts = PLANETS.map(({ r }, i) => {
    const R = r * size * 0.86;
    return [cx + Math.cos(angles[i]) * R, cy + Math.sin(angles[i]) * R];
  });

  const type = occType(occasion);
  // Build arc point sequence based on occasion
  let arcPts, closed = false, extraPts = null;
  if (type === "anniversary") {
    arcPts = [...pts, pts[0]]; closed = true;           // eternal loop
  } else if (type === "wedding") {
    arcPts = [pts[0], pts[2], pts[4]];                  // first arc
    extraPts = [pts[1], pts[3]];                         // paired second arc
  } else if (type === "graduation") {
    arcPts = pts;                                        // Mercury → Jupiter, ascending
  } else if (type === "birthday") {
    arcPts = [pts[0], pts[4], pts[1], pts[3], pts[2]]; // zigzag inner↔outer, burst feel
  } else {
    const order = [0,1,2,3,4].sort((a,b) => rand(seed+a*7.3) - rand(seed+b*7.3));
    arcPts = order.map(i => pts[i]);
  }

  const buildD = (ap) => {
    let d = `M${ap[0][0].toFixed(1)},${ap[0][1].toFixed(1)}`;
    for(let i=1;i<ap.length;i++){
      const [px,py]=ap[i-1],[x,y]=ap[i];
      const qx = ((px+x)/2 + (rand(seed+i*31)-0.5)*size*0.07).toFixed(1);
      const qy = ((py+y)/2 + (rand(seed+i*17)-0.5)*size*0.07).toFixed(1);
      d += ` Q${qx},${qy} ${x.toFixed(1)},${y.toFixed(1)}`;
    }
    return d;
  };

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} overflow="visible">
      <circle cx={cx} cy={cy} r={size*0.020} fill={stroke}/>
      {PLANETS.map(({ name, r, dot }, i) => {
        const R = r * size * 0.86;
        return (
          <g key={name}>
            <circle cx={cx} cy={cy} r={R} fill="none" stroke={stroke} strokeWidth="0.4" opacity="0.22"/>
            <circle cx={pts[i][0]} cy={pts[i][1]} r={dot} fill={stroke}/>
          </g>
        );
      })}
      <path d={buildD(arcPts) + (closed?" Z":"")} fill="none" stroke={stroke} strokeWidth="1.0" opacity="0.50" strokeLinecap="round"/>
      {extraPts && <path d={buildD(extraPts)} fill="none" stroke={stroke} strokeWidth="1.0" opacity="0.50" strokeLinecap="round"/>}
    </svg>
  );
}

/* ===== 2. Lunation — real moon phase + occasion-shaped rings ===== */
function Lunation({ size = 200, seed = 1, phase = 0.5, occasion = "", stroke = "currentColor" }) {
  const cx = size/2, cy = size/2, r = size*0.28;
  const angle = phase * Math.PI * 2;
  const rx = Math.abs(Math.cos(angle)) * r;
  const waxing = phase < 0.5;
  const id = `mc${Math.round(phase*1000)+seed}`;
  const type = occType(occasion);

  const rings = Array.from({length:6}, (_,i) => {
    const base = r * (1.5 + i*0.28);
    if (type === "birthday") {
      // Uniform circles — even, celebratory pulse
      return { rx: base, ry: base * (0.97 + rand(seed+i*3)*0.03), rot: 0, op: 0.17 - i*0.022 };
    } else if (type === "anniversary") {
      // All aligned elongated — orbital, continuous
      return { rx: base * 1.22, ry: base * 0.76, rot: 18, op: 0.17 - i*0.022 };
    } else if (type === "wedding") {
      // Alternating axes — two interleaved families of rings
      return { rx: base * (i%2===0?1.18:0.82), ry: base * (i%2===0?0.82:1.18), rot: i%2===0?0:90, op: 0.17 - i*0.022 };
    } else if (type === "graduation") {
      // Spiralling eccentricity — expanding, ascending
      const ecc = 1 + i*0.14;
      return { rx: base * ecc, ry: base / ecc, rot: 22, op: 0.17 - i*0.022 };
    } else {
      return { rx: base * (0.88 + rand(seed+i*3)*0.24), ry: base * (0.88 + rand(seed+i*5)*0.24), rot: rand(seed+i*7)*180, op: 0.18 - i*0.025 };
    }
  });

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} overflow="visible">
      {rings.map((ring,i) => (
        <ellipse key={i} cx={cx} cy={cy} rx={ring.rx} ry={ring.ry}
          fill="none" stroke={stroke} strokeWidth="0.65" opacity={ring.op}
          transform={`rotate(${ring.rot} ${cx} ${cy})`}/>
      ))}
      <defs><clipPath id={id}><circle cx={cx} cy={cy} r={r}/></clipPath></defs>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={stroke} strokeWidth="1.2" opacity="0.9"/>
      <g clipPath={`url(#${id})`}>
        <rect x={waxing ? cx : cx-r} y={cy-r} width={r} height={r*2} fill={stroke} opacity="0.85"/>
        <ellipse cx={cx} cy={cy} rx={rx} ry={r}
          fill={waxing ? (phase<0.25?"none":stroke) : (phase>0.75?"none":stroke)}
          opacity="0.85"/>
      </g>
    </svg>
  );
}

/* ===== 3. SolarSign — sun in zodiac + occasion-shaped chord web ===== */
function SolarSign({ size = 200, seed = 1, date = null, occasion = "", stroke = "currentColor" }) {
  const cx = size/2, cy = size/2;
  let longitude = rand(seed * 3.7) * 360;
  if (date && typeof Astronomy !== "undefined") {
    try {
      const geo = Astronomy.GeoVector("Sun", date, false);
      const ecl = Astronomy.Ecliptic(geo);
      longitude = ecl.elon;
    } catch(e) {}
  }
  const outerR = size*0.43, innerR = size*0.27, midR = (outerR+innerR)/2;
  const sunAngle = (longitude/360)*Math.PI*2 - Math.PI/2;
  const activeSign = Math.floor(longitude/30) % 12;
  const sx = cx + Math.cos(sunAngle)*midR, sy = cy + Math.sin(sunAngle)*midR;
  const spokes = Array.from({length:12}, (_,i) => {
    const a = (i/12)*Math.PI*2 - Math.PI/2;
    return [cx+Math.cos(a)*innerR, cy+Math.sin(a)*innerR];
  });

  const type = occType(occasion);
  let picked;
  if (type === "anniversary") {
    // Cyclic polygon — closed ring of chords
    picked = Array.from({length:12}, (_,i) => [i,(i+1)%12]);
  } else if (type === "wedding") {
    // Mirror pairs — perfect symmetry
    picked = Array.from({length:6}, (_,i) => [i, 12-i > 0 ? 12-i : 0]);
  } else if (type === "birthday") {
    // Dense star polygon — festive, full web
    picked = [];
    for(let i=0;i<12;i++) { picked.push([i,(i+4)%12]); picked.push([i,(i+5)%12]); }
    picked = picked.filter(([a,b]) => a<b);
  } else if (type === "graduation") {
    // Chords only ascending (lower half → upper half of zodiac ring)
    picked = [];
    for(let i=6;i<12;i++) for(let j=0;j<6;j++) if(rand(seed+i*7+j*13)>0.65) picked.push([i,j]);
    picked = picked.slice(0,6);
  } else {
    const chords = [];
    for(let i=0;i<12;i++){ const j=(i+1+Math.floor(rand(seed+i*13)*5))%12; if(j!==i) chords.push([i,j]); }
    picked = chords.filter((_,k) => rand(seed*2+k*9) > 0.45).slice(0,7);
  }

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} overflow="visible">
      <circle cx={cx} cy={cy} r={outerR} fill="none" stroke={stroke} strokeWidth="0.7" opacity="0.48"/>
      <circle cx={cx} cy={cy} r={innerR} fill="none" stroke={stroke} strokeWidth="0.4" opacity="0.20"/>
      {picked.map(([i,j],k) => (
        <line key={k} x1={spokes[i][0]} y1={spokes[i][1]} x2={spokes[j][0]} y2={spokes[j][1]}
          stroke={stroke} strokeWidth="0.55" opacity="0.30"/>
      ))}
      {Array.from({length:12}).map((_,i) => {
        const a = (i/12)*Math.PI*2 - Math.PI/2;
        const active = i === activeSign;
        return <line key={i}
          x1={cx+Math.cos(a)*innerR} y1={cy+Math.sin(a)*innerR}
          x2={cx+Math.cos(a)*outerR} y2={cy+Math.sin(a)*outerR}
          stroke={stroke} strokeWidth={active?1.4:0.4} opacity={active?0.9:0.25}/>;
      })}
      <circle cx={sx} cy={sy} r={3.5} fill={stroke}/>
      {Array.from({length:6}).map((_,i) => {
        const ra = (i/6)*Math.PI*2;
        return <line key={i} x1={sx+Math.cos(ra)*5} y1={sy+Math.sin(ra)*5}
          x2={sx+Math.cos(ra)*7.5} y2={sy+Math.sin(ra)*7.5} stroke={stroke} strokeWidth="0.8" opacity="0.6"/>;
      })}
    </svg>
  );
}

/* ===== 4. Starfield — seasonal sky + occasion-shaped constellation ===== */
function Starfield({ size = 200, seed = 1, date = null, occasion = "", stroke = "currentColor" }) {
  const cx = size/2, cy = size/2;
  const month = date ? date.getMonth() : Math.floor(rand(seed)*12);
  const season = Math.floor(((month+1)%12)/3);
  const SEASONAL = [
    [[.50,.55],[.45,.61],[.55,.61],[.41,.69],[.59,.69],[.37,.76],[.63,.76],[.24,.40],[.19,.63],[.79,.35],[.81,.24],[.14,.29],[.86,.71],[.50,.88]],
    [[.46,.40],[.38,.35],[.29,.32],[.56,.38],[.63,.43],[.69,.51],[.61,.59],[.19,.24],[.81,.29],[.76,.66],[.24,.71],[.50,.19],[.14,.54],[.86,.54]],
    [[.50,.29],[.50,.44],[.50,.57],[.50,.70],[.37,.44],[.63,.44],[.34,.29],[.66,.29],[.19,.39],[.81,.39],[.24,.64],[.76,.64],[.50,.86],[.50,.14]],
    [[.40,.40],[.60,.40],[.60,.60],[.40,.60],[.24,.34],[.19,.27],[.79,.29],[.83,.21],[.34,.24],[.66,.24],[.19,.66],[.81,.66],[.50,.86],[.50,.14]],
  ];
  const raw = SEASONAL[season];
  const R = size*0.44;
  const stars = raw.map(([x,y],i) => ({
    x: x*size + (rand(seed+i*1.3)-0.5)*size*0.07,
    y: y*size + (rand(seed+i*2.7)-0.5)*size*0.07,
    r: 0.9 + rand(seed*2+i)*2.0,
    op: 0.55 + rand(seed+i*4)*0.45,
  }));
  const n = stars.length;
  const type = occType(occasion);
  let lines;

  if (type === "birthday") {
    // Hub-and-spoke from star nearest to center — radial burst
    const hub = stars.reduce((b,s,i) => { const d=Math.hypot(s.x-cx,s.y-cy); return d<b.d?{d,i}:b; }, {d:Infinity,i:0});
    lines = stars.map((_,i) => i!==hub.i ? [hub.i,i] : null).filter(Boolean).slice(0,8);
  } else if (type === "anniversary") {
    // Chain loop — eternal cycle
    lines = stars.map((_,i) => [i,(i+1)%n]);
  } else if (type === "wedding") {
    // Paired bonds — connect adjacent pairs
    lines = Array.from({length:Math.floor(n/2)}, (_,i) => [i*2, (i*2+1)<n ? i*2+1 : 0]);
  } else if (type === "graduation") {
    // Lower stars connect to upper — ascending paths
    const upper = stars.map((_,i)=>i).filter(i=>stars[i].y < cy);
    const lower = stars.map((_,i)=>i).filter(i=>stars[i].y >= cy);
    lines = lower.slice(0,5).map((li,k) => [li, upper[k % Math.max(1,upper.length)]]);
  } else {
    const conns = [];
    for(let i=0;i<n;i++){ const j=(i+1+Math.floor(rand(seed+i*13)*4))%n; conns.push([i,j]); }
    lines = conns.filter((_,k) => rand(seed*3+k*11) > 0.52).slice(0,6);
  }

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} overflow="visible">
      <circle cx={cx} cy={cy} r={R} fill="none" stroke={stroke} strokeWidth="0.4" opacity="0.15"/>
      {lines.map(([i,j],k) => (
        <line key={k} x1={stars[i].x} y1={stars[i].y} x2={stars[j].x} y2={stars[j].y}
          stroke={stroke} strokeWidth="0.55" opacity="0.28"/>
      ))}
      {stars.map(({x,y,r,op},i) => <circle key={i} cx={x} cy={y} r={r} fill={stroke} opacity={op}/>)}
      {Array.from({length:8}).map((_,i) => {
        const x=rand(seed*3+i*7)*size, y=rand(seed*5+i*11)*size;
        if(Math.hypot(x-cx,y-cy)>R) return null;
        return <circle key={`f${i}`} cx={x} cy={y} r={0.55} fill={stroke} opacity={0.18}/>;
      })}
    </svg>
  );
}

const MOTIFS = [
  { id:"orrery",    name:"Orrery",    desc:"The solar system, exactly as it sat that day.",   Thumb:Orrery },
  { id:"lunation",  name:"Lunation",  desc:"The face the moon wore that night.",               Thumb:Lunation },
  { id:"solarsign", name:"Solar Sign",desc:"The sign the sun was crossing when it happened.",  Thumb:SolarSign },
  { id:"starfield", name:"Starfield", desc:"The stars that held the sky that night.",          Thumb:Starfield },
];

const OCCASIONS = ["Birthday","Anniversary","Wedding","First met","First date","First kiss","Graduation","New baby","Just because"];
const LOGO_SRC = "Once_Logo.png";

function LogoImage({ className = "", alt = "Once Graphics" }) {
  return <img className={className} src={LOGO_SRC} alt={alt}/>;
}

/* ===== icons ===== */
const Icon = {
  Gift: (p)=>(<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 12v8a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-8"/><rect x="2" y="7" width="20" height="5" rx="1"/><path d="M12 21V7"/><path d="M12 7H7.5a2.5 2.5 0 1 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 1 0 0-5C13 2 12 7 12 7z"/></svg>),
  Heart: (p)=>(<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>),
  Arrow: (p)=>(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>),
  Check: (p)=>(<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20 6 9 17l-5-5"/></svg>),
  Mail: (p)=>(<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg>),
  Lock: (p)=>(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>),
  Stamp: (p)=>(<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 21h14"/><path d="M5 17h14v-2c0-1.1-.9-2-2-2h-1.5l.5-2.5a3 3 0 1 0-6 0L10.5 13H9c-1.1 0-2 .9-2 2v2z"/></svg>),
};

/* ===== helpers ===== */
function makeID(seed){
  // deterministic-ish 8-char id
  function r(s){ return Math.floor((Math.sin(s)*10000-Math.floor(Math.sin(s)*10000))*36); }
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  let id = "";
  for (let i = 0; i < 8; i++) id += chars[r(seed*7+i*13.7) % chars.length];
  return id.slice(0,4) + "-" + id.slice(4);
}
function fmtDate(d){
  if(!d || isNaN(d)) return "—";
  return d.toLocaleDateString(undefined, { day:"numeric", month:"long", year:"numeric" });
}
function fmtDateShort(d){
  if(!d || isNaN(d)) return "—";
  return d.toLocaleDateString(undefined, { day:"2-digit", month:"2-digit", year:"numeric" });
}
function fmtGiftDate(d){
  if(!d || isNaN(d)) return "—";
  return d.toLocaleDateString(undefined, { month:"short", day:"2-digit", year:"numeric" });
}
function todayInput(){
  const d = new Date();
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}
function parseInputDate(value){
  if (!value) return null;
  const [y, m, d] = value.split("-").map(Number);
  if (!y || !m || !d) return null;
  return new Date(y, m - 1, d);
}

/* ===== Nav ===== */
function Nav({ onCreate }) {
  return (
    <div className="nav-wrap">
      <nav className="nav">
        <a href="#" className="brand" onClick={(e)=>{e.preventDefault(); window.scrollTo({top:0,behavior:"smooth"});}}>
          <span className="brand-mark"><LogoImage alt=""/></span>
          <span className="brand-name">Once Graphics</span>
        </a>
        <div className="nav-links">
          <a className="nav-link active" href="#create" onClick={(e)=>{e.preventDefault(); onCreate();}}>Create</a>
          <a className="nav-link" href="#how">How it works</a>
          <a className="nav-link" href="#pricing">Pricing</a>
        </div>
        <button className="nav-cta" onClick={onCreate}>Mint one</button>
      </nav>
    </div>
  );
}

/* ===== Hero ===== */
function Hero({ onStart }) {
  return (
    <section>
      <div className="hero">
        <div className="eyebrow fade-up">
          <span className="eyebrow-pill">NEW</span>
          Every moment, uniquely once.
        </div>
        <h1 className="hero-title fade-up" style={{animationDelay:'.05s'}}>
          A moment happens uniquely <span className="accent">once</span>
        </h1>
        <p className="lede fade-up" style={{animationDelay:'.1s'}}>
          Because that moment only minted once, it deserves more than a regular gift card.
        </p>
        <div className="hero-cta-row fade-up" style={{animationDelay:'.15s'}}>
          <button className="btn-primary" onClick={onStart}>Mint one</button>
          <button className="btn-ghost" onClick={()=>document.getElementById("how")?.scrollIntoView({block:"start", behavior:"smooth"})}>How it works →</button>
        </div>
      </div>
    </section>
  );
}

/* ===== Generator ===== */
function Generator({ scrollRef }) {
  const [step, setStep] = useState(0);
  const [data, setData] = useState({
    audience: null, name: "", byName: "", occasion: "", motif: "",
    dateMode: "this-year", monthDay: "", origDate: "",
    note: "", signDate: todayInput(), signName: "", email: "", customOccasion: "",
    deliveryMode: "direct", updates: false,
  });
  const [paying, setPaying] = useState(false);
  const set = (k,v) => setData(d=>({...d,[k]:v}));
  const totalSteps = 7;

  const canNext = () => {
    if (step === 0) return !!data.audience;
    if (step === 1) return data.name.trim().length > 0 && (data.audience === "self" || data.byName.trim().length > 0);
    if (step === 2) return !!data.occasion || data.customOccasion.trim().length > 0;
    if (step === 3) return data.dateMode === "this-year" ? !!data.monthDay : !!data.origDate;
    if (step === 4) return !!data.motif;
    if (step === 5) return true;
    if (step === 6) return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email.trim());
    return false;
  };
  const next = () => {
    if (!canNext()) return;
    if (step === 6) { setStep(7); return; }
    setStep(s => Math.min(8, s + 1));
  };
  const back = () => setStep(s => Math.max(0, s - 1));
  const reset = () => { setStep(0); setData({ audience:null, name:"", byName:"", occasion:"", motif:"", dateMode:"this-year", monthDay:"", origDate:"", note:"", signDate:todayInput(), signName:"", email:"", customOccasion:"", deliveryMode:"direct", updates:false }); };

  const pay = () => {
    setPaying(true);
    setTimeout(() => { setPaying(false); setStep(8); }, 1800);
  };

  const display = useMemo(() => {
    const recipient = data.audience === "self" ? (data.name || "Me") : (data.name || "—");
    const occ = data.occasion || data.customOccasion || "—";
    let date = null;
    if (data.dateMode === "this-year" && data.monthDay) {
      const [m, d] = data.monthDay.split("-");
      date = new Date(new Date().getFullYear(), parseInt(m,10)-1, parseInt(d,10));
    } else if (data.dateMode === "anniversary" && data.origDate) {
      date = parseInputDate(data.origDate);
    }
    const nameHash = recipient.split("").reduce((a,c)=>a*31+c.charCodeAt(0)|0, 17);
    const occHash  = occ.split("").reduce((a,c)=>a*37+c.charCodeAt(0)|0, 13);
    const idSeed = Math.abs(nameHash ^ occHash ^ (date ? +date : 1)) % 9999 + 1;
    const lunarCycle = 29.53059;
    const moonPhase = date ? (((date.getTime() - new Date(2000,0,6).getTime()) / 86400000 % lunarCycle) + lunarCycle) % lunarCycle / lunarCycle : 0.5;
    const sender = data.audience === "self" ? "Myself" : (data.byName.trim() || "—");
    const signed = parseInputDate(data.signDate) || new Date();
    return {
      recipient, occasion: occ, date, signed,
      issued: new Date(),
      id: makeID(idSeed),
      seed: idSeed,
      moonPhase,
      buyer: sender,
      note: data.note.trim(),
      signName: data.signName.trim() || (data.audience === "self" ? (data.name || "—") : (data.byName.trim() || "—")),
    };
  }, [data]);

  return (
    <section className="generator-section" id="create" ref={scrollRef}>
      <div className="generator">
        <FormPanel
          step={step} totalSteps={totalSteps}
          data={data} set={set}
          canNext={canNext} next={next} back={back}
          paying={paying} pay={pay} reset={reset}
        />
        <PreviewPanel display={display} motif={data.motif} audience={data.audience} step={step}/>
      </div>
    </section>
  );
}

/* ===== Form panel ===== */
function FormPanel({ step, totalSteps, data, set, canNext, next, back, paying, pay, reset }) {
  if (step === 8) return <div className="panel"><Success email={data.email} onAgain={reset}/></div>;

  if (step === 7) {
    // checkout step
    return (
      <div className="panel">
        <div className="panel-head">
          <span className="step-label">Checkout</span>
          <div className="progress">
            {Array.from({length: totalSteps}).map((_,i)=><span key={i} className="done"/>)}
          </div>
        </div>
        <h2 className="q-title">One <em>last step:</em> payment.</h2>
        <p className="q-help">Your gift card is locked in. Pay $8.99 CAD and we'll send everything to your inbox.</p>
        <div className="checkout-summary">
          <div className="row"><span className="k">Once Gift Card</span><span className="v">{data.motif ? (MOTIFS.find(m=>m.id === data.motif)?.name ?? data.motif) : ""}</span></div>
          <div className="row"><span className="k">For</span><span className="v">{data.name}</span></div>
          <div className="row"><span className="k">By</span><span className="v">{data.audience === "self" ? "Myself" : data.byName}</span></div>
          <div className="row"><span className="k">Occasion</span><span className="v">{data.occasion || data.customOccasion}</span></div>
          <div className="row"><span className="k">Note</span><span className="v">{data.note.trim() ? "Included" : "None"}</span></div>
          <div className="row"><span className="k">Includes</span><span className="v">Artwork + Gift Card + UDID</span></div>
          <div className="row total"><span>Total</span><span>$8.99 CAD</span></div>
        </div>
        <div className="stripe-box">
          <div className="stripe-mark">S</div>
          <div className="text">
            <div className="t">Pay with Stripe</div>
            <div className="s">Secure checkout · Cards, Apple Pay, Google Pay</div>
          </div>
          <Icon.Lock />
        </div>
        <div className="form-foot">
          <button className="btn-back" onClick={back} disabled={paying}>← Back</button>
          <button className="btn-next cta-yellow" onClick={pay} disabled={paying}>
            {paying ? "Processing…" : "Pay $8.99"} {!paying && <Icon.Arrow/>}
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="panel">
      <div className="panel-head">
        <span className="step-label">Step {step+1} of {totalSteps}</span>
        <div className="progress">
          {Array.from({length: totalSteps}).map((_, i) => (
            <span key={i} className={i < step ? "done" : i === step ? "active" : ""}/>
          ))}
        </div>
      </div>

      {step === 0 && (
        <div className="fade-up" key="s0">
          <h2 className="q-title">Who is this <em>for</em>?</h2>
          <p className="q-help">A gift sets a heartfelt tone. For yourself, we'll keep it intimate.</p>
          <div className="radio-row">
            <div className={"radio-card" + (data.audience === "gift" ? " selected" : "")} onClick={()=>set("audience","gift")}>
              <span className="icon"><Icon.Gift/></span>
              <span className="title">It's a gift</span>
              <span className="sub">For someone else.</span>
            </div>
            <div className={"radio-card" + (data.audience === "self" ? " selected" : "")} onClick={()=>set("audience","self")}>
              <span className="icon"><Icon.Heart/></span>
              <span className="title">For me</span>
              <span className="sub">A moment of my own.</span>
            </div>
          </div>
        </div>
      )}

      {step === 1 && (
        <div className="fade-up" key="s1">
          <h2 className="q-title">{data.audience === "self" ? <>What is <em>your name</em>?</> : <>What are <em>the names</em>?</>}</h2>
          <p className="q-help">{data.audience === "self" ? "Your name becomes the For field, and By will read Myself." : "Your name becomes the By field. Their name becomes the For field."}</p>
          {data.audience === "gift" ? (
            <>
              <label className="field-label">Your name</label>
              <input className="field" placeholder="Your name for the By field" value={data.byName} onChange={e=>set("byName", e.target.value)} autoFocus maxLength={28}/>
              <label className="field-label" style={{marginTop:16}}>Recipient name</label>
              <input className="field" placeholder="Recipient's name" value={data.name} onChange={e=>set("name", e.target.value)} maxLength={28}/>
            </>
          ) : (
            <>
              <label className="field-label">Your name</label>
              <input className="field" placeholder="Your name" value={data.name} onChange={e=>set("name", e.target.value)} autoFocus maxLength={28}/>
            </>
          )}
        </div>
      )}

      {step === 2 && (
        <div className="fade-up" key="s2">
          <h2 className="q-title">What's the <em>occasion</em>?</h2>
          <p className="q-help">Tap one, or write your own.</p>
          <div className="chips">
            {OCCASIONS.map(o => (
              <button key={o} className={"chip" + (data.occasion === o ? " selected" : "")} onClick={()=>{ set("occasion", o); set("customOccasion",""); }}>
                {o}
              </button>
            ))}
            <div className={"chip chip-other" + (data.customOccasion ? " selected" : "")}>
              <input
                placeholder="+ Write your own"
                value={data.customOccasion}
                onChange={e=>{ set("customOccasion", e.target.value); set("occasion",""); }}
              />
            </div>
          </div>
        </div>
      )}

      {step === 3 && (
        <div className="fade-up" key="s3">
          <h2 className="q-title">When did it <em>happen</em>?</h2>
          <p className="q-help">Either the original date, or this year's celebration.</p>
          <div className="toggle-row">
            <button className={data.dateMode === "anniversary" ? "active" : ""} onClick={()=>set("dateMode","anniversary")}>Original date</button>
            <button className={data.dateMode === "this-year" ? "active" : ""} onClick={()=>set("dateMode","this-year")}>This year's date</button>
          </div>
          <div>
            {data.dateMode === "this-year" ? (
              <>
                <label className="field-label">Month & day</label>
                <input className="field" type="date" value={data.monthDay ? `${new Date().getFullYear()}-${data.monthDay}` : ""} onChange={e=>{ const v=e.target.value; if(v){ const p=v.split("-"); set("monthDay", `${p[1]}-${p[2]}`); } else set("monthDay",""); }}/>
              </>
            ) : (
              <>
                <label className="field-label">Original date</label>
                <input className="field" type="date" value={data.origDate} onChange={e=>set("origDate", e.target.value)}/>
              </>
            )}
          </div>
        </div>
      )}

      {step === 4 && (
        <div className="fade-up" key="s4">
          <h2 className="q-title">Select <em>their graphic</em>.</h2>
          <p className="q-help">Each gift card is composed uniquely from the moment's coordinates. Never repeated.</p>
          <div className="motif-grid">
            {MOTIFS.map(m => {
              const T = m.Thumb;
              return (
                <div key={m.id} className={"motif" + (data.motif === m.id ? " selected" : "")} onClick={()=>set("motif", m.id)}>
                  <div className="thumb"><T size={48}/></div>
                  <div>
                    <div className="name">{m.name}</div>
                    <div className="desc">{m.desc}</div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {step === 5 && (
        <div className="fade-up" key="s5">
          <h2 className="q-title">Add a <em>note</em>?</h2>
          <p className="q-help">Optional. Write something personal and choose the date it should be signed.</p>
          <label className="field-label">Personal note</label>
          <textarea
            className="field note-field"
            placeholder="A short note for them..."
            value={data.note}
            onChange={e=>set("note", e.target.value)}
            maxLength={180}
            autoFocus
          />
          <div className="note-count">{data.note.length}/180</div>
          <label className="field-label" style={{marginTop:16}}>Signature name</label>
          <input className="field" placeholder={data.audience === "self" ? (data.name || "Your name") : (data.byName || "Your name")} value={data.signName} onChange={e=>set("signName", e.target.value)} maxLength={28}/>
          <label className="field-label" style={{marginTop:16}}>Signature date</label>
          <input className="field" type="date" value={data.signDate} onChange={e=>set("signDate", e.target.value || todayInput())}/>
        </div>
      )}

      {step === 6 && (
        <div className="fade-up" key="s6">
          <h2 className="q-title">Where should we <em>send it</em>?</h2>
          <p className="q-help">Artwork, gift card and your Unique Design ID, straight to your inbox.</p>
          <label className="field-label">Email address</label>
          <input className="field" type="email" inputMode="email" autoComplete="email" placeholder="you@example.com" value={data.email} onChange={e=>set("email", e.target.value)} autoFocus/>
          {data.audience === "gift" && (
            <div style={{marginTop:20}}>
              <label className="field-label">How should we deliver it?</label>
              <div className="radio-row" style={{marginTop:8}}>
                <div className={"radio-card" + (data.deliveryMode === "direct" ? " selected" : "")} onClick={()=>set("deliveryMode","direct")} style={{fontSize:13}}>
                  <span className="title" style={{fontSize:13}}>Send to {data.name || "them"}</span>
                  <span className="sub">We send it directly. You'll get a copy.</span>
                </div>
                <div className={"radio-card" + (data.deliveryMode === "self-forward" ? " selected" : "")} onClick={()=>set("deliveryMode","self-forward")} style={{fontSize:13}}>
                  <span className="title" style={{fontSize:13}}>Send to me</span>
                  <span className="sub">You forward it yourself.</span>
                </div>
              </div>
            </div>
          )}
          <div style={{display:"flex", gap:8, alignItems:"center", marginTop:18}}>
            <input type="checkbox" id="updates-chk" checked={data.updates} onChange={e=>set("updates", e.target.checked)} style={{width:15, height:15, accentColor:"#ffc30f", cursor:"pointer", flexShrink:0}}/>
            <label htmlFor="updates-chk" style={{fontSize:12, color:"var(--ink-mute)", cursor:"pointer", lineHeight:1.4}}>Keep me updated once in a while. No newsletters, just a note when something new happens.</label>
          </div>
          <div style={{display:"flex", gap:8, alignItems:"center", marginTop: 10, color: "var(--ink-mute)", fontSize: 12}}>
            <Icon.Mail /> <span>Delivery in under 2 minutes after payment.</span>
          </div>
        </div>
      )}

      <div className="form-foot">
        <button className="btn-back" onClick={back} disabled={step === 0}>← Back</button>
        <button className={"btn-next" + (step === 6 ? " cta-yellow" : "")} disabled={!canNext()} onClick={next}>
          {step === 6 ? "Continue to payment" : step === 5 ? "Skip or continue" : "Continue"} <Icon.Arrow/>
        </button>
      </div>
    </div>
  );
}

function ArtworkGraphic({ motif, size = 132, seed = 1, phase = 0.5, date = null, occasion = "" }) {
  const M = MOTIFS.find(m => m.id === motif);
  return M ? (
    <M.Thumb size={size} stroke="currentColor" seed={seed} phase={phase} date={date} occasion={occasion}/>
  ) : (
    <svg width={size} height={size} viewBox="0 0 140 140">
      <rect x="14" y="14" width="112" height="112" fill="none" stroke="currentColor" strokeWidth="0.6" strokeDasharray="3 4" opacity="0.4"/>
      <text x="70" y="76" textAnchor="middle" fontFamily="Fraunces, serif" fontStyle="italic" fontSize="14" fill="currentColor" opacity="0.5">pick a graphic</text>
    </svg>
  );
}

/* ===== Preview panel with 3 tabs ===== */
function PreviewPanel({ display, motif, audience, step }) {
  const [tab, setTab] = useState("artwork");
  return (
    <div className="preview">
      <div className="preview-tabs">
        <button className={tab==="artwork" ? "active":""} onClick={()=>setTab("artwork")}>Artwork</button>
        <button className={tab==="gift" ? "active":""} onClick={()=>setTab("gift")}>Gift Card</button>
        <button className={tab==="udid" ? "active":""} onClick={()=>setTab("udid")}>UDID</button>
      </div>
      <div className="stage">
        {tab === "artwork" && <Artwork display={display} motif={motif} watermark={step < 8}/>}
        {tab === "gift" && <GiftCard display={display} motif={motif} audience={audience} watermark={step < 8}/>}
        {tab === "udid" && <UDID display={display} motif={motif} watermark={step < 8}/>}
      </div>
      <div className="preview-caption">
        {step < 7 ? "Updates as you fill in the form" : step === 7 ? "Your artwork, gift card, and UDID, ready to mint" : "Sent ✶ Check your inbox"}
      </div>
    </div>
  );
}

/* ===== (1) Artwork ===== */
function Artwork({ display, motif, watermark }) {
  const fact = motif ? getMotifFact(motif, display.date, display.date ? fmtDate(display.date) : null) : null;
  return (
    <div className={"artwork-card" + (watermark ? " watermarked" : "")}>
      <ArtworkGraphic motif={motif} size={220} seed={display.seed} phase={display.moonPhase} date={display.date} occasion={display.occasion}/>
      {fact && (
        <div style={{position:"absolute", bottom:30, left:"50%", transform:"translateX(-50%)", zIndex:2, whiteSpace:"nowrap", textAlign:"center"}}>
          <span style={{fontFamily:"'Fraunces',serif", fontStyle:"italic", fontSize:10, color:"#111", opacity:0.45}}>{fact}</span>
        </div>
      )}
      <div style={{position:"absolute", bottom:12, left:"50%", transform:"translateX(-50%)", display:"flex", alignItems:"center", gap:6, zIndex:2, whiteSpace:"nowrap"}}>
        <span style={{fontFamily:"'JetBrains Mono',monospace", fontSize:8, letterSpacing:"0.12em", color:"#111", opacity:0.4}}>#{display.id}</span>
        <img src={LOGO_SRC} alt="" style={{width:12, height:12, borderRadius:3, objectFit:"cover", opacity:0.5}}/>
        <span style={{fontFamily:"'JetBrains Mono',monospace", fontSize:8, letterSpacing:"0.06em", color:"#111", opacity:0.4}}>www.once.graphics</span>
      </div>
    </div>
  );
}

/* ===== (2) Gift card ===== */
function GiftCard({ display, motif, audience, watermark }) {
  const note = display.note || <>You will forever own this moment of <em>{display.occasion?.toLowerCase?.()||"your moment"}</em>. It happened to you, <em>once</em>, and to no one else, ever again.</>;
  const dateLabel = fmtGiftDate(display.date);
  const signatureName = display.signName;
  return (
    <div className={"giftcard" + (watermark ? " watermarked" : "")}>
      <div className="gc-design-panel">
        <div className="gc-art">
          <div className="gc-artwork">
            <ArtworkGraphic motif={motif} seed={display.seed} phase={display.moonPhase} date={display.date} occasion={display.occasion}/>
            <LogoImage className="design-logo" alt=""/>
          </div>
        </div>
        <div className="gc-design-meta">
          <div className="for">Minted once for</div>
          <div className="name">{display.recipient}</div>
          <div className="occasion">{display.occasion}</div>
          <div className="occasion date-line">{dateLabel}</div>
        </div>
      </div>
      <div className="gc-note-panel">
        <div className="gc-head">
          <span className="mark"><LogoImage className="mini-logo" alt=""/><span style={{fontFamily:"'Fraunces',serif", fontStyle:"italic", fontSize:13, letterSpacing:0, textTransform:"none"}}>Once Graphics</span></span>
          <span>№ {display.id}</span>
        </div>
        <div className="gc-body">
          <div className="gc-greeting gc-note-text">{note}</div>
          <div className="gc-signoff">
            <div>{signatureName},</div>
            <div>{fmtGiftDate(display.signed)}</div>
          </div>
        </div>
        <div className="gc-foot">
          <span>Minted with love at Once ©<br/>www.once.graphics</span>
        </div>
      </div>
    </div>
  );
}

/* ===== motif fact for UDID back + artwork ===== */
function getMotifFact(motif, date, dateStr) {
  const when = dateStr || "this day";
  if (motif === "lunation") {
    if (!date) return `On ${when}, the moon kept its secrets.`;
    const lunarCycle = 29.53059;
    const knownNew = new Date(2000, 0, 6).getTime();
    const days = (date.getTime() - knownNew) / 86400000;
    const phase = ((days % lunarCycle) + lunarCycle) % lunarCycle;
    const pct = Math.round(50 - 50 * Math.cos((phase / lunarCycle) * Math.PI * 2));
    return `On ${when}, lunar illumination was ${pct}%.`;
  }
  if (motif === "orrery") {
    if (!date) return `On ${when}, the planets kept their courses.`;
    if (typeof Astronomy !== "undefined") {
      try {
        const v = Astronomy.HelioVector("Jupiter", date);
        const angle = Math.round(((Math.atan2(v.y, v.x) * 180 / Math.PI) + 360) % 360);
        return `On ${when}, Jupiter stood at ${angle}° in its orbit.`;
      } catch(e) {}
    }
    const dayOfYear = Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000);
    const angle = Math.round(((dayOfYear * 0.0831) % 360 + 360) % 360);
    return `On ${when}, Jupiter stood at ${angle}° in its orbit.`;
  }
  if (motif === "solarsign") {
    if (!date) return `On ${when}, the sun held its place.`;
    const SIGNS = ["Capricorn","Aquarius","Pisces","Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius"];
    let longitude = null;
    if (typeof Astronomy !== "undefined") {
      try {
        const geo = Astronomy.GeoVector("Sun", date, false);
        const ecl = Astronomy.Ecliptic(geo);
        longitude = ecl.elon;
      } catch(e) {}
    }
    if (longitude === null) {
      const dayOfYear = Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000);
      longitude = ((dayOfYear - 10) / 365 * 360 + 360) % 360;
    }
    const sign = SIGNS[Math.floor(longitude / 30) % 12];
    const deg = Math.round(longitude % 30);
    return `On ${when}, the sun sat at ${deg}° ${sign}.`;
  }
  if (motif === "starfield") {
    if (!date) return `On ${when}, the stars kept their watch.`;
    const SEASON_STARS = ["Orion","Virgo","Scorpius","Pegasus"];
    const season = Math.floor(((date.getMonth() + 1) % 12) / 3);
    return `On ${when}, ${SEASON_STARS[season]} ruled the midnight sky.`;
  }
  return null;
}

/* ===== (3) UDID — like a passport for the gift card ===== */
function UDID({ display, motif, watermark }) {
  const M = MOTIFS.find(m => m.id === motif);
  const fact = getMotifFact(motif, display.date, display.date ? fmtDate(display.date) : null);
  return (
    <div style={{width:"min(310px, 86%)", display:"flex", flexDirection:"column", gap:14}}>
      <div className={"udid" + (watermark ? " watermarked" : "")} style={{width:"100%"}}>
        <div className="udid-pattern"/>
        <div className="udid-head">
          <div className="label">UNIQUE<br/>DESIGN ID</div>
          <div className="id">#{display.id}</div>
        </div>
        <div className="udid-body">
          <div className="udid-photo">
            {M ? <M.Thumb size={68} stroke="oklch(0.22 0.05 80)" seed={display.seed} phase={display.moonPhase} date={display.date} occasion={display.occasion}/> : null}
          </div>
          <div className="udid-fields">
            <div className="udid-field"><span className="k">To remember:</span><span className="v">{display.date ? fmtDate(display.date) : ""}</span></div>
            <div className="udid-field"><span className="k">For</span><span className="v">{display.recipient}</span></div>
            <div className="udid-field"><span className="k">By</span><span className="v" style={{fontSize:11, fontFamily:"'Inter', sans-serif", fontStyle:"normal", letterSpacing:"0.02em"}}>{display.buyer}</span></div>
          </div>
        </div>
        <div className="udid-foot">
          <img src={LOGO_SRC} alt="Once" style={{width:28, height:28, borderRadius:7, objectFit:"cover", boxShadow:"0 4px 12px -4px rgba(0,0,0,0.5)"}}/>
          <span className="stamp">MINTED · {fmtDateShort(display.issued)}<br/>www.once.graphics</span>
        </div>
      </div>
      {/* UDID back */}
      <div className={"udid" + (watermark ? " watermarked" : "")} style={{width:"100%", alignItems:"center", justifyContent:"center", gap:14}}>
        <div className="udid-pattern"/>
        <img src={LOGO_SRC} alt="Once" style={{width:38, height:38, borderRadius:10, objectFit:"cover", boxShadow:"0 6px 18px -6px rgba(0,0,0,0.6)", position:"relative", zIndex:2}}/>
        <p style={{fontFamily:"'Inter',sans-serif", fontWeight:500, fontSize:9, letterSpacing:"0.06em", color:"oklch(0.65 0.08 88)", textAlign:"center", margin:0, lineHeight:1.7, maxWidth:"76%", position:"relative", zIndex:2}}>{fact}</p>
      </div>
    </div>
  );
}

/* ===== Success ===== */
function Success({ email, onAgain }) {
  return (
    <div className="success">
      <div className="success-mark"><Icon.Check/></div>
      <h3>Minted. Once, and only once.</h3>
      <p>Your artwork, gift card, and Unique Design ID are heading to <b>{email}</b>. Watch out for an email from <b>design@once.graphics</b>.</p>
      <button className="btn-next" onClick={onAgain}>Make another <Icon.Arrow/></button>
    </div>
  );
}

/* ===== Features ===== */
function Features() {
  const items = [
    { n:"01", t:"Tell us the moment", d:"Who, when, the occasion, and which graphic speaks to it. About 60 seconds." },
    { n:"02", t:"We compose it", d:"Our system draws the gift card from the coordinates of that exact moment. Never the same twice." },
    { n:"03", t:"Minted, signed, sent", d:"You receive the Artwork, Gift Card, and a Unique Design ID. Proof it's yours alone." },
  ];
  return (
    <section className="features" id="how">
      <div className="features-head">
        <h2>Three steps. <em>One moment, forever.</em></h2>
        <p>Every Once Graphics card is composed from the unique coordinates of your moment: date, occasion, name. No two are ever alike.</p>
      </div>
      <div className="feat-grid">
        {items.map(i => (
          <div className="feat" key={i.n}>
            <div className="num">{i.n}</div>
            <h4>{i.t}</h4>
            <p>{i.d}</p>
          </div>
        ))}
      </div>
    </section>
  );
}

/* ===== Pricing ===== */
function Pricing({ onCreate }) {
  return (
    <section className="pricing" id="pricing">
      <div className="price-card">
        <div className="price-tag">ONCE GRAPHICS · Single Mint</div>
        <div className="price-amount"><span className="currency">CA$</span>8<em>.</em><span className="cents">99</span></div>
        <div style={{fontSize:12, color:"var(--ink-mute)", marginTop:4, fontFamily:"'JetBrains Mono',monospace", letterSpacing:"0.04em", position:"relative"}}>+ GST</div>
        <div className="price-sub">One unique artwork, gift card, and Unique Design ID. That's it.</div>
        <div className="price-includes">
          <div className="incl-item">
            <div className="glyph"><Icon.Heart/></div>
            <div className="t">The Artwork</div>
            <div className="s">Hi-res, print-ready</div>
          </div>
          <div className="incl-item">
            <div className="glyph"><Icon.Gift/></div>
            <div className="t">The Gift Card</div>
            <div className="s">Personalised note</div>
          </div>
          <div className="incl-item">
            <div className="glyph"><Icon.Stamp/></div>
            <div className="t">The UDID</div>
            <div className="s">Your card's passport</div>
          </div>
        </div>
        <button className="btn-primary" onClick={onCreate}>Mint mine for $8.99</button>
      </div>
    </section>
  );
}

/* ===== App ===== */
function App() {
  const generatorRef = useRef(null);
  const scrollToGenerator = () => {
    const el = generatorRef.current;
    if (!el) return;
    const top = el.getBoundingClientRect().top + window.scrollY - 24;
    window.scrollTo({top, behavior:"smooth"});
  };
  return (
    <>
      <Nav onCreate={scrollToGenerator}/>
      <Hero onStart={scrollToGenerator}/>
      <Generator scrollRef={generatorRef}/>
      <Features/>
      <Pricing onCreate={scrollToGenerator}/>
      <footer>
        <div className="row">
          <span>© 2026 Once Graphics · Made with care</span>
          <span>design@once.graphics</span>
        </div>
      </footer>
    </>
  );
}

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