/* global React */
const { useState, useRef, useEffect } = React;

const EXTERNAL_ROUTES = {
  blog: "/blog",
  freeColoringPages: "/free-coloring-pages",
};

// ------- Sparkle / star icons (inline SVG) -------

const Sparkle = ({ size = 14, color = "currentColor" }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill={color} aria-hidden="true">
    <path d="M12 0 L13.6 9.4 L24 12 L13.6 14.6 L12 24 L10.4 14.6 L0 12 L10.4 9.4 Z" />
  </svg>
);

const StarSolid = ({ size = 14, color = "currentColor" }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill={color} aria-hidden="true">
    <path d="M12 2 L14.6 8.6 L21.8 9.2 L16.3 13.9 L18 21 L12 17.1 L6 21 L7.7 13.9 L2.2 9.2 L9.4 8.6 Z" />
  </svg>
);

const HeadphonesIcon = ({ size = 18, color = "currentColor" }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke={color}
       strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <path d="M3 14v-2a9 9 0 0 1 18 0v2"/>
    <path d="M21 14a2 2 0 0 1-2 2h-1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1a2 2 0 0 1 2 2z"/>
    <path d="M3 14a2 2 0 0 0 2 2h1a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H5a2 2 0 0 0-2 2z"/>
  </svg>
);

const CartIcon = ({ size = 18 }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
       strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <circle cx="9" cy="20" r="1.4"/><circle cx="18" cy="20" r="1.4"/>
    <path d="M3 4h2l2.4 11.2a2 2 0 0 0 2 1.6h7.6a2 2 0 0 0 2-1.6L21 8H6"/>
  </svg>
);

const MenuIcon = ({ size = 22 }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
       strokeWidth="1.8" strokeLinecap="round" aria-hidden="true">
    <path d="M4 7h16" />
    <path d="M4 12h16" />
    <path d="M4 17h16" />
  </svg>
);

const ChevronRight = ({ size = 16 }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
       strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <polyline points="9 6 15 12 9 18"/>
  </svg>
);

const Close = ({ size = 18 }) => (
  <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
       strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
  </svg>
);

function useIsNarrow(maxWidth = 720) {
  const getMatches = () => (
    typeof window !== "undefined"
      ? window.matchMedia(`(max-width: ${maxWidth}px)`).matches
      : false
  );
  const [matches, setMatches] = useState(getMatches);

  useEffect(() => {
    if (typeof window === "undefined") return undefined;
    const query = window.matchMedia(`(max-width: ${maxWidth}px)`);
    const update = () => setMatches(query.matches);
    update();
    query.addEventListener?.("change", update);
    return () => query.removeEventListener?.("change", update);
  }, [maxWidth]);

  return matches;
}

// ------- Logo (inline SVG so it scales) -------

const Logo = ({ color = "var(--lwc-navy-900)", accent = "var(--lwc-gold-500)", height = 28 }) => (
  <svg viewBox="0 0 480 80" height={height} role="img" aria-label="Learn with Coloring">
    <text x="0" y="55" style={{
      fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 44,
      letterSpacing: 0, fill: color
    }}>Learn with Color<tspan dx="2">i</tspan>ng</text>
    <g transform="translate(385 19)" fill={accent}>
      <path d="M0 -8 L1.6 -1.6 L8 0 L1.6 1.6 L0 8 L-1.6 1.6 L-8 0 L-1.6 -1.6 Z"/>
    </g>
  </svg>
);

// ------- Button -------

const Button = ({ variant = "primary", size = "md", icon, children, onClick, style, ...rest }) => {
  const base = {
    display: "inline-flex", alignItems: "center", gap: 8,
    fontFamily: "var(--font-sans)", fontWeight: 700, letterSpacing: "0.01em",
    borderRadius: 999, border: "none", cursor: "pointer",
    maxWidth: "100%", minWidth: 0, whiteSpace: "normal", textAlign: "center",
    transition: "background var(--dur-base) var(--ease-out), transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out)",
  };
  const sizes = {
    sm: { padding: "8px 16px",  fontSize: 13 },
    md: { padding: "13px 26px", fontSize: 15 },
    lg: { padding: "16px 32px", fontSize: 16 },
  };
  const variants = {
    primary: {
      background: "var(--lwc-gold-500)", color: "var(--lwc-navy-900)",
      boxShadow: "0 1px 2px rgba(20,33,61,.06), 0 6px 16px rgba(184,134,46,.22)",
    },
    secondary: {
      background: "var(--lwc-navy-700)", color: "var(--lwc-cream-50)",
    },
    ghost: {
      background: "transparent", color: "var(--lwc-navy-900)",
      border: "1.5px solid var(--lwc-navy-700)",
    },
    text: {
      background: "transparent", color: "var(--lwc-navy-900)",
      textDecoration: "underline", textUnderlineOffset: 4,
      padding: "8px 12px",
    },
  };
  return (
    <button onClick={onClick} {...rest}
      style={{ ...base, ...sizes[size], ...variants[variant], ...style }}
      onMouseDown={e => e.currentTarget.style.transform = "scale(0.97)"}
      onMouseUp={e => e.currentTarget.style.transform = ""}
      onMouseLeave={e => e.currentTarget.style.transform = ""}>
      {icon}{children}
    </button>
  );
};

// ------- AnnouncementBar -------

const AnnouncementBar = () => (
  <div style={{
    background: "var(--lwc-navy-900)", color: "var(--lwc-cream-50)",
    fontFamily: "var(--font-sans)", fontSize: 13, fontWeight: 500,
    textAlign: "center", padding: "10px 16px",
    display: "flex", justifyContent: "center", alignItems: "center", gap: 10,
  }}>
    <Sparkle size={12} color="var(--lwc-gold-300)" />
    <span>Every book pairs with a custom audio adventure — narrated, with music & sound effects.</span>
    <Sparkle size={12} color="var(--lwc-gold-300)" />
  </div>
);

// ------- Header -------

