// shared.jsx — primitives + project data + interactives shared across all 5 home page directions

const PROJECTS = [
  {
    id: 'officehours',
    name: 'Office Hours',
    tagline: 'A workplace survival quiz game.',
    blurb: 'Pick a workplace archetype, play real scenarios, find out your survival type.',
    href: 'https://techclatters.vercel.app/officehoursgame',
    kind: 'Game · Web',
    emoji: '☕',
    color: '#FF7A6B',
    accent: '#FFD66B',
    year: 'Apr 2026',
  },
  {
    id: 'moneyticker',
    name: 'Money Ticker',
    tagline: 'Watch your hourly earnings tick up in real time.',
    blurb: 'Celebrations at every milestone and at the end of your work day.',
    href: 'https://chromewebstore.google.com/detail/money-ticker/agiaaekccnmeolhalkejpcjfmpogbngb',
    kind: 'Chrome Extension',
    emoji: '💸',
    color: '#7BB87A',
    accent: '#9DC3FF',
    year: 'Apr 2026',
  },
  {
    id: 'underpaint',
    name: 'Underpaint',
    tagline: 'Upload a photo — get a step-by-step painting lesson.',
    blurb: 'Drop in any image and Underpaint walks you through how to paint it, layer by layer.',
    href: '/underpaint',
    kind: 'AI · Painting',
    emoji: '🎨',
    color: '#C9A2E6',
    accent: '#FFE66B',
    year: 'Jun 2026',
  },
  {
    id: 'hoops',
    name: 'Hoops',
    tagline: 'Organize local basketball pickup games.',
    blurb: 'Find runs, fill spots, and show up ready to ball.',
    href: '#',
    kind: 'Community · Sports',
    emoji: '🏀',
    color: '#FF7A6B',
    accent: '#FFE66B',
    year: 'May 2026',
    noLink: true,
  },
  {
    id: 'soon1',
    name: 'Cooking…',
    tagline: 'Something tiny and useful is in the oven.',
    blurb: 'Idea-stage. Stay tuned.',
    href: '#',
    kind: 'Coming soon',
    emoji: '🍳',
    color: '#FFE66B',
    accent: '#C9A2E6',
    year: 'soon',
    soft: true,
  },
];

const ABOUT = {
  oneLine: "I build little things for fun & occasional utility.",
  likes: ['art', 'DIYs', 'machine learning', 'computer vision', 'software'],
  email: 'techclatters@gmail.com',
};

