<script setup lang="ts">
definePageMeta({ layout: false });
import confetti from "canvas-confetti";

const route = useRoute();
const topicId = route.params.id as string;

// ── JIT generation state ───────────────────────────────────────────────────

type GenState = "loading" | "ready" | "error";

const lesson = ref<any>(null);
const lessonPending = ref(false);
const lessonError = ref<any>(null);
const genState = ref<GenState | null>(null);

const abortController = ref<AbortController | null>(null);
const generationFired = ref(false);

const GEN_TIPS = [
  "Reading through your past papers...",
  "Crafting analogies just for this topic...",
  "Writing your lesson from scratch...",
  "Recording your audio narration...",
  "Almost there...",
];
const GEN_STAGES = [
  "Understanding the topic",
  "Writing your lesson",
  "Recording narration",
  "Finishing up",
];

const genTipIndex = ref(0);
const genTipVisible = ref(true);
const genStageIndex = ref(0);

let genTipTimer: ReturnType<typeof setInterval> | null = null;
let genStageTimer: ReturnType<typeof setInterval> | null = null;

function startGenAnimations() {
  if (genTipTimer) return;
  genTipTimer = setInterval(() => {
    genTipVisible.value = false;
    setTimeout(() => {
      genTipIndex.value = (genTipIndex.value + 1) % GEN_TIPS.length;
      genTipVisible.value = true;
    }, 300);
  }, 3000);

  genStageTimer = setInterval(() => {
    genStageIndex.value = (genStageIndex.value + 1) % GEN_STAGES.length;
  }, 4500);
}

function stopGenAnimations() {
  if (genTipTimer) { clearInterval(genTipTimer); genTipTimer = null; }
  if (genStageTimer) { clearInterval(genStageTimer); genStageTimer = null; }
}

async function loadLesson() {
  lesson.value = null;

  const pendingTimer = setTimeout(() => { lessonPending.value = true; }, 100);

  abortController.value = new AbortController();

  try {
    const data = await $fetch<any>(`/api/topics/${topicId}/lesson`);
    lesson.value = data;
    genState.value = "ready";
  } catch (err: any) {
    if (err?.statusCode === 404 || err?.status === 404) {
      // lesson not generated yet — trigger JIT generation
      await triggerGeneration();
    } else {
      lessonError.value = err;
    }
  } finally {
    clearTimeout(pendingTimer);
    lessonPending.value = false;
  }
}

async function triggerGeneration() {
  if (generationFired.value) return;
  generationFired.value = true;

  genState.value = "loading";
  startGenAnimations();
  try {
    const result = await $fetch<{ status: string }>(`/api/topics/${topicId}/generate`, { method: "POST" });
    stopGenAnimations();
    if (result.status === "ready") {
      const data = await $fetch<any>(`/api/topics/${topicId}/lesson`);
      lesson.value = data;
      genState.value = "ready";
    } else {
      genState.value = "error";
    }
  } catch {
    stopGenAnimations();
    genState.value = "error";
  }
}

onMounted(loadLesson);

onUnmounted(() => {
  stopGenAnimations();
  abortController.value?.abort();
});

// ── branch loading overlay ─────────────────────────────────────────────────

const branchOverlayVisible = ref(false);
const branchOverlayError = ref(false);

let branchPollTimer: ReturnType<typeof setInterval> | null = null;
const branchPollStart = ref(0);

function startBranchPoll() {
  if (branchPollTimer) return;

  branchPollStart.value = Date.now();

  branchPollTimer = setInterval(async () => {
    try {
      const data = await $fetch<any>(`/api/topics/${topicId}/lesson`);
      if (data.branchStatus === "ready") {
        lesson.value = data;
        stopBranchPoll();
        branchOverlayVisible.value = false;
      } else if (data.branchStatus === "error") {
        stopBranchPoll();
        branchOverlayError.value = true;
      }
    } catch { /* ignore poll errors */ }

    if (Date.now() - branchPollStart.value > 60_000) {
      stopBranchPoll();
      branchOverlayError.value = true;
    }
  }, 2000);
}

function stopBranchPoll() {
  if (branchPollTimer) { clearInterval(branchPollTimer); branchPollTimer = null; }
}

// ── lesson state machine ───────────────────────────────────────────────────

type LessonMode = "main" | "transition" | "branch" | "confirm" | "hardStop";

const lessonState = reactive({
  mode: "main" as LessonMode,
  stepIndex: 0,
  branchOption: "" as string,
  branchStepIndex: 0,
});

type Step = {
  type: "concept" | "example" | "question" | "summary"
  title?: string
  body?: string
  callout?: string
  options?: string[]
  answer?: string
  explanation?: string
  bullets?: string[]
  audioPath?: string
  audioChunks?: { text: string; start: number; end: number }[]
  questionAudioPath?: string
  questionAudioChunks?: { text: string; start: number; end: number }[]
  optionAudioPaths?: (string | null)[]
  branches?: Record<string, BranchData>
}

interface BranchData {
  steps: BranchStep[]
  confirmQuestion: {
    body: string
    options: string[]
    answer: string
    explanation: string
    questionAudioPath?: string
    questionAudioChunks?: { text: string; start: number; end: number }[]
    optionAudioPaths?: (string | null)[]
  }
  hardStop: string
  hardStopAudioPath?: string
}

interface BranchStep {
  type: "concept"
  title: string
  body: string
  audioPath?: string
  audioChunks?: { text: string; start: number; end: number }[]
}

const steps = computed<Step[]>(() => lesson.value?.content?.steps ?? []);
const totalSteps = computed(() => steps.value.length);

const isLastMainStep = computed(() => lessonState.stepIndex === totalSteps.value - 1);

// ── current display step ───────────────────────────────────────────────────

const currentBranch = computed<BranchData | null>(() => {
  if (!["branch", "confirm", "hardStop"].includes(lessonState.mode)) return null;
  const mainStep = steps.value[lessonState.stepIndex];
  return mainStep?.branches?.[lessonState.branchOption] ?? null;
});

const currentBranchStep = computed<BranchStep | null>(() => {
  if (lessonState.mode !== "branch") return null;
  return currentBranch.value?.steps[lessonState.branchStepIndex] ?? null;
});

const displayStep = computed<Step | null>(() => {
  const s = lessonState;
  if (s.mode === "main") return steps.value[s.stepIndex] ?? null;
  if (s.mode === "branch") {
    const bs = currentBranchStep.value;
    if (!bs) return null;
    return { type: "concept", title: bs.title, body: bs.body, audioPath: bs.audioPath, audioChunks: bs.audioChunks };
  }
  if (s.mode === "confirm") {
    const cq = currentBranch.value?.confirmQuestion;
    if (!cq) return null;
    return {
      type: "question",
      body: cq.body,
      options: cq.options,
      answer: cq.answer,
      explanation: cq.explanation,
      questionAudioPath: cq.questionAudioPath,
      questionAudioChunks: cq.questionAudioChunks,
      optionAudioPaths: cq.optionAudioPaths,
    };
  }
  return null;
});

const stepKey = computed(() =>
  `${lessonState.mode}-${lessonState.stepIndex}-${lessonState.branchOption}-${lessonState.branchStepIndex}`
);

