/* global React, LWC_BOOKS, LWC_REVIEWS, LWC_TRUST, LWC_CHARACTERS, LWC_COVER_IMAGES, LWC_AUDIO_DEMOS, LWC_NAME_FONTS */
const { useEffect, useState } = React;

function getCoverImage(book, character) {
  return LWC_COVER_IMAGES?.[book.id]?.[character.id] || book.coverImage || character.coverImage;
}

function getCharactersForGender(gender) {
  return LWC_CHARACTERS.filter(character => character.gender === gender);
}

function getFirstCharacterForGender(gender) {
  return getCharactersForGender(gender)[0] || LWC_CHARACTERS[0];
}

function normalizeChildName(value) {
  const cleaned = value.replace(/\s+/g, " ").replace(/^\s+/, "");
  if (!cleaned) return "";
  return cleaned.replace(/(^|[\s-])([a-z])/g, (_, prefix, letter) => `${prefix}${letter.toUpperCase()}`);
}

function shuffleArray(items) {
  const copy = [...items];
  for (let i = copy.length - 1; i > 0; i -= 1) {
    const swapIndex = Math.floor(Math.random() * (i + 1));
    [copy[i], copy[swapIndex]] = [copy[swapIndex], copy[i]];
  }
  return copy;
}

const POPULAR_US_GIRL_NAMES = [
  "Olivia",
  "Charlotte",
  "Emma",
  "Amelia",
  "Sophia",
  "Mia",
  "Isabella",
  "Evelyn",
  "Sofia",
  "Eliana",
];

const POPULAR_US_BOY_NAMES = [
  "Liam",
  "Noah",
  "Oliver",
  "Theodore",
  "Henry",
  "James",
  "Elijah",
  "Mateo",
  "William",
  "Lucas",
];

function SampleBookScroller({ book, name, character, nameFont, compact = false }) {
  const dragRef = React.useRef({ active: false, startX: 0, deltaX: 0 });
  const [pageIndex, setPageIndex] = useState(0);
  const isCompact = compact || useIsNarrow();
  const samples = book.samplePages || [];
  const pageCount = samples.length + 1;
  const coverImage = getCoverImage(book, character);
  const previewItems = [
    { id: "cover", kind: "cover", title: "Cover", thumb: coverImage },
    ...samples.map((sample, index) => ({
      id: sample.src,
      kind: "page",
      title: sample.title,
      thumb: sample.src,
      pageNumber: index + 1,
      src: sample.src,
    })),
  ];

  useEffect(() => {
    setPageIndex(0);
    dragRef.current = { active: false, startX: 0, deltaX: 0 };
  }, [book.id, character.id]);

  function onPointerDown(event) {
    dragRef.current = { active: true, startX: event.clientX, deltaX: 0 };
    event.currentTarget.setPointerCapture?.(event.pointerId);
  }

  function onPointerMove(event) {
    if (!dragRef.current.active) return;
    event.preventDefault();
    dragRef.current.deltaX = event.clientX - dragRef.current.startX;
  }

  function endDrag(event) {
    const threshold = isCompact ? 36 : 52;
    const deltaX = dragRef.current.deltaX;
    dragRef.current.active = false;
    event.currentTarget?.releasePointerCapture?.(event.pointerId);
    if (Math.abs(deltaX) < threshold) return;
    setPageIndex(current => Math.max(0, Math.min(pageCount - 1, current + (deltaX < 0 ? 1 : -1))));
  }

  function goToPage(next) {
    setPageIndex(Math.max(0, Math.min(pageCount - 1, next)));
  }

  const arrowButton = {
    position: "absolute", top: "50%", transform: "translateY(-50%)",
    width: 46, height: 46, borderRadius: 999, border: "1px solid var(--border-soft)",
    background: "rgba(255, 252, 246, .94)", color: "var(--fg-primary)",
    boxShadow: "var(--shadow-card)", display: "grid", placeItems: "center",
    cursor: "pointer", zIndex: 3,
  };

  const activeItem = previewItems[pageIndex];
  const railMinWidth = isCompact ? "100%" : 96;
  const pageHeight = isCompact ? "min(42vh, 360px)" : "min(58vh, 560px)";

  function renderMockup(item) {
    if (item.kind === "cover") {
      return (
        <div style={{ width: isCompact ? "min(68vw, 300px)" : "min(74%, 360px)" }}>
          <BookMock
            name={name || "Emma"}
            title={book.coverTitle || book.subtitle.split(".")[0]}
            width="100%"
            label={book.coverLabel}
            coverCallout={book.coverCallout}
            ageRange={book.ageRange}
            coverImage={coverImage}
            nameFont={nameFont}
          />
        </div>
      );
    }

    return (
      <div style={{
        position: "relative",
        width: isCompact ? "min(72vw, 320px)" : "min(76%, 390px)",
        display: "grid",
        placeItems: "center",
      }}>
        <div style={{
          position: "absolute",
          inset: isCompact ? "7% 4% 0 12%" : "7% 3% 0 12%",
          borderRadius: "10px 18px 18px 10px",
          background: "linear-gradient(180deg, rgba(255,255,255,.96), rgba(238,231,219,.96))",
          border: "1px solid rgba(20,33,61,.06)",
          boxShadow: "10px 0 24px rgba(20,33,61,.10)",
        }}/>
        <div style={{
          position: "relative",
          width: "100%",
          background: "linear-gradient(180deg, rgba(255,255,255,.98), rgba(248,243,234,.98))",
          borderRadius: "10px 18px 18px 10px",
          border: "1px solid rgba(20,33,61,.08)",
          boxShadow: "0 16px 36px rgba(20,33,61,.14)",
          padding: isCompact ? 16 : 18,
          display: "grid",
          placeItems: "center",
          overflow: "hidden",
        }}>
          <div style={{
            position: "absolute",
            left: 10,
            top: 12,
            bottom: 12,
            width: isCompact ? 12 : 16,
            background: "linear-gradient(90deg, rgba(20,33,61,.18), rgba(20,33,61,.04) 58%, rgba(20,33,61,0))",
            borderRadius: 999,
          }}/>
          <img
            src={item.src}
            alt={`${book.title} sample page ${item.pageNumber}: ${item.title}`}
            draggable="false"
            style={{
              display: "block",
              width: "100%",
              maxWidth: isCompact ? 320 : 390,
              height: "auto",
              maxHeight: pageHeight,
              aspectRatio: "2 / 3",
              objectFit: "cover",
              background: "var(--bg-surface)",
              borderRadius: 16,
              border: "1px solid var(--border-soft)",
              boxShadow: "-6px 6px 0 rgba(20,33,61,.08), var(--shadow-lift)",
            }}
          />
        </div>
      </div>
    );
  }

  return (
    <div style={{
      width: "100%",
      display: "grid",
      gridTemplateColumns: isCompact ? "1fr" : `${railMinWidth}px minmax(0, 1fr)`,
      gap: isCompact ? 16 : 18,
      alignItems: "stretch",
    }}>
      <div style={{
        display: "grid",
        gridAutoFlow: isCompact ? "column" : "row",
        gridAutoColumns: isCompact ? "minmax(74px, 92px)" : "unset",
        alignContent: "start",
        gap: 12,
        overflowX: isCompact ? "auto" : "visible",
        overflowY: "visible",
        paddingRight: isCompact ? 0 : 4,
      }}>
        {previewItems.map((item, index) => {
          const active = index === pageIndex;
          return (
            <button
              key={item.id}
              type="button"
              onClick={() => goToPage(index)}
              aria-label={`Show ${item.title}`}
              style={{
                border: active ? "2px solid var(--brand-primary)" : "1px solid var(--border-soft)",
                background: "var(--bg-surface)",
                borderRadius: 18,
                padding: 6,
                cursor: "pointer",
                boxShadow: active ? "var(--shadow-lift)" : "var(--shadow-card)",
                transform: active ? "translateX(2px)" : "none",
                transition: "transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out)",
                textAlign: "left",
                minWidth: isCompact ? 82 : "auto",
              }}>
              <div style={{
                aspectRatio: "3 / 4",
                borderRadius: 12,
                overflow: "hidden",
                position: "relative",
                background: item.kind === "cover"
                  ? `linear-gradient(180deg, rgba(20,33,61,.12), rgba(20,33,61,.02)), url("${item.thumb}") center / cover no-repeat`
                  : `url("${item.thumb}") center / cover no-repeat`,
                border: "1px solid rgba(20,33,61,.08)",
              }}>
                <div style={{
                  position: "absolute",
                  inset: "auto 6px 6px",
                  background: "rgba(255,253,247,.92)",
                  borderRadius: 999,
                  padding: "4px 8px",
                  fontFamily: "var(--font-sans)",
                  fontWeight: 800,
                  fontSize: 10.5,
                  color: "var(--fg-primary)",
                }}>
                  {item.kind === "cover" ? "Cover" : `${item.pageNumber}`}
                </div>
              </div>
            </button>
          );
        })}
      </div>

      <div style={{
        minHeight: isCompact ? 360 : 540,
        height: isCompact ? "min(52vh, 480px)" : "min(78vh, 760px)",
        background: `linear-gradient(180deg, color-mix(in srgb, ${book.bg} 90%, white), ${book.bg})`,
        borderRadius: 28,
        position: "relative",
        overflow: "hidden",
        display: "grid",
        alignItems: "center",
        border: "1px solid rgba(20,33,61,.05)",
      }}>
        <div style={{
          position: "absolute",
          inset: "8% 7% auto auto",
          width: isCompact ? 120 : 180,
          height: isCompact ? 120 : 180,
          borderRadius: 999,
          background: "rgba(255,255,255,.28)",
          filter: "blur(2px)",
        }}/>
        <div style={{
          position: "absolute",
          inset: "auto auto 6% 7%",
          width: isCompact ? 160 : 260,
          height: isCompact ? 50 : 76,
          borderRadius: 999,
          background: "rgba(20,33,61,.10)",
          filter: "blur(24px)",
        }}/>

        <div
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={endDrag}
          onPointerCancel={endDrag}
          style={{
            height: "100%",
            cursor: dragRef.current.active ? "grabbing" : "grab",
            touchAction: "pan-x",
            userSelect: "none",
            display: "grid",
            placeItems: "center",
          }}>
          <div style={{
            width: "100%",
            height: "100%",
            display: "grid",
            placeItems: "center",
            padding: isCompact ? "20px 18px 74px" : "28px 28px 86px",
          }}>
            {renderMockup(activeItem)}
          </div>
        </div>

        <div style={{
          position: "absolute", left: 18, bottom: 18,
          background: "var(--bg-surface)", padding: "8px 12px", borderRadius: 999,
          fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 12,
          color: "var(--fg-primary)", boxShadow: "var(--shadow-card)",
          pointerEvents: "none",
        }}>
          {pageIndex === 0 ? "Swipe through cover + sample pages" : `Sample ${pageIndex} of ${samples.length} · ${activeItem.title}`}
        </div>
        <button
          type="button"
          aria-label="Previous preview page"
          onClick={() => goToPage(pageIndex - 1)}
          disabled={pageIndex === 0}
          style={{
            ...arrowButton, left: 16, opacity: pageIndex === 0 ? .35 : 1,
            pointerEvents: pageIndex === 0 ? "none" : "auto",
          }}>
          <span style={{ transform: "rotate(180deg)", display: "grid" }}><ChevronRight size={22}/></span>
        </button>
        <button
          type="button"
          aria-label="Next preview page"
          onClick={() => goToPage(pageIndex + 1)}
          disabled={pageIndex >= pageCount - 1}
          style={{
            ...arrowButton, right: 16, opacity: pageIndex >= pageCount - 1 ? .35 : 1,
            pointerEvents: pageIndex >= pageCount - 1 ? "none" : "auto",
          }}>
          <ChevronRight size={22}/>
        </button>
        <div style={{
          position: "absolute", right: 18, bottom: 18, display: "flex", gap: 6,
          background: "rgba(255, 252, 246, .92)", padding: "8px 10px", borderRadius: 999,
          boxShadow: "var(--shadow-card)", pointerEvents: "none",
        }}>
          {Array.from({ length: pageCount }).map((_, index) => (
            <span key={index} style={{
              width: index === pageIndex ? 18 : 7, height: 7, borderRadius: 999,
              background: index === pageIndex ? "var(--lwc-navy-700)" : "rgba(20,33,61,.24)",
              transition: "width var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out)",
            }}/>
          ))}
        </div>
      </div>
    </div>
  );
}