const Header = ({ cartCount, onCart, onLogo, onBrowse, onNavigate }) => {
  const isMobile = useIsNarrow();
  const [menuOpen, setMenuOpen] = useState(false);
  const closeMenu = () => setMenuOpen(false);
  const navItems = [
    { label: "Shop books", href: "/books" },
    { label: "How it works", action: () => onNavigate && onNavigate("faq") },
    { label: "The audio", action: () => onNavigate && onNavigate("home") },
    { label: "Blog", href: EXTERNAL_ROUTES.blog },
    { label: "Free coloring pages", href: EXTERNAL_ROUTES.freeColoringPages },
    { label: "Shipping", action: () => onNavigate && onNavigate("delivery") },
  ];
  const navLinkStyle = {
    color: "var(--fg-primary)",
    cursor: "pointer",
    textDecoration: "none",
    lineHeight: 1.35,
  };

  return (
  <header style={{
    display: isMobile ? "grid" : "flex",
    gridTemplateColumns: isMobile ? "1fr auto auto" : undefined,
    alignItems: "center",
    gap: isMobile ? 10 : 28,
    flexWrap: "wrap",
    padding: isMobile ? "14px var(--container-pad) 16px" : "18px var(--container-pad)",
    borderBottom: "1px solid var(--border-soft)",
    background: "var(--bg-canvas)",
  }}>
    <a onClick={() => { closeMenu(); onLogo?.(); }} style={{ cursor: "pointer", display: "flex", minWidth: 0 }}>
      <Logo height={isMobile ? 26 : 28} />
    </a>
    <nav style={{
      display: isMobile ? (menuOpen ? "grid" : "none") : "flex",
      gap: isMobile ? "10px 16px" : 22,
      marginLeft: isMobile ? 0 : 12,
      fontFamily: "var(--font-sans)",
      fontWeight: 600,
      fontSize: 14,
      flexWrap: "wrap",
      gridColumn: isMobile ? "1 / -1" : undefined,
      gridTemplateColumns: isMobile ? "1fr" : undefined,
      minWidth: 0,
      padding: isMobile ? "14px 0 2px" : undefined,
      borderTop: isMobile ? "1px solid var(--border-soft)" : undefined,
      order: isMobile ? 4 : undefined,
    }}>
      {navItems.map((item) => item.href ? (
        <a key={item.label} href={item.href} target="_top" style={navLinkStyle} onClick={closeMenu}>{item.label}</a>
      ) : (
        <a key={item.label} onClick={() => { closeMenu(); item.action?.(); }} style={navLinkStyle}>{item.label}</a>
      ))}
    </nav>
    <div style={{ marginLeft: isMobile ? 0 : "auto", display: "flex", gap: 10, alignItems: "center", justifyContent: "flex-end", order: isMobile ? 2 : undefined }}>
      <button onClick={onCart} style={{
        position: "relative", background: "transparent", border: "1.5px solid var(--border-soft)",
        borderRadius: 999, padding: isMobile ? "9px 12px" : "10px 14px", display: "flex", alignItems: "center", gap: 8,
        color: "var(--fg-primary)", cursor: "pointer", fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13,
      }}>
        <CartIcon /> Waitlist
        {cartCount > 0 && (
          <span style={{
            position: "absolute", top: -6, right: -6, minWidth: 20, height: 20, borderRadius: 999,
            background: "var(--lwc-gold-500)", color: "var(--lwc-navy-900)",
            fontSize: 11, fontWeight: 800, display: "grid", placeItems: "center", padding: "0 6px",
          }}>{cartCount}</span>
        )}
      </button>
    </div>
    {isMobile && (
      <button
        type="button"
        aria-label={menuOpen ? "Close menu" : "Open menu"}
        aria-expanded={menuOpen}
        onClick={() => setMenuOpen(open => !open)}
        style={{
          width: 42,
          height: 42,
          borderRadius: 999,
          border: "1.5px solid var(--border-soft)",
          background: "var(--bg-surface)",
          color: "var(--fg-primary)",
          display: "grid",
          placeItems: "center",
          cursor: "pointer",
          boxShadow: "var(--shadow-card)",
          order: 3,
        }}
      >
        {menuOpen ? <Close /> : <MenuIcon />}
      </button>
    )}
  </header>
  );
};

// ------- Book mock (used in product cards and hero) -------

// Auto-size the script name so it always fits the cover.
// Sizes are in cqi (% of BookMock width) so they shrink proportionally with the card.
// Caveat avg char width ≈ 0.5em, usable inline budget ≈ 70cqi after padding.
function fitNameSize(name) {
  const len = Math.max(1, (name || "").trim().length);
  // Empirical: aim for total text width ≈ 70cqi. Cap at 34cqi so short names don't blow out.
  const cqi = Math.min(34, 145 / len);
  return `${cqi.toFixed(2)}cqi`;
}

const NAME_MAX = 14;

function useCoverCrossfade(coverImage) {
  const [displayCoverImage, setDisplayCoverImage] = useState(coverImage || "");
  const [incomingCoverImage, setIncomingCoverImage] = useState(null);
  const [incomingVisible, setIncomingVisible] = useState(false);

  useEffect(() => {
    if (!coverImage) {
      setDisplayCoverImage("");
      setIncomingCoverImage(null);
      setIncomingVisible(false);
      return undefined;
    }

    if (!displayCoverImage) {
      setDisplayCoverImage(coverImage);
      return undefined;
    }

    if (coverImage === displayCoverImage) {
      setIncomingCoverImage(null);
      setIncomingVisible(false);
      return undefined;
    }

    let cancelled = false;
    const image = new window.Image();
    image.onload = () => {
      if (cancelled) return;
      setIncomingCoverImage(coverImage);
      window.requestAnimationFrame(() => {
        if (!cancelled) setIncomingVisible(true);
      });
    };
    image.src = coverImage;

    return () => {
      cancelled = true;
    };
  }, [coverImage, displayCoverImage]);

  useEffect(() => {
    if (!incomingVisible || !incomingCoverImage) return undefined;

    const timer = window.setTimeout(() => {
      setDisplayCoverImage(incomingCoverImage);
      setIncomingCoverImage(null);
      setIncomingVisible(false);
    }, 220);

    return () => window.clearTimeout(timer);
  }, [incomingVisible, incomingCoverImage]);

  return {
    displayCoverImage,
    incomingCoverImage,
    incomingVisible,
  };
}

const CoverArcText = ({ text, idSuffix, y = 46, curveDepth = 18, fontSize = 24, letterSpacing = "0.18em", color = "var(--lwc-gold-300)", fontStyle = "normal", fontFamily = "var(--font-sans)", fontWeight = 700 }) => {
  const pathId = `cover-arc-${idSuffix}`;
  return (
    <svg viewBox="0 0 100 32" preserveAspectRatio="none" aria-hidden="true" style={{
      position: "absolute", left: "50%", top: `${y}%`, transform: "translateX(-50%)",
      width: "78%", height: "12%", overflow: "visible",
      filter: "drop-shadow(0 0.75cqi 1.6cqi rgba(20,33,61,.5))",
      pointerEvents: "none",
    }}>
      <defs>
        <path id={pathId} d={`M 4 22 Q 50 ${22 - curveDepth} 96 22`} />
      </defs>
      <text
        fill={color}
        fontFamily={fontFamily}
        fontSize={fontSize}
        fontWeight={fontWeight}
        fontStyle={fontStyle}
        letterSpacing={letterSpacing}
        textAnchor="middle">
        <textPath href={`#${pathId}`} startOffset="50%">
          {text}
        </textPath>
      </text>
    </svg>
  );
};