const stepBg = computed(() => {
  if (lessonPending.value || lessonError.value) return "#FAFAF8";
  if (lessonState.mode === "confirm") return "#FFFBEB";
  if (lessonState.mode === "branch") return "#FAFAF8";
  if (lessonState.mode === "transition") return "#FFFBEB";
  if (lessonState.mode === "hardStop") return "#FFFBEB";
  const map: Record<string, string> = {
    concept: "#FAFAF8", example: "#FFFBEB", question: "#F0F4FF", summary: "#F0FDF4",
  };
  return displayStep.value ? (map[displayStep.value.type] ?? "#FAFAF8") : "#FAFAF8";
});

const accentColor = computed(() => {
  if (lessonState.mode === "confirm") return "#F59E0B";
  if (lessonState.mode === "branch") return "#F59E0B";
  if (!displayStep.value) return "#6366F1";
  const map: Record<string, string> = {
    concept: "#6366F1", example: "#F59E0B", question: "#6366F1", summary: "#10B981",
  };
  return map[displayStep.value.type] ?? "#6366F1";
});

const typeLabel = computed(() => {
  if (lessonState.mode === "confirm") return "Let's Check";
  if (lessonState.mode === "branch") return "A Closer Look";
  const map: Record<string, string> = {
    concept: "Concept", example: "Worked Example", question: "Question", summary: "Key Takeaways",
  };
  return displayStep.value ? (map[displayStep.value.type] ?? "") : "";
});

// ── answer tracking ────────────────────────────────────────────────────────

// main question answers, keyed by step index
const selectedAnswers = ref<Record<number, string>>({});
// confirm question answer (one at a time)
const confirmSelectedAnswer = ref<string | null>(null);

const selectedAnswer = computed(() => {
  if (lessonState.mode === "main") return selectedAnswers.value[lessonState.stepIndex] ?? null;
  if (lessonState.mode === "confirm") return confirmSelectedAnswer.value;
  return null;
});

const hasAnswered = computed(() => selectedAnswer.value !== null);

const isCorrect = computed(() => {
  if (!hasAnswered.value || !selectedAnswer.value) return null;
  return selectedAnswer.value === displayStep.value?.answer;
});

const canContinue = computed(() => {
  if (!displayStep.value) return false;
  if (displayStep.value.type === "question") return showMainExplanation.value;
  if (lessonState.mode === "branch") return true;
  return lessonState.mode === "main";
});

// show explanation: main question — only for correct answers or wrong with no branch
const showMainExplanation = computed(() => {
  if (lessonState.mode !== "main") return false;
  if (!hasAnswered.value) return false;
  if (isCorrect.value) return true;
  // wrong: only show if no branch exists for this option
  const mainStep = steps.value[lessonState.stepIndex];
  const sel = selectedAnswers.value[lessonState.stepIndex];
  return !mainStep?.branches?.[sel];
});

// ── shuffled options ───────────────────────────────────────────────────────

const shuffledOptions = ref<string[]>([]);

