/* WGOArtwork.jsx
   ------------------------------------------------------------------
   Album-art treatment for "What's Going On":
     - <WGOCover>     CSS recreation of the iconic 1971 portrait sleeve.
                      Pure CSS/SVG, no copyrighted asset shipped.
                      Optional hotspots overlay the artwork.
     - <AboutCover>   "About the cover" editorial section with hotspots.
     - <MakingGallery> Photo-style gallery with placeholder frames
                       describing the imagery that would live there.
     - <StickyBuyRail> Amazon affiliate module that follows the reader.
   ------------------------------------------------------------------ */

const { useState: useWAState, useEffect: useWAEffect, useRef: useWARef } = React;

/* ============================================================
   1. WGOCover, typographic + portrait recreation
   ------------------------------------------------------------
   Shape language of the real cover:
     - Full-bleed close-crop portrait (warm umber, sepia/rain wash)
     - Marvin in a tan trench, collar up, head turned three-quarters
     - "MARVIN GAYE" in a large condensed serif, top
     - "What's Going On" hand-script overlapping the portrait
     - Rain streaks rendered as faint diagonal lines
   We don't ship a photograph. We render an iconographic stand-in
   that reads as the cover at a glance.
   ============================================================ */
function WGOCover({ size = 460, hotspots = false, onHotspot }) {
  const [active, setActive] = useWAState(null);
  const wrap = {
    width: size, height: size, position: "relative", overflow: "hidden",
    flexShrink: 0,
    background: "linear-gradient(165deg, #2a1a10 0%, #1a0e07 60%, #0d0703 100%)",
    boxShadow: "0 1px 0 rgba(0,0,0,.04), 0 28px 56px -28px rgba(0,0,0,.55)",
    color: "#e8c896",
    fontFamily: "'Playfair Display', serif",
  };

  return (
    <div className="wgo-cover" style={wrap}>
      {/* Atmospheric vignette, dark edges, warm centre glow */}
      <div style={{
        position: "absolute", inset: 0,
        background: "radial-gradient(ellipse at 42% 48%, rgba(217,119,87,0.42) 0%, rgba(120,52,28,0.18) 28%, rgba(0,0,0,0) 65%)",
        pointerEvents: "none"
      }} />

      {/* Portrait silhouette, Marvin three-quarter, collar up.
          Built from layered SVG ellipses + paths. Iconographic, not photo. */}
      <svg viewBox="0 0 460 460" width={size} height={size}
           style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
           preserveAspectRatio="xMidYMid slice">
        <defs>
          <radialGradient id="wgoFace" cx="0.45" cy="0.42" r="0.5">
              <stop offset="0%" stopColor="#5a3520" />
              <stop offset="55%" stopColor="#3d2113" />
              <stop offset="100%" stopColor="#1a0a05" />
          </radialGradient>
          <linearGradient id="wgoCoat" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#7a4a2a" />
            <stop offset="100%" stopColor="#2a1608" />
          </linearGradient>
          <linearGradient id="wgoCoatHi" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0%" stopColor="#a66a3c" stopOpacity="0.4" />
            <stop offset="60%" stopColor="#5a3520" stopOpacity="0" />
          </linearGradient>
          <filter id="wgoGrain">
            <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="3" />
            <feColorMatrix values="0 0 0 0 0.4   0 0 0 0 0.25  0 0 0 0 0.12  0 0 0 0.18 0" />
            <feComposite in2="SourceGraphic" operator="in" />
          </filter>
        </defs>

        {/* Coat, broad shoulder mass */}
        <path d="M 60 460 L 60 360 Q 120 290 180 286 L 280 286 Q 360 296 410 360 L 410 460 Z"
              fill="url(#wgoCoat)" />
        {/* Coat highlight, collar lapel */}
        <path d="M 175 290 L 220 360 L 200 460 L 130 460 L 110 380 Z"
              fill="url(#wgoCoatHi)" />
        {/* Turned-up collar V */}
        <path d="M 200 290 L 235 240 L 270 290 L 250 320 L 235 305 L 220 320 Z"
              fill="#0e0604" opacity="0.85" />

        {/* Neck */}
        <path d="M 210 270 Q 235 280 260 270 L 258 300 Q 235 308 212 300 Z"
              fill="#3a1f10" />

        {/* Head, three-quarter facing left */}
        <ellipse cx="232" cy="200" rx="68" ry="82" fill="url(#wgoFace)" />

        {/* Cheek/jaw shadow, defines the turn */}
        <path d="M 175 200 Q 180 245 220 270 L 210 280 Q 175 260 168 220 Z"
              fill="#0d0604" opacity="0.55" />

        {/* Hair, close-cropped afro silhouette, top edge */}
        <path d="M 162 168 Q 168 122 232 116 Q 295 122 300 172 Q 290 150 232 145 Q 178 152 162 168 Z"
              fill="#0a0402" />
        <ellipse cx="232" cy="142" rx="68" ry="22" fill="#0a0402" />

        {/* Brow shadow */}
        <path d="M 178 175 Q 232 168 286 178 L 282 188 Q 232 180 182 188 Z"
              fill="#0d0604" opacity="0.7" />

        {/* Faint moustache, Marvin trademark */}
        <path d="M 218 226 Q 232 232 246 226 L 244 232 Q 232 236 220 232 Z"
              fill="#1a0a05" opacity="0.7" />

        {/* Eye, single visible (three-quarter view), looking off-camera */}
        <ellipse cx="218" cy="195" rx="4.5" ry="2.2" fill="#0a0402" />

        {/* Rain streaks, diagonal soft lines */}
        <g stroke="#e8c896" strokeWidth="0.6" opacity="0.18">
          {Array.from({ length: 38 }).map((_, i) => {
            const x = (i * 17) % 460;
            const y = (i * 31) % 460;
            return <line key={i} x1={x} y1={y} x2={x - 14} y2={y + 30} />;
          })}
        </g>
        <g stroke="#fff1d6" strokeWidth="0.4" opacity="0.1">
          {Array.from({ length: 22 }).map((_, i) => {
            const x = (i * 41 + 8) % 460;
            const y = (i * 23 + 12) % 460;
            return <line key={i} x1={x} y1={y} x2={x - 10} y2={y + 22} />;
          })}
        </g>

        {/* Subtle film grain layer */}
        <rect width="460" height="460" filter="url(#wgoGrain)" opacity="0.55" />
      </svg>

      {/* "MARVIN GAYE" type, top, condensed sepia */}
      <div style={{
        position: "absolute", top: size * 0.06, left: size * 0.07, right: size * 0.07,
        fontFamily: "'Playfair Display', serif",
        fontWeight: 900,
        fontSize: size * 0.082,
        letterSpacing: "0.04em",
        color: "#f4d39a",
        textShadow: "0 1px 2px rgba(0,0,0,0.5)",
        zIndex: 2
      }}>MARVIN GAYE</div>

      {/* "What's Going On", script-style italic overlapping the portrait */}
      <div style={{
        position: "absolute",
        top: size * 0.32,
        left: size * 0.06,
        right: size * 0.06,
        fontFamily: "'Playfair Display', serif",
        fontStyle: "italic",
        fontWeight: 700,
        fontSize: size * 0.155,
        lineHeight: 0.92,
        color: "#fbe2b3",
        letterSpacing: "-0.01em",
        textShadow: "0 2px 6px rgba(0,0,0,0.55)",
        zIndex: 2,
        textWrap: "balance"
      }}>
        <span style={{ display: "block", transform: "rotate(-2deg)", transformOrigin: "left top" }}>What&apos;s</span>
        <span style={{ display: "block", marginLeft: size * 0.18, transform: "rotate(-1deg)", marginTop: size * 0.01 }}>Going On</span>
      </div>

      {/* Tamla logo / catalog, bottom-right, mono */}
      <div style={{
        position: "absolute", bottom: size * 0.04, right: size * 0.06,
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: size * 0.028,
        letterSpacing: "0.18em",
        color: "#d9a874",
        opacity: 0.85,
        zIndex: 2
      }}>TAMLA · TS-310</div>

      {/* Bottom-left small year */}
      <div style={{
        position: "absolute", bottom: size * 0.04, left: size * 0.06,
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: size * 0.028,
        letterSpacing: "0.2em",
        color: "#d9a874",
        opacity: 0.85,
        zIndex: 2
      }}>1971</div>

      {/* Hotspots overlay, only when requested */}
      {hotspots && WGO_HOTSPOTS.map((h) => (
        <button
          key={h.id}
          className={"wgo-hot " + (active === h.id ? "is-active" : "")}
          style={{ left: h.x + "%", top: h.y + "%" }}
          onClick={() => {
            const next = active === h.id ? null : h.id;
            setActive(next);
            onHotspot && onHotspot(next);
          }}
          aria-label={h.title}
        >
          <span className="wgo-hot-dot">{h.n}</span>
        </button>
      ))}

      {/* "Recreation, not original" disclosure, tiny, corner */}
      <div className="wgo-cover-disclosure" aria-hidden="true">
        Editorial recreation
      </div>
    </div>
  );
}