const BookMock = ({ name, title, accent = "var(--lwc-gold-300)", width = "60%", label = "The adventures of", coverImage, coverCallout, ageRange, nameFont }) => {
  const display = (name || "").slice(0, NAME_MAX);
  const font = nameFont || {};
  const {
    displayCoverImage,
    incomingCoverImage,
    incomingVisible,
  } = useCoverCrossfade(coverImage);
  const activeCoverImage = displayCoverImage || coverImage;
  const nameLength = display.trim().length;
  let subtitleY = 27.8;
  let dividerTop = "23.8%";

  if (nameLength <= 4) {
    subtitleY = 30.4;
    dividerTop = "25.9%";
  } else if (nameLength <= 6) {
    subtitleY = 29.5;
    dividerTop = "25%";
  } else if (nameLength <= 8) {
    subtitleY = 29.3;
    dividerTop = "25.1%";
  } else if (nameLength <= 10) {
    subtitleY = 28.7;
    dividerTop = "24.3%";
  }
  return (
  <div style={{
    width, aspectRatio: "3 / 4",
    backgroundColor: "var(--lwc-navy-700)",
    borderRadius: "4px 14px 14px 4px",
    boxShadow: "-6px 6px 0 rgba(20,33,61,.10), inset 8px 0 0 rgba(0,0,0,.20), 0 18px 40px rgba(20,33,61,.25)",
    display: "grid", alignItems: "start", justifyItems: "center", padding: activeCoverImage ? "15% 11% 9%" : "26% 11% 9%", textAlign: "center",
    color: "var(--lwc-cream-50)",
    position: "relative", overflow: "hidden",
    containerType: "inline-size",
  }}>
    {activeCoverImage && (
      <div style={{
        position: "absolute",
        inset: 0,
        backgroundImage: `linear-gradient(180deg, rgba(20,33,61,.34), rgba(20,33,61,.08) 42%, rgba(20,33,61,.04)), url("${activeCoverImage}")`,
        backgroundSize: "cover",
        backgroundPosition: "center",
        backgroundRepeat: "no-repeat",
      }}/>
    )}
    {incomingCoverImage && (
      <div style={{
        position: "absolute",
        inset: 0,
        backgroundImage: `linear-gradient(180deg, rgba(20,33,61,.34), rgba(20,33,61,.08) 42%, rgba(20,33,61,.04)), url("${incomingCoverImage}")`,
        backgroundSize: "cover",
        backgroundPosition: "center",
        backgroundRepeat: "no-repeat",
        opacity: incomingVisible ? 1 : 0,
        transition: "opacity 180ms ease",
      }}/>
    )}
    <div style={{
      position: "absolute", inset: 0,
      background: activeCoverImage ? "linear-gradient(180deg, rgba(16,28,44,.10) 0%, rgba(16,28,44,0) 28%, rgba(16,28,44,0) 62%, rgba(16,28,44,.10) 100%)" : "none",
      pointerEvents: "none",
    }}/>
    {activeCoverImage && (
      <div style={{
        position: "absolute", top: "4.8%", left: "50%", transform: "translateX(-50%)",
        width: "84%", height: "28%",
        borderRadius: "999px 999px 36% 36% / 28% 28% 22% 22%",
        background: "radial-gradient(ellipse at center, rgba(10,22,39,.34) 0%, rgba(10,22,39,.24) 44%, rgba(10,22,39,0) 78%)",
        filter: "blur(6px)",
        pointerEvents: "none",
      }}/>
    )}
    <div style={{ position: "absolute", top: "5.5%", right: "8%", zIndex: 2 }}><Sparkle size={14} color={accent} /></div>
    {activeCoverImage ? (
      <>
        <CoverArcText
          text={label}
          idSuffix={`label-${label.replace(/\s+/g, "-").toLowerCase()}-${display.toLowerCase()}`}
          y={8.2}
          curveDepth={9}
          fontSize={6.9}
          letterSpacing="0.12em"
          color="rgba(255,236,188,1)"
          fontFamily="var(--font-sans)"
          fontWeight={900}
        />
        <div style={{
          position: "absolute", top: nameLength <= 8 ? "15.5%" : "16.6%", left: "50%", transform: "translateX(-50%)",
          width: "76%", display: "grid", justifyItems: "center",
          gap: "0.8cqi",
          zIndex: 2,
        }}>
          <div style={{
            fontFamily: font.family || "var(--font-script)",
            fontWeight: font.weight || 600,
            fontStyle: font.style || "normal",
            color: accent,
            fontSize: `min(${fitNameSize(display)}, 20.5cqi)`,
            lineHeight: 0.92,
            wordBreak: "break-word",
            textShadow: "0 0.85cqi 1.7cqi rgba(20,33,61,.62), 0 0 0.16cqi rgba(255,239,201,.24)",
          }}>
            {display}
          </div>
        </div>
        <CoverArcText
          text={title}
          idSuffix={`title-${title.replace(/\s+/g, "-").toLowerCase()}-${display.toLowerCase()}`}
          y={subtitleY}
          curveDepth={4}
          fontSize={6}
          letterSpacing="0.02em"
          color="rgba(48,34,18,0.98)"
          fontFamily="var(--font-serif)"
          fontStyle="italic"
          fontWeight={700}
        />
        <div style={{
          position: "absolute", top: dividerTop, left: "50%", transform: "translateX(-50%)",
          width: "48%",
          height: "0.45cqi",
          borderRadius: 999,
          background: "linear-gradient(90deg, rgba(255,215,122,0), rgba(255,215,122,.95) 22%, rgba(255,215,122,.95) 78%, rgba(255,215,122,0))",
          boxShadow: "0 0 1.2cqi rgba(255,215,122,.35)",
          zIndex: 2,
        }}/>
      </>
    ) : (
      <div style={{ width: "100%" }}>
        <div style={{ fontSize: "6.5cqi", letterSpacing: "0.22em", color: accent, textTransform: "uppercase", fontWeight: 700 }}>
          {label}
        </div>
        <div style={{
          fontFamily: font.family || "var(--font-script)",
          fontWeight: font.weight || 600,
          fontStyle: font.style || "normal",
          color: accent,
          fontSize: fitNameSize(display),
          lineHeight: 1, margin: "4cqi 0 2cqi",
          wordBreak: "break-word",
        }}>
          {display}
        </div>
        <div style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: "7.5cqi", lineHeight: 1.25 }}>
          {title}
        </div>
      </div>
    )}
    {activeCoverImage && coverCallout && (
      <div style={{
        position: "absolute", left: "5.5%", top: "44%", width: "29%",
        aspectRatio: "1 / 1", borderRadius: "46% 54% 44% 56% / 52% 42% 58% 48%",
        background: "linear-gradient(180deg, rgba(255,249,236,.97), rgba(250,233,193,.95))",
        border: "0.75cqi solid rgba(255,215,122,.92)",
        color: "var(--lwc-cream-50)", display: "grid", placeItems: "center",
        padding: "2.5cqi", boxShadow: "0 1.2cqi 3cqi rgba(20,33,61,.28), inset 0 0 0 0.25cqi rgba(255,255,255,.4)",
        transform: "rotate(-6deg)",
        zIndex: 2,
      }}>
        <div style={{
          position: "absolute", inset: "8%", borderRadius: "46% 54% 44% 56% / 52% 42% 58% 48%",
          border: "0.18cqi solid rgba(112,82,24,.16)",
        }}/>
        <span style={{
          fontFamily: "var(--font-serif)", fontWeight: 700,
          color: "var(--lwc-navy-900)",
          fontSize: "3.95cqi", lineHeight: 1.08, textAlign: "center",
          textWrap: "balance",
        }}>{coverCallout}</span>
      </div>
    )}
    {activeCoverImage && ageRange && (
      <div style={{
        position: "absolute", right: "5.8%", top: "49%", width: "24%",
        aspectRatio: "1 / 1", borderRadius: "42% 58% 54% 46% / 44% 42% 58% 56%",
        background: "linear-gradient(180deg, rgba(255,249,236,.97), rgba(248,228,186,.96))",
        border: "0.75cqi solid rgba(255,215,122,.92)",
        color: "var(--lwc-cream-50)", display: "grid", placeItems: "center",
        padding: "2.1cqi", boxShadow: "0 1.2cqi 3cqi rgba(20,33,61,.28), inset 0 0 0 0.25cqi rgba(255,255,255,.4)",
        transform: "rotate(7deg)",
        zIndex: 2,
      }}>
        <div style={{
          position: "absolute", inset: "9%", borderRadius: "42% 58% 54% 46% / 44% 42% 58% 56%",
          border: "0.18cqi solid rgba(112,82,24,.16)",
        }}/>
        <span style={{
          fontFamily: "var(--font-serif)", fontWeight: 700,
          color: "var(--lwc-navy-900)",
          fontSize: "3.25cqi", lineHeight: 1.02, textAlign: "center",
          textWrap: "balance", letterSpacing: "0.04em", textTransform: "uppercase",
        }}>
          <span style={{ display: "block", fontSize: "2.2cqi", letterSpacing: "0.16em", marginBottom: "0.25cqi" }}>Ages</span>
          <span style={{ display: "block", fontSize: "5.1cqi", letterSpacing: "0.01em", lineHeight: 0.95 }}>3-10</span>
          <span style={{ display: "block", fontSize: "2.05cqi", letterSpacing: "0.14em", marginTop: "0.2cqi" }}>Years</span>
        </span>
      </div>
    )}
    <div style={{ position: "absolute", bottom: "7%", left: 0, right: 0, display: "flex", justifyContent: "center", gap: 4, zIndex: 2 }}>
      <Sparkle size={8} color={accent}/><Sparkle size={6} color={accent}/><Sparkle size={8} color={accent}/>
    </div>
  </div>
);
};