function seededShuffle(arr: string[], seed: number): string[] {
  const a = [...arr];
  let s = seed;
  for (let i = a.length - 1; i > 0; i--) {
    s = (s * 1664525 + 1013904223) >>> 0;
    const j = s % (i + 1);
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

watch([() => stepKey.value, steps], () => {
  const s = displayStep.value;
  if (s?.type === "question" && s.options) {
    const seed = lessonState.stepIndex * 31 + (lessonState.branchOption ? lessonState.branchOption.charCodeAt(0) : 0);
    shuffledOptions.value = seededShuffle(s.options, seed);
  } else {
    shuffledOptions.value = [];
  }
}, { immediate: true });

const LABELS = ["A", "B", "C", "D"];

// ── branch tracking ────────────────────────────────────────────────────────

const pendingBranchStep = ref<number | null>(null);
const pendingBranchOption = ref<string>("");

// when branch overlay dismisses after poll completes, fire the deferred branch
watch(branchOverlayVisible, (visible) => {
  if (!visible && pendingBranchStep.value !== null && !branchOverlayError.value) {
    const step = pendingBranchStep.value;
    const opt = pendingBranchOption.value;
    pendingBranchStep.value = null;
    pendingBranchOption.value = "";
    enterBranchMode(step, opt);
  }
});

const branchesEntered = ref(0);
let transitionTimeout: ReturnType<typeof setTimeout> | null = null;
let confirmAdvanceTimeout: ReturnType<typeof setTimeout> | null = null;

function enterBranchMode(stepIndex: number, wrongOption: string) {
  // if branches arent ready yet, show overlay and wait
  if (lesson.value?.branchStatus !== "ready") {
    branchOverlayVisible.value = true;
    branchOverlayError.value = false;
    startBranchPoll();
    // store where we want to go when branches become available
    pendingBranchStep.value = stepIndex;
    pendingBranchOption.value = wrongOption;
    return;
  }

  branchesEntered.value++;
  lessonState.mode = "transition";
  lessonState.stepIndex = stepIndex;
  lessonState.branchOption = wrongOption;

  if (focusMode.value) {
    stopAllAudio();
    const audio = new Audio("/audio/branch-transition.mp3");
    audio.play().catch(() => {});
  }

  transitionTimeout = setTimeout(() => {
    lessonState.mode = "branch";
    lessonState.branchStepIndex = 0;
    confirmSelectedAnswer.value = null;
    if (focusMode.value) {
      nextTick(() => startStepAudio());
    }
  }, 1500);
}

function advanceBranchStep() {
  const branch = currentBranch.value;
  if (!branch) return;
  if (lessonState.branchStepIndex < branch.steps.length - 1) {
    lessonState.branchStepIndex++;
    if (focusMode.value) nextTick(() => startStepAudio());
  } else {
    // last branch step → go to confirm
    lessonState.mode = "confirm";
    confirmSelectedAnswer.value = null;
    if (focusMode.value) nextTick(() => startStepAudio());
  }
}

function selectConfirmOption(opt: string) {
  if (confirmSelectedAnswer.value) return;
  confirmSelectedAnswer.value = opt;

  if (opt === currentBranch.value?.confirmQuestion?.answer) {
    // correct — advance to next main step after 1.5s
    confirmAdvanceTimeout = setTimeout(() => {
      const nextStep = lessonState.stepIndex + 1;
      lessonState.mode = "main";
      lessonState.branchOption = "";
      lessonState.branchStepIndex = 0;
      if (nextStep < totalSteps.value) {
        lessonState.stepIndex = nextStep;
      } else {
        lessonState.stepIndex = totalSteps.value - 1;
      }
      confirmSelectedAnswer.value = null;
      if (focusMode.value) nextTick(() => startStepAudio());
    }, 1500);
  } else {
    // wrong → hard stop after brief moment
    setTimeout(() => {
      lessonState.mode = "hardStop";
      if (focusMode.value && currentBranch.value?.hardStopAudioPath) {
        stopAllAudio();
        const audio = new Audio(currentBranch.value.hardStopAudioPath);
        audio.play().catch(() => {});
      }
    }, 700);
  }
}

function retryFromHardStop() {
  const stepIndex = lessonState.stepIndex;
  lessonState.mode = "main";
  lessonState.branchOption = "";
  lessonState.branchStepIndex = 0;
  delete selectedAnswers.value[stepIndex];
  confirmSelectedAnswer.value = null;
  stopAllAudio();
  if (focusMode.value) nextTick(() => startStepAudio());
}

function randomCorrectVariant(): number {
  return Math.floor(Math.random() * 4) + 1;
}

function selectOption(opt: string) {
  if (hasAnswered.value) return;

  if (lessonState.mode === "main") {
    selectedAnswers.value[lessonState.stepIndex] = opt;
    const mainStep = steps.value[lessonState.stepIndex];

    if (focusMode.value) {
      stopAllAudio();
      if (opt === mainStep?.answer) {
        // correct in focus mode — affirmation → explanation → advance
        const v = randomCorrectVariant();
        const clips: string[] = [`/audio/correct_${v}.mp3`];

        const explanationPath = (mainStep as any).explanationAudioPath as string | undefined;
        if (explanationPath) clips.push(explanationPath);

        playSequence(clips).then(() => {
          advance();
        }).catch(() => {});
      } else if (mainStep?.branches?.[opt]) {
        // wrong with branch — transition fires automatically (same as non-focus)
        setTimeout(() => enterBranchMode(lessonState.stepIndex, opt), 700);
      }
      // wrong with no branch — nothing plays, student sees explanation, can tap continue
    } else {
      if (mainStep && opt !== mainStep.answer && mainStep.branches?.[opt]) {
        setTimeout(() => enterBranchMode(lessonState.stepIndex, opt), 700);
      }
    }
  } else if (lessonState.mode === "confirm") {
    selectConfirmOption(opt);
  }
}

// ── navigation ─────────────────────────────────────────────────────────────

function advance() {
  if (lessonState.mode === "branch") {
    advanceBranchStep();
    return;
  }
  if (lessonState.mode !== "main") return;
  if (!canContinue.value) return;
  if (isLastMainStep.value) { completeLesson(); return; }
  lessonState.stepIndex++;
}

const lessonScore = ref(0);
watch(() => ({ ...selectedAnswers.value }), (opts, prev) => {
  const newKeys = Object.keys(opts).map(Number).filter(k => !(k in prev));
  for (const k of newKeys) {
    const s = steps.value[k];
    if (s?.type === "question" && opts[k] === s.answer) lessonScore.value++;
  }
});

watch(() => [lessonState.mode, lessonState.stepIndex], ([mode, idx]) => {
  if (mode === "main" && steps.value[idx as number]?.type === "summary") fireCelebration();
});

const celebrationFired = ref(false);

function fireCelebration() {
  if (celebrationFired.value) return;
  celebrationFired.value = true;

  const count = 160;
  const defaults = { startVelocity: 35, spread: 80, ticks: 70, zIndex: 200 };
  const interval = setInterval(() => {
    confetti({ ...defaults, particleCount: count * 0.3, origin: { x: 0.4, y: -0.1 }, colors: ["#6366F1", "#10B981", "#F59E0B", "#EC4899", "#ffffff"] });
    confetti({ ...defaults, particleCount: count * 0.3, origin: { x: 0.6, y: -0.1 }, colors: ["#6366F1", "#10B981", "#F59E0B", "#EC4899", "#ffffff"] });
  }, 180);
  setTimeout(() => clearInterval(interval), 900);
}

const lessonDone = ref(false);
const completing = ref(false);
const completeError = ref(false);

async function completeLesson() {
  if (completing.value || lessonDone.value) return;
  completing.value = true;
  completeError.value = false;

  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), 10000);

  try {
    await $fetch(`/api/topics/${topicId}/progress`, {
      method: "POST",
      signal: ctrl.signal,
      body: {
        lessonComplete: true,
        tookBranches: branchesEntered.value > 0,
        branchCount: branchesEntered.value,
      },
    });
    lessonDone.value = true;
  } catch {
    completing.value = false;
    completeError.value = true;
    return;
  } finally {
    clearTimeout(timer);
    completing.value = false;
  }
}

const router = useRouter();
function goBack() {
  if (history.length <= 1) { router.push("/"); return; }
  history.back();
}

function stepBack() {
  stopAllAudio();
  if (lessonState.mode === "branch" || lessonState.mode === "confirm" || lessonState.mode === "hardStop") {
    // exit branch back to main question
    lessonState.mode = "main";
    lessonState.branchOption = "";
    lessonState.branchStepIndex = 0;
    confirmSelectedAnswer.value = null;
    return;
  }
  if (lessonState.stepIndex > 0) {
    lessonState.stepIndex--;
    // clear the answer for this step so they can re-answer
    delete selectedAnswers.value[lessonState.stepIndex];
  }
}

// ── FOCUS MODE ────────────────────────────────────────────────────────────

const focusMode = ref(false);
const currentChunk = ref<{ text: string; start: number; end: number } | null>(null);
const karaokeAudioEl = ref<HTMLAudioElement | null>(null);
const questionPlaying = ref(false);
const narratingOptionIndex = ref<number | null>(null);;
const focusEverActivated = ref(false);

if (import.meta.client) {
  const stored = localStorage.getItem("revisione-focus-mode");
  if (stored === "true") focusMode.value = true;
}

const hasAudio = computed(() => {
  const s = steps.value[0];
  return !!(s?.audioPath || s?.questionAudioPath);
});

const showKaraoke = computed(() => {
  if (!focusMode.value) return false;
  const ds = displayStep.value;
  if (!((lessonState.mode === "main" || lessonState.mode === "branch") &&
    (ds?.type === "concept" || ds?.type === "example"))) return false;
  // dont show overlay if this step has no audio — avoids perma-"..." with missing TTS
  return !!(ds?.audioPath && ds?.audioChunks && (ds.audioChunks as any[]).length > 0);
});

// kept for compat — no longer drives a full overlay, just used to gate the continue button
const showQuestionOverlay = computed(() =>
  focusMode.value &&
  (lessonState.mode === "main" || lessonState.mode === "confirm") &&
  displayStep.value?.type === "question" &&
  questionPlaying.value
);

async function toggleFocusMode() {
  focusMode.value = !focusMode.value;
  if (import.meta.client) localStorage.setItem("revisione-focus-mode", String(focusMode.value));

  if (focusMode.value) {
    // on first activation, re-fetch lesson so we have the latest audio paths from the DB
    if (!focusEverActivated.value) {
      focusEverActivated.value = true;
      try {
        const fresh = await $fetch<any>(`/api/topics/${topicId}/lesson`);
        lesson.value = fresh;
        const s0 = fresh?.content?.steps?.[0];
        console.log("[focus] step 0 audioPath:", s0?.audioPath, "| chunks:", s0?.audioChunks?.length ?? "none");
      } catch (e) {
        console.warn("[focus] re-fetch failed, using cached lesson data", e);
      }
    }

    nextTick(() => startStepAudio());
  } else {
    stopAllAudio();
  }
}

// ── karaoke ────────────────────────────────────────────────────────────────