/* Hotspot positions are percentage offsets within the cover frame. */
const WGO_HOTSPOTS = [
  {
    id: "type",
    n: 1,
    x: 12, y: 9,
    title: "The masthead",
    body: "Curtis McNair set 'MARVIN GAYE' in a custom hand-drawn condensed serif, sized so the artist's name reads first. The hierarchy was a deliberate break from Motown's house style, where the song title usually led."
  },
  {
    id: "script",
    n: 2,
    x: 12, y: 38,
    title: "The script",
    body: "The script title is two-line and tilted, handlettered by McNair, intentionally informal so it would feel personal rather than promotional. The looping G's were redrawn nine times before final."
  },
  {
    id: "rain",
    n: 3,
    x: 78, y: 25,
    title: "The rain",
    body: "Photographer Jim Hendin shot Gaye in a real January downpour outside Marvin's home, no studio rain rig. The streaks you see in the printed sleeve are airbrushed in over Hendin's negative to amplify what the weather already gave them."
  },
  {
    id: "collar",
    n: 4,
    x: 50, y: 68,
    title: "The collar",
    body: "The turned-up trench collar is the design's emotional anchor, it tells you the man is cold, looking outward, alone with the weather. McNair cropped tight to the collar specifically; the original Hendin frames showed Gaye's full torso."
  }
];