const INFO_PAGES = {
  returns: {
    eyebrow: "Returns policy",
    title: "Fair, plain-English support if something is not right.",
    intro: "Every book is printed especially for your child, so our returns policy is designed around made-to-order keepsakes while still protecting you if anything arrives damaged, faulty, or incorrect.",
    sections: [
      {
        title: "Personalized books and change of mind",
        body: [
          "Because each book is made with your child's name on the cover and throughout the story, we cannot accept returns for change of mind once production has started.",
          "Please double-check the spelling, book title, and delivery details before ordering. If you spot an issue quickly, email us and we will help if production has not begun.",
        ],
      },
      {
        title: "Damaged or defective books",
        body: [
          "If your book arrives damaged, misprinted, or with a production defect, we will make it right with a replacement or refund.",
          "Email hello@learnwithcoloring.com within 7 days of delivery with your order reference, a clear photo of the issue, and your preferred resolution.",
        ],
      },
      {
        title: "Wrong item received",
        body: [
          "If we send the wrong title, wrong name, or wrong order, that is on us. We will send the correct book at no extra cost, or refund you if you prefer.",
        ],
      },
      {
        title: "Waitlist and pre-launch",
        body: [
          "Joining the waitlist does not take payment. Refunds only apply once paid orders are open and an order has been placed.",
        ],
      },
    ],
  },
  delivery: {
    eyebrow: "Shipping",
    title: "Made to order, packed carefully, sent with tracking.",
    intro: "The current launch plan is USA printing and delivery first, with wider shipping options to follow. Timings below apply once paid orders open.",
    sections: [
      {
        title: "Standard delivery",
        body: [
          "Books are printed to order. Please allow 1-2 business days for production before dispatch.",
          "Standard delivery is expected to take 3-5 business days after dispatch. We will email tracking details when your book leaves the printer.",
        ],
      },
      {
        title: "Packaging",
        body: [
          "Each book is sent in protective packaging so it arrives flat, clean, and ready to give or colour straight away.",
        ],
      },
      {
        title: "International shipping",
        body: [
          "International delivery is planned after launch. If you are outside the USA, join the waitlist and we will let you know when your location opens.",
        ],
      },
    ],
  },
  privacy: {
    eyebrow: "Privacy policy",
    title: "We collect only what we need to make and deliver your book.",
    intro: "Last updated: May 2026. This summary keeps the earlier policy content and aligns it with the new waitlist-first experience.",
    sections: [
      {
        title: "What we collect",
        body: [
          "We may collect a parent or guardian's name, email address, book preferences, child name for personalization, order details, and delivery address when orders open.",
          "We do not knowingly collect data directly from children. The service is for parents, guardians, and gift-givers.",
        ],
      },
      {
        title: "How we use it",
        body: [
          "We use your information to manage waitlist signups, produce personalized books, send order and shipping updates, provide support, and improve the website.",
          "Marketing emails are optional. You can unsubscribe or ask us to remove waitlist data by emailing hello@learnwithcoloring.com.",
        ],
      },
      {
        title: "Sharing and storage",
        body: [
          "We do not sell personal data. We only share what is needed with trusted providers such as print partners, shipping partners, payment processors, and email services.",
          "Waitlist data is kept only as long as needed for launch communications, unless you ask us to remove it sooner.",
        ],
      },
      {
        title: "Your choices",
        body: [
          "You can ask to access, correct, or delete your personal data by contacting hello@learnwithcoloring.com.",
        ],
      },
    ],
  },
  terms: {
    eyebrow: "Terms of use",
    title: "The practical terms for using Learn with Coloring.",
    intro: "Last updated: May 2026. By using the site or joining the waitlist, you agree to use the service lawfully and provide accurate information.",
    sections: [
      {
        title: "Pre-launch waitlist",
        body: [
          "We are currently operating a waitlist. Joining expresses interest only. No payment is taken and no contract of sale is formed until paid orders open and we confirm an order.",
        ],
      },
      {
        title: "Personalization",
        body: [
          "You are responsible for checking that personalization details are accurate and appropriate. We produce made-to-order books using the information you provide.",
          "If we make an error in production, we will investigate and arrange a fair replacement or refund.",
        ],
      },
      {
        title: "Pricing and availability",
        body: [
          "The design-system launch price is $19.99 per book, with the optional audio adventure shown at +$9.99. Final availability and checkout terms will be confirmed at launch.",
        ],
      },
      {
        title: "Intellectual property",
        body: [
          "The Learn with Coloring brand, copy, artwork, layouts, and product concepts belong to Learn with Coloring. Purchasing a book grants personal use of the physical product, not reuse of the underlying artwork.",
        ],
      },
    ],
  },
  cookies: {
    eyebrow: "Cookie policy",
    title: "Small files, used only where they help the site work better.",
    intro: "Last updated: May 2026. This page explains the cookie approach for the storefront prototype and launch site.",
    sections: [
      {
        title: "Essential cookies",
        body: [
          "Essential cookies may be used to keep forms, waitlist state, checkout, and security features working properly.",
        ],
      },
      {
        title: "Analytics cookies",
        body: [
          "If analytics are enabled, they help us understand which pages people visit and where the site needs improvement. Analytics should be used in aggregate, not to sell personal profiles.",
        ],
      },
      {
        title: "Managing cookies",
        body: [
          "You can block or delete cookies in your browser settings. Some essential features may not work correctly if all cookies are disabled.",
        ],
      },
    ],
  },
  faq: {
    eyebrow: "FAQ",
    title: "Answers for parents before launch.",
    intro: "A quick, updated version of the earlier FAQ, focused on the new personalized book plus audio adventure offer.",
    sections: [
      {
        title: "What is Learn with Coloring?",
        body: [
          "It is a premium personalized coloring book with your child's name woven into the cover and story, paired with an optional immersive audio adventure.",
        ],
      },
      {
        title: "What ages is it for?",
        body: [
          "The books are designed for children aged roughly 3-8, with bold scenes, calm storytelling, and enough detail to make colouring feel satisfying.",
        ],
      },
      {
        title: "How does the audio work?",
        body: [
          "The audio adventure is a narrated, scene-by-scene companion with music and sound effects, so your child can listen while colouring without needing a screen.",
        ],
      },
      {
        title: "Can I order now?",
        body: [
          "The app is currently collecting waitlist interest. You can choose books, add the audio option, and save your email to be notified when ordering opens.",
        ],
      },
      {
        title: "Can personalized books be returned?",
        body: [
          "Personalized books cannot usually be returned for change of mind, but damaged, defective, or incorrect orders will be fixed with a replacement or refund.",
        ],
      },
    ],
  },
  contact: {
    eyebrow: "Contact",
    title: "Need a hand before you join the waitlist?",
    intro: "We are a small team and we genuinely want the book to feel right before you order.",
    sections: [
      {
        title: "Email",
        body: [
          "Write to hello@learnwithcoloring.com with questions about personalization, shipping, gifting, returns, or launch timing.",
        ],
      },
      {
        title: "Response time",
        body: [
          "We aim to reply within one business day during launch preparation.",
        ],
      },
    ],
  },
};