// ------- NameInput (script-font textarea) -------

const NameInput = ({ value, onChange, placeholder = "Your child's first name", onFocus, onBlur }) => {
  const isMobile = useIsNarrow();
  const len = (value || "").length;
  const remaining = NAME_MAX - len;
  const [hintPhase, setHintPhase] = useState(0);
  const dots = ".".repeat((hintPhase % 3) + 1);

  useEffect(() => {
    if ((value || "").trim()) return undefined;
    const timer = window.setInterval(() => {
      setHintPhase(current => (current + 1) % 3);
    }, 650);
    return () => window.clearInterval(timer);
  }, [value]);

  return (
  <div style={{ minWidth: 0, maxWidth: "100%" }}>
    <div style={{
      position: "relative",
      display: "flex", alignItems: "center", gap: 12,
      background: "#fff", border: "1.5px solid var(--lwc-cream-200)",
      borderRadius: 18,
      padding: isMobile ? "10px 12px 8px" : "10px 14px 6px",
      boxShadow: "0 1px 2px rgba(20,33,61,.04)",
      minWidth: 0,
      maxWidth: "100%",
      overflow: "hidden",
    }}>
      {!(value || "").trim() && (
        <span style={{
          position: "absolute",
          left: isMobile ? 12 : 14,
          right: isMobile ? 54 : 86,
          top: isMobile ? 15 : 14,
          fontFamily: "var(--font-script)",
          fontSize: isMobile ? 30 : 36,
          lineHeight: 1.05,
          color: "rgba(20,33,61,.52)",
          pointerEvents: "none",
          whiteSpace: "nowrap",
          overflow: "hidden",
          textOverflow: "ellipsis",
        }}>
          {placeholder}{dots}
        </span>
      )}
      <input value={value} placeholder=""
        maxLength={NAME_MAX}
        onChange={e => onChange(e.target.value.slice(0, NAME_MAX))}
        onFocus={onFocus}
        onBlur={onBlur}
        style={{
          position: "relative", zIndex: 1,
          flex: 1, border: "none", outline: "none", background: "transparent",
          fontFamily: "var(--font-script)", fontSize: isMobile ? 30 : 36, lineHeight: 1.05,
          color: "var(--brand-primary)", padding: "4px 0 0", minWidth: 0,
        }}/>
      <span style={{
        fontFamily: "var(--font-sans)", fontSize: 11, fontWeight: 700,
        color: remaining <= 3 ? "var(--lwc-gold-700)" : "var(--fg-muted)",
        letterSpacing: "0.04em",
        flex: "0 0 auto",
      }}>{len}/{NAME_MAX}</span>
      {!isMobile && <Sparkle size={18} color="var(--brand-accent)" />}
    </div>
  </div>
);
};

// ------- Category chips -------

const CategoryChips = ({ themes, active, onPick }) => (
  <div style={{ display: "flex", flexWrap: "wrap", gap: 10 }}>
    {themes.map(t => {
      const isActive = active === t.id;
      return (
        <button key={t.id} onClick={() => onPick && onPick(t.id)}
          style={{
            display: "inline-flex", alignItems: "center", gap: 8,
            padding: "9px 16px", borderRadius: 999, cursor: "pointer",
            fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13.5,
            border: isActive ? "1.5px solid var(--lwc-navy-700)" : "1px solid var(--border-soft)",
            background: isActive ? "var(--lwc-navy-700)" : "var(--bg-surface)",
            color: isActive ? "var(--lwc-cream-50)" : "var(--lwc-navy-900)",
          }}>
          <span style={{ width: 8, height: 8, borderRadius: 999, background: t.swatch }}/>
          {t.label}
        </button>
      );
    })}
  </div>
);