/* ============================================================
   2. AboutCover, designer story + clickable hotspots
   ============================================================
   Renders the album's real cover image (covers/<rank>.jpg) at 420px
   square with absolutely-positioned hotspot buttons sourced from
   data.hotspots. Falls back to the WGO CSS recreation only if no
   album is passed (legacy path). */
function AboutCover({ data, album }) {
  const [active, setActive] = useWAState(null);
  const hotspots = (data && data.hotspots) || (album && album.slug === "whats-going-on" ? WGO_HOTSPOTS : []);
  return (
    <div className="cover-about">
      <div className="cover-about-art">
        {album ? (
          <CoverWithHotspots
            album={album}
            hotspots={hotspots}
            active={active}
            setActive={setActive}
            size={420}
          />
        ) : (
          <WGOCover size={420} hotspots={true} onHotspot={setActive} />
        )}
      </div>

      <div className="cover-about-body">
        <div className="cover-credits">
          <div><b>Art direction</b><span>{data.artDirector}</span></div>
          <div><b>Photography</b><span>{data.photographer}</span></div>
          <div><b>Type</b><span>{data.typeface}</span></div>
          <div><b>Format</b><span>{data.format}</span></div>
        </div>

        <p className="cover-story-lede">{data.lede}</p>
        {data.paragraphs.map((p, i) => (
          <p key={i} className="cover-story-p" dangerouslySetInnerHTML={{ __html: p }} />
        ))}

        <div className="cover-hint">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
            <circle cx="12" cy="12" r="10"/>
            <line x1="12" y1="8" x2="12" y2="13"/>
            <circle cx="12" cy="16" r="0.8" fill="currentColor"/>
          </svg>
          Click the numbered points on the cover to read the design notes.
        </div>

        {active && (() => {
          const h = hotspots.find(h => h.id === active);
          if (!h) return null;
          return (
            <div className="cover-callout-active">
              <span className="cover-callout-num">{h.n}</span>
              <div>
                <div className="cover-callout-title">{h.title}</div>
                <p>{h.body}</p>
              </div>
            </div>
          );
        })()}
      </div>
    </div>
  );
}

/* CoverWithHotspots, square frame holding the real album cover image
   (covers/<rank>.jpg) with absolutely-positioned hotspot buttons. The
   hotspots use the same .wgo-hot class as the legacy WGO recreation, so
   styling carries over for free. */
