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

// ─── Dev mode ────────────────────────────────────────────────────────────────
// Set to true locally to bypass Firebase auth during development.
// Must be false in production.
const DEV_MODE = false;
const DEV_USER = { email: "dev@mastersinclarity.com", uid: "dev" };

// ─── Constants ───────────────────────────────────────────────────────────────

const CTA = "https://calendly.com/dh--8/meeting-with-dolores?back=1";

const WPM = 130;

const STEPS = [
  { id:1, iconKey:"anchor",    title:"Hook",             tagline:"Grab them in 10 seconds."        },
  { id:2, iconKey:"lightbulb", title:"Main Idea",        tagline:"One sentence. One truth."        },
  { id:3, iconKey:"user",      title:"About You",        tagline:"Why should they listen?"         },
  { id:4, iconKey:"layers",    title:"Three Key Points", tagline:"Teach. Prove. Inspire."          },
  { id:5, iconKey:"repeat",    title:"Reinforce",        tagline:"Bring it back home."             },
  { id:6, iconKey:"sunrise",   title:"New Reality",      tagline:"Paint the transformation."       },
  { id:7, iconKey:"send",      title:"Call to Action",   tagline:"Tell them exactly what's next."  },
];

const TESTIMONIAL = {
  quote: "I had a talk inside me for six years. The Composer pulled it out in two weeks. I delivered it at SXSW four months later.",
  name:  "Aaron Reyes",
  title: "Founder, Northwind Labs",
};

// ─── SVG Icons (Lucide line icons, 1.5 stroke) ───────────────────────────────

const ICONS = {
  anchor: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="5" r="2"/><path d="M12 22V7"/><path d="M5 12H2a10 10 0 0 0 20 0h-3"/><path d="M8 11h8"/>
    </svg>
  ),
  lightbulb: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>
    </svg>
  ),
  user: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/>
    </svg>
  ),
  layers: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="m12 3 9 5-9 5-9-5 9-5z"/><path d="m3 13 9 5 9-5"/><path d="m3 18 9 5 9-5" opacity=".55"/>
    </svg>
  ),
  repeat: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/>
    </svg>
  ),
  sunrise: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M17 18a5 5 0 0 0-10 0"/><path d="M12 2v7"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 6-4-4-4 4"/>
    </svg>
  ),
  send: (
    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4 20-7Z"/>
    </svg>
  ),
  pen: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M21.17 6.83 17.17 2.83a2 2 0 0 0-2.83 0L3 14.17V21h6.83L21.17 9.66a2 2 0 0 0 0-2.83Z"/><path d="m15 5 4 4"/>
    </svg>
  ),
  cards: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="6" width="14" height="14" rx="2"/><path d="M7 3h12a2 2 0 0 1 2 2v12"/>
    </svg>
  ),
  slides: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="4" width="18" height="13" rx="2"/><path d="M9 21h6"/><path d="M12 17v4"/>
    </svg>
  ),
  mic: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v3"/><path d="M8 21h8"/>
    </svg>
  ),
  arrowRight: (
    <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
    </svg>
  ),
  check: (
    <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M20 6 9 17l-5-5"/>
    </svg>
  ),
  chevronRight: (
    <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="m9 6 6 6-6 6"/>
    </svg>
  ),
  chevronLeft: (
    <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="m15 6-6 6 6 6"/>
    </svg>
  ),
  scroll: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/>
    </svg>
  ),
  badge: (
    <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><circle cx="12" cy="14" r="2"/><path d="M8 21v-1a4 4 0 0 1 8 0v1"/>
    </svg>
  ),
  edit3: (
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4Z"/>
    </svg>
  ),
  download: (
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>
    </svg>
  ),
  sparkles: (
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"/>
    </svg>
  ),
  copy: (
    <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
    </svg>
  ),
};

// ─── AI system prompts ────────────────────────────────────────────────────────

const ST = "STYLE: Max 4 lines per response. Direct, warm. No bullets. Like a text message. Every 3 exchanges naturally remind them to book a Talk Review Call with Dolores at " + CTA + " — after saving, when stuck, or after a strong draft. Never salesy." +
" CRITICAL — YOU ARE A CO-WRITER, NOT JUST A COACH: Once you have enough context (business, audience, topic), immediately propose actual language. Write draft lines, offer 2-3 options with different angles, and invite the speaker to react. Do not keep asking questions when you have enough to write something. The pattern is: gather minimal context, then WRITE, then refine together." +
" After every 1-2 questions, offer a concrete draft or 2 options and ask 'which of these feels more like you?' or 'read this out loud — does it sound like you?' NEVER give pure guidance without also proposing language.";

const PROMPTS = [
  // Step 1 — Hook
  ST + " You are coaching Step 1: THE HOOK. Your job: help them stop the internal chatter in the room within 60 seconds. The audience's first thought is 'do I care about what this person has to say?' — everything in this step answers that question for them." +
  " RULES: Present tense ONLY. If you see past tense, rewrite it immediately before anything else. Minimum context, straight to the crux. Zero pleasantries, zero self-introduction in the hook." +
  " TWO STRATEGIES: (1) Resonant questions — ask the room something they can answer in their head that creates a common-denominator experience. (2) Present-tense story — open mid-scene, sensory and specific, get to the crux fast. Can combine both." +
  " STORY HOOK RULES — critical: The speaker must be a PARTICIPANT in the scene, not a narrator watching from outside. They are IN the moment — sitting across from someone, standing in a room, on a call. The hook's job is to make them NEED to know what happens next. NEVER give the answer or resolution inside the hook." +
  " CRUX-FIRST SEQUENCING — the most important rule: Open WITH the tension, not after it. The most compelling line — the question, the surprise, the jolt — goes FIRST. Context comes after, only as much as needed. Build the scene AROUND the crux, not up to it." +
  " BAD (builds up to the crux): 'She's standing in front of 40 people. Her slides are polished. The room is nodding. She walks off and waits. Three weeks later — zero clients. She calls me and asks: what did I miss?' — The crux is last. The audience almost left before getting there." +
  " GOOD (opens with the crux): '\"What did I miss?\" Three weeks. Forty small business owners. A room full of nodding heads. Zero clients.' — The question lands first. Now they need to know the answer." +
  " BAD STORY (narrator, gives away answer): 'I am watching a client finish her presentation. The room is nodding. Two weeks later — nothing. I know exactly what went wrong.' — Speaker is outside the scene and hands over the resolution." +
  " BAD: 'My name is X and today I want to talk about...' / 'I had spent years struggling...' / 'Thank you so much for having me'" +
  " GOOD resonant question: 'How many of you have walked off stage feeling like you nailed it — and two weeks later, not a single new client? Raise your hand.'" +
  " START by asking: what is their business and who is their audience? After ONE follow-up question about their audience's pain, write 2 draft hook options — one resonant question version and one present-tense story version — and ask which feels more like them. Then refine together." +
  " When the hook is solid, say: Type SAVE alone to lock in this section.",

  // Step 2 — Main Idea
  ST + " You are coaching Step 2: THE MAIN IDEA. Your job: help them distill their life's work into one clear sentence the audience can hold onto forever. Under 30 seconds to deliver." +
  " RULES: One sentence only. No 'and' splitting it into two ideas. No jargon — a 12-year-old must be able to repeat it. Challenge every abstract noun ('transformation,' 'empowerment,' 'alignment') immediately — ask what it actually means." +
  " TWO FRAMES: (1) 'I guide [who] to [action] so that [outcome]' — example: 'I guide entrepreneurs to clarity of message so their ideas can grow wings.' (2) 'I believe that [bold claim]' — example: 'I believe that when you have a clear message, you can engage any audience.'" +
  " The deeper prep question: what question has been driving their work for years? The core idea is usually the answer to that question." +
  " BAD: 'I help people with their mindset and communication so they can achieve their goals and step into their potential' (jargon + two ideas + no specifics)" +
  " GOOD: 'I guide first-generation executives to speak with the authority they already have.' (who + what + specific outcome)" +
  " START by asking: what is the ONE thing they want the audience to walk away knowing? Then immediately draft 2 options — one using 'I guide [who] to [what] so that [outcome]' and one using 'I believe...' — using their actual language. Ask which one they'd say out loud." +
  " When the main idea is tight and jargon-free, say: Type SAVE alone to lock in this section.",

  // Step 3 — About You
  ST + " You are coaching Step 3: ABOUT YOU. Your job: help them make the audience feel SAFE — not impressed. The audience trusts the message only after they trust the messenger. This step is not optional." +
  " RULES: No resume recitation. No listing credentials. Find one specific moment or story that earns them the right to be in the room, and weave their background into that story. The credentials emerge from the narrative — they are never announced." +
  " TWO TYPES of credibility: (1) Academic/professional — required for medical, scientific, legal topics where authority is critical. (2) Experiential/lived — for transformation, business, relationships. 'I have been where you are' is the most powerful credential there is." +
  " TECHNIQUE: Ask for one specific moment that contains their relevant background. Push past the list. 'Tell me that credential as if it happened yesterday.' A good About You ends with the audience thinking 'they've been where I am' or 'they've taken people like me exactly where I want to go.'" +
  " BAD: 'I have 15 years of experience and have been featured in Forbes and worked with Fortune 500 companies'" +
  " GOOD: 'Seven years ago I was the only woman at the table and I was terrified to open my mouth. That is why I now do this work.'" +
  " If they say 'I don't like talking about myself' — respond: this is not about preference, it is your responsibility to your audience. Let's find the story that makes it feel natural." +
  " START by asking: what experience or moment made them the right person to deliver this message?" +
  " When the introduction builds trust through story, say: Type SAVE alone to lock in this section.",

  // Step 4 — Three Key Points
  ST + " You are coaching Step 4: THREE KEY POINTS. This is the meat and potatoes — the core content they came to deliver. Steps 1-3 prepared the audience; now it is time to deliver." +
  " RULES: Every fact needs a story. Every data point needs a human attached. Head + heart at all times. Three distinct insights — not three angles on the same idea." +
  " FIRST QUESTION — always ask this before anything else: is this a free/awareness talk or a paid teaching engagement? The coaching differs completely." +
  " FREE TALK: Give three data points, each wrapped in a story or client result. Goal is curiosity and connection, not full instruction. Structure: insight or statistic + story that makes it real and relatable." +
  " PAID TALK: Teach the full framework or process. Go deep. But even here, every step of the teaching gets a client story or case study woven in. Teach AND build credibility simultaneously." +
  " EMOTIONAL AWARENESS: Ask what the audience will be feeling at the end of Step 4. Heavy data on a hard problem? They may feel hopeless — Step 5 must lift them. Teaching a framework? They should feel clarity and empowerment." +
  " BAD: Three abstract tips with no stories. Three points that are really sub-points of the same idea. Statistics without humans attached." +
  " GOOD: 'Point 1: Clarity is a decision, not a feeling. I worked with a CEO who rewrote her bio 11 times. We made one decision and she never changed it again.'" +
  " For every point the client writes, ask: where is the story? If there is no story, the point is not ready." +
  " When three distinct points each have a story attached, say: Type SAVE alone to lock in this section.",

  // Step 5 — Reinforce
  ST + " You are coaching Step 5: REINFORCE. Your job: help them echo the core idea from Step 2 so it lands as an arrival, not a repetition. This step transitions the audience from absorbing information to entering possibility." +
  " RULES: Under 75 words. Use Step 2's exact language — not a paraphrase, the actual words deepened by the journey. No new content. No summary of Step 4. Feels like a deep breath before turning toward the future." +
  " LANGUAGE PATTERNS: 'As I said at the beginning, I believe that...' / 'All of this comes back to one idea: [Step 2 core idea]' / 'We've journeyed together through this because [core idea]'" +
  " The test: does it feel like a landing or a departure? It must feel like landing." +
  " BAD: Summarizing all three key points. Introducing a new idea. Over 100 words. Generic: 'And so, in conclusion...'" +
  " GOOD: Short, grounded, uses Step 2's language, creates a moment of 'yes — that is exactly why we are here.'" +
  " START by asking: what was their core idea from Step 2? Get the exact words before writing anything." +
  " When the Reinforce echoes Step 2 and feels like an arrival, say: Type SAVE alone to lock in this section.",

  // Step 6 — New Reality
  ST + " You are coaching Step 6: NEW REALITY. Your job: begin the dreaming process for the audience before they leave the room. Plant the seed of what becomes possible when they apply the idea." +
  " CORE PRINCIPLE: The audience has been absorbing your talk. Their brains have been processing — they have not had space to dream. You start that dreaming for them while you still have them. By the time they walk out, the vision goes with them." +
  " RULES: Vivid and cinematic — the listener should be able to see themselves in it. Specific to the actual audience, not generic. Sensory, not abstract. Connect to the audience's real desired outcome." +
  " LANGUAGE PATTERNS: 'Imagine...' / 'What becomes possible when...' / 'Picture your life where...' / 'What would your world look like if...' / 'The future could look like this, if...'" +
  " BAD: 'You will feel more confident and successful' (vague, generic, tells not shows)" +
  " GOOD: 'Imagine standing at the front of the room and feeling the silence shift. Not because you said something perfect — because they felt something true.'" +
  " Push for specificity. 'More confident' is not cinematic. Where are they standing? What are they doing? What specifically is different about that moment?" +
  " Check that this step flows naturally into Step 7. The dream must set up the action." +
  " START by asking: what does life look like for the audience AFTER they apply this idea?" +
  " When the New Reality is vivid and specific enough to feel real, say: Type SAVE alone to lock in this section.",

  // Step 7 — Call to Action
  ST + " You are coaching Step 7: CALL TO ACTION. Your job: help the speaker give THEIR audience one clear, low-friction, emotionally connected next step toward working with THEM — not with Dolores or Masters in Clarity." +
  " RULES: ONE action only. No menu of options — the more choices, the less likely any happen. Flows naturally from Step 6's dream. Warm invitation, never a pitch. Doable immediately or within 24 hours." +
  " The CTA must be the speaker's own offer: booking a call with them, joining their program, signing up for their list, downloading their resource — whatever their next step is. Ask what it is if not clear from prior steps." +
  " LANGUAGE PATTERNS: 'If what I just described sounds like where you want to go, the simplest next step is...' / 'Take out your phone right now and...' / 'One thing I want you to do when you walk out of here is...'" +
  " BAD: Multiple options. Disconnected from Step 6. Starting with price or logistics. Apologetic: 'Only if it feels right, maybe you could consider...')" +
  " GOOD: 'If you are ready to [transformation from Step 6], [speaker's specific next step]. It takes [time] and it changes everything.'" +
  " Check that the CTA flows directly from the image painted in Step 6 — they should feel like one continuous movement, not two separate moments." +
  " START by asking: what is the ONE action they want the audience to take, and what does the audience get when they do it?" +
  " When the CTA is one clear action that flows from Step 6, say: Type SAVE alone to lock in this section.",
];

