/* ============================================================
   ENERGY LITERACY TEST — in-site React view (buildless)
   A gamified 10-question quiz. Mounted at #/energy-literacy-test.
   This is the SAME logic as the standalone quiz/ app (questions,
   fair-draw engine, scoring, persistence) ported to the site's
   buildless React + Babel setup and themed via energy-literacy-test.css.
   SOURCE OF TRUTH for content: content/energy_literacy_quiz_battery.md
   ============================================================ */
(function () {
  const { useState, useEffect, useRef, useMemo, useCallback } = React;

  /* ===================== CONFIG ===================== */
  const ASOF = "February 2025";
  const COURSE_URL = "https://example.com/energy-course";
  const SESSION_SIZE = 10;
  const DIFFICULTY_TARGET = { easy: 3, medium: 4, hard: 3 };
  const CATEGORIES = ["scale", "hidden_energy", "density", "rated_vs_real", "losses", "units"];

  const LEVEL_BANDS = [
    { min: 0, max: 2, tag: "Energy Novice",
      vibeLine: "Right now, energy is mostly just the stuff that comes out of the wall — and honestly, that's where almost everyone starts, because none of this is taught.",
      courseHook: "You just found out how much there is to know — the fun part. The course starts exactly where you are." },
    { min: 3, max: 4, tag: "Energy Apprentice",
      vibeLine: "You've got the everyday picture, but the scale of things still catches you off guard. Good instincts, rough numbers.",
      courseHook: "The course gives you the figures to back the instincts — and to win the dinner-table argument." },
    { min: 5, max: 6, tag: "Energy Literate",
      vibeLine: "You can smell a wrong number, and a slick headline rarely fools you. You think in comparisons, not slogans.",
      courseHook: "Literacy is the floor. The course takes you to fluency — reading any energy story in seconds." },
    { min: 7, max: 8, tag: "Energy Enthusiast",
      vibeLine: "You see the system: where energy hides, why labels mislead, which trade-offs are real. People come to you to explain the news.",
      courseHook: "You already think like an analyst. The course sharpens the subtleties that separate good takes from great ones." },
    { min: 9, max: 10, tag: "Energy Master",
      vibeLine: "Density, scale, conversion losses, rated-versus-real — you grasp them by instinct. Rare air.",
      courseHook: "You clearly didn't need this quiz. The course is where you go from knowing the answers to being the person others quote." },
  ];
  function levelForScore(score) {
    return LEVEL_BANDS.find((b) => score >= b.min && score <= b.max) || LEVEL_BANDS[0];
  }

  const CATEGORY_META = {
    scale: { label: "Scale", session: "Session 1 (Energy Foundations) + the city-scale material" },
    hidden_energy: { label: "Hidden Energy", session: "Session 1 (units, scale literacy)" },
    units: { label: "Units", session: "Session 1 (units, scale literacy)" },
    density: { label: "Density", session: "Sessions 3–4 (oil, gas/LNG) + nuclear (Session 7)" },
    rated_vs_real: { label: "Rated-vs-Real", session: "Session 5 (power markets) + 6 (renewables/storage)" },
    losses: { label: "Losses", session: "Session 1 (primary vs final) + Session 6b (storage round-trips)" },
  };

  /* ===================== QUESTIONS (40) ===================== */
  const QUESTIONS = [
    { id: "A1", category: "scale", difficulty: "medium", flags: ["caveat"],
      prompt: "A single fully loaded gas tanker ship carries chemical energy equal to about how many Hiroshima-sized bombs?",
      options: [{ text: "Less than one" }, { text: "About five" }, { text: "About seventy", correct: true }, { text: "About five thousand" }],
      why: "Dozens of times the Hiroshima yield — but released slowly and usefully as fuel over weeks, not in microseconds. A lesson in hydrocarbon energy density, not danger." },
    { id: "A2", category: "scale", difficulty: "medium", flags: ["time"],
      prompt: "All the world's Bitcoin mining together uses more electricity in a year than which entire country?",
      options: [{ text: "China" }, { text: "Argentina", correct: true }, { text: "None — it's a tiny amount" }, { text: "The United States" }],
      why: "A single digital currency rivals a mid-sized nation's whole electricity use." },
    { id: "A3", category: "scale", difficulty: "hard", flags: ["caveat"],
      prompt: "Can a single modern solar park generate as much electricity in a year as the Hoover Dam?",
      options: [{ text: "No — not even close" }, { text: "Only about half" }, { text: "Yes — the biggest ones match or beat it", correct: true }, { text: "Solar produces nothing useful at that scale" }],
      why: "The largest parks match or exceed Hoover; China's biggest complexes make several Hoovers' worth. The dam still wins on releasing power exactly when needed." },
    { id: "A4", category: "scale", difficulty: "medium", flags: ["time"],
      prompt: "One of the big new AI data centers, running full-time, can use as much electricity as a city of about…",
      options: [{ text: "7,000 people" }, { text: "70,000 people" }, { text: "700,000 people", correct: true }, { text: "70 million people" }],
      why: "A single campus can match Seattle or Denver; the largest planned ones rival entire US states. Power, not chips, is becoming AI’s bottleneck." },
    { id: "A5", category: "scale", difficulty: "hard", flags: [],
      prompt: "To power a city of half a million people using only solar panels, the panels would cover an area about the size of…",
      options: [{ text: "A few city blocks" }, { text: "A small town (around 50 km²)", correct: true }, { text: "An entire small country" }, { text: "It simply can't be done" }],
      why: "More than \"just rooftops,\" far less than \"pave the whole state.\" The truth sits in a surprising middle." },
    { id: "A6", category: "scale", difficulty: "medium", flags: ["time"],
      prompt: "Add up every data center on Earth. Together they use about what share of the world's electricity?",
      options: [{ text: "About a tenth of a percent" }, { text: "About 1.5%", correct: true }, { text: "About 15%" }, { text: "About half" }],
      why: "Despite the headlines, all the world's data centers were about 1.5% of global electricity in 2024 — small today, but the fastest-growing slice." },
    { id: "A7", category: "scale", difficulty: "medium", flags: [],
      prompt: "How much of the world's energy still comes from fossil fuels — coal, oil, and gas combined?",
      options: [{ text: "About a quarter" }, { text: "About half" }, { text: "About 80%", correct: true }, { text: "Almost 100%" }],
      why: "After decades of transition talk, fossil fuels still supply roughly four-fifths of all energy. The shift is real but the base is enormous." },

    { id: "B1", category: "hidden_energy", difficulty: "easy", flags: [],
      prompt: "Over a full year, charging your phone every single day uses about as much energy as…",
      options: [{ text: "One flight across the country" }, { text: "Taking four hot showers", correct: true }, { text: "Driving 1,000 miles" }, { text: "A month of heating your home" }],
      why: "A year of charging is a rounding error — about four showers’ worth. The device in your hand is tiny; the hot water and the car are where energy goes." },
    { id: "B2", category: "hidden_energy", difficulty: "medium", flags: ["us", "caveat"],
      prompt: "Flying one-way across the country — Los Angeles to New York — uses about how much energy per passenger?",
      options: [{ text: "A few hours of your home's electricity" }, { text: "About a week of it" }, { text: "More than a month of it", correct: true }, { text: "A whole year of it" }],
      why: "One coast-to-coast seat burns more than a month of home electricity. Flying is among the most energy-intensive things an ordinary person does." },
    { id: "B3", category: "hidden_energy", difficulty: "easy", flags: ["us"],
      prompt: "In a typical home over a full year, which of these uses the most energy?",
      options: [{ text: "Lights, screens, and electronics" }, { text: "Heating the space and the water", correct: true }, { text: "The refrigerator" }, { text: "Cooking" }],
      why: "Gadgets are visible so we blame them — but heating rooms and water dwarfs everything else. The biggest user is the one you never see." },
    { id: "B4", category: "hidden_energy", difficulty: "medium", flags: [],
      prompt: "The average American uses about how much more total energy than the average European?",
      options: [{ text: "About the same" }, { text: "Around 50% more" }, { text: "About twice as much", correct: true }, { text: "About ten times as much" }],
      why: "Two rich modern societies, double the energy — bigger homes, more driving, more air conditioning. \"Developed\" isn’t one fixed number." },
    { id: "B5", category: "hidden_energy", difficulty: "medium", flags: ["us"],
      prompt: "Which single everyday activity uses the most energy?",
      options: [{ text: "Charging your phone for a whole month" }, { text: "One hot shower" }, { text: "One hot-water load of laundry" }, { text: "Driving 20 miles to work and back", correct: true }],
      why: "The commute wins by a landslide — more than a month of charging, a shower, and a laundry load combined. Moving ourselves dominates personal energy." },
    { id: "B6", category: "hidden_energy", difficulty: "medium", flags: ["us"],
      prompt: "Which uses more energy: one cross-country flight, or running your refrigerator for an entire year?",
      options: [{ text: "The single flight", correct: true }, { text: "A year of the fridge" }, { text: "They're about equal" }, { text: "Neither uses much" }],
      why: "The fridge runs 24/7 all year, yet one flight still beats it — about three fridge-years in a single coast-to-coast trip. Continuous-but-small loses to brief-but-intense." },
    { id: "B7", category: "hidden_energy", difficulty: "medium", flags: ["us"],
      prompt: "Across the US on average, does a home use more energy heating in winter or cooling in summer?",
      options: [{ text: "Heating", correct: true }, { text: "Cooling" }, { text: "Exactly equal" }, { text: "Neither — homes are climate-neutral" }],
      why: "Air conditioning gets the attention, but nationwide, heating still uses more home energy than cooling. (Flip this in the hottest states.)" },

    { id: "C1", category: "density", difficulty: "medium", flags: ["caveat"],
      prompt: "The energy in one barrel of oil — worth around $80 — could do the same physical work as one person laboring for about…",
      options: [{ text: "A single day" }, { text: "One month" }, { text: "A decade", correct: true }, { text: "A century" }],
      why: "A barrel holds roughly eleven years of human muscle, bought for the price of a nice dinner. Every barrel is a small army of invisible workers." },
    { id: "C2", category: "density", difficulty: "medium", flags: [],
      prompt: "A single kilogram of uranium fuel can release as much energy as burning roughly how much coal?",
      options: [{ text: "About the same — one kilogram" }, { text: "About a hundred kilograms" }, { text: "About 14 tons", correct: true }, { text: "About a thousand tons" }],
      why: "A fist-sized lump holds a truckload of coal's energy — why one small nuclear site powers a region, and why the waste is tiny in volume." },
    { id: "C3", category: "density", difficulty: "hard", flags: [],
      prompt: "Pound for pound, gasoline holds about how much more energy than a phone or electric-car battery?",
      options: [{ text: "About the same" }, { text: "About 5 times more" }, { text: "About 50 times more", correct: true }, { text: "About 5,000 times more" }],
      why: "Batteries feel powerful, but gasoline packs ~50× the energy per pound. This single gap is why electric planes and long-haul electric trucks are so hard." },
    { id: "C4", category: "density", difficulty: "medium", flags: [],
      prompt: "Hydrogen actually holds more energy per pound than any common fuel. So what's its big problem?",
      options: [{ text: "It's too heavy" }, { text: "It takes up an enormous amount of space", correct: true }, { text: "It's radioactive" }, { text: "It has almost no energy" }],
      why: "Hydrogen is the lightest fuel by weight but spreads out hugely by volume — storing and shipping it is the real challenge, not its energy." },
    { id: "C5", category: "density", difficulty: "medium", flags: [],
      prompt: "A supertanker of oil arrives at a city of half a million people. Burned for everything — power, heat, transport — it lasts about…",
      options: [{ text: "One day" }, { text: "One week" }, { text: "About seven weeks", correct: true }, { text: "A full year" }],
      why: "One tanker runs a whole mid-sized city for nearly two months. The scale of a single shipment is hard to feel until you measure it in city-time." },
    { id: "C6", category: "density", difficulty: "medium", flags: [],
      prompt: "Which holds more energy: a teaspoon of gasoline, or a fully charged AA battery?",
      options: [{ text: "The teaspoon of gasoline", correct: true }, { text: "The AA battery" }, { text: "They're about equal" }, { text: "Neither holds usable energy" }],
      why: "A mere teaspoon of gasoline beats a whole AA battery by about ten times. Liquid fuels are astonishingly concentrated." },

    { id: "D1", category: "rated_vs_real", difficulty: "medium", flags: ["caveat"],
      prompt: "A solar farm and a nuclear plant are both rated at the same maximum power. Over a whole year, the solar farm actually produces about…",
      options: [{ text: "The same amount of electricity" }, { text: "Three-quarters as much" }, { text: "A quarter as much", correct: true }, { text: "Twice as much" }],
      why: "\"Rated power\" is a perfect sunny noon. Averaged over nights, clouds, and winter, solar delivers ~a quarter of its label; nuclear over ninety percent." },
    { id: "D2", category: "rated_vs_real", difficulty: "medium", flags: [],
      prompt: "A news story describes a new battery as \"100 megawatts.\" What does that number NOT tell you?",
      options: [{ text: "Nothing — it tells you everything" }, { text: "How long the battery can actually run", correct: true }, { text: "Where it was built" }, { text: "Who made it" }],
      why: "Megawatts is the width of the tap; it says nothing about the size of the tank. A \"100 MW\" battery might last one hour or eight." },
    { id: "D3", category: "rated_vs_real", difficulty: "medium", flags: [],
      prompt: "A large wind farm spreads turbines across a wide landscape. How much of that land is actually taken up by turbines and roads?",
      options: [{ text: "Almost all of it" }, { text: "About half" }, { text: "About 1% — the rest stays farmland or pasture", correct: true }, { text: "None at all" }],
      why: "Wind farms look land-hungry but use ~1% of their area; crops and cattle carry on between the towers. The footprint is mostly air." },
    { id: "D4", category: "rated_vs_real", difficulty: "medium", flags: [],
      prompt: "Which produces electricity more of the time: wind turbines out at sea, or on land?",
      options: [{ text: "At sea", correct: true }, { text: "On land" }, { text: "Exactly the same" }, { text: "Neither produces reliably" }],
      why: "Offshore winds are stronger and steadier, so sea-based turbines run productively more hours — at the cost of being far harder to build." },
    { id: "D5", category: "rated_vs_real", difficulty: "hard", flags: [],
      prompt: "France runs its many nuclear plants at a lower yearly output than the US does. Why?",
      options: [{ text: "The French plants are old and broken" }, { text: "They deliberately turn them down to match demand", correct: true }, { text: "French people barely use electricity" }, { text: "It's too cold for nuclear" }],
      why: "Counterintuitively, a lower number here is a feature: France throttles its reactors up and down to follow demand, something most countries don’t ask of nuclear." },
    { id: "D6", category: "rated_vs_real", difficulty: "medium", flags: [],
      prompt: "Some gas power plants run only about a tenth of the time. Why do they exist at all?",
      options: [{ text: "They're failed projects" }, { text: "To cover rare spikes in demand", correct: true }, { text: "Purely for decoration" }, { text: "They're permanently broken" }],
      why: "\"Peaker\" plants sit idle most of the year and earn their keep in the few hours of extreme demand — expensive insurance the grid needs." },
    { id: "D7", category: "rated_vs_real", difficulty: "hard", flags: [],
      prompt: "A four-hour battery is great for the evening peak. What is it useless for?",
      options: [{ text: "A hot summer afternoon" }, { text: "Several cloudy, windless days in a row", correct: true }, { text: "The morning rush" }, { text: "Any real problem" }],
      why: "Batteries shift power within a day; they can’t bridge a multi-day lull in wind and sun. That gap is why fully renewable grids still need a firm backup." },

    { id: "E1", category: "losses", difficulty: "medium", flags: [],
      prompt: "When a power plant burns coal, roughly how much of that energy actually reaches your home as electricity?",
      options: [{ text: "Almost all of it" }, { text: "About two-thirds" }, { text: "About a third", correct: true }, { text: "About a tenth" }],
      why: "Most of the coal’s energy escapes as waste heat up the towers; only ~a third arrives as electricity. This quietly distorts a lot of energy statistics." },
    { id: "E2", category: "losses", difficulty: "medium", flags: ["caveat"],
      prompt: "To travel the same distance, an electric car uses about how much energy compared with a gasoline car?",
      options: [{ text: "About the same" }, { text: "Slightly less" }, { text: "Three to four times less", correct: true }, { text: "A hundred times less" }],
      why: "A gasoline engine wastes most of its fuel as heat; an electric motor wastes little. Even counting the power station, the EV does the trip on a fraction of the energy." },
    { id: "E3", category: "losses", difficulty: "hard", flags: [],
      prompt: "You read that renewables are \"7% of world energy.\" Another reputable source says \"16%.\" What's going on?",
      options: [{ text: "One of them is simply wrong" }, { text: "They measured different years" }, { text: "They count the wasted heat from fossil and nuclear plants differently", correct: true }, { text: "One counts only solar" }],
      why: "Both are honest, answering different questions. One credits renewables only for clean electricity; the other also credits the wasted heat they help avoid — nearly doubling the figure." },
    { id: "E4", category: "losses", difficulty: "medium", flags: [],
      prompt: "When you store electricity in a battery and take it back out later, you get back about…",
      options: [{ text: "All of it" }, { text: "About 85%", correct: true }, { text: "About half" }, { text: "About a tenth" }],
      why: "Modern batteries are remarkably efficient — you lose only ~15% on the round trip. (In the real world, with cooling and other losses, it’s closer to 75–80%.)" },
    { id: "E5", category: "losses", difficulty: "hard", flags: [],
      prompt: "If you turn electricity into hydrogen and later turn that hydrogen back into electricity, you get back about…",
      options: [{ text: "Almost all of it" }, { text: "About two-thirds" }, { text: "About a third", correct: true }, { text: "None of it" }],
      why: "Each conversion bleeds energy, so the full hydrogen round trip returns only ~a third — far worse than a battery. That’s why hydrogen is aimed at jobs batteries can’t do, not everyday storage." },
    { id: "E6", category: "losses", difficulty: "easy", flags: [],
      prompt: "An old-style incandescent light bulb turned most of its electricity into…",
      options: [{ text: "Light" }, { text: "Heat", correct: true }, { text: "Sound" }, { text: "Nothing at all" }],
      why: "Old bulbs were tiny heaters that happened to glow — ~90% of the energy became heat, not light. LEDs flipped that, which is why they sip power." },
    { id: "E7", category: "losses", difficulty: "medium", flags: [],
      prompt: "The world's largest \"battery\" isn't made of lithium at all. What is it?",
      options: [{ text: "Giant lithium farms" }, { text: "Water pumped uphill into reservoirs", correct: true }, { text: "Compressed air in caves" }, { text: "Spinning flywheels" }],
      why: "Most of the world's stored grid energy is water pumped uphill when power is cheap and released through turbines when it's needed — a battery made of gravity." },

    { id: "F1", category: "units", difficulty: "easy", flags: [],
      prompt: "One unit of electricity — the \"kilowatt-hour\" your bill counts — is roughly enough to…",
      options: [{ text: "Drive an electric car across town (about 6 km)", correct: true }, { text: "Power your whole house for a day" }, { text: "Charge your phone a thousand times" }, { text: "Run your oven for a week" }],
      why: "A single unit drives an efficient EV a few kilometers, or runs an LED for about four days — a small, human-sized amount your home burns through dozens of times a day." },
    { id: "F2", category: "units", difficulty: "medium", flags: ["us"],
      prompt: "If you tried to power a typical home by pedaling bicycle generators, how many people pedaling non-stop would it take?",
      options: [{ text: "Just one" }, { text: "About three" }, { text: "About fifteen", correct: true }, { text: "About a hundred" }],
      why: "A hard-pedaling person makes less than a hair dryer needs. Keeping a home running takes ~fifteen people pedaling around the clock — a vivid measure of what electricity does for us." },
    { id: "F3", category: "units", difficulty: "easy", flags: ["us"],
      prompt: "Your home draws electricity at an average rate roughly equal to…",
      options: [{ text: "One hair dryer running constantly", correct: true }, { text: "A single lightbulb" }, { text: "An entire factory" }, { text: "A phone charger" }],
      why: "Averaged over day and night, a home pulls about as much as one hair dryer left on — modest, but never switched off." },
    { id: "F4", category: "units", difficulty: "medium", flags: ["us"],
      prompt: "One megawatt of power — a modest power plant — is roughly enough for how many average homes?",
      options: [{ text: "About 1" }, { text: "About 800", correct: true }, { text: "About a million" }, { text: "Just a streetlight" }],
      why: "A megawatt sounds huge but covers under a thousand homes. It's a useful yardstick: divide any plant's size by ~800 to picture the homes behind it." },
    { id: "F5", category: "units", difficulty: "medium", flags: ["us"],
      prompt: "A gigawatt — the size of a big power plant — could power about how many homes?",
      options: [{ text: "About 800" }, { text: "About 8,000" }, { text: "About 800,000", correct: true }, { text: "About 80 million" }],
      why: "A gigawatt is a thousand megawatts — roughly 800,000 homes. It’s also the unit AI companies now use to describe single data-center campuses." },
    { id: "F6", category: "units", difficulty: "easy", flags: [],
      prompt: "A 60-watt bulb and a 60-watt phone charger are each left on for one hour. Which uses more energy?",
      options: [{ text: "The bulb" }, { text: "The charger" }, { text: "Exactly the same", correct: true }, { text: "It depends on the color" }],
      why: "Same watts for the same time means the same energy, whatever the device. Watts measure the rate; the job it does doesn’t change the bill." },
  ];

  /* ===================== RNG ===================== */
  function shuffle(arr, rng) {
    const out = arr.slice();
    for (let i = out.length - 1; i > 0; i--) {
      const j = Math.floor(rng() * (i + 1));
      const t = out[i]; out[i] = out[j]; out[j] = t;
    }
    return out;
  }

  /* ===================== DRAW ENGINE ===================== */
  function emptyServed() { return { lastServed: {}, tick: 0, mediumSeen: [] }; }

  function recency(served, id) { return served.lastServed[id] !== undefined ? served.lastServed[id] : -1; }
  function freshSort(pool, served, rng) {
    return shuffle(pool, rng).sort((a, b) => recency(served, a.id) - recency(served, b.id));
  }
  function takeFresh(pool, n, used) {
    const picked = [];
    for (const q of pool) { if (picked.length === n) break; if (!used.has(q.id)) picked.push(q); }
    return picked;
  }
  function withShuffledOptions(q, rng) {
    return Object.assign({}, q, { options: shuffle(q.options, rng).map((o) => Object.assign({}, o)) });
  }

  function drawSession(bank, served, rng) {
    rng = rng || Math.random;
    const target = Object.assign({}, DIFFICULTY_TARGET);
    const DIFFS = ["easy", "medium", "hard"];

    const allMediums = bank.filter((q) => q.difficulty === "medium");
    let mediumSeen = new Set(served.mediumSeen);
    let unseenMediums = allMediums.filter((q) => !mediumSeen.has(q.id));
    if (unseenMediums.length < target.medium) { mediumSeen = new Set(); unseenMediums = allMediums; }

    const easyPool = freshSort(bank.filter((q) => q.difficulty === "easy"), served, rng);
    const hardPool = freshSort(bank.filter((q) => q.difficulty === "hard"), served, rng);
    const mediumPool = freshSort(unseenMediums, served, rng);
    const poolFor = (d) => (d === "easy" ? easyPool : d === "hard" ? hardPool : mediumPool);

    const cats = shuffle(CATEGORIES, rng);
    const freshestOf = (cat, d) => poolFor(d).find((q) => q.category === cat);

    let primaries = null;
    let primaryCounts = { easy: 0, medium: 0, hard: 0 };

    const fillable = (counts, usedPrimary) => {
      const usedIds = new Set(usedPrimary.map((q) => q.id));
      return DIFFS.every((d) => {
        const leftover = target[d] - counts[d];
        if (leftover < 0) return false;
        const available = poolFor(d).filter((q) => !usedIds.has(q.id)).length;
        return available >= leftover;
      });
    };
    const backtrack = (i, chosen, counts) => {
      if (i === cats.length) {
        if (!fillable(counts, chosen)) return false;
        primaries = chosen.slice(); primaryCounts = Object.assign({}, counts); return true;
      }
      const cat = cats[i];
      for (const d of shuffle(DIFFS, rng)) {
        if (counts[d] + 1 > target[d]) continue;
        const q = freshestOf(cat, d);
        if (!q) continue;
        chosen.push(q); counts[d]++;
        if (backtrack(i + 1, chosen, counts)) return true;
        counts[d]--; chosen.pop();
      }
      return false;
    };
    if (!backtrack(0, [], { easy: 0, medium: 0, hard: 0 }) || !primaries) {
      throw new Error("drawSession: no feasible assignment");
    }

    const used = new Set(primaries.map((q) => q.id));
    const wildcards = [];
    for (const d of DIFFS) {
      const leftover = target[d] - primaryCounts[d];
      const picks = takeFresh(poolFor(d), leftover, used);
      picks.forEach((q) => used.add(q.id));
      wildcards.push.apply(wildcards, picks);
    }
    const selection = primaries.concat(wildcards);

    const tick = served.tick + 1;
    const lastServed = Object.assign({}, served.lastServed);
    selection.forEach((q) => { lastServed[q.id] = tick; });
    selection.forEach((q) => { if (q.difficulty === "medium") mediumSeen.add(q.id); });

    const presented = shuffle(selection, rng).map((q) => withShuffledOptions(q, rng));
    return { questions: presented, served: { lastServed, tick, mediumSeen: Array.from(mediumSeen) } };
  }

  /* ===================== SCORING ===================== */
  function emptyPerCategory() {
    return CATEGORIES.reduce((a, c) => { a[c] = { correct: 0, total: 0 }; return a; }, {});
  }
  function gradeSession(questions, chosen) {
    const perCategory = emptyPerCategory();
    let score = 0;
    questions.forEach((q, i) => {
      perCategory[q.category].total += 1;
      const correctText = (q.options.find((o) => o.correct) || {}).text;
      if (chosen[i] != null && chosen[i] === correctText) {
        score += 1; perCategory[q.category].correct += 1;
      }
    });
    return { score, total: questions.length, level: levelForScore(score), perCategory };
  }
  function weakestFromTally(per) {
    const rows = CATEGORIES.map((category) => {
      const t = per[category];
      return { category, correct: t.correct, total: t.total, accuracy: t.total > 0 ? t.correct / t.total : Infinity };
    }).filter((r) => r.total > 0);
    if (rows.length === 0) return null;
    rows.sort((a, b) => a.accuracy - b.accuracy || b.total - a.total);
    return rows[0];
  }
  function mergePerCategory(tallies) {
    const out = emptyPerCategory();
    tallies.forEach((t) => CATEGORIES.forEach((c) => { out[c].correct += t[c].correct; out[c].total += t[c].total; }));
    return out;
  }

  /* ===================== STORAGE ===================== */
  const SERVED_KEY = "quiz:served";
  const HISTORY_KEY = "quiz:history";
  const EMAIL_KEY = "quiz:email";
  function getStore() {
    try {
      const s = window.localStorage;
      s.setItem("__quiz_probe__", "1"); s.removeItem("__quiz_probe__");
      return s;
    } catch (e) { return null; }
  }
  function readJSON(key, fallback) {
    const s = getStore(); if (!s) return fallback;
    try { const raw = s.getItem(key); return raw ? JSON.parse(raw) : fallback; } catch (e) { return fallback; }
  }
  function writeJSON(key, value) {
    const s = getStore(); if (!s) return;
    try { s.setItem(key, JSON.stringify(value)); } catch (e) {}
  }
  function loadServed() {
    const s = readJSON(SERVED_KEY, {});
    return { lastServed: s.lastServed || {}, tick: typeof s.tick === "number" ? s.tick : 0, mediumSeen: Array.isArray(s.mediumSeen) ? s.mediumSeen : [] };
  }
  function saveServed(served) { writeJSON(SERVED_KEY, served); }
  function loadHistory() { const h = readJSON(HISTORY_KEY, []); return Array.isArray(h) ? h : []; }
  function appendHistory(entry) { const h = loadHistory(); h.push(entry); writeJSON(HISTORY_KEY, h); return h; }
  function cumulativeProfile(history) {
    history = history || loadHistory();
    const perCategory = mergePerCategory(history.map((h) => h.perCategoryCorrect));
    return { plays: history.length, perCategory, weakest: weakestFromTally(perCategory) };
  }
  function exploredCount(served) { served = served || loadServed(); return Object.keys(served.lastServed).length; }
  function lastLevel(history) { history = history || loadHistory(); return history.length ? history[history.length - 1].level : null; }
  function loadEmail() { const s = getStore(); if (!s) return ""; try { return s.getItem(EMAIL_KEY) || ""; } catch (e) { return ""; } }
  function saveEmail(email) { const s = getStore(); if (!s) return; try { s.setItem(EMAIL_KEY, email); } catch (e) {} }
  function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((email || "").trim()); }

  /* ===================== UI ===================== */
  function IntroScreen({ onBegin, lastLevelTag, explored, total, initialEmail }) {
    const h = useRef(null);
    const [email, setEmail] = useState(initialEmail || "");
    const [error, setError] = useState("");
    const valid = isValidEmail(email);
    useEffect(() => { if (h.current) h.current.focus(); }, []);

    const submit = (e) => {
      if (e) e.preventDefault();
      if (!valid) { setError("Please enter a valid email address."); return; }
      setError("");
      onBegin(email.trim());
    };

    return (
      <section className="elt-intro" aria-labelledby="elt-intro-h">
        <div className="elt-eyebrow">Energy Literacy · Self-assessment</div>
        <h1 id="elt-intro-h" ref={h} tabIndex={-1} className="elt-title">
          How energy-literate <em>are you</em>, really?
        </h1>
        <p className="elt-lede">Ten quick questions. No jargon. Find out where you land — and exactly what to learn next.</p>
        {lastLevelTag && (
          <p className="elt-resume">Last time you were an <strong>{lastLevelTag.replace("Energy ", "")}</strong> — beat it?</p>
        )}
        <form className="elt-email-form" onSubmit={submit} noValidate>
          <label className="elt-email-label" htmlFor="elt-email">Enter your email to begin</label>
          <input
            id="elt-email"
            type="email"
            className="elt-email-input"
            placeholder="you@example.com"
            value={email}
            autoComplete="email"
            aria-invalid={error ? "true" : "false"}
            aria-describedby={error ? "elt-email-error" : undefined}
            onChange={(e) => { setEmail(e.target.value); if (error) setError(""); }}
          />
          {error && <p id="elt-email-error" className="elt-email-error" role="alert">{error}</p>}
          <button type="submit" className="elt-cta" disabled={!valid}><span>Begin</span><span>→</span></button>
        </form>
        <p className="elt-explored" aria-live="polite">You've explored {explored} of {total} questions</p>
      </section>
    );
  }

  function QuestionScreen({ question, index, total, selected, onSelect, onNext, isLast }) {
    const answered = selected != null;
    const stem = useRef(null);
    const nextBtn = useRef(null);
    const letters = ["A", "B", "C", "D"];
    const correctText = (question.options.find((o) => o.correct) || {}).text;
    const isTimed = question.flags.indexOf("time") !== -1;
    const progress = ((index + (answered ? 1 : 0)) / total) * 100;

    useEffect(() => { if (stem.current) stem.current.focus(); }, [question.id]);
    useEffect(() => { if (answered && nextBtn.current) nextBtn.current.focus(); }, [answered]);
    useEffect(() => {
      if (answered) return;
      const onKey = (e) => {
        const n = Number(e.key);
        if (n >= 1 && n <= question.options.length) onSelect(question.options[n - 1].text);
      };
      window.addEventListener("keydown", onKey);
      return () => window.removeEventListener("keydown", onKey);
    }, [answered, question, onSelect]);

    return (
      <section className="elt-q" aria-labelledby="elt-q-h">
        <div className="elt-q-meta">
          <span>Question {index + 1} / {total}</span>
          <span>{CATEGORY_META[question.category].label}</span>
        </div>
        <div className="elt-progress" role="progressbar" aria-valuemin={0} aria-valuemax={total} aria-valuenow={index + (answered ? 1 : 0)} aria-label="Quiz progress">
          <div className="elt-progress-fill" style={{ width: progress + "%" }} />
        </div>

        <h2 id="elt-q-h" ref={stem} tabIndex={-1} className="elt-stem">{question.prompt}</h2>

        <ul className="elt-options">
          {question.options.map((opt, i) => {
            const isChosen = selected === opt.text;
            const isCorrect = opt.text === correctText;
            let cls = "elt-option";
            if (answered) {
              if (isCorrect) cls += " is-correct";
              else if (isChosen) cls += " is-wrong";
              else cls += " is-dim";
            }
            return (
              <li key={opt.text}>
                <button type="button" className={cls} disabled={answered} aria-pressed={isChosen} onClick={() => onSelect(opt.text)}>
                  <span className="elt-option-letter" aria-hidden="true">{letters[i]}</span>
                  <span className="elt-option-text">{opt.text}</span>
                  <span className="elt-option-status" aria-hidden="true">{answered && isCorrect ? "✓" : answered && isChosen ? "✗" : ""}</span>
                </button>
              </li>
            );
          })}
        </ul>

        <div className="elt-reveal" aria-live="polite">
          {answered ? (
            <div>
              <p className={"elt-reveal-label " + (selected === correctText ? "ok" : "bad")}>
                {selected === correctText ? "Correct" : "Not quite"}
                {isTimed && <span className="elt-asof">as of {ASOF}</span>}
              </p>
              <p className="elt-why">{question.why}</p>
            </div>
          ) : (
            <div className="elt-reveal-empty">Pick an answer to see why.</div>
          )}
        </div>

        <div className="elt-next">
          <button ref={nextBtn} type="button" className="elt-cta" disabled={!answered} onClick={onNext}>
            <span>{isLast ? "See my result" : "Next"}</span><span>→</span>
          </button>
        </div>
      </section>
    );
  }

  function ResultScreen({ result, weakest, onRetake, onShare }) {
    const h = useRef(null);
    useEffect(() => { if (h.current) h.current.focus(); }, []);
    const shortTag = result.level.tag.replace("Energy ", "");
    return (
      <section className="elt-result" aria-labelledby="elt-result-h">
        <div className="elt-result-head">
          <div className="elt-result-eyebrow">You're an</div>
          <h1 id="elt-result-h" ref={h} tabIndex={-1} className="elt-level">Energy <em>{shortTag}</em></h1>
          <div className="elt-score" aria-label={"You scored " + result.score + " out of " + result.total}>{result.score} / {result.total}</div>
        </div>

        <div className="elt-panel"><p className="elt-vibe">{result.level.vibeLine}</p></div>

        <div className="elt-panel elt-weakest">
          <p className="elt-weakest-label">Here's exactly what you'd learn next</p>
          <p className="elt-weakest-body">
            Your weakest area {weakest.source === "cumulative" ? "across " + weakest.plays + " plays" : "this round"} is{" "}
            <strong>{weakest.meta.label}</strong> <span>({weakest.correct}/{weakest.total} correct)</span>.
            Go straight to <strong>{weakest.meta.session}</strong>.
          </p>
        </div>

        <div className="elt-panel elt-hook">
          <p className="elt-hook-text">{result.level.courseHook}</p>
          <a className="elt-cta" href={COURSE_URL} target="_blank" rel="noopener noreferrer"><span>Start the course</span><span>→</span></a>
        </div>

        <div className="elt-actions">
          <button type="button" className="elt-btn elt-btn-ghost" onClick={onRetake}>Retake · fresh questions</button>
          <button type="button" className="elt-btn" onClick={onShare}>Share</button>
        </div>
      </section>
    );
  }

  function ShareCard({ levelTag, score, total, onClose }) {
    const canvasRef = useRef(null);
    const closeRef = useRef(null);
    const [pngUrl, setPngUrl] = useState("");
    const [copied, setCopied] = useState(false);
    const shortTag = levelTag.replace("Energy ", "");
    const shareText = "I'm an Energy " + shortTag + " — what are you? I scored " + score + "/" + total + " on the Energy Literacy quiz. " + COURSE_URL;

    useEffect(() => {
      const canvas = canvasRef.current; if (!canvas) return;
      const ctx = canvas.getContext("2d"); if (!ctx) return;
      const W = 1080, H = 1080; canvas.width = W; canvas.height = H;
      const g = ctx.createLinearGradient(0, 0, W, H);
      g.addColorStop(0, "#1a1205"); g.addColorStop(1, "#0b0c0d");
      ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);
      ctx.textAlign = "center";
      ctx.fillStyle = "rgba(255,255,255,0.55)"; ctx.font = "700 40px 'JetBrains Mono', monospace";
      ctx.fillText("ENERGY LITERACY", W / 2, 220);
      ctx.fillStyle = "#ffffff"; ctx.font = "500 92px Georgia, serif";
      ctx.fillText("I'm an Energy", W / 2, 460);
      ctx.fillStyle = "#e0a23b"; ctx.font = "600 132px Georgia, serif";
      ctx.fillText(shortTag, W / 2, 600);
      ctx.fillStyle = "#ffffff"; ctx.font = "500 52px 'JetBrains Mono', monospace";
      ctx.fillText("Scored " + score + " / " + total, W / 2, 720);
      ctx.fillStyle = "rgba(255,255,255,0.85)"; ctx.font = "500 56px Georgia, serif";
      ctx.fillText("— what are you?", W / 2, 860);
      ctx.fillStyle = "rgba(255,255,255,0.4)"; ctx.font = "400 34px 'JetBrains Mono', monospace";
      ctx.fillText("TAKE THE QUIZ", W / 2, 980);
      try { setPngUrl(canvas.toDataURL("image/png")); } catch (e) { setPngUrl(""); }
    }, [shortTag, score, total]);

    useEffect(() => {
      if (closeRef.current) closeRef.current.focus();
      const onKey = (e) => { if (e.key === "Escape") onClose(); };
      window.addEventListener("keydown", onKey);
      return () => window.removeEventListener("keydown", onKey);
    }, [onClose]);

    const copy = () => {
      const done = () => { setCopied(true); setTimeout(() => setCopied(false), 1800); };
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(shareText).then(done, done);
      } else {
        const ta = document.createElement("textarea"); ta.value = shareText; document.body.appendChild(ta); ta.select();
        try { document.execCommand("copy"); } catch (e) {} document.body.removeChild(ta); done();
      }
    };

    return (
      <div className="elt-share-overlay" role="dialog" aria-modal="true" aria-labelledby="elt-share-h" onClick={onClose}>
        <div className="elt-share-card" onClick={(e) => e.stopPropagation()}>
          <p id="elt-share-h" className="elt-share-title">Share your level</p>
          <canvas ref={canvasRef} className="elt-share-canvas" aria-label={"Share card: I'm an Energy " + shortTag + ", scored " + score + " of " + total} />
          <p className="elt-share-text">{shareText}</p>
          <div className="elt-share-actions">
            <button type="button" className="elt-btn" onClick={copy}>{copied ? "Copied!" : "Copy text"}</button>
            {pngUrl && <a className="elt-btn elt-btn-ghost" href={pngUrl} download={"energy-" + shortTag.toLowerCase() + ".png"}>Download image</a>}
          </div>
          <button ref={closeRef} type="button" className="elt-share-close" onClick={onClose}>Close</button>
        </div>
      </div>
    );
  }

  function EnergyLiteracyTestView() {
    const [history, setHistory] = useState(() => loadHistory());
    const [explored, setExplored] = useState(() => exploredCount());
    const [screen, setScreen] = useState("intro");
    const [session, setSession] = useState([]);
    const [index, setIndex] = useState(0);
    const [answers, setAnswers] = useState([]);
    const [result, setResult] = useState(null);
    const [showShare, setShowShare] = useState(false);

    const startSession = useCallback((email) => {
      if (email) saveEmail(email.trim());
      const draw = drawSession(QUESTIONS, loadServed(), Math.random);
      saveServed(draw.served);
      setSession(draw.questions);
      setAnswers(new Array(draw.questions.length).fill(null));
      setIndex(0); setResult(null); setScreen("question");
      setExplored(exploredCount());
    }, []);

    const selectAnswer = useCallback((text) => {
      setAnswers((prev) => { if (prev[index] != null) return prev; const next = prev.slice(); next[index] = text; return next; });
    }, [index]);

    const finish = useCallback((finalAnswers) => {
      const res = gradeSession(session, finalAnswers);
      const newHistory = appendHistory({ timestamp: Date.now(), score: res.score, perCategoryCorrect: res.perCategory, level: res.level.tag });
      setHistory(newHistory); setResult(res); setScreen("result");
    }, [session]);

    const next = useCallback(() => {
      if (index < session.length - 1) setIndex((i) => i + 1);
      else finish(answers);
    }, [index, session.length, answers, finish]);

    const weakest = useMemo(() => {
      if (!result) return null;
      const plays = history.length;
      if (plays >= 2) {
        const cum = cumulativeProfile(history);
        if (cum.weakest) return Object.assign({ source: "cumulative", plays, meta: CATEGORY_META[cum.weakest.category] }, cum.weakest);
      }
      const w = weakestFromTally(result.perCategory);
      if (!w) return null;
      return Object.assign({ source: "run", plays, meta: CATEGORY_META[w.category] }, w);
    }, [result, history]);

    return (
      <div className="elt">
        <div className="elt-bar">
          <span className="elt-bar-brand"><span className="elt-bar-dot" />Energy Literacy test</span>
          <span className="elt-bar-right">10 questions · fair draw</span>
        </div>

        {screen === "intro" && (
          <IntroScreen onBegin={startSession} lastLevelTag={lastLevel(history)} explored={explored} total={QUESTIONS.length} initialEmail={loadEmail()} />
        )}
        {screen === "question" && session[index] && (
          <QuestionScreen key={index} question={session[index]} index={index} total={session.length}
            selected={answers[index]} onSelect={selectAnswer} onNext={next} isLast={index === session.length - 1} />
        )}
        {screen === "result" && result && weakest && (
          <ResultScreen result={result} weakest={weakest} onRetake={startSession} onShare={() => setShowShare(true)} />
        )}

        {showShare && result && (
          <ShareCard levelTag={result.level.tag} score={result.score} total={result.total} onClose={() => setShowShare(false)} />
        )}
      </div>
    );
  }

  window.EnergyLiteracyTestView = EnergyLiteracyTestView;
})();