function CoverWithHotspots({ album, hotspots, active, setActive, size = 420 }) {
  const src = "/covers/" + album.slug + ".jpg";
  return (
    <div
      className="cover-hot-wrap"
      style={{ width: "100%", maxWidth: size, aspectRatio: "1 / 1", position: "relative", flexShrink: 0, background: "#0a0a0a" }}
    >
      <img
        src={src}
        alt={album.title + " by " + album.artist + ", original cover"}
        style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
      />
      {hotspots.map((h) => (
        <button
          key={h.id}
          className={"wgo-hot " + (active === h.id ? "is-active" : "")}
          style={{ left: h.x + "%", top: h.y + "%" }}
          onClick={() => {
            const next = active === h.id ? null : h.id;
            setActive(next);
            if (next) {
              // Nudge the note (which renders below the image) into view so it's
              // obvious the dot changed the copy.
              setTimeout(() => {
                const el = document.querySelector(".cover-callout-active");
                if (el && window.matchMedia("(max-width: 760px)").matches) {
                  el.scrollIntoView({ behavior: "smooth", block: "center" });
                }
              }, 60);
            }
          }}
          aria-label={h.title}
          aria-pressed={active === h.id}
        >
          <span className="wgo-hot-dot">{h.n}</span>
        </button>
      ))}
    </div>
  );
}

/* ============================================================
   3. MakingGallery, large + 2-stack layout (option 0)
   Each frame is a CSS-illustrated placeholder with caption and
   an explicit "would-be" credit so the editor knows what to drop in.
   ============================================================ */
function MakingGallery({ images }) {
  const [lightbox, setLightbox] = useWAState(null);
  // True when at least one frame has a reference image wired in.
  const anyRef = images.some((im) => im && im.imageUrl);
  // Robust to short galleries, only render frames that actually exist.
  const primary = images[0];
  const stacked = images.slice(1, 3);
  if (!primary) return null;
  return (
    <>
      <div className={"mk-gallery" + (stacked.length === 0 ? " mk-gallery--solo" : "")}>
        <button className="mk-frame mk-frame-large" onClick={() => setLightbox(0)}>
          <FrameArt image={primary} />
          <span className="mk-cap">
            <span className="mk-cap-num">01</span>
            <span className="mk-cap-title">{primary.title}</span>
            <span className="mk-cap-credit">{primary.credit}</span>
          </span>
        </button>
        {stacked.length > 0 && (
          <div className="mk-stack">
            {stacked.map((im, i) => (
              <button key={i} className="mk-frame" onClick={() => setLightbox(i + 1)}>
                <FrameArt image={im} />
                <span className="mk-cap">
                  <span className="mk-cap-num">{String(i + 2).padStart(2, "0")}</span>
                  <span className="mk-cap-title">{im.title}</span>
                  <span className="mk-cap-credit">{im.credit}</span>
                </span>
              </button>
            ))}
          </div>
        )}
      </div>
      <p className="mk-disclosure">
        {anyRef
          ? <>Frames above use unlicensed reference imagery for layout review only, replace with licensed final art before publication. Each frame's <code>imageSource</code> flag marks it as a stand-in.</>
          : <>Frames above are editorial illustrations standing in for archival photography pending licensing.</>}
      </p>

      {lightbox !== null && (
        <div className="mk-lightbox" onClick={() => setLightbox(null)} role="dialog">
          <button className="mk-close" onClick={() => setLightbox(null)} aria-label="Close">×</button>
          <div className="mk-lightbox-inner" onClick={(e) => e.stopPropagation()}>
            <div className="mk-lightbox-art"><FrameArt image={images[lightbox]} large /></div>
            <div className="mk-lightbox-meta">
              <div className="mk-cap-num">{String(lightbox + 1).padStart(2, "0")}</div>
              <h4>{images[lightbox].title}</h4>
              <p>{images[lightbox].caption}</p>
              <div className="mk-credit-row">
                <b>Pictured —</b> {images[lightbox].pictured}
              </div>
              <div className="mk-credit-row">
                <b>Credit —</b> {images[lightbox].credit}
              </div>
              {images[lightbox].imageSource && (
                <div className="mk-credit-row mk-credit-ref">
                  <b>Reference image —</b> {images[lightbox].imageSource}
                </div>
              )}
            </div>
          </div>
        </div>
      )}
    </>
  );
}