// ─────────────────────────────────────────────────────────────
// Pixel robot mascot — a chunky little buddy. Eyes blink + track cursor.
// ─────────────────────────────────────────────────────────────
function PixelBuddy({ size = 80, mood = 'happy', talking = false, palette }) {
  const p = palette || { body: '#FFE66B', shade: '#E8C932', shadow: '#1f1a14', eye: '#1f1a14', cheek: '#FF7A6B', antenna: '#7BB87A' };
  // 16x16 pixel-art robot. Each char maps to a color.
  // .=transparent, B=body, S=shade, K=outline, E=eye-bg(white), P=pupil, C=cheek, A=antenna, T=antennaTip
  const art = [
    '.......T........',
    '.......A........',
    '.......A........',
    '..KKKKKKKKKK....',
    '.KBBBBBBBBBBK...',
    'KBEEKBBKEEKBSK..',
    'KBEPKBBKEPKBSK..',
    'KBBBBCBBBCBBSK..',
    'KBBBBBBBBBBBSK..',
    'KBBBKKKKKKBBSK..',
    '.KBBBBBBBBBBSK..',
    '..KKKKKKKKKK....',
    '..K.KK..KK.K....',
    '..K.KK..KK.K....',
    '..KKKK..KKKK....',
    '................',
  ];
  const colors = { '.': 'transparent', B: p.body, S: p.shade, K: p.shadow, E: '#fffdf5', P: p.eye, C: p.cheek, A: p.antenna, T: p.antenna };
  const px = size / 16;
  return (
    <div style={{ position: 'relative', width: size, height: size, imageRendering: 'pixelated', display: 'inline-block' }}>
      <svg viewBox="0 0 16 16" width={size} height={size} style={{ display: 'block', shapeRendering: 'crispEdges' }}>
        {art.map((row, y) =>
          row.split('').map((ch, x) =>
            ch === '.' ? null : <rect key={`${x}-${y}`} x={x} y={y} width="1" height="1" fill={colors[ch]} />
          )
        )}
        {/* blink */}
        <g style={{ animation: 'buddy-blink 4.7s infinite' }}>
          <rect x="3" y="5" width="3" height="2" fill={p.body} opacity="0" />
          <rect x="10" y="5" width="3" height="2" fill={p.body} opacity="0" />
        </g>
      </svg>
      {talking && (
        <div style={{
          position: 'absolute', left: size + 8, top: -4,
          background: '#fff', border: `2px solid ${p.shadow}`, padding: '6px 10px',
          borderRadius: 8, fontSize: 12, whiteSpace: 'nowrap', fontFamily: 'inherit',
          color: p.shadow, boxShadow: `2px 2px 0 ${p.shadow}`,
        }}>
          {talking}
          <div style={{ position: 'absolute', left: -7, top: 12, width: 0, height: 0, borderTop: '5px solid transparent', borderBottom: '5px solid transparent', borderRight: `7px solid ${p.shadow}` }} />
        </div>
      )}
      <style>{`
        @keyframes buddy-blink {
          0%, 92%, 100% { opacity: 0; }
          94%, 96% { opacity: 1; }
        }
      `}</style>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Anagram reveal: SCARLETT ⇄ CLATTERS. Click letters scramble.
// ─────────────────────────────────────────────────────────────
function AnagramReveal({ size = 28, color = '#1f1a14', accent = '#FF7A6B', defaultPhase = 0, onChange }) {
  const FROM = 'SCARLETT';
  const TO = 'CLATTERS';
  // permutation: which index of FROM each TO letter came from
  // S(0)C(1)A(2)R(3)L(4)E(5)T(6)T(7) → C(1)L(4)A(2)T(6)T(7)E(5)R(3)S(0)
  const perm = [1, 4, 2, 6, 7, 5, 3, 0];
  const [phase, setPhase] = React.useState(defaultPhase); // 0 = SCARLETT, 1 = CLATTERS
  const letters = phase === 0 ? FROM.split('') : TO.split('');
  return (
    <button
      onClick={() => { const next = 1 - phase; setPhase(next); onChange?.(next); }}
      style={{ border: 'none', background: 'transparent', padding: 0, cursor: 'pointer', display: 'inline-flex', gap: 1, fontFamily: 'inherit' }}
      title="click me"
    >
      {letters.map((ch, i) => (
        <span key={i}
          style={{
            display: 'inline-block', fontSize: size, fontWeight: 800,
            color, letterSpacing: '0.02em',
            transform: phase === 1 ? `translateY(${(i % 2 ? -2 : 2)}px) rotate(${(i % 2 ? -3 : 3)}deg)` : 'none',
            transition: `transform .35s cubic-bezier(.5,1.6,.4,1) ${i * 30}ms, color .2s`,
          }}>
          <span style={{ color: phase === 1 && (i === 0 || i === 7) ? accent : color }}>{ch}</span>
        </span>
      ))}
    </button>
  );
}

// ─────────────────────────────────────────────────────────────
// Tiny mini-game: "Feed the Kitty" — click flying treats to feed
// a sleepy cat. Cat purrs (gets bigger/happier) the more you feed.
// ─────────────────────────────────────────────────────────────
function FeedTheKitty({ width = 280, height = 180, palette }) {
  const p = palette || { bg: '#fffaf0', border: '#1f1a14', cat: '#FF7A6B', score: '#FF7A6B', ground: '#FFE66B' };
  const [treats, setTreats] = React.useState([]);
  const [score, setScore] = React.useState(0);
  const [running, setRunning] = React.useState(false);
  const [timeLeft, setTimeLeft] = React.useState(20);
  const [purr, setPurr] = React.useState(0); // 0..3 happiness
  const idRef = React.useRef(0);
  const runningRef = React.useRef(false);
  runningRef.current = running;

  React.useEffect(() => {
    if (!running) return;
    const spawn = setInterval(() => {
      if (!runningRef.current) return;
      idRef.current += 1;
      const id = idRef.current;
      const startX = 20 + Math.random() * (width - 40);
      const drift = (Math.random() - 0.5) * 60;
      const kind = Math.random() < 0.5 ? 'fish' : 'heart';
      setTreats((ts) => [...ts, { id, startX, drift, kind }]);
      setTimeout(() => setTreats((ts) => ts.filter((t) => t.id !== id)), 3500);
    }, 750);
    const tick = setInterval(() => setTimeLeft((t) => Math.max(0, t - 1)), 1000);
    return () => { clearInterval(spawn); clearInterval(tick); };
  }, [running, width]);

  React.useEffect(() => {
    if (timeLeft === 0 && running) setRunning(false);
  }, [timeLeft, running]);

  const start = () => { setTreats([]); setScore(0); setTimeLeft(20); setPurr(0); setRunning(true); };
  const feed = (id) => {
    setTreats((ts) => ts.filter((t) => t.id !== id));
    setScore((s) => s + 1);
    setPurr((p) => Math.min(3, p + 0.3));
    setTimeout(() => setPurr((p) => Math.max(0, p - 0.15)), 600);
  };

  // cat in bottom-right corner
  const catSize = 56 + purr * 6;

  return (
    <div style={{
      position: 'relative', width, height, background: p.bg,
      border: `2px solid ${p.border}`, borderRadius: 4, overflow: 'hidden',
      boxShadow: `3px 3px 0 ${p.border}`, fontFamily: 'inherit',
    }}>
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 14, background: p.ground, borderTop: `2px dotted ${p.border}` }} />
      <div style={{ position: 'absolute', top: 6, left: 8, right: 8, display: 'flex', justifyContent: 'space-between', fontSize: 11, fontWeight: 700, color: p.border, letterSpacing: '.05em' }}>
        <span>FED · <span style={{ color: p.score }}>{score}</span> {purr > 1 ? '· purring 💕' : ''}</span>
        <span>{running ? `${timeLeft}s` : score > 0 ? `${score} treats · cat is happy` : 'FEED THE KITTY'}</span>
      </div>

      {/* the cat */}
      <div style={{
        position: 'absolute', right: 12, bottom: 14, width: catSize, height: catSize,
        transition: 'width .25s, height .25s, transform .15s',
        transform: purr > 0.5 ? `rotate(${Math.sin(Date.now() / 200) * 4}deg)` : 'none',
      }}>
        <svg viewBox="0 0 60 60" width={catSize} height={catSize} style={{ shapeRendering: 'geometricPrecision' }}>
          {/* body */}
          <ellipse cx="30" cy="42" rx="22" ry="14" fill={p.cat} stroke={p.border} strokeWidth="1.5" />
          {/* head */}
          <circle cx="30" cy="24" r="14" fill={p.cat} stroke={p.border} strokeWidth="1.5" />
          {/* ears */}
          <polygon points="18,16 16,6 24,12" fill={p.cat} stroke={p.border} strokeWidth="1.5" strokeLinejoin="round" />
          <polygon points="42,16 44,6 36,12" fill={p.cat} stroke={p.border} strokeWidth="1.5" strokeLinejoin="round" />
          {/* eyes — closed (happy) when purring */}
          {purr > 1 ? (
            <>
              <path d="M22 24 Q24 22 26 24" fill="none" stroke={p.border} strokeWidth="1.5" strokeLinecap="round" />
              <path d="M34 24 Q36 22 38 24" fill="none" stroke={p.border} strokeWidth="1.5" strokeLinecap="round" />
            </>
          ) : (
            <>
              <circle cx="24" cy="24" r="1.6" fill={p.border} />
              <circle cx="36" cy="24" r="1.6" fill={p.border} />
            </>
          )}
          {/* nose + mouth */}
          <path d="M29 28 L31 28 L30 29 Z" fill={p.border} />
          <path d="M30 29 Q28 32 26 30 M30 29 Q32 32 34 30" fill="none" stroke={p.border} strokeWidth="1" strokeLinecap="round" />
          {/* whiskers */}
          <line x1="14" y1="28" x2="22" y2="29" stroke={p.border} strokeWidth=".8" />
          <line x1="38" y1="29" x2="46" y2="28" stroke={p.border} strokeWidth=".8" />
          {/* tail */}
          <path d="M50 42 Q58 36 54 28" fill="none" stroke={p.border} strokeWidth="3" strokeLinecap="round" />
        </svg>
      </div>

      {/* falling treats */}
      {treats.map((t) => (
        <button key={t.id} onClick={() => feed(t.id)}
          style={{
            position: 'absolute', top: -24, left: t.startX,
            width: 26, height: 26, padding: 0, border: 'none', background: 'transparent', cursor: 'pointer',
            animation: `treat-fall-${t.id % 2} 3.3s linear forwards`,
            ['--drift']: `${t.drift}px`, ['--fall']: `${height - 30}px`,
          }}>
          {t.kind === 'fish' ? (
            <svg viewBox="0 0 24 24" width="24" height="24">
              <ellipse cx="11" cy="12" rx="7" ry="4.5" fill="#9DC3FF" stroke={p.border} strokeWidth="1.2" />
              <polygon points="18,12 23,8 23,16" fill="#9DC3FF" stroke={p.border} strokeWidth="1.2" strokeLinejoin="round" />
              <circle cx="8" cy="11" r="1" fill={p.border} />
            </svg>
          ) : (
            <svg viewBox="0 0 24 24" width="24" height="24">
              <path d="M12 21 C 4 14 4 8 8 6 C 10 5 12 7 12 9 C 12 7 14 5 16 6 C 20 8 20 14 12 21 Z"
                fill="#FF7A6B" stroke={p.border} strokeWidth="1.4" strokeLinejoin="round" />
            </svg>
          )}
        </button>
      ))}

      {!running && (
        <button onClick={start}
          style={{
            position: 'absolute', left: '34%', top: '50%', transform: 'translate(-50%, -50%)',
            background: p.score, color: '#fffdf5', border: `2px solid ${p.border}`,
            padding: '8px 16px', borderRadius: 4, fontFamily: 'inherit', fontWeight: 800,
            cursor: 'pointer', boxShadow: `2px 2px 0 ${p.border}`, fontSize: 12, letterSpacing: '.08em',
          }}>
          {score > 0 ? 'AGAIN' : 'FEED ME'}
        </button>
      )}

      <style>{`
        @keyframes treat-fall-0 {
          from { transform: translate(0, 0) rotate(0); }
          to   { transform: translate(var(--drift), var(--fall)) rotate(180deg); }
        }
        @keyframes treat-fall-1 {
          from { transform: translate(0, 0) rotate(0); }
          to   { transform: translate(var(--drift), var(--fall)) rotate(-180deg); }
        }
      `}</style>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Sparkle/confetti cursor trail (scoped to a container)
// ─────────────────────────────────────────────────────────────
function CursorTrail({ containerRef, kind = 'confetti', colors = ['#FF7A6B', '#FFE66B', '#7BB87A', '#9DC3FF', '#C9A2E6'], enabled = true }) {
  const [bits, setBits] = React.useState([]);
  const idRef = React.useRef(0);

  React.useEffect(() => {
    if (!enabled) return;
    const el = containerRef.current;
    if (!el) return;
    let last = 0;
    const onMove = (e) => {
      const now = Date.now();
      if (now - last < 40) return;
      last = now;
      const r = el.getBoundingClientRect();
      const x = e.clientX - r.left;
      const y = e.clientY - r.top;
      idRef.current += 1;
      const id = idRef.current;
      const color = colors[Math.floor(Math.random() * colors.length)];
      const dx = (Math.random() - 0.5) * 30;
      const dy = 30 + Math.random() * 20;
      const rot = (Math.random() - 0.5) * 360;
      setBits((b) => [...b.slice(-30), { id, x, y, color, dx, dy, rot, kind }]);
      setTimeout(() => setBits((b) => b.filter((p) => p.id !== id)), 900);
    };
    el.addEventListener('mousemove', onMove);
    return () => el.removeEventListener('mousemove', onMove);
  }, [containerRef, kind, enabled, colors.join('|')]);

  return (
    <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden', zIndex: 1000 }}>
      {bits.map((b) => (
        <span key={b.id}
          style={{
            position: 'absolute', left: b.x, top: b.y, width: kind === 'sparkle' ? 8 : 6, height: kind === 'sparkle' ? 8 : 10,
            background: kind === 'sparkle' ? 'transparent' : b.color, color: b.color,
            borderRadius: kind === 'star' ? '50%' : 0,
            animation: `trail-fall .9s ease-out forwards`,
            ['--dx']: `${b.dx}px`, ['--dy']: `${b.dy}px`, ['--rot']: `${b.rot}deg`,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 12,
          }}>
          {kind === 'sparkle' ? '✦' : kind === 'star' ? '' : ''}
        </span>
      ))}
      <style>{`
        @keyframes trail-fall {
          from { transform: translate(0, 0) rotate(0); opacity: 1; }
          to   { transform: translate(var(--dx), var(--dy)) rotate(var(--rot)); opacity: 0; }
        }
      `}</style>
    </div>
  );
}

// Confetti dot pattern bg (decorative)
function ConfettiDots({ density = 1, palette = ['#FF7A6B', '#FFE66B', '#7BB87A', '#9DC3FF', '#C9A2E6'] }) {
  const dots = React.useMemo(() => {
    const seed = 42;
    const rand = (i) => {
      const x = Math.sin(seed + i * 12.9898) * 43758.5453;
      return x - Math.floor(x);
    };
    const n = Math.floor(60 * density);
    return Array.from({ length: n }, (_, i) => ({
      x: rand(i) * 100,
      y: rand(i + 1000) * 100,
      r: 2 + rand(i + 2000) * 4,
      c: palette[Math.floor(rand(i + 3000) * palette.length)],
      rot: rand(i + 4000) * 360,
      kind: rand(i + 5000) > 0.6 ? 'rect' : 'dot',
    }));
  }, [density, palette.join('|')]);
  return (
    <svg width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 100 100" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
      {dots.map((d, i) =>
        d.kind === 'dot'
          ? <circle key={i} cx={d.x} cy={d.y} r={d.r * 0.15} fill={d.c} opacity="0.8" />
          : <rect key={i} x={d.x} y={d.y} width={d.r * 0.4} height={d.r * 0.15} fill={d.c} opacity="0.7" transform={`rotate(${d.rot} ${d.x} ${d.y})`} />
      )}
    </svg>
  );
}

// Wiggle on hover wrapper
function Wiggle({ children, intensity = 1, ...rest }) {
  const ref = React.useRef(null);
  const [hov, setHov] = React.useState(false);
  return (
    <span ref={ref} {...rest}
      onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      style={{
        display: 'inline-block',
        animation: hov ? `wiggle-${intensity} .35s ease-in-out infinite alternate` : 'none',
        ...rest.style,
      }}>
      {children}
      <style>{`
        @keyframes wiggle-1 { from { transform: rotate(-2deg); } to { transform: rotate(2deg); } }
        @keyframes wiggle-2 { from { transform: rotate(-5deg) scale(1.05); } to { transform: rotate(5deg) scale(1.05); } }
      `}</style>
    </span>
  );
}

// ─────────────────────────────────────────────────────────────
// Office Hours clock — minimal mark, light-mode treatment.
// Mirrors the favicon-scale clock from the brand sheet.
// ─────────────────────────────────────────────────────────────
function OfficeHoursClock({ size = 64, radius = 10, bg = '#F5F2ED', accent = '#C4622D', ink = '#2A2520', border }) {
  const cx = 32, cy = 32, r = 20;
  return (
    <svg viewBox="0 0 64 64" width={size} height={size} style={{ display: 'block' }}>
      <rect x="0.75" y="0.75" width="62.5" height="62.5" rx={radius} fill={bg} stroke={border || 'none'} strokeWidth={border ? 1.5 : 0} />
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={accent} strokeWidth="1.5" />
      {Array.from({ length: 12 }).map((_, i) => {
        const a = (i / 12) * Math.PI * 2 - Math.PI / 2;
        const x1 = cx + Math.cos(a) * (r - 2);
        const y1 = cy + Math.sin(a) * (r - 2);
        const x2 = cx + Math.cos(a) * r;
        const y2 = cy + Math.sin(a) * r;
        return <line key={i} x1={x1} y1={y1} x2={x2} y2={y2} stroke={accent} strokeWidth="1" strokeLinecap="round" />;
      })}
      {/* minute hand pointing to 9 */}
      <line x1={cx} y1={cy} x2={cx - 14} y2={cy} stroke={accent} strokeWidth="2" strokeLinecap="round" />
      {/* hour hand pointing toward 1 */}
      <line x1={cx} y1={cy} x2={cx + 4} y2={cy - 16} stroke={ink} strokeWidth="1.5" strokeLinecap="round" />
      <circle cx={cx} cy={cy} r="2.2" fill={accent} />
    </svg>
  );
}

// ─────────────────────────────────────────────────────────────
// Jamón — Scarlett's gray guinea pig with a yellow fur patch on
// her back-left leg. Side-view, pear-shaped (fatter butt). Clickable.
// ─────────────────────────────────────────────────────────────
function JamonGuineaPig({ size = 140, onClick, talking, idle = true, walking = false, squish = false }) {
  const ink = '#1f1a14';
  const fur = '#a4a3a8';
  const furDark = '#74737a';
  const furLight = '#e3e2e6';
  const cheek = '#FF9CA0';
  const yellow = '#FFE066';
  const w = size;
  const bodyD = "M 28 100 C 22 38, 80 24, 138 38 C 178 46, 200 64, 200 92 C 200 118, 180 132, 142 142 C 92 154, 28 158, 28 100 Z";
  return (
    <div style={{ position: 'relative', display: 'inline-block' }}>
      <button
        onClick={onClick}
        title="chat with Jamón"
        aria-label="Chat with Jamón"
        style={{
          border: 'none', background: 'transparent', padding: 0, margin: 0,
          cursor: onClick ? 'pointer' : 'default', display: 'block',
          animation: idle ? 'jamon-bob 3.6s ease-in-out infinite' : 'none',
          transformOrigin: '50% 90%',
        }}
      >
        <svg viewBox="0 0 240 182" width={w} height={w * 182 / 240} style={{ display: 'block', overflow: 'visible' }}>
          <defs>
            <clipPath id="jamon-side-body-clip">
              <path d={bodyD} />
            </clipPath>
          </defs>
          {/* drop shadow */}
          <ellipse cx="115" cy="168" rx="86" ry="6" fill={ink} opacity="0.12" />

          {/* back feet — gray (far) behind yellow (near), raised to match front feet level */}
          <ellipse cx="42" cy="151" rx="9" ry="7" fill={furDark} stroke={ink} strokeWidth="2"
            style={walking ? { animation: 'jamon-foot-a 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
          <ellipse cx="56" cy="148" rx="9" ry="7" fill={yellow} stroke={ink} strokeWidth="2"
            style={walking ? { animation: 'jamon-foot-b 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />

          {/* body fill */}
          <path d={bodyD} fill={fur} />
          {/* yellow fur patch — clipped strictly inside body */}
          <g clipPath="url(#jamon-side-body-clip)">
            <ellipse cx="50" cy="118" rx="26" ry="19" fill={yellow} />
            <ellipse cx="40" cy="108" rx="10" ry="6" fill={yellow} opacity="0.7" />
          </g>
          {/* tummy */}
          <ellipse cx="115" cy="130" rx="62" ry="16" fill={furLight} opacity=".55" />
          {/* fur tufts */}
          <path d="M50 70 Q 56 62 62 70" fill="none" stroke={furDark} strokeWidth="1.4" strokeLinecap="round" />
          <path d="M82 56 Q 86 50 92 56" fill="none" stroke={furDark} strokeWidth="1.4" strokeLinecap="round" />
          <path d="M110 50 Q 114 44 120 50" fill="none" stroke={furDark} strokeWidth="1.4" strokeLinecap="round" />
          <path d="M138 54 Q 142 48 148 54" fill="none" stroke={furDark} strokeWidth="1.4" strokeLinecap="round" />
          {/* body stroke */}
          <path d={bodyD} fill="none" stroke={ink} strokeWidth="2.5" />

          {/* front legs — alternate walk animation */}
          <ellipse cx="148" cy="146" rx="9" ry="7" fill={furDark} stroke={ink} strokeWidth="2"
            style={walking ? { animation: 'jamon-foot-b 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
          <ellipse cx="124" cy="148" rx="9" ry="7" fill={furDark} stroke={ink} strokeWidth="2"
            style={walking ? { animation: 'jamon-foot-a 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />

          {/* head — moved down for better balance */}
          <circle cx="180" cy="90" r="52" fill={fur} stroke={ink} strokeWidth="2.5" />

          {/* ear */}
          <g transform="rotate(-18 165 52)">
            <ellipse cx="165" cy="52" rx="12" ry="15" fill={furDark} stroke={ink} strokeWidth="2" />
            <ellipse cx="165" cy="54" rx="5.5" ry="9" fill={cheek} opacity=".55" />
          </g>

          {/* eye — squish shows > shape */}
          {squish ? (
            <path d="M196 82 L202 88 L196 94" fill="none" stroke={ink} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
          ) : (
            <>
              <circle cx="202" cy="86" r="5" fill={ink} />
              <circle cx="203.8" cy="84.2" r="1.4" fill="#fff" />
              <rect x="196" y="83" width="12" height="6" rx="3" fill={fur}
                style={{ animation: 'jamon-blink 5.3s infinite', transformBox: 'fill-box', transformOrigin: 'center' }} />
            </>
          )}

          {/* cheek */}
          <circle cx="192" cy="110" r="6" fill={cheek} opacity=".55" />

          {/* nose + mouth */}
          <path d="M219 100 Q 226 102 219 105 Q 214 102 219 100 Z" fill={ink} />
          <path d="M219 105 L219 112" stroke={ink} strokeWidth="1.5" strokeLinecap="round" fill="none" />
          <path d="M213 116 Q 219 112 225 116" stroke={ink} strokeWidth="1.5" strokeLinecap="round" fill="none" />

          {/* whiskers */}
          <line x1="212" y1="108" x2="232" y2="105" stroke={ink} strokeWidth=".8" strokeLinecap="round" />
          <line x1="212" y1="111" x2="234" y2="114" stroke={ink} strokeWidth=".8" strokeLinecap="round" />
        </svg>
      </button>

      {talking && (
        <div style={{
          position: 'absolute', left: -8, top: -38,
          background: '#fff', border: `2px solid ${ink}`, padding: '5px 10px',
          borderRadius: 8, fontSize: 12, whiteSpace: 'nowrap', color: ink,
          boxShadow: `2px 2px 0 ${ink}`, fontFamily: 'inherit', fontWeight: 700,
          pointerEvents: 'none',
        }}>
          {talking}
          <div style={{ position: 'absolute', left: 26, bottom: -7, width: 0, height: 0,
            borderLeft: '5px solid transparent', borderRight: '5px solid transparent',
            borderTop: `7px solid ${ink}` }} />
        </div>
      )}

      <style>{`
        @keyframes jamon-blink {
          0%, 92%, 100% { transform: scaleY(0); }
          94%, 96% { transform: scaleY(1); }
        }
        @keyframes jamon-bob {
          0%, 100% { transform: translateY(0) rotate(-1deg); }
          50% { transform: translateY(-3px) rotate(1deg); }
        }
        @keyframes jamon-foot-a {
          from { transform: translateY(-4px) rotate(-8deg); }
          to   { transform: translateY(4px) rotate(8deg); }
        }
        @keyframes jamon-foot-b {
          from { transform: translateY(4px) rotate(8deg); }
          to   { transform: translateY(-4px) rotate(-8deg); }
        }
      `}</style>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Jamón Chat — fixed bottom-right chat window. Jamón is a cute
// chatbot. Email is only sent if Jamón offers and the user agrees.
// ─────────────────────────────────────────────────────────────
function _jamonPick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }

function JamonChat({ open, onClose, accent = '#FF7A6B' }) {
  const ink = '#1f1a14';
  const paper = '#FBF7EE';

  const [messages, setMessages] = React.useState([
    { from: 'jamon', text: "*squeak squeak!* hi! I'm Jamón, Ms. Clatters' guinea pig 🐹 Chat with me! If you have an idea or feedback, tell me everything — when you're done just say 'send it' and I'll deliver it straight to her 📬" },
  ]);
  const [input, setInput] = React.useState('');
  const [typing, setTyping] = React.useState(false);
  const [emailForm, setEmailForm] = React.useState(false);
  const [formName, setFormName] = React.useState('');
  const [formEmail, setFormEmail] = React.useState('');
  const [formExtra, setFormExtra] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const msgsRef = React.useRef(null);
  const taRef = React.useRef(null);
  const endRef = React.useRef(null);

  React.useEffect(() => {
    if (open && taRef.current && !emailForm) setTimeout(() => taRef.current?.focus(), 50);
  }, [open, emailForm]);

  React.useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages, typing, emailForm]);

  if (!open) return null;

  const addJamon = (text, delay = 900) => {
    setTyping(true);
    setTimeout(() => {
      setTyping(false);
      setMessages((m) => [...m, { from: 'jamon', text }]);
    }, delay);
  };

  const submitEmailForm = async () => {
    const name = formName.trim();
    const email = formEmail.trim();
    if (!name || !email || sending) return;
    setSending(true);

    const chatLog = messages
      .map((m) => `${m.from === 'user' ? name : 'Jamón'}: ${m.text}`)
      .join('\n');

    try {
      const r = await fetch('/api/send-chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, visitorEmail: email, chatLog, extraNote: formExtra.trim() }),
      });
      setSending(false);
      setEmailForm(false);
      setFormName('');
      setFormEmail('');
      setFormExtra('');
      if (r.ok) {
        addJamon("*sprints to inbox* 🏃💨 wheeeek!! delivered to Ms. Clatters!! 📬 she'll reply to your email when she can!", 300);
      } else {
        const body = await r.json().catch(() => ({}));
        const errMsg = body.error || `HTTP ${r.status}`;
        console.error('send-chat error:', errMsg);
        addJamon(`*worried squeak* something went wrong: ${errMsg} 😅`, 300);
      }
    } catch (e) {
      setSending(false);
      console.error('send-chat fetch error:', e);
      addJamon(`*worried squeak* couldn't reach the server: ${e.message} 😅`, 300);
    }
  };

  // Detect explicit "send to Scarlett" intent — user must say it, not just mention an idea
  const _wantsSend = (t) => (
    (/\bsend\s+(this|it|that|my\s+(?:message|idea|feedback|note))?\s*(?:to\s+(?:scarlett|her))?\b/.test(t) && !/\bdon'?t\s+send\b/.test(t) && !/\bsend\s+me\b/.test(t)) ||
    /\bsend\s+to\s+scarlett\b/.test(t) ||
    /\bforward\s+(this|it)\b/.test(t) ||
    t.trim() === 'send'
  );

  const send = () => {
    const raw = input.trim();
    if (!raw || typing || emailForm) return;
    setMessages((m) => [...m, { from: 'user', text: raw }]);
    setInput('');
    const t = raw.toLowerCase();

    let reply;
    let showForm = false;

    if (_wantsSend(t)) {
      showForm = true;
      reply = "*squeak!!* yay!! 📬 I just need a tiny bit of info — fill in the form below and I'll run to Ms. Clatters' inbox!";
    } else if (t.match(/\b(hi|hello|hey|howdy|hiya|sup|yo|heyy)\b/)) {
      reply = _jamonPick([
        "*wheek wheek!* hi hi!! 🐹✨ so happy you're here!",
        "*happy vibrate* HI!! you came to chat with me!! 🎉",
        "*squeak!* hello friend!! I'm Jamón 🐹",
      ]);
    } else if (t.match(/\b(thank|thanks|thx|ty|appreciate|grateful)\b/)) {
      reply = _jamonPick([
        "*happy squeak!* aww 🥹 you're SO sweet!!",
        "*popcorns excitedly* that made my whole tiny day!! 💕",
        "no no YOU'RE wonderful!! *wheek wheek*",
      ]);
    } else if (t.match(/\b(idea|build|feature|cool if|what if|suggest|should make|could add|wish|add a|you should)\b/)) {
      reply = _jamonPick([
        "*ears perk WAY up* 👀 ooh!! tell me EVERYTHING!! take your time — when you're all done just say 'send it' and I'll deliver it straight to her inbox 📬",
        "*vibrating with excitement* 🐹 I love ideas!! keep going!! say 'send it' whenever you're ready and I'll run it over to her 💌",
      ]);
    } else if (t.match(/\b(feedback|bug|broken|issue|problem|annoying|confusing|wrong|fix|improve|doesn't work|not working)\b/)) {
      reply = _jamonPick([
        "*thoughtful nibble* 🌿 I hear you — keep going, get it all out!! when you're done just say 'send it' and I'll make sure she sees it!",
        "*nods tiny head seriously* noted! tell me more — say 'send it' when you're ready and I'll deliver your message 📬",
      ]);
    } else if (t.match(/\b(who|scarlett|she|techclatters|made|built|this site|workshop)\b/)) {
      reply = _jamonPick([
        "Ms. Clatters made all of this! 🛠️ she builds tiny fun things — games, extensions, all sorts of stuff!",
        "this is Ms. Clatters' little workshop 🏠 she makes small software things for fun and curiosity!",
        "oh Ms. Clatters is SO cool (I live with her so I'm an expert 🐹) — she makes lil apps and games!",
      ]);
    } else if (t.match(/\b(cute|adorable|love you|you're great|best|awesome|i like you|you're cool)\b/)) {
      reply = _jamonPick([
        "*blushes furiously* 🥺 stoppp you're making me popcorn!!",
        "*happy squeak intensifies* 💕💕 I LOVE THIS CONVERSATION",
        "I'M TELLING MS. CLATTERS YOU SAID THAT 📢 *wheek*",
      ]);
    } else if (t.match(/\b(food|eat|snack|hungry|hay|pellets|lettuce|carrot|veggie|grass)\b/)) {
      reply = _jamonPick([
        "*perks up IMMEDIATELY* 👀 did someone say... snacks???",
        "*zooms to food bowl* WHEEK WHEEK WHEEK 🥬🥬",
        "I am ALWAYS thinking about food. always. what are we eating. 🥕",
      ]);
    } else if (t.match(/\b(bye|goodbye|cya|see ya|later|gotta go|leaving)\b/)) {
      reply = _jamonPick([
        "*sad squeak* nooo come back soon!! 🥹",
        "byeee!! *waves tiny paw* 🐾💕",
        "*runs after you* wait wait— okay okay. bye!! come back!! 🐹",
        "*dramatically flops* the loneliness... it is too much... (jk bye 💕)",
      ]);
    } else if (t.match(/\b(scarlett|your owner|pet|you live with|where do you live)\b/)) {
      reply = _jamonPick([
        "I live with Ms. Clatters!! she's very nice and gives me veggies 🥬 I approve of her",
        "Ms. Clatters is my human 🐹 she codes a lot and sometimes lets me sit on the desk while she works",
        "*whispers* between you and me... I am her MUSE. all these projects? inspired by my existence. probably.",
      ]);
    } else if (t.match(/\b(sleep|tired|nap|rest|sleepy|yawn|zzz)\b/)) {
      reply = _jamonPick([
        "*does a little loaf* ..zzzz...... hm? OH I'm awake!! totally awake 👀",
        "sleep is IMPORTANT. I take 47 naps a day and I am THRIVING 😤💤",
        "*yawns enormously* oh excuse me... what were you saying... *immediately falls asleep*",
      ]);
    } else if (t.match(/\b(play|game|fun|bored)\b/)) {
      reply = _jamonPick([
        "*zooms in a figure-8* I AM PLAYING RIGHT NOW!! THIS IS IT!! 🐾🐾",
        "you should try the Office Hours game upstairs!! Ms. Clatters made it 👆",
        "*shakes a tiny rattle* I'm SO fun. everyone says so.",
        "I play a game called 'run laps at 3am'. Ms. Clatters is less enthusiastic about it than I am.",
      ]);
    } else if (t.match(/\b(soft|fluffy|floofy|fur|fuzzy|pet you|cuddle|hold)\b/)) {
      reply = _jamonPick([
        "*vibrates intensely* 🐹 yes. YES. I am extremely pettable.",
        "my fur is described as 'soft gray cloud' by experts (me) 🌤",
        "*leans into imaginary pets* ...don't stop... ever...",
      ]);
    } else if (t.match(/\b(how are you|how's it going|you okay|feeling|you good|you alright)\b/)) {
      reply = _jamonPick([
        "*popcorns* GREAT!! 7/10 — could use more parsley, but genuinely thriving!!",
        "I am SPECTACULAR. I ate a carrot earlier. life is good 🥕",
        "doing a little wheek, a little squeak, a little exploring. so: perfect 🐹✨",
      ]);
    } else if (t.match(/\b(name|called|you're|your name|who are you)\b/)) {
      reply = _jamonPick([
        "I'm Jamón!! like the Spanish ham, but I am NOT ham. I am a guinea pig. very important distinction. 🐹",
        "Jamón!! (jah-MOHN) 🐹 named after Spanish cured meat, which feels slightly ominous but I choose not to dwell on it",
      ]);
    } else if (t.match(/\b(weather|outside|rain|sun|cold|hot|temperature)\b/)) {
      reply = _jamonPick([
        "I live inside so all weather is Irrelevant to me 🐹 it is currently: cozy.",
        "ideal temperature: exactly 72°F. anything else is a CRISIS. *wraps in tiny blanket*",
      ]);
    } else {
      reply = _jamonPick([
        "*squeak squeak!* interesting!! tell me more 👂",
        "*happy wheek!* I hear you!! 🐹",
        "*nibble nibble* hmm... *thinks with tiny brain*...",
        "*popcorns!* 🎉",
        "*runs in a tiny circle* I'm listening!!",
        "wheek wheek!! that's fascinating to me, a guinea pig 🐾",
        "*tilts head curiously* yeah? yeah!! *squeak*",
        "*vibrates with excitement* 🐹✨",
        "*pauses mid-munch* oh! yeah! go on!",
        "incredible. truly. *squeaks approvingly*",
        "*does a tiny binky* I just felt happy for no reason!! anyway what were you saying",
        "as a guinea pig, my perspective on this is: wheek wheek 🐾",
      ]);
    }

    addJamon(reply);
    if (showForm) setTimeout(() => setEmailForm(true), 1100);
  };

  const inputStyle = { border: `1.5px solid ${ink}`, padding: '6px 8px', fontFamily: 'inherit', fontSize: 12, background: '#fff', outline: 'none', width: '100%', boxSizing: 'border-box' };

  return (
    <div style={{
      position: 'fixed', right: 16, bottom: 96, width: 320, maxWidth: 'calc(100vw - 32px)', zIndex: 9999,
      background: paper, border: `2px solid ${ink}`,
      boxShadow: `6px 6px 0 ${ink}`, display: 'flex', flexDirection: 'column',
      overflow: 'hidden', fontFamily: 'inherit',
      animation: 'jamon-pop .25s cubic-bezier(.4,1.6,.5,1)',
      transformOrigin: '95% 100%',
    }}>
      {/* header */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '8px 10px', background: '#FFE66B', borderBottom: `2px solid ${ink}`,
        flexShrink: 0,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontWeight: 800, fontSize: 13 }}>
          <span style={{
            display: 'inline-block', width: 9, height: 9, borderRadius: '50%',
            background: '#7BB87A', boxShadow: `0 0 0 1.5px ${ink}`,
            animation: 'jamon-pulse 1.6s ease-in-out infinite',
          }} />
          chat with Jamón 🐹
        </div>
        <button onClick={onClose} aria-label="Close chat" style={{
          border: `1.5px solid ${ink}`, background: paper, cursor: 'pointer',
          width: 22, height: 22, fontWeight: 800, fontSize: 14, padding: 0, lineHeight: 1, borderRadius: 2,
        }}>×</button>
      </div>

      {/* mascot strip */}
      <div style={{
        padding: '6px 10px', borderBottom: `1.5px dashed ${ink}`,
        background: '#fffdf5', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
      }}>
        <JamonFront size={40} />
        <div style={{ fontSize: 11, color: '#7a6a55', fontWeight: 600, letterSpacing: '.04em' }}>
          chatting with <b style={{ color: ink }}>jamón</b> · <span style={{ color: accent }}>online</span>
        </div>
      </div>

      {/* messages */}
      <div ref={msgsRef} style={{
        padding: 12, maxHeight: 220, overflowY: 'auto',
        display: 'flex', flexDirection: 'column', gap: 8,
        fontSize: 13, lineHeight: 1.4,
      }}>
        {messages.map((m, i) => (
          <div key={i} style={{
            alignSelf: m.from === 'user' ? 'flex-end' : 'flex-start',
            maxWidth: '85%',
            background: m.from === 'user' ? '#9DC3FF' : '#fff',
            border: `1.5px solid ${ink}`, padding: '6px 10px', borderRadius: 6,
            whiteSpace: 'pre-wrap',
          }}>{m.text}</div>
        ))}
        {typing && (
          <div style={{
            alignSelf: 'flex-start', background: '#fff',
            border: `1.5px solid ${ink}`, padding: '6px 12px', borderRadius: 6,
            fontSize: 18, letterSpacing: 3, color: '#999',
          }}>···</div>
        )}
        <div ref={endRef} />
      </div>

      {/* email form — shown after user says "send to Scarlett" */}
      {emailForm && (
        <div style={{
          padding: 12, borderTop: `2px solid ${ink}`,
          background: '#fffdf5', display: 'flex', flexDirection: 'column', gap: 8, flexShrink: 0,
        }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: ink }}>*squeak!* who should Ms. Clatters reply to? 🐹</div>
          <input
            autoFocus
            value={formName}
            onChange={(e) => setFormName(e.target.value)}
            placeholder="your name"
            style={inputStyle}
          />
          <input
            value={formEmail}
            onChange={(e) => setFormEmail(e.target.value)}
            placeholder="your email"
            type="email"
            style={inputStyle}
          />
          <div style={{ fontSize: 11, fontWeight: 700, color: '#7a6a55', marginTop: 2 }}>anything else to add? (optional)</div>
          <textarea
            value={formExtra}
            onChange={(e) => setFormExtra(e.target.value)}
            placeholder="extra note for Scarlett…"
            rows={2}
            style={{ ...inputStyle, resize: 'none' }}
          />
          <div style={{ display: 'flex', gap: 6 }}>
            <button onClick={() => setEmailForm(false)} style={{
              flex: 1, background: paper, border: `1.5px solid ${ink}`, padding: '6px 0',
              fontFamily: 'inherit', fontWeight: 700, fontSize: 11, cursor: 'pointer', borderRadius: 2,
            }}>cancel</button>
            <button
              onClick={submitEmailForm}
              disabled={sending || !formName.trim() || !formEmail.trim()}
              style={{
                flex: 2, background: ink, color: paper, border: `1.5px solid ${ink}`,
                padding: '6px 0', fontFamily: 'inherit', fontWeight: 800, fontSize: 11,
                cursor: 'pointer', letterSpacing: '.04em', borderRadius: 2,
                opacity: (!formName.trim() || !formEmail.trim()) ? 0.45 : 1,
              }}>{sending ? 'SENDING…' : 'SEND TO SCARLETT 📬'}</button>
          </div>
        </div>
      )}

      {/* chat input — hidden while email form is open */}
      {!emailForm && (
        <div style={{
          padding: 10, borderTop: `2px dashed ${ink}`, display: 'flex', gap: 6,
          background: '#fffdf5', flexShrink: 0,
        }}>
          <textarea
            ref={taRef}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
            placeholder="say anything…"
            rows={2}
            style={{
              flex: 1, resize: 'none', border: `1.5px solid ${ink}`, padding: 6,
              fontFamily: 'inherit', fontSize: 12, background: '#fff', outline: 'none',
            }}
          />
          <button onClick={send} style={{
            background: ink, color: paper, border: `1.5px solid ${ink}`,
            padding: '0 12px', fontWeight: 800, fontSize: 11, cursor: 'pointer',
            letterSpacing: '.05em', borderRadius: 2,
          }}>SEND</button>
        </div>
      )}

      <style>{`
        @keyframes jamon-pop {
          from { transform: scale(.8) translateY(8px); opacity: 0; }
          to   { transform: scale(1) translateY(0); opacity: 1; }
        }
        @keyframes jamon-pulse {
          0%, 100% { transform: scale(1); }
          50% { transform: scale(1.4); }
        }
      `}</style>
    </div>
  );
}

// Fixed bottom-right trigger button — always on screen, opens/closes the chat.
function JamonFloatingTrigger({ open, onClick }) {
  const ink = '#1f1a14';
  return (
    <button
      onClick={onClick}
      aria-label={open ? 'Close chat' : 'Chat with Jamón'}
      style={{
        position: 'fixed', bottom: 24, right: 16, zIndex: 9999,
        width: 64, height: 64, border: `2px solid ${ink}`, borderRadius: '50%',
        background: '#FFE66B', boxShadow: `3px 3px 0 ${ink}`,
        cursor: 'pointer', padding: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        animation: open ? 'none' : 'jamon-bob 3.6s ease-in-out infinite',
        transformOrigin: '50% 90%',
        transition: 'box-shadow .15s, transform .15s',
      }}
    >
      {open
        ? <span style={{ fontSize: 24, fontWeight: 800, color: ink, lineHeight: 1 }}>×</span>
        : <JamonFront size={48} />
      }
    </button>
  );
}

// ─────────────────────────────────────────────────────────────
// Jamón pose sprites — front (face), back (away), lay (resting)
// All scaled to a uniform ~140×120 box. Side view is JamonGuineaPig.
// ─────────────────────────────────────────────────────────────
const JAMON_INK = '#1f1a14';
const JAMON_FUR = '#a4a3a8';
const JAMON_FUR_DARK = '#74737a';
const JAMON_FUR_LIGHT = '#e3e2e6';
const JAMON_CHEEK = '#FF9CA0';
const JAMON_CAST = '#FFE66B';

function JamonFront({ size = 120, squish = false, walking = false, happy = false }) {
  const s = size, w = 140, h = 140;
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width={s} height={s * h / w} style={{ display: 'block', overflow: 'visible' }}>
      <ellipse cx="70" cy="132" rx="42" ry="5" fill={JAMON_INK} opacity="0.12" />
      <ellipse cx="44" cy="38" rx="10" ry="14" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" transform="rotate(-15 44 38)" />
      <ellipse cx="96" cy="38" rx="10" ry="14" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" transform="rotate(15 96 38)" />
      <ellipse cx="44" cy="40" rx="4.5" ry="7" fill={JAMON_CHEEK} opacity=".55" transform="rotate(-15 44 40)" />
      <ellipse cx="96" cy="40" rx="4.5" ry="7" fill={JAMON_CHEEK} opacity=".55" transform="rotate(15 96 40)" />
      <ellipse cx="70" cy="80" rx="50" ry="46" fill={JAMON_FUR} stroke={JAMON_INK} strokeWidth="2.5" />
      <ellipse cx="70" cy="100" rx="32" ry="22" fill={JAMON_FUR_LIGHT} opacity=".55" />
      <path d="M58 32 Q62 24 66 32" fill="none" stroke={JAMON_INK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M74 32 Q78 24 82 32" fill="none" stroke={JAMON_INK} strokeWidth="1.4" strokeLinecap="round" />
      {/* eyes — squish: >< | happy: ^^ closed | normal: blink */}
      {squish ? (
        <>
          <path d="M49 58 L54 63 L49 68" fill="none" stroke={JAMON_INK} strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round" />
          <path d="M91 58 L86 63 L91 68" fill="none" stroke={JAMON_INK} strokeWidth="2.8" strokeLinecap="round" strokeLinejoin="round" />
        </>
      ) : happy ? (
        <>
          <path d="M49 65 Q54 59 59 65" fill="none" stroke={JAMON_INK} strokeWidth="2.4" strokeLinecap="round" />
          <path d="M81 65 Q86 59 91 65" fill="none" stroke={JAMON_INK} strokeWidth="2.4" strokeLinecap="round" />
        </>
      ) : (
        <>
          <circle cx="54" cy="62" r="4" fill={JAMON_INK} />
          <circle cx="55.5" cy="60.5" r="1.2" fill="#fff" />
          <circle cx="86" cy="62" r="4" fill={JAMON_INK} />
          <circle cx="87.5" cy="60.5" r="1.2" fill="#fff" />
          {/* blink overlays */}
          <rect x="50" y="58" width="8" height="8" rx="4" fill={JAMON_FUR}
            style={{ animation: 'jamon-front-blink 6.1s infinite', transformBox: 'fill-box', transformOrigin: 'center' }} />
          <rect x="82" y="58" width="8" height="8" rx="4" fill={JAMON_FUR}
            style={{ animation: 'jamon-front-blink 6.1s infinite', transformBox: 'fill-box', transformOrigin: 'center' }} />
        </>
      )}
      <circle cx="44" cy="78" r="6" fill={JAMON_CHEEK} opacity=".55" />
      <circle cx="96" cy="78" r="6" fill={JAMON_CHEEK} opacity=".55" />
      <path d="M65 78 Q70 84 75 78 Q70 80 65 78 Z" fill={JAMON_INK} />
      <path d="M70 80 L70 86" stroke={JAMON_INK} strokeWidth="1.4" strokeLinecap="round" fill="none" />
      <path d="M64 90 Q70 86 76 90" stroke={JAMON_INK} strokeWidth="1.4" strokeLinecap="round" fill="none" />
      <ellipse cx="50" cy="124" rx="9" ry="6" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2"
        style={walking ? { animation: 'jamon-foot-a 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
      <ellipse cx="90" cy="124" rx="9" ry="6" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2"
        style={walking ? { animation: 'jamon-foot-b 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
      <style>{`
        @keyframes jamon-front-blink {
          0%, 90%, 100% { transform: scaleY(0); }
          93%, 97% { transform: scaleY(1); }
        }
      `}</style>
    </svg>
  );
}

function JamonBack({ size = 120, walking = false }) {
  const s = size, w = 140, h = 140;
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width={s} height={s * h / w} style={{ display: 'block', overflow: 'visible' }}>
      <ellipse cx="70" cy="132" rx="42" ry="5" fill={JAMON_INK} opacity="0.12" />
      <ellipse cx="50" cy="38" rx="9" ry="12" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" />
      <ellipse cx="90" cy="38" rx="9" ry="12" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" />
      <ellipse cx="70" cy="80" rx="50" ry="46" fill={JAMON_FUR} />
      <ellipse cx="40" cy="108" rx="22" ry="18" fill={JAMON_CAST} />
      <ellipse cx="70" cy="80" rx="50" ry="46" fill="none" stroke={JAMON_INK} strokeWidth="2.5" />
      <path d="M40 56 Q44 50 48 56" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M58 50 Q62 44 66 50" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M74 50 Q78 44 82 50" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M92 56 Q96 50 100 56" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M55 76 Q60 70 65 76" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1" opacity=".55" />
      <path d="M75 76 Q80 70 85 76" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1" opacity=".55" />
      <ellipse cx="70" cy="108" rx="5" ry="3.5" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="1.5" />
      <ellipse cx="92" cy="124" rx="9" ry="6" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2"
        style={walking ? { animation: 'jamon-foot-a 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
      <ellipse cx="48" cy="126" rx="9" ry="6" fill={JAMON_CAST} stroke={JAMON_INK} strokeWidth="2"
        style={walking ? { animation: 'jamon-foot-b 0.38s ease-in-out infinite alternate', transformBox: 'fill-box', transformOrigin: 'center' } : {}} />
    </svg>
  );
}

function JamonLay({ size = 160 }) {
  const w = 265, h = 156;
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width={size} height={size * h / w} style={{ display: 'block', overflow: 'visible' }}>
      <defs>
        <clipPath id="jamon-lay-body-clip">
          <ellipse cx="115" cy="94" rx="88" ry="48" />
        </clipPath>
      </defs>
      {/* shadow */}
      <ellipse cx="122" cy="150" rx="96" ry="6" fill={JAMON_INK} opacity="0.12" />

      {/* back left leg kicked out — drawn first so body overlaps the root */}
      {/* hip: center sits inside the body so it visibly roots there */}
      <ellipse cx="38" cy="108" rx="26" ry="13" fill={JAMON_CAST} stroke={JAMON_INK} strokeWidth="2" transform="rotate(-38 38 108)" />
      {/* lower leg + paw: positioned to overlap the hip's lower-left end */}
      <ellipse cx="16" cy="128" rx="19" ry="10" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" transform="rotate(-22 16 128)" />

      {/* yellow patch on butt — clipped to body */}
      <ellipse cx="46" cy="100" rx="36" ry="26" fill={JAMON_CAST} clipPath="url(#jamon-lay-body-clip)" />

      {/* body — fat round loaf */}
      <ellipse cx="115" cy="94" rx="88" ry="48" fill={JAMON_FUR} />
      <ellipse cx="115" cy="94" rx="88" ry="48" fill="none" stroke={JAMON_INK} strokeWidth="2.5" />

      {/* tummy highlight */}
      <ellipse cx="118" cy="114" rx="66" ry="20" fill={JAMON_FUR_LIGHT} opacity=".5" />

      {/* fur tufts on back */}
      <path d="M50 68 Q54 61 58 68" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M84 56 Q88 49 92 56" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />
      <path d="M120 52 Q124 45 128 52" fill="none" stroke={JAMON_FUR_DARK} strokeWidth="1.4" strokeLinecap="round" />

      {/* front tucked feet peeking at front-right */}
      <ellipse cx="188" cy="130" rx="11" ry="6" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="1.8" />
      <ellipse cx="210" cy="126" rx="11" ry="6" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="1.8" />

      {/* head */}
      <circle cx="202" cy="82" r="44" fill={JAMON_FUR} stroke={JAMON_INK} strokeWidth="2.5" />

      {/* ear */}
      <g transform="rotate(-28 186 48)">
        <ellipse cx="186" cy="48" rx="11" ry="14" fill={JAMON_FUR_DARK} stroke={JAMON_INK} strokeWidth="2" />
        <ellipse cx="186" cy="50" rx="5" ry="8" fill={JAMON_CHEEK} opacity=".55" />
      </g>

      {/* happy closed sleepy eyes */}
      <path d="M197 78 Q202 74 207 78" fill="none" stroke={JAMON_INK} strokeWidth="2.4" strokeLinecap="round" />

      {/* cheek blush */}
      <circle cx="216" cy="94" r="6" fill={JAMON_CHEEK} opacity=".55" />

      {/* nose */}
      <path d="M226 86 Q230 88 226 90 Q223 88 226 86 Z" fill={JAMON_INK} />

      {/* tiny content smile */}
      <path d="M220 94 Q226 91 232 94" fill="none" stroke={JAMON_INK} strokeWidth="1.4" strokeLinecap="round" />

      {/* whisker */}
      <line x1="219" y1="90" x2="235" y2="88" stroke={JAMON_INK} strokeWidth=".7" strokeLinecap="round" />

      {/* Z's */}
      <text x="240" y="52" fontSize="17" fontWeight="800" fill={JAMON_INK} fontFamily="'Caveat', cursive">z</text>
      <text x="252" y="36" fontSize="12" fontWeight="800" fill={JAMON_INK} fontFamily="'Caveat', cursive">z</text>
    </svg>
  );
}

function JamonSprite({ pose = 'right', size = 120, squish = false, happy = false }) {
  const walking = pose === 'right' || pose === 'left' || pose === 'up' || pose === 'down';
  if (pose === 'lay') return <JamonLay size={size * 1.4} />;
  if (pose === 'front' || pose === 'down' || pose === 'sit') return <JamonFront size={size} squish={squish} walking={walking} happy={happy} />;
  if (pose === 'back' || pose === 'up') return <JamonBack size={size} walking={walking} />;
  return (
    <div style={{ transform: pose === 'left' ? 'scaleX(-1)' : 'none', display: 'inline-block' }}>
      <JamonGuineaPig size={size * 1.2} idle={false} walking={walking} squish={squish} />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Guinea pig house — decorative house Jamon can enter.
// ─────────────────────────────────────────────────────────────
function JamonHouse({ size = 110, jamonInside = false, style: extraStyle = {}, onClick }) {
  const ink = '#1f1a14';
  return (
    <div style={{ display: 'inline-block', ...extraStyle, cursor: onClick ? 'pointer' : 'default' }}
      title={jamonInside ? "Jamón is cozy inside 🐹" : "Click to call Jamón home 🐹"}
      onClick={onClick}>
      <svg viewBox="0 0 80 76" width={size} height={size * 76 / 80} style={{ display: 'block', overflow: 'visible' }}>
        {/* shadow */}
        <ellipse cx="40" cy="74" rx="28" ry="4" fill={ink} opacity="0.10" />
        {/* chimney */}
        <rect x="52" y="8" width="10" height="22" rx="1" fill="#7BB87A" stroke={ink} strokeWidth="1.8" />
        {/* house body */}
        <rect x="6" y="36" width="68" height="38" rx="2" fill="#C9A2E6" stroke={ink} strokeWidth="2" />
        {/* roof */}
        <polygon points="2,36 40,4 78,36" fill="#FF7A6B" stroke={ink} strokeWidth="2" strokeLinejoin="round" />
        {/* roof ridge */}
        <line x1="40" y1="4" x2="40" y2="36" stroke={ink} strokeWidth="1" opacity="0.3" />
        {/* window — right side of door */}
        <rect x="54" y="44" width="16" height="14" rx="2" fill="#9DC3FF" stroke={ink} strokeWidth="1.5" />
        <line x1="62" y1="44" x2="62" y2="58" stroke={ink} strokeWidth="1" />
        <line x1="54" y1="51" x2="70" y2="51" stroke={ink} strokeWidth="1" />
        {/* door arch — shifted 5px left of centre */}
        <path d="M 23 74 L 23 55 Q 23 47 35 47 Q 47 47 47 55 L 47 74 Z" fill="#FFE66B" stroke={ink} strokeWidth="1.5" />
        {/* name sign */}
        <rect x="20" y="24" width="40" height="13" rx="2" fill="#FBF7EE" stroke={ink} strokeWidth="1.5" />
        <text x="40" y="34" fontSize="7.5" fontWeight="800" fill={ink} fontFamily="inherit" textAnchor="middle">JAMÓN'S</text>
      </svg>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// JamonRoamer — wanders a rectangular bounds. Walks (with directional
// pose), sits to show face, lays at top-right corner. Click → onClick.
// ─────────────────────────────────────────────────────────────
function JamonRoamer({ width = 1280, height = 800, onClick, paused = false, hint = "click me — i'm jamón, a guinea pig 🐹", fixedViewport = false, onEnterHouse, onLeaveHouse, goHomeTrigger = 0, houseRef = null, containerRef = null }) {
  const PAD = fixedViewport ? 14 : 36;
  const SPRITE = fixedViewport ? 68 : 90;
  const SPEED = 0.035;
  // Use actual rendered sprite width so Jamon never overflows container edges
  const SPRITE_W = fixedViewport ? Math.ceil(SPRITE * 1.25) : Math.ceil(SPRITE * 1.35);
  const minX = PAD;
  const maxX = fixedViewport ? width - PAD - SPRITE_W - 90 : width - PAD - SPRITE_W - 20;
  const minY = fixedViewport ? 70 : 90;
  const maxY = fixedViewport ? height - 120 : height - 60 - 100;
  const layX = fixedViewport ? minX + 20 : maxX - 30;
  const layY = minY + 10;
  const showHouse = !fixedViewport && width >= 1024;
  // Fallback house coords (used if refs not yet available)
  const houseX = fixedViewport ? width - 90 : maxX - 60;
  const houseY = fixedViewport ? 18 : 270;

  // Measure actual house position from DOM — avoids grid-math guessing.
  // getBoundingClientRect is scroll-invariant when both elements are in the same flow.
  const getHouseTarget = () => {
    if (houseRef?.current && containerRef?.current) {
      const hr = houseRef.current.getBoundingClientRect();
      const cr = containerRef.current.getBoundingClientRect();
      if (hr.width > 0) {
        // Door center x = 50% of house width; door bottom y = 74/76 of house height
        const x = Math.max(minX, Math.min(maxX, Math.round((hr.left - cr.left) + hr.width * (35 / 80) - SPRITE_W / 2)));
        const y = Math.max(minY, Math.round((hr.top - cr.top) + hr.height * (74 / 76) - SPRITE));
        return { x, y };
      }
    }
    return { x: houseX, y: houseY };
  };
  const getHouseTargetRef = React.useRef(getHouseTarget);
  getHouseTargetRef.current = getHouseTarget;

  const [pos, setPos] = React.useState(() => ({ x: maxX - 200, y: minY + 20 }));
  const [pose, setPose] = React.useState('right');
  const [showHint, setShowHint] = React.useState(true);
  const [squish, setSquish] = React.useState(false);
  const [inHouse, setInHouse] = React.useState(false);

  const stateRef = React.useRef({ pos, pose });
  stateRef.current = { pos, pose };
  const pausedRef = React.useRef(paused);
  const inHouseRef = React.useRef(false);
  const goHomeTriggerRef = React.useRef(goHomeTrigger);
  const lastGoHomeRef = React.useRef(goHomeTrigger);
  const fastHomeRef = React.useRef(false);
  const draggingRef = React.useRef(false);
  const hasDraggedRef = React.useRef(false);
  const dragLastRef = React.useRef({ x: 0, y: 0 });
  React.useEffect(() => { goHomeTriggerRef.current = goHomeTrigger; }, [goHomeTrigger]);
  React.useEffect(() => {
    pausedRef.current = paused;
    if (paused) setPose('front');
  }, [paused]);

  React.useEffect(() => {
    let raf;
    let mode = 'walk'; // walk | walk_to_lay | walk_to_house | sit | lay | in_house
    let modeUntil = 0;
    let target = { x: minX + Math.random() * (maxX - minX), y: minY + Math.random() * (maxY - minY) };
    let lastT = performance.now();
    let frame = 0;

    const step = (now) => {
      if (pausedRef.current) { lastT = now; raf = requestAnimationFrame(step); return; }
      if (draggingRef.current) { lastT = now; raf = requestAnimationFrame(step); return; }
      const dt = Math.min(50, now - lastT);
      lastT = now;
      frame++;
      const cur = stateRef.current.pos;

      // Handle double-click "go home" trigger — walk at 2× speed
      if (showHouse && goHomeTriggerRef.current !== lastGoHomeRef.current && mode !== 'in_house') {
        lastGoHomeRef.current = goHomeTriggerRef.current;
        mode = 'walk_to_house';
        target = getHouseTargetRef.current();
        fastHomeRef.current = true;
      }

      if (mode === 'walk' || mode === 'walk_to_lay' || mode === 'walk_to_house') {
        const dx = target.x - cur.x;
        const dy = target.y - cur.y;
        const dist = Math.hypot(dx, dy);
        if (dist < 6) {
          if (mode === 'walk_to_lay') {
            mode = 'lay';
            modeUntil = now + 4500 + Math.random() * 3500;
            setPose('lay');
          } else if (mode === 'walk_to_house') {
            mode = 'in_house';
            modeUntil = now + 5000 + Math.random() * 4000;
            setPose('front');
            inHouseRef.current = true;
            fastHomeRef.current = false;
            setInHouse(true);
            onEnterHouse?.();
          } else {
            const r = Math.random();
            if (showHouse && r < 0.15) {
              target = getHouseTargetRef.current();
              mode = 'walk_to_house';
              fastHomeRef.current = false;
            } else if (r < 0.38) {
              target = { x: layX, y: layY };
              mode = 'walk_to_lay';
            } else if (r < 0.65) {
              mode = 'sit';
              modeUntil = now + 1800 + Math.random() * 1800;
              setPose('front');
            } else {
              target = { x: minX + Math.random() * (maxX - minX), y: minY + Math.random() * (maxY - minY) };
            }
          }
        } else {
          const speed = (fastHomeRef.current && mode === 'walk_to_house') ? SPEED * 1.5 : SPEED;
          const move = Math.min(dist, speed * dt);
          const nx = cur.x + (dx / dist) * move;
          const ny = cur.y + (dy / dist) * move;
          if (frame % 4 === 0) {
            let next;
            if (Math.abs(dx) > Math.abs(dy) * 1.2) next = dx > 0 ? 'right' : 'left';
            else next = dy > 0 ? 'down' : 'up';
            if (next !== stateRef.current.pose) setPose(next);
          }
          setPos({ x: nx, y: ny });
        }
      } else if (mode === 'sit' || mode === 'lay') {
        if (now > modeUntil) {
          mode = 'walk';
          target = { x: minX + Math.random() * (maxX - minX), y: minY + Math.random() * (maxY - minY) };
        }
      } else if (mode === 'in_house') {
        if (now > modeUntil) {
          mode = 'walk';
          inHouseRef.current = false;
          setInHouse(false);
          onLeaveHouse?.();
          setPos({ x: houseX - SPRITE - 10, y: houseY + 20 });
          target = { x: minX + Math.random() * (maxX - minX), y: minY + Math.random() * (maxY - minY) };
        }
      }
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [width, height]);

  React.useEffect(() => {
    const t = setTimeout(() => setShowHint(false), 5500);
    return () => clearTimeout(t);
  }, []);

  const handlePointerDown = (e) => {
    const cx = e.touches ? e.touches[0].clientX : e.clientX;
    const cy = e.touches ? e.touches[0].clientY : e.clientY;
    hasDraggedRef.current = false;
    draggingRef.current = true;
    dragLastRef.current = { x: cx, y: cy };
    document.body.style.cursor = 'grabbing';
    document.body.style.userSelect = 'none';
    const onMove = (ev) => {
      if (!draggingRef.current) return;
      if (ev.cancelable) ev.preventDefault();
      const cx2 = ev.touches ? ev.touches[0].clientX : ev.clientX;
      const cy2 = ev.touches ? ev.touches[0].clientY : ev.clientY;
      const dx = cx2 - dragLastRef.current.x;
      const dy = cy2 - dragLastRef.current.y;
      dragLastRef.current = { x: cx2, y: cy2 };
      if (dx !== 0 || dy !== 0) hasDraggedRef.current = true;
      setPos(prev => ({ x: prev.x + dx, y: prev.y + dy }));
    };
    const onUp = () => {
      draggingRef.current = false;
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
      setPose('front');
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('touchend', onUp);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('touchmove', onMove, { passive: false });
    window.addEventListener('mouseup', onUp);
    window.addEventListener('touchend', onUp);
  };

  const handleClick = () => {
    if (hasDraggedRef.current) return;
    setShowHint(false);
    setSquish(true);
    setTimeout(() => setSquish(false), 700);
    onClick?.();
  };

  return (
    <>
      <div
        onClick={handleClick}
        onMouseDown={handlePointerDown}
        onTouchStart={handlePointerDown}
        style={{
          position: 'absolute',
          left: pos.x, top: pos.y,
          width: SPRITE + 20, height: SPRITE + 10,
          cursor: 'grab', zIndex: 40,
          userSelect: 'none',
        }}
      >
        <JamonSprite pose={pose} size={pose === 'lay' ? SPRITE + 20 : SPRITE} squish={squish} happy={inHouse} />
        {showHint && pose === 'front' && !inHouse && (
          <div style={{
            position: 'absolute', left: 80, top: -12,
            background: '#fff', border: `2px solid ${JAMON_INK}`, padding: '4px 8px',
            borderRadius: 8, fontSize: 11, whiteSpace: 'nowrap', color: JAMON_INK,
            boxShadow: `2px 2px 0 ${JAMON_INK}`, fontWeight: 700,
            pointerEvents: 'none', fontFamily: 'inherit',
          }}>
            {hint}
          </div>
        )}
      </div>
    </>
  );
}

Object.assign(window, { PROJECTS, ABOUT, PixelBuddy, AnagramReveal, FeedTheKitty, CursorTrail, ConfettiDots, Wiggle, OfficeHoursClock, JamonGuineaPig, JamonChat, JamonFloatingTrigger, JamonSprite, JamonRoamer, JamonFront, JamonBack, JamonLay, JamonHouse });