const CharacterSelector = ({ characters, selectedId, onSelect, name = "Emma", title = "in the fairy glen", nameFont, previewImages, showGenderToggle = true }) => {
  const selected = characters.find(c => c.id === selectedId) || characters[0];
  const gender = selected.gender;
  const visible = characters.filter(c => c.gender === gender);
  const displayName = (name || "Emma").slice(0, NAME_MAX);
  const font = nameFont || {};
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, flexWrap: "wrap" }}>
        <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 13, color: "var(--fg-primary)" }}>
          Choose their cover character
        </span>
        {showGenderToggle && (
          <div style={{
            display: "inline-grid", gridTemplateColumns: "1fr 1fr", gap: 4,
            padding: 4, background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
            borderRadius: 999,
          }}>
            {["girl", "boy"].map(option => {
              const isActive = gender === option;
              const firstForGender = characters.find(c => c.gender === option);
              return (
                <button key={option} onClick={() => firstForGender && onSelect(firstForGender.id)}
                  style={{
                    border: "none", borderRadius: 999, padding: "7px 14px",
                    background: isActive ? "var(--lwc-navy-700)" : "transparent",
                    color: isActive ? "var(--lwc-cream-50)" : "var(--fg-primary)",
                    fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 12,
                    cursor: "pointer", textTransform: "capitalize",
                  }}>
                  {option}
                </button>
              );
            })}
          </div>
        )}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 10 }}>
        {visible.map(character => {
          const isActive = character.id === selectedId;
          const previewImage = previewImages?.[character.id] || character.coverImage;
          return (
            <button key={character.id} onClick={() => onSelect(character.id)}
              style={{
                display: "grid", gridTemplateRows: "auto auto", gap: 7,
                textAlign: "left", borderRadius: 16, padding: 8,
                border: isActive ? "2px solid var(--lwc-gold-500)" : "1px solid var(--border-soft)",
                background: "var(--bg-surface)", cursor: "pointer",
                boxShadow: isActive ? "0 0 0 4px rgba(212,162,76,.16)" : "none",
              }}>
              <span style={{
                display: "grid", alignItems: "start", justifyItems: "center",
                aspectRatio: "1 / 1", borderRadius: 12, overflow: "hidden",
                position: "relative", padding: "12% 9% 0",
                backgroundColor: "var(--lwc-navy-700)",
                backgroundImage: `linear-gradient(180deg, rgba(20,33,61,.36), rgba(20,33,61,.02) 54%), url("${previewImage}")`,
                backgroundSize: "cover", backgroundPosition: "center 70%",
                border: "1px solid var(--border-soft)",
              }}>
                <span style={{
                  display: "grid", gap: 2, justifyItems: "center",
                  width: "100%", color: "var(--lwc-gold-300)",
                  textAlign: "center", textShadow: "0 1px 4px rgba(20,33,61,.55)",
                }}>
                  <span style={{
                    fontSize: 7, lineHeight: 1, letterSpacing: ".18em",
                    textTransform: "uppercase", fontWeight: 800,
                  }}>
                    The adventures of
                  </span>
                  <span style={{
                    fontFamily: font.family || "var(--font-script)",
                    fontWeight: font.weight || 600,
                    fontStyle: font.style || "normal",
                    fontSize: 19,
                    lineHeight: .9, maxWidth: "100%", overflow: "hidden",
                    textOverflow: "ellipsis", whiteSpace: "nowrap",
                  }}>
                    {displayName}
                  </span>
                  <span style={{
                    fontFamily: "var(--font-serif)", fontStyle: "italic",
                    color: "var(--lwc-cream-50)", fontSize: 9, lineHeight: 1.05,
                    maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis",
                    whiteSpace: "nowrap",
                  }}>
                    {title}
                  </span>
                </span>
              </span>
              <span>
                <strong style={{ display: "block", fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--fg-primary)" }}>
                  {character.label}
                </strong>
                <span style={{ display: "block", fontSize: 11, color: "var(--fg-muted)" }}>{character.tone}</span>
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
};

const NameFontSelector = ({ fonts, selectedId, onSelect, name = "Emma" }) => (
  <div style={{ display: "grid", gap: 8 }}>
    <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 13, color: "var(--fg-primary)" }}>
      Choose name style
    </span>
    <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 8 }}>
      {fonts.map(font => {
        const isActive = font.id === selectedId;
        return (
          <button key={font.id} type="button" onClick={() => onSelect(font.id)}
            style={{
              borderRadius: 14, border: isActive ? "2px solid var(--lwc-gold-500)" : "1px solid var(--border-soft)",
              background: "var(--bg-surface)", padding: "10px 8px", cursor: "pointer",
              boxShadow: isActive ? "0 0 0 4px rgba(212,162,76,.14)" : "none",
              display: "grid", gap: 4,
            }}>
            <span style={{
              fontFamily: font.family, fontWeight: font.weight, fontStyle: font.style,
              fontSize: 20, lineHeight: 1.05, color: "var(--lwc-navy-900)",
              whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
            }}>
              {name || "Emma"}
            </span>
            <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 11, color: "var(--fg-muted)" }}>
              {font.label}
            </span>
          </button>
        );
      })}
    </div>
  </div>
);

// ------- Product card -------

const ProductCard = ({ book, name = "Ava", onOpen, character, coverImage, nameFont, audioDemo, audioActive, onAudioToggle }) => {
  const cs = book.comingSoon;
  const resolvedCoverImage = coverImage || book.coverImage || character?.coverImage;
  const hasAudio = Boolean(audioDemo && !cs);
  const supportingLine = cs
    ? "Notify me when it launches"
    : book.category === "adventure"
      ? "Optional narrated audio companion available"
      : "Personalized printed gift book";
  return (
  <article onClick={cs ? undefined : onOpen} style={{
    background: "var(--bg-surface)", borderRadius: 20, overflow: "hidden",
    boxShadow: "var(--shadow-card)", cursor: cs ? "default" : "pointer",
    transition: "transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out)",
    position: "relative",
  }}
    onMouseEnter={e => { if (cs) return; e.currentTarget.style.transform = "translateY(-4px)"; e.currentTarget.style.boxShadow = "var(--shadow-lift)"; }}
    onMouseLeave={e => { if (cs) return; e.currentTarget.style.transform = ""; e.currentTarget.style.boxShadow = "var(--shadow-card)"; }}>
    <div style={{ aspectRatio: "4 / 5", background: book.bg, display: "grid", placeItems: "center", position: "relative", filter: cs ? "saturate(0.6)" : "none" }}>
      {book.badge && (
        <span style={{
          position: "absolute", top: 14, left: 14,
          background: cs ? "var(--lwc-gold-500)" : "var(--lwc-navy-900)",
          color: cs ? "var(--lwc-navy-900)" : "var(--lwc-cream-50)",
          fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 11,
          letterSpacing: "0.1em", textTransform: "uppercase",
          padding: "5px 10px", borderRadius: 999,
        }}>{book.badge}</span>
      )}
      <BookMock
        name={name}
        title={book.coverTitle || book.subtitle.split(".")[0]}
        width="55%"
        label={book.coverLabel}
        coverImage={resolvedCoverImage}
        nameFont={nameFont}
        coverCallout={book.coverCallout}
        ageRange={book.ageRange}
      />
    </div>
    <div style={{ padding: "16px 18px 18px" }}>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 10 }}>
        <h3 style={{ margin: 0, fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 19, color: "var(--fg-primary)" }}>
          {book.title}
        </h3>
        <strong style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 16, color: "var(--fg-primary)" }}>
          ${book.price.toFixed(2)}
        </strong>
      </div>
      <p style={{ margin: "6px 0 10px", fontSize: 13, color: "var(--fg-muted)" }}>
        {book.pages} pages · {book.theme}
      </p>
      <div
        style={{
          display: "flex", alignItems: "center", gap: 6,
          width: "100%", padding: 0, border: "none", background: "transparent",
          color: "var(--lwc-gold-700)", fontSize: 12, fontWeight: 700,
          textAlign: "left",
          fontFamily: "var(--font-sans)",
        }}>
        {book.category === "adventure" && !cs
          ? <HeadphonesIcon size={14} color="var(--lwc-gold-700)"/>
          : <Sparkle size={12} color="var(--lwc-gold-700)"/>}
        <span>{supportingLine}</span>
      </div>
      {audioActive && audioDemo && (
        <div style={{
          marginTop: 12, padding: "12px 13px", borderRadius: 14,
          background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
          color: "var(--fg-secondary)", fontFamily: "var(--font-serif)",
          fontStyle: "italic", fontSize: 13, lineHeight: 1.35,
        }}>
          "{audioDemo.script}"
        </div>
      )}
    </div>
    {cs && (
      <div style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: "linear-gradient(180deg, rgba(251,247,239,0) 40%, rgba(251,247,239,.30) 100%)",
      }}/>
    )}
  </article>
);
};