// ─── Firebase ─────────────────────────────────────────────────────────────────
const FIREBASE_CONFIG = {
  apiKey:            "AIzaSyCSH6i-LCtnPpkEjeLeSPVSzCNeGA8La-o",
  authDomain:        "wyt-ai-coach.firebaseapp.com",
  projectId:         "wyt-ai-coach",
  storageBucket:     "wyt-ai-coach.firebasestorage.app",
  messagingSenderId: "350669024141",
  appId:             "1:350669024141:web:6f3fbbe76f3cf0614a6bc4",
};

firebase.initializeApp(FIREBASE_CONFIG);
const auth = firebase.auth();
const db   = firebase.firestore();

const EMAIL_KEY = "wyt_signin_email";
const LS        = "stc_v4";

const load = () => { try { return JSON.parse(localStorage.getItem(LS)||"{}"); } catch { return {}; } };

function talkDuration(sc) {
  const words = sc.filter(s => s?.trim()).join(" ").split(/\s+/).filter(Boolean).length;
  return words / WPM;
}

// ─── AI helper ────────────────────────────────────────────────────────────────
// Always goes through the server-side proxy (api/chat.js) — the Anthropic
// API key never touches the browser. Run `vercel dev` locally (not Live
// Server) when testing AI features, so api/chat.js actually runs.
async function ai(messages, system, maxTokens = 300) {
  if (!auth.currentUser) return "Please sign in again.";
  const idToken = await auth.currentUser.getIdToken();
  const res = await fetch("/api/chat", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${idToken}`,
    },
    body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: maxTokens, system, messages }),
  });
  if (!res.ok) {
    if (res.status === 403) return "This tool is invite-only. Contact Dolores for access.";
    return "Something went wrong. Please try again.";
  }
  const d = await res.json();
  return d.content?.[0]?.text || "Something went wrong. Try again.";
}

// ─── Download helpers ────────────────────────────────────────────────────────

function dlTalk(sc, transitions = []) {
  const parts = STEPS.map((s, i) => {
    const text = sc[i]?.trim();
    if (!text) return "";
    const section = `<h2 style="color:#E36A2C;font-family:'Georgia',serif;margin-top:36px">${s.id}. ${s.title}</h2><p style="font-family:'Georgia',serif;font-size:16px;line-height:1.9;color:#1A1614">${text.replace(/\n/g,"<br/>")}</p>`;
    const trans = transitions[i] ? `<p style="font-family:'Georgia',serif;font-size:14px;font-style:italic;color:#8a7a6e;text-align:center;margin:20px 0;letter-spacing:0.02em">— ${transitions[i]} —</p>` : "";
    return section + trans;
  }).join("");
  const html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>My Signature Talk</title></head><body style="max-width:720px;margin:40px auto;padding:0 32px;background:#FAF7F2"><h1 style="font-family:'Georgia',serif;border-bottom:2px solid #E36A2C;padding-bottom:12px;color:#1A1614">My Signature Talk</h1><p style="font-family:system-ui,sans-serif;color:#6B5F57;font-size:12px;letter-spacing:0.1em;text-transform:uppercase">Masters in Clarity · Signature Talk Composer</p>${parts}<hr style="border:none;border-top:1px solid #E8E1D6;margin-top:40px"/><p style="font-family:system-ui,sans-serif;font-size:11px;color:#8B8279;letter-spacing:0.08em">mastersinclarity.com</p></body></html>`;
  const a = document.createElement("a");
  a.href = URL.createObjectURL(new Blob([html], { type:"application/msword" }));
  a.download = "My-Signature-Talk.doc"; a.click();
}

function dlSheet(sheet, info = {}) {
  const e = s => (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  const takeaways = (sheet.takeaways || []).map(t =>
    `<li style="margin-bottom:10px;line-height:1.6">${e(t)}</li>`).join("");
  const photoHtml = info.photo
    ? `<img src="${info.photo}" style="width:220px;height:260px;object-fit:cover;border-radius:4px;flex-shrink:0"/>`
    : `<div style="width:220px;height:260px;background:#e0d8cc;border-radius:4px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:#8a7a6e;font-size:12px;text-align:center;padding:16px">Add your photo here</div>`;
  const html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Speaker One Sheet</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{font-family:'Helvetica Neue',Arial,sans-serif;background:#fff;color:#1a1614;width:816px;min-height:1056px}
  .top{padding:52px 56px 40px;background:#f8f4ef;display:flex;gap:40px;align-items:flex-start}
  .top-left{flex:1}
  .eyebrow{font-size:10px;font-weight:700;letter-spacing:0.18em;text-transform:uppercase;color:#E36A2C;margin-bottom:10px}
  .speaker-name{font-family:Georgia,serif;font-size:48px;font-weight:700;line-height:1.05;color:#1a1614;margin-bottom:10px}
  .tagline{font-size:13px;font-weight:600;letter-spacing:0.12em;text-transform:uppercase;color:#6b5f57;margin-bottom:20px}
  .bio{font-size:14px;line-height:1.8;color:#3a3028;max-width:440px}
  .mid{display:grid;grid-template-columns:1fr 1fr}
  .topic-col{background:#C4604F;padding:40px 44px;color:#fff}
  .topic-col h2{font-size:11px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;margin-bottom:20px;opacity:0.85}
  .talk-title{font-family:Georgia,serif;font-size:24px;font-style:italic;line-height:1.35;margin-bottom:16px}
  .talk-desc{font-size:13px;line-height:1.75;opacity:0.9}
  .takeaway-col{background:#2a2421;padding:40px 44px;color:#fff}
  .takeaway-col h2{font-size:11px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;margin-bottom:20px;color:#E36A2C}
  .takeaway-col ul{list-style:none;padding:0}
  .takeaway-col ul li{font-size:13px;line-height:1.6;margin-bottom:10px;padding-left:18px;position:relative;opacity:0.88}
  .takeaway-col ul li::before{content:"→";position:absolute;left:0;color:#E36A2C;font-weight:700}
  .bot{background:#1a1614;padding:24px 56px;display:flex;align-items:center;justify-content:space-between}
  .bot-cta{font-family:Georgia,serif;font-size:18px;font-style:italic;color:#f8f4ef}
  .bot-contact{font-size:11px;color:#8a7a6e;letter-spacing:0.10em;text-align:right;line-height:1.8}
  .watermark{text-align:center;padding:14px;font-size:10px;color:#c0b8b0;letter-spacing:0.12em;text-transform:uppercase}
</style></head><body>
<div class="top">
  <div class="top-left">
    <div class="eyebrow">Speaker One Sheet</div>
    <div class="speaker-name">${e(info.name || "[Your Name]")}</div>
    <div class="tagline">${e(info.title || "[Your Title / Credentials]")}</div>
    <div class="bio">${e(info.bio || "[Add your bio here — 3-4 sentences about your background, expertise, and why you're the right person to deliver this message.]")}</div>
  </div>
  ${photoHtml}
</div>
<div class="mid">
  <div class="topic-col">
    <h2>Signature Talk</h2>
    <div class="talk-title">${e(sheet.title)}</div>
    <div class="talk-desc">${e(sheet.description)}</div>
  </div>
  <div class="takeaway-col">
    <h2>Your Audience Will Walk Away With</h2>
    <ul>${takeaways}</ul>
  </div>
</div>
<div class="bot">
  <div class="bot-cta">Book ${e(info.name || "[Your Name]")} for your next event</div>
  <div class="bot-contact">${info.email ? e(info.email)+"<br/>" : ""}${info.website ? e(info.website) : ""}</div>
</div>
<div class="watermark">Built with Masters in Clarity · Signature Talk Composer · mastersinclarity.com</div>
</body></html>`;
  const a = document.createElement("a");
  a.href = URL.createObjectURL(new Blob([html], { type:"text/html" }));
  a.download = "Speaker-One-Sheet.html"; a.click();
}

function dlSlides(slides) {
  const e = s => (s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  const html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Slides</title><style>body{margin:0;background:#1A1614;font-family:'Georgia',serif}.slide{width:900px;min-height:480px;margin:20px auto;padding:56px 64px;box-sizing:border-box;position:relative;overflow:hidden}.slide:nth-child(odd){background:#1A1614}.slide:nth-child(even){background:#2A2421}.emoji{position:absolute;top:36px;right:52px;font-size:64px;line-height:1;opacity:.80}.num{position:absolute;bottom:24px;right:36px;font-size:10px;color:rgba(250,247,242,.25);font-weight:600;letter-spacing:3px;font-family:system-ui,sans-serif}.keyword{font-size:54px;font-weight:600;color:#FAF7F2;line-height:1;margin-bottom:0;max-width:76%}.rule{height:1.5px;width:28px;background:#E36A2C;margin:22px 0 20px;border-radius:2px}.title{font-size:22px;font-weight:400;color:#FAF7F2;line-height:1.4;margin-bottom:12px;font-style:italic;max-width:76%}.body{font-size:15px;color:rgba(250,247,242,.65);line-height:1.85;font-family:system-ui,sans-serif;max-width:76%}.footer{text-align:center;color:#6B5F57;font-size:11px;padding:28px;font-family:system-ui,sans-serif;letter-spacing:0.1em}@media print{.slide{page-break-after:always}}</style></head><body>${slides.map((s,i)=>`<div class="slide">${s.emoji?`<div class="emoji">${s.emoji}</div>`:""}<div class="num">SLIDE ${String(i+1).padStart(2,"0")} / ${String(slides.length).padStart(2,"0")}</div>${s.keyword?`<div class="keyword">${e(s.keyword)}</div>`:""}<div class="rule"></div><div class="title">${e(s.title)}</div><div class="body">${e(s.body)}</div></div>`).join("")}<div class="footer">MASTERS IN CLARITY · MASTERSINCLARITY.COM</div></body></html>`;
  const a = document.createElement("a");
  a.href = URL.createObjectURL(new Blob([html],{type:"text/html"}));
  a.download = "Signature-Talk-Slides.html"; a.click();
}

function dlSlidesPptx(slides) {
  const pptx = new PptxGenJS();
  pptx.layout = "LAYOUT_WIDE";
  pptx.title  = "Signature Talk — Masters in Clarity";

  slides.forEach((s, i) => {
    const slide = pptx.addSlide();
    slide.background = { color: "1a2744" };

    // Emoji — faded right background
    if (s.emoji) {
      slide.addText(s.emoji, {
        x: 7.5, y: 0.5, w: 5.3, h: 5.5,
        fontSize: 180, valign: "middle", align: "center",
        transparency: 75,
      });
    }

    // Keyword — huge, coral
    slide.addText(s.keyword || s.title, {
      x: 0.5, y: 0.35, w: 7.5, h: 2.2,
      fontSize: 88, bold: true, color: "c4604f",
      fontFace: "Georgia", valign: "middle",
    });

    // Coral rule
    slide.addShape(pptx.ShapeType.rect, {
      x: 0.5, y: 2.7, w: 0.7, h: 0.05, fill: { color: "c4604f" }, line: { color: "c4604f" },
    });

    // Title — italic, off-white
    slide.addText(s.title, {
      x: 0.5, y: 2.85, w: 7.5, h: 0.9,
      fontSize: 26, italic: true, color: "f8f4ef",
      fontFace: "Georgia",
    });

    // Body — muted
    slide.addText(s.body, {
      x: 0.5, y: 3.9, w: 7.5, h: 2.2,
      fontSize: 17, color: "b8b0a5",
      fontFace: "Helvetica Neue", lineSpacingMultiple: 1.4,
    });

    // Slide counter + brand
    slide.addText(`${String(i + 1).padStart(2,"0")} / ${String(slides.length).padStart(2,"0")}  ·  MASTERS IN CLARITY`, {
      x: 0.5, y: 6.85, w: 12.3, h: 0.3,
      fontSize: 9, color: "6b6058", fontFace: "Helvetica Neue",
      charSpacing: 2,
    });
  });

  pptx.writeFile({ fileName: "Signature-Talk-Slides.pptx" });
}

// ─── Logo ────────────────────────────────────────────────────────────────────

function Logo({ height = 32 }) {
  const [err, setErr] = useState(false);
  if (!err) return (
    <img
      src="assets/logo-full.png"
      alt="Masters in Clarity"
      onError={() => setErr(true)}
      style={{ height, width:"auto", display:"block" }}
    />
  );
  return (
    <div className="product-brand-fallback">
      <svg width="34" height="34" viewBox="0 0 44 44" fill="none">
        <defs><linearGradient id="lg" x1="0" y1="0" x2="44" y2="44" gradientUnits="userSpaceOnUse">
          <stop offset="0%" stopColor="#E74F2B"/><stop offset="100%" stopColor="#F89A1F"/>
        </linearGradient></defs>
        <circle cx="22" cy="22" r="19.5" stroke="url(#lg)" strokeWidth="2" fill="none"/>
        <path d="M11 31C12 31 13 16 16 21C18 25 19 18 22 13C25 18 26 25 28 21C31 16 32 31 33 31"
          stroke="url(#lg)" strokeWidth="2.2" strokeLinecap="round" fill="none"/>
      </svg>
      <div>
        <div className="brand-text">MASTERS IN CLARITY</div>
        <div className="brand-sub">Signature Talk Composer</div>
      </div>
    </div>
  );
}

// ─── Landing Page ─────────────────────────────────────────────────────────────

function LandingPage({ onSignIn }) {
  const [email, setEmail]       = useState("");
  const [submitted, setSubmitted] = useState(false);
  const [busy, setBusy]         = useState(false);
  const [err, setErr]           = useState("");

  async function handleSubmit(e) {
    e.preventDefault();
    const addr = email.trim().toLowerCase();
    if (!addr || busy) return;
    setBusy(true); setErr("");
    try {
      const check = await fetch("/api/check-invite", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: addr }),
      }).then(r => r.json()).catch(() => ({ allowed: false }));
      if (!check.allowed) {
        setErr("This tool is invite-only right now. Contact Dolores for access.");
        setBusy(false);
        return;
      }
      await auth.sendSignInLinkToEmail(addr, {
        url: window.location.origin,
        handleCodeInApp: true,
      });
      localStorage.setItem(EMAIL_KEY, addr);
      setSubmitted(true);
    } catch(ex) {
      setErr(ex.message);
    }
    setBusy(false);
  }

  return (
    <div className="landing-root la-root">
      {/* Nav */}
      <div className="la-nav">
        <div className="la-shell la-nav-inner">
          <Logo height={30} />
          <div className="la-nav-links">
            <a href="#what-you-get">What you get</a>
            <a href={CTA} target="_blank" rel="noopener noreferrer">Book a call</a>
            <button
              className="wy-btn outline sm"
              type="button"
              onClick={onSignIn}
            >
              Sign in
            </button>
          </div>
        </div>
      </div>

      {/* Hero */}
      <section className="la-shell la-hero">
        <span className="la-eyebrow">A Masters in Clarity tool</span>
        <h1 className="la-h1">
          Write the talk you've had in you <em>for years</em><span className="gold">.</span>
        </h1>
        <p className="la-lede">
          Seven steps. Your own words. An advisor at your shoulder. The Signature Talk Composer turns the talk you keep almost giving into the one you can deliver from any stage.
        </p>

        {submitted ? (
          <div className="la-success">
            Check your inbox — we sent a sign-in link to <strong>{email}</strong>.
          </div>
        ) : (
          <form className="la-capture" onSubmit={handleSubmit}>
            <input
              type="email"
              required
              placeholder="your@email.com"
              value={email}
              onChange={e => setEmail(e.target.value)}
              autoFocus
            />
            <button className="wy-btn primary" type="submit" disabled={busy}>
              {busy ? "Sending…" : <>{ICONS.arrowRight} Begin</>}
            </button>
          </form>
        )}
        {err && <div style={{ color:"var(--ember)", fontSize:12, marginTop:10, textAlign:"center" }}>{err}</div>}
        <div className="la-meta">No password. We'll send a sign-in link to your inbox.</div>

        <div className="la-trust">
          <span><strong>2,000+</strong> founders coached</span>
          <span><strong>500+</strong> talks delivered</span>
          <span><strong>10+ years</strong> of TEDx pedigree</span>
        </div>
      </section>

      {/* Divider */}
      <div className="la-divider">
        <span className="rule"></span>
        <span>What you get</span>
        <span className="rule"></span>
      </div>

      {/* What you get */}
      <section className="la-shell la-what" id="what-you-get">
        <div className="la-section-head">
          <h2>Four tools. <em>One workshop.</em></h2>
          <p>From the first draft to the final delivery, every part of the talk lives in one place — and learns from the last.</p>
        </div>
        <div className="la-grid">
          <div className="la-card">
            <span className="ord">Tool 01</span>
            <div className="glyph">{ICONS.pen}</div>
            <h4>Compose</h4>
            <p>A 7-step conversation with an AI coach trained on Dolores' method. You answer. It clarifies. You save.</p>
          </div>
          <div className="la-card">
            <span className="ord">Tool 02</span>
            <div className="glyph">{ICONS.cards}</div>
            <h4>Flashcards</h4>
            <p>Ten cue cards drawn from your saved sections. Tap to flip. Drill until your talk lives in your body.</p>
          </div>
          <div className="la-card">
            <span className="ord">Tool 03</span>
            <div className="glyph">{ICONS.slides}</div>
            <h4>Slides</h4>
            <p>Seven Ignite-style slides — one idea each. Editorial typography. Downloadable to your stage.</p>
          </div>
          <div className="la-card">
            <span className="ord">Tool 04</span>
            <div className="glyph">{ICONS.mic}</div>
            <h4>Rehearse</h4>
            <p>Deliver any section out loud. Get sharp, kind feedback from your AI coach — within seconds.</p>
          </div>
        </div>
      </section>

      {/* The Method */}
      <section className="la-process">
        <div className="la-shell">
          <div className="la-section-head">
            <span className="la-eyebrow" style={{ justifyContent:"center" }}>The method</span>
            <h2>Seven steps. <em>One throughline.</em></h2>
          </div>
          <div className="la-steps">
            {STEPS.map(s => (
              <div className="la-step" key={s.id}>
                <div className="n">{String(s.id).padStart(2,"0")}</div>
                <div className="t">{s.title}</div>
                <div className="d">{s.tagline}</div>
              </div>
            ))}
          </div>
        </div>
      </section>

      {/* Quote */}
      <section className="la-shell la-quote">
        <blockquote>{TESTIMONIAL.quote}</blockquote>
        <div className="la-who">
          {TESTIMONIAL.name}
          <span>{TESTIMONIAL.title}</span>
        </div>
      </section>

      {/* Closer */}
      <section className="la-closer">
        <div className="la-shell">
          <h2>Your talk is <em>waiting</em>.</h2>
          <p>Begin today. The first three sections take less than thirty minutes — most clients save them in one sitting.</p>
          <button className="wy-btn inverse" type="button" onClick={onSignIn}>
            Sign in or sign up {ICONS.arrowRight}
          </button>
          <div className="la-foot">
            <div>© Masters in Clarity · mastersinclarity.com</div>
            <div>Privacy · Terms · Contact</div>
          </div>
        </div>
      </section>
    </div>
  );
}

// ─── Magic Link Screen (sign-in completion) ───────────────────────────────────

function MagicLinkScreen() {
  const [email, setEmail]       = useState("");
  const [needEmail, setNeedEmail] = useState(false);
  const [busy, setBusy]         = useState(false);
  const [err, setErr]           = useState("");

  useEffect(() => {
    const saved = localStorage.getItem(EMAIL_KEY);
    if (saved) {
      finishSignIn(saved);
    } else {
      setNeedEmail(true);
    }
  }, []);

  async function finishSignIn(addr) {
    const target = (typeof addr === "string" ? addr : email).trim().toLowerCase();
    if (!target) return;
    setBusy(true); setErr("");
    try {
      await auth.signInWithEmailLink(target, window.location.href);
      localStorage.removeItem(EMAIL_KEY);
      window.history.replaceState({}, document.title, window.location.pathname);
    } catch {
      setErr("Link expired or already used. Request a new one.");
      setNeedEmail(true);
    }
    setBusy(false);
  }

  if (!needEmail) {
    return (
      <div className="loading-screen">
        <span>Signing you in…</span>
      </div>
    );
  }

  return (
    <div className="magic-wrap" style={{ "--p-accent":"#E36A2C" }}>
      <div className="magic-card">
        <Logo />
        <h2>Confirm your email</h2>
        <p>You're signing in from a new device. Enter your email to continue.</p>
        {err && <div className="err">{err}</div>}
        <input
          value={email}
          onChange={e => setEmail(e.target.value)}
          placeholder="your@email.com"
          type="email"
          autoFocus
        />
        <button
          className="wy-btn primary"
          style={{ width:"100%", justifyContent:"center" }}
          onClick={() => finishSignIn()}
          disabled={busy || !email.trim()}
        >
          {busy ? "Signing in…" : "Complete sign-in"}
        </button>
      </div>
    </div>
  );
}

// ─── Save indicator ───────────────────────────────────────────────────────────

function SaveIndicator({ state, lastSavedAt }) {
  function label() {
    if (state === "saving") return "Saving…";
    if (state === "error")  return "Offline — will sync";
    if (!lastSavedAt)       return "Saved";
    const diff = Math.floor((Date.now() - lastSavedAt) / 1000);
    if (diff < 5)   return "Saved · just now";
    if (diff < 60)  return `Saved · ${diff}s ago`;
    if (diff < 3600) return `Saved · ${Math.floor(diff/60)}m ago`;
    return "Saved";
  }
  return (
    <span className="save-indicator" data-state={state}>
      <span className="dot"></span>
      {label()}
    </span>
  );
}

// ─── Top Nav ──────────────────────────────────────────────────────────────────

function TopNav({ user, view, setView, saveState, lastSavedAt, onSignOut, onNewTalk }) {
  const [overflow, setOverflow] = useState(false);
  const overflowRef = useRef();

  useEffect(() => {
    if (!overflow) return;
    function close(e) {
      if (!overflowRef.current?.contains(e.target)) setOverflow(false);
    }
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [overflow]);

  const tabs = [
    { id:"compose",    label:"Compose",    icon: ICONS.pen    },
    { id:"flashcards", label:"Flashcards", icon: ICONS.cards  },
    { id:"slides",     label:"Slides",     icon: ICONS.slides },
    { id:"yourtalk",   label:"Your Talk",  icon: ICONS.scroll },
    { id:"onesheet",   label:"One Sheet",  icon: ICONS.badge  },
  ];

  return (
    <header className="product-topnav">
      <div className="product-shell product-topnav-inner">
        <div className="product-brand">
          <Logo height={32} />
        </div>
        <div className="product-topnav-right">
          <SaveIndicator state={saveState} lastSavedAt={lastSavedAt} />
          <button
            className="wy-btn ghost sm start-new-btn"
            type="button"
            onClick={onNewTalk}
            title="Erase current talk and start fresh"
          >
            + Start New Talk
          </button>
          <div className="user-pill" ref={overflowRef}>
            <span className="email">{user.email}</span>
            <button
              className="overflow-btn"
              type="button"
              onClick={() => setOverflow(o => !o)}
              aria-label="More options"
            >
              ···
            </button>
            {overflow && (
              <div className="overflow-menu">
                <button type="button" className="danger" onClick={() => { setOverflow(false); onNewTalk(); }}>
                  Start a new talk…
                </button>
                <button type="button" onClick={() => { setOverflow(false); onSignOut(); }}>
                  Sign out
                </button>
              </div>
            )}
          </div>
          <a className="wy-btn outline sm" href={CTA} target="_blank" rel="noopener noreferrer">
            Book a Call
          </a>
        </div>
      </div>
      <div className="product-shell">
        <nav className="product-tabs" role="tablist">
          {tabs.map(t => (
            <button
              key={t.id}
              role="tab"
              aria-pressed={view === t.id ? "true" : "false"}
              className="product-tab"
              type="button"
              onClick={() => setView(t.id)}
            >
              {t.icon} {t.label}
            </button>
          ))}
        </nav>
      </div>
    </header>
  );
}

// ─── Step Card ────────────────────────────────────────────────────────────────

function StepCard({ step, done, current, onClick }) {
  return (
    <button
      className="step-card"
      type="button"
      onClick={onClick}
      aria-current={current ? "true" : "false"}
      data-done={done ? "true" : "false"}
    >
      <span className="icon-slot">
        {ICONS[step.iconKey]}
      </span>
      <span className="step-body">
        <span className="step-eyebrow">Step {String(step.id).padStart(2,"0")}</span>
        <span className="step-title">{step.title}</span>
        <span className="step-tag">{step.tagline}</span>
      </span>
      <span className="step-trail">
        {done
          ? <>{ICONS.check} Saved</>
          : current
            ? <>Open {ICONS.chevronRight}</>
            : ICONS.chevronRight
        }
      </span>
    </button>
  );
}

// ─── Bubble ───────────────────────────────────────────────────────────────────

function Bubble({ m }) {
  return <div className={`bubble ${m.role}`}>{m.content}</div>;
}

// ─── Manuscript (saved-text view) ────────────────────────────────────────────

function Manuscript({ step, text, onRefine }) {
  function copyText() {
    navigator.clipboard?.writeText(text).catch(() => {});
  }
  return (
    <div className="manuscript">
      <div className="ms-eyebrow">
        {ICONS.check} Saved · Step {String(step.id).padStart(2,"0")} · {step.title}
      </div>
      {text.split("\n\n").map((para, i) => (
        <p key={i}>{para}</p>
      ))}
      <div className="ms-actions">
        <button className="wy-btn outline sm" type="button" onClick={() => window.open(CTA, "_blank")}>
          {ICONS.edit3} Refine with coach
        </button>
        <button className="wy-btn ghost sm" type="button" onClick={copyText}>
          {ICONS.copy} Copy text
        </button>
      </div>
    </div>
  );
}

// ─── Compose Tab ─────────────────────────────────────────────────────────────

function ComposeTab({ sc, hist, active, loading, inp, refining, done,
                      setActive, setInp, send, openStep, onRefine, onCloseRefine, onDownload }) {
  const cb = useRef();
  useEffect(() => {
    const el = cb.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [hist, active, loading]);

  const step = active !== null ? STEPS[active] : null;
  const msgs = active !== null ? hist[active] : [];
  const savedText = active !== null ? sc[active] : "";
  const showManuscript = savedText?.trim() && !refining;

  return (
    <div className="compose">
      {/* Left: step list */}
      <aside className="step-list" aria-label="Talk sections">
        <div className="step-list-head">
          <h3>Your Talk</h3>
          <span>{done}/7 saved</span>
        </div>
        {STEPS.map((s, i) => (
          <StepCard
            key={s.id}
            step={s}
            done={!!sc[i]?.trim()}
            current={active === i}
            onClick={() => openStep(i)}
          />
        ))}
        {done === 7 && (
          <button className="wy-btn outline sm" type="button" style={{ width:"100%", marginTop:12, justifyContent:"center" }} onClick={onDownload}>
            {ICONS.download} Download talk
          </button>
        )}
      </aside>

      {/* Right: canvas */}
      {step ? (
        <div className="canvas">
          <div className="canvas-header">
            <div>
              <div className="crumb">
                Step {String(step.id).padStart(2,"0")} · {savedText?.trim() ? "Saved draft" : "In progress"}
              </div>
              <h1>{step.title} <em>· {step.tagline}</em></h1>
            </div>
            {refining && (
              <div className="actions">
                <button className="wy-btn ghost sm" type="button" onClick={onCloseRefine}>
                  Back to draft
                </button>
              </div>
            )}
          </div>

          {showManuscript ? (
            <Manuscript step={step} text={savedText} onRefine={onRefine} />
          ) : (
            <>
              <div className="chat-stream" ref={cb}>
                {msgs.map((m, i) => <Bubble key={i} m={m} />)}
                {loading && <div className="chat-thinking"><span/><span/><span/></div>}
              </div>
              {msgs.length === 0 && !loading && (
                <div className="canvas-empty" style={{ flex:"none", paddingTop:32 }}>
                  <div className="glyph">{ICONS[step.iconKey]}</div>
                  <h3>{step.title}</h3>
                  <p>{step.tagline} Your coach will ask one question to begin — answer in your own words.</p>
                </div>
              )}
              <div className="composer-row">
                <input
                  value={inp}
                  onChange={e => setInp(e.target.value)}
                  onKeyDown={e => e.key === "Enter" && !e.shiftKey && send()}
                  placeholder="Reply here… type SAVE alone to lock in this section"
                />
                <button className="wy-btn primary sm" type="button" onClick={send} disabled={loading}>
                  {ICONS.arrowRight} Send
                </button>
              </div>
              <span className="kbd-hint">
                Press <kbd>SAVE</kbd> alone to lock in this section · <kbd>↵</kbd> to send
              </span>
            </>
          )}
        </div>
      ) : (
        <div className="canvas">
          <div className="canvas-empty" style={{ flex:1 }}>
            <div className="glyph">{ICONS.pen}</div>
            <h3>Ready to write?</h3>
            <p>Select a step on the left to start building your talk with your AI coach.</p>
            {done > 0 && (
              <p style={{ fontSize:11, color:"var(--p-accent)", fontWeight:600, letterSpacing:"0.10em", textTransform:"uppercase", margin:0 }}>
                {ICONS.check} {done} step{done > 1 ? "s" : ""} saved
              </p>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Flashcards Tab ───────────────────────────────────────────────────────────

function FlashcardsTab({ sc, gen, err, done, onGenerate }) {
  const [ci, setCi]           = useState(0);
  const [flipped, setFlipped] = useState(false);

  const savedSteps = STEPS.filter(s => sc[s.id - 1]?.trim());

  useEffect(() => {
    setCi(0); setFlipped(false);
  }, [sc]);

  useEffect(() => {
    function onKey(e) {
      if (e.key === "ArrowRight" || e.key === "ArrowDown") {
        e.preventDefault(); setCi(c => Math.min(savedSteps.length - 1, c + 1)); setFlipped(false);
      }
      if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
        e.preventDefault(); setCi(c => Math.max(0, c - 1)); setFlipped(false);
      }
      if (e.key === " " || e.key === "Enter") { e.preventDefault(); setFlipped(f => !f); }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [savedSteps.length]);

  if (done === 0) {
    return (
      <div className="cards-wrap">
        <div className="cards-empty">
          <div className="glyph">{ICONS.cards}</div>
          <h3>Nothing saved yet</h3>
          <p>Complete at least one step in Compose to start rehearsing.</p>
        </div>
      </div>
    );
  }

  const step    = savedSteps[ci];
  const text    = step ? sc[step.id - 1] : "";
  const bullets = text.split(/\n+/).filter(l => l.trim()).slice(0, 5);

  return (
    <div className="rehearse-split">

      {/* ── Left: full talk outline ── */}
      <aside className="rehearse-outline">
        <div className="ro-header">
          <span>Your Talk</span>
          <span className="ro-count">{done} / 7 saved</span>
        </div>
        {STEPS.map((s, i) => {
          const saved   = !!sc[i]?.trim();
          const isActive = step && s.id === step.id;
          const idx     = savedSteps.findIndex(x => x.id === s.id);
          return (
            <div
              key={s.id}
              className={`ro-step${isActive ? " active" : ""}${!saved ? " dim" : ""}`}
              onClick={() => { if (idx >= 0) { setCi(idx); setFlipped(false); } }}
              style={{ cursor: saved ? "pointer" : "default" }}
            >
              <div className="ro-step-head">
                <span className="ro-num">{String(s.id).padStart(2,"0")}</span>
                <span className="ro-title">{s.title}</span>
                {saved && <span className="ro-saved-dot" />}
              </div>
              {saved && (
                <div className="ro-preview">
                  {sc[i].slice(0, 100)}{sc[i].length > 100 ? "…" : ""}
                </div>
              )}
            </div>
          );
        })}
      </aside>

      {/* ── Right: practice card ── */}
      <div className="rehearse-card-col">
        <div className="section-header" style={{ marginBottom: 20 }}>
          <div>
            <h2>Rehearse Your Talk</h2>
            <div className="subtitle">Step through each section. Flip to reveal your saved text.</div>
          </div>
        </div>

        {step ? (
          <>
            <div
              className="flashcard"
              data-flipped={flipped ? "true" : "false"}
              role="button"
              tabIndex={0}
              onClick={() => setFlipped(f => !f)}
              onKeyDown={e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setFlipped(f => !f); } }}
              aria-label={`Step ${step.id}: ${step.title}, ${flipped ? "showing saved text" : "showing cue"}`}
            >
              <div className="fc-eyebrow">
                {flipped ? `Step ${step.id} · ${step.title}` : `Step ${step.id} · Cue`}
              </div>
              {!flipped ? (
                <>
                  <div className="fc-text" style={{ fontFamily:"var(--font-display)", fontSize:26 }}>
                    {step.title}
                  </div>
                  <div style={{ fontStyle:"italic", color:"var(--ink-500)", fontSize:16, marginTop:8 }}>
                    {step.tagline}
                  </div>
                  <div style={{ marginTop:24, fontSize:13, color:"var(--ink-500)", letterSpacing:"0.06em" }}>
                    Deliver this section from memory, then flip to check.
                  </div>
                </>
              ) : (
                <div className="fc-text" style={{ fontSize:19, lineHeight:1.9, textAlign:"left", fontFamily:"var(--font-display)", color:"rgba(250,247,242,0.88)" }}>
                  {text}
                </div>
              )}
              <div className="fc-foot">{flipped ? "Tap to hide" : "Tap to reveal your saved text"}</div>
            </div>

            <div className="cards-controls">
              <button
                className="wy-btn outline sm"
                type="button"
                disabled={ci === 0}
                onClick={() => { setCi(c => Math.max(0, c - 1)); setFlipped(false); }}
              >
                {ICONS.chevronLeft} Prev
              </button>
              <div className="cards-meta">{ci + 1} / {savedSteps.length}</div>
              <button
                className="wy-btn outline sm"
                type="button"
                disabled={ci === savedSteps.length - 1}
                onClick={() => { setCi(c => Math.min(savedSteps.length - 1, c + 1)); setFlipped(false); }}
              >
                Next {ICONS.chevronRight}
              </button>
            </div>
          </>
        ) : (
          <div className="cards-empty">
            <p>No saved steps to rehearse yet.</p>
          </div>
        )}
        {err && <div style={{ color:"var(--ember)", fontSize:12, marginTop:10, textAlign:"center" }}>{err}</div>}
      </div>
    </div>
  );
}

// ─── Slides Tab ───────────────────────────────────────────────────────────────

function SlidesTab({ slides, gen, err, done, onGenerate, onDownload, onDownloadPptx }) {
  const [si, setSi] = useState(0);

  if (slides.length === 0) {
    return (
      <div className="slides-wrap">
        <div className="section-header">
          <div>
            <h2>Your Slide Deck</h2>
            <div className="subtitle">Seven slides. One throughline. Ignite-style — one idea per slide.</div>
          </div>
        </div>
        <div className="slides-empty">
          <div className="glyph">{ICONS.slides}</div>
          <h3>No slides yet</h3>
          <p>{done === 0 ? "Complete at least one step in Compose first." : "Ready — generate your slide deck below."}</p>
          <button className="wy-btn primary" type="button" onClick={onGenerate} disabled={gen || done === 0}>
            {gen ? "Generating…" : <>{ICONS.sparkles} Generate Slides</>}
          </button>
          {err && <div style={{ color:"var(--ember)", fontSize:12, marginTop:12 }}>{err}</div>}
        </div>
      </div>
    );
  }

  const cur = slides[si] || {};

  return (
    <div className="slides-wrap">
      <div className="section-header">
        <div>
          <h2>Your Slide Deck</h2>
          <div className="subtitle">Seven slides. One throughline. Ignite-style — one idea per slide.</div>
        </div>
        <div style={{ display:"flex", gap:8, alignItems:"center", flexWrap:"wrap" }}>
          <button className="wy-btn ghost sm" type="button" onClick={onGenerate} disabled={gen}>
            {gen ? "Regenerating…" : <>{ICONS.sparkles} Regenerate</>}
          </button>
          <button className="wy-btn primary sm" type="button" onClick={onDownloadPptx}>
            {ICONS.download} PowerPoint
          </button>
          <button className="wy-btn outline sm" type="button" onClick={onDownload}>
            {ICONS.download} HTML
          </button>
        </div>
      </div>

      <div style={{ fontSize:11, color:"var(--ink-500)", marginBottom:16, letterSpacing:"0.04em" }}>
        PowerPoint opens directly in Google Slides, Canva, and Keynote.
      </div>

      <div className="slide-stage">
        {/* Main slide */}
        <div className="slide slide-redesign">
          <div className="slide-counter">{String(si+1).padStart(2,"0")} / {String(slides.length).padStart(2,"0")}</div>
          {cur.emoji && <div className="slide-emoji">{cur.emoji}</div>}
          {cur.keyword && (
            <div className="slide-keyword">{cur.keyword}</div>
          )}
          <div className="slide-rule"></div>
          <div className="slide-title">{cur.title}</div>
          <div className="slide-body">{cur.body}</div>
        </div>

        <div className="slides-nav">
          <button className="wy-btn outline sm" type="button" disabled={si === 0}
            onClick={() => setSi(s => Math.max(0, s-1))}>
            {ICONS.chevronLeft} Prev
          </button>
          <span className="slides-nav-counter">{si+1} / {slides.length}</span>
          <button className="wy-btn outline sm" type="button" disabled={si === slides.length-1}
            onClick={() => setSi(s => Math.min(slides.length-1, s+1))}>
            Next {ICONS.chevronRight}
          </button>
        </div>

        <div className="slides-strip">
          {slides.map((s, i) => (
            <button
              key={i}
              type="button"
              className="slide-thumb"
              aria-current={i === si ? "true" : "false"}
              onClick={() => setSi(i)}
            >
              <div className="st-num">{String(i+1).padStart(2,"0")}</div>
              {s.emoji && <div className="st-emoji">{s.emoji}</div>}
              {s.keyword && <div className="st-keyword">{s.keyword}</div>}
            </button>
          ))}
        </div>
      </div>
      {err && <div style={{ color:"var(--ember)", fontSize:12, marginTop:10, textAlign:"center" }}>{err}</div>}
    </div>
  );
}

// ─── Rehearse Tab ─────────────────────────────────────────────────────────────

function YourTalkTab({ sc, transitions, genTrans, onGenTrans, onDownload, onCopy, onAdapt, adapting }) {
  const done = sc.filter(s => s?.trim()).length;
  const hasTransitions = transitions.some(t => t?.trim());
  const [targetLen, setTargetLen] = useState(null);
  const mins = talkDuration(sc);
  const minsLabel = mins < 0.5 ? "< 1 min" : mins < 1.5 ? "~1 min" : `~${Math.round(mins)} min`;

  if (done === 0) {
    return (
      <div className="yourtalk-wrap">
        <div className="slides-empty">
          <div className="glyph">{ICONS.scroll}</div>
          <h3>Your talk isn't written yet</h3>
          <p>Complete at least one step in Compose to see your talk here.</p>
        </div>
      </div>
    );
  }

  // Interleave sections and transitions
  const pieces = [];
  STEPS.forEach((step, i) => {
    const text = sc[i]?.trim();
    if (text) {
      pieces.push({ type:"section", step, text });
      // Add transition if there's a next saved section
      const nextSaved = sc.slice(i + 1).find(s => s?.trim());
      if (nextSaved && transitions[i]) {
        pieces.push({ type:"transition", text: transitions[i], index: i });
      }
    }
  });

  return (
    <div className="yourtalk-wrap">
      <div className="yourtalk-header">
        <div>
          <h2>Your Talk</h2>
          <div className="subtitle">{done} of 7 sections · {minsLabel} · full draft {hasTransitions ? "with transitions" : "without transitions"}</div>
        </div>
        <div style={{ display:"flex", gap:8, flexWrap:"wrap", alignItems:"center" }}>
          {done >= 2 && (
            <button className="wy-btn ghost sm" type="button" onClick={onGenTrans} disabled={genTrans}>
              {genTrans ? "Writing…" : <>{ICONS.sparkles} {transitions.length ? "Rewrite transitions" : "Add transitions"}</>}
            </button>
          )}
          <button className="wy-btn outline sm" type="button" onClick={onCopy}>
            {ICONS.copy} Copy all
          </button>
          <button className="wy-btn primary sm" type="button" onClick={onDownload}>
            {ICONS.download} Download
          </button>
        </div>
      </div>

      <div style={{
        display:"flex", alignItems:"center", gap:16, flexWrap:"wrap",
        padding:"12px 16px", background:"var(--surface-2,#f0ebe0)",
        borderRadius:8, marginBottom:20, fontSize:13,
      }}>
        <div style={{ display:"flex", alignItems:"center", gap:8 }}>
          <span style={{ fontWeight:700, color:"var(--ink,#1a1614)", fontSize:16 }}>{minsLabel}</span>
          <span style={{ color:"var(--ink-500,#8a7a6e)", fontSize:12 }}>at {WPM} wpm</span>
        </div>
        <div style={{ display:"flex", alignItems:"center", gap:6, flexWrap:"wrap" }}>
          <span style={{ color:"var(--ink-500,#8a7a6e)", fontSize:12, marginRight:2 }}>Adapt to:</span>
          {[5, 10, 15, 20, 30].map(m => (
            <button
              key={m}
              type="button"
              className="wy-btn ghost sm"
              style={targetLen === m ? { background:"var(--p-accent,#E36A2C)", color:"#fff", borderColor:"var(--p-accent,#E36A2C)" } : {}}
              onClick={() => setTargetLen(t => t === m ? null : m)}
            >
              {m} min
            </button>
          ))}
          {targetLen && (
            <button
              className="wy-btn outline sm"
              type="button"
              onClick={() => onAdapt(targetLen)}
              disabled={adapting}
            >
              {adapting ? "Adapting…" : <>{ICONS.sparkles} Adapt</>}
            </button>
          )}
        </div>
      </div>

      <div className="yourtalk-doc">
        {pieces.map((piece, i) => {
          if (piece.type === "section") return (
            <div key={`s-${piece.step.id}`} className="yt-section">
              <div className="yt-eyebrow">
                <span className="yt-num">{String(piece.step.id).padStart(2,"0")}</span>
                <span className="yt-title">{piece.step.title}</span>
              </div>
              <div className="yt-text">{piece.text}</div>
            </div>
          );
          return (
            <div key={`t-${piece.index}`} className="yt-transition">
              <span className="yt-trans-rule" />
              <span className="yt-trans-text">{piece.text}</span>
              <span className="yt-trans-rule" />
            </div>
          );
        })}

        {done < 7 && (
          <div className="yt-missing">
            {STEPS.filter((s, i) => !sc[i]?.trim()).map(s => (
              <span key={s.id} className="yt-missing-pill">Step {s.id}: {s.title}</span>
            ))}
            <span style={{ fontSize:12, color:"var(--ink-500)" }}>not yet written</span>
          </div>
        )}
      </div>

    </div>
  );
}

// ─── Speaker One Sheet Tab ────────────────────────────────────────────────────

function SpeakerSheetTab({ sc, sheet, genSheet, onGenSheet, onDlSheet, speakerInfo, setSpeakerInfo }) {
  const done = sc.filter(s => s?.trim()).length;
  const photoRef = React.useRef();

  function handlePhoto(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = ev => setSpeakerInfo(prev => ({ ...prev, photo: ev.target.result }));
    reader.readAsDataURL(file);
  }

  function field(key, placeholder, multiline) {
    const props = {
      className: "os-field",
      placeholder,
      value: speakerInfo[key] || "",
      onChange: e => setSpeakerInfo(prev => ({ ...prev, [key]: e.target.value })),
    };
    return multiline
      ? <textarea {...props} rows={3} />
      : <input {...props} type="text" />;
  }

  return (
    <div className="onesheet-wrap">
      <div className="section-header">
        <div>
          <h2>Speaker One Sheet</h2>
          <div className="subtitle">For meeting planners and event organizers. Fill in your details, generate the talk copy, then download.</div>
        </div>
        <div style={{ display:"flex", gap:8, flexWrap:"wrap" }}>
          <button className="wy-btn ghost sm" type="button" onClick={onGenSheet} disabled={genSheet || done < 2}>
            {genSheet ? "Generating…" : <>{ICONS.sparkles} {sheet ? "Regenerate talk copy" : "Generate talk copy"}</>}
          </button>
          {sheet && (
            <button className="wy-btn primary sm" type="button" onClick={() => onDlSheet(speakerInfo)}>
              {ICONS.download} Download One Sheet
            </button>
          )}
        </div>
      </div>

      <div className="onesheet-layout">

        {/* ── Left: speaker info + photo ── */}
        <div className="os-panel">
          <div className="os-section-label">Your Details</div>

          {/* Photo upload */}
          <div className="os-photo-row">
            <div
              className="os-photo-box"
              onClick={() => photoRef.current?.click()}
              style={{ backgroundImage: speakerInfo.photo ? `url(${speakerInfo.photo})` : "none" }}
            >
              {!speakerInfo.photo && (
                <div className="os-photo-prompt">
                  <div style={{ fontSize:24, marginBottom:6 }}>+</div>
                  <div>Add photo</div>
                </div>
              )}
            </div>
            <input ref={photoRef} type="file" accept="image/*" style={{ display:"none" }} onChange={handlePhoto} />
            <div className="os-photo-meta">
              <p>Click the box to upload your headshot.</p>
              <p>Square or portrait works best.</p>
              {speakerInfo.photo && (
                <button className="wy-btn ghost sm" style={{ marginTop:8 }} onClick={() => setSpeakerInfo(prev => ({ ...prev, photo: "" }))}>
                  Remove photo
                </button>
              )}
            </div>
          </div>

          <div className="os-field-group">
            <label>Full name</label>
            {field("name", "e.g. Sofia Hirschmann")}
          </div>
          <div className="os-field-group">
            <label>Title / credentials</label>
            {field("title", "e.g. AI Researcher & Healthcare Strategist")}
          </div>
          <div className="os-field-group">
            <label>Bio (3–4 sentences)</label>
            {field("bio", "Background, expertise, and why you're the right person to deliver this message…", true)}
          </div>
          <div className="os-field-group">
            <label>Email</label>
            {field("email", "your@email.com")}
          </div>
          <div className="os-field-group">
            <label>Website</label>
            {field("website", "yourwebsite.com")}
          </div>
        </div>

        {/* ── Right: talk copy preview ── */}
        <div className="os-panel">
          <div className="os-section-label">Talk Copy {done < 2 ? "— complete 2+ steps in Compose first" : ""}</div>

          {!sheet && !genSheet && (
            <div className="yt-sheet-empty" style={{ minHeight:200 }}>
              <p>{done < 2 ? "Complete at least 2 steps in Compose to generate your talk copy." : "Click \"Generate talk copy\" to fill this in from your saved talk."}</p>
            </div>
          )}

          {genSheet && (
            <div className="yt-sheet-empty" style={{ minHeight:200 }}>
              <div className="chat-thinking"><span/><span/><span/></div>
            </div>
          )}

          {sheet && !genSheet && (
            <div className="yt-sheet-preview">
              <div className="yt-sp-block yt-sp-coral">
                <div className="yt-sp-label">Signature Talk</div>
                <div className="yt-sp-talktitle">{sheet.title}</div>
                <div className="yt-sp-desc">{sheet.description}</div>
              </div>
              <div className="yt-sp-block yt-sp-dark">
                <div className="yt-sp-label" style={{ color:"var(--p-accent)" }}>Your Audience Will Walk Away With</div>
                <ul className="yt-sp-list">
                  {(sheet.takeaways || []).map((t, i) => <li key={i}>{t}</li>)}
                </ul>
              </div>
              <div className="yt-sp-note">
                Open the downloaded file in any browser and print to PDF.
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Main App ─────────────────────────────────────────────────────────────────

function App() {
  const [user, setUser]               = useState(null);
  const [authReady, setAuthReady]     = useState(false);
  const [cloudLoading, setCloudLoading] = useState(false);
  const [isSignInLink, setIsSignInLink] = useState(false);

  const d = load();
  const [view, setView]     = useState("compose");
  const [active, setActive] = useState(null);
  const [sc, setSc]         = useState(d.sc    || Array(7).fill(""));
  const [hist, setHist]     = useState(d.hist  || Array(7).fill(null).map(() => []));
  const [loading, setLoading] = useState(false);
  const [inp, setInp]       = useState("");
  const [refining, setRefining] = useState(false);
  const [cards, setCards]   = useState(d.cards  || []);
  const [slides, setSlides] = useState(d.slides || []);
  const [gen, setGen]       = useState(false);
  const [cardsErr, setCardsErr]   = useState(null);
  const [slidesErr, setSlidesErr] = useState(null);
  const [transitions, setTransitions] = useState(d.transitions || []);
  const [genTrans, setGenTrans]       = useState(false);
  const [sheet, setSheet]             = useState(d.sheet || null);
  const [genSheet, setGenSheet]       = useState(false);
  const [adapting, setAdapting]       = useState(false);
  const [speakerInfo, setSpeakerInfoRaw] = useState(d.speakerInfo || { name:"", title:"", bio:"", email:"", website:"", photo:"" });
  function setSpeakerInfo(updater) {
    setSpeakerInfoRaw(prev => {
      const next = typeof updater === "function" ? updater(prev) : updater;
      persist({ speakerInfo: { ...next, photo: next.photo?.slice(0, 500000) || "" } });
      return next;
    });
  }
  const [confirm, setConfirm] = useState(false);
  const [saveState, setSaveState]     = useState("saved");
  const [lastSavedAt, setLastSavedAt] = useState(null);

  // Check for magic link on mount
  useEffect(() => {
    setIsSignInLink(auth.isSignInWithEmailLink(window.location.href));
  }, []);

  // Auth state listener
  useEffect(() => {
    return auth.onAuthStateChanged(u => { setUser(u); setAuthReady(true); });
  }, []);

  // Load cloud data when user signs in
  useEffect(() => {
    if (!user) return;
    setCloudLoading(true);
    db.collection("users").doc(user.uid).get()
      .then(doc => {
        if (doc.exists) {
          const data = doc.data();
          if (data.sc)          setSc(data.sc);
          if (data.hist)        setHist(data.hist);
          if (data.cards)       setCards(data.cards);
          if (data.slides)      setSlides(data.slides);
          if (data.transitions)  setTransitions(data.transitions);
          if (data.sheet)        setSheet(data.sheet);
          if (data.speakerInfo)  setSpeakerInfoRaw(data.speakerInfo);
        }
      })
      .catch(console.error)
      .finally(() => setCloudLoading(false));
  }, [user]);

  // When active step changes, clear refining state
  useEffect(() => { setRefining(false); }, [active]);

  const persist = useCallback(async (updates) => {
    setSaveState("saving");
    const safe = { ...updates };
    if (safe.hist) safe.hist = safe.hist.map(msgs => msgs.slice(-20));
    try {
      localStorage.setItem(LS, JSON.stringify({ ...load(), ...safe }));
      if (user && !DEV_MODE) await db.collection("users").doc(user.uid).set(safe, { merge: true });
      setSaveState("saved");
      setLastSavedAt(new Date());
    } catch {
      setSaveState("error");
    }
  }, [user]);

  const done = sc.filter(s => s?.trim()).length;

  async function openStep(i) {
    setActive(i); setInp(""); setRefining(false);
    if (hist[i].length > 0) return;
    setLoading(true);
    const prior = sc.slice(0, i).filter(s => s?.trim());
    const contextNote = prior.length > 0
      ? `\n\nCONTEXT ALREADY ESTABLISHED — do NOT ask about anything covered here:\n${prior.map((s, j) => `Step ${j + 1}: ${s}`).join("\n\n")}`
      : "";
    const reply = await ai([{ role:"user", content:"Let's work on this step." }], PROMPTS[i] + contextNote, 300);
    const h = hist.map((x, j) => j === i ? [{ role:"assistant", content:reply }] : x);
    setHist(h); persist({ hist: h }); setLoading(false);
  }

  async function send() {
    if (!inp.trim() || loading) return;
    const m = inp.trim(); setInp("");
    const msgs = hist[active];
    const up = [...msgs, { role:"user", content:m }];
    const h1 = hist.map((x, j) => j === active ? up : x);
    setHist(h1); persist({ hist: h1 }); setLoading(true);

    if (/^\s*save\s*$/i.test(m)) {
      const txt = await ai(
        [...up, { role:"user", content:"Give me ONLY the final polished text for this section. No labels or commentary." }],
        PROMPTS[active], 400
      );
      const ns = sc.map((x, j) => j === active ? txt : x);
      setSc(ns); persist({ sc: ns });
      const nextLabel = active < 6 ? ` Moving to Step ${active + 2} now.` : " You've completed all 7 steps — head to Rehearse when you're ready.";
      const ack = { role:"assistant", content:`Section saved.${nextLabel}` };
      const h2 = hist.map((x, j) => j === active ? [...up, ack] : x);
      setHist(h2); persist({ hist: h2 });
      setRefining(false);
      if (active < 6) setTimeout(() => openStep(active + 1), 3000);
    } else {
      const reply = await ai(up, PROMPTS[active], 300);
      const h2 = hist.map((x, j) => j === active ? [...up, { role:"assistant", content:reply }] : x);
      setHist(h2); persist({ hist: h2 });
    }
    setLoading(false);
  }

  async function makeCards() {
    const t = sc.filter(s => s?.trim()).join("\n\n"); if (!t) return;
    setGen(true); setCardsErr(null);
    const r = await ai(
      [{ role:"user", content:t }],
      'Create 10 rehearsal flashcards. Return ONLY valid JSON: [{"front":"cue","back":"answer"}]. No markdown.',
      800
    );
    try {
      const c = JSON.parse(r.replace(/```json|```/g,"").trim());
      setCards(c); persist({ cards: c });
    } catch {
      setCardsErr("Couldn't parse the AI response. Try regenerating.");
    }
    setGen(false);
  }

  async function makeSlides() {
    const t = sc.filter(s => s?.trim()).join("\n\n"); if (!t) return;
    setGen(true); setSlidesErr(null);
    const r = await ai(
      [{ role:"user", content:t }],
      'Create 7 Ignite-style slides from this talk. Return ONLY valid JSON array, no markdown: [{"keyword":"1-3 WORD PHRASE IN CAPS","emoji":"single relevant emoji","title":"punchy italic title","body":"2-3 sentences max"}]. keyword = most powerful concept, all caps, 1-3 words. emoji = one single emoji that visually represents the slide theme.',
      1200
    );
    try {
      const s = JSON.parse(r.replace(/```json|```/g,"").trim());
      setSlides(s); persist({ slides: s });
    } catch {
      setSlidesErr("Couldn't parse the AI response. Try regenerating.");
    }
    setGen(false);
  }

  async function makeTransitions() {
    const saved = STEPS.map((s, i) => ({ step: s, text: sc[i]?.trim() })).filter(x => x.text);
    if (saved.length < 2) return;
    setGenTrans(true);
    const prompt = saved.map(x => `Section ${x.step.id} — ${x.step.title}:\n${x.text}`).join("\n\n---\n\n");
    const r = await ai(
      [{ role:"user", content:prompt }],
      `You are a speech writer. Given ${saved.length} consecutive sections of a signature talk, write exactly ${saved.length - 1} short spoken bridge sentences — one between each pair of adjacent sections. Each bridge: 1-2 sentences, 15-30 words, natural spoken language, flows from the ending idea of one section into the opening theme of the next. Never generic. Return ONLY a JSON array of ${saved.length - 1} strings. No markdown.`,
      600
    );
    try {
      const t = JSON.parse(r.replace(/```json|```/g, "").trim());
      // Map transitions back to original 7-step indices
      const full = Array(6).fill("");
      let ti = 0;
      for (let i = 0; i < 6; i++) {
        if (sc[i]?.trim() && sc.slice(i + 1).find(s => s?.trim())) {
          full[i] = t[ti++] || "";
        }
      }
      setTransitions(full); persist({ transitions: full });
    } catch { /* silent — transitions are optional */ }
    setGenTrans(false);
  }

  async function makeSheet() {
    const saved = STEPS.map((s, i) => sc[i]?.trim() ? `${s.title}: ${sc[i]}` : "").filter(Boolean).join("\n\n");
    if (!saved) return;
    setGenSheet(true);
    const r = await ai(
      [{ role:"user", content:saved }],
      `You are a speech writer creating a speaker one sheet for a meeting planner. Based on this signature talk, generate the following. Return ONLY valid JSON, no markdown:
{
  "title": "Your #1 Tool to [specific desired outcome from this talk]",
  "description": "In this talk, I introduce [specific audience] to the #1 tool they need to [key benefit]. I believe that when we [core belief or method from the talk], we not only [minimize a key pain], but we also [enable a key result].",
  "takeaways": [
    "A clear understanding of the [#] critical [principles/tools] they need to [desired outcome]",
    "A new way of thinking about [core topic from the talk]",
    "A practical strategy for [specific actionable step from the talk]"
  ]
}
Make every field specific to this actual talk — no generic placeholders. The title must start with "Your #1 Tool to".`,
      600
    );
    try {
      const s = JSON.parse(r.replace(/```json|```/g, "").trim());
      setSheet(s); persist({ sheet: s });
    } catch { /* silent */ }
    setGenSheet(false);
  }

  async function adaptTalk(targetMins) {
    const sections = STEPS
      .map((s, i) => sc[i]?.trim() ? `Step ${s.id} — ${s.title}:\n${sc[i]}` : null)
      .filter(Boolean);
    if (!sections.length) return;
    setAdapting(true);
    const currentMins = Math.round(talkDuration(sc)) || 1;
    const targetWords = Math.round(targetMins * WPM);
    const maxTok = Math.min(6000, Math.max(800, Math.round(targetMins * WPM * 1.5 + 400)));
    const r = await ai(
      [{ role:"user", content: sections.join("\n\n---\n\n") }],
      `You are a speech coach. This talk currently runs ~${currentMins} minutes. Rewrite each section so the full talk fits ${targetMins} minutes (~${targetWords} words total). Preserve the speaker's exact voice, personal stories, and key ideas — only expand or contract the length. Return ONLY a valid JSON array of exactly 7 strings — one per step in order (use "" for steps not present in the input). No markdown, no commentary.`,
      maxTok
    );
    try {
      const adapted = JSON.parse(r.replace(/```json|```/g, "").trim());
      if (Array.isArray(adapted) && adapted.length === 7) {
        const ns = sc.map((orig, i) => orig?.trim() ? (adapted[i] || orig) : orig);
        setSc(ns); persist({ sc: ns });
      }
    } catch { /* silent — sc unchanged on parse failure */ }
    setAdapting(false);
  }

  function copyTalk() {
    const parts = [];
    STEPS.forEach((step, i) => {
      const text = sc[i]?.trim();
      if (!text) return;
      parts.push(`${step.title.toUpperCase()}\n${text}`);
      if (transitions[i]) parts.push(`\n${transitions[i]}\n`);
    });
    navigator.clipboard?.writeText(parts.join("\n\n")).catch(() => {});
  }

  async function handleSignOut() {
    await auth.signOut();
    localStorage.removeItem(LS);
    setSc(Array(7).fill("")); setHist(Array(7).fill(null).map(() => []));
    setCards([]); setSlides([]); setTransitions([]); setSheet(null);
    setActive(null); setInp(""); setUser(null);
  }

  function reset() {
    localStorage.removeItem(LS);
    if (user) db.collection("users").doc(user.uid).delete().catch(console.error);
    setSc(Array(7).fill("")); setHist(Array(7).fill(null).map(() => []));
    setCards([]); setSlides([]); setTransitions([]); setSheet(null);
    setActive(null); setInp(""); setRefining(false); setConfirm(false);
  }

  // ─── Routing ──────────────────────────────────────────────────────────────

  const [devView, setDevView] = useState("app"); // "landing" | "app"
  const effectiveUser = DEV_MODE && devView === "app" ? DEV_USER : user;

  const DevToggle = DEV_MODE ? (
    <div style={{
      position:"fixed", bottom:20, right:20, zIndex:9999,
      display:"flex", alignItems:"center", gap:8,
      background:"#1A1614", borderRadius:99, padding:"8px 14px",
      boxShadow:"0 4px 20px rgba(0,0,0,0.35)", fontFamily:"monospace",
    }}>
      <span style={{ fontSize:11, color:"rgba(250,247,242,0.5)", letterSpacing:"0.08em" }}>DEV</span>
      <button
        type="button"
        onClick={() => setDevView("landing")}
        style={{
          padding:"5px 12px", borderRadius:99, border:"none", cursor:"pointer",
          fontSize:11, fontWeight:600, fontFamily:"monospace",
          background: devView === "landing" ? "#E36A2C" : "rgba(250,247,242,0.12)",
          color: devView === "landing" ? "#fff" : "rgba(250,247,242,0.55)",
          transition:"all 120ms",
        }}
      >Landing</button>
      <button
        type="button"
        onClick={() => setDevView("app")}
        style={{
          padding:"5px 12px", borderRadius:99, border:"none", cursor:"pointer",
          fontSize:11, fontWeight:600, fontFamily:"monospace",
          background: devView === "app" ? "#E36A2C" : "rgba(250,247,242,0.12)",
          color: devView === "app" ? "#fff" : "rgba(250,247,242,0.55)",
          transition:"all 120ms",
        }}
      >App</button>
    </div>
  ) : null;

  if (!authReady && !DEV_MODE) {
    return (
      <div className="loading-screen">
        <span>Loading…</span>
      </div>
    );
  }

  // Landing from a magic link — show completion screen
  if (isSignInLink && !effectiveUser) {
    return (
      <>
        <div style={{ "--p-accent":"#E36A2C" }}>
          <MagicLinkScreen />
        </div>
        {DevToggle}
      </>
    );
  }

  // Unauthenticated → full landing page
  if (!effectiveUser) {
    return (
      <>
        <LandingPage
          onSignIn={() => {
            window.scrollTo({ top: 0, behavior:"smooth" });
          }}
        />
        {DevToggle}
      </>
    );
  }

  if (cloudLoading && !DEV_MODE) {
    return (
      <div className="loading-screen">
        <span>Loading your talk…</span>
      </div>
    );
  }

  // ─── Product App ──────────────────────────────────────────────────────────

  return (
    <div className="product-root" data-accent="ember">
      {DevToggle}
      <TopNav
        user={effectiveUser}
        view={view}
        setView={setView}
        saveState={saveState}
        lastSavedAt={lastSavedAt}
        onSignOut={handleSignOut}
        onNewTalk={() => setConfirm(true)}
      />

      <div className="progress-band">
        <div className="product-shell progress-band-inner">
          <span className="pitch">Seven steps. Your story. <em>One unforgettable talk.</em></span>
          <div className="progress-bar-wrap">
            <span className="progress-bar-label">{done}/7 saved</span>
            <div className="progress-bar">
              <div style={{ width: (done/7*100)+"%" }}></div>
            </div>
          </div>
        </div>
      </div>

      <main className="product-shell product-main">
        {view === "compose" && (
          <ComposeTab
            sc={sc}
            hist={hist}
            active={active}
            loading={loading}
            inp={inp}
            refining={refining}
            done={done}
            setActive={setActive}
            setInp={setInp}
            send={send}
            openStep={openStep}
            onRefine={() => setRefining(true)}
            onCloseRefine={() => setRefining(false)}
            onDownload={() => dlTalk(sc)}
          />
        )}

        {view === "flashcards" && (
          <FlashcardsTab
            sc={sc}
            gen={gen}
            err={cardsErr}
            done={done}
            onGenerate={makeCards}
          />
        )}

        {view === "slides" && (
          <SlidesTab
            slides={slides}
            gen={gen}
            err={slidesErr}
            done={done}
            onGenerate={makeSlides}
            onDownload={() => dlSlides(slides)}
            onDownloadPptx={() => dlSlidesPptx(slides)}
          />
        )}

        {view === "yourtalk" && (
          <YourTalkTab
            sc={sc}
            transitions={transitions}
            genTrans={genTrans}
            onGenTrans={makeTransitions}
            onDownload={() => dlTalk(sc, transitions)}
            onCopy={copyTalk}
            onAdapt={adaptTalk}
            adapting={adapting}
          />
        )}

        {view === "onesheet" && (
          <SpeakerSheetTab
            sc={sc}
            sheet={sheet}
            genSheet={genSheet}
            onGenSheet={makeSheet}
            onDlSheet={info => dlSheet(sheet, info)}
            speakerInfo={speakerInfo}
            setSpeakerInfo={setSpeakerInfo}
          />
        )}
      </main>

      {/* CTA Footer */}
      <div className="cta-footer">
        <div className="product-shell">
          <h2>Ready to deliver the best talk of your life?</h2>
          <p>Book a personal Talk Review Call with the Masters in Clarity team.</p>
          <a className="wy-btn inverse" href={CTA} target="_blank" rel="noopener noreferrer">
            Book Your Talk Review Call {ICONS.arrowRight}
          </a>
        </div>
      </div>

      {/* Confirm modal */}
      {confirm && (
        <div className="modal-overlay" onClick={e => e.target === e.currentTarget && setConfirm(false)}>
          <div className="modal">
            <h2>Start a new talk?</h2>
            <p>This will permanently erase your current talk, flashcards, and slides. There is no undo.</p>
            <div className="modal-actions">
              <button className="wy-btn ghost" type="button" onClick={() => setConfirm(false)}>Cancel</button>
              <button className="wy-btn primary" type="button" onClick={reset}>Yes, start fresh</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode><App /></React.StrictMode>
);
