// Shared components: nav, logo, garments, product card, footer, now-playing

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

// ───────────────────────── icons ─────────────────────────
const Icon = {
  menu:   (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" {...p}><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg>,
  search: (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" {...p}><circle cx="10.5" cy="10.5" r="6"/><line x1="15" y1="15" x2="20.5" y2="20.5"/></svg>,
  cart:   (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M1 22 L1 4 L5 4 L19 12 L21 12 L21 22 Z"/><line x1="5" y1="4" x2="3" y2="-4"/><line x1="7.5" y1="5.4" x2="5.5" y2="-2.6"/><line x1="10" y1="6.9" x2="8" y2="-1.1"/><line x1="12.5" y1="8.3" x2="10.5" y2="0.3"/><line x1="15" y1="9.7" x2="13" y2="1.7"/><line x1="17.5" y1="11.1" x2="15.5" y2="3.1"/><line x1="19" y1="12" x2="17" y2="4"/></svg>,
  heart:  (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 20C12 20 4.5 15 4.5 9.5a4.5 4.5 0 0 1 7.5-3.35A4.5 4.5 0 0 1 19.5 9.5C19.5 15 12 20 12 20Z"/></svg>,
  heartFill:(p)=> <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 20C12 20 4.5 15 4.5 9.5a4.5 4.5 0 0 1 7.5-3.35A4.5 4.5 0 0 1 19.5 9.5C19.5 15 12 20 12 20Z"/></svg>,
  user:   (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.4" {...p}><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>,
  close:  (p) => <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.4" {...p}><path d="M6 6 18 18M18 6 6 18"/></svg>,
  arrow:  (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.4" {...p}><path d="M5 12h14M13 6l6 6-6 6"/></svg>,
  plus:   (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.4" {...p}><path d="M12 5v14M5 12h14"/></svg>,
  minus:  (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.4" {...p}><path d="M5 12h14"/></svg>,
  star:   (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" {...p}><path d="m12 2 2.95 6.65 7.05.8-5.3 4.8 1.6 7.05L12 17.7l-6.3 3.6 1.6-7.05L2 9.45l7.05-.8L12 2Z"/></svg>,
  yt:     (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M23 7.5s-.2-1.6-.9-2.3c-.9-.9-1.8-.9-2.3-1C16.5 4 12 4 12 4s-4.5 0-7.8.2c-.5.1-1.4.1-2.3 1C1.2 5.9 1 7.5 1 7.5S.8 9.3.8 11.2v1.6c0 1.9.2 3.7.2 3.7s.2 1.6.9 2.3c.9.9 2.1.9 2.6 1 1.9.2 8 .2 8 .2s4.5 0 7.8-.2c.5-.1 1.4-.1 2.3-1 .7-.7.9-2.3.9-2.3s.2-1.8.2-3.7v-1.6c0-1.9-.2-3.7-.2-3.7ZM10 15V8l6 3.5-6 3.5Z"/></svg>,
  ig:     (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" {...p}><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r=".8" fill="currentColor"/></svg>,
  sc:     (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M20.66 10.71a3.82 3.82 0 0 0-.68.07 5.47 5.47 0 0 0-5.39-4.4 5.56 5.56 0 0 0-2 .37.84.84 0 0 0-.6.8v9a.84.84 0 0 0 .8.83h7.85a3.33 3.33 0 0 0 0-6.66Z"/><path d="M9.74 7.11a.75.75 0 0 0-.74.75v9a.75.75 0 0 0 1.5 0v-9a.76.76 0 0 0-.76-.75Z"/><path d="M6.75 8.61a.75.75 0 0 0-.75.75v7.51a.75.75 0 0 0 1.5 0V9.36a.76.76 0 0 0-.75-.75Z"/><path d="M3.75 10.61a.76.76 0 0 0-.75.75v5.51a.75.75 0 0 0 1.5 0v-5.51a.75.75 0 0 0-.75-.75Z"/><path d="M.76 11.61a.76.76 0 0 0-.75.75v4a.75.75 0 0 0 .75.75.74.74 0 0 0 .75-.75v-4a.75.75 0 0 0-.75-.75Z"/></svg>,
  tt:     (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M19 8.4a6.4 6.4 0 0 1-4-1.4v8.3a5.7 5.7 0 1 1-5.7-5.7c.4 0 .7 0 1 .1v3a2.8 2.8 0 1 0 1.9 2.6V2h3a4.4 4.4 0 0 0 3.8 3.7v2.7Z"/></svg>,
  spotify:(p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm4.6 14.4a.6.6 0 0 1-.9.2c-2.4-1.4-5.4-1.8-9-1a.6.6 0 1 1-.3-1.2c4-.9 7.3-.5 10 1.1a.6.6 0 0 1 .2.9Zm1.2-2.6a.8.8 0 0 1-1.1.2c-2.8-1.7-7-2.2-10.3-1.2a.8.8 0 1 1-.4-1.6c3.8-1.1 8.4-.6 11.6 1.4.4.2.5.7.2 1.1Zm.1-2.7c-3.3-2-8.8-2.2-12-1.2a1 1 0 1 1-.6-1.9c3.7-1.1 9.7-.9 13.6 1.4a1 1 0 0 1-1 1.7Z"/></svg>,
  patreon:(p)=> <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M22.957 7.21c-.004-3.064-2.391-5.576-5.191-6.482-3.478-1.125-8.064-.962-11.384.604C2.357 3.231 1.093 7.391 1.046 11.54c-.039 3.411.302 12.396 5.369 12.46 3.765.047 4.326-4.804 6.068-7.141 1.24-1.662 2.836-2.132 4.801-2.618 3.376-.836 5.678-3.501 5.673-7.031Z"/></svg>,
  discord:(p)=> <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>,
  coffee: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path fillRule="evenodd" d="M6 2h12l-1.5 4H7.5L6 2zM5.5 7h13v2H5.5zM7 10l2 12h6l2-12H7z"/></svg>,
  trash:  (p) => <svg viewBox="0 0 512 512" width="14" height="14" fill="currentColor" {...p}><g transform="translate(64,42.667)"><path fillRule="evenodd" d="M256,42.667 L128,42.667 L128,0 L256,0 L256,42.667 Z M170.667,170.667 L128,170.667 L128,341.333 L170.667,341.333 L170.667,170.667 Z M256,170.667 L213.333,170.667 L213.333,341.333 L256,341.333 L256,170.667 Z M384,85.333 L384,128 L341.333,128 L341.333,426.667 L42.667,426.667 L42.667,128 L0,128 L0,85.333 L384,85.333 Z"/></g></svg>,
  check:  (p) => <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" {...p}><polyline points="20 6 9 17 4 12"/></svg>,
};

// ───────────────────────── garment SVGs ─────────────────────────
// Stylized, monochromatic apparel silhouettes. Designed to read as
// editorial product photography placeholders. User can drop in real
// images via <image-slot>.
function Garment({ kind, color = 'currentColor', graphic = true, back = false }) {
  // viewBox 0 0 200 240
  const common = { width: '100%', height: '100%', viewBox: '0 0 200 240', preserveAspectRatio: 'xMidYMid meet' };
  const stroke = 'currentColor';
  const fill = color;

  const Label = () => (
    <g opacity="0.85">
      <text x="100" y="122" textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="7" letterSpacing="3" fill="currentColor">PVL</text>
      <line x1="82" y1="126" x2="118" y2="126" stroke="currentColor" strokeWidth="0.6"/>
    </g>
  );
  const BackGraphic = () => (
    <g opacity="0.7">
      <circle cx="100" cy="130" r="24" fill="none" stroke="currentColor" strokeWidth="0.8"/>
      <circle cx="100" cy="130" r="17" fill="none" stroke="currentColor" strokeWidth="0.8"/>
      <circle cx="100" cy="130" r="10" fill="none" stroke="currentColor" strokeWidth="0.8"/>
      <circle cx="100" cy="130" r="5"  fill="currentColor"/>
    </g>
  );

  if (kind === 'tee' || kind === 'tee-crop') {
    const bottom = kind === 'tee-crop' ? 175 : 220;
    return (
      <svg {...common}>
        <path d={`M40 70 L70 50 Q85 56 100 56 Q115 56 130 50 L160 70 L150 95 L138 90 L138 ${bottom} L62 ${bottom} L62 90 L50 95 Z`}
              fill={fill} stroke={stroke} strokeWidth="1.2"/>
        <path d="M82 56 Q100 70 118 56" fill="none" stroke={stroke} strokeWidth="1"/>
        <path d="M70 50 L75 72 M130 50 L125 72" fill="none" stroke={stroke} strokeWidth="0.6" opacity="0.5"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'longsleeve') {
    return (
      <svg {...common}>
        <path d="M30 70 L70 50 Q85 56 100 56 Q115 56 130 50 L170 70 L168 175 L155 180 L150 95 L138 92 L138 220 L62 220 L62 92 L50 95 L45 180 L32 175 Z"
              fill={fill} stroke={stroke} strokeWidth="0.6"/>
        <path d="M76 54 Q100 64 124 54 L122 60 Q100 68 78 60 Z" fill={stroke} opacity="0.18"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'hoodie' || kind === 'hoodie-zip' || kind === 'pullover') {
    return (
      <svg {...common}>
        {kind === 'hoodie' && (
          <path d="M76 36 Q100 22 124 36 Q132 50 130 60 L70 60 Q68 50 76 36 Z"
                fill={fill} stroke={stroke} strokeWidth="1"/>
        )}
        <path d="M38 76 L70 58 L130 58 L162 76 L154 100 L142 94 L142 220 L58 220 L58 94 L46 100 Z"
              fill={fill} stroke={stroke} strokeWidth="1.2"/>
        {kind === 'hoodie' && (
          <path d="M80 62 Q100 86 120 62" fill="none" stroke={stroke} strokeWidth="0.8"/>
        )}
        {kind === 'pullover' && (
          <>
            <path d="M82 58 Q100 72 118 58 L116 64 Q100 74 84 64 Z" fill={stroke} opacity="0.22"/>
            <path d="M82 58 Q100 72 118 58" fill="none" stroke={stroke} strokeWidth="0.6"/>
          </>
        )}
        {kind === 'hoodie' && (
          <>
            <line x1="94" y1="74" x2="92" y2="120" stroke={stroke} strokeWidth="0.8"/>
            <line x1="106" y1="74" x2="108" y2="120" stroke={stroke} strokeWidth="0.8"/>
            <circle cx="92" cy="121" r="1.8" fill={stroke}/>
            <circle cx="108" cy="121" r="1.8" fill={stroke}/>
            <path d="M70 140 Q100 156 130 140 L132 175 L68 175 Z" fill="none" stroke={stroke} strokeWidth="0.8" opacity="0.6"/>
          </>
        )}
        {kind === 'hoodie-zip' && (
          <line x1="100" y1="62" x2="100" y2="220" stroke={stroke} strokeWidth="0.6" strokeDasharray="2 2"/>
        )}
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'tote') {
    return (
      <svg {...common}>
        <path d="M75 60 Q75 30 100 30 Q125 30 125 60" fill="none" stroke={stroke} strokeWidth="3"/>
        <path d="M50 60 L150 60 L160 220 L40 220 Z" fill={fill} stroke={stroke} strokeWidth="1.2"/>
        <rect x="72" y="58" width="6" height="14" fill={stroke} opacity="0.5"/>
        <rect x="122" y="58" width="6" height="14" fill={stroke} opacity="0.5"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'cap5') {
    return (
      <svg {...common}>
        {/* crown */}
        <path d="M45 130 Q45 90 100 86 Q155 90 155 130 L150 142 L50 142 Z" fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {/* panel seams */}
        <line x1="100" y1="86" x2="100" y2="142" stroke={stroke} strokeWidth="0.3" opacity="0.4"/>
        <line x1="75" y1="92" x2="80" y2="142" stroke={stroke} strokeWidth="0.3" opacity="0.4"/>
        <line x1="125" y1="92" x2="120" y2="142" stroke={stroke} strokeWidth="0.3" opacity="0.4"/>
        {/* brim */}
        <path d="M40 142 Q100 168 160 142 L155 154 Q100 175 45 154 Z" fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {/* logo */}
        {graphic && (
          <g opacity="0.75">
            <text x="100" y="124" textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="6" letterSpacing="2" fill={stroke}>PVL</text>
          </g>
        )}
      </svg>
    );
  }
  if (kind === 'cap-trucker') {
    return (
      <svg {...common}>
        <path d="M45 130 Q45 90 100 86 Q155 90 155 130 L150 142 L50 142 Z" fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {/* mesh back hint */}
        <pattern id="mesh" patternUnits="userSpaceOnUse" width="4" height="4"><circle cx="2" cy="2" r="0.4" fill={stroke} opacity="0.6"/></pattern>
        <path d="M120 95 Q140 100 155 130 L150 142 L120 142 Z" fill="url(#mesh)" opacity="0.5"/>
        <path d="M45 95 Q60 92 80 90 L80 142 L50 142 Z" fill="url(#mesh)" opacity="0.5"/>
        <path d="M40 142 Q100 168 160 142 L155 154 Q100 175 45 154 Z" fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {graphic && <rect x="86" y="106" width="28" height="14" fill="none" stroke={stroke} strokeWidth="0.6" opacity="0.8"/>}
      </svg>
    );
  }
  if (kind === 'beanie') {
    return (
      <svg {...common}>
        <path d="M50 100 Q50 60 100 60 Q150 60 150 100 L150 150 Q150 170 130 175 L70 175 Q50 170 50 150 Z"
              fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {/* ribbed cuff */}
        <rect x="50" y="148" width="100" height="28" fill="none" stroke={stroke} strokeWidth="0.3" opacity="0.5"/>
        {[58, 68, 78, 88, 98, 108, 118, 128, 138].map(x => (
          <line key={x} x1={x} y1="148" x2={x} y2="176" stroke={stroke} strokeWidth="0.3" opacity="0.5"/>
        ))}
        {graphic && <rect x="88" y="156" width="24" height="10" fill={stroke} opacity="0.25"/>}
      </svg>
    );
  }
  if (kind === 'tank') {
    return (
      <svg {...common}>
        {/* armhole-deep tank with low arm cuts */}
        <path d={`M68 60 Q85 56 100 56 Q115 56 132 60 L138 66 L132 92 Q124 84 124 78 L124 220 L76 220 L76 78 Q76 84 68 92 L62 66 Z`}
              fill={fill} stroke={stroke} strokeWidth="0.6"/>
        <path d="M82 56 Q100 70 118 56" fill="none" stroke={stroke} strokeWidth="0.5"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'pullover') {
    return (
      <svg {...common}>
        {/* crewneck pullover (no hood) */}
        <path d="M38 76 L70 58 L130 58 L162 76 L154 100 L142 94 L142 220 L58 220 L58 94 L46 100 Z"
              fill={fill} stroke={stroke} strokeWidth="0.6"/>
        {/* ribbed crewneck */}
        <path d="M82 58 Q100 72 118 58 L116 64 Q100 74 84 64 Z" fill={stroke} opacity="0.16"/>
        <path d="M82 58 Q100 72 118 58" fill="none" stroke={stroke} strokeWidth="0.4"/>
        {/* ribbed hem */}
        <line x1="58" y1="212" x2="142" y2="212" stroke={stroke} strokeWidth="0.3" opacity="0.4"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  if (kind === 'carrier') {
    return (
      <svg {...common}>
        {/* longer/larger carrier bag */}
        <path d="M65 50 Q65 18 100 18 Q135 18 135 50" fill="none" stroke={stroke} strokeWidth="2.4"/>
        <path d="M42 50 L158 50 L168 222 L32 222 Z" fill={fill} stroke={stroke} strokeWidth="0.6"/>
        <rect x="62" y="48" width="6" height="16" fill={stroke} opacity="0.4"/>
        <rect x="132" y="48" width="6" height="16" fill={stroke} opacity="0.4"/>
        {graphic && !back && <Label />}
        {back && <BackGraphic />}
      </svg>
    );
  }
  return null;
}

function sampleCardBg(img, set) {
  try {
    const c = document.createElement('canvas');
    c.width = c.height = 2;
    const ctx = c.getContext('2d', { willReadFrequently: true });
    ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, 2, 2);
    const d = ctx.getImageData(0, 0, 2, 2).data;
    const spread = Math.max(d[0],d[4],d[8],d[12]) - Math.min(d[0],d[4],d[8],d[12]);
    if (spread < 30) set(`rgb(${Math.round((d[0]+d[4]+d[8]+d[12])/4)},${Math.round((d[1]+d[5]+d[9]+d[13])/4)},${Math.round((d[2]+d[6]+d[10]+d[14])/4)})`);
  } catch(e) {}
}
function readCardAltBg(imgEl) {
  try {
    const w = imgEl.naturalWidth, h = imgEl.naturalHeight;
    const c = document.createElement('canvas'); c.width = c.height = 1;
    const ctx = c.getContext('2d');
    const px = (x, y) => { ctx.drawImage(imgEl, x, y, 1, 1, 0, 0, 1, 1); return [...ctx.getImageData(0,0,1,1).data]; };
    const tl = px(0,0), tr = px(w-1,0), bl = px(0,h-1), br = px(w-1,h-1);
    const dist = (a,b) => Math.abs(a[0]-b[0]) + Math.abs(a[1]-b[1]) + Math.abs(a[2]-b[2]);
    const uniform = dist(tl,tr) < 40 && dist(tl,bl) < 40 && dist(tl,br) < 40;
    if (tl[3] < 128) return null;
    return { bg: `rgb(${tl[0]},${tl[1]},${tl[2]})`, full: !uniform };
  } catch(_) { return null; }
}

// ───────────────────────── product card ─────────────────────────
function ProductCard({ p, onOpen, wish, onWish, initialVariantType, initialCwIdx, initialShirtColorIdx, compact }) {
  const [cwIdx, setCwIdx] = useState(initialCwIdx ?? 0);
  const [variantType, setVariantType] = useState(initialVariantType ?? (p.printed?.length > 0 ? 'printed' : 'embroidered'));
  const [added, setAdded] = useState(false);
  const [pickingSize, setPickingSize] = useState(false);
  const [showMoreSizes, setShowMoreSizes] = useState(false);
  const [sampledBg, setSampledBg] = useState(null);
  const [altBg, setAltBg] = useState(null);
  const [hovered, setHovered] = useState(false);
  const isWished = wish.has(p.id);
  const card = useRef(null);

  const colorways = p[variantType]?.length ? p[variantType] : (p.printed?.length ? p.printed : (p.embroidered || []));
  const cw = colorways[Math.min(cwIdx, colorways.length - 1)] || colorways[0];
  const bg = cw?.hex || '#e8dfc8';

  const CARD_DEFAULT_SIZES = new Set(['S', 'M', 'L']);
  const allSizes = cw?.sizes || p.sizes || [];
  const hasSML = allSizes.some(s => CARD_DEFAULT_SIZES.has(s));
  const visibleSizes = (showMoreSizes || !hasSML) ? allSizes : allSizes.filter(s => CARD_DEFAULT_SIZES.has(s));
  const hasMoreSizes = !showMoreSizes && hasSML && allSizes.some(s => !CARD_DEFAULT_SIZES.has(s));

  const doAdd = (size) => {
    const shirtColor = cw?.shirtColors?.[0] || null;
    window.dispatchEvent(new CustomEvent('peripheral:quick-add', {
      detail: { product: p, colorway: cw, shirtColor, size, variantType }
    }));
    setPickingSize(false);
    setAdded(true);
    setTimeout(() => setAdded(false), 1800);
  };

  const handleQuickAdd = (e) => {
    e.stopPropagation();
    if (pickingSize) { setPickingSize(false); return; }
    const allSizes = cw?.sizes || p.sizes || [];
    if (allSizes.length > 1) { setPickingSize(true); return; }
    doAdd(allSizes[0] || '');
  };

  const scIdx = initialShirtColorIdx ?? 0;
  const frontImg = cw?.shirtColors?.[scIdx]?.images?.[0] || cw?.images?.[0] || null;
  const isBag = p.silhouette === 'tote' || p.silhouette === 'carrier';
  const _altArr = cw?.shirtColors?.[scIdx]?.images || cw?.images || [];
  const altImg = isBag
    ? (_altArr[_altArr.length - 1] || frontImg)
    : (cw?.shirtColors?.[scIdx]?.images?.[2] || cw?.images?.[2]
      || (variantType === 'embroidered' && cw?.mockupImg ? cw.mockupImg : frontImg));
  const altIsReal = altImg !== frontImg;

  return (
    <article className="card fade-in" ref={card}
             onMouseEnter={() => {
               setHovered(true);
               onCursor('hover');
               const urls = new Set();
               ['printed','embroidered'].forEach(vt => {
                 (p[vt]||[]).forEach(cw => {
                   (cw.images||[]).forEach(u => urls.add(u));
                   (cw.shirtColors||[]).forEach(sc => (sc.images||[]).forEach(u => urls.add(u)));
                 });
               });
               urls.forEach(u => { const i = new Image(); i.crossOrigin = 'anonymous'; i.src = u; });
             }}
             onMouseLeave={() => { setHovered(false); onCursor('default'); setPickingSize(false); setShowMoreSizes(false); }}
             onClick={() => { onCursor('default'); onOpen(p, { variantType, cwIdx, shirtColorIdx: scIdx }); }}>
      <div className="card-media" style={(hovered && altBg?.bg) ? { background: altBg.bg } : (sampledBg ? { background: sampledBg } : undefined)}>
        {frontImg ? (
          <>
            <div className="main" style={isBag
              ? { position:'absolute', inset:0, overflow:'hidden' }
              : { position:'absolute', top:6, right:6, bottom:6, left:6, borderRadius:8, overflow:'hidden', display:'flex', alignItems:'center', justifyContent:'center' }}>
              <img crossOrigin="anonymous" src={frontImg} alt={p.name}
                   onLoad={(isBag && compact) ? e => { const r = readCardAltBg(e.target); if (r) setSampledBg(r.bg); } : undefined}
                   style={{ width:'100%', height:'100%', objectFit: (isBag && !compact) ? 'cover' : 'contain', padding: (isBag && !compact) ? 0 : (compact ? (isBag ? '4% 8%' : '0') : '6% 10%') }}/>
            </div>
            <div className="alt" style={altIsReal
              ? { position:'absolute', inset:0, overflow:'hidden' }
              : { position:'absolute', top:6, right:6, bottom:6, left:6, borderRadius:8, overflow:'hidden', display:'flex', alignItems:'center', justifyContent:'center' }}>
              <img src={altImg} alt={p.name} crossOrigin="anonymous"
                   onLoad={altIsReal && altImg !== cw?.mockupImg ? e => setAltBg(readCardAltBg(e.target)) : undefined}
                   style={{ width:'100%', height:'100%',
                   objectFit: altIsReal ? 'cover' : 'contain',
                   padding: (altIsReal || isBag) ? 0 : (compact ? '0' : '6% 10%') }}/>
            </div>
          </>
        ) : (
          <>
            <div className="main" style={{ position:'absolute', inset:0, padding:'10% 16%' }}>
              <Garment kind={p.silhouette} color={bg}/>
            </div>
            <div className="alt" style={{ position:'absolute', inset:0, padding:'10% 16%' }}>
              {variantType === 'embroidered'
                ? <Garment kind={p.silhouette} color="#e8e0cc"/>
                : <Garment kind={p.silhouette} color={bg} back graphic={false}/>}
            </div>
          </>
        )}

        <div className="card-swatches" onClick={e => e.stopPropagation()}>
          {colorways.length > 1 && (
            <button className="swatch-arrow"
                    onClick={e => { e.stopPropagation(); setCwIdx(v => (v - 1 + colorways.length) % colorways.length); }}
                    onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
              <svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
            </button>
          )}
          {(colorways || []).slice(0, 5).map((c, i) => (
            <span key={i} className="swatch" title={c.name}
                  style={{ background: c.hex, boxShadow: (Math.min(cwIdx, colorways.length-1)) === i ? '0 0 0 2px var(--bg), 0 0 0 3.5px var(--fg-mute)' : 'none' }}
                  onClick={(e) => { e.stopPropagation(); setCwIdx(i); }}/>
          ))}
          {colorways.length > 1 && (
            <button className="swatch-arrow"
                    onClick={e => { e.stopPropagation(); setCwIdx(v => (v + 1) % colorways.length); }}
                    onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
              <svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
            </button>
          )}
        </div>

        <button className={`card-wish ${isWished ? 'on':''}`} onClick={(e)=>{e.stopPropagation(); onWish(p.id);}}
                onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
          {isWished ? <Icon.heartFill/> : <Icon.heart/>}
        </button>
        <div className={`card-add-wrap${pickingSize ? ' picking' : ''}`}>
          <button className={`card-add-btn ${added ? 'done' : ''}`}
                  onClick={e => { e.stopPropagation(); handleQuickAdd(e); }}
                  onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
            {added
              ? <Icon.check width={16} height={16}/>
              : <><Icon.cart width={18} height={18}/><span className="card-add-plus">+</span></>
            }
          </button>
          {pickingSize && (
            <div className="card-size-pill" onClick={e => e.stopPropagation()}>
              {visibleSizes.map(s => (
                <button key={s} className="card-size-item"
                        onClick={() => doAdd(s)}
                        onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
                  <span>{s}</span>
                </button>
              ))}
              {hasMoreSizes && (
                <button className="card-size-item card-size-more"
                        onClick={() => setShowMoreSizes(true)}
                        onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
                  <span>+</span>
                </button>
              )}
            </div>
          )}
        </div>
      </div>

      <div className="card-meta" onMouseEnter={() => onCursor('default')} onMouseLeave={() => onCursor('hover')}>
        <div className="card-meta-head">
          <div className="name">{p.name}</div>
          <div className="price">{window.fmt ? window.fmt(cw?.price || p.price) : `€${cw?.price || p.price}`}</div>
        </div>
        {p.hasEmbroidered && p.printed?.length > 0 ? (
          <div className="card-type-toggle" onClick={e => e.stopPropagation()}>
            <button className={`card-type-btn ${variantType === 'printed' ? 'on' : ''}`}
                    onClick={() => { const n = colorways[cwIdx]?.name; const cws = p.printed || []; const i = cws.findIndex(c => c.name === n); setVariantType('printed'); setCwIdx(i >= 0 ? i : 0); }}
                    onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
              Print
            </button>
            <button className={`card-type-btn ${variantType === 'embroidered' ? 'on' : ''}`}
                    onClick={() => { const n = colorways[cwIdx]?.name; const cws = p.embroidered || []; const i = cws.findIndex(c => c.name === n); setVariantType('embroidered'); setCwIdx(i >= 0 ? i : 0); }}
                    onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
              Embroidered
            </button>
          </div>
        ) : (p.printed?.length > 0 || p.hasEmbroidered) ? (
          <div className="card-type-toggle card-type-single">
            <div className="card-type-btn on">
              {p.printed?.length > 0 ? 'Print' : 'Embroidered'}
            </div>
          </div>
        ) : (
          <div className="label-mono">{p.type}</div>
        )}
      </div>
    </article>
  );
}

// ───────────────────────── nav ─────────────────────────
function Nav({ route, setRoute, navigate, cartCount, wishCount, onOpenCart, onOpenSearch, onOpenWish, openCollection, transparent }) {
  const [scrolled, setScrolled] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  useEffect(() => {
    const NAV_H = 72;
    if (!transparent) {
      setScrolled(window.scrollY > NAV_H);
      const onScroll = () => setScrolled(window.scrollY > NAV_H);
      window.addEventListener('scroll', onScroll, { passive: true });
      return () => window.removeEventListener('scroll', onScroll);
    }
    // On transparent pages, go solid the moment the hero-image bottom crosses the nav
    const hero = document.querySelector('.hero-image');
    if (!hero) {
      setScrolled(window.scrollY > NAV_H);
      const onScroll = () => setScrolled(window.scrollY > NAV_H);
      window.addEventListener('scroll', onScroll, { passive: true });
      return () => window.removeEventListener('scroll', onScroll);
    }
    const check = () => setScrolled(hero.getBoundingClientRect().bottom <= NAV_H);
    check();
    window.addEventListener('scroll', check, { passive: true });
    return () => window.removeEventListener('scroll', check);
  }, [transparent]);
  useEffect(() => {
    document.body.style.overflow = menuOpen ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [menuOpen]);

  const showBg = !transparent || scrolled || menuOpen;
  const goShop = () => navigate({ name:'home' }, '/merch');
  const goLanding = () => navigate({ name:'landing' }, '/');
  const close = () => setMenuOpen(false);
  return (
    <>
      <header className={`nav ${scrolled ? 'scrolled' : ''} ${showBg ? 'nav-bg' : ''} ${menuOpen ? 'menu-open' : ''}`}>
        <a className="logo" onClick={goShop}
           onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
          <span className="logo-mark"><img src="/assets/logo-mark.png" alt="Peripheral"/></span>
          <span className="logo-word">Peripheral</span>
        </a>
        <nav className="nav-center">
          <a className={`nav-link ${route.name==='home' ? 'active':''}`} onClick={goShop}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Home</a>
          <a className={`nav-link ${route.name==='collection' ? 'active':''}`} onClick={() => openCollection('all')}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Collections</a>
          <a className="nav-link" onClick={() => navigate({ name:'about' }, '/merch/about')}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>About</a>
        </nav>
        <div className="nav-right">
          <button className="nav-icon nav-hamburger" onClick={() => setMenuOpen(v => !v)} aria-label="Menu">
            {menuOpen ? <Icon.close/> : <Icon.menu/>}
          </button>
          <button className="nav-icon nav-desktop-only" onClick={onOpenSearch} aria-label="Search"><Icon.search/></button>
          <button className="nav-icon nav-desktop-only" onClick={onOpenWish} aria-label="Wishlist">
            <Icon.heart/>
            {wishCount > 0 && <span className="badge num">{wishCount}</span>}
          </button>
          <button className="nav-icon" onClick={onOpenCart} aria-label="Crate">
            <Icon.cart/>
            {cartCount > 0 && <span className="badge num">{cartCount}</span>}
          </button>
        </div>
      </header>
      {menuOpen && (
        <div className="nav-mobile-menu">
          <a className="nav-mobile-link" onClick={() => { goShop(); close(); }}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Home</a>
          <a className="nav-mobile-link" onClick={() => { openCollection('all'); close(); }}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Collections</a>
          <a className="nav-mobile-link" onClick={() => { navigate({ name:'about' }, '/merch/about'); close(); }}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>About</a>
          <div className="nav-mobile-icons">
            <button className="nav-icon" onClick={() => { onOpenSearch(); close(); }} aria-label="Search"><Icon.search/></button>
            <button className="nav-icon" onClick={() => { onOpenWish(); close(); }} aria-label="Wishlist">
              <Icon.heart/>
              {wishCount > 0 && <span className="badge num">{wishCount}</span>}
            </button>
          </div>
        </div>
      )}
    </>
  );
}

function scrollToId(id) {
  const el = document.getElementById(id);
  if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 80, behavior: 'smooth' });
}

// ───────────────────────── footer ─────────────────────────
const PAY_ICONS = [
  { id: 'visa', label: 'Visa', el: (
    <svg viewBox="0 0 38 24" xmlns="http://www.w3.org/2000/svg" width="38" height="24" role="img" aria-labelledby="pi-visa"><title id="pi-visa">Visa</title><path opacity=".07" d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"/><path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32"/><path d="M28.3 10.1H28c-.4 1-.7 1.5-1 3h1.9c-.3-1.5-.3-2.2-.6-3zm2.9 5.9h-1.7c-.1 0-.1 0-.2-.1l-.2-.9-.1-.2h-2.4c-.1 0-.2 0-.2.2l-.3.9c0 .1-.1.1-.1.1h-2.1l.2-.5L27 8.7c0-.5.3-.7.8-.7h1.5c.1 0 .2 0 .2.2l1.4 6.5c.1.4.2.7.2 1.1.1.1.1.1.1.2zm-13.4-.3l.4-1.8c.1 0 .2.1.2.1.7.3 1.4.5 2.1.4.2 0 .5-.1.7-.2.5-.2.5-.7.1-1.1-.2-.2-.5-.3-.8-.5-.4-.2-.8-.4-1.1-.7-1.2-1-.8-2.4-.1-3.1.6-.4.9-.8 1.7-.8 1.2 0 2.5 0 3.1.2h.1c-.1.6-.2 1.1-.4 1.7-.5-.2-1-.4-1.5-.4-.3 0-.6 0-.9.1-.2 0-.3.1-.4.2-.2.2-.2.5 0 .7l.5.4c.4.2.8.4 1.1.6.5.3 1 .8 1.1 1.4.2.9-.1 1.7-.9 2.3-.5.4-.7.6-1.4.6-1.4 0-2.5.1-3.4-.2-.1.2-.1.2-.2.1zm-3.5.3c.1-.7.1-.7.2-1 .5-2.2 1-4.5 1.4-6.7.1-.2.1-.3.3-.3H18c-.2 1.2-.4 2.1-.7 3.2-.3 1.5-.6 3-1 4.5 0 .2-.1.2-.3.2M5 8.2c0-.1.2-.2.3-.2h3.4c.5 0 .9.3 1 .8l.9 4.4c0 .1 0 .1.1.2 0-.1.1-.1.1-.1l2.1-5.1c-.1-.1 0-.2.1-.2h2.1c0 .1 0 .1-.1.2l-3.1 7.3c-.1.2-.1.3-.2.4-.1.1-.3 0-.5 0H9.7c-.1 0-.2 0-.2-.2L7.9 9.5c-.2-.2-.5-.5-.9-.6-.6-.3-1.7-.5-1.9-.5L5 8.2z" fill="#142688"/></svg>
  )},
  { id: 'mc', label: 'Mastercard', el: (
    <svg viewBox="0 0 38 24" xmlns="http://www.w3.org/2000/svg" role="img" width="38" height="24" aria-labelledby="pi-master"><title id="pi-master">Mastercard</title><path opacity=".07" d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"/><path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32"/><circle fill="#EB001B" cx="15" cy="12" r="7"/><circle fill="#F79E1B" cx="23" cy="12" r="7"/><path fill="#FF5F00" d="M22 12c0-2.4-1.2-4.5-3-5.7-1.8 1.3-3 3.4-3 5.7s1.2 4.5 3 5.7c1.8-1.2 3-3.3 3-5.7z"/></svg>
  )},
  { id: 'amex', label: 'American Express', el: (
    <svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="pi-american_express" viewBox="0 0 38 24" width="38" height="24"><title id="pi-american_express">American Express</title><path fill="#000" d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3Z" opacity=".07"/><path fill="#006FCF" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32Z"/><path fill="#FFF" d="M22.012 19.936v-8.421L37 11.528v2.326l-1.732 1.852L37 17.573v2.375h-2.766l-1.47-1.622-1.46 1.628-9.292-.02Z"/><path fill="#006FCF" d="M23.013 19.012v-6.57h5.572v1.513h-3.768v1.028h3.678v1.488h-3.678v1.01h3.768v1.531h-5.572Z"/><path fill="#006FCF" d="m28.557 19.012 3.083-3.289-3.083-3.282h2.386l1.884 2.083 1.89-2.082H37v.051l-3.017 3.23L37 18.92v.093h-2.307l-1.917-2.103-1.898 2.104h-2.321Z"/><path fill="#FFF" d="M22.71 4.04h3.614l1.269 2.881V4.04h4.46l.77 2.159.771-2.159H37v8.421H19l3.71-8.421Z"/><path fill="#006FCF" d="m23.395 4.955-2.916 6.566h2l.55-1.315h2.98l.55 1.315h2.05l-2.904-6.566h-2.31Zm.25 3.777.875-2.09.873 2.09h-1.748Z"/><path fill="#006FCF" d="M28.581 11.52V4.953l2.811.01L32.84 9l1.456-4.046H37v6.565l-1.74.016v-4.51l-1.644 4.494h-1.59L30.35 7.01v4.51h-1.768Z"/></svg>
  )},
  { id: 'paypal', label: 'PayPal', el: (
    <svg viewBox="0 0 38 24" xmlns="http://www.w3.org/2000/svg" role="img" width="38" height="24" aria-labelledby="pi-paypal"><title id="pi-paypal">PayPal</title><path opacity=".07" d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"/><path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32"/><path fill="#003087" d="M23.9 8.3c.2-1 0-1.7-.6-2.3-.6-.7-1.7-1-3.1-1h-4.1c-.3 0-.5.2-.6.5L14 15.6c0 .2.1.4.3.4H17l.4-3.4 1.8-2.2 4.7-2.1z"/><path fill="#3086C8" d="M23.9 8.3l-.2.2c-.5 2.8-2.2 3.8-4.6 3.8H18c-.3 0-.5.2-.6.5l-.6 3.9-.2 1c0 .2.1.4.3.4H19c.3 0 .5-.2.5-.4v-.1l.4-2.4v-.1c0-.2.3-.4.5-.4h.3c2.1 0 3.7-.8 4.1-3.2.2-1 .1-1.8-.4-2.4-.1-.5-.3-.7-.5-.8z"/><path fill="#012169" d="M23.3 8.1c-.1-.1-.2-.1-.3-.1-.1 0-.2 0-.3-.1-.3-.1-.7-.1-1.1-.1h-3c-.1 0-.2 0-.2.1-.2.1-.3.2-.3.4l-.7 4.4v.1c0-.3.3-.5.6-.5h1.3c2.5 0 4.1-1 4.6-3.8v-.2c-.1-.1-.3-.2-.5-.2h-.1z"/></svg>
  )},
  { id: 'applepay', label: 'Apple Pay', el: (
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" role="img" x="0" y="0" width="38" height="24" viewBox="0 0 165.521 105.965" xmlSpace="preserve" aria-labelledby="pi-apple_pay"><title id="pi-apple_pay">Apple Pay</title><path fill="#000" d="M150.698 0H14.823c-.566 0-1.133 0-1.698.003-.477.004-.953.009-1.43.022-1.039.028-2.087.09-3.113.274a10.51 10.51 0 0 0-2.958.975 9.932 9.932 0 0 0-4.35 4.35 10.463 10.463 0 0 0-.975 2.96C.113 9.611.052 10.658.024 11.696a70.22 70.22 0 0 0-.022 1.43C0 13.69 0 14.256 0 14.823v76.318c0 .567 0 1.132.002 1.699.003.476.009.953.022 1.43.028 1.036.09 2.084.275 3.11a10.46 10.46 0 0 0 .974 2.96 9.897 9.897 0 0 0 1.83 2.52 9.874 9.874 0 0 0 2.52 1.83c.947.483 1.917.79 2.96.977 1.025.183 2.073.245 3.112.273.477.011.953.017 1.43.02.565.004 1.132.004 1.698.004h135.875c.565 0 1.132 0 1.697-.004.476-.002.952-.009 1.431-.02 1.037-.028 2.085-.09 3.113-.273a10.478 10.478 0 0 0 2.958-.977 9.955 9.955 0 0 0 4.35-4.35c.483-.947.789-1.917.974-2.96.186-1.026.246-2.074.274-3.11.013-.477.02-.954.022-1.43.004-.567.004-1.132.004-1.699V14.824c0-.567 0-1.133-.004-1.699a63.067 63.067 0 0 0-.022-1.429c-.028-1.038-.088-2.085-.274-3.112a10.4 10.4 0 0 0-.974-2.96 9.94 9.94 0 0 0-4.35-4.35A10.52 10.52 0 0 0 156.939.3c-1.028-.185-2.076-.246-3.113-.274a71.417 71.417 0 0 0-1.431-.022C151.83 0 151.263 0 150.698 0z"/><path fill="#FFF" d="M150.698 3.532l1.672.003c.452.003.905.008 1.36.02.793.022 1.719.065 2.583.22.75.135 1.38.34 1.984.648a6.392 6.392 0 0 1 2.804 2.807c.306.6.51 1.226.645 1.983.154.854.197 1.783.218 2.58.013.45.019.9.02 1.36.005.557.005 1.113.005 1.671v76.318c0 .558 0 1.114-.004 1.682-.002.45-.008.9-.02 1.35-.022.796-.065 1.725-.221 2.589a6.855 6.855 0 0 1-.645 1.975 6.397 6.397 0 0 1-2.808 2.807c-.6.306-1.228.511-1.971.645-.881.157-1.847.2-2.574.22-.457.01-.912.017-1.379.019-.555.004-1.113.004-1.669.004H14.801c-.55 0-1.1 0-1.66-.004a74.993 74.993 0 0 1-1.35-.018c-.744-.02-1.71-.064-2.584-.22a6.938 6.938 0 0 1-1.986-.65 6.337 6.337 0 0 1-1.622-1.18 6.355 6.355 0 0 1-1.178-1.623 6.935 6.935 0 0 1-.646-1.985c-.156-.863-.2-1.788-.22-2.578a66.088 66.088 0 0 1-.02-1.355l-.003-1.327V14.474l.002-1.325a66.7 66.7 0 0 1 .02-1.357c.022-.792.065-1.717.222-2.587a6.924 6.924 0 0 1 .646-1.981c.304-.598.7-1.144 1.18-1.623a6.386 6.386 0 0 1 1.624-1.18 6.96 6.96 0 0 1 1.98-.646c.865-.155 1.792-.198 2.586-.22.452-.012.905-.017 1.354-.02l1.677-.003h135.875"/><g><g><path fill="#000" d="M43.508 35.77c1.404-1.755 2.356-4.112 2.105-6.52-2.054.102-4.56 1.355-6.012 3.112-1.303 1.504-2.456 3.959-2.156 6.266 2.306.2 4.61-1.152 6.063-2.858"/><path fill="#000" d="M45.587 39.079c-3.35-.2-6.196 1.9-7.795 1.9-1.6 0-4.049-1.8-6.698-1.751-3.447.05-6.645 2-8.395 5.1-3.598 6.2-.95 15.4 2.55 20.45 1.699 2.5 3.747 5.25 6.445 5.151 2.55-.1 3.549-1.65 6.647-1.65 3.097 0 3.997 1.65 6.696 1.6 2.798-.05 4.548-2.5 6.247-5 1.95-2.85 2.747-5.6 2.797-5.75-.05-.05-5.396-2.101-5.446-8.251-.05-5.15 4.198-7.6 4.398-7.751-2.399-3.548-6.147-3.948-7.447-4.048"/></g><g><path fill="#000" d="M78.973 32.11c7.278 0 12.347 5.017 12.347 12.321 0 7.33-5.173 12.373-12.529 12.373h-8.058V69.62h-5.822V32.11h14.062zm-8.24 19.807h6.68c5.07 0 7.954-2.729 7.954-7.46 0-4.73-2.885-7.434-7.928-7.434h-6.706v14.894z"/><path fill="#000" d="M92.764 61.847c0-4.809 3.665-7.564 10.423-7.98l7.252-.442v-2.08c0-3.04-2.001-4.704-5.562-4.704-2.938 0-5.07 1.507-5.51 3.82h-5.252c.157-4.86 4.731-8.395 10.918-8.395 6.654 0 10.995 3.483 10.995 8.89v18.663h-5.38v-4.497h-.13c-1.534 2.937-4.914 4.782-8.579 4.782-5.406 0-9.175-3.222-9.175-8.057zm17.675-2.417v-2.106l-6.472.416c-3.64.234-5.536 1.585-5.536 3.95 0 2.288 1.975 3.77 5.068 3.77 3.95 0 6.94-2.522 6.94-6.03z"/><path fill="#000" d="M120.975 79.652v-4.496c.364.051 1.247.103 1.715.103 2.573 0 4.029-1.09 4.913-3.899l.52-1.663-9.852-27.293h6.082l6.863 22.146h.13l6.862-22.146h5.927l-10.216 28.67c-2.34 6.577-5.017 8.735-10.683 8.735-.442 0-1.872-.052-2.261-.157z"/></g></g></svg>
  )},
  { id: 'gpay', label: 'Google Pay', el: (
    <svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 38 24" width="38" height="24" aria-labelledby="pi-google_pay"><title id="pi-google_pay">Google Pay</title><path d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z" fill="#000" opacity=".07"/><path d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32" fill="#FFF"/><path d="M18.093 11.976v3.2h-1.018v-7.9h2.691a2.447 2.447 0 0 1 1.747.692 2.28 2.28 0 0 1 .11 3.224l-.11.116c-.47.447-1.098.69-1.747.674l-1.673-.006zm0-3.732v2.788h1.698c.377.012.741-.135 1.005-.404a1.391 1.391 0 0 0-1.005-2.354l-1.698-.03zm6.484 1.348c.65-.03 1.286.188 1.778.613.445.43.682 1.03.65 1.649v3.334h-.969v-.766h-.049a1.93 1.93 0 0 1-1.673.931 2.17 2.17 0 0 1-1.496-.533 1.667 1.667 0 0 1-.613-1.324 1.606 1.606 0 0 1 .613-1.336 2.746 2.746 0 0 1 1.698-.515c.517-.02 1.03.093 1.49.331v-.208a1.134 1.134 0 0 0-.417-.901 1.416 1.416 0 0 0-.98-.368 1.545 1.545 0 0 0-1.319.717l-.895-.564a2.488 2.488 0 0 1 2.182-1.06zM23.29 13.52a.79.79 0 0 0 .337.662c.223.176.5.269.785.263.429-.001.84-.17 1.146-.472.305-.286.478-.685.478-1.103a2.047 2.047 0 0 0-1.324-.374 1.716 1.716 0 0 0-1.03.294.883.883 0 0 0-.392.73zm9.286-3.75l-3.39 7.79h-1.048l1.281-2.728-2.224-5.062h1.103l1.612 3.885 1.569-3.885h1.097z" fill="#5F6368"/><path d="M13.986 11.284c0-.308-.024-.616-.073-.92h-4.29v1.747h2.451a2.096 2.096 0 0 1-.9 1.373v1.134h1.464a4.433 4.433 0 0 0 1.348-3.334z" fill="#4285F4"/><path d="M9.629 15.721a4.352 4.352 0 0 0 3.01-1.097l-1.466-1.14a2.752 2.752 0 0 1-4.094-1.44H5.577v1.17a4.53 4.53 0 0 0 4.052 2.507z" fill="#34A853"/><path d="M7.079 12.05a2.709 2.709 0 0 1 0-1.735v-1.17H5.577a4.505 4.505 0 0 0 0 4.075l1.502-1.17z" fill="#FBBC04"/><path d="M9.629 8.44a2.452 2.452 0 0 1 1.74.68l1.3-1.293a4.37 4.37 0 0 0-3.065-1.183 4.53 4.53 0 0 0-4.027 2.5l1.502 1.171a2.715 2.715 0 0 1 2.55-1.875z" fill="#EA4335"/></svg>
  )},
  { id: 'ideal_wero', label: 'iDEAL / Wero', style: {background:'white',borderRadius:3,marginTop:1}, el: (
    <svg width="61" height="23" viewBox="0 0 480 182" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="pi-ideal-wero"><title id="pi-ideal-wero">iDEAL / Wero</title><path d="M11.0164 22.8711V159.27C11.0164 165.798 16.3839 171.141 22.9443 171.141H104.83C166.736 171.141 193.574 136.655 193.574 90.8925C193.574 45.3667 166.736 11 104.83 11H22.9443C16.3839 11 11.0164 16.342 11.0164 22.8711Z" fill="white"/><path d="M65.8253 44.4757V145.261H109.899C149.917 145.261 167.273 122.765 167.273 90.9507C167.273 60.5016 149.917 36.8782 109.899 36.8782H73.4592C69.2247 36.8782 65.8253 40.3208 65.8253 44.4757Z" fill="#CC0066"/><path fillRule="evenodd" clipRule="evenodd" d="M33.6188 160.279H104.829C154.867 160.279 182.479 135.646 182.479 90.9514C182.479 65.1914 172.4 21.9214 104.829 21.9214H33.6188C27.297 21.9214 22.168 27.0259 22.168 33.3176V148.883C22.168 155.175 27.297 160.279 33.6188 160.279ZM25.9821 33.3177C25.9821 29.1034 29.3816 25.7202 33.616 25.7202H104.826C132.379 25.7202 178.66 34.208 178.66 90.9514C178.66 133.213 152.419 156.48 104.826 156.48H33.616C29.3816 156.48 25.9821 153.096 25.9821 148.883V33.3177Z" fill="#232323"/><path fillRule="evenodd" clipRule="evenodd" d="M87.5134 76.3046C86.1718 75.825 84.7719 75.5851 83.2548 75.5851V75.5251H73.1625V99.8693H83.3721C85.1803 99.8693 86.7557 99.5096 88.0973 98.91C89.439 98.2506 90.5477 97.411 91.4227 96.3314C92.2977 95.2526 92.939 93.933 93.4057 92.434C93.814 90.935 94.0477 89.3163 94.0477 87.5175C94.0477 85.4787 93.7558 83.7397 93.231 82.2407C92.6479 80.8014 91.8893 79.5424 90.9561 78.5233C89.9638 77.5638 88.8559 76.7843 87.5134 76.3046ZM85.119 95.0732C84.3604 95.3132 83.6601 95.4328 82.9023 95.4328V95.4934H78.2933V80.1428H82.0273C83.3107 80.1428 84.3604 80.323 85.2354 80.6826C86.1104 81.0422 86.8108 81.6418 87.3356 82.302C87.8605 82.9614 88.2689 83.8609 88.5026 84.8201C88.7363 85.7793 88.8527 86.9187 88.8527 88.1179C88.8527 89.4973 88.6773 90.5761 88.3279 91.5353C87.9777 92.4953 87.5111 93.2146 86.9855 93.8142C86.4606 94.4138 85.8193 94.8332 85.119 95.0732Z" fill="white"/><path d="M115.108 75.5851V80.0822H102.623V85.2985H114.116V89.4359H102.623V95.3722H115.4V99.8693H97.4313V75.5251H115.108V75.5851Z" fill="white"/><path fillRule="evenodd" clipRule="evenodd" d="M141.826 99.9283L132.958 75.5845H127.533L118.607 99.9283H123.857L125.725 94.5319H134.592L136.4 99.9283H141.826ZM130.275 81.5813L133.25 90.5148H127.125L130.217 81.5813H130.275Z" fill="white"/><path d="M150.286 75.5845V95.4313H161.837V99.9283H145.094V75.5845H150.286Z" fill="white"/><path d="M45.3467 100.287C51.6667 100.287 56.79 95.1623 56.79 88.8402C56.79 82.5181 51.6667 77.3931 45.3467 77.3931C39.0267 77.3931 33.9033 82.5181 33.9033 88.8402C33.9033 95.1623 39.0267 100.287 45.3467 100.287Z" fill="#232323"/><path d="M52.1562 146.076C43.2997 146.076 36.1919 138.425 36.1919 129.026V115.712C36.1919 111.012 39.7458 107.156 44.2022 107.156C48.6023 107.156 52.2126 110.952 52.2126 115.712V146.076H52.1562Z" fill="#232323"/><path d="M215.607 39V143" stroke="rgba(0,0,0,0.15)" strokeWidth="4" strokeLinecap="round"/><path d="M407.535 90.6198C407.535 77.2454 417.166 65.0493 433.798 65.0493C450.5 65.0493 460.131 77.2454 460.131 90.6198C460.131 103.994 450.5 116.19 433.798 116.19C417.169 116.193 407.535 103.994 407.535 90.6198ZM445.512 90.6198C445.512 84.1737 441.215 78.2158 433.801 78.2158C426.454 78.2158 422.09 84.1766 422.09 90.6198C422.09 97.0658 426.457 103.024 433.801 103.024C441.215 103.024 445.512 97.0658 445.512 90.6198Z" fill="#5A2CA0"/><path d="M397.309 100.115C402.853 96.9962 406.178 91.0382 406.178 84.4536C406.178 74.6834 399.038 66.366 387.951 66.366H364.668V114.876H379.082V102.472H381.785L390.031 114.876H407.009L397.309 100.115ZM385.043 91.3126H379.082V77.5917H385.112C388.994 77.5917 391.489 80.7107 391.489 84.4536C391.489 88.1936 388.925 91.3126 385.043 91.3126Z" fill="#5A2CA0"/><path d="M298.868 66.3142L290.221 95.2261L281.782 66.3142H270.302L261.794 95.2261L253.217 66.3142H238L255.429 114.732H267.949L276.041 88.3786L284.067 114.732H296.655L314.085 66.3142H298.868Z" fill="#5A2CA0"/><path d="M335.895 102.905C335.884 102.905 335.872 102.905 335.861 102.905C330.469 102.905 326.688 99.6879 325.065 95.4281H361.515H361.697C361.991 93.8253 362.144 92.1849 362.144 90.5243C362.144 77.1846 352.542 65.0203 335.895 65.0029V78.1463C341.319 78.1607 345.062 81.3722 346.668 85.6233H310.088C309.796 87.2262 309.646 88.8666 309.646 90.5272C309.646 103.876 319.26 116.051 335.861 116.051C335.872 116.051 335.884 116.051 335.895 116.051V102.905Z" fill="#5A2CA0"/><path d="M335.861 116.046C336.424 116.046 336.978 116.028 337.527 116.002C340.9 115.832 343.961 115.153 346.691 114.059C349.42 112.964 351.817 111.459 353.856 109.64C355.895 107.821 357.578 105.689 358.884 103.341C360.071 101.207 360.937 98.8966 361.48 96.4851H346.217C345.905 97.1291 345.541 97.7443 345.125 98.319C344.556 99.1045 343.889 99.8179 343.133 100.433C342.376 101.048 341.527 101.568 340.591 101.969C339.655 102.371 338.636 102.654 337.533 102.795C336.996 102.865 336.441 102.905 335.866 102.905C332.433 102.905 329.651 101.597 327.667 99.5493L317.744 109.472C322.154 113.504 328.277 116.046 335.861 116.046Z" fill="url(#paint0_linear_2815_10708)"/><path d="M335.86 65C321.781 65 312.727 73.7564 310.301 84.5634H325.521C327.341 80.8407 330.925 78.1433 335.86 78.1433C339.794 78.1433 342.846 79.8242 344.842 82.3656L354.855 72.3529C350.384 67.8707 343.952 65 335.86 65Z" fill="url(#paint1_linear_2815_10708)"/><defs><linearGradient id="paint0_linear_2815_10708" x1="353.798" y1="87.5063" x2="331.339" y2="115.329" gradientUnits="userSpaceOnUse"><stop offset="0.0243" stopColor="#5A2CA0" stopOpacity="0"/><stop offset="0.676" stopColor="#5A2CA0"/></linearGradient><linearGradient id="paint1_linear_2815_10708" x1="314.501" y1="93.7404" x2="337.579" y2="70.6619" gradientUnits="userSpaceOnUse"><stop offset="0.0243" stopColor="#5A2CA0" stopOpacity="0"/><stop offset="0.676" stopColor="#5A2CA0"/></linearGradient></defs></svg>
  )},
];

function Footer({ setRoute, openCollection, navigate }) {
  return (
    <footer className="footer">

      <div className="footer-top">
        <div className="footer-col">
          <h4>Get 5% off your first order</h4>
          <p style={{color:'rgba(235,227,206,0.5)', fontSize:14, lineHeight:1.65, maxWidth:340, marginBottom:20}}>
            Join Peripheral on Patreon for free and get your discount code instantly.
          </p>
          <div style={{display:'inline-flex', flexDirection:'column', alignItems:'center', gap:8}}>
            <button className="footer-connect-btn" onClick={() => {
              localStorage.setItem('peripheral-popup-v1', '1');
              localStorage.setItem('pvl-patreon-pending', '1');
              window.addEventListener('beforeunload', () => {}, { once: true });
              window.location.href = '/api/patreon/auth';
            }}
                    onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
              Join free on Patreon →
            </button>
            <p className="ep-fine" style={{margin:0, fontSize:7.5, letterSpacing:'0.06em'}}>Free to join. No card required.</p>
          </div>
        </div>
        <div className="footer-col">
          <h4>Shop</h4>
          <ul>
            <li><a onClick={() => openCollection('all')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>All</a></li>
            <li><a onClick={() => openCollection('tees')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Tops</a></li>
            <li><a onClick={() => openCollection('knits')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Knits</a></li>
            <li><a onClick={() => openCollection('bags')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Totes</a></li>
          </ul>
        </div>
        <div className="footer-col">
          <h4>Info</h4>
          <ul>
            <li><a onClick={() => navigate({ name:'info', id:'shipping' }, '/merch/shipping')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Shipping & Returns</a></li>
            <li><a onClick={() => navigate({ name:'info', id:'faq' }, '/merch/faq')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>FAQ</a></li>
            <li><a onClick={() => navigate({ name:'info', id:'size-guide' }, '/merch/size-guide')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Size Guide</a></li>
            <li><a onClick={() => navigate({ name:'info', id:'contact' }, '/merch/contact')} style={{cursor:'none'}} onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Contact</a></li>
          </ul>
        </div>
        <div className="footer-col">
          <h4>Links</h4>
          <ul style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:'10px 24px'}}>
            <li><a target="_blank" rel="noreferrer" href="https://www.youtube.com/@peripheralvinyl"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.yt/> YouTube</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://www.instagram.com/peripheral.vinyl"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.ig/> Instagram</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://soundcloud.com/peripheralvinyl"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.sc/> SoundCloud</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="http://open.spotify.com/user/31zygcqtusf4yur7vtayweoyvtzu"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.spotify/> Spotify</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://www.tiktok.com/@peripheralvinyl"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.tt/> TikTok</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://discord.gg/qwEG4WAvAq"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.discord/> Discord</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://www.patreon.com/peripheralvinyl/membership"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.patreon/> Patreon</span></a></li>
            <li><a target="_blank" rel="noreferrer" href="https://www.buymeacoffee.com/peripheralvinyl"><span style={{display:'inline-flex', alignItems:'center', gap:8}}><Icon.coffee/> Buy Me a Vinyl</span></a></li>
          </ul>
        </div>
      </div>

      <div className="footer-divider"/>

      <div className="footer-brand" onClick={() => navigate({ name:'about' }, '/merch/about')} style={{cursor:'none'}}
           onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>
        <div className="footer-brand-lockup">
          <span className="footer-logo-mark"><img src="/assets/logo-mark.png" alt="Peripheral"/></span>
          <span className="footer-logo-word">Peripheral</span>
        </div>
        <div className="footer-brand-desc-wrap">
          <p className="footer-brand-desc">
            Peripheral is my take on a modern listening experience, somewhere between a DJ set, listening bar, and a curated journey through sound. Each set is built with intention: slower pacing, careful selection, and a focus on how tracks interact over time rather than stand out.
          </p>
          <p className="footer-brand-desc">
            This is where you can help keep those sessions going and become part of the people behind them.
          </p>
        </div>
      </div>


      <div className="footer-divider"/>

      <div className="footer-pay">
        {PAY_ICONS.map(p => (
          <span key={p.id} className="pay-icon" style={p.style} title={p.label}>{p.el}</span>
        ))}
      </div>

      <div className="footer-bot">
        <div className="footer-legal">
          <a onClick={() => navigate({ name:'info', id:'privacy' }, '/merch/privacy')} style={{cursor:'none'}}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Privacy Policy</a>
          <span className="sep">·</span>
          <a onClick={() => navigate({ name:'info', id:'terms' }, '/merch/terms')} style={{cursor:'none'}}
             onMouseEnter={()=>onCursor('hover')} onMouseLeave={()=>onCursor('default')}>Terms</a>
          <span className="sep">·</span>
          <span>© 2026 Peripheral. All rights reserved.</span>
        </div>
        <div>Fulfilment by Fourthwall · Made in Portugal</div>
      </div>
    </footer>
  );
}

// ───────────────────────── now playing widget ─────────────────────────
function NowPlaying() {
  const tracks = [
    { side: 'A1', title: 'Yotto — Walls of Jericho (Mix 02)' },
    { side: 'A2', title: 'Ben Böhmer — Lights Out (live edit)' },
    { side: 'B1', title: 'Tinlicker — Because You Move Me' },
    { side: 'B2', title: 'Marsh — Endless (Anjuna Deep)' },
  ];
  const [i, setI] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setI(v => (v + 1) % tracks.length), 8000);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="now-playing">
      <div className="np-spin"/>
      <div className="np-text">
        <div className="l1">Now Spinning · Side {tracks[i].side}</div>
        <div className="l2">{tracks[i].title}</div>
      </div>
    </div>
  );
}

// ───────────────────────── custom cursor helpers ─────────────────────────
let _cursorZoomActive = false; // true only while hovering a lightbox zoom target
let _cursorDefaultRaf = null;  // pending default-reset frame — cancelled if hover fires first

function onCursor(mode) {
  const c = document.getElementById('cursor');
  if (!c) return;
  if (mode === 'default') {
    // Defer the reset by one frame. If another element calls onCursor('hover')
    // before then (e.g. moving from a child button to its parent card), the
    // pending reset is cancelled and the cursor stays in hover state.
    if (_cursorDefaultRaf) cancelAnimationFrame(_cursorDefaultRaf);
    _cursorDefaultRaf = requestAnimationFrame(() => {
      _cursorDefaultRaf = null;
      // If the cursor is still over the card image area, keep hover state — don't reset.
      if (document.querySelector('.card-media:hover')) return;
      c.classList.remove('hover', 'text', 'hidden');
      _cursorZoomActive = false;
    });
  } else {
    if (_cursorDefaultRaf) { cancelAnimationFrame(_cursorDefaultRaf); _cursorDefaultRaf = null; }
    c.classList.remove('hover', 'text', 'hidden');
    if (mode) c.classList.add(mode);
    _cursorZoomActive = (mode === 'hidden');
  }
}

function useCursor() {
  useEffect(() => {
    const c = document.getElementById('cursor');
    if (!c) return;
    if (!c.querySelector('.vinyl')) {
      const inner = document.createElement('div');
      inner.className = 'vinyl';
      c.appendChild(inner);
    }
    let x = window.innerWidth / 2, y = window.innerHeight / 2;
    let tx = x, ty = y;
    let needsSnap = false;
    let hasMovedOnce = false;

    c.style.opacity = '0';

    const onBlur     = () => { needsSnap = true; };
    const onMove     = (e) => {
      const dx = e.clientX - tx, dy = e.clientY - ty;
      tx = e.clientX; ty = e.clientY;
      // Snap instead of lerp on big positional jumps (returning from browser UI like
      // Chrome bookmarks dropdown — normal movement is <14px/frame at 60fps).
      const bigJump = (dx * dx + dy * dy) > 40000; // >200px
      if (needsSnap || bigJump) { x = tx; y = ty; needsSnap = false; }
      if (!hasMovedOnce) { x = tx; y = ty; hasMovedOnce = true; c.style.opacity = ''; }
      // If cursor is stuck hidden but we're not actively in zoom mode, clear it.
      if (c.classList.contains('hidden') && !_cursorZoomActive) {
        c.classList.remove('hidden');
      }
    };
    // Hide cursor IN the bfcache snapshot so the frozen frame is already clean.
    // pagehide fires before the snapshot is taken, so first-paint after restore
    // sees opacity:0 without needing pageshow to catch up.
    const onPageHide = (e) => {
      if (!e.persisted) return;
      c.style.opacity = '0';
      hasMovedOnce = false;
      needsSnap    = true;
    };
    // Safety net: also hide on thaw in case pagehide didn't run.
    // macOS caches the OS-level cursor from the previous site and ignores
    // cursor:none until the CSS value actually changes — toggling auto→none
    // forces the OS cursor API to re-run. Two RAFs = one visible paint cycle
    // with cursor:auto so the OS registers the change before we revert.
    const onPageShow = (e) => {
      if (!e.persisted) return;
      hasMovedOnce = false;
      needsSnap    = true;
      c.style.opacity = '0';
      // Override * { cursor: none !important } on every element so macOS
      // re-evaluates the OS-level cursor. Setting on documentElement alone
      // doesn't work — macOS checks the element under the pointer, which is
      // always a child with its own cursor:none from the * rule.
      const tmp = document.createElement('style');
      tmp.textContent = '* { cursor: auto !important; }';
      document.head.appendChild(tmp);
      requestAnimationFrame(() => requestAnimationFrame(() => tmp.remove()));
    };

    // Fired when cursor enters the document from browser chrome (toolbar, bookmark
    // dropdowns, scrollbar). Chrome does NOT dispatch mousemove while its native UI
    // overlays are open, so onMove never fires during that gap. mouseenter gives us
    // clientX/Y so we can snap the vinyl cursor to the right position immediately.
    const onDocEnter = (e) => {
      tx = e.clientX; ty = e.clientY;
      x = tx; y = ty;
      c.style.left = x + 'px';
      c.style.top  = y + 'px';
      needsSnap = false;
      _cursorZoomActive = false;
      c.classList.remove('hidden');
      if (!hasMovedOnce) { hasMovedOnce = true; c.style.opacity = ''; }
    };

    window.addEventListener('mousemove', onMove);
    window.addEventListener('blur',      onBlur);
    window.addEventListener('pagehide',  onPageHide);
    window.addEventListener('pageshow',  onPageShow);
    document.body.addEventListener('mouseenter', onDocEnter);

    let raf;
    const tick = () => {
      x += (tx - x) * 0.28;
      y += (ty - y) * 0.28;
      c.style.left = x + 'px';
      c.style.top  = y + 'px';
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('blur',      onBlur);
      window.removeEventListener('pagehide',  onPageHide);
      window.removeEventListener('pageshow',  onPageShow);
      document.body.removeEventListener('mouseenter', onDocEnter);
      cancelAnimationFrame(raf);
    };
  }, []);
}

// Cart thumbnail — handles bag images with corner-sampled background so side
// gutters blend seamlessly instead of showing the default --bg-elev colour.
function CartThumb({ line, img, onClick }) {
  const isBag = line.silhouette === 'tote' || line.silhouette === 'carrier';
  const [bg, setBg] = useState(null);
  const imgRef = useRef(null);

  const sample = (el) => {
    try {
      const c = document.createElement('canvas');
      c.width = c.height = 1;
      const ctx = c.getContext('2d');
      ctx.drawImage(el, 0, 0, 1, 1, 0, 0, 1, 1);
      const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
      if (a >= 128) setBg(`rgb(${r},${g},${b})`);
    } catch (_) {}
  };

  useEffect(() => {
    if (!isBag || !imgRef.current) return;
    if (imgRef.current.complete && imgRef.current.naturalWidth > 0) sample(imgRef.current);
  }, [img]);

  return (
    <div className="cart-thumb" onClick={onClick} style={{ cursor: 'none', background: (isBag && bg) ? bg : undefined }}>
      {img
        ? <img ref={isBag ? imgRef : undefined}
               src={img} alt={line.name}
               crossOrigin={isBag ? 'anonymous' : undefined}
               onLoad={isBag ? (e) => sample(e.target) : undefined}
               style={{ objectFit: isBag ? 'contain' : 'cover' }}/>
        : <div style={{ position:'absolute', inset:'10%', display:'flex', alignItems:'center', justifyContent:'center' }}>
            <Garment kind={line.silhouette} color={line.colorway?.hex || '#888'}/>
          </div>
      }
    </div>
  );
}

// expose
Object.assign(window, { Icon, Garment, ProductCard, Nav, Footer, NowPlaying, onCursor, useCursor, scrollToId, CartThumb });