let timeupdateHandler: (() => void) | null = null;
let endedHandler: (() => void) | null = null;

function startKaraoke(
  audioEl: HTMLAudioElement,
  chunks: { text: string; start: number; end: number }[],
  onEnd: () => void
) {
  currentChunk.value = chunks[0] ?? null;

  timeupdateHandler = () => {
    const t = audioEl.currentTime;
    const found = chunks.find(c => t >= c.start && t < c.end);
    if (found) currentChunk.value = found;
  };

  endedHandler = async () => {
    currentChunk.value = null;
    await new Promise(r => setTimeout(r, 600));
    onEnd();
  };

  audioEl.addEventListener("timeupdate", timeupdateHandler);
  audioEl.addEventListener("ended", endedHandler);
  audioEl.play().catch(() => {});
}

function stopAllAudio() {
  if (karaokeAudioEl.value) {
    const a = karaokeAudioEl.value;
    a.pause();
    if (timeupdateHandler) { a.removeEventListener("timeupdate", timeupdateHandler); timeupdateHandler = null; }
    if (endedHandler) { a.removeEventListener("ended", endedHandler); endedHandler = null; }
  }
  stopQueue();
}

// ── question audio queue ───────────────────────────────────────────────────

let queueAbort: (() => void) | null = null;

function stopQueue() {
  if (queueAbort) { queueAbort(); queueAbort = null; }
  questionPlaying.value = false;
}

async function playSequence(urls: string[]): Promise<void> {
  return new Promise((resolve, reject) => {
    let cancelled = false;
    let current: HTMLAudioElement | null = null;

    queueAbort = () => {
      cancelled = true;
      current?.pause();
      reject(new Error("aborted"));
    };

    let i = 0;
    function playNext() {
      if (cancelled || i >= urls.length) { resolve(); return; }
      const url = urls[i++];
      if (!url) { playNext(); return; }
      const audio = new Audio(url);
      current = audio;
      audio.onended = playNext;
      audio.onerror = playNext;
      audio.play().catch(playNext);
    }

    playNext();
  });
}

function getVariant(stepIndex: number): number {
  return (Math.abs(stepIndex) % 4) + 1;
}

async function playQuestionLike(
  questionStep: Step,
  stepIndex: number,
  variantKey: number
) {
  stopQueue();
  questionPlaying.value = true;
  narratingOptionIndex.value = null;

  const variant = getVariant(variantKey);

  try {
    // play question stem first
    if (questionStep.questionAudioPath) {
      await playSequence([questionStep.questionAudioPath]);
    }

    // play each option in order, highlighting it while it plays
    for (let oi = 0; oi < shuffledOptions.value.length; oi++) {
      narratingOptionIndex.value = oi;
      const label = LABELS[oi];
      const origIdx = (questionStep.options ?? []).indexOf(shuffledOptions.value[oi]);
      const optAudio = questionStep.optionAudioPaths?.[origIdx];
      const clips: string[] = [`/audio/labels/${label}_${variant}.mp3`];
      if (optAudio) clips.push(optAudio);
      await playSequence(clips);
    }
  } catch {
    // aborted
  }

  narratingOptionIndex.value = null;
  questionPlaying.value = false;
}

function startStepAudio() {
  if (!focusMode.value) return;

  if (lessonState.mode === "main") {
    const s = steps.value[lessonState.stepIndex];
    if (!s) return;

    if (s.type === "concept" || s.type === "example" || s.type === "summary") {
      if (!s.audioPath || !s.audioChunks || (s.audioChunks as any[]).length === 0) {
        // no usable audio — advance state only, the watcher fires startStepAudio for the new step
        if (!isLastMainStep.value) {
          lessonState.stepIndex++;
        } else {
          completeLesson();
        }
        return;
      }
      const audio = karaokeAudioEl.value;
      if (!audio) return;
      audio.src = s.audioPath;
      audio.load();
      startKaraoke(audio, s.audioChunks, () => {
        if (!isLastMainStep.value) {
          lessonState.stepIndex++;
          // watcher handles startStepAudio for the new step
        } else {
          completeLesson();
        }
      });
    } else if (s.type === "question") {
      stopAllAudio();
      playQuestionLike(s, lessonState.stepIndex, lessonState.stepIndex);
    }
  } else if (lessonState.mode === "branch") {
    const bs = currentBranchStep.value;
    if (!bs?.audioPath || !bs.audioChunks || (bs.audioChunks as any[]).length === 0) {
      advanceBranchStep();
      return;
    }
    const audio = karaokeAudioEl.value;
    if (!audio) return;
    audio.src = bs.audioPath;
    audio.load();
    startKaraoke(audio, bs.audioChunks, () => {
      advanceBranchStep();
    });
  } else if (lessonState.mode === "confirm") {
    const cq = currentBranch.value?.confirmQuestion;
    if (!cq) return;
    const questionStep: Step = {
      type: "question",
      body: cq.body,
      options: cq.options,
      answer: cq.answer,
      explanation: cq.explanation,
      questionAudioPath: cq.questionAudioPath,
      questionAudioChunks: cq.questionAudioChunks,
      optionAudioPaths: cq.optionAudioPaths,
    };
    stopAllAudio();
    playQuestionLike(questionStep, lessonState.stepIndex, -1);
  }
}

watch(() => [lessonState.mode, lessonState.stepIndex, lessonState.branchStepIndex], () => {
  if (focusMode.value && lessonState.mode !== "transition" && lessonState.mode !== "hardStop") {
    stopAllAudio();
    nextTick(() => startStepAudio());
  }
});

watch(focusMode, (on) => { if (!on) stopAllAudio(); });

function skipStep() {
  stopAllAudio();
  if (lessonState.mode === "branch") {
    advanceBranchStep();
  } else if (!isLastMainStep.value) {
    lessonState.stepIndex++;
  } else {
    completeLesson();
  }
}

function continueAfterQuestion() {
  stopQueue();
  advance();
}

onBeforeUnmount(() => {
  stopAllAudio();
  stopBranchPoll();
  if (transitionTimeout) clearTimeout(transitionTimeout);
  if (confirmAdvanceTimeout) clearTimeout(confirmAdvanceTimeout);
});
</script>