function InfoScreen({ page = "faq", onNavigate }) {
  const info = INFO_PAGES[page] || INFO_PAGES.faq;
  return (
    <main>
      <section style={{
        maxWidth: "var(--container-max)", margin: "0 auto",
        padding: "64px var(--container-pad) 36px",
      }}>
        <button onClick={() => onNavigate("home")} style={{
          background: "transparent", border: "none", cursor: "pointer",
          fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13,
          color: "var(--fg-muted)", padding: 0, marginBottom: 26,
        }}>← Back to the shop</button>
        <p className="t-eyebrow" style={{ margin: 0 }}>{info.eyebrow}</p>
        <h1 className="t-display" style={{ margin: "12px 0 18px", maxWidth: 860 }}>{info.title}</h1>
        <p className="t-lede" style={{ margin: 0, maxWidth: 760 }}>{info.intro}</p>
      </section>
      <section style={{
        maxWidth: 900, margin: "0 auto",
        padding: "12px var(--container-pad) 40px",
        display: "grid", gap: 18,
      }}>
        {info.sections.map((section) => (
          <article key={section.title} style={{
            background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
            borderRadius: 20, padding: "clamp(22px, 4vw, 34px)",
            boxShadow: "var(--shadow-card)",
          }}>
            <h2 className="t-h3" style={{ margin: "0 0 12px" }}>{section.title}</h2>
            {section.body.map((para, index) => (
              <p key={index} className="t-body-lg" style={{
                margin: index === 0 ? 0 : "12px 0 0",
                color: "var(--fg-secondary)",
              }}>{para}</p>
            ))}
          </article>
        ))}
        <div style={{
          marginTop: 10, background: "var(--lwc-cream-100)",
          border: "1px solid var(--border-soft)", borderRadius: 20,
          padding: "24px 28px", display: "flex", alignItems: "center",
          justifyContent: "space-between", gap: 18, flexWrap: "wrap",
        }}>
          <div>
            <h2 className="t-h4" style={{ margin: "0 0 4px" }}>Still need help?</h2>
            <p className="t-caption" style={{ margin: 0 }}>Email hello@learnwithcoloring.com and we will point you in the right direction.</p>
          </div>
          <Button variant="primary" size="md" onClick={() => window.location.href = "mailto:hello@learnwithcoloring.com"}>Email us</Button>
        </div>
      </section>
      <Footer onNavigate={onNavigate} />
    </main>
  );
}

// ---- Home ----
function BrandSchemeToggle({ scheme, onChange }) {
  const isMobile = useIsNarrow();
  const options = [
    { id: "storybook", label: "Storybook navy" },
    { id: "forest", label: "Forest green" },
  ];
  return (
    <div style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
      borderRadius: 999, padding: 6, boxShadow: "var(--shadow-card)",
      maxWidth: "100%",
    }}>
      {options.map(option => {
        const active = scheme === option.id;
        return (
          <button
            key={option.id}
            type="button"
            onClick={() => onChange(option.id)}
            style={{
              border: "none", borderRadius: 999, cursor: "pointer",
              padding: isMobile ? "9px 11px" : "9px 13px", fontFamily: "var(--font-sans)",
              fontWeight: 900, fontSize: 12,
              background: active ? "var(--brand-primary)" : "transparent",
              color: active ? "var(--fg-on-brand)" : "var(--fg-primary)",
              boxShadow: active ? "0 4px 12px rgba(20,33,61,.12)" : "none",
              transition: "background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out)",
            }}>
            {option.label}
          </button>
        );
      })}
    </div>
  );
}