/* FrameArt, render the wired reference image when present, fall back to the
   editorial CSS/SVG illustration otherwise. Keeps the placeholder behaviour
   untouched for frames that haven't been wired yet. */
function FrameArt({ image, large }) {
  if (image && image.imageUrl) {
    return (
      <div className="ga-art ga-photo">
        <img
          src={image.imageUrl}
          alt={image.title || ""}
          loading="lazy"
          style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
        />
        <span className="ga-ref-flag" aria-label="Reference image, not licensed">REF</span>
      </div>
    );
  }
  return <GalleryArt kind={image && image.kind} large={large} />;
}

/* GalleryArt, illustrated CSS/SVG placeholders per scene type */
function GalleryArt({ kind, large }) {
  if (kind === "hitsville") {
    // Studio control room, warm tungsten, mixing console, headphones
    return (
      <div className="ga-art ga-hitsville">
        <svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
          <defs>
            <linearGradient id="hRoom" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor="#3a2a1c"/>
              <stop offset="100%" stopColor="#0f0905"/>
            </linearGradient>
            <radialGradient id="hLamp" cx="0.7" cy="0.25" r="0.5">
              <stop offset="0%" stopColor="#f5c46a" stopOpacity="0.7"/>
              <stop offset="100%" stopColor="#f5c46a" stopOpacity="0"/>
            </radialGradient>
          </defs>
          <rect width="800" height="600" fill="url(#hRoom)"/>
          <rect width="800" height="600" fill="url(#hLamp)"/>
          {/* Back wall, acoustic panels */}
          {Array.from({ length: 8 }).map((_, i) => (
            <rect key={i} x={40 + i * 92} y="80" width="76" height="180" fill="#5a3a22" opacity="0.65"/>
          ))}
          {/* Mixing console */}
          <rect x="60" y="340" width="680" height="180" fill="#1a1208" stroke="#d97757" strokeWidth="1.5"/>
          <rect x="60" y="340" width="680" height="22" fill="#2a1c10"/>
          {/* Console knobs grid */}
          {Array.from({ length: 28 }).map((_, i) => {
            const col = i % 14, row = Math.floor(i / 14);
            return <circle key={i} cx={90 + col * 47} cy={395 + row * 60} r="9" fill="#3a2614" stroke="#d97757" strokeWidth="0.8"/>;
          })}
          {/* Faders */}
          {Array.from({ length: 14 }).map((_, i) => (
            <g key={i}>
              <rect x={84 + i * 47} y="475" width="6" height="40" fill="#0a0604"/>
              <rect x={80 + i * 47} y={478 + (i % 5) * 6} width="14" height="8" fill="#e8c896"/>
            </g>
          ))}
          {/* Tape reels */}
          <circle cx="180" cy="160" r="55" fill="#1a1208" stroke="#7a5028" strokeWidth="3"/>
          <circle cx="180" cy="160" r="22" fill="#2a1c10"/>
          <circle cx="180" cy="160" r="6" fill="#d97757"/>
          <circle cx="320" cy="160" r="55" fill="#1a1208" stroke="#7a5028" strokeWidth="3"/>
          <circle cx="320" cy="160" r="22" fill="#2a1c10"/>
          <circle cx="320" cy="160" r="6" fill="#d97757"/>
          {/* Microphone silhouette */}
          <g transform="translate(580 130)">
            <rect x="-8" y="0" width="16" height="50" rx="6" fill="#d4a574"/>
            <rect x="-2" y="50" width="4" height="100" fill="#5a3a22"/>
            <rect x="-30" y="150" width="60" height="6" fill="#3a2614"/>
          </g>
          {/* Headphones on console */}
          <g transform="translate(640 320)">
            <path d="M 0 30 Q 0 -10 35 -10 Q 70 -10 70 30" fill="none" stroke="#2a1c10" strokeWidth="6"/>
            <ellipse cx="2" cy="40" rx="14" ry="18" fill="#1a1208"/>
            <ellipse cx="68" cy="40" rx="14" ry="18" fill="#1a1208"/>
          </g>
          {/* Light streak */}
          <path d="M 600 0 L 760 0 L 760 200 Z" fill="#f5c46a" opacity="0.06"/>
        </svg>
        {large && <div className="ga-glow"/>}
      </div>
    );
  }

  if (kind === "session") {
    // Marvin at the mic, three-quarter, condenser, headphones
    return (
      <div className="ga-art ga-session">
        <svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
          <defs>
            <radialGradient id="sBg" cx="0.5" cy="0.5" r="0.7">
              <stop offset="0%" stopColor="#3a2210"/>
              <stop offset="100%" stopColor="#080402"/>
            </radialGradient>
            <radialGradient id="sLight" cx="0.62" cy="0.38" r="0.35">
              <stop offset="0%" stopColor="#f5c46a" stopOpacity="0.55"/>
              <stop offset="100%" stopColor="#f5c46a" stopOpacity="0"/>
            </radialGradient>
          </defs>
          <rect width="800" height="600" fill="url(#sBg)"/>
          <rect width="800" height="600" fill="url(#sLight)"/>

          {/* Figure, silhouette, three-quarter, behind mic */}
          <ellipse cx="500" cy="220" rx="80" ry="98" fill="#3a2210"/>
          <ellipse cx="500" cy="160" rx="80" ry="40" fill="#0a0402"/>{/* hair */}
          <path d="M 420 240 Q 420 320 460 360 L 540 360 Q 580 320 580 240 L 580 600 L 420 600 Z" fill="#1a0e07"/>
          {/* Headphone band over silhouette */}
          <path d="M 422 165 Q 500 100 578 165" stroke="#5a3a22" strokeWidth="10" fill="none"/>
          <ellipse cx="420" cy="195" rx="22" ry="28" fill="#0a0402"/>
          <ellipse cx="580" cy="195" rx="22" ry="28" fill="#0a0402"/>

          {/* Mic stand + condenser */}
          <rect x="318" y="280" width="6" height="320" fill="#3a2614"/>
          <ellipse cx="321" cy="600" rx="80" ry="6" fill="#1a0e07"/>
          <g transform="translate(290 220)">
            <rect x="0" y="0" width="62" height="100" rx="8" fill="#d4a574" stroke="#7a5028" strokeWidth="2"/>
            {/* Mesh lines */}
            {Array.from({ length: 8 }).map((_, i) => (
              <line key={i} x1="6" y1={12 + i * 11} x2="56" y2={12 + i * 11} stroke="#5a3a22" strokeWidth="0.7"/>
            ))}
            {Array.from({ length: 5 }).map((_, i) => (
              <line key={i} x1={12 + i * 12} y1="6" x2={12 + i * 12} y2="94" stroke="#5a3a22" strokeWidth="0.7"/>
            ))}
            <rect x="20" y="100" width="22" height="14" fill="#5a3a22"/>
          </g>

          {/* Pop filter, circle */}
          <circle cx="380" cy="270" r="42" fill="none" stroke="#f5e3c0" strokeWidth="1.5" opacity="0.5"/>
          <circle cx="380" cy="270" r="42" fill="#f5e3c0" opacity="0.04"/>

          {/* Lyric sheet held in hand silhouette */}
          <g transform="translate(120 380) rotate(-8)">
            <rect width="140" height="170" fill="#e8d5a8" stroke="#7a5028" strokeWidth="1"/>
            {Array.from({ length: 9 }).map((_, i) => (
              <line key={i} x1="14" y1={20 + i * 16} x2="120" y2={20 + i * 16} stroke="#3a2210" strokeWidth="1"/>
            ))}
          </g>

          {/* Lens flare */}
          <circle cx="640" cy="120" r="4" fill="#f5c46a"/>
          <circle cx="640" cy="120" r="40" fill="#f5c46a" opacity="0.08"/>
        </svg>
      </div>
    );
  }

  if (kind === "lyrics") {
    // Lyric sheet on a desk, handwritten lines
    return (
      <div className="ga-art ga-lyrics">
        <svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
          <defs>
            <linearGradient id="lDesk" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor="#1f1208"/>
              <stop offset="100%" stopColor="#0a0604"/>
            </linearGradient>
          </defs>
          <rect width="800" height="600" fill="url(#lDesk)"/>
          {/* Wood grain */}
          {Array.from({ length: 30 }).map((_, i) => (
            <line key={i} x1="0" y1={i * 22} x2="800" y2={i * 22 + 6} stroke="#3a2210" strokeWidth="0.5" opacity="0.4"/>
          ))}
          {/* Sheet */}
          <g transform="translate(120 60) rotate(-3)">
            <rect width="500" height="500" fill="#f0e0bc" stroke="#7a5028" strokeWidth="1.5"/>
            <rect x="0" y="0" width="500" height="36" fill="#7a5028" opacity="0.18"/>
            {/* Header text, hand-style */}
            <text x="20" y="60" fontFamily="'Playfair Display', serif" fontStyle="italic" fontSize="22" fill="#3a2210">
              What's Going On, Verse 1
            </text>
            {/* Lines, with strikethroughs and edits */}
            {[
              { y: 100, w: 380, edit: false },
              { y: 130, w: 320, edit: true },
              { y: 160, w: 400, edit: false },
              { y: 190, w: 280, edit: false },
              { y: 220, w: 360, edit: true },
              { y: 250, w: 300, edit: false },
              { y: 290, w: 200, edit: false },
              { y: 320, w: 350, edit: false },
              { y: 350, w: 260, edit: true },
              { y: 380, w: 320, edit: false },
              { y: 420, w: 340, edit: false },
              { y: 450, w: 280, edit: false },
            ].map((l, i) => (
              <g key={i}>
                <rect x="20" y={l.y} width={l.w} height="3" fill="#3a2210" opacity="0.7"/>
                {l.edit && <line x1="20" y1={l.y + 1} x2={20 + l.w * 0.6} y2={l.y + 1} stroke="#a83232" strokeWidth="1.2"/>}
                {l.edit && (
                  <text x={20 + l.w + 8} y={l.y + 5} fontFamily="'Playfair Display', serif" fontStyle="italic" fontSize="14" fill="#a83232">
                    rev.
                  </text>
                )}
              </g>
            ))}
            <text x="20" y="490" fontFamily="'JetBrains Mono', monospace" fontSize="11" fill="#7a5028">
              MG / Aug 1970
            </text>
          </g>
          {/* Pen */}
          <g transform="translate(560 380) rotate(40)">
            <rect width="180" height="10" rx="3" fill="#1a0e07"/>
            <rect x="170" width="14" height="10" fill="#d97757"/>
            <polygon points="184,0 200,5 184,10" fill="#3a2210"/>
          </g>
          {/* Coffee ring */}
          <circle cx="640" cy="150" r="58" fill="none" stroke="#5a3a22" strokeWidth="3" opacity="0.45"/>
          <circle cx="640" cy="150" r="58" fill="#3a2210" opacity="0.18"/>
        </svg>
      </div>
    );
  }

  return null;
}