<template>
  <!-- ── JIT GENERATION LOADING SCREEN ─────────────────────────────────────── -->
  <Transition name="fullscreen-fade">
    <div v-if="genState === 'loading'" class="gen-loading-screen">
      <div class="gen-inner">
        <div class="gen-logo">Revisi<span>.one</span></div>

        <div class="gen-pulse-ring">
          <div class="gen-pulse-circle" />
          <div class="gen-pulse-dot">
            <svg width="28" height="28" fill="none" viewBox="0 0 24 24" stroke="oklch(54% 0.140 44)">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
                d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
            </svg>
          </div>
        </div>

        <Transition name="tip">
          <p v-if="genTipVisible" :key="genTipIndex" class="gen-tip">{{ GEN_TIPS[genTipIndex] }}</p>
        </Transition>

        <div class="gen-stepper">
          <div
            v-for="(stage, i) in GEN_STAGES"
            :key="i"
            class="gen-step"
            :class="i === genStageIndex ? 'gen-step--active' : i < genStageIndex ? 'gen-step--done' : 'gen-step--pending'"
          >
            <div class="gen-step-node">
              <div v-if="i < genStageIndex" class="gen-node-done">
                <svg width="10" height="10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
                </svg>
              </div>
              <div v-else-if="i === genStageIndex" class="gen-node-active">
                <div class="gen-node-ring" />
                <div class="gen-node-dot" />
              </div>
              <div v-else class="gen-node-pending" />
            </div>
            <span class="gen-step-label">{{ stage }}</span>
          </div>
        </div>
      </div>
    </div>
  </Transition>

  <!-- ── JIT GENERATION ERROR ────────────────────────────────────────────── -->
  <Transition name="fullscreen-fade">
    <div v-if="genState === 'error'" class="gen-loading-screen">
      <div class="gen-inner">
        <div class="gen-logo">Revisi<span>.one</span></div>
        <p class="gen-error-msg">We couldn't generate this lesson. Please try again.</p>
        <button class="gen-retry-btn" @click="triggerGeneration">Try again</button>
      </div>
    </div>
  </Transition>

  <div
    v-if="genState === 'ready' || genState === null"
    class="lesson-shell"
    :style="{ '--step-bg': stepBg, '--accent': accentColor }"
  >
    <div class="accent-bar" />

    <audio ref="karaokeAudioEl" preload="auto" style="display:none" />

    <!-- ── TOP BAR ─────────────────────────────────────────────────────────── -->
    <header class="topbar">
      <button class="topbar-back" @click="stepBack" aria-label="Previous step" :disabled="lessonState.stepIndex === 0 && lessonState.mode === 'main'">
        <svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
        </svg>
      </button>

      <div class="progress-segments" role="progressbar" :aria-valuenow="lessonState.stepIndex + 1" :aria-valuemax="totalSteps">
        <div
          v-for="i in totalSteps"
          :key="i"
          class="seg"
          :class="{ 'seg--filled': i - 1 <= lessonState.stepIndex, 'seg--empty': i - 1 > lessonState.stepIndex }"
        />
      </div>

      <span class="topbar-count">{{ lessonPending ? '…' : `${lessonState.stepIndex + 1} / ${totalSteps}` }}</span>

      <button
        v-if="hasAudio && !lessonPending"
        class="focus-toggle"
        :class="{ 'focus-toggle--on': focusMode }"
        @click="toggleFocusMode"
        :title="focusMode ? 'Exit Focus Mode' : 'Focus Mode'"
        aria-label="Toggle Focus Mode"
      >
        <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/>
        </svg>
        <span class="focus-label">Focus Mode</span>
      </button>

      <button class="topbar-exit" @click="goBack" aria-label="Exit lesson" title="Back to course">
        <svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
        </svg>
      </button>
    </header>

    <!-- ── KARAOKE OVERLAY ────────────────────────────────────────────────── -->
    <Transition name="karaoke-fade">
      <div v-if="showKaraoke" class="karaoke-overlay" :style="{ backgroundColor: stepBg }">
        <template v-if="currentChunk">
          <p class="karaoke-text">{{ currentChunk.text }}</p>
          <button class="karaoke-skip" @click="skipStep" title="Skip">
            <svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
            </svg>
          </button>
        </template>
        <template v-else>
          <button class="karaoke-start" @click="startStepAudio">
            <svg width="22" height="22" fill="currentColor" viewBox="0 0 24 24">
              <path d="M8 5v14l11-7z"/>
            </svg>
            Start
          </button>
        </template>
      </div>
    </Transition>

    <!-- ── BRANCH TRANSITION SCREEN ───────────────────────────────────────── -->
    <Transition name="fullscreen-fade">
      <div v-if="lessonState.mode === 'transition'" class="branch-transition-screen">
        <div class="transition-inner">
          <div class="transition-icon">
            <svg width="40" height="40" fill="none" viewBox="0 0 24 24" stroke="#F59E0B">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
                d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
            </svg>
          </div>
          <p class="transition-text">Let's look at this a different way…</p>
        </div>
      </div>
    </Transition>

    <!-- ── HARD STOP SCREEN ────────────────────────────────────────────────── -->
    <Transition name="fullscreen-fade">
      <div v-if="lessonState.mode === 'hardStop'" class="hard-stop-screen">
        <div class="hard-stop-inner">
          <div class="hard-stop-icon">
            <svg width="48" height="48" fill="none" viewBox="0 0 24 24" stroke="#F59E0B">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25"
                d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
            </svg>
          </div>
          <p class="hard-stop-text">
            {{ currentBranch?.hardStop ?? "This concept needs a bit more time. Take a break, do some reading, then come back." }}
          </p>
          <button class="hard-stop-btn" @click="retryFromHardStop">
            I've done some reading — let me try again
          </button>
        </div>
      </div>
    </Transition>

    <!-- ── MAIN ───────────────────────────────────────────────────────────── -->
    <main
      class="lesson-main"
      v-show="!showKaraoke && lessonState.mode !== 'transition' && lessonState.mode !== 'hardStop'"
    >

      <div v-if="lessonPending" class="state-center">
        <div class="indigo-dots"><span /><span /><span /></div>
        <p class="state-label">Loading lesson…</p>
      </div>

      <div v-else-if="lessonError" class="err-state">
        <p>Couldn't load this lesson. Please go back and try again.</p>
      </div>

      <div v-else-if="lesson" class="step-container">
        <Transition name="step" mode="out-in">
          <div :key="stepKey" class="step-inner">

            <!-- confirm accent bar -->
            <div v-if="lessonState.mode === 'confirm'" class="confirm-accent-bar" />

            <p class="type-label" :style="{ color: accentColor }">{{ typeLabel }}</p>

            <!-- ── CONCEPT ──────────────────────────────────────── -->
            <template v-if="displayStep?.type === 'concept'">
              <h1 class="step-title">{{ displayStep.title }}</h1>
              <p class="step-body">{{ displayStep.body }}</p>
            </template>

            <!-- ── EXAMPLE ──────────────────────────────────────── -->
            <template v-else-if="displayStep?.type === 'example'">
              <h1 class="step-title">{{ displayStep.title }}</h1>
              <p class="step-body">{{ displayStep.body }}</p>
              <div v-if="displayStep.callout" class="callout">
                <p class="callout-text">{{ displayStep.callout }}</p>
              </div>
            </template>

            <!-- ── QUESTION (main + confirm) ─────────────────────── -->
            <template v-else-if="displayStep?.type === 'question'">
              <p class="question-text">{{ displayStep.body }}</p>

              <div class="options-list">
                <button
                  v-for="(opt, i) in shuffledOptions"
                  :key="opt"
                  class="option"
                  :class="{
                    'option--hover-ready': !hasAnswered && narratingOptionIndex === null,
                    'option--narrating':   !hasAnswered && narratingOptionIndex === i,
                    'option--correct':     hasAnswered && opt === displayStep.answer,
                    'option--wrong':       hasAnswered && selectedAnswer === opt && opt !== displayStep.answer,
                    'option--dim':         hasAnswered && selectedAnswer !== opt && opt !== displayStep.answer,
                  }"
                  :disabled="hasAnswered"
                  @click="selectOption(opt)"
                >
                  <span class="option-badge">{{ LABELS[i] }}</span>
                  <span class="option-text">{{ opt }}</span>
                  <span v-if="hasAnswered && opt === displayStep.answer" class="option-mark option-mark--check">
                    <svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/>
                    </svg>
                  </span>
                  <span v-else-if="hasAnswered && selectedAnswer === opt && opt !== displayStep.answer" class="option-mark option-mark--x">
                    <svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 18L18 6M6 6l12 12"/>
                    </svg>
                  </span>
                </button>
              </div>

              <!-- main question feedback -->
              <Transition name="feedback">
                <div
                  v-if="lessonState.mode === 'main' && showMainExplanation"
                  class="explanation"
                  :class="isCorrect ? 'explanation--correct' : 'explanation--wrong'"
                >
                  <span class="explanation-verdict">{{ isCorrect ? 'Correct!' : 'Not quite.' }}</span>
                  {{ displayStep.explanation }}
                </div>
                <div
                  v-else-if="lessonState.mode === 'confirm' && hasAnswered"
                  class="explanation"
                  :class="isCorrect ? 'explanation--correct' : 'explanation--wrong'"
                >
                  <span class="explanation-verdict">{{ isCorrect ? "You've got it." : "Not quite." }}</span>
                  {{ displayStep.explanation }}
                </div>
                <p v-else-if="!hasAnswered" class="answer-hint">Select an answer to continue</p>
              </Transition>
            </template>

            <!-- ── SUMMARY ──────────────────────────────────────── -->
            <template v-else-if="displayStep?.type === 'summary'">
              <div class="summary-icon">✦</div>
              <h1 class="step-title summary-title">{{ displayStep.title }}</h1>

              <ul class="bullet-list">
                <li
                  v-for="(b, i) in displayStep.bullets"
                  :key="i"
                  class="bullet-item"
                  :style="{ animationDelay: `${i * 150}ms` }"
                >
                  <span class="bullet-check">
                    <svg width="10" height="10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" d="M5 13l4 4L19 7"/>
                    </svg>
                  </span>
                  <span>{{ b }}</span>
                </li>
              </ul>

              <div v-if="lessonDone" class="done-msg">
                <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="color:#10B981">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
                </svg>
                Lesson complete — great work.
                <button class="back-btn" @click="goBack">← Back to course</button>
              </div>

              <button
                v-if="!lessonDone && !completeError"
                class="summary-cta"
                :disabled="completing"
                @click="completeLesson"
              >
                {{ completing ? 'Saving…' : 'Complete Lesson ✓' }}
              </button>

              <button v-if="completeError" class="summary-cta" @click="completeLesson">Retry ✓</button>
            </template>

          </div>
        </Transition>
      </div>
    </main>

    <!-- ── CONTINUE FOOTER ───────────────────────────────────────────────── -->
    <template v-if="!showKaraoke && lessonState.mode !== 'transition' && lessonState.mode !== 'hardStop'">

      <!-- non-question, non-summary steps -->
      <div
        v-if="!lessonPending && !lessonError && lesson && displayStep?.type !== 'question' && displayStep?.type !== 'summary'"
        class="continue-zone"
      >
        <Transition name="continue-btn">
          <button v-if="canContinue" class="continue-pill" @click="advance">
            Continue
            <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 5l7 7-7 7"/>
            </svg>
          </button>
        </Transition>
      </div>

      <!-- question step — show continue once explanation visible; in focus mode only show for wrong+no-branch (correct and wrong+branch auto-advance) -->
      <div
        v-if="!lessonPending && !lessonError && lesson && displayStep?.type === 'question' && showMainExplanation && (!focusMode || !isCorrect)"
        class="continue-zone"
      >
        <Transition name="continue-btn">
          <button class="continue-pill" @click="focusMode ? continueAfterQuestion() : advance()">
            {{ isLastMainStep && lessonState.mode === 'main' ? 'Finish Lesson' : 'Continue' }}
            <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 5l7 7-7 7"/>
            </svg>
          </button>
        </Transition>
      </div>

    </template>

    <!-- ── BRANCH LOADING OVERLAY ────────────────────────────────────────── -->
    <Transition name="fullscreen-fade">
      <div v-if="branchOverlayVisible" class="branch-loading-overlay">
        <div class="branch-loading-inner">
          <template v-if="!branchOverlayError">
            <div class="branch-loading-pulse">
              <div class="branch-loading-ring" />
              <div class="branch-loading-icon">
                <svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="#92400E">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
                    d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
                </svg>
              </div>
            </div>
            <p class="branch-loading-text">Preparing personalised feedback…</p>
          </template>
          <template v-else>
            <p class="branch-loading-error">We couldn't prepare feedback for this answer.</p>
            <button class="branch-loading-skip" @click="() => { branchOverlayVisible = false; pendingBranchStep = null; }">
              Continue
            </button>
          </template>
        </div>
      </div>
    </Transition>

  </div>