function GenderQuickPick({ character, setCharacterId }) {
  const isMobile = useIsNarrow();
  return (
    <div style={{ display: "grid", gap: 8 }}>
      <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 13, color: "var(--fg-primary)" }}>
        Start with
      </span>
      <div style={{
        display: "inline-flex", width: isMobile ? "100%" : "fit-content", gap: 6,
        background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
        borderRadius: 999, padding: 6,
      }}>
        {["girl", "boy"].map(option => {
          const active = character.gender === option;
          const firstForGender = LWC_CHARACTERS.find(c => c.gender === option);
          return (
            <button
              key={option}
              type="button"
              onClick={() => firstForGender && setCharacterId(firstForGender.id)}
              style={{
                border: "none", borderRadius: 999, cursor: "pointer",
                    padding: "10px 22px", fontFamily: "var(--font-sans)",
                    fontWeight: 900, fontSize: 14,
                background: active ? "var(--brand-primary)" : "transparent",
                color: active ? "var(--fg-on-brand)" : "var(--fg-primary)",
              }}>
              {option === "girl" ? "Girl" : "Boy"}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function HomeScreen({ name, setName, character, setCharacterId, nameFont, brandScheme, setBrandScheme, onOpenProduct, onNavigate }) {
  const isMobile = useIsNarrow();
  const [activeAudioBook, setActiveAudioBook] = useState(null);
  const [heroCoverIndex, setHeroCoverIndex] = useState(0);
  const [isNameEditing, setIsNameEditing] = useState(false);
  const [frozenHero, setFrozenHero] = useState(null);
  const audioRef = React.useRef(null);
  const adventures = LWC_BOOKS.filter(b => b.category === "adventure");
  const gifts      = LWC_BOOKS.filter(b => b.category === "gift");
  const byo        = LWC_BOOKS.filter(b => b.category === "byo");
  const previewName = name || "Emma";
  const currentDemo = getDemoForCharacter(LWC_AUDIO_DEMOS, character, "fairy-glen");
  const heroCharacters = getCharactersForGender(character.gender);
  const heroNames = React.useMemo(() => {
    if ((name || "").trim()) return [name];
    const defaults = character.gender === "boy" ? POPULAR_US_BOY_NAMES : POPULAR_US_GIRL_NAMES;
    return shuffleArray(defaults);
  }, [name, character.gender]);
  const heroSequence = React.useMemo(() => {
    const shuffledCovers = shuffleArray(
      adventures.flatMap(book => heroCharacters.map(heroCharacter => ({ book, heroCharacter })))
    );
    if (heroNames.length <= 1) {
      return shuffledCovers.map(item => ({ ...item, heroName: heroNames[0] || previewName }));
    }
    return shuffledCovers.map((item, index) => ({
      ...item,
      heroName: heroNames[index % heroNames.length],
    }));
  }, [adventures, heroCharacters, heroNames]);
  const rotatingHero = heroSequence[heroCoverIndex] || {
    book: adventures[0],
    heroCharacter: heroCharacters[0] || character,
    heroName: previewName,
  };
  const hasTypedName = Boolean((name || "").trim());
  const activeHero = isNameEditing && frozenHero ? frozenHero : rotatingHero;
  const displayHeroName = hasTypedName ? previewName : (activeHero.heroName || previewName);
  const heroVisualKey = hasTypedName
    ? `${activeHero.book.id}-${activeHero.heroCharacter.id}`
    : `${activeHero.book.id}-${activeHero.heroCharacter.id}-${activeHero.heroName}-${heroCoverIndex}`;

  useEffect(() => {
    setHeroCoverIndex(0);
  }, [character.gender]);

  useEffect(() => {
    if (isNameEditing) return undefined;
    if (heroSequence.length <= 1) return undefined;
    const timer = window.setInterval(() => {
      setHeroCoverIndex(current => (current + 1) % heroSequence.length);
    }, 3600);
    return () => window.clearInterval(timer);
  }, [heroSequence.length, character.gender, name, isNameEditing]);

  function toggleCardAudio(bookId) {
    const audio = audioRef.current;
    if (!audio) return;
    if (activeAudioBook === bookId && !audio.paused) {
      audio.pause();
      setActiveAudioBook(null);
      return;
    }
    audio.pause();
    audio.currentTime = 0;
    audio.src = currentDemo.src;
    audio.play()
      .then(() => setActiveAudioBook(bookId))
      .catch(() => setActiveAudioBook(bookId));
  }

  function scrollToAdventures() {
    document.getElementById("personalized-adventures")?.scrollIntoView({ behavior: "smooth", block: "start" });
  }

  function handleNameFocus() {
    setFrozenHero(rotatingHero);
    setIsNameEditing(true);
  }

  function handleNameBlur() {
    setIsNameEditing(false);
    setFrozenHero(null);
  }

  return (
    <main>
      <audio ref={audioRef} onEnded={() => setActiveAudioBook(null)} />
      {/* HERO */}
      <section style={{
        maxWidth: "var(--container-max)", margin: "0 auto",
        padding: isMobile ? "34px var(--container-pad) 28px" : "56px var(--container-pad) 32px",
        display: "grid",
        gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(360px, 100%), 1fr))",
        gap: isMobile ? 34 : 56,
        alignItems: "center",
        overflow: "hidden",
      }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ marginBottom: 18 }}>
              <BrandSchemeToggle scheme={brandScheme} onChange={setBrandScheme} />
            </div>
            <p className="t-eyebrow" style={{ margin: 0 }}>Screen-free magic, starring your child</p>
          <h1 className="t-display" style={{ margin: "14px 0 18px", fontSize: isMobile ? "clamp(42px, 12vw, 52px)" : undefined }}>
            <span style={{ display: "block" }}>A coloring</span>
            <span style={{ display: "block", whiteSpace: isMobile ? "normal" : "nowrap" }}>
              book <span style={{ display: "inline-block", fontStyle: "italic", color: "var(--lwc-navy-700)" }}>just for</span>
            </span>
            <span
              key={displayHeroName}
              style={{
                display: "block",
                fontStyle: "italic",
                color: "var(--lwc-navy-700)",
                animation: hasTypedName ? "none" : "lwcHeroFade 1200ms ease both",
              }}>
              {displayHeroName}.
            </span>
          </h1>
          <p className="t-lede" style={{ margin: 0, maxWidth: 520 }}>
            Premium 32-page books with your child's name on the cover and in the story —
            paired with an immersive audio adventure, narrated scene-by-scene.
          </p>
          <div style={{ display: "grid", gap: 14, marginTop: 28, width: "100%", maxWidth: isMobile ? "100%" : 460, minWidth: 0 }}>
            <NameInput value={name} onChange={setName} onFocus={handleNameFocus} onBlur={handleNameBlur} />
            <GenderQuickPick character={character} setCharacterId={setCharacterId} />
            <div style={{ display: "flex", gap: 12, flexWrap: "wrap", minWidth: 0 }}>
              <Button variant="primary" size="lg" icon={<Sparkle size={14}/>}
                onClick={scrollToAdventures}
                style={isMobile ? { width: "100%", justifyContent: "center", paddingInline: 18 } : undefined}>Choose {previewName}&apos;s adventure</Button>
              <Button variant="ghost" size="lg" onClick={() => onNavigate("faq")}
                style={isMobile ? { width: "100%", justifyContent: "center", paddingInline: 18 } : undefined}>How it works</Button>
            </div>
            <p style={{ margin: "-2px 0 0", color: "var(--fg-muted)", fontSize: 13, lineHeight: 1.45 }}>
              First pick their story, then match hair, skin tone, name font, and audio so the preview feels like your child.
            </p>
          </div>
        </div>
        <div style={{ position: "relative", aspectRatio: "1 / 1.05", display: "grid", placeItems: "center", minHeight: isMobile ? 330 : undefined }}>
          <div style={{ position: "absolute", inset: 0, background: "var(--lwc-cream-100)", borderRadius: 32, transform: "rotate(-2deg)" }}/>
          <div style={{ position: "absolute", inset: "auto 8% 8% auto", width: "32%", aspectRatio: "1", background: "var(--lwc-rose-100)", borderRadius: 999, opacity: 0.9 }}/>
          <div
            key={heroVisualKey}
            style={{
              position: "relative", zIndex: 1, width: isMobile ? "72%" : "65%",
              animation: (isNameEditing || hasTypedName) ? "none" : "lwcHeroFade 1200ms ease both",
            }}>
            <BookMock
              name={displayHeroName}
              title={activeHero.book.coverTitle || activeHero.book.subtitle.split(".")[0]}
              width="100%"
              label={activeHero.book.coverLabel}
              coverImage={getCoverImage(activeHero.book, activeHero.heroCharacter)}
              coverCallout={activeHero.book.coverCallout}
              ageRange={activeHero.book.ageRange}
              nameFont={nameFont}
            />
          </div>
          <div style={{ position: "absolute", top: "8%", left: "6%", display: "flex", alignItems: "center", gap: 8, background: "var(--bg-surface)", padding: "10px 14px", borderRadius: 999, boxShadow: "var(--shadow-lift)", zIndex: 2 }}>
            <HeadphonesIcon size={16} color="var(--brand-primary)" />
            <span style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 12 }}>22 min audio</span>
          </div>
        </div>
      </section>

      {/* Trust */}
      <section style={{ maxWidth: "var(--container-max)", margin: "0 auto", padding: "0 var(--container-pad)" }}>
        <TrustBar items={LWC_TRUST} />
      </section>

      {/* CATEGORY 1 — Personalized adventures */}
      <section id="personalized-adventures" style={{ maxWidth: "var(--container-max)", margin: "56px auto 0", padding: "0 var(--container-pad)", scrollMarginTop: 24 }}>
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", flexWrap: "wrap", gap: 18, marginBottom: 22 }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0 }}>1 · Personalized adventures</p>
            <h2 className="t-h2" style={{ margin: "8px 0 4px" }}>Pick an adventure.</h2>
            <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)" }}>32 pages · paired with a custom audio story.</p>
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(210px, 1fr))", gap: 20 }}>
          {adventures.map(b => (
            <ProductCard
              key={b.id}
              book={b}
              name={previewName}
              character={character}
              nameFont={nameFont}
              coverImage={getCoverImage(b, character)}
              audioDemo={LWC_COVER_IMAGES?.[b.id] ? currentDemo : null}
              audioActive={activeAudioBook === b.id}
              onAudioToggle={toggleCardAudio}
              onOpen={() => onOpenProduct(b.id)}
            />
          ))}
        </div>
      </section>

      {/* CATEGORY 2 — Gift books */}
      <section style={{ maxWidth: "var(--container-max)", margin: "72px auto 0", padding: "0 var(--container-pad)" }}>
        <div style={{ marginBottom: 22 }}>
          <p className="t-eyebrow" style={{ margin: 0 }}>2 · Gift books — birthdays & holidays</p>
          <h2 className="t-h2" style={{ margin: "8px 0 4px" }}>A fast, thoughtful gift.</h2>
          <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 540 }}>
            Beautifully themed books with the child's name on the cover. Perfect from aunts, grandparents, and anyone short on time.
          </p>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(210px, 1fr))", gap: 20 }}>
          {gifts.map(b => (
            <ProductCard
              key={b.id}
              book={b}
              name={previewName}
              character={character}
              nameFont={nameFont}
              coverImage={getCoverImage(b, character)}
              audioDemo={LWC_COVER_IMAGES?.[b.id] ? currentDemo : null}
              audioActive={activeAudioBook === b.id}
              onAudioToggle={toggleCardAudio}
              onOpen={() => onOpenProduct(b.id)}
            />
          ))}
        </div>
      </section>

      {/* CATEGORY 3 — Build-your-own (coming soon) */}
      <section style={{ maxWidth: "var(--container-max)", margin: "72px auto 0", padding: "0 var(--container-pad)" }}>
        <div style={{
          display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(320px, 100%), 1fr))", gap: 40, alignItems: "center",
          background: "var(--lwc-cream-100)", borderRadius: 32, padding: "clamp(28px, 5vw, 48px)",
          border: "1.5px dashed var(--lwc-cream-300)",
        }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0, color: "var(--lwc-navy-700)" }}>3 · Build-your-own · Coming soon</p>
            <h2 className="t-h2" style={{ margin: "8px 0 12px" }}>Mix &amp; match the perfect book.</h2>
            <p className="t-lede" style={{ margin: 0, maxWidth: 520 }}>
              Pick three theme packs — say <em>10 pages of Space</em>, <em>10 of Animals</em>, <em>10 of Trucks</em> — and we'll print a one-of-a-kind book with their name on the cover. Launching Phase 2.
            </p>
            <div style={{ display: "flex", gap: 12, alignItems: "center", marginTop: 20, flexWrap: "wrap" }}>
              <Button variant="secondary" size="md" icon={<Sparkle size={12}/>}>Join the waitlist</Button>
              <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>Be first to know · no spam, just one launch email.</span>
            </div>
            <div style={{ display: "flex", gap: 8, marginTop: 22, flexWrap: "wrap" }}>
              {["Space", "Animals", "Trucks", "Princesses", "Underwater", "Sports"].map(t => (
                <span key={t} style={{
                  display: "inline-flex", alignItems: "center", gap: 6,
                  padding: "6px 12px", borderRadius: 999,
                  background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
                  fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 12,
                  color: "var(--fg-primary)",
                }}><span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--brand-accent)" }}/>{t}</span>
              ))}
            </div>
          </div>
          <div>
            {byo[0] && <ProductCard book={byo[0]} name={previewName} character={character} nameFont={nameFont} onOpen={() => {}} />}
          </div>
        </div>
      </section>

      {/* Audio explainer */}
      <section style={{ maxWidth: "var(--container-max)", margin: "72px auto 0", padding: "0 var(--container-pad)" }}>
        <AudioDemo character={character} demos={LWC_AUDIO_DEMOS} />
      </section>

      <section style={{ maxWidth: "var(--container-max)", margin: "32px auto 0", padding: "0 var(--container-pad)" }}>
        <AudioExplainer />
      </section>

      {/* Reviews */}
      <section style={{ maxWidth: "var(--container-max)", margin: "72px auto 0", padding: "0 var(--container-pad)" }}>
        <TestimonialBlock reviews={LWC_REVIEWS} />
      </section>

      <Footer onNavigate={onNavigate} />
    </main>
  );
}