// ------- Audio explainer -------

const AudioExplainer = () => {
  const isMobile = useIsNarrow();
  return (
  <section style={{
    background: "var(--lwc-cream-100)", borderRadius: 32,
    padding: "clamp(28px, 5vw, 56px)",
    display: "grid",
    gridTemplateColumns: isMobile ? "1fr" : "1.05fr 0.95fr",
    gap: isMobile ? 28 : 40,
    alignItems: "center",
  }}>
    <div>
      <p className="t-eyebrow" style={{ margin: 0 }}>The audio adventure</p>
      <h2 className="t-h1" style={{ margin: "10px 0 14px" }}>
        Listen along while you color.
      </h2>
      <p className="t-lede" style={{ margin: 0, maxWidth: 460 }}>
        Each book pairs with an immersive audio story — a professional narrator, music,
        and sound effects, telling <em>their</em> story scene-by-scene as they color.
      </p>
      <ul style={{ listStyle: "none", padding: 0, margin: "22px 0 26px", display: "grid", gap: 10 }}>
        {[
          "20+ minutes per book, scene-by-scene",
          "Headphone-friendly, screen-free",
          "Quiet time that builds fine motor skills",
        ].map(t => (
          <li key={t} style={{ display: "flex", gap: 10, alignItems: "center", fontSize: 15 }}>
            <Sparkle size={14} color="var(--brand-accent)"/>
            <span>{t}</span>
          </li>
        ))}
      </ul>
      <Button variant="primary" size="lg" icon={<HeadphonesIcon size={16}/>}>Hear a sample</Button>
    </div>
    <div style={{
      aspectRatio: "1 / 1", background: "var(--lwc-sage-100)", borderRadius: 24,
      display: "grid", placeItems: "center", position: "relative", overflow: "hidden",
    }}>
      <div style={{
        position: "absolute", inset: "11%", background: "var(--lwc-cream-50)",
        borderRadius: 20, boxShadow: "var(--shadow-card)",
      }}/>
      <div style={{ position: "relative", zIndex: 1, width: "46%" }}>
        <BookMock name="Amelia" title="in the Fairy Glen" width="100%" />
      </div>
      <div style={{
        position: "absolute", top: "14%", right: "14%", width: 76, height: 76,
        borderRadius: 999, background: "var(--lwc-gold-100)",
        display: "grid", placeItems: "center", color: "var(--lwc-navy-900)",
        boxShadow: "var(--shadow-card)",
      }}>
        <HeadphonesIcon size={34} />
      </div>
      <div style={{
        position: "absolute", left: "12%", bottom: "17%",
        display: "flex", alignItems: "end", gap: 5, height: 42,
      }}>
        {[18, 30, 24, 38, 26, 34].map((height, index) => (
          <span key={index} style={{
            width: 7, height, borderRadius: 999,
            background: index % 2 ? "var(--lwc-gold-500)" : "var(--lwc-navy-700)",
            opacity: 0.88,
          }}/>
        ))}
      </div>
      <div style={{
        position: "absolute", bottom: 22, right: 22,
        background: "var(--bg-surface)", borderRadius: 999, padding: "10px 14px",
        display: "flex", alignItems: "center", gap: 10, boxShadow: "var(--shadow-lift)",
      }}>
        <div style={{ width: 36, height: 36, borderRadius: 999, background: "var(--lwc-gold-500)", display: "grid", placeItems: "center" }}>
          <svg viewBox="0 0 24 24" width="14" height="14" fill="var(--lwc-navy-900)"><path d="M7 5v14l11-7z"/></svg>
        </div>
        <div>
          <div style={{ fontSize: 12, fontWeight: 800, color: "var(--fg-primary)" }}>Amelia in the Fairy Glen</div>
          <div style={{ fontSize: 11, color: "var(--fg-muted)" }}>0:42 / 22:18</div>
        </div>
      </div>
    </div>
  </section>
  );
};

function getDemoForCharacter(demos, character, bookId = "fairy-glen") {
  const bookDemos = demos?.[bookId] || demos?.["fairy-glen"] || demos;
  return bookDemos?.[character?.gender || "girl"] || bookDemos?.girl;
}