</template>

<style scoped>
.lesson-shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background-color: var(--step-bg, #FAFAF8);
  transition: background-color 300ms ease;
  position: relative;
}

.accent-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: var(--accent, #6366F1);
  transition: background 300ms ease;
  z-index: 50;
}

/* ── top bar ─────────────────────────────────────────────────────────────── */
.topbar {
  position: sticky;
  top: 4px;
  z-index: 40;
  background: oklch(97.5% 0.008 78 / 0.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid oklch(88% 0.022 73);
  height: 56px;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 1.5rem;
}

.topbar-back {
  color: oklch(20% 0.020 55);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  opacity: 0.5;
  transition: opacity 0.15s;
}
.topbar-back:hover:not(:disabled) { opacity: 1; }
.topbar-back:disabled { opacity: 0.2; cursor: default; }

.topbar-exit {
  color: oklch(20% 0.020 55);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  opacity: 0.4;
  transition: opacity 0.15s;
  margin-left: 4px;
}
.topbar-exit:hover { opacity: 0.85; }

.progress-segments {
  flex: 1;
  display: flex;
  gap: 4px;
  align-items: center;
}
.seg {
  flex: 1;
  height: 3px;
  border-radius: 4px;
  transition: background 0.35s ease;
}
.seg--filled { background: oklch(54% 0.140 44); }
.seg--empty  { background: oklch(88% 0.022 73); }

.topbar-count {
  font-size: 12px;
  color: oklch(60% 0.016 68);
  flex-shrink: 0;
  min-width: 3rem;
  text-align: right;
  letter-spacing: 0.04em;
}

.focus-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid oklch(82% 0.026 70);
  border-radius: 20px;
  padding: 5px 12px;
  cursor: pointer;
  color: oklch(60% 0.016 68);
  font-size: 12px;
  font-weight: 500;
  flex-shrink: 0;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.focus-toggle:hover { border-color: oklch(54% 0.140 44); color: oklch(54% 0.140 44); }
.focus-toggle--on { border-color: oklch(54% 0.140 44); background: oklch(93% 0.040 76); color: oklch(54% 0.140 44); }
.focus-label { white-space: nowrap; }

/* ── karaoke overlay ─────────────────────────────────────────────────────── */
.karaoke-overlay {
  position: fixed;
  inset: 0;
  z-index: 35;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.karaoke-text {
  font-family: "Lora", Georgia, serif;
  font-size: 2.5rem;
  font-weight: 600;
  color: oklch(20% 0.020 55);
  max-width: 500px;
  text-align: center;
  line-height: 1.3;
}

.karaoke-text--question {
  font-size: 1.25rem;
  font-weight: 400;
  color: #6B7280;
  font-style: italic;
}

.karaoke-skip {
  position: fixed;
  bottom: max(2rem, env(safe-area-inset-bottom));
  right: 2rem;
  background: none;
  border: none;
  cursor: pointer;
  color: #9CA3AF;
  opacity: 0.45;
  transition: opacity 0.15s;
  padding: 8px;
}
.karaoke-skip:hover { opacity: 0.85; }

.karaoke-fade-enter-active { transition: opacity 0.2s ease; }
.karaoke-fade-leave-active { transition: opacity 0.15s ease; }
.karaoke-fade-enter-from,
.karaoke-fade-leave-to { opacity: 0; }

/* ── branch transition screen ────────────────────────────────────────────── */
.branch-transition-screen {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: #FFFBEB;
  display: flex;
  align-items: center;
  justify-content: center;
}

.transition-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  padding: 2rem;
  text-align: center;
}

.transition-icon {
  opacity: 0.8;
  animation: float 2s ease-in-out infinite;
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(-6px); }
}