function ProductDetailSections({ book, name, character, hasAudio }) {
  const isMobile = useIsNarrow();
  const childName = name || "Emma";
  const [openFaq, setOpenFaq] = useState(null);
  const firstSample = book.samplePages?.[0];
  const secondSample = book.samplePages?.[1] || firstSample;
  const detailRows = [
    {
      title: "How is the book personalized?",
      body: `${childName}'s name appears on the cover and throughout the adventure. For personalized adventures, parents can also choose a cover character that feels closest to their child.`,
    },
    {
      title: "What's the story?",
      body: `${book.title} is a gentle, imaginative coloring adventure built for small children: simple scenes, friendly characters, and enough story to make every page feel like part of one journey.`,
    },
    {
      title: "Size & quality",
      body: `${book.pages} coloring pages for ages 3-10, planned as a premium printed book on thick cream paper with a keepsake-style cover.`,
    },
  ];
  if (hasAudio) {
    detailRows.push({
      title: "Audio companion",
      body: "Personalized adventures can include a narrated audio version, so children can listen along while coloring without needing to watch a screen.",
    });
  }
  const faqs = [
    {
      q: `What age is ${book.title} best for?`,
      a: "The page style and story rhythm are designed for children aged 3-10, with simple scenes for younger kids and enough detail for older children to enjoy.",
    },
    {
      q: "Can I preview pages first?",
      a: "Yes. The preview book on this page lets you swipe through the cover and sample coloring pages before joining the waitlist.",
    },
    {
      q: "Is audio included?",
      a: hasAudio
        ? "Audio is an optional add-on for personalized adventures. Gift books and mix-and-match books are planned as printed books only for now."
        : "No. Gift books and mix-and-match books are planned as printed books only for now.",
    },
  ];

  return (
    <>
      <section style={{
        marginTop: isMobile ? 48 : 72, display: "grid",
        gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(320px, 100%), 1fr))",
        gap: 28, alignItems: "center",
      }}>
        <div style={{
          background: "var(--lwc-cream-100)", borderRadius: 28, padding: "clamp(26px, 4vw, 46px)",
          border: "1px solid var(--border-soft)",
        }}>
          <p className="t-eyebrow" style={{ margin: 0 }}>This one's a keeper</p>
          <h2 className="t-h2" style={{ margin: "10px 0 14px" }}>A little book with their name woven in.</h2>
          <p className="t-body-lg" style={{ margin: 0, color: "var(--fg-secondary)" }}>
            It is designed to feel personal before the first page is colored: their name on the cover,
            their chosen character in the scene, and a gentle story that makes the book feel made for them.
          </p>
        </div>
        <div style={{
          minHeight: 340, background: book.bg, borderRadius: 28, padding: 26,
          display: "grid", placeItems: "center", overflow: "hidden",
        }}>
          {firstSample ? (
            <img src={firstSample.src} alt={`${book.title} coloring sample`} style={{
              width: "min(70%, 280px)", aspectRatio: "2 / 3", objectFit: "cover",
              borderRadius: 16, background: "var(--bg-surface)",
              boxShadow: "-8px 8px 0 rgba(20,33,61,.08), var(--shadow-lift)",
            }} />
          ) : (
            <BookMock
              name={childName}
              title={book.coverTitle || book.subtitle.split(".")[0]}
              width="55%"
              label={book.coverLabel}
              coverImage={getCoverImage(book, character)}
            />
          )}
        </div>
      </section>

      <section style={{
        marginTop: 28, display: "grid",
        gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(320px, 100%), 1fr))",
        gap: 28, alignItems: "center",
      }}>
        <div style={{
          minHeight: 340, background: "var(--bg-surface)", borderRadius: 28, padding: 26,
          display: "grid", placeItems: "center", border: "1px solid var(--border-soft)",
          boxShadow: "var(--shadow-card)",
        }}>
          {secondSample && (
            <img src={secondSample.src} alt={`${book.title} interior sample`} style={{
              width: "min(72%, 300px)", aspectRatio: "2 / 3", objectFit: "cover",
              borderRadius: 16, border: "1px solid var(--border-soft)",
            }} />
          )}
        </div>
        <div>
          <p className="t-eyebrow" style={{ margin: 0 }}>For them, page after page</p>
          <h2 className="t-h2" style={{ margin: "10px 0 14px" }}>Coloring, story, and a keepsake in one.</h2>
          <p className="t-body-lg" style={{ margin: 0, color: "var(--fg-secondary)" }}>
            Each spread gives children something calm and creative to do, while the personalized story makes
            the finished book feel worth keeping on the shelf.
          </p>
        </div>
      </section>

      <section style={{ marginTop: 72 }}>
        <p className="t-eyebrow" style={{ margin: 0 }}>Product details</p>
        <div style={{ marginTop: 14, display: "grid", gap: 12 }}>
          {detailRows.map(row => (
            <article key={row.title} style={{
              background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
              borderRadius: 18, padding: "18px 20px", boxShadow: "var(--shadow-card)",
            }}>
              <h3 className="t-h4" style={{ margin: 0 }}>{row.title}</h3>
              <p className="t-body" style={{ margin: "8px 0 0", color: "var(--fg-secondary)" }}>{row.body}</p>
            </article>
          ))}
        </div>
      </section>

      <section style={{
        marginTop: 72, background: "var(--lwc-cream-100)",
        border: "1px solid var(--border-soft)", borderRadius: 28,
        padding: "clamp(26px, 4vw, 44px)",
      }}>
        <p className="t-eyebrow" style={{ margin: 0 }}>Early family feedback</p>
        <blockquote className="t-quote" style={{ margin: "10px 0 10px", maxWidth: 780 }}>
          "Parents in our initial testing group loved the idea of a coloring book that feels personal,
          especially when the child's name appears in the story."
        </blockquote>
        <p className="t-caption" style={{ margin: 0, color: "var(--fg-muted)" }}>
          Based on early concept testing, not public customer reviews.
        </p>
      </section>

      <section style={{ marginTop: 72 }}>
        <p className="t-eyebrow" style={{ margin: 0 }}>FAQs for {book.title}</p>
        <div style={{ marginTop: 14, display: "grid", gap: 12 }}>
          {faqs.map((item, index) => {
            const open = openFaq === index;
            return (
            <article key={item.q} style={{
              background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
              borderRadius: 18, overflow: "hidden",
            }}>
              <button
                type="button"
                aria-expanded={open}
                onClick={() => setOpenFaq(open ? null : index)}
                style={{
                  width: "100%", border: "none", background: "transparent", cursor: "pointer",
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                  gap: 16, padding: "18px 20px", textAlign: "left",
                  color: "var(--fg-primary)",
                }}>
                <span className="t-h4" style={{ margin: 0 }}>{item.q}</span>
                <span style={{
                  width: 30, height: 30, borderRadius: 999,
                  background: open ? "var(--brand-primary)" : "var(--lwc-cream-100)",
                  color: open ? "var(--fg-on-brand)" : "var(--fg-primary)",
                  display: "grid", placeItems: "center", flex: "0 0 auto",
                  transition: "background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out)",
                  transform: open ? "rotate(45deg)" : "rotate(0deg)",
                  fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 20,
                  lineHeight: 1,
                }}>+</span>
              </button>
              {open && (
                <p className="t-body" style={{
                  margin: 0, padding: "0 20px 18px",
                  color: "var(--fg-secondary)",
                }}>{item.a}</p>
              )}
            </article>
          );})}
        </div>
      </section>

      <section style={{
        marginTop: isMobile ? 48 : 72, background: "var(--lwc-navy-900)", color: "var(--lwc-cream-50)",
        borderRadius: 28, padding: "clamp(28px, 5vw, 52px)",
        display: "grid", gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(280px, 100%), 1fr))", gap: 24, alignItems: "center",
      }}>
        <div>
          <p className="t-eyebrow" style={{ margin: 0, color: "var(--lwc-gold-300)" }}>Launch updates</p>
          <h2 className="t-h2" style={{ margin: "10px 0 12px", color: "var(--lwc-cream-50)" }}>
            Join the list for first access.
          </h2>
          <p className="t-body-lg" style={{ margin: 0, color: "rgba(251,247,239,.78)" }}>
            We will send one useful launch email when personalized ordering opens.
          </p>
        </div>
        <div style={{ display: "grid", gap: 10 }}>
          <input placeholder="hello@example.com" style={{
            width: "100%", border: "1px solid rgba(251,247,239,.28)", borderRadius: 999,
            padding: "14px 18px", fontFamily: "var(--font-sans)", fontSize: 14,
            background: "rgba(255,255,255,.08)", color: "var(--lwc-cream-50)",
            outline: "none",
          }}/>
          <Button variant="primary" size="lg" icon={<Sparkle size={14}/>}>Notify me</Button>
        </div>
      </section>
    </>
  );
}