/* ============================================================
   4. StickyBuyRail, Amazon affiliate module that follows reader
   ------------------------------------------------------------
   Mounts to the right edge of the article column.
   - Hides until the user has scrolled past the hero.
   - Auto-tucks above the footer (via IntersectionObserver of footer).
   - Dismissible; remembers dismissal in localStorage per slug.
   ============================================================ */
function StickyBuyRail({ slug, items, title, subtitle, artist, albumTitle }) {
  // Many of the per-pressing Amazon URLs in the dataset return "No results
  // for your search query in Music" because the qualifier text is too
  // specific. We route every vinyl-format Amazon item to a clean album-vinyl
  // search instead, and collapse those items into a single "Vinyl LP, current
  // pressings" card so the same destination isn't shown under three different
  // headings. Books, biographies and documentaries (13 entries in the dataset)
  // keep their original URL and format text.
  const genericAmazon = (artist && albumTitle)
    ? "https://www.amazon.co.uk/s?k=" + encodeURIComponent(artist + " " + albumTitle + " vinyl") + "&i=popular&tag=theleadin1-21"
    : null;
  const NON_VINYL_RE = /\b(book|biograph|documentary|film|cassette|tape|cd only|hardcover|paperback)\b/i;
  const isAmazonVinyl = (it) =>
    (it.vendor || "").toLowerCase().includes("amazon") && !NON_VINYL_RE.test(it.format || "");

  // Build a working list: collapse Amazon-vinyl items into one card, keep
  // non-vinyl items as themselves.
  const safeItems = items || [];
  const amazonVinyl = safeItems.filter(isAmazonVinyl);
  const others = safeItems.filter(it => !isAmazonVinyl(it));
  let displayItems = safeItems;
  if (amazonVinyl.length && genericAmazon) {
    const nums = amazonVinyl
      .map(it => parseFloat((it.price || "").replace(/[^\d.]/g, "")))
      .filter(n => !isNaN(n));
    const minP = nums.length ? Math.min(...nums) : null;
    const collapsed = {
      format: amazonVinyl.length > 1 ? "Vinyl LP, current pressings" : "Vinyl LP",
      vendor: "Amazon UK",
      price: minP != null ? "From £" + minP.toFixed(2) : (amazonVinyl[0].price || ""),
      url: genericAmazon,
      note: "Browse the current Amazon UK listings for " + (albumTitle || "this record") + " on vinyl.",
    };
    displayItems = [collapsed, ...others];
  }
  const resolveUrl = (it) => (it && it.url) || "#";
  const [show, setShow] = useWAState(false);
  const [hide, setHide] = useWAState(false);
  const [dismissed, setDismissed] = useWAState(() => {
    try { return localStorage.getItem("bbr-buyrail-dismiss-" + slug) === "1"; } catch (e) { return false; }
  });
  const [openMore, setOpenMore] = useWAState(false);

  useWAEffect(() => {
    const onScroll = () => {
      // Show after 600px scroll (past hero); hide if footer is in viewport
      const sc = window.scrollY || document.documentElement.scrollTop;
      setShow(sc > 600);
      const footer = document.querySelector(".endnote");
      if (footer) {
        const r = footer.getBoundingClientRect();
        setHide(r.top < window.innerHeight - 60);
      }
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const dismiss = () => {
    setDismissed(true);
    try { localStorage.setItem("bbr-buyrail-dismiss-" + slug, "1"); } catch (e) {}
  };

  if (dismissed || !items || !items.length) return null;

  const visible = show && !hide;
  const top = displayItems[0];
  const more = displayItems.slice(1);

  return (
    <aside className={"buyrail " + (visible ? "is-visible" : "")} aria-label="Get a copy">
      <button className="buyrail-close" onClick={dismiss} aria-label="Dismiss">×</button>
      <div className="buyrail-eyebrow">
        <span className="buyrail-dot" /> Get a copy
      </div>
      <div className="buyrail-title">{title}</div>
      <div className="buyrail-sub">{subtitle}</div>

      <a className="buyrail-primary" href={resolveUrl(top)} target="_blank" rel="sponsored noopener">
        <div className="buyrail-prim-row">
          <span className="buyrail-format">{top.format}</span>
          <span className="buyrail-price">{top.price}</span>
        </div>
        <div className="buyrail-vendor">
          <AmazonGlyph /> Buy on {top.vendor}
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M7 17L17 7M9 7h8v8"/></svg>
        </div>
        {top.note && <div className="buyrail-note">{top.note}</div>}
      </a>

      {more.length > 0 && (
        <>
          <button
            className="buyrail-toggle"
            onClick={() => setOpenMore(!openMore)}
            aria-expanded={openMore}
          >
            {openMore ? "Hide" : "Show"} {more.length} more format{more.length === 1 ? "" : "s"}
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ transform: openMore ? "rotate(180deg)" : "none", transition: "transform .2s" }}><path d="M6 9l6 6 6-6"/></svg>
          </button>
          {openMore && (
            <div className="buyrail-more">
              {more.map((m, i) => (
                <a key={i} className="buyrail-row" href={resolveUrl(m)} target="_blank" rel="sponsored noopener">
                  <span className="buyrail-row-fmt">{m.format}</span>
                  <span className="buyrail-row-price">{m.price}</span>
                  <span className="buyrail-row-cta">View →</span>
                </a>
              ))}
            </div>
          )}
        </>
      )}

      <div className="buyrail-disclose">
        The Lead-In is an Amazon Associate. We earn a small commission on qualifying purchases, at no extra cost to you.
      </div>
    </aside>
  );
}

function AmazonGlyph() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
      <path d="M3 16c4 4 14 4 18 0" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
      <path d="M19 17l3 1-1-3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
      <text x="12" y="11" textAnchor="middle" fontFamily="'JetBrains Mono', monospace" fontSize="9" fontWeight="700" fill="currentColor">a</text>
    </svg>
  );
}

Object.assign(window, {
  WGOCover, AboutCover, CoverWithHotspots, MakingGallery, StickyBuyRail
});