.transition-text {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.5rem;
  color: #92400E;
  line-height: 1.4;
}

/* ── hard stop screen ────────────────────────────────────────────────────── */
.hard-stop-screen {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: #FFFBEB;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.hard-stop-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  max-width: 480px;
  text-align: center;
}

.hard-stop-icon {
  opacity: 0.75;
}

.hard-stop-text {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.1875rem;
  line-height: 1.7;
  color: #92400E;
}

.hard-stop-btn {
  background: #F59E0B;
  color: #ffffff;
  border: none;
  border-radius: 9999px;
  padding: 0.85rem 1.75rem;
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
  max-width: 360px;
  line-height: 1.4;
}
.hard-stop-btn:hover { background: #D97706; }
.hard-stop-btn:active { transform: scale(0.98); }

.fullscreen-fade-enter-active { transition: opacity 0.3s ease; }
.fullscreen-fade-leave-active { transition: opacity 0.2s ease; }
.fullscreen-fade-enter-from,
.fullscreen-fade-leave-to { opacity: 0; }

/* ── confirm accent bar ──────────────────────────────────────────────────── */
.confirm-accent-bar {
  height: 3px;
  background: #F59E0B;
  border-radius: 9999px;
  margin-bottom: 1.5rem;
  width: 48px;
}

/* ── main ────────────────────────────────────────────────────────────────── */
.lesson-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 0 8rem;
}

.step-container {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 0 24px;
}

.step-inner { width: 100%; }

.type-label {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: 1rem;
  transition: color 300ms ease;
  font-weight: 600;
}

.step-title {
  font-family: "Lora", Georgia, serif;
  font-size: 2rem;
  line-height: 1.2;
  color: oklch(20% 0.020 55);
  margin-bottom: 1.5rem;
  font-weight: 600;
}

.step-body {
  font-family: "DM Sans", system-ui, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.8;
  color: oklch(35% 0.018 58);
  font-weight: 400;
}

.callout {
  margin-top: 1.75rem;
  padding: 1rem 1.25rem;
  background: #FEF3C7;
  border-left: 3px solid #F59E0B;
  border-radius: 8px;
}
.callout-text {
  font-family: Georgia, serif;
  font-size: 0.95rem;
  line-height: 1.65;
  color: #92400E;
  font-style: italic;
  margin: 0;
}

.question-text {
  font-family: "Lora", Georgia, serif;
  font-size: 1.25rem;
  line-height: 1.55;
  color: oklch(20% 0.020 55);
  font-weight: 500;
  font-style: italic;
  margin-bottom: 2rem;
}

.options-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 1.25rem;
}