function PersonalizeModal({ open, onClose, book, name, setName, character, setCharacterId, nameFont, setNameFontId, audio, setAudio, hasAudio, onWaitlist }) {
  const isMobile = useIsNarrow();
  const [step, setStep] = useState(0);
  const [parentName, setParentName] = useState("");
  const [parentEmail, setParentEmail] = useState("");
  const previewImages = LWC_COVER_IMAGES?.[book.id];
  const launchBookPrice = 19.99;
  const launchBundlePrice = 24.99;
  const standardBookPrice = 29.99;
  const standardBundlePrice = 39.99;
  const selectedGender = character.gender;
  const genderCharacters = getCharactersForGender(selectedGender);
  const childName = name || "Emma";
  const launchPrice = hasAudio && audio ? launchBundlePrice : launchBookPrice;
  const stepTitles = [
    "Child's name",
    "Girl or boy",
    "Cover variant",
    "Name style",
    "Final review",
  ];

  useEffect(() => {
    if (!open) return;
    setStep(0);
    setParentName("");
    setParentEmail("");
  }, [open, book.id]);

  if (!open) return null;

  const canContinue = [
    (name || "").trim().length > 0,
    Boolean(character?.gender),
    Boolean(character?.id),
    Boolean(nameFont?.id),
    parentName.trim().length > 0 && parentEmail.includes("@"),
  ][step];

  function moveStep(direction) {
    setStep(current => Math.max(0, Math.min(stepTitles.length - 1, current + direction)));
  }

  function renderStepContent() {
    if (step === 0) {
      return (
        <div style={{ display: "grid", gap: 16 }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0 }}>Step 1</p>
            <h3 className="t-h3" style={{ margin: "6px 0 10px" }}>Start with the child's name.</h3>
            <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 520 }}>
              Start with the name you want to see on the cover and throughout the adventure.
            </p>
          </div>
          <NameInput value={name} onChange={setName} />
        </div>
      );
    }

    if (step === 1) {
      return (
        <div style={{ display: "grid", gap: 16 }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0 }}>Step 2</p>
            <h3 className="t-h3" style={{ margin: "6px 0 10px" }}>Choose girl or boy.</h3>
            <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 520 }}>
              Choose the version that matches your child best before we fine-tune the cover.
            </p>
          </div>
          <div style={{
            display: "inline-grid", gridTemplateColumns: "1fr 1fr", gap: 8,
            padding: 8, background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
            borderRadius: 999, width: "fit-content",
          }}>
            {["girl", "boy"].map(option => {
              const active = selectedGender === option;
              const firstForGender = getFirstCharacterForGender(option);
              return (
                <button
                  key={option}
                  type="button"
                  onClick={() => setCharacterId(firstForGender.id)}
                  style={{
                    border: "none", borderRadius: 999, cursor: "pointer",
                    padding: "12px 24px", fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 14,
                    background: active ? "var(--brand-primary)" : "transparent",
                    color: active ? "var(--fg-on-brand)" : "var(--fg-primary)",
                  }}>
                  {option === "girl" ? "Girl" : "Boy"}
                </button>
              );
            })}
          </div>
        </div>
      );
    }

    if (step === 2) {
      return (
        <div style={{ display: "grid", gap: 16 }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0 }}>Step 3</p>
            <h3 className="t-h3" style={{ margin: "6px 0 10px" }}>Pick the closest cover variant.</h3>
            <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 560 }}>
              Pick the cover look that feels most like your child. Each option keeps the same magical world while changing the child on the cover.
            </p>
          </div>
          {LWC_COVER_IMAGES?.[book.id] && (
            <CharacterSelector
              characters={LWC_CHARACTERS}
              selectedId={character.id}
              onSelect={setCharacterId}
              name={name}
              title={book.coverTitle || book.subtitle.split(".")[0]}
              nameFont={nameFont}
              previewImages={previewImages}
              showGenderToggle={false}
            />
          )}
        </div>
      );
    }

    if (step === 3) {
      return (
        <div style={{ display: "grid", gap: 16 }}>
          <div>
            <p className="t-eyebrow" style={{ margin: 0 }}>Step 4</p>
            <h3 className="t-h3" style={{ margin: "6px 0 10px" }}>Choose the name style.</h3>
            <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 520 }}>
              Choose the lettering style you’d love to see on the finished cover.
            </p>
          </div>
          <NameFontSelector
            fonts={LWC_NAME_FONTS}
            selectedId={nameFont.id}
            onSelect={setNameFontId}
            name={name}
          />
        </div>
      );
    }

    return (
      <div style={{ display: "grid", gap: 18 }}>
        <div>
          <p className="t-eyebrow" style={{ margin: 0 }}>Step 5</p>
          <h3 className="t-h3" style={{ margin: "6px 0 10px" }}>Final review and launch offer.</h3>
          <p className="t-body" style={{ margin: 0, color: "var(--fg-muted)", maxWidth: 560 }}>
            Here’s the finished version with the preview pages, plus the optional audio companion before you reserve your launch spot.
          </p>
        </div>

        {hasAudio && (
          <AudioUpsellPlayer
            checked={audio}
            onToggle={setAudio}
            price={9.99}
            demo={getDemoForCharacter(LWC_AUDIO_DEMOS, character, book.id)}
          />
        )}

        <div style={{
          background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
          borderRadius: 20, padding: "20px 22px", display: "grid", gap: 10,
        }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
            <strong style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 22, color: "var(--fg-primary)" }}>
              Add me to the waitlist and reserve the launch offer
            </strong>
            <span style={{ fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 24, color: "var(--brand-primary)" }}>
              ${launchPrice.toFixed(2)}
            </span>
          </div>
          <p style={{ margin: 0, fontSize: 14, color: "var(--fg-secondary)", lineHeight: 1.5 }}>
            {audio
              ? <>Instead of <s>${standardBundlePrice.toFixed(2)}</s> for the full bundle with audio, the launch offer is <strong>${launchBundlePrice.toFixed(2)}</strong>.</>
              : <>Instead of <s>${standardBookPrice.toFixed(2)}</s> for the coloring book alone, the launch offer is <strong>${launchBookPrice.toFixed(2)}</strong>.</>}
          </p>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: 12 }}>
            <label style={{ display: "grid", gap: 6 }}>
              <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 12, color: "var(--fg-primary)" }}>First name</span>
              <input
                type="text"
                value={parentName}
                onChange={(event) => setParentName(event.target.value)}
                placeholder="Emma"
                style={{
                  padding: "12px 14px", borderRadius: 14, border: "1.5px solid var(--border-soft)",
                  fontFamily: "var(--font-sans)", fontSize: 14, background: "var(--bg-surface)",
                  color: "var(--fg-primary)", outline: "none",
                }}
              />
            </label>
            <label style={{ display: "grid", gap: 6 }}>
              <span style={{ fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 12, color: "var(--fg-primary)" }}>Email</span>
              <input
                type="email"
                value={parentEmail}
                onChange={(event) => setParentEmail(event.target.value)}
                placeholder="hello@example.com"
                style={{
                  padding: "12px 14px", borderRadius: 14, border: "1.5px solid var(--border-soft)",
                  fontFamily: "var(--font-sans)", fontSize: 14, background: "var(--bg-surface)",
                  color: "var(--fg-primary)", outline: "none",
                }}
              />
            </label>
          </div>
          <p style={{ margin: 0, fontSize: 12.5, color: "var(--fg-muted)" }}>
            No payment now. We’ll only email you when this exact version is ready to order.
          </p>
        </div>
      </div>
    );
  }

  return (
    <>
      <div onClick={onClose} style={{
        position: "fixed", inset: 0, background: "rgba(20,33,61,.45)",
        zIndex: 60,
      }}/>
      <section role="dialog" aria-modal="true" aria-label="Personalize your book" style={{
        position: "fixed",
        top: isMobile ? 12 : "clamp(14px, 4vw, 42px)",
        bottom: isMobile ? 0 : "clamp(14px, 4vw, 42px)",
        left: "50%", transform: "translateX(-50%)",
        width: isMobile ? "calc(100vw - 16px)" : "min(1120px, calc(100vw - 28px))",
        zIndex: 61,
        background: "var(--bg-canvas)", borderRadius: isMobile ? "24px 24px 0 0" : 28,
        boxShadow: "0 24px 80px rgba(20,33,61,.26)",
        display: "grid", gridTemplateRows: "auto 1fr auto",
        overflow: "hidden",
      }}>
        <header style={{
          display: "flex", alignItems: "center", gap: 16,
          padding: isMobile ? "16px 18px" : "18px clamp(20px, 4vw, 34px)",
          borderBottom: "1px solid var(--border-soft)",
          background: "var(--bg-surface)",
        }}>
          <div style={{ minWidth: 0 }}>
            <p className="t-eyebrow" style={{ margin: 0 }}>{book.theme}</p>
            <h2 className="t-h3" style={{ margin: "4px 0 0" }}>{stepTitles[step]}</h2>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 7, marginLeft: "auto", marginRight: 8, flex: "0 0 auto" }}>
            {stepTitles.map((title, index) => (
              <span
                key={title}
                title={title}
                style={{
                  width: index === step ? 22 : 8,
                  height: 8,
                  borderRadius: 999,
                  background: index <= step ? "var(--brand-primary)" : "rgba(20,33,61,.16)",
                  transition: "width var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out)",
                }}
              />
            ))}
          </div>
          <button type="button" onClick={onClose} aria-label="Close personalization" style={{
            width: 42, height: 42, borderRadius: 999,
            border: "1px solid var(--border-soft)", background: "var(--bg-surface)",
            color: "var(--fg-primary)", cursor: "pointer", display: "grid", placeItems: "center",
          }}><Close /></button>
        </header>

        <div style={{
          overflowY: "auto",
          padding: isMobile ? "18px" : "clamp(20px, 4vw, 34px)",
          display: "grid",
          gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(320px, 100%), 1fr))",
          gap: isMobile ? 18 : 28,
          alignItems: "start",
        }}>
          <div style={{ display: "grid", gap: 14, position: isMobile ? "relative" : "sticky", top: 0 }}>
            {step === stepTitles.length - 1 ? (
              <SampleBookScroller book={book} name={name || "Emma"} character={character} nameFont={nameFont} compact />
            ) : (
            <div style={{
              background: `linear-gradient(180deg, color-mix(in srgb, ${book.bg} 90%, white), ${book.bg})`,
              borderRadius: 28,
              border: "1px solid rgba(20,33,61,.05)",
              padding: "20px 18px 24px",
              display: "grid",
              justifyItems: "center",
              gap: 12,
            }}>
              <div style={{ width: isMobile ? "min(76vw, 300px)" : "min(100%, 320px)" }}>
                <BookMock
                  name={childName}
                  title={book.coverTitle || book.subtitle.split(".")[0]}
                  width="100%"
                  label={book.coverLabel}
                  coverImage={getCoverImage(book, character)}
                  coverCallout={book.coverCallout}
                  ageRange={book.ageRange}
                  nameFont={nameFont}
                />
              </div>
              <div style={{ display: "grid", gap: 4, textAlign: "center" }}>
                <strong style={{ fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 13, color: "var(--fg-primary)" }}>
                  {book.title}
                </strong>
                <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>
                  {selectedGender === "girl" ? `${genderCharacters.length} girl cover variants` : `${genderCharacters.length} boy cover variants`}
                </span>
              </div>
            </div>
            )}
          </div>

          <div style={{ display: "grid", gap: 18, alignContent: "start" }}>
            {renderStepContent()}
            {step < stepTitles.length - 1 && (
              <div style={{ fontSize: 12.5, color: "var(--fg-muted)" }}>
                We’ll keep the current cover preview visible as you move through each step.
              </div>
            )}
          </div>
        </div>

        <footer style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          gap: 16, flexWrap: "wrap", padding: isMobile ? "14px 18px calc(14px + env(safe-area-inset-bottom))" : "18px clamp(20px, 4vw, 34px)",
          borderTop: "1px solid var(--border-soft)", background: "var(--bg-surface)",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
            {step > 0 && (
              <Button variant="ghost" size="md" onClick={() => moveStep(-1)}>
                Back
              </Button>
            )}
            <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>
              Step {step + 1} of {stepTitles.length}
            </span>
          </div>
          {step < stepTitles.length - 1 ? (
            <Button variant="primary" size="lg" icon={<Sparkle size={14}/>} onClick={() => moveStep(1)} disabled={!canContinue} style={isMobile ? { flex: "1 1 180px", justifyContent: "center" } : undefined}>
              Continue
            </Button>
          ) : (
            <Button
              variant="primary"
              size="lg"
              icon={<Sparkle size={14}/>}
              onClick={() => onWaitlist({ parentName, parentEmail, launchPrice })}
              disabled={!canContinue}
              style={isMobile ? { flex: "1 1 220px", justifyContent: "center" } : undefined}>
              Add me to the waitlist
            </Button>
          )}
        </footer>
      </section>
    </>
  );
}