const AudioDemo = ({ character, demos }) => {
  const [mode, setMode] = useState(character?.gender || "girl");
  const [playing, setPlaying] = useState(false);
  const [missing, setMissing] = useState(false);
  const audioRef = useRef(null);
  const bookDemos = demos?.["fairy-glen"] || demos;
  const demo = bookDemos[mode] || bookDemos.girl;

  useEffect(() => {
    setMode(character?.gender || "girl");
  }, [character?.gender]);

  useEffect(() => {
    setPlaying(false);
    setMissing(false);
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.currentTime = 0;
    }
  }, [mode]);

  function toggleAudio() {
    const audio = audioRef.current;
    if (!audio) return;
    if (playing) {
      audio.pause();
      setPlaying(false);
      return;
    }
    setMissing(false);
    audio.play()
      .then(() => setPlaying(true))
      .catch(() => {
        setPlaying(false);
        setMissing(true);
        if ("speechSynthesis" in window) {
          window.speechSynthesis.cancel();
          const utterance = new SpeechSynthesisUtterance(demo.script);
          utterance.rate = 0.92;
          utterance.pitch = mode === "girl" ? 1.08 : 0.92;
          window.speechSynthesis.speak(utterance);
        }
      });
  }

  return (
    <section style={{
      background: "var(--lwc-cream-100)", borderRadius: 32,
      padding: "clamp(24px, 4vw, 40px)", display: "grid",
      gridTemplateColumns: "repeat(auto-fit, minmax(min(300px, 100%), 1fr))",
      gap: 28, alignItems: "center", border: "1px solid var(--border-soft)",
    }}>
      <div>
        <p className="t-eyebrow" style={{ margin: 0 }}>Try the audio</p>
        <h2 className="t-h2" style={{ margin: "8px 0 12px" }}>Hear the story come alive.</h2>
        <p className="t-body-lg" style={{ margin: 0, color: "var(--fg-secondary)" }}>
          A narrated scene gives children a gentle prompt while they colour the matching page.
        </p>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 18 }}>
          {["girl", "boy"].map(option => (
            <button key={option} onClick={() => setMode(option)} style={{
              border: mode === option ? "1.5px solid var(--lwc-navy-700)" : "1px solid var(--border-soft)",
              background: mode === option ? "var(--lwc-navy-700)" : "var(--bg-surface)",
              color: mode === option ? "var(--lwc-cream-50)" : "var(--fg-primary)",
              borderRadius: 999, padding: "8px 14px", cursor: "pointer",
              fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 12,
            }}>
              {bookDemos[option].label}
            </button>
          ))}
        </div>
      </div>
      <div style={{
        background: "var(--bg-surface)", borderRadius: 20, padding: 20,
        boxShadow: "var(--shadow-card)", display: "grid", gap: 14,
      }}>
        <audio ref={audioRef} src={demo.src} onEnded={() => setPlaying(false)} onError={() => setMissing(true)} />
        <button onClick={toggleAudio} style={{
          display: "flex", alignItems: "center", justifyContent: "center", gap: 10,
          width: "100%", border: "none", borderRadius: 999,
          padding: "14px 20px", background: "var(--lwc-gold-500)",
          color: "var(--lwc-navy-900)", fontFamily: "var(--font-sans)",
          fontWeight: 900, fontSize: 15, cursor: "pointer",
          boxShadow: "0 8px 20px rgba(184,134,46,.22)",
        }}>
          <span style={{
            width: 34, height: 34, borderRadius: 999, background: "rgba(20,33,61,.10)",
            display: "grid", placeItems: "center",
          }}>{playing ? "Ⅱ" : "▶"}</span>
          {playing ? "Pause sample" : "Play audio sample"}
        </button>
        <p style={{
          margin: 0, fontFamily: "var(--font-serif)", fontStyle: "italic",
          fontSize: 17, lineHeight: 1.45, color: "var(--fg-secondary)",
        }}>
          "{demo.script}"
        </p>
        {missing && (
          <p className="t-caption" style={{ margin: 0, color: "var(--lwc-gold-700)" }}>
            MP3 file is not available yet, so this browser is using a local speech preview.
          </p>
        )}
      </div>
    </section>
  );
};

// ------- Trust bar -------

const TrustBar = ({ items }) => {
  const isMobile = useIsNarrow();
  return (
  <section style={{
    display: "grid",
    gridTemplateColumns: isMobile ? "repeat(2, minmax(0, 1fr))" : `repeat(${items.length}, 1fr)`,
    gap: isMobile ? 16 : 24,
    padding: "20px 0", borderTop: "1px solid var(--border-soft)", borderBottom: "1px solid var(--border-soft)",
  }}>
    {items.map(it => (
      <div key={it.label} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        <span style={{ display: "flex", gap: 6, alignItems: "center", fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 14 }}>
          <Sparkle size={12} color="var(--brand-accent)"/>{it.label}
        </span>
        <span style={{ fontSize: 12.5, color: "var(--fg-muted)" }}>{it.sub}</span>
      </div>
    ))}
  </section>
  );
};

// ------- Testimonial -------

const TestimonialBlock = ({ reviews }) => {
  const isMobile = useIsNarrow();
  return (
  <section>
    <p className="t-eyebrow" style={{ margin: "0 0 8px" }}>Loved by parents</p>
    <h2 className="t-h2" style={{ margin: "0 0 28px", maxWidth: 600 }}>
      The first thing they reach for after breakfast.
    </h2>
    <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(3, 1fr)", gap: 18 }}>
      {reviews.map((r, i) => (
        <figure key={i} style={{
          margin: 0, background: "var(--bg-surface)", borderRadius: 20, padding: 22,
          border: "1px solid var(--border-soft)",
          display: "flex", flexDirection: "column", gap: 14,
        }}>
          <div style={{ display: "flex", gap: 2, color: "var(--brand-accent)" }}>
            {Array.from({ length: r.rating }).map((_, n) => <StarSolid key={n} size={14} color="var(--lwc-gold-500)"/>)}
          </div>
          <blockquote className="t-quote" style={{ margin: 0, fontSize: 17 }}>"{r.body}"</blockquote>
          <figcaption style={{ fontSize: 13, color: "var(--fg-muted)", fontWeight: 600 }}>— {r.name}</figcaption>
        </figure>
      ))}
    </div>
  </section>
  );
};

// ------- Footer -------

const Footer = ({ onNavigate }) => {
  const isMobile = useIsNarrow();
  const go = (page) => (event) => {
    event.preventDefault();
    if (onNavigate) onNavigate(page);
  };
  return (
  <footer style={{
    background: "var(--lwc-navy-900)", color: "var(--lwc-cream-50)",
    padding: "56px var(--container-pad) 36px", marginTop: 64,
  }}>
    <div style={{
      maxWidth: "var(--container-max)",
      margin: "0 auto",
      display: "grid",
      gridTemplateColumns: isMobile ? "1fr 1fr" : "repeat(auto-fit, minmax(180px, 1fr))",
      gap: isMobile ? "28px 20px" : 32,
    }}>
      <div>
        <Logo color="var(--lwc-cream-50)" accent="var(--lwc-gold-500)" height={26} />
        <p style={{ margin: "16px 0 0", fontSize: 14, color: "rgba(251,247,239,.70)", maxWidth: 320, lineHeight: 1.6 }}>
          Premium personalized coloring books with immersive audio adventures.
          Screen-free magic, starring your child.
        </p>
      </div>
        {[
        { h: "Shop",     links: [{ label: "All books", page: "home" }, { label: "Personalized adventures", page: "home" }, { label: "Gift books", page: "home" }] },
        { h: "About",    links: [{ label: "The audio", page: "home" }, { label: "Reviews", page: "home" }, { label: "FAQ", page: "faq" }] },
        { h: "Resources", links: [{ label: "Blog", href: EXTERNAL_ROUTES.blog }, { label: "Free coloring pages", href: EXTERNAL_ROUTES.freeColoringPages }] },
        { h: "Support",  links: [{ label: "Shipping", page: "delivery" }, { label: "Returns", page: "returns" }, { label: "Contact", page: "contact" }] },
      ].map(col => (
        <div key={col.h}>
          <h4 style={{ margin: "0 0 14px", fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 13, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--lwc-gold-300)" }}>{col.h}</h4>
          <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }}>
            {col.links.map(l => (
              <li key={l.label} style={{ fontSize: 14 }}>
                {l.href ? (
                  <a href={l.href} target="_top" style={{ color: "rgba(251,247,239,.85)", textDecoration: "none" }}>
                    {l.label}
                  </a>
                ) : (
                  <a href={`#/${l.page}`} onClick={go(l.page)} style={{ color: "rgba(251,247,239,.85)", textDecoration: "none", cursor: "pointer" }}>
                    {l.label}
                  </a>
                )}
              </li>
            ))}
          </ul>
        </div>
      ))}
    </div>
    <div style={{ maxWidth: "var(--container-max)", margin: "32px auto 0", paddingTop: 22, borderTop: "1px solid rgba(251,247,239,.12)", display: "flex", justifyContent: "space-between", gap: 16, flexWrap: "wrap", fontSize: 12, color: "rgba(251,247,239,.55)", gridColumn: isMobile ? "1 / -1" : undefined }}>
      <span>© Learn with Coloring · Made with care in the USA.</span>
      <span>
        <a href="#/privacy" onClick={go("privacy")} style={{ color: "inherit", textDecoration: "none" }}>Privacy</a>
        {" · "}
        <a href="#/terms" onClick={go("terms")} style={{ color: "inherit", textDecoration: "none" }}>Terms</a>
        {" · "}
        <a href="#/cookies" onClick={go("cookies")} style={{ color: "inherit", textDecoration: "none" }}>Cookies</a>
      </span>
    </div>
  </footer>
);
};