.option {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  width: 100%;
  text-align: left;
  padding: 18px 20px;
  border: 1.5px solid #C7D2FE;
  border-radius: 12px;
  background: #ffffff;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.2s, opacity 0.2s;
  font-size: 1rem;
}
.option--hover-ready:hover { border-color: #6366F1; background: #F5F3FF; }
.option--narrating { border-color: #6366F1; background: #EEF2FF; box-shadow: 0 0 0 3px rgba(99,102,241,0.15); }
.option--correct { border-color: #10B981; background: #D1FAE5; animation: spring-correct 0.3s ease-out; }
.option--wrong   { border-color: #EF4444; background: #FEE2E2; }
.option--dim     { opacity: 0.38; }

@keyframes spring-correct {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.025); }
  70%  { transform: scale(0.995); }
  100% { transform: scale(1); }
}

.option-badge {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.68rem;
  font-weight: 500;
  color: #6366F1;
  background: #EEF2FF;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s;
}
.option--correct .option-badge { background: #A7F3D0; color: #065F46; }
.option--wrong   .option-badge { background: #FECACA; color: #991B1B; }
.option--dim     .option-badge { background: #F3F4F6; color: #9CA3AF; }

.option-text {
  flex: 1;
  font-family: Georgia, serif;
  font-size: 1rem;
  color: #374151;
  line-height: 1.5;
}

.option-mark {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.option-mark--check { background: #059669; color: #ffffff; }
.option-mark--x     { background: #DC2626; color: #ffffff; }

.answer-hint {
  font-family: Georgia, serif;
  font-size: 0.8125rem;
  color: #9CA3AF;
  font-style: italic;
  text-align: center;
  margin-top: 0.5rem;
}

.explanation {
  margin-top: 1.25rem;
  padding: 1rem 1.25rem;
  border-radius: 8px;
  font-family: Georgia, serif;
  font-size: 0.9375rem;
  line-height: 1.65;
}
.explanation--correct { background: #ECFDF5; border-left: 3px solid #10B981; color: #065F46; }
.explanation--wrong   { background: #FFF1F2; border-left: 3px solid #EF4444; color: #9F1239; }
.explanation-verdict {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  display: block;
  margin-bottom: 0.4rem;
}

.summary-icon {
  font-size: 2.5rem;
  color: #10B981;
  display: block;
  margin-bottom: 1.25rem;
  line-height: 1;
}
.summary-title { font-size: 1.75rem; }

.bullet-list {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  margin-top: 1.5rem;
  margin-bottom: 2rem;
  padding: 0;
  list-style: none;
}

.bullet-item {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  font-family: Georgia, serif;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: #374151;
  opacity: 0;
  transform: translateY(8px);
  animation: bullet-in 0.35s ease-out forwards;
}

@keyframes bullet-in {
  to { opacity: 1; transform: translateY(0); }
}

.bullet-check {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #10B981;
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 2px;
}

.summary-cta {
  width: 100%;
  height: 52px;
  background: #10B981;
  color: #ffffff;
  border: none;
  border-radius: 12px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: opacity 0.15s, transform 0.1s;
  margin-top: 0.5rem;
}
.summary-cta:hover:not(:disabled) { opacity: 0.88; }
.summary-cta:active:not(:disabled) { transform: scale(0.99); }
.summary-cta:disabled { opacity: 0.5; cursor: not-allowed; }

.done-msg {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  color: #6B7280;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.back-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  color: #6366F1;
  padding: 0;
  text-decoration: underline;
  transition: opacity 0.15s;
}
.back-btn:hover { opacity: 0.7; }

/* ── loading / error ─────────────────────────────────────────────────────── */
.state-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 5rem 0;
  width: 100%;
}

.indigo-dots { display: flex; gap: 5px; }
.indigo-dots span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #6366F1;
  animation: dot-bounce 1.2s ease-in-out infinite;
}
.indigo-dots span:nth-child(2) { animation-delay: 0.15s; }
.indigo-dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes dot-bounce {
  0%, 80%, 100% { opacity: 0.25; transform: scale(0.75); }
  40%            { opacity: 1;    transform: scale(1); }
}

.state-label {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  color: #6B7280;
  text-transform: uppercase;
}

.err-state {
  max-width: 480px;
  font-family: Georgia, serif;
  font-size: 0.9375rem;
  color: #B91C1C;
  background: #FFF1F2;
  border: 1px solid #FECACA;
  border-radius: 8px;
  padding: 1.25rem 1.5rem;
}

/* ── continue zone ───────────────────────────────────────────────────────── */
.continue-zone {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  display: flex;
  justify-content: center;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
  padding-top: 2.5rem;
  background: linear-gradient(to top, var(--step-bg, #FAFAF8) 55%, transparent);
  pointer-events: none;
  z-index: 30;
  transition: background 300ms ease;
}

.continue-pill {
  pointer-events: all;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  width: 280px;
  height: 52px;
  justify-content: center;
  background: #6366F1;
  color: #ffffff;
  border: none;
  border-radius: 9999px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  cursor: pointer;
  box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3);
  transition: opacity 0.15s, transform 0.15s, box-shadow 0.15s;
}
.continue-pill:hover { opacity: 0.92; transform: scale(0.98); box-shadow: 0 2px 12px rgba(99, 102, 241, 0.25); }
.continue-pill:active { transform: scale(0.96); }

/* ── transitions ─────────────────────────────────────────────────────────── */
.step-enter-active { transition: opacity 0.2s ease; }
.step-leave-active { transition: opacity 0.15s ease; }
.step-enter-from, .step-leave-to { opacity: 0; }

.feedback-enter-active { transition: opacity 0.25s ease, transform 0.25s ease; }
.feedback-leave-active { transition: opacity 0.15s ease; }
.feedback-enter-from { opacity: 0; transform: translateY(8px); }
.feedback-leave-to   { opacity: 0; }

.continue-btn-enter-active { transition: opacity 0.2s ease, transform 0.2s ease; }
.continue-btn-leave-active { transition: opacity 0.15s ease; }
.continue-btn-enter-from { opacity: 0; transform: translateY(8px); }
.continue-btn-leave-to   { opacity: 0; }

@media (max-width: 500px) {
  .lesson-main { padding: 2rem 0 7rem; }
  .step-title { font-size: 1.625rem; }
  .step-body { font-size: 1rem; }
  .focus-label { display: none; }
}

/* ── JIT generation loading screen ──────────────────────────────────────── */
.gen-loading-screen {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #FAFAF8;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gen-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  padding: 2rem;
  max-width: 380px;
  width: 100%;
}

.gen-logo {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.5rem;
  color: oklch(35% 0.018 58);
  letter-spacing: -0.02em;
}

.gen-logo span {
  color: oklch(54% 0.140 44);
}

.gen-pulse-ring {
  position: relative;
  width: 72px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gen-pulse-circle {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid oklch(82% 0.055 76);
  animation: gen-pulse 2s ease-in-out infinite;
}

@keyframes gen-pulse {
  0%, 100% { transform: scale(1); opacity: 0.8; }
  50% { transform: scale(1.15); opacity: 0.3; }
}

.gen-pulse-dot {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: oklch(95% 0.030 76);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid oklch(85% 0.045 72);
}

.gen-tip {
  font-family: Georgia, serif;
  font-size: 0.9375rem;
  color: oklch(50% 0.016 65);
  font-style: italic;
  text-align: center;
  line-height: 1.6;
  min-height: 2.5rem;
}

.gen-stepper {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: 100%;
}

.gen-step {
  display: flex;
  align-items: center;
  gap: 0.875rem;
}

.gen-step-node {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gen-node-done {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: oklch(54% 0.140 44);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gen-node-active {
  position: relative;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gen-node-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid oklch(54% 0.140 44);
  animation: gen-pulse 1.5s ease-in-out infinite;
}

.gen-node-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: oklch(54% 0.140 44);
}

.gen-node-pending {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: oklch(82% 0.022 73);
}

.gen-step-label {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  transition: color 0.3s;
}

.gen-step--done .gen-step-label { color: oklch(54% 0.140 44); }
.gen-step--active .gen-step-label { color: oklch(35% 0.018 58); font-weight: 600; }
.gen-step--pending .gen-step-label { color: oklch(72% 0.016 65); }

.gen-error-msg {
  font-family: Georgia, serif;
  font-size: 0.9375rem;
  color: oklch(45% 0.18 25);
  text-align: center;
  line-height: 1.6;
}

.gen-retry-btn {
  background: oklch(54% 0.140 44);
  color: #fff;
  border: none;
  border-radius: 9999px;
  padding: 0.75rem 2rem;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.875rem;
  cursor: pointer;
  transition: opacity 0.15s;
}
.gen-retry-btn:hover { opacity: 0.85; }

.tip-enter-active { transition: opacity 0.3s ease; }
.tip-leave-active { transition: opacity 0.2s ease; }
.tip-enter-from, .tip-leave-to { opacity: 0; }

/* ── branch loading overlay ──────────────────────────────────────────────── */
.branch-loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(255, 251, 235, 0.88);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.branch-loading-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  padding: 2rem;
  text-align: center;
}

.branch-loading-pulse {
  position: relative;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.branch-loading-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid #F59E0B;
  animation: gen-pulse 2s ease-in-out infinite;
}

.branch-loading-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #FEF3C7;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid #FCD34D;
}

.branch-loading-text {
  font-family: "DM Serif Display", Georgia, serif;
  font-size: 1.0625rem;
  color: #92400E;
  line-height: 1.5;
}

.branch-loading-error {
  font-family: Georgia, serif;
  font-size: 0.9375rem;
  color: #92400E;
  line-height: 1.6;
}

.branch-loading-skip {
  background: #F59E0B;
  color: #fff;
  border: none;
  border-radius: 9999px;
  padding: 0.75rem 2rem;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.875rem;
  cursor: pointer;
  transition: opacity 0.15s;
}
.branch-loading-skip:hover { opacity: 0.85; }
</style>