// ---- Product detail ----
function ProductScreen({ bookId, name, setName, character, setCharacterId, nameFont, setNameFontId, onBack, onAdd, onNavigate }) {
  const isMobile = useIsNarrow();
  const book = LWC_BOOKS.find(b => b.id === bookId) || LWC_BOOKS[0];
  const hasAudio = book.category === "adventure";
  const [audio, setAudio] = useState(hasAudio);
  const [personalizeOpen, setPersonalizeOpen] = useState(false);
  useEffect(() => setAudio(hasAudio), [book.id, hasAudio]);
  const heroFeatures = [
    "Best for ages 3-10",
    `Preview ${book.samplePages?.length || 4} sample pages before launch`,
    "Personalized cover and story",
    hasAudio ? "Optional narrated audio companion" : "Printed gift book only",
  ];
  return (
    <main style={{ maxWidth: "var(--container-max)", margin: "0 auto", padding: isMobile ? "24px var(--container-pad) 48px" : "28px var(--container-pad) 56px" }}>
      <button onClick={onBack} style={{
        background: "transparent", border: "none", cursor: "pointer",
        fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13, color: "var(--fg-muted)",
        display: "inline-flex", alignItems: "center", gap: 6, padding: 0, marginBottom: 22,
      }}>← All books</button>

      <section style={{
        display: "grid",
        gridTemplateColumns: isMobile ? "1fr" : "repeat(auto-fit, minmax(min(360px, 100%), 1fr))",
        gap: isMobile ? 30 : 48,
        alignItems: "start",
      }}>
        <SampleBookScroller book={book} name={name} character={character} nameFont={nameFont} />
        <div>
          <p className="t-eyebrow" style={{ margin: 0 }}>{book.theme}</p>
          <h1 className="t-h1" style={{ margin: "10px 0 12px", fontSize: isMobile ? undefined : 44 }}>{book.title}</h1>
          <p className="t-lede" style={{ margin: 0 }}>{book.subtitle} Hand-illustrated, on premium 200gsm cream paper.</p>

          <div style={{ display: "flex", gap: 4, color: "var(--brand-accent)", margin: "16px 0 4px", alignItems: "center" }}>
            {Array.from({ length: 5 }).map((_, i) => <StarSolid key={i} size={16} color="var(--lwc-gold-500)"/>)}
            <span style={{ fontSize: 13, color: "var(--fg-muted)", fontWeight: 700 }}>Based on initial family testing</span>
          </div>

          <div style={{ marginTop: 18, display: "grid", gap: 10 }}>
            {heroFeatures.map(feature => (
              <div key={feature} style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 14, color: "var(--fg-secondary)", fontWeight: 700 }}>
                <Sparkle size={12} color="var(--brand-accent)"/>
                <span>{feature}</span>
              </div>
            ))}
          </div>

          <div style={{ marginTop: 24, display: "grid", gap: 14 }}>
            <label style={{ display: "block" }}>
              <span style={{ display: "block", fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13, marginBottom: 6 }}>
                Personalize with their name <Sparkle size={10} color="var(--brand-accent)"/>
              </span>
              <NameInput value={name} onChange={setName} />
              <span style={{ fontSize: 12, color: "var(--fg-muted)", display: "block", marginTop: 6 }}>
                Appears on the cover and 12+ times in the story.
              </span>
            </label>
          </div>

          <div style={{ display: "flex", alignItems: isMobile ? "stretch" : "center", gap: 18, marginTop: 28, flexDirection: isMobile ? "column" : "row" }}>
            <button type="button" onClick={() => setPersonalizeOpen(true)} style={{
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
              minWidth: isMobile ? 0 : 300, width: isMobile ? "100%" : undefined, padding: "14px 30px", borderRadius: 999, border: "none",
              background: "var(--lwc-gold-500)", color: "var(--lwc-navy-900)",
              fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 16,
              cursor: "pointer", boxShadow: "0 1px 2px rgba(20,33,61,.06), 0 6px 16px rgba(184,134,46,.22)",
            }}>
              <Sparkle size={14}/> Personalize my book
            </button>
            <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>Step through cover, character, font and{hasAudio ? " audio" : " preview"} next</span>
          </div>
        </div>
      </section>
      <PersonalizeModal
        open={personalizeOpen}
        onClose={() => setPersonalizeOpen(false)}
        book={book}
        name={name}
        setName={setName}
        character={character}
        setCharacterId={setCharacterId}
        nameFont={nameFont}
        setNameFontId={setNameFontId}
        audio={audio}
        setAudio={setAudio}
        hasAudio={hasAudio}
        onWaitlist={({ parentName, parentEmail, launchPrice }) => {
          onAdd(book, audio, name || "Emma", { parentName, parentEmail, launchPrice });
          setPersonalizeOpen(false);
        }}
      />
      <ProductDetailSections book={book} name={name} character={character} hasAudio={hasAudio} />
      <Footer onNavigate={onNavigate} />
    </main>
  );
}