// ------- Audio upsell (used on product page) -------

const AudioUpsell = ({ checked, onToggle, price = 6 }) => (
  <label style={{
    display: "flex", gap: 14, alignItems: "center", cursor: "pointer",
    padding: "16px 18px", borderRadius: 16,
    background: checked ? "var(--lwc-gold-100)" : "var(--lwc-cream-100)",
    border: `1.5px solid ${checked ? "var(--lwc-gold-500)" : "var(--border-soft)"}`,
    transition: "all var(--dur-base) var(--ease-out)",
  }}>
    <div style={{
      width: 44, height: 44, borderRadius: 999, flex: "0 0 auto",
      background: checked ? "var(--lwc-gold-500)" : "var(--lwc-cream-200)",
      color: "var(--lwc-navy-900)", display: "grid", placeItems: "center",
    }}>
      <HeadphonesIcon size={22} />
    </div>
    <div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0, flex: 1 }}>
      <span style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 17, color: "var(--lwc-navy-900)" }}>
        Add the audio adventure
      </span>
      <span style={{ fontSize: 13, color: "#7A5A19" }}>
        A professional narrator tells their story — with music & sound effects.
      </span>
    </div>
    <span style={{ fontWeight: 800, color: "var(--lwc-navy-900)", marginRight: 6 }}>+ ${price.toFixed(2)}</span>
    <input type="checkbox" checked={checked} onChange={e => onToggle(e.target.checked)}
      style={{ width: 20, height: 20, accentColor: "var(--lwc-gold-500)" }}/>
  </label>
);

const AudioUpsellPlayer = ({ checked, onToggle, price = 6, demo }) => {
  const [playing, setPlaying] = useState(false);
  const audioRef = useRef(null);
  const isMobile = useIsNarrow();

  function togglePlay(event) {
    event.preventDefault();
    event.stopPropagation();
    const audio = audioRef.current;
    if (!audio) return;
    if (playing) {
      audio.pause();
      setPlaying(false);
      return;
    }
    audio.play()
      .then(() => setPlaying(true))
      .catch(() => setPlaying(false));
  }

  return (
    <div style={{
      padding: "16px 18px", borderRadius: 16,
      background: checked ? "var(--lwc-gold-100)" : "var(--lwc-cream-100)",
      border: `1.5px solid ${checked ? "var(--lwc-gold-500)" : "var(--border-soft)"}`,
      display: "grid", gap: 12,
    }}>
      <audio ref={audioRef} src={demo?.src} onEnded={() => setPlaying(false)} />
      <label style={{ display: "flex", gap: 14, alignItems: isMobile ? "flex-start" : "center", cursor: "pointer", flexWrap: isMobile ? "wrap" : "nowrap" }}>
        <div style={{
          width: 44, height: 44, borderRadius: 999, flex: "0 0 auto",
          background: checked ? "var(--lwc-gold-500)" : "var(--lwc-cream-200)",
          color: "var(--lwc-navy-900)", display: "grid", placeItems: "center",
        }}>
          <HeadphonesIcon size={22} />
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0, flex: 1 }}>
          <span style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 17, color: "var(--lwc-navy-900)" }}>
            Add the audio adventure
          </span>
          <span style={{ fontSize: 13, color: "#7A5A19" }}>
            A professional narrator tells their story — with music & sound effects.
          </span>
        </div>
        <span style={{ fontWeight: 800, color: "var(--lwc-navy-900)", marginRight: 6, marginLeft: isMobile ? 58 : undefined }}>+ ${price.toFixed(2)}</span>
        <input type="checkbox" checked={checked} onChange={e => onToggle(e.target.checked)}
          style={{ width: 20, height: 20, accentColor: "var(--lwc-gold-500)" }}/>
      </label>
      {demo && (
        <div style={{
          display: "grid", gap: 10, padding: 14,
          borderRadius: 14, background: "rgba(255,255,255,.58)",
          border: "1px solid rgba(212,162,76,.32)",
        }}>
          <button type="button" onClick={togglePlay} style={{
            display: "inline-flex", justifyContent: "center", alignItems: "center", gap: 8,
            border: "none", borderRadius: 999, padding: "10px 16px",
            background: "var(--lwc-navy-700)", color: "var(--lwc-cream-50)",
            fontFamily: "var(--font-sans)", fontWeight: 900, cursor: "pointer",
          }}>
            <span>{playing ? "Ⅱ" : "▶"}</span>
            {playing ? "Pause sample" : "Play sample"}
          </button>
          <p style={{
            margin: 0, fontFamily: "var(--font-serif)", fontStyle: "italic",
            fontSize: 15, lineHeight: 1.42, color: "var(--fg-secondary)",
          }}>
            "{demo.script}"
          </p>
        </div>
      )}
    </div>
  );
};

// expose
Object.assign(window, {
  Sparkle, StarSolid, HeadphonesIcon, CartIcon, ChevronRight, Close,
  Logo, Button, AnnouncementBar, Header, BookMock, NameInput, useIsNarrow,
  CategoryChips, CharacterSelector, NameFontSelector, ProductCard, AudioExplainer, AudioDemo, TrustBar,
  TestimonialBlock, Footer, AudioUpsell, AudioUpsellPlayer, getDemoForCharacter,
});
