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

const CURRENCIES = {
  AU: { prefix: "$",   code: "AUD" },
  CA: { prefix: "CA$", code: "CAD" },
  US: { prefix: "$",   code: "USD" },
  GB: { prefix: "£",   code: "GBP" },
};
const DEFAULT_CURRENCY = CURRENCIES.CA;

function analyticsClientId() {
  try {
    const key = "once_funnel_id";
    let id = localStorage.getItem(key);
    if (!id) {
      id = `once_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
      localStorage.setItem(key, id);
    }
    return id;
  } catch (e) {
    return `once_${Date.now().toString(36)}`;
  }
}

function trackEvent(eventName, metadata = {}) {
  try {
    const payload = JSON.stringify({
      clientId: analyticsClientId(),
      eventName,
      step: Number.isInteger(metadata.step) ? metadata.step : null,
      mode: metadata.mode || "",
      path: window.location.pathname + window.location.search,
      metadata,
    });
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/admin/track', new Blob([payload], { type: 'application/json' }));
      return;
    }
    fetch('/api/admin/track', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: payload,
      keepalive: true,
    }).catch(() => {});
  } catch (e) {}
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function waitForImages(node) {
  const images = Array.from(node?.querySelectorAll?.("img") || []);
  await Promise.all(images.map((image) => {
    if (image.complete && image.naturalWidth) return Promise.resolve();
    if (image.decode) return image.decode().catch(() => {});
    return new Promise((resolve) => {
      image.addEventListener("load", resolve, { once: true });
      image.addEventListener("error", resolve, { once: true });
    });
  }));
}

async function captureNodePng(node, widthPx) {
  if (!window.htmlToImage) throw new Error("The PNG exporter did not load");
  if (document.fonts?.ready) await document.fonts.ready;
  await waitForImages(node);
  await sleep(80);
  const contentRect = (node.firstElementChild || node).getBoundingClientRect();
  const pixelRatio = Math.max(1, widthPx / Math.max(contentRect.width, 1));
  const options = {
    pixelRatio,
    cacheBust: true,
    backgroundColor: "transparent",
    style: { transform: "none" },
  };
  const blob = await window.htmlToImage.toBlob(node, options);
  if (blob && blob.size) return blob;
  const dataUrl = await window.htmlToImage.toPng(node, options);
  return fetch(dataUrl).then(response => response.blob());
}

async function uploadOrderAsset({ orderId, token, type, blob }) {
  const params = new URLSearchParams({ orderId, type });
  const response = await fetch(`/api/order-assets?${params.toString()}`, {
    method: "POST",
    headers: {
      "Content-Type": "image/png",
      "X-Once-Asset-Token": token,
    },
    body: blob,
  });
  const json = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(json.error || `Could not save ${type} PNG`);
  return json;
}

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 idRef = useRef(null);
  if (!idRef.current) idRef.current = `mc${Math.round(phase*1000)+seed}-${Math.random().toString(36).slice(2, 8)}`;
  const id = idRef.current;
  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";
const UDID_ICON_SRC = "icon.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>),
  Eye: (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="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>),
  EyeOff: (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="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></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);
}

/* ===== WizardBar — sticky top bar in create mode ===== */
function WizardBar({ step, onHome, back, next, canNext, paying, pay, confirmPromo, promoApplied, currency }) {
  const isFirst    = step === 0;
  const isCheckout = step === 7;
  const isSuccess  = step >= 8;
  const handleBack = () => isFirst ? onHome() : back();
  const handleNext = () => isCheckout ? (promoApplied ? confirmPromo() : pay()) : next();
  const nextLabel  = paying ? "Processing…" : isCheckout ? (promoApplied ? "Confirm order" : `Pay ${currency.prefix}8.99`) : "Continue →";
  return (
    <div className="wizard-bar">
      {/* Left — mobile: Back; desktop: empty spacer */}
      <div className="wiz-left">
        <button className="wiz-back" onClick={handleBack} disabled={paying}>
          {isFirst ? "← Home" : "← Back"}
        </button>
      </div>
      {/* Center: logo — always */}
      <a href="#" className="wiz-brand" onClick={e=>{e.preventDefault(); onHome();}}>
        <img src={LOGO_SRC} alt="" style={{width:28,height:28,borderRadius:7,objectFit:"cover"}}/>
        <span>Once Graphics</span>
      </a>
      {/* Right — desktop: Home; mobile: Continue/Pay */}
      <div className="wiz-right">
        <button className="wiz-home" onClick={onHome}>← Home</button>
        {!isSuccess && (
          <button
            className={"wiz-next" + (canNext() && !paying ? " ready" : "")}
            onClick={handleNext}
            disabled={!canNext() || paying}
          >{nextLabel}</button>
        )}
      </div>
    </div>
  );
}

/* ===== Nav ===== */
function Nav({ onCreate, onHome, onHow, onPricing, backMode = false, hideOnMobile = false, showCreateAction = false }) {
  const goHome = onHome || onCreate;
  return (
    <div className={"nav-wrap" + (hideOnMobile ? " nav-hide-mobile" : "")}>
      <nav className={"nav" + (showCreateAction ? " nav-page" : "")}>
        <a href="#" className="brand" onClick={(e)=>{e.preventDefault(); goHome();}}>
          <span className="brand-mark"><LogoImage alt=""/></span>
          <span className="brand-name">Once Graphics</span>
        </a>
        {!backMode ? (
          <div className="nav-links">
            <a className="nav-link nav-how" href="#how" onClick={(e)=>{e.preventDefault(); onHow?.();}}>How it works</a>
            <a className="nav-link" href="#pricing" onClick={(e)=>{e.preventDefault(); onPricing?.();}}>Pricing</a>
          </div>
        ) : showCreateAction ? (
          <div className="nav-actions">
            <button className="nav-link nav-home" onClick={goHome}>← Home</button>
            <button className="nav-cta nav-design" onClick={onCreate}>Design my eCard</button>
          </div>
        ) : <div style={{flex:1}}/>}
        {!showCreateAction && (
          <button className={"nav-cta" + (!backMode ? " nav-mint" : "")} onClick={backMode ? goHome : onCreate}>
            {backMode ? "← Home" : "Design my eCard"}
          </button>
        )}
      </nav>
    </div>
  );
}

/* ===== Hero ===== */
function Hero({ onStart, onHow }) {
  return (
    <section>
      <div className="hero">

        <h1 className="hero-title fade-up" style={{animationDelay:'.05s'}}>
          <span className="accent"><span className="accent-o">O</span>nce,</span><br/>it happened
        </h1>
        <p className="lede fade-up" style={{animationDelay:'.1s'}}>
          Because every moment happens once, it deserves more than a card. Design a one-of-one artwork from the exact date, shaped by the universe as it was that day.
        </p>
        <div className="hero-cta-row fade-up" style={{animationDelay:'.15s'}}>
          <button className="btn-primary" onClick={onStart}>Design my eCard</button>
          <button className="btn-ghost" onClick={onHow}>How it works →</button>
        </div>
      </div>
    </section>
  );
}

/* ===== Generator ===== */
function Generator({ onBack, currency = DEFAULT_CURRENCY }) {
  const [step, setStep] = useState(0);
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewNudge, setPreviewNudge] = useState(false);
  const previewNudgeTimer = useRef(null);
  const [data, setData] = useState({
    audience: null, name: "", byName: "", occasion: "", motif: "",
    dateMode: "anniversary", monthDay: "", origDate: "",
    note: "", signDate: todayInput(), signName: "", email: "", recipientEmail: "", customOccasion: "",
    deliveryMode: "direct", updates: true,
  });
  const [paying, setPaying] = useState(false);
  const [payError, setPayError] = useState("");
  const [promoCode, setPromoCode] = useState("");
  const [promoApplied, setPromoApplied] = useState(false);
  const [promoData, setPromoData] = useState(null);
  const [promoError, setPromoError] = useState("");
  const [promoLoading, setPromoLoading] = useState(false);
  const [captureDisplay, setCaptureDisplay] = useState(null);

  const triggerPreviewNudge = () => {
    if (previewNudgeTimer.current) clearTimeout(previewNudgeTimer.current);
    setPreviewNudge(false);
    requestAnimationFrame(() => {
      setPreviewNudge(true);
      previewNudgeTimer.current = setTimeout(() => setPreviewNudge(false), 760);
    });
  };

  useEffect(() => () => {
    if (previewNudgeTimer.current) clearTimeout(previewNudgeTimer.current);
  }, []);

  useEffect(() => {
    if (step === 5) {
      const t = setTimeout(() => setPreviewOpen(true), 350);
      return () => clearTimeout(t);
    }
  }, [step]);
  useEffect(() => {
    trackEvent('wizard_step', {
      step,
      audience: data.audience || '',
      dateMode: data.dateMode,
      deliveryMode: data.deliveryMode,
      motif: data.motif || '',
      hasOccasion: Boolean(data.occasion || data.customOccasion),
      hasNote: Boolean(data.note.trim()),
    });
  }, [step]);
  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 === 7) return true;
    if (step === 6) {
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email.trim())) return false;
      if (data.audience === "gift" && data.deliveryMode === "direct")
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.recipientEmail.trim());
      return true;
    }
    return false;
  };
  const next = () => {
    if (!canNext()) return;
    trackEvent(step === 6 ? 'checkout_started' : 'wizard_next', { step, toStep: step + 1 });
    if (step === 6) { setStep(7); return; }
    setStep(s => Math.min(8, s + 1));
    triggerPreviewNudge();
  };
  const back = () => {
    trackEvent('wizard_back', { step, toStep: Math.max(0, step - 1) });
    setStep(s => Math.max(0, s - 1));
  };
  const reset = () => { setStep(0); setData({ audience:null, name:"", byName:"", occasion:"", motif:"", dateMode:"anniversary", monthDay:"", origDate:"", note:"", signDate:todayInput(), signName:"", email:"", recipientEmail:"", customOccasion:"", deliveryMode:"direct", updates:false }); };

  const orderPayload = (extra = {}) => ({
    ...extra,
    audience: data.audience,
    email: data.email,
    recipientEmail: data.recipientEmail,
    name: data.name,
    byName: data.audience === 'self' ? data.name : data.byName,
    occasion: data.occasion || data.customOccasion,
    motif: data.motif,
    dateMode: data.dateMode,
    monthDay: data.monthDay,
    origDate: data.origDate,
    deliveryMode: data.deliveryMode,
    currency: currency.code,
    note: data.note,
    signDate: data.signDate,
    signName: data.signName,
    updates: data.updates,
  });

  const saveOrderPngs = async ({ orderId, assetToken, designId }) => {
    if (!orderId || !assetToken) throw new Error("Missing design asset upload details");
    const exportDisplay = {
      ...display,
      id: designId || display.id,
      issued: new Date(),
    };
    setCaptureDisplay(exportDisplay);
    await sleep(0);
    await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));

    const captures = [
      { type: "artwork", id: "once-capture-artwork", width: 1800 },
      { type: "ecard", id: "once-capture-ecard", width: 2000 },
      { type: "udid", id: "once-capture-udid", width: 1500 },
    ];

    try {
      for (const capture of captures) {
        const node = document.getElementById(capture.id);
        if (!node) throw new Error(`Missing ${capture.type} preview`);
        const blob = await captureNodePng(node, capture.width);
        await uploadOrderAsset({
          orderId,
          token: assetToken,
          type: capture.type,
          blob,
        });
      }
    } finally {
      setCaptureDisplay(null);
    }
  };

  const pay = async () => {
    trackEvent('payment_clicked', {
      step,
      audience: data.audience || '',
      deliveryMode: data.deliveryMode,
      motif: data.motif || '',
    });
    setPaying(true);
    try {
      const res = await fetch('/api/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(orderPayload()),
      });
      const json = await res.json();
      if (json.error) throw new Error(json.error);
      await saveOrderPngs({
        orderId: json.orderId,
        assetToken: json.assetToken,
        designId: json.designId,
      });
      window.location.href = json.url;
    } catch (err) {
      console.error('Checkout error:', err.message);
      setPaying(false);
      setPayError(err.message || 'Something went wrong starting payment. Please try again.');
    }
  };

  const applyPromo = async () => {
    const code = promoCode.trim().toUpperCase();
    if (!code) return;
    setPromoLoading(true);
    setPromoError("");
    try {
      const res = await fetch('/api/promo/validate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code }),
      });
      const json = await res.json();
      if (json.error) throw new Error(json.error);
      setPromoData(json.promo);
      setPromoApplied(true);
    } catch (err) {
      setPromoError(err.message);
    } finally {
      setPromoLoading(false);
    }
  };

  const confirmPromo = async () => {
    setPaying(true);
    try {
      const res = await fetch('/api/create-promo-order', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(orderPayload({
          promoCode: promoData.code,
        })),
      });
      const json = await res.json();
      if (json.error) throw new Error(json.error);
      await saveOrderPngs({
        orderId: json.orderId,
        assetToken: json.assetToken,
        designId: json.designId,
      });
      setStep(8);
      setPaying(false);
    } catch (err) {
      console.error('Promo order error:', err.message);
      setPayError(err.message || 'Something went wrong. Please try again.');
      setPaying(false);
    }
  };

  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() || "—")),
      dateMode: data.dateMode,
    };
  }, [data]);

  return (
    <section className="generator-section" id="create">
      {/* Mobile: wizard bar replaces nav + form-foot */}
      <WizardBar
        step={step} totalSteps={totalSteps}
        onHome={onBack} back={back} next={next}
        canNext={canNext} paying={paying} pay={pay}
        confirmPromo={confirmPromo} promoApplied={promoApplied}
        currency={currency}
      />
      <div className="generator">
        <FormPanel
          step={step} totalSteps={totalSteps}
          data={data} set={set}
          canNext={canNext} next={next} back={back}
          paying={paying} pay={pay} reset={reset}
          currency={currency} payError={payError}
          promoCode={promoCode} setPromoCode={setPromoCode}
          promoApplied={promoApplied} promoData={promoData}
          promoError={promoError} promoLoading={promoLoading}
          applyPromo={applyPromo} confirmPromo={confirmPromo}
        />
        <PreviewPanel display={display} motif={data.motif} audience={data.audience} step={step}/>
      </div>
      {step < 7 && (
        <button className={"preview-float-btn" + (step >= 1 && step <= 3 && !previewOpen ? " pulse" : "") + (previewNudge && !previewOpen ? " nudge" : "")} onClick={()=>setPreviewOpen(o=>!o)} aria-label="Preview design">
          {previewOpen ? <Icon.EyeOff/> : <Icon.Eye/>}
          <span className="preview-float-label">{previewOpen ? "Close" : "See design"}</span>
        </button>
      )}
      {previewOpen && <div className="preview-backdrop" onClick={()=>setPreviewOpen(false)}/>}
      <div className={"preview-sheet"+(previewOpen?" open":"")}>
        <div className="preview-sheet-handle"/>
        <PreviewPanel display={display} motif={data.motif} audience={data.audience} step={step}/>
      </div>
      <DesignCaptureStage display={captureDisplay || display} motif={data.motif} audience={data.audience}/>
    </section>
  );
}

/* ===== Form panel ===== */
function FormPanel({ step, totalSteps, data, set, canNext, next, back, paying, pay, reset, currency = DEFAULT_CURRENCY, payError = "",
  promoCode = "", setPromoCode = ()=>{}, promoApplied = false, promoData = null, promoError = "", promoLoading = false,
  applyPromo = ()=>{}, confirmPromo = ()=>{} }) {
  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 eCard is locked in. Pay {currency.prefix}8.99 and we'll send everything to your inbox.</p>
        {payError && <p style={{color:"#c0392b", fontSize:13, marginBottom:12, padding:"10px 14px", background:"#fff0f0", borderRadius:10}}>{payError}</p>}
        <div className="checkout-summary">
          <div className="row"><span className="k">Once eCard</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 + eCard + UDID</span></div>
          <div className="row total"><span>Total</span><span>{promoApplied && promoData?.discountPercent === 100 ? <span style={{textDecoration:"line-through",opacity:.5,marginRight:8}}>{currency.prefix}8.99</span> : null}{promoApplied && promoData?.discountPercent === 100 ? "Free" : `${currency.prefix}8.99 ${currency.code}`}</span></div>
        </div>
        <div style={{marginBottom:16}}>
          {promoApplied ? (
            <div style={{display:"flex",alignItems:"center",gap:10,padding:"11px 14px",background:"oklch(0.97 0.04 130)",border:"1px solid oklch(0.82 0.10 130)",borderRadius:12,fontSize:14}}>
              <span style={{fontSize:16}}>✓</span>
              <span><b>{promoData?.code}</b> — {promoData?.discountPercent}% off applied</span>
              <button style={{marginLeft:"auto",background:"transparent",border:"none",color:"var(--ink-soft)",cursor:"pointer",fontSize:13}} onClick={()=>{setPromoCode(""); window.location.reload();}}>Remove</button>
            </div>
          ) : (
            <div style={{display:"flex",gap:8}}>
              <input className="field" style={{flex:1}} placeholder="Promo code" value={promoCode} onChange={e=>setPromoCode(e.target.value.toUpperCase())} onKeyDown={e=>e.key==="Enter"&&applyPromo()}/>
              <button onClick={applyPromo} disabled={promoLoading||!promoCode.trim()} style={{padding:"0 18px",borderRadius:12,border:"1px solid var(--line)",background:"var(--card)",cursor:"pointer",fontWeight:500,fontSize:14,whiteSpace:"nowrap",opacity:promoCode.trim()?1:0.5}}>{promoLoading?"…":"Apply"}</button>
            </div>
          )}
          {promoError && <p style={{color:"#c0392b",fontSize:12,margin:"6px 0 0"}}>{promoError}</p>}
        </div>
        <div className="stripe-box" onClick={!paying && !promoApplied ? pay : undefined} style={{cursor: paying || promoApplied ? "default" : "pointer", opacity: promoApplied ? 0.35 : 1, pointerEvents: promoApplied ? "none" : "auto"}}>
          <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={promoApplied ? confirmPromo : pay} disabled={paying}>
            {paying ? "Processing…" : promoApplied ? "Confirm order →" : `Pay ${currency.prefix}8.99`} {!paying && <Icon.Arrow/>}
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="panel design-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" 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 eCard 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={`You will forever own this moment of ${(data.occasion || data.customOccasion || "your moment").toLowerCase()}. It happened to you, once, and to no one else, ever again.`}
            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, eCard and your Unique Design ID, straight to your inbox.</p>

          {/* Delivery mode — gift only, shown first */}
          {data.audience === "gift" && (
            <div style={{marginBottom: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>
          )}

          {/* Email fields — conditional on delivery mode */}
          {data.audience === "gift" && data.deliveryMode === "direct" ? (
            <>
              <label className="field-label">Your 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/>
              <label className="field-label" style={{marginTop:16}}>{data.name || "Their"}'s email address</label>
              <input className="field" type="email" inputMode="email" placeholder={`${data.name || "them"}@example.com`} value={data.recipientEmail} onChange={e=>set("recipientEmail", e.target.value)}/>
            </>
          ) : (
            <>
              <label className="field-label">Your 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/>
            </>
          )}

          <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. Please check inbox or spam folder for design@once.graphics</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("gift");
  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")}>eCard</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, eCard, and UDID, ready" : "Sent ✶ Check your inbox"}
      </div>
    </div>
  );
}

function DesignCaptureStage({ display, motif, audience }) {
  return (
    <div className="once-capture-stage" aria-hidden="true">
      <div id="once-capture-artwork" className="once-capture-frame once-capture-artwork">
        <Artwork display={display} motif={motif} watermark={false}/>
      </div>
      <div id="once-capture-ecard" className="once-capture-frame once-capture-ecard">
        <GiftCard display={display} motif={motif} audience={audience} watermark={false}/>
      </div>
      <div id="once-capture-udid" className="once-capture-frame once-capture-udid">
        <UDID display={display} motif={motif} watermark={false}/>
      </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">Designed 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:"clamp(9px, 2.4cqw, 13px)", letterSpacing:0, textTransform:"none"}}>Once Graphics</span></span>
          <span>№ {display.id}</span>
        </div>
        <div className="gc-body">
          <div className="gc-greeting gc-note-text">
            <div style={{marginBottom:"0.4em"}}>Dear {display.recipient},</div>
            {note}
          </div>
          <div className="gc-signoff">
            <div>{signatureName},</div>
            <div>{fmtGiftDate(display.signed)}</div>
          </div>
        </div>
        <div className="gc-foot">
          <span>Designed 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">{display.dateMode === "this-year" ? "Again this year:" : "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">From</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={UDID_ICON_SRC} alt="Once" style={{width:28, height:28, objectFit:"contain", filter:"drop-shadow(0 4px 8px rgba(0,0,0,0.35))"}}/>
          <span className="stamp">DESIGNED · {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={UDID_ICON_SRC} alt="Once" style={{width:38, height:38, objectFit:"contain", filter:"drop-shadow(0 6px 10px rgba(0,0,0,0.42))", 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>Designed. Once, and only once.</h3>
      <p>Your artwork, eCard, 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 eCard from the coordinates of that exact moment. Never the same twice." },
    { n:"03", t:"Designed, signed, sent", d:"You receive the Artwork, eCard, 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 <em>Once</em> greeting package 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, currency = DEFAULT_CURRENCY }) {
  return (
    <section className="pricing" id="pricing">
      <div className="price-card">
        <div className="price-tag">ONCE GRAPHICS · Single Design</div>
        <div className="price-amount"><span className="currency">{currency.prefix}</span>8<em>.</em><span className="cents">99</span></div>
        <div className="price-sub">One unique artwork, eCard, 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 eCard</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}>Design mine for {currency.prefix}8.99</button>
      </div>
    </section>
  );
}

/* ===== Not Available ===== */
function NotAvailable({ goHome }) {
  const [email, setEmail] = useState("");
  const [sent, setSent] = useState(false);
  const submit = () => {
    if (!email.includes("@")) return;
    // TODO: POST to Supabase waitlist table
    console.log("Waitlist:", email);
    setSent(true);
  };
  return (
    <>
      <Nav onCreate={goHome} backMode={true}/>
      <section style={{minHeight:"calc(100vh - 72px)", display:"flex", alignItems:"center", justifyContent:"center", padding:"40px 20px"}}>
        <div style={{maxWidth:480, textAlign:"center"}}>
          <div style={{fontFamily:"'Fraunces',serif", fontSize:"clamp(48px,8vw,72px)", lineHeight:1, marginBottom:24, opacity:0.15}}>✦</div>
          <h2 style={{fontFamily:"'Fraunces',serif", fontSize:"clamp(28px,5vw,40px)", fontWeight:400, lineHeight:1.2, marginBottom:16}}>
            Not quite <em>your corner</em> yet.
          </h2>
          <p style={{color:"var(--ink-soft)", fontSize:16, lineHeight:1.65, marginBottom:36}}>
            Once Graphics is currently available in Australia, Canada, the US, and the UK. We're expanding — leave your email and we'll let you know when we arrive.
          </p>
          {!sent ? (
            <div style={{display:"flex", gap:10, maxWidth:380, margin:"0 auto"}}>
              <input
                type="email" value={email}
                onChange={e=>setEmail(e.target.value)}
                onKeyDown={e=>e.key==="Enter"&&submit()}
                placeholder="your@email.com"
                style={{flex:1, padding:"12px 16px", borderRadius:10, border:"1.5px solid var(--line)", background:"var(--bg-2)", color:"var(--ink)", fontSize:15, outline:"none", fontFamily:"inherit"}}
              />
              <button className="btn-primary" onClick={submit} style={{whiteSpace:"nowrap", flexShrink:0}}>Notify me</button>
            </div>
          ) : (
            <div style={{background:"var(--bg-2)", border:"1.5px solid var(--line)", borderRadius:14, padding:"20px 28px", color:"var(--ink-soft)", fontSize:15, lineHeight:1.6}}>
              You're on the list. We'll reach out when Once Graphics comes to your region.
            </div>
          )}
        </div>
      </section>
    </>
  );
}

/* ===== App ===== */
function App() {
  const [mode, setMode] = useState("landing");
  const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
  const [unsupported, setUnsupported] = useState(false);

  useEffect(() => {
    trackEvent('page_view', { mode });
  }, [mode]);

  useEffect(() => {
    fetch("https://ipapi.co/json/")
      .then(r => r.json())
      .then(d => {
        const c = CURRENCIES[d.country_code];
        if (c) setCurrency(c);
        else setUnsupported(true);
      })
      .catch(() => {});
  }, []);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const payment = params.get('payment');
    if (payment === 'success') {
      trackEvent('payment_success_page', {});
      setMode('success');
      window.history.replaceState({}, '', window.location.pathname);
    } else if (payment === 'cancel') {
      trackEvent('payment_cancel_page', {});
      setMode('cancelled');
      window.history.replaceState({}, '', window.location.pathname);
    }
  }, []);

  const goCreate = () => {
    trackEvent('design_started', { fromMode: mode, unsupported });
    if (unsupported) { setMode("unavailable"); window.scrollTo({top:0,behavior:"instant"}); return; }
    setMode("create"); window.scrollTo({top:0,behavior:"instant"});
  };
  const goHome = () => { setMode("landing"); window.scrollTo({top:0,behavior:"instant"}); };
  const goHow = () => { setMode("how"); window.scrollTo({top:0,behavior:"instant"}); };
  const goPricing = () => { setMode("pricing"); window.scrollTo({top:0,behavior:"instant"}); };

  if (mode === "unavailable") return <NotAvailable goHome={goHome}/>;

  if (mode === "success") {
    return (
      <div className="page-shell">
        <Nav onCreate={goCreate} onHome={goHome} onHow={goHow} onPricing={goPricing}/>
        <main className="page-main" style={{display:"flex", alignItems:"center", justifyContent:"center"}}>
          <div style={{textAlign:"center", padding:"40px 20px", maxWidth:480}}>
            <div className="success-mark" style={{margin:"0 auto 24px"}}><Icon.Check/></div>
            <h2 style={{fontFamily:"'Fraunces',serif", fontSize:"clamp(28px,5vw,40px)", fontWeight:400, letterSpacing:"-0.02em", margin:"0 0 14px"}}>
              Designed. <em style={{fontStyle:"italic"}}>Once,</em> and only once.
            </h2>
            <p style={{color:"var(--ink-soft)", fontSize:15, lineHeight:1.65, margin:"0 0 32px"}}>
              Your artwork, eCard, and Unique Design ID are heading to your inbox.<br/>
              Watch out for an email from <b>design@once.graphics</b>. Check spam if it doesn't arrive within 2 minutes.
            </p>
            <button className="btn-primary" onClick={goHome}>Back to home</button>
          </div>
        </main>
        <footer>
          <div className="row">
            <span>© 2026 Once Graphics · Made with care</span>
            <span>hello@once.graphics</span>
          </div>
        </footer>
      </div>
    );
  }

  if (mode === "cancelled") {
    return (
      <div className="page-shell">
        <Nav onCreate={goCreate} onHome={goHome} onHow={goHow} onPricing={goPricing}/>
        <main className="page-main" style={{display:"flex", alignItems:"center", justifyContent:"center"}}>
          <div style={{textAlign:"center", padding:"40px 20px", maxWidth:480}}>
            <div style={{fontFamily:"'Fraunces',serif", fontSize:64, lineHeight:1, marginBottom:24, opacity:0.15}}>✦</div>
            <h2 style={{fontFamily:"'Fraunces',serif", fontSize:"clamp(26px,5vw,36px)", fontWeight:400, letterSpacing:"-0.02em", margin:"0 0 14px"}}>
              Payment cancelled.
            </h2>
            <p style={{color:"var(--ink-soft)", fontSize:15, lineHeight:1.65, margin:"0 0 32px"}}>
              No charge was made. Your design is ready whenever you are.
            </p>
            <div style={{display:"flex", gap:12, justifyContent:"center", flexWrap:"wrap"}}>
              <button className="btn-primary" onClick={goCreate}>Try again</button>
              <button className="btn-ghost" onClick={goHome}>Back to home</button>
            </div>
          </div>
        </main>
        <footer>
          <div className="row">
            <span>© 2026 Once Graphics · Made with care</span>
            <span>hello@once.graphics</span>
          </div>
        </footer>
      </div>
    );
  }

  if (mode === "create") {
    return (
      <>
        <Nav onCreate={goCreate} onHome={goHome} backMode={true} hideOnMobile={true}/>
        <Generator onBack={goHome} currency={currency}/>
      </>
    );
  }

  if (mode === "how") {
    return (
      <div className="page-shell">
        <Nav onCreate={goCreate} onHome={goHome} backMode={true} showCreateAction={true}/>
        <main className="page-main">
          <Features/>
        </main>
        <footer>
          <div className="row">
            <span>© 2026 Once Graphics · Made with care</span>
            <span>hello@once.graphics</span>
          </div>
        </footer>
      </div>
    );
  }

  if (mode === "pricing") {
    return (
      <div className="page-shell">
        <Nav onCreate={goCreate} onHome={goHome} backMode={true} showCreateAction={true}/>
        <main className="page-main">
          <Pricing onCreate={goCreate} currency={currency}/>
        </main>
        <footer>
          <div className="row">
            <span>© 2026 Once Graphics · Made with care</span>
            <span>hello@once.graphics</span>
          </div>
        </footer>
      </div>
    );
  }

  return (
    <div className="page-shell landing-shell">
      <Nav onCreate={goCreate} onHome={goHome} onHow={goHow} onPricing={goPricing}/>
      <main className="page-main landing-main">
        <Hero onStart={goCreate} onHow={goHow}/>
      </main>
      <footer>
        <div className="row">
          <span>© 2026 Once Graphics · Made with care</span>
          <span>hello@once.graphics</span>
        </div>
      </footer>
    </div>
  );
}

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