// ---- Waitlist drawer ----
function CartDrawer({ open, items, onClose, onCheckout, onRemove }) {
  const isMobile = useIsNarrow();
  const [email, setEmail] = React.useState("");
  const [submitted, setSubmitted] = React.useState(false);

  useEffect(() => {
    const reservedEmail = items.find(item => item.email)?.email || "";
    if (reservedEmail) {
      setEmail(reservedEmail);
      setSubmitted(true);
      return;
    }
    if (!open) {
      setEmail("");
      setSubmitted(false);
    }
  }, [items, open]);

  return (
    <>
      <div onClick={onClose} style={{
        position: "fixed", inset: 0, background: "rgba(20,33,61,.40)",
        opacity: open ? 1 : 0, pointerEvents: open ? "auto" : "none",
        transition: "opacity var(--dur-base) var(--ease-out)", zIndex: 50,
      }}/>
      <aside style={{
        position: "fixed", top: 0, right: 0, bottom: 0, width: isMobile ? "100%" : "min(440px, 100%)",
        background: "var(--bg-canvas)", boxShadow: "-20px 0 60px rgba(20,33,61,.20)",
        transform: open ? "translateX(0)" : "translateX(100%)",
        transition: "transform var(--dur-slow) var(--ease-out)",
        display: "flex", flexDirection: "column", zIndex: 51,
      }}>
        <header style={{ padding: "20px 24px", display: "flex", alignItems: "center", borderBottom: "1px solid var(--border-soft)" }}>
          <h3 className="t-h3" style={{ margin: 0 }}>Your waitlist</h3>
          <button onClick={onClose} style={{ marginLeft: "auto", background: "transparent", border: "none", cursor: "pointer", color: "var(--fg-primary)" }}><Close /></button>
        </header>
        <div style={{ flex: 1, overflowY: "auto", padding: isMobile ? "18px" : "20px 24px", display: "grid", gap: 16, alignContent: "start" }}>
          {items.length === 0 && (
            <div style={{ textAlign: "center", marginTop: 40 }}>
              <p style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", color: "var(--fg-muted)", margin: 0 }}>
                Your waitlist is empty.
              </p>
              <p style={{ fontSize: 13, color: "var(--fg-muted)", marginTop: 8 }}>
                Browse the library and tap <strong>Join the waitlist</strong> on any book you'd like first dibs on.
              </p>
            </div>
          )}
          {items.map((it, i) => (
            <div key={i} style={{
              display: "grid",
              gridTemplateColumns: isMobile ? "56px minmax(0, 1fr)" : "64px 1fr auto",
              gap: 14,
              alignItems: "center",
              padding: 12, borderRadius: 16, background: "var(--bg-surface)", border: "1px solid var(--border-soft)",
            }}>
              <div style={{ width: isMobile ? 56 : 64, height: isMobile ? 70 : 80, background: it.bg, borderRadius: 8, display: "grid", placeItems: "center" }}>
                <div style={{ width: "70%", height: "85%", background: "var(--lwc-navy-700)", borderRadius: "2px 4px 4px 2px", boxShadow: "inset 4px 0 0 rgba(0,0,0,.2)" }}/>
              </div>
              <div>
                <div style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 16 }}>{it.title}</div>
                <div style={{ fontSize: 12, color: "var(--fg-muted)", marginTop: 2 }}>
                  For {it.name}{it.audio ? " · + audio" : ""} · ${it.price.toFixed(2)} at launch
                </div>
                <button onClick={() => onRemove(i)} style={{ background: "transparent", border: "none", padding: 0, fontSize: 11, color: "var(--fg-muted)", cursor: "pointer", textDecoration: "underline", marginTop: 4 }}>Remove</button>
              </div>
              <span style={{ fontSize: 11, fontWeight: 700, color: "var(--brand-accent)", letterSpacing: ".06em", textTransform: "uppercase", gridColumn: isMobile ? "2" : undefined }}>Waitlisted</span>
            </div>
          ))}
        </div>
        {items.length > 0 && (
          <footer style={{ padding: isMobile ? "18px 18px calc(18px + env(safe-area-inset-bottom))" : 24, borderTop: "1px solid var(--border-soft)", display: "grid", gap: 12 }}>
            {submitted ? (
              <div style={{
                display: "grid", gap: 6, padding: 16, borderRadius: 14,
                background: "var(--lwc-cream-100)", border: "1px solid var(--border-soft)",
              }}>
                <strong style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 18 }}>You're on the list ✨</strong>
                <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>
                  We'll email <strong>{email}</strong> the moment {items.length === 1 ? items[0].title : `these ${items.length} books`} go live. No spam, ever.
                </span>
              </div>
            ) : (
              <>
                <div style={{ fontSize: 12, color: "var(--fg-muted)" }}>
                  We'll only email you when one of <strong>your</strong> {items.length === 1 ? "book" : `${items.length} books`} is ready to order.
                </div>
                <label style={{ display: "grid", gap: 6 }}>
                  <span style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 12 }}>Your email</span>
                  <input
                    type="email" value={email} onChange={(e) => setEmail(e.target.value)}
                    placeholder="hello@example.com"
                    style={{
                      padding: "12px 14px", borderRadius: 12, border: "1.5px solid var(--border-soft)",
                      fontFamily: "var(--font-sans)", fontSize: 14, background: "var(--bg-surface)",
                      color: "var(--fg-primary)", outline: "none",
                    }}/>
                </label>
                <Button variant="primary" size="lg"
                  onClick={() => { if (email.includes("@")) { onCheckout(items, email); setSubmitted(true); } }}
                  icon={<Sparkle size={14}/>}>
                  Save my waitlist
                </Button>
              </>
            )}
          </footer>
        )}
      </aside>
    </>
  );
}

// ---- App ----
function App() {
  const urlParams = new URL(window.location.href).searchParams;
  const initialName = normalizeChildName(urlParams.get("name") || "");
  const initialCharacterId = urlParams.get("character");
  const hasInitialCharacter = LWC_CHARACTERS.some(character => character.id === initialCharacterId);

  const routeFromLocation = () => {
    const url = new URL(window.location.href);
    const bookId = url.searchParams.get("book");
    const pageParam = url.searchParams.get("page");
    const hashValue = window.location.hash.replace(/^#\/?/, "");
    const page = pageParam || hashValue;

    if (bookId && LWC_BOOKS.some(book => book.id === bookId)) {
      return { name: "product", id: bookId };
    }

    return INFO_PAGES[page] ? { name: "info", page } : { name: "home" };
  };
  const [route, setRoute] = useState(routeFromLocation);
  const [name, setNameState] = useState(initialName);
  const [characterId, setCharacterId] = useState(hasInitialCharacter ? initialCharacterId : "girl-medium-black");
  const [nameFontId, setNameFontId] = useState("storybook");
  const [brandScheme, setBrandScheme] = useState("storybook");
  const [cart, setCart] = useState([]);
  const [cartOpen, setCartOpen] = useState(false);
  const character = LWC_CHARACTERS.find(c => c.id === characterId) || LWC_CHARACTERS[0];
  const nameFont = LWC_NAME_FONTS.find(f => f.id === nameFontId) || LWC_NAME_FONTS[0];

  function setName(value) {
    setNameState(normalizeChildName(value));
  }

  useEffect(() => {
    const syncRoute = () => setRoute(routeFromLocation());
    window.addEventListener("hashchange", syncRoute);
    window.addEventListener("popstate", syncRoute);
    return () => {
      window.removeEventListener("hashchange", syncRoute);
      window.removeEventListener("popstate", syncRoute);
    };
  }, []);

  useEffect(() => {
    document.body.dataset.brandScheme = brandScheme === "forest" ? "forest" : "storybook";
  }, [brandScheme]);

  function navigate(page) {
    const url = new URL(window.location.href);
    if (page === "home") {
      url.searchParams.delete("book");
      url.searchParams.delete("page");
      url.hash = "";
      window.history.pushState("", document.title, `${url.pathname}${url.search}`);
      setRoute({ name: "home" });
    } else {
      url.searchParams.delete("book");
      url.searchParams.set("page", page);
      url.hash = "";
      window.history.pushState("", document.title, `${url.pathname}${url.search}`);
      setRoute(INFO_PAGES[page] ? { name: "info", page } : { name: "home" });
    }
    window.scrollTo({ top: 0 });
  }

  function openProduct(id) {
    const url = new URL(window.location.href);
    url.searchParams.set("book", id);
    url.searchParams.delete("page");
    url.hash = "";
    window.history.pushState("", document.title, `${url.pathname}${url.search}`);
    setRoute({ name: "product", id });
    window.scrollTo({ top: 0 });
  }
  function addToCart(book, audio, forName, meta = {}) {
    const launchPrice = meta.launchPrice || (book.category === "adventure" ? (audio ? 24.99 : 19.99) : book.price);
    setCart(c => [...c, {
      id: book.id, title: book.title, bg: book.bg, name: forName,
      audio, price: launchPrice, email: meta.parentEmail || "", parentName: meta.parentName || "",
    }]);
    setCartOpen(true);
  }

  return (
    <>
      <AnnouncementBar />
      <Header
        cartCount={cart.length}
        onLogo={() => navigate("home")}
        onBrowse={() => navigate("home")}
        onCart={() => setCartOpen(true)}
        onNavigate={navigate}
      />
      {route.name === "home" && (
        <HomeScreen
          name={name}
          setName={setName}
          character={character}
          setCharacterId={setCharacterId}
          nameFont={nameFont}
          brandScheme={brandScheme}
          setBrandScheme={setBrandScheme}
          onOpenProduct={openProduct}
          onNavigate={navigate}
        />
      )}
      {route.name === "product" && (
        <ProductScreen bookId={route.id} name={name} setName={setName}
          character={character}
          setCharacterId={setCharacterId}
          nameFont={nameFont}
          setNameFontId={setNameFontId}
          onBack={() => navigate("home")}
          onAdd={addToCart}
          onNavigate={navigate}/>
      )}
      {route.name === "info" && (
        <InfoScreen page={route.page} onNavigate={navigate}/>
      )}
      <CartDrawer open={cartOpen} items={cart}
        onClose={() => setCartOpen(false)}
        onRemove={i => setCart(c => c.filter((_, n) => n !== i))}
        onCheckout={(books, email) => console.log("[waitlist]", email, books.map(b => `${b.title} for ${b.name}`))}/>
    </>
  );
}

Object.assign(window, { HomeScreen, ProductScreen, CartDrawer, App });
