/* ============================================================
   Cases — Case Calculator (app.mesper.de/cases)
   Meta / Google / TikTok · Lead Gen / E-Commerce · DACH / US.
   Benchmarks als Startwerte (Quelle sichtbar), alles editierbar,
   Company-Templates aus eigenen Cases, Export als 16:9 Slide-PDF.
   Math lives in cases-engine.js (window.CasesEngine, unit-tested).
   ============================================================ */
const { useState, useEffect, useRef, useMemo } = React;
const CE = window.CasesEngine;

/* ---- i18n (mirrors the Docs pattern: shared mesper.lang key) ---- */
const CASES_DICT = {
  en: {
    cases: "Cases", templates: "Templates", newCase: "New case", back: "Back",
    newCaseSub: "A budget in, a click, lead and cost forecast out.",
    clientPh: "e.g. Acme Inc.", industryHint: "Loads the matching industry benchmark – falls back to General if none exists.",
    tplValues: "Benchmark values", tplScope: "Scope", tplIdentity: "Identity", tplSource: "Source & confidence",
    client: "Client / prospect", vertical: "Vertical", region: "Region", platforms: "Platforms",
    leadgen: "Lead Gen", ecom: "E-Commerce", dach: "DACH", us: "US",
    industry: "Industry", allIndustries: "All industries", allPlatforms: "All platforms",
    allVerticals: "All verticals", allRegions: "All regions",
    generalFallback: "No industry benchmark for this combination yet – general set loaded.",
    create: "Create", cancel: "Cancel", save: "Save", saved: "Saved", saving: "Saving…",
    archive: "Archive", archiveCase: "Archive this case?", archived: "Archived.",
    lastEdited: "Edited", by: "by", emptyCases: "No cases yet – build the first forecast.",
    emptyCasesHint: "A case takes a budget and turns it into a click, lead and cost forecast your prospect understands.",
    loadError: "Could not load. If this is the first run, apply case-schema.sql in Supabase first.",
    setup: "Setup", budgetDay: "Budget / day", runtime: "Runtime", days: "days",
    adSpend: "Ad spend", estClicks: "Est. clicks", baseCpc: "CPC (base)", cpcSpread: "CPC spread",
    cpcLow: "CPC low", cpcHigh: "CPC high",
    cvrScenarios: "CVR scenarios", worst: "Worst", mid: "Mid", best: "Best",
    cvr: "CVR", leads: "Leads", cpa: "CPA", cpl: "CPL", clicks: "Clicks",
    funnel: "Funnel", stage: "Stage", rate: "Rate", addStage: "Add stage", preset: "Load preset…",
    presetSql: "Lead > SQL > Opportunity > Close", presetWebinar: "Lead > Webinar > Opportunity > Close",
    stageName: "Stage name", costPer: "Cost per", perMonth: "per runtime",
    orders: "Orders", cpo: "CPO", revenue: "Revenue (net)", roas: "ROAS (net)",
    aov: "AOV net (client)", margin: "CM margin (client)", breakEven: "Break-even ROAS (net)",
    contribution: "Contribution after ad spend", clientEconomics: "Client economics",
    aovHint: "Always from the client – nothing is assumed. AOV = avg. order value net (excl. VAT). CM margin = contribution margin before marketing: after COGS, shipping & payment – not just product margin.",
    ecomLocked: "Enter the client's net AOV to unlock revenue & ROAS.",
    addPlatform: "Add platform", removeBlock: "Remove platform", removeBlockQ: "Remove this platform block?",
    template: "Template", benchmark: "Benchmark", company: "Company case",
    resetToTemplate: "Reset to template", resetQ: "Overwrite current values with the template?",
    benchmarkNote: "Benchmarks orient – the client's own numbers decide.",
    sourceLabel: "Source", stand: "as of", confidence: "Confidence",
    comparison: "Platform comparison", scenarioMid: "mid case",
    export: "Export", print: "Print / save PDF", close: "Close",
    exportTitle: "Performance Forecast", assumptions: "Assumptions", scenarios: "Scenarios",
    matrixTitle: "Click & cost scenarios", funnelTitle: "From lead to",
    econTitle: "Revenue & profitability", compTitle: "Platform comparison",
    disclaimer: "Forecast based on market benchmarks and experience. Actual results depend on creative, offer and tracking.",
    newTemplate: "New template", name: "Name", notes: "Notes", kind: "Type",
    tplEmpty: "No templates for this combination yet.",
    tplBenchmarks: "Benchmarks", tplCompany: "Company cases", edit: "Edit",
    deleteStage: "Remove", noBlocks: "Add the first platform to start calculating.",
    monthly: "monthly", total: "total", scenarioNote: "worst = high CPC × low CVR · mid = base · best = low CPC × high CVR",
    perDay: "/day", untitled: "Untitled", pageOf: "Page", readGuide: "How to read this",
    funnelGuide: "Every stage applies a conversion rate to the one before it. The cost per final stage is what one closed deal costs at the given budget.",
    econGuide: "Calculated net of VAT: break-even ROAS = 1 / CM margin. Above it, every ad euro adds contribution.",
    values: "Values", missingParams: "Enter budget, CPC and CVR values to calculate.",
    open: "Open", restore: "Restore", showArchived: "Show archived", hideArchived: "Hide archived",
    project: "Project", projects: "Projects", newProject: "New project",
    newProjectSub: "One client, several cases – create the project and start with Case 1.",
    projectPh: "e.g. Acme Inc.", caseWord: "Case", duplicate: "Duplicate", duplicateCase: "Duplicate case",
    subIndustry: "Sub-industry", noSub: "– none –", allSubIndustries: "All sub-industries",
    compare: "Compare", addPlatformTab: "Add", inputs: "Inputs",
    archiveProject: "Archive project", archiveProjectQ: "Archive this project?",
    emptyProjects: "No projects yet – create the first one.",
    emptyProjectsHint: "A project groups all case variants for one client – case 1, 2, 3 with different assumptions.",
    resultsEmpty: "Enter a budget to see the click, lead and cost forecast here.",
    budgetTotal: "Total budget", casesCount: "Cases", projectDeepLink: "Open project",
    newCaseSub2: "A fresh variant in this project – set the budget and the numbers appear.",
  },
  de: {
    cases: "Cases", templates: "Templates", newCase: "Neuer Case", back: "Zurück",
    newCaseSub: "Budget rein, Klick-, Lead- und Kosten-Forecast raus.",
    clientPh: "z.B. Acme GmbH", industryHint: "Lädt das passende Branchen-Benchmark – fällt auf Allgemein zurück, wenn keins existiert.",
    tplValues: "Benchmark-Werte", tplScope: "Geltungsbereich", tplIdentity: "Bezeichnung", tplSource: "Quelle & Confidence",
    client: "Kunde / Prospect", vertical: "Vertical", region: "Region", platforms: "Plattformen",
    leadgen: "Lead Gen", ecom: "E-Commerce", dach: "DACH", us: "US",
    industry: "Branche", allIndustries: "Alle Branchen", allPlatforms: "Alle Plattformen",
    allVerticals: "Alle Verticals", allRegions: "Alle Regionen",
    generalFallback: "Für diese Kombination gibt es noch kein Branchen-Benchmark – Allgemein geladen.",
    create: "Anlegen", cancel: "Abbrechen", save: "Speichern", saved: "Gespeichert", saving: "Speichert…",
    archive: "Archivieren", archiveCase: "Diesen Case archivieren?", archived: "Archiviert.",
    lastEdited: "Bearbeitet", by: "von", emptyCases: "Noch keine Cases – bau den ersten Forecast.",
    emptyCasesHint: "Ein Case macht aus einem Budget einen Klick-, Lead- und Kosten-Forecast, den dein Prospect versteht.",
    loadError: "Konnte nicht laden. Beim ersten Start zuerst case-schema.sql in Supabase ausführen.",
    setup: "Setup", budgetDay: "Budget / Tag", runtime: "Laufzeit", days: "Tage",
    adSpend: "Ad Spend", estClicks: "Klicks (geschätzt)", baseCpc: "CPC (Basis)", cpcSpread: "CPC-Spanne",
    cpcLow: "CPC niedrig", cpcHigh: "CPC hoch",
    cvrScenarios: "CVR-Szenarien", worst: "Worst", mid: "Mid", best: "Best",
    cvr: "CVR", leads: "Leads", cpa: "CPA", cpl: "CPL", clicks: "Klicks",
    funnel: "Funnel", stage: "Stufe", rate: "Rate", addStage: "Stufe hinzufügen", preset: "Preset laden…",
    presetSql: "Lead > SQL > Opportunity > Abschluss", presetWebinar: "Lead > Webinar > Opportunity > Abschluss",
    stageName: "Name der Stufe", costPer: "Kosten pro", perMonth: "pro Laufzeit",
    orders: "Bestellungen", cpo: "CPO", revenue: "Umsatz (netto)", roas: "ROAS (netto)",
    aov: "AOV netto (Kunde)", margin: "DB-Marge (Kunde)", breakEven: "Break-even ROAS (netto)",
    contribution: "DB nach Ad Spend", clientEconomics: "Kunden-Ökonomie",
    aovHint: "Kommt immer vom Kunden – nichts wird angenommen. AOV = Ø Bestellwert netto (ohne MwSt.). DB-Marge = Deckungsbeitrag vor Marketing: nach Wareneinsatz, Versand & Payment – nicht nur Produktmarge.",
    ecomLocked: "AOV netto des Kunden eintragen, um Umsatz & ROAS zu rechnen.",
    addPlatform: "Plattform hinzufügen", removeBlock: "Plattform entfernen", removeBlockQ: "Diesen Plattform-Block entfernen?",
    template: "Template", benchmark: "Benchmark", company: "Company Case",
    resetToTemplate: "Auf Template zurücksetzen", resetQ: "Aktuelle Werte mit dem Template überschreiben?",
    benchmarkNote: "Benchmarks orientieren – die Account-Zahlen des Kunden entscheiden.",
    sourceLabel: "Quelle", stand: "Stand", confidence: "Confidence",
    comparison: "Plattform-Vergleich", scenarioMid: "Mid Case",
    export: "Export", print: "Drucken / PDF sichern", close: "Schließen",
    exportTitle: "Performance Forecast", assumptions: "Annahmen", scenarios: "Szenarien",
    matrixTitle: "Klick- & Kosten-Szenarien", funnelTitle: "Vom Lead bis", econTitle: "Umsatz & Profitabilität",
    compTitle: "Plattform-Vergleich",
    disclaimer: "Forecast auf Basis von Markt-Benchmarks und Erfahrungswerten. Tatsächliche Ergebnisse hängen von Creative, Angebot und Tracking ab.",
    newTemplate: "Neues Template", name: "Name", notes: "Notizen", kind: "Typ",
    tplEmpty: "Für diese Kombination gibt es noch kein Template.",
    tplBenchmarks: "Benchmarks", tplCompany: "Company Cases", edit: "Bearbeiten",
    deleteStage: "Entfernen", noBlocks: "Füg die erste Plattform hinzu, um zu rechnen.",
    monthly: "monatlich", total: "gesamt", scenarioNote: "Worst = hoher CPC × niedrige CVR · Mid = Basis · Best = niedriger CPC × hohe CVR",
    perDay: "/Tag", untitled: "Ohne Titel", pageOf: "Seite", readGuide: "So liest du das",
    funnelGuide: "Jede Stufe wendet ihre Rate auf die vorherige an. Die Kosten der letzten Stufe zeigen, was ein Abschluss beim gesetzten Budget kostet.",
    econGuide: "Rechnet netto (ohne MwSt.): Break-even ROAS = 1 / DB-Marge. Alles darüber liefert Deckungsbeitrag.",
    values: "Werte", missingParams: "Budget, CPC- und CVR-Werte eintragen, um zu rechnen.",
    open: "Öffnen", restore: "Wiederherstellen", showArchived: "Archiv zeigen", hideArchived: "Archiv ausblenden",
    project: "Projekt", projects: "Projekte", newProject: "Neues Projekt",
    newProjectSub: "Ein Kunde, mehrere Cases – leg das Projekt an und starte mit Case 1.",
    projectPh: "z.B. Acme GmbH", caseWord: "Case", duplicate: "Duplizieren", duplicateCase: "Case duplizieren",
    subIndustry: "Sub-Branche", noSub: "– keine –", allSubIndustries: "Alle Sub-Branchen",
    compare: "Vergleich", addPlatformTab: "Hinzufügen", inputs: "Eingaben",
    archiveProject: "Projekt archivieren", archiveProjectQ: "Dieses Projekt archivieren?",
    emptyProjects: "Noch keine Projekte – leg das erste an.",
    emptyProjectsHint: "Ein Projekt bündelt alle Case-Varianten für einen Kunden – Case 1, 2, 3 mit unterschiedlichen Annahmen.",
    resultsEmpty: "Budget eintragen, dann erscheint hier der Klick-, Lead- und Kosten-Forecast.",
    budgetTotal: "Budget gesamt", casesCount: "Cases", projectDeepLink: "Projekt öffnen",
    newCaseSub2: "Eine frische Variante in diesem Projekt – Budget setzen und die Zahlen erscheinen.",
  },
};
const CaseI18n = React.createContext({ lang: "de", t: (k) => k });
function useCT() { return React.useContext(CaseI18n); }

/* ---- constants ---- */
const CASE_PLATFORMS = {
  meta:   { label: "Meta Ads",   fg: "#0B5FD9", bg: "#E4EEFD" },
  google: { label: "Google Ads", fg: "#B0500F", bg: "#FDEBDD" },
  tiktok: { label: "TikTok Ads", fg: "#0E7E86", bg: "#DFF7F8" },
};
const CASE_SCN = {
  worst: { fg: "var(--st-declined)", bg: "var(--st-declined-soft)" },
  mid:   { fg: "var(--st-pending)",  bg: "var(--st-pending-soft)" },
  best:  { fg: "var(--st-approved)", bg: "var(--st-approved-soft)" },
};

/* ---- Industries: Benchmarks gelten nur fuer ihre Branche.
   'general' ist die branchenuebergreifende Baseline und zugleich
   der Fallback, wenn es (noch) kein Branchen-Set gibt. ---- */
const CASE_INDUSTRIES = [
  ["general",        { de: "Allgemein",                 en: "General" },                 null],
  ["beauty",         { de: "Beauty & Kosmetik",         en: "Beauty & Cosmetics" },      "ecom"],
  ["fashion",        { de: "Fashion & Apparel",         en: "Fashion & Apparel" },       "ecom"],
  ["home",           { de: "Home & Living",             en: "Home & Living" },           "ecom"],
  ["food",           { de: "Food & Beverage",           en: "Food & Beverage" },         "ecom"],
  ["health",         { de: "Health & Supplements",      en: "Health & Supplements" },    "ecom"],
  ["electronics",    { de: "Electronics & Gadgets",     en: "Electronics & Gadgets" },   "ecom"],
  ["gaming",         { de: "Gaming & Entertainment",    en: "Gaming & Entertainment" },  "ecom"],
  ["b2b_saas",       { de: "B2B Software & SaaS",       en: "B2B Software & SaaS" },     "leadgen"],
  ["b2b_services",   { de: "B2B Dienstleistungen",      en: "B2B Services" },            "leadgen"],
  ["local_services", { de: "Lokale Dienstleistungen",   en: "Local Services" },          "leadgen"],
  ["finance",        { de: "Finanzen & Versicherungen", en: "Finance & Insurance" },     "leadgen"],
  ["education",      { de: "Coaching & Bildung",        en: "Coaching & Education" },    "leadgen"],
];
const caseIndustryLabel = (key, lang) => {
  const e = CASE_INDUSTRIES.find((x) => x[0] === (key || "general"));
  return e ? e[1][lang === "en" ? "en" : "de"] : key;
};
const industriesForVertical = (vertical) => CASE_INDUSTRIES.filter(([, , v]) => v === null || v === vertical);

/* ---- Sub-Industries: verfeinern eine Branche. Freier Text in der DB,
   Taxonomie lebt hier. Struktur [key, {de,en}, parentIndustryKey].
   'general' hat keine Subs; ohne Sub-Seed greift die Fallback-Kette. ---- */
const CASE_SUB_INDUSTRIES = [
  // beauty
  ["skincare", { de: "Skincare / Hautpflege", en: "Skincare" }, "beauty"],
  ["makeup", { de: "Make-up / Dekorative Kosmetik", en: "Make-up / Color Cosmetics" }, "beauty"],
  ["haircare", { de: "Haircare / Haarpflege", en: "Haircare" }, "beauty"],
  ["fragrance", { de: "Parfum / Duefte", en: "Fragrance" }, "beauty"],
  ["anti_aging", { de: "Anti-Aging / Wirkstoffpflege", en: "Anti-Aging / Actives" }, "beauty"],
  ["acne_sensitive", { de: "Akne- & Sensitivpflege", en: "Acne & Sensitive Skincare" }, "beauty"],
  ["clean_beauty", { de: "Naturkosmetik / Clean Beauty", en: "Natural / Clean Beauty" }, "beauty"],
  ["nail_care", { de: "Nagelpflege", en: "Nail Care" }, "beauty"],
  ["mens_grooming", { de: "Maennerpflege / Grooming", en: "Men's Grooming" }, "beauty"],
  // home
  ["furniture", { de: "Moebel", en: "Furniture" }, "home"],
  ["decor", { de: "Deko & Wohnaccessoires", en: "Home Decor" }, "home"],
  ["garden", { de: "Garten & Outdoor", en: "Garden & Outdoor" }, "home"],
  ["kitchen_household", { de: "Kueche & Haushalt", en: "Kitchen & Household" }, "home"],
  ["mattresses", { de: "Matratzen", en: "Mattresses" }, "home"],
  ["luxury_home", { de: "Luxus Home & Garden", en: "Luxury Home & Garden" }, "home"],
  ["bedding", { de: "Bettwaren & Heimtextilien", en: "Bedding & Home Textiles" }, "home"],
  ["outdoor_furniture", { de: "Gartenmoebel", en: "Outdoor Furniture" }, "home"],
  ["home_improvement", { de: "Heimwerken & DIY", en: "Home Improvement & DIY" }, "home"],
  // food
  ["coffee_tea", { de: "Kaffee & Tee", en: "Coffee & Tea" }, "food"],
  ["snacks_sweets", { de: "Snacks & Suesses", en: "Snacks & Sweets" }, "food"],
  ["beverages", { de: "Getraenke (alkoholfrei & functional)", en: "Non-Alcoholic & Functional Beverages" }, "food"],
  ["special_diet", { de: "Spezialernaehrung (vegan/keto/glutenfrei)", en: "Special Diet Nutrition" }, "food"],
  ["alcohol", { de: "Alkohol / Wein & Spirituosen", en: "Alcohol / Wine & Spirits" }, "food"],
  ["meal_kits", { de: "Meal Kits & Fertiggerichte", en: "Meal Kits & Prepared Meals" }, "food"],
  // health
  ["supplements", { de: "Nahrungsergaenzung & Vitamine", en: "Supplements & Vitamins" }, "health"],
  ["fitness", { de: "Fitness & Sport", en: "Fitness & Sports" }, "health"],
  ["personal_care", { de: "Koerperpflege", en: "Personal Care" }, "health"],
  ["wellness", { de: "Wellness & Naturheilkunde", en: "Wellness & Holistic Health" }, "health"],
  ["cbd", { de: "CBD & Hanf", en: "CBD & Hemp" }, "health"],
  ["weight_management", { de: "Abnehmen & Gewichtsmanagement", en: "Weight Management" }, "health"],
  ["medical_devices", { de: "Medizinprodukte & OTC", en: "Medical Devices & OTC" }, "health"],
  ["sports_nutrition", { de: "Sporternaehrung & Protein", en: "Sports Nutrition & Protein" }, "health"],
  // electronics
  ["audio", { de: "Audio (Kopfhoerer & Speaker)", en: "Audio (Headphones & Speakers)" }, "electronics"],
  ["smart_home", { de: "Smart Home", en: "Smart Home" }, "electronics"],
  ["computing", { de: "Computing (PC, Laptops & Peripherie)", en: "Computing (PCs, Laptops & Peripherals)" }, "electronics"],
  ["gadgets", { de: "Gadgets & Consumer Tech", en: "Gadgets & Consumer Tech" }, "electronics"],
  ["wearables", { de: "Wearables (Smartwatches & Tracker)", en: "Wearables (Smartwatches & Trackers)" }, "electronics"],
  ["home_entertainment", { de: "Home Entertainment (TV & Heimkino)", en: "Home Entertainment (TV & Home Cinema)" }, "electronics"],
  // gaming
  ["games_software", { de: "Games & Software (Titel/Downloads)", en: "Games & Software" }, "gaming"],
  ["gaming_hardware", { de: "Gaming-Hardware & Peripherie", en: "Gaming Hardware & Peripherals" }, "gaming"],
  ["toys_collectibles", { de: "Spielzeug & Sammler", en: "Toys & Collectibles" }, "gaming"],
  ["mobile_games", { de: "Mobile Games / App-Installs", en: "Mobile Games / App Installs" }, "gaming"],
  ["board_games", { de: "Brett- & Tabletop-Spiele", en: "Board & Tabletop Games" }, "gaming"],
  ["entertainment", { de: "Entertainment & Streaming", en: "Entertainment & Streaming" }, "gaming"],
  // b2b_saas
  ["it_security", { de: "IT-Security & Cybersecurity-Software", en: "Cybersecurity Software" }, "b2b_saas"],
  ["finance_sw", { de: "FinTech- & Payment-Software", en: "FinTech & Payments Software" }, "b2b_saas"],
  ["hr_sw", { de: "HR- & Recruiting-Software", en: "HR Tech / HRIS" }, "b2b_saas"],
  ["marketing_sales_sw", { de: "Marketing- & Sales-Software (MarTech/CRM)", en: "MarTech / Sales Tech" }, "b2b_saas"],
  ["vertical_saas", { de: "Branchen-/Vertical-SaaS", en: "Vertical / Industry-Specific SaaS" }, "b2b_saas"],
  ["devtools", { de: "Developer-Tools & API-/Infra-Software", en: "DevTools / Developer Platforms" }, "b2b_saas"],
  ["project_mgmt", { de: "Projektmanagement & Collaboration", en: "Project Management / Collaboration" }, "b2b_saas"],
  ["erp_operations", { de: "ERP- & Operations-Software", en: "ERP / Operations Software" }, "b2b_saas"],
  ["healthtech_saas", { de: "HealthTech- & Medical-Software", en: "HealthTech / Medical Software" }, "b2b_saas"],
  ["legaltech_saas", { de: "LegalTech- & Compliance-Software", en: "LegalTech / Compliance Software" }, "b2b_saas"],
  // b2b_services
  ["it_services", { de: "IT-Services, Software & SaaS", en: "IT Services & Software" }, "b2b_services"],
  ["industrial_services", { de: "Industrie-Services & Industriegueter", en: "Industrial Services & B2B Goods" }, "b2b_services"],
  ["agencies_consulting", { de: "Agenturen & Unternehmensberatung", en: "Agencies & Consulting" }, "b2b_services"],
  ["logistics", { de: "Logistik & Transport", en: "Logistics & Transportation" }, "b2b_services"],
  ["legal_services", { de: "Rechtsberatung & Kanzleien", en: "Legal Services & Law Firms" }, "b2b_services"],
  ["recruiting", { de: "Personalvermittlung & Recruiting", en: "Staffing & Recruiting" }, "b2b_services"],
  // local_services
  ["sanierung", { de: "Sanierung & Wasser-/Brandschaden", en: "Restoration & Water/Fire Damage" }, "local_services"],
  ["umzug_entruempelung", { de: "Umzug & Entruempelung", en: "Moving & Junk Removal" }, "local_services"],
  ["handwerk_bau", { de: "Handwerk & Bau (allgemein)", en: "General Trades & Construction" }, "local_services"],
  ["reinigung", { de: "Reinigung (Gebaeude/Haushalt/Glas)", en: "Cleaning (Building/Domestic/Window)" }, "local_services"],
  ["schaedlinge", { de: "Schaedlingsbekaempfung", en: "Pest Control" }, "local_services"],
  ["hvac", { de: "Heizung, Klima & Lueftung (HVAC)", en: "HVAC / Heating & Cooling" }, "local_services"],
  ["sanitaer", { de: "Sanitaer & Installateur", en: "Plumbing" }, "local_services"],
  ["dachdecker", { de: "Dachdecker & Dachsanierung", en: "Roofing & Gutters" }, "local_services"],
  ["elektriker", { de: "Elektriker & Elektroinstallation", en: "Electricians" }, "local_services"],
  ["maler", { de: "Maler & Lackierer", en: "Painting" }, "local_services"],
  ["garten_landschaftsbau", { de: "Garten- & Landschaftsbau", en: "Landscaping & Garden" }, "local_services"],
  ["fenster_tueren", { de: "Fenster & Tueren", en: "Doors & Windows" }, "local_services"],
  // finance
  ["versicherungen", { de: "Versicherung (allgemein/Sonstige)", en: "Insurance (General/Other)" }, "finance"],
  ["kredite", { de: "Ratenkredit & Konsumkredit", en: "Consumer & Personal Loans" }, "finance"],
  ["geldanlage", { de: "Geldanlage & Robo-Advisor", en: "Investing & Robo-Advisor" }, "finance"],
  ["steuern", { de: "Steuer-, Finanz- & Buchhaltungsdienste", en: "Tax, Financial & Accounting Services" }, "finance"],
  ["kfz_versicherung", { de: "Kfz-Versicherung", en: "Auto Insurance" }, "finance"],
  ["lebensversicherung", { de: "Lebens- & Risikolebensversicherung", en: "Life & Term Life Insurance" }, "finance"],
  ["pkv", { de: "Private Kranken- & Gesundheitsversicherung", en: "Private Health Insurance" }, "finance"],
  ["sterbegeld", { de: "Sterbegeldversicherung", en: "Final Expense Insurance" }, "finance"],
  ["hausrat_wohngebaeude", { de: "Hausrat- & Wohngebaeudeversicherung", en: "Home & Property Insurance" }, "finance"],
  ["gewerbeversicherung", { de: "Gewerbe- & Firmenversicherung", en: "Commercial / Business Insurance" }, "finance"],
  ["baufinanzierung", { de: "Baufinanzierung / Immobilienkredit", en: "Mortgage & Home Financing" }, "finance"],
  ["schuldenregulierung", { de: "Schuldenregulierung & Umschuldung", en: "Debt Relief & Settlement" }, "finance"],
  // education
  ["business_coaching", { de: "Business- & Karriere-Coaching", en: "Business & Career Coaching" }, "education"],
  ["online_kurse", { de: "Online-Kurse & Infoprodukte", en: "Online Courses & Info Products" }, "education"],
  ["sprachen_nachhilfe", { de: "Sprachen & Nachhilfe (K-12)", en: "Language Learning & Tutoring" }, "education"],
  ["weiterbildung", { de: "Weiterbildung & Zertifizierungen", en: "Professional Development & Certifications" }, "education"],
  ["test_prep", { de: "Test- & Pruefungsvorbereitung", en: "Test Prep & Exam Coaching" }, "education"],
  ["higher_education", { de: "Studium & Hochschulen", en: "Higher Education / Degrees" }, "education"],
  ["berufsausbildung", { de: "Berufsausbildung & Umschulung", en: "Vocational & Trade Schools" }, "education"],
  ["coding_bootcamp", { de: "Coding- & Tech-Bootcamps", en: "Coding & Tech Bootcamps" }, "education"],
  // fashion
  ["apparel", { de: "Bekleidung", en: "Apparel & Clothing" }, "fashion"],
  ["shoes", { de: "Schuhe", en: "Footwear" }, "fashion"],
  ["accessories_jewelry", { de: "Accessoires & Schmuck", en: "Accessories & Jewelry" }, "fashion"],
  ["sports_fashion", { de: "Sportmode & Activewear", en: "Sportswear & Activewear" }, "fashion"],
  ["luxury_fashion", { de: "Luxusmode", en: "Luxury Fashion" }, "fashion"],
  ["lingerie_underwear", { de: "Dessous & Unterwaesche", en: "Lingerie & Underwear" }, "fashion"],
  ["watches", { de: "Uhren", en: "Watches" }, "fashion"],
  ["eyewear", { de: "Brillen & Eyewear", en: "Eyewear" }, "fashion"],
  ["childrens_fashion", { de: "Kinder- & Babymode", en: "Kids & Baby Apparel" }, "fashion"],
  ["fashion_subscriptions", { de: "Mode-Abos & Boxen", en: "Fashion Subscriptions" }, "fashion"],
];
const subIndustriesFor = (industryKey) => CASE_SUB_INDUSTRIES.filter(([, , p]) => p === industryKey);
const caseSubIndustryLabel = (key, lang) => {
  const e = CASE_SUB_INDUSTRIES.find((x) => x[0] === key);
  return e ? e[1][lang === "en" ? "en" : "de"] : key;
};

// Bestes Template fuer eine Kombination. 6-stufige Fallback-Kette:
// (industry, sub, benchmark) → (industry, sub, any) → (industry, –, benchmark)
// → (industry, –, any) → (general, –, benchmark) → (general, –, any).
function casePickTemplate(templates, platform, vertical, region, industry, subIndustry) {
  const cand = templates.filter((x) => x.platform === platform && x.vertical === vertical && x.region === region);
  const ind = industry || "general";
  const sub = subIndustry || "";
  const indMatch = (x) => (x.industry || "general") === ind;
  const genMatch = (x) => (x.industry || "general") === "general";
  const subMatch = (x) => (x.subIndustry || "") === sub;
  const noSub = (x) => !(x.subIndustry || "");
  return (sub && cand.find((x) => indMatch(x) && subMatch(x) && x.kind === "benchmark"))
    || (sub && cand.find((x) => indMatch(x) && subMatch(x)))
    || cand.find((x) => indMatch(x) && noSub(x) && x.kind === "benchmark")
    || cand.find((x) => indMatch(x) && noSub(x))
    || cand.find((x) => genMatch(x) && x.kind === "benchmark")
    || cand.find((x) => genMatch(x))
    || null;
}

// Gelber Hinweis: benennt die tatsaechlich geladene Ebene, wenn nicht das
// angefragte Sub- bzw. Branchen-Set greift.
function caseFallbackNote(caseIndustry, caseSub, tpl, lang) {
  if (!tpl) return null;
  const ind = caseIndustry || "general";
  const tplInd = tpl.industry || "general";
  const tplSub = tpl.subIndustry || "";
  const en = lang === "en";
  const requested = caseSub ? caseSubIndustryLabel(caseSub, lang) : caseIndustryLabel(ind, lang);
  const msg = (loaded) => (en ? `No ${requested} benchmark – ${loaded} loaded.` : `Kein ${requested}-Benchmark – ${loaded} geladen.`);
  if (tplInd === "general" && ind !== "general") return msg(caseIndustryLabel("general", lang));
  if (caseSub && tplInd === ind && tplSub !== caseSub) return msg(caseIndustryLabel(ind, lang));
  return null;
}
const CASE_STAGE_PRESETS = (lang) => ([
  { key: "sql", label: CASES_DICT[lang].presetSql, stages: [
    { name: lang === "en" ? "Sales Qualified" : "Sales Qualified", rate: 0.7 },
    { name: "Opportunity", rate: 0.5 },
    { name: lang === "en" ? "Close" : "Abschluss", rate: 0.5 },
  ]},
  { key: "webinar", label: CASES_DICT[lang].presetWebinar, stages: [
    { name: lang === "en" ? "Webinar attendee" : "Webinar-Teilnehmer", rate: 0.2 },
    { name: "Opportunity", rate: 0.1 },
    { name: lang === "en" ? "Close" : "Abschluss", rate: 0.5 },
  ]},
]);
const caseCurrency = (region) => (region === "us" ? "USD" : "EUR");
const casePersonName = (id) => { const p = (typeof VP_DATA !== "undefined" && VP_DATA.byId && VP_DATA.byId[id]) || null; return p ? `${p.first || ""} ${p.last || ""}`.trim() : ""; };
function caseRel(iso, lang) { if (!iso) return ""; try { return new Date(iso).toLocaleDateString(lang === "en" ? "en-US" : "de-DE", { day: "numeric", month: "short" }); } catch (_) { return ""; } }
const fmtMoneyAuto = (v, cur, lang) => CE.fmtMoney(v, cur, lang, { decimals: v !== null && Math.abs(v) >= 1000 ? 0 : 2 });
const fmtPctAuto = (v, lang) => CE.fmtPct(v, lang, v !== null && v < 0.01 ? 2 : 1);

/* ---- shared styles ---- */
const caseNavBtn = { width: 40, height: 40, display: "grid", placeItems: "center", borderRadius: "var(--r-sm)", border: "1px solid var(--line-2)", background: "var(--surface)", color: "var(--ink-2)", cursor: "pointer" };
const caseInput = { width: "100%", padding: "11px 12px", borderRadius: "var(--r-sm)", border: "1px solid var(--line-2)", background: "var(--surface)", fontSize: 15, color: "var(--ink)", outline: "none", fontFamily: "var(--font-body)" };
const caseLabel = { fontSize: 12.5, fontWeight: 600, color: "var(--ink-4)", marginBottom: 6, display: "block", whiteSpace: "nowrap" };

/* ---- number input: locale-tolerant (7,5 or 7.5), commits parsed values ---- */
function NumField({ label, value, onChange, suffix, pct, width, placeholder, disabled, title, fill }) {
  const toDisplay = (v) => (v === null || v === undefined ? "" : String(CE.round2(pct ? v * 100 : v)).replace(".", ","));
  const [s, setS] = useState(toDisplay(value));
  const focused = useRef(false);
  useEffect(() => { if (!focused.current) setS(toDisplay(value)); }, [value]);
  const commit = (raw) => {
    setS(raw);
    const n = CE.num(raw);
    onChange(n === null ? null : (pct ? n / 100 : n));
  };
  const wrap = fill ? { minWidth: 0 } : { width: width || 110, flex: "0 0 auto" };
  return (
    <label style={wrap} title={title}>
      {label && <span style={caseLabel}>{label}</span>}
      <span style={{ position: "relative", display: "block" }}>
        <input value={s} disabled={disabled} placeholder={placeholder || ""} inputMode="decimal"
          onFocus={() => { focused.current = true; }}
          onBlur={() => { focused.current = false; setS(toDisplay(value)); }}
          onChange={(e) => commit(e.target.value)}
          style={{ ...caseInput, paddingRight: suffix ? 34 : 11, opacity: disabled ? 0.55 : 1 }} />
        {suffix && <span style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)", fontSize: 12.5, color: "var(--ink-4)", pointerEvents: "none" }}>{suffix}</span>}
      </span>
    </label>
  );
}

/* ---- Modal shell: header / body / footer with clear hierarchy ----
   Matches the native RequestForm pattern (padded header + divider,
   padded body, set-off sticky footer) so Cases modals read the same. */
function CaseModalShell({ title, subtitle, chips, onClose, width, children, footer }) {
  // Escape any transformed ancestor (the .vp-fade-up wrappers keep a
  // translateY(0), which would make the fixed overlay center relative to
  // them instead of the viewport). Portal to body like the export overlay.
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);
  const narrow = useIsNarrow(820);
  const bodyPad = narrow ? "18px 16px" : "22px 26px";
  return ReactDOM.createPortal(
    <div className="cases-scope" onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 70, background: "rgba(20,20,43,0.5)", backdropFilter: "blur(4px)",
      display: "grid", placeItems: narrow ? "end center" : "center", padding: narrow ? 0 : 24,
    }}>
      <div onClick={(e) => e.stopPropagation()} className="vp-pop" style={{
        width: "100%", maxWidth: narrow ? "100%" : width, maxHeight: narrow ? "94vh" : "90vh",
        background: "var(--surface)", display: "flex", flexDirection: "column",
        borderRadius: narrow ? "var(--r-xl) var(--r-xl) 0 0" : "var(--r-xl)",
        boxShadow: "var(--sh-pop)", border: "1px solid var(--line)", overflow: "hidden",
      }}>
        <div style={{ padding: narrow ? "18px 16px 16px" : "22px 26px 18px", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "flex-start", gap: 12, flex: "0 0 auto" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <h2 style={{ fontSize: 20, lineHeight: 1.2, margin: 0 }}>{title}</h2>
            {subtitle && <div style={{ fontSize: 13.5, color: "var(--ink-4)", marginTop: 5, lineHeight: 1.45 }}>{subtitle}</div>}
            {chips && <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 11 }}>{chips}</div>}
          </div>
          <button type="button" onClick={onClose} className="vp-focusable case-navbtn" title="Schließen"
            style={{ width: 40, height: 40, display: "grid", placeItems: "center", borderRadius: "var(--r-sm)", border: "1px solid var(--line-2)", background: "var(--surface-2)", color: "var(--ink-4)", cursor: "pointer", flex: "0 0 auto" }}>
            <Icon name="x" size={18} />
          </button>
        </div>
        <div style={{ padding: bodyPad, display: "grid", gap: 20, overflowY: "auto", flex: "1 1 auto", minHeight: 0 }}>{children}</div>
        {footer && (
          <div style={{ padding: narrow ? "13px 16px" : "15px 26px", borderTop: "1px solid var(--line)", background: "var(--surface-2)", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flex: "0 0 auto" }}>
            {footer}
          </div>
        )}
      </div>
    </div>,
    document.body
  );
}

/* ---- Labeled field group: uppercase eyebrow + controls, gives depth ---- */
function CaseFieldGroup({ label, children, style, hint }) {
  return (
    <div style={style}>
      {label && <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 9 }}>{label}</div>}
      {children}
      {hint && <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 7, lineHeight: 1.45 }}>{hint}</div>}
    </div>
  );
}

/* ---- Segmented control (connected, active raised) — replaces flat buttons ---- */
function SegControl({ value, options, onChange, size }) {
  const narrow = useIsNarrow(560);
  const pad = size === "sm" ? "8px 11px" : "9px 14px";
  const fs = size === "sm" ? 13 : 13.5;
  // Full-width equal segments on small screens so it never forces intrinsic width.
  return (
    <div style={{ display: narrow ? "flex" : "inline-flex", width: narrow ? "100%" : "auto", background: "var(--surface-2)", border: "1px solid var(--line-2)", borderRadius: "var(--r-sm)", padding: 3, gap: 3 }}>
      {options.map(([v, label]) => {
        const on = value === v;
        return (
          <button key={v} type="button" onClick={() => onChange(v)} className="vp-focusable"
            style={{ padding: pad, borderRadius: 6, border: "none", cursor: "pointer", fontFamily: "var(--font-body)", fontWeight: 600, fontSize: fs, whiteSpace: "nowrap",
              flex: narrow ? "1 1 0" : "0 0 auto", minWidth: 0,
              background: on ? "var(--surface)" : "transparent", color: on ? "var(--accent-ink)" : "var(--ink-3)", boxShadow: on ? "var(--sh-sm)" : "none", transition: "color .14s, background .14s" }}>
            {label}
          </button>
        );
      })}
    </div>
  );
}

/* ---- Multi-select toggle chips (platforms) ---- */
function ToggleChips({ options, selected, onToggle }) {
  return (
    <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
      {options.map(([v, label]) => {
        const on = selected.includes(v);
        return (
          <button key={v} type="button" onClick={() => onToggle(v)} className="vp-focusable"
            style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 13px 8px 11px", borderRadius: "var(--r-sm)", cursor: "pointer", fontFamily: "var(--font-body)", fontWeight: 600, fontSize: 13.5, transition: "all .14s",
              border: on ? "1px solid var(--accent)" : "1px solid var(--line-2)", background: on ? "var(--accent-soft)" : "var(--surface)", color: on ? "var(--accent-ink)" : "var(--ink-2)" }}>
            <span style={{ width: 15, height: 15, borderRadius: 5, display: "grid", placeItems: "center", background: on ? "var(--accent)" : "transparent", border: on ? "none" : "1.5px solid var(--line-strong)", flex: "0 0 auto" }}>
              {on && <Icon name="check" size={11} stroke={3} style={{ color: "#fff" }} />}
            </span>
            {label}
          </button>
        );
      })}
    </div>
  );
}

/* ---- chips ---- */
function CaseChip({ children, fg, bg, style }) {
  return <span style={{ display: "inline-flex", alignItems: "center", gap: 5, background: bg || "var(--paper-2)", color: fg || "var(--ink-2)", padding: "3px 10px", borderRadius: "var(--r-pill)", fontSize: 12, fontWeight: 700, lineHeight: 1.5, whiteSpace: "nowrap", ...style }}>{children}</span>;
}
function PlatformChip({ platform, size }) {
  const p = CASE_PLATFORMS[platform] || { label: platform, fg: "var(--ink-2)", bg: "var(--paper-2)" };
  return <CaseChip fg={p.fg} bg={p.bg} style={size === "sm" ? { fontSize: 11, padding: "2px 8px" } : null}>{p.label}</CaseChip>;
}
function KindChip({ kind }) {
  const { t } = useCT();
  return kind === "benchmark"
    ? <CaseChip fg="var(--accent-ink)" bg="var(--accent-soft)">{t("benchmark")}</CaseChip>
    : <CaseChip fg="#5E37A0" bg="#ECE1F8">{t("company")}</CaseChip>;
}

/* ============================================================
   Result tables (shared between editor and export; mode:"export"
   renders print-dense with fixed ink colors on white cards)
   ============================================================ */
function tableTokens(mode) {
  return mode === "export"
    ? { ink: "#14142B", sub: "#5C6680", line: "#E4E8F1", head: "#F6F8FC", fs: 13, pad: "7px 12px" }
    : { ink: "var(--ink)", sub: "var(--ink-3)", line: "var(--line)", head: "var(--surface-2)", fs: 13.5, pad: "8px 12px" };
}
const scnSolid = { worst: { fg: "#B42318", bg: "#FCE3E4" }, mid: { fg: "#8A6D00", bg: "#FBF0C4" }, best: { fg: "#1E6B3E", bg: "#DAF1E3" } };
function scnColors(sc, mode) { return mode === "export" ? scnSolid[sc] : CASE_SCN[sc]; }

function MatrixTable({ result, vertical, currency, lang, mode }) {
  const { t } = useCT();
  const tk = tableTokens(mode);
  if (!result || !result.matrix) return null;
  const cvrs = { worst: null, mid: null, best: null };
  result.matrix.forEach((r) => CE.SCENARIOS.forEach((sc) => { if (r.cells[sc]) cvrs[sc] = r.cells[sc].cvr; }));
  const th = { padding: tk.pad, fontSize: tk.fs - 1, fontWeight: 700, textAlign: "right", borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap" };
  const td = { padding: tk.pad, fontSize: tk.fs, textAlign: "right", borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" };
  const countLabel = vertical === "ecom" ? t("orders") : t("leads");
  const costLabel = vertical === "ecom" ? t("cpo") : t("cpa");
  return (
    <div style={{ overflowX: "auto", maxWidth: "100%" }}>
      <table style={{ borderCollapse: "collapse", width: "100%", color: tk.ink }}>
        <thead>
          <tr>
            <th style={{ ...th, textAlign: "left", background: tk.head }}>CPC</th>
            {CE.SCENARIOS.map((sc) => {
              const c = scnColors(sc, mode);
              return <th key={sc} colSpan={2} style={{ ...th, textAlign: "center", background: c.bg, color: c.fg }}>
                {t(sc)}{cvrs[sc] !== null ? ` · ${t("cvr")} ${fmtPctAuto(cvrs[sc], lang)}` : ""}
              </th>;
            })}
          </tr>
          <tr>
            <th style={{ ...th, textAlign: "left", background: tk.head, color: tk.sub }}></th>
            {CE.SCENARIOS.map((sc) => (
              <React.Fragment key={sc}>
                <th style={{ ...th, background: tk.head, color: tk.sub }}>{countLabel}</th>
                <th style={{ ...th, background: tk.head, color: tk.sub }}>{costLabel}</th>
              </React.Fragment>
            ))}
          </tr>
        </thead>
        <tbody>
          {result.matrix.map((row, i) => {
            const isBase = row.cpc === result.steps.base;
            return (
              <tr key={i} style={isBase ? { fontWeight: 700 } : null}>
                <td style={{ ...td, textAlign: "left" }}>{CE.fmtMoney(row.cpc, currency, lang)}</td>
                {CE.SCENARIOS.map((sc) => {
                  const cell = row.cells[sc];
                  const c = scnColors(sc, mode);
                  const tint = { background: mode === "export" ? c.bg + "55" : "transparent" };
                  if (!cell) return <React.Fragment key={sc}><td style={{ ...td, ...tint }}>–</td><td style={{ ...td, ...tint }}>–</td></React.Fragment>;
                  return (
                    <React.Fragment key={sc}>
                      <td style={{ ...td, ...tint }}>{vertical === "ecom" ? CE.fmtNum(cell.orders, lang, 1) : CE.fmtInt(cell.leads, lang)}</td>
                      <td style={{ ...td, ...tint }}>{CE.fmtMoney(cell.cpa !== undefined ? cell.cpa : cell.cpo, currency, lang)}</td>
                    </React.Fragment>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function FunnelTable({ result, stages, currency, lang, mode }) {
  const { t } = useCT();
  const tk = tableTokens(mode);
  const sc = result && result.scenarios;
  if (!sc || (!sc.worst && !sc.mid && !sc.best)) return null;
  const anyScn = sc.mid || sc.best || sc.worst;
  const rows = [{ name: t("leads"), rate: null, key: "leads" }].concat((stages || []).map((s, i) => ({ name: s.name || "?", rate: s.rate, key: i })));
  const lastName = stages && stages.length ? stages[stages.length - 1].name : t("leads");
  const th = { padding: tk.pad, fontSize: tk.fs - 1, fontWeight: 700, borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap" };
  const td = { padding: tk.pad, fontSize: tk.fs, borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" };
  const val = (scenario, idx) => {
    const s = sc[scenario]; if (!s) return "–";
    if (idx === "leads") return CE.fmtNum(s.leads, lang, 1);
    const st = s.stages[idx]; return st && st.count !== null ? CE.fmtNum(st.count, lang, st.count < 10 ? 1 : 0) : "–";
  };
  return (
    <div style={{ overflowX: "auto", maxWidth: "100%" }}>
      <table style={{ borderCollapse: "collapse", width: "100%", color: tk.ink }}>
        <thead>
          <tr>
            <th style={{ ...th, textAlign: "left", background: tk.head }}>{t("stage")}</th>
            <th style={{ ...th, textAlign: "right", background: tk.head, color: tk.sub }}>{t("rate")}</th>
            {CE.SCENARIOS.map((s) => { const c = scnColors(s, mode); return <th key={s} style={{ ...th, textAlign: "right", background: c.bg, color: c.fg }}>{t(s)}</th>; })}
          </tr>
        </thead>
        <tbody>
          {rows.map((r) => (
            <tr key={String(r.key)}>
              <td style={{ ...td, fontWeight: 600 }}>{r.name}</td>
              <td style={{ ...td, textAlign: "right", color: tk.sub }}>{r.rate !== null ? CE.fmtPct(r.rate, lang, 0) : (anyScn ? `${t("cvr")} ` + fmtPctAuto(anyScn.cvr, lang) : "–")}</td>
              {CE.SCENARIOS.map((s) => <td key={s} style={{ ...td, textAlign: "right" }}>{val(s, r.key)}</td>)}
            </tr>
          ))}
          <tr style={{ fontWeight: 700 }}>
            <td style={{ ...td, borderBottom: "none" }}>{t("costPer")} {lastName}</td>
            <td style={{ ...td, borderBottom: "none" }}></td>
            {CE.SCENARIOS.map((s) => (
              <td key={s} style={{ ...td, textAlign: "right", borderBottom: "none", color: scnColors(s, mode).fg }}>
                {sc[s] ? fmtMoneyAuto(sc[s].finalCostPer, currency, lang) : "–"}
              </td>
            ))}
          </tr>
        </tbody>
      </table>
    </div>
  );
}

function EcomTable({ result, currency, lang, mode }) {
  const { t } = useCT();
  const tk = tableTokens(mode);
  const sc = result && result.scenarios;
  if (!sc || (!sc.worst && !sc.mid && !sc.best)) return null;
  const any = sc.mid || sc.best || sc.worst;
  const hasAov = any && any.revenue !== null;
  const hasMargin = any && any.breakEvenRoas !== null;
  const rows = [
    { label: t("clicks"), get: (s) => CE.fmtInt(s.clicks, lang) },
    { label: t("orders"), get: (s) => CE.fmtNum(s.orders, lang, 1) },
    { label: t("cpo"), get: (s) => CE.fmtMoney(s.cpo, currency, lang) },
  ];
  if (hasAov) {
    rows.push({ label: t("revenue"), get: (s) => fmtMoneyAuto(s.revenue, currency, lang) });
    rows.push({ label: t("roas"), get: (s) => CE.fmtNum(s.roas, lang, 2), bold: true });
  }
  if (hasAov && hasMargin) rows.push({ label: t("contribution"), get: (s) => fmtMoneyAuto(s.contribution, currency, lang), signed: true });
  const th = { padding: tk.pad, fontSize: tk.fs - 1, fontWeight: 700, borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap" };
  const td = { padding: tk.pad, fontSize: tk.fs, borderBottom: `1px solid ${tk.line}`, whiteSpace: "nowrap", textAlign: "right", fontVariantNumeric: "tabular-nums" };
  return (
    <div style={{ overflowX: "auto", maxWidth: "100%" }}>
      <table style={{ borderCollapse: "collapse", width: "100%", color: tk.ink }}>
        <thead>
          <tr>
            <th style={{ ...th, textAlign: "left", background: tk.head }}></th>
            {CE.SCENARIOS.map((s) => { const c = scnColors(s, mode); return <th key={s} style={{ ...th, textAlign: "right", background: c.bg, color: c.fg }}>{t(s)} {sc[s] ? `· ${fmtPctAuto(sc[s].cvr, lang)}` : ""}</th>; })}
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={i} style={r.bold ? { fontWeight: 700 } : null}>
              <td style={{ ...td, textAlign: "left", fontWeight: 600 }}>{r.label}</td>
              {CE.SCENARIOS.map((s) => {
                const v = sc[s] ? r.get(sc[s]) : "–";
                const neg = r.signed && sc[s] && sc[s].contribution !== null && sc[s].contribution < 0;
                return <td key={s} style={{ ...td, color: r.signed ? (neg ? "#B42318" : "#1E6B3E") : undefined }}>{v}</td>;
              })}
            </tr>
          ))}
          {hasMargin && (
            <tr>
              <td style={{ ...td, textAlign: "left", borderBottom: "none", color: tk.sub, fontWeight: 600 }}>{t("breakEven")}</td>
              <td colSpan={3} style={{ ...td, borderBottom: "none", color: tk.sub }}>{CE.fmtNum(any.breakEvenRoas, lang, 2)}</td>
            </tr>
          )}
        </tbody>
      </table>
    </div>
  );
}

function ComparisonTable({ blocks, results, vertical, currency, lang, mode }) {
  const { t } = useCT();
  const tk = tableTokens(mode);
  const th = { padding: tk.pad, fontSize: tk.fs - 1, fontWeight: 700, borderBottom: `1px solid ${tk.line}`, textAlign: "right", whiteSpace: "nowrap", background: tk.head, color: tk.sub };
  const td = { padding: tk.pad, fontSize: tk.fs, borderBottom: `1px solid ${tk.line}`, textAlign: "right", whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" };
  const lastLabel = (b) => (b.params && b.params.stages && b.params.stages.length ? b.params.stages[b.params.stages.length - 1].name : t("cpl"));
  const lastLabels = Array.from(new Set(blocks.map(lastLabel)));
  const finalColHead = vertical === "ecom" ? t("roas") : `${t("costPer")} ${lastLabels.length === 1 ? lastLabels[0] : "…"}`;
  return (
    <div style={{ overflowX: "auto", maxWidth: "100%" }}>
      <table style={{ borderCollapse: "collapse", width: "100%", color: tk.ink }}>
        <thead>
          <tr>
            <th style={{ ...th, textAlign: "left" }}>{t("comparison")} · {t("scenarioMid")}</th>
            <th style={th}>{t("adSpend")}</th>
            <th style={th}>{t("clicks")}</th>
            <th style={th}>{vertical === "ecom" ? t("orders") : t("leads")}</th>
            <th style={th}>{vertical === "ecom" ? t("cpo") : t("cpl")}</th>
            <th style={th}>{finalColHead}</th>
          </tr>
        </thead>
        <tbody>
          {blocks.map((b, i) => {
            const r = results[i];
            const mid = r && r.scenarios && r.scenarios.mid;
            return (
              <tr key={i}>
                <td style={{ ...td, textAlign: "left" }}><PlatformChip platform={b.platform} size="sm" /></td>
                <td style={td}>{r ? fmtMoneyAuto(r.spend, currency, lang) : "–"}</td>
                <td style={td}>{mid ? CE.fmtInt(mid.clicks, lang) : "–"}</td>
                <td style={td}>{mid ? (vertical === "ecom" ? CE.fmtNum(mid.orders, lang, 1) : CE.fmtInt(mid.leads, lang)) : "–"}</td>
                <td style={td}>{mid ? CE.fmtMoney(vertical === "ecom" ? mid.cpo : mid.cpl, currency, lang) : "–"}</td>
                <td style={td}>
                  {mid ? (vertical === "ecom"
                    ? (mid.roas !== null ? CE.fmtNum(mid.roas, lang, 2) : "–")
                    : `${fmtMoneyAuto(mid.finalCostPer, currency, lang)}${lastLabels.length > 1 ? ` (${lastLabel(b)})` : ""}`) : "–"}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

/* ============================================================
   Stages editor (lead gen funnel)
   ============================================================ */
function StagesEditor({ stages, onChange, lang }) {
  const { t } = useCT();
  const list = Array.isArray(stages) ? stages : [];
  const set = (i, patch) => onChange(list.map((s, j) => (j === i ? { ...s, ...patch } : s)));
  const remove = (i) => onChange(list.filter((_, j) => j !== i));
  const add = () => onChange([...list, { name: "", rate: null }]);
  const presets = CASE_STAGE_PRESETS(lang);
  return (
    <div>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 8 }}>
        <span style={{ ...caseLabel, marginBottom: 0, flex: "0 0 auto" }}>{t("funnel")}</span>
        <select value="" onChange={(e) => { const p = presets.find((x) => x.key === e.target.value); if (p) onChange(p.stages.map((s) => ({ ...s }))); }}
          style={{ ...caseInput, width: "auto", maxWidth: 220, minWidth: 0, padding: "6px 30px 6px 10px", fontSize: 12.5, color: "var(--ink-4)" }}>
          <option value="">{t("preset")}</option>
          {presets.map((p) => <option key={p.key} value={p.key}>{p.label}</option>)}
        </select>
      </div>
      {list.map((s, i) => (
        <div key={i} style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }}>
          <span style={{ fontSize: 12.5, color: "var(--ink-4)", width: 14, textAlign: "right", flex: "0 0 auto" }}>{i + 1}.</span>
          <input value={s.name || ""} placeholder={t("stageName")} onChange={(e) => set(i, { name: e.target.value })} style={{ ...caseInput, flex: 1, minWidth: 0 }} />
          <NumField value={s.rate} onChange={(v) => set(i, { rate: v })} pct suffix="%" width={80} />
          <button type="button" onClick={() => remove(i)} title={t("deleteStage")} className="case-navbtn danger" style={{ ...caseNavBtn, flex: "0 0 auto", color: "var(--ink-4)" }}><Icon name="x" size={15} /></button>
        </div>
      ))}
      <Button variant="ghost" size="sm" icon="plus" onClick={add}>{t("addStage")}</Button>
    </div>
  );
}

/* ---- Template candidates for a platform block, grouped by scope level.
   Region- and industry-strict; the case's sub-industry surfaces first. ---- */
function casePlatformTemplates(templates, platform, vertical, region, caseIndustry, caseSub) {
  const ind = caseIndustry || "general";
  const all = templates.filter((x) => x.platform === platform && x.vertical === vertical && x.region === region);
  const subTpls = caseSub ? all.filter((x) => (x.industry || "general") === ind && (x.subIndustry || "") === caseSub) : [];
  const indTpls = all.filter((x) => (x.industry || "general") === ind && !(x.subIndustry || ""));
  const genTpls = ind === "general" ? [] : all.filter((x) => (x.industry || "general") === "general");
  return { ind, subTpls, indTpls, genTpls, list: [...subTpls, ...indTpls, ...genTpls] };
}

/* ---- Input column: Setup / Benchmark / Funnel|Economics sections.
   Collapsible on narrow via a toggle header (chevron rotates). ---- */
function PlatformInputCard({ block, vertical, region, caseIndustry, caseSub, currency, economics, onEcomChange, templates, lang, onChange, onRemove, result, collapsible }) {
  const { t } = useCT();
  const [open, setOpen] = useState(true);
  const params = block.params || {};
  const setParam = (patch) => onChange({ ...block, params: { ...params, ...patch } });
  const sym = currency === "USD" ? "$" : "€";
  const grp = casePlatformTemplates(templates, block.platform, vertical, region, caseIndustry, caseSub);
  const tpl = grp.list.find((x) => x.id === block.templateId) || null;
  const fallbackNote = caseFallbackNote(caseIndustry, caseSub, tpl, lang);
  const applyTemplate = (tp) => onChange({ ...block, templateId: tp.id, templateName: tp.name, params: JSON.parse(JSON.stringify(tp.params || {})) });
  const secLabel = { fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 9 };
  const showBody = !collapsible || open;

  return (
    <Card style={{ boxShadow: "var(--sh-md)", padding: 0, overflow: "hidden" }}>
      {/* header — toggles the body on narrow */}
      <button type="button" onClick={collapsible ? () => setOpen((o) => !o) : undefined}
        style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "14px 16px", minHeight: 48, background: "transparent", border: "none", cursor: collapsible ? "pointer" : "default", fontFamily: "var(--font-body)", textAlign: "left" }}>
        <PlatformChip platform={block.platform} />
        {tpl && <KindChip kind={tpl.kind} />}
        <div style={{ flex: 1 }} />
        {collapsible && <span style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-4)" }}>{t("inputs")}</span>}
        {collapsible && <Icon name="chevDown" size={18} style={{ color: "var(--ink-4)", transform: open ? "rotate(180deg)" : "none", transition: "transform .16s" }} />}
      </button>

      {showBody && (
        <div style={{ padding: "0 16px 16px" }}>
          {/* Setup */}
          <div style={secLabel}>{t("setup")}</div>
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-end" }}>
            <NumField label={t("budgetDay")} value={block.budgetDaily} onChange={(v) => onChange({ ...block, budgetDaily: v })} suffix={sym} width={130} />
            <NumField label={t("runtime")} value={block.runtimeDays} onChange={(v) => onChange({ ...block, runtimeDays: v })} suffix={t("days")} width={120} />
          </div>

          {/* Benchmark */}
          <div style={{ ...secLabel, marginTop: 20 }}>{t("benchmark")}</div>
          <select value={block.templateId || ""} onChange={(e) => { const tp = grp.list.find((x) => x.id === e.target.value); if (tp && (!block.templateId || window.confirm(t("resetQ")))) applyTemplate(tp); }}
            style={{ ...caseInput, fontSize: 13.5, padding: "9px 11px" }}>
            <option value="">{t("template")}…</option>
            {grp.subTpls.length > 0 && (
              <optgroup label={caseSubIndustryLabel(caseSub, lang)}>
                {grp.subTpls.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
              </optgroup>
            )}
            {grp.indTpls.length > 0 && (
              <optgroup label={caseIndustryLabel(grp.ind, lang)}>
                {grp.indTpls.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
              </optgroup>
            )}
            {grp.genTpls.length > 0 && (
              <optgroup label={caseIndustryLabel("general", lang)}>
                {grp.genTpls.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
              </optgroup>
            )}
          </select>
          {fallbackNote && <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--st-pending)", marginTop: 9 }}>{fallbackNote}</div>}
          {tpl && (tpl.source || tpl.sourceDate) && (
            <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 9, lineHeight: 1.5 }}>
              {t("sourceLabel")}: {tpl.source || "–"}{tpl.sourceDate ? ` · ${t("stand")} ${tpl.sourceDate}` : ""}{tpl.confidence ? ` · ${t("confidence")}: ${tpl.confidence}` : ""}
            </div>
          )}
          {tpl && (
            <button type="button" onClick={() => { if (window.confirm(t("resetQ"))) applyTemplate(tpl); }}
              style={{ border: "none", background: "transparent", color: "var(--accent)", fontSize: 12.5, fontWeight: 600, cursor: "pointer", padding: "6px 0 0" }}>
              {t("resetToTemplate")}
            </button>
          )}
          <div style={{ marginTop: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 8 }}>CPC</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0,1fr))", gap: 10 }}>
              <NumField fill label={t("cpcLow")} value={params.cpcLow} onChange={(v) => setParam({ cpcLow: v })} suffix={sym}
                placeholder={result && result.steps ? String(result.steps.low).replace(".", ",") : ""} />
              <NumField fill label={t("baseCpc")} value={params.baseCpc} onChange={(v) => setParam({ baseCpc: v })} suffix={sym} />
              <NumField fill label={t("cpcHigh")} value={params.cpcHigh} onChange={(v) => setParam({ cpcHigh: v })} suffix={sym}
                placeholder={result && result.steps ? String(result.steps.high).replace(".", ",") : ""} />
            </div>
          </div>
          <div style={{ marginTop: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 8 }}>{t("cvrScenarios")}</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0,1fr))", gap: 10 }}>
              {CE.SCENARIOS.map((sc) => (
                <NumField fill key={sc} label={t(sc)} value={params.cvr && params.cvr[sc]} onChange={(v) => setParam({ cvr: { ...(params.cvr || {}), [sc]: v } })} pct suffix="%" />
              ))}
            </div>
          </div>

          {/* Funnel (leadgen) / Client economics (ecom) */}
          {vertical === "leadgen" && (
            <div style={{ marginTop: 20 }}>
              <div style={secLabel}>{t("funnel")}</div>
              <StagesEditor stages={params.stages} onChange={(st) => setParam({ stages: st })} lang={lang} />
            </div>
          )}
          {vertical === "ecom" && (
            <div style={{ marginTop: 20 }}>
              <div style={secLabel}>{t("clientEconomics")}</div>
              <div style={{ fontSize: 12, color: "var(--ink-4)", marginBottom: 10, lineHeight: 1.45 }}>{t("aovHint")}</div>
              <div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-end" }}>
                <NumField label={t("aov")} value={(economics || {}).aov} onChange={(v) => onEcomChange({ ...(economics || {}), aov: v })} suffix={sym} width={120} />
                <NumField label={t("margin")} value={(economics || {}).margin} onChange={(v) => onEcomChange({ ...(economics || {}), margin: v })} pct suffix="%" width={110} />
              </div>
            </div>
          )}

          <div style={{ marginTop: 20, paddingTop: 14, borderTop: "1px solid var(--line)" }}>
            <Button variant="ghost" size="sm" icon="trash" onClick={onRemove} style={{ color: "var(--ink-4)" }}>{t("removeBlock")}</Button>
          </div>
        </div>
      )}
    </Card>
  );
}

/* ---- Result column: KPI strip + scenario/funnel/economics tables. ---- */
function PlatformResults({ block, vertical, currency, economics, result, lang, narrow }) {
  const { t } = useCT();
  const params = block.params || {};
  const lastStage = params.stages && params.stages.length ? params.stages[params.stages.length - 1].name : null;

  if (!result || !result.matrix) {
    return (
      <Card style={{ boxShadow: "var(--sh-md)", display: "flex", alignItems: "center", gap: 12, minHeight: 160, color: "var(--ink-4)", fontSize: 14.5, lineHeight: 1.5 }}>
        <Icon name={narrow ? "chevDown" : "arrowLeft"} size={22} style={{ color: "var(--accent)", flex: "0 0 auto", transform: narrow ? "rotate(180deg)" : "none" }} />
        <span>{t("resultsEmpty")}</span>
      </Card>
    );
  }
  const mid = result.scenarios && result.scenarios.mid;
  const kpis = [
    { l: `${t("adSpend")} (${t("total")})`, v: fmtMoneyAuto(result.spend, currency, lang) },
    { l: t("estClicks"), v: CE.fmtInt(result.estClicks, lang) },
    vertical === "leadgen"
      ? { l: `${t("leads")} · ${t("scenarioMid")}`, v: mid ? CE.fmtInt(mid.leads, lang) : "–" }
      : { l: `${t("orders")} · ${t("scenarioMid")}`, v: mid ? CE.fmtNum(mid.orders, lang, 1) : "–" },
    vertical === "leadgen"
      ? { l: `${t("cpl")} · ${t("scenarioMid")}`, v: mid ? CE.fmtMoney(mid.cpl, currency, lang) : "–" }
      : { l: `${t("cpo")} · ${t("scenarioMid")}`, v: mid ? CE.fmtMoney(mid.cpo, currency, lang) : "–" },
    vertical === "leadgen" && lastStage && mid
      ? { l: `${t("costPer")} ${lastStage} · ${t("scenarioMid")}`, v: fmtMoneyAuto(mid.finalCostPer, currency, lang), hot: true }
      : (vertical === "ecom" && mid && mid.roas !== null
        ? { l: `${t("roas")} · ${t("scenarioMid")}`, v: CE.fmtNum(mid.roas, lang, 2), hot: true } : null),
  ].filter(Boolean);

  return (
    <Card style={{ boxShadow: "var(--sh-md)" }}>
      <div style={{ display: "flex", gap: 10, flexWrap: "wrap", marginBottom: 16 }}>
        {kpis.map((k, i) => (
          <div key={i} style={{ background: k.hot ? "var(--accent-soft)" : "var(--surface)", border: k.hot ? "1px solid var(--accent-soft)" : "1px solid var(--line-2)", boxShadow: k.hot ? "none" : "var(--sh-sm)", borderRadius: "var(--r-md)", padding: "11px 15px", minWidth: 132, flex: "1 1 132px" }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: k.hot ? "var(--accent-ink)" : "var(--ink-4)", marginBottom: 4, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{k.l}</div>
            <div style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: 20, color: k.hot ? "var(--accent-ink)" : "var(--ink)" }}>{k.v}</div>
          </div>
        ))}
      </div>

      <Eyebrow style={{ marginBottom: 8 }}>{t("matrixTitle")}</Eyebrow>
      <MatrixTable result={result} vertical={vertical} currency={currency} lang={lang} />
      <div style={{ fontSize: 12.5, color: "var(--ink-4)", margin: "8px 2px 0" }}>{t("scenarioNote")}</div>

      {vertical === "leadgen" && params.stages && params.stages.length > 0 && (
        <div style={{ marginTop: 20 }}>
          <Eyebrow style={{ marginBottom: 8 }}>{t("funnel")}</Eyebrow>
          <FunnelTable result={result} stages={params.stages} currency={currency} lang={lang} />
        </div>
      )}
      {vertical === "ecom" && (
        <div style={{ marginTop: 20 }}>
          <Eyebrow style={{ marginBottom: 8 }}>{t("econTitle")}</Eyebrow>
          {(!economics || !economics.aov) && <div style={{ fontSize: 13, color: "var(--ink-4)", margin: "4px 0 10px" }}>{t("ecomLocked")}</div>}
          <EcomTable result={result} currency={currency} lang={lang} />
        </div>
      )}
    </Card>
  );
}

/* ---- One platform tab: input column (fixed ~360px desktop) + results. ---- */
function PlatformTab({ block, vertical, region, caseIndustry, caseSub, currency, economics, onEcomChange, templates, lang, onChange, onRemove, narrow }) {
  const params = block.params || {};
  const result = useMemo(() => CE.computePlatform({
    vertical, budgetDaily: block.budgetDaily, runtimeDays: block.runtimeDays,
    params: { ...params, aov: economics && economics.aov, margin: economics && economics.margin },
  }), [vertical, block.budgetDaily, block.runtimeDays, params, economics]);

  const input = (
    <PlatformInputCard block={block} vertical={vertical} region={region} caseIndustry={caseIndustry} caseSub={caseSub}
      currency={currency} economics={economics} onEcomChange={onEcomChange} templates={templates} lang={lang}
      onChange={onChange} onRemove={onRemove} result={result} collapsible={narrow} />
  );
  const results = (
    <PlatformResults block={block} vertical={vertical} currency={currency} economics={economics} result={result} lang={lang} narrow={narrow} />
  );

  if (narrow) {
    // minmax(0,1fr) stops the nowrap result tables from blowing the column
    // wider than the viewport; the tables scroll inside their own overflow box.
    return <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr)", gap: "var(--gap)" }}>{input}{results}</div>;
  }
  return (
    <div style={{ display: "flex", gap: "var(--gap)", alignItems: "flex-start" }}>
      <div style={{ flex: "0 0 360px", maxWidth: 360, position: "sticky", top: 0 }}>{input}</div>
      <div style={{ flex: "1 1 0", minWidth: 0 }}>{results}</div>
    </div>
  );
}

/* ============================================================
   Forecast editor (one case)
   ============================================================ */
function ForecastEditor({ id, user, navigate }) {
  const { t, lang } = useCT();
  const [fc, setFc] = useState(null);
  const [project, setProject] = useState(null);
  const [templates, setTemplates] = useState([]);
  const [err, setErr] = useState("");
  const [saveState, setSaveState] = useState("idle"); // idle | dirty | saving | saved
  const [exporting, setExporting] = useState(false);
  const [activeTab, setActiveTab] = useState(0); // block index | "compare"
  const [addOpen, setAddOpen] = useState(false);
  const [dupBusy, setDupBusy] = useState(false);
  const narrow = useIsNarrow(1000);
  const saveTimer = useRef(null);
  const fcRef = useRef(null);

  useEffect(() => {
    let on = true;
    MesperDB.casesGetForecast(id)
      .then((f) => {
        if (!on) return;
        setFc(f); fcRef.current = f;
        MesperDB.casesListTemplates().then((tp) => on && setTemplates(tp));
        if (f.projectId && MesperDB.casesGetProject) MesperDB.casesGetProject(f.projectId).then((p) => on && setProject(p)).catch(() => {});
      })
      .catch((e) => on && setErr((e && e.message) || "error"));
    return () => { on = false; };
  }, [id]);

  // Autosave: never lose work — debounce writes after any change.
  const mutate = (patch) => {
    setFc((f) => {
      const next = { ...f, ...patch };
      fcRef.current = next;
      return next;
    });
    setSaveState("dirty");
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(async () => {
      const cur = fcRef.current;
      if (!cur) return;
      setSaveState("saving");
      try {
        await MesperDB.casesUpdateForecast(cur.id, { label: cur.label, blocks: cur.blocks, ecom: cur.ecom || {}, notes: cur.notes });
        setSaveState("saved");
      } catch (_) { setSaveState("dirty"); }
    }, 1100);
  };
  useEffect(() => () => clearTimeout(saveTimer.current), []);

  if (err) return <div style={{ padding: 30, color: "var(--st-declined)", fontSize: 14.5 }}>{t("loadError")}<div style={{ color: "var(--ink-4)", fontSize: 12.5, marginTop: 6 }}>{err}</div></div>;
  if (!fc) return <div style={{ padding: 30, color: "var(--ink-4)" }}>…</div>;

  const currency = fc.currency;
  const setBlock = (i, b) => mutate({ blocks: fc.blocks.map((x, j) => (j === i ? b : x)) });
  const removeBlock = (i) => {
    if (!window.confirm(t("removeBlockQ"))) return;
    mutate({ blocks: fc.blocks.filter((_, j) => j !== i) });
    setActiveTab(0);
  };
  const addPlatform = (platform) => {
    const tpl = casePickTemplate(templates, platform, fc.vertical, fc.region, fc.industry, fc.subIndustry);
    const nextIndex = fc.blocks.length;
    mutate({ blocks: [...fc.blocks, {
      platform, templateId: tpl ? tpl.id : null, templateName: tpl ? tpl.name : null,
      budgetDaily: null, runtimeDays: 30,
      params: tpl ? JSON.parse(JSON.stringify(tpl.params || {})) : { cpcSpread: 0.2, cvr: {}, stages: fc.vertical === "leadgen" ? CASE_STAGE_PRESETS(lang)[0].stages.map((s) => ({ ...s })) : undefined },
    }] });
    setActiveTab(nextIndex);
    setAddOpen(false);
  };
  const freePlatforms = Object.keys(CASE_PLATFORMS).filter((p) => !fc.blocks.some((b) => b.platform === p));
  const results = fc.blocks.map((b) => CE.computePlatform({
    vertical: fc.vertical, budgetDaily: b.budgetDaily, runtimeDays: b.runtimeDays,
    params: { ...(b.params || {}), aov: (fc.ecom || {}).aov, margin: (fc.ecom || {}).margin },
  }));

  // Keep the active tab valid as blocks are added/removed.
  let active = activeTab;
  if (active === "compare" && fc.blocks.length < 2) active = 0;
  if (typeof active === "number" && active >= fc.blocks.length) active = fc.blocks.length ? fc.blocks.length - 1 : null;
  if (active === null && fc.blocks.length) active = 0;

  const projectId = fc.projectId || (project && project.id) || null;
  const projectName = (project && project.clientName) || fc.clientName || t("untitled");
  const caseLabelPh = `${t("caseWord")} ${fc.caseNo || 1}`;
  const saveLabel = saveState === "saving" ? t("saving") : saveState === "saved" ? t("saved") : saveState === "dirty" ? "•" : "";

  async function duplicate() {
    if (dupBusy || !MesperDB.casesDuplicateForecast) return;
    setDupBusy(true);
    try {
      let nextNo = (fc.caseNo || 1) + 1;
      if (projectId && MesperDB.casesListForecastsByProject) {
        const sibs = await MesperDB.casesListForecastsByProject(projectId);
        nextNo = sibs.reduce((m, s) => Math.max(m, s.caseNo || 0), 0) + 1;
      }
      const dup = await MesperDB.casesDuplicateForecast(fc.id, nextNo);
      navigate("/cases/c/" + dup.id);
    } catch (e) { window.alert((e && e.message) || "error"); } finally { setDupBusy(false); }
  }

  const tabBtn = (key, label, on, onClick, dot) => (
    <button key={key} type="button" onClick={onClick} className="vp-focusable"
      style={{ display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 13px", minHeight: 40, borderRadius: 6, border: "none", cursor: "pointer", fontFamily: "var(--font-body)", fontWeight: 600, fontSize: 13.5, whiteSpace: "nowrap",
        background: on ? "var(--surface)" : "transparent", color: on ? "var(--accent-ink)" : "var(--ink-3)", boxShadow: on ? "var(--sh-sm)" : "none", transition: "color .14s, background .14s" }}>
      {dot && <span style={{ width: 8, height: 8, borderRadius: 3, background: dot, flex: "0 0 auto" }} />}
      {label}
    </button>
  );

  return (
    <div className="vp-fade-up" style={{ maxWidth: 1240, margin: "0 auto" }}>
      {/* header — breadcrumb (project) + editable case label, actions right */}
      <div style={{ display: "flex", alignItems: "flex-start", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
        <div style={{ flex: "1 1 260px", minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4, minWidth: 0 }}>
            <button type="button" onClick={() => navigate(projectId ? "/cases/p/" + projectId : "/cases")} className="vp-focusable"
              style={{ border: "none", background: "transparent", color: "var(--accent-ink)", fontFamily: "var(--font-body)", fontWeight: 700, fontSize: 13.5, cursor: "pointer", padding: "2px 2px", maxWidth: "60%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {projectName}
            </button>
            <span style={{ color: "var(--ink-4)", fontSize: 13.5, flex: "0 0 auto" }}>/</span>
          </div>
          <input value={fc.label || ""} onChange={(e) => mutate({ label: e.target.value })} placeholder={caseLabelPh}
            className="case-title-input"
            style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: narrow ? 23 : 29, color: "var(--ink)", border: "none", background: "transparent", outline: "none", width: "100%", padding: "2px 4px", margin: "-2px -4px", borderRadius: "var(--r-sm)", letterSpacing: "-0.015em" }} />
        </div>
        <div style={{ flex: "0 0 auto", display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontSize: 12.5, color: "var(--ink-4)", minWidth: 24, textAlign: "right" }}>{saveLabel}</span>
          <Button variant="outline" size="sm" icon="repeat" onClick={duplicate} disabled={dupBusy}>{t("duplicate")}</Button>
          <Button size="sm" icon="download" onClick={() => setExporting(true)} disabled={!results.some(Boolean)}>{t("export")}</Button>
        </div>
      </div>

      {/* scope chips — read only; industry/sub are edited on the project page */}
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center", marginBottom: 18, paddingBottom: 16, borderBottom: "1px solid var(--line)" }}>
        <CaseChip>{t(fc.vertical)}</CaseChip>
        <CaseChip>{t(fc.region)} · {currency}</CaseChip>
        {(fc.industry || "general") !== "general" && (
          <CaseChip>{caseIndustryLabel(fc.industry, lang)}{fc.subIndustry ? ` · ${caseSubIndustryLabel(fc.subIndustry, lang)}` : ""}</CaseChip>
        )}
      </div>

      {/* platform tabs */}
      <div style={{ position: "relative", marginBottom: "var(--gap)" }}>
        <div style={{ display: "inline-flex", gap: 3, background: "var(--surface-2)", border: "1px solid var(--line-2)", borderRadius: "var(--r-md)", padding: 3, flexWrap: "wrap", maxWidth: "100%" }}>
          {fc.blocks.map((b, i) => tabBtn("blk" + i, CASE_PLATFORMS[b.platform] ? CASE_PLATFORMS[b.platform].label : b.platform, active === i, () => setActiveTab(i), CASE_PLATFORMS[b.platform] && CASE_PLATFORMS[b.platform].fg))}
          {fc.blocks.length > 1 && tabBtn("cmp", t("compare"), active === "compare", () => setActiveTab("compare"))}
          {freePlatforms.length > 0 && (
            <button type="button" onClick={() => setAddOpen((o) => !o)} className="vp-focusable"
              style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "8px 12px", minHeight: 40, borderRadius: 6, border: "none", cursor: "pointer", fontFamily: "var(--font-body)", fontWeight: 600, fontSize: 13.5, background: "transparent", color: "var(--ink-4)", whiteSpace: "nowrap" }}>
              <Icon name="plus" size={15} stroke={2.2} /> {t("addPlatformTab")}
            </button>
          )}
        </div>
        {addOpen && freePlatforms.length > 0 && (
          <div style={{ position: "absolute", top: "100%", left: 0, marginTop: 6, zIndex: 20, background: "var(--surface)", border: "1px solid var(--line-2)", borderRadius: "var(--r-md)", boxShadow: "var(--sh-pop)", padding: 6, minWidth: 180, display: "grid", gap: 2 }}>
            {freePlatforms.map((p) => (
              <button key={p} type="button" onClick={() => addPlatform(p)} className="vp-focusable"
                style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "9px 11px", minHeight: 40, border: "none", background: "transparent", borderRadius: "var(--r-sm)", cursor: "pointer", fontFamily: "var(--font-body)", fontWeight: 600, fontSize: 13.5, color: "var(--ink-2)", textAlign: "left" }}>
                <span style={{ width: 8, height: 8, borderRadius: 3, background: CASE_PLATFORMS[p].fg, flex: "0 0 auto" }} />
                {CASE_PLATFORMS[p].label}
              </button>
            ))}
          </div>
        )}
      </div>

      {/* active tab content */}
      {fc.blocks.length === 0 ? (
        <Card style={{ textAlign: "center", padding: "40px 20px", boxShadow: "var(--sh-md)" }}>
          <div style={{ color: "var(--ink-4)", fontSize: 14.5 }}>{t("noBlocks")}</div>
        </Card>
      ) : active === "compare" ? (
        <Card style={{ boxShadow: "var(--sh-md)" }}>
          <ComparisonTable blocks={fc.blocks} results={results} vertical={fc.vertical} currency={currency} lang={lang} />
        </Card>
      ) : (
        <PlatformTab key={fc.blocks[active].platform + active} block={fc.blocks[active]} vertical={fc.vertical} region={fc.region}
          caseIndustry={fc.industry} caseSub={fc.subIndustry} currency={currency} economics={fc.ecom}
          onEcomChange={(ec) => mutate({ ecom: ec })} templates={templates} lang={lang} narrow={narrow}
          onChange={(nb) => setBlock(active, nb)} onRemove={() => removeBlock(active)} />
      )}

      {exporting && <CaseExport forecast={fc} project={project} results={results} templates={templates} user={user} onClose={() => setExporting(false)} />}
    </div>
  );
}

/* ============================================================
   New project modal — creates the project and its Case 1
   ============================================================ */
function NewProjectModal({ templates, onClose, onCreated }) {
  const { t, lang } = useCT();
  const [clientName, setClientName] = useState("");
  const [vertical, setVerticalState] = useState("leadgen");
  const [region, setRegion] = useState("dach");
  const [industry, setIndustryState] = useState("general");
  const [subIndustry, setSubIndustry] = useState("");
  const [platforms, setPlatforms] = useState(["meta"]);
  const [busy, setBusy] = useState(false);
  const subs = subIndustriesFor(industry);
  // Beim Vertical-/Branchen-Wechsel nicht mehr passende Ebenen zuruecksetzen.
  const setVertical = (v) => { setVerticalState(v); if (!industriesForVertical(v).some(([k]) => k === industry)) { setIndustryState("general"); setSubIndustry(""); } };
  const setIndustry = (v) => { setIndustryState(v); setSubIndustry(""); };
  const toggle = (p) => setPlatforms((ps) => (ps.includes(p) ? ps.filter((x) => x !== p) : [...ps, p]));

  const buildBlocks = () => platforms.map((p) => {
    const tpl = casePickTemplate(templates, p, vertical, region, industry, subIndustry);
    return {
      platform: p, templateId: tpl ? tpl.id : null, templateName: tpl ? tpl.name : null,
      budgetDaily: null, runtimeDays: 30,
      params: tpl ? JSON.parse(JSON.stringify(tpl.params || {})) : { cpcSpread: 0.2, cvr: {}, stages: vertical === "leadgen" ? CASE_STAGE_PRESETS(lang)[0].stages.map((s) => ({ ...s })) : undefined },
    };
  });

  async function create() {
    if (busy || !clientName.trim() || platforms.length === 0) return;
    setBusy(true);
    try {
      const name = clientName.trim();
      const currency = caseCurrency(region);
      const project = await MesperDB.casesCreateProject({ clientName: name, vertical, region, industry, subIndustry: subIndustry || null, currency });
      const fc = await MesperDB.casesCreateForecast({
        projectId: project.id, clientName: name, vertical, region, industry, subIndustry: subIndustry || null,
        currency, blocks: buildBlocks(), caseNo: 1, label: `${t("caseWord")} 1`,
      });
      onCreated(project, fc);
    } catch (e) { setBusy(false); window.alert((e && e.message) || "error"); }
  }

  const footer = (
    <>
      <span />
      <div style={{ display: "flex", gap: 10 }}>
        <Button variant="ghost" onClick={onClose}>{t("cancel")}</Button>
        <Button onClick={create} disabled={busy || !clientName.trim() || platforms.length === 0} icon="plus">{t("create")}</Button>
      </div>
    </>
  );
  return (
    <CaseModalShell title={t("newProject")} subtitle={t("newProjectSub")} onClose={onClose} width={540} footer={footer}>
      <CaseFieldGroup label={t("client")}>
        <input autoFocus value={clientName} onChange={(e) => setClientName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") create(); }}
          placeholder={t("projectPh")} style={{ ...caseInput, fontSize: 15, padding: "11px 13px" }} />
      </CaseFieldGroup>
      <div style={{ display: "flex", gap: 18, flexWrap: "wrap" }}>
        <CaseFieldGroup label={t("vertical")} style={{ flex: "1 1 190px", minWidth: 0 }}>
          <SegControl value={vertical} onChange={setVertical} options={[["leadgen", t("leadgen")], ["ecom", t("ecom")]]} />
        </CaseFieldGroup>
        <CaseFieldGroup label={t("region")} style={{ flex: "1 1 190px", minWidth: 0 }}>
          <SegControl value={region} onChange={setRegion} options={[["dach", `DACH · ${caseCurrency("dach")}`], ["us", `US · ${caseCurrency("us")}`]]} />
        </CaseFieldGroup>
      </div>
      <div style={{ display: "flex", gap: 18, flexWrap: "wrap" }}>
        <CaseFieldGroup label={t("industry")} hint={subs.length === 0 ? t("industryHint") : null} style={{ flex: "1 1 240px", minWidth: 0 }}>
          <select value={industry} onChange={(e) => setIndustry(e.target.value)} style={{ ...caseInput }}>
            {industriesForVertical(vertical).map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
          </select>
        </CaseFieldGroup>
        {subs.length > 0 && (
          <CaseFieldGroup label={t("subIndustry")} hint={t("industryHint")} style={{ flex: "1 1 240px", minWidth: 0 }}>
            <select value={subIndustry} onChange={(e) => setSubIndustry(e.target.value)} style={{ ...caseInput }}>
              <option value="">{t("noSub")}</option>
              {subs.map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
            </select>
          </CaseFieldGroup>
        )}
      </div>
      <CaseFieldGroup label={t("platforms")}>
        <ToggleChips options={Object.keys(CASE_PLATFORMS).map((p) => [p, CASE_PLATFORMS[p].label])} selected={platforms} onToggle={toggle} />
      </CaseFieldGroup>
    </CaseModalShell>
  );
}

/* ============================================================
   Cases home (project list)
   ============================================================ */
function CasesHome({ user, navigate }) {
  const { t, lang } = useCT();
  const [list, setList] = useState(null);
  const [templates, setTemplates] = useState([]);
  const [err, setErr] = useState("");
  const [creating, setCreating] = useState(false);
  const narrow = useIsNarrow(820);

  useEffect(() => {
    let on = true;
    Promise.all([MesperDB.casesListProjects(), MesperDB.casesListTemplates()])
      .then(([l, tp]) => { if (on) { setList(l); setTemplates(tp); } })
      .catch((e) => on && setErr((e && e.message) || "error"));
    return () => { on = false; };
  }, []);

  if (err) return <div style={{ padding: 30, color: "var(--st-declined)", fontSize: 14.5 }}>{t("loadError")}<div style={{ color: "var(--ink-4)", fontSize: 12.5, marginTop: 6 }}>{err}</div></div>;

  return (
    <div className="vp-fade-up" style={{ maxWidth: 1000, margin: "0 auto" }}>
      <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 14, marginBottom: 22, flexWrap: "wrap" }}>
        <div>
          <Eyebrow>Mesper Cases</Eyebrow>
          <h1 style={{ fontSize: narrow ? 26 : 32, margin: "6px 0 0" }}>{t("projects")}</h1>
        </div>
        <Button icon="plus" onClick={() => setCreating(true)}>{t("newProject")}</Button>
      </div>

      {list === null ? (
        <div style={{ color: "var(--ink-4)", padding: 20 }}>…</div>
      ) : list.length === 0 ? (
        <Card style={{ textAlign: "center", padding: "56px 24px", boxShadow: "var(--sh-md)" }}>
          <div style={{ width: 60, height: 60, margin: "0 auto 16px", borderRadius: "var(--r-lg)", background: CASE_GRADIENT, color: "#fff", display: "grid", placeItems: "center", boxShadow: "0 8px 22px rgba(90,88,231,0.28)" }}><Icon name="chart" size={28} stroke={2} /></div>
          <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 19, marginBottom: 7 }}>{t("emptyProjects")}</div>
          <div style={{ color: "var(--ink-4)", fontSize: 14.5, maxWidth: 440, margin: "0 auto 22px", lineHeight: 1.55 }}>{t("emptyProjectsHint")}</div>
          <Button icon="plus" onClick={() => setCreating(true)}>{t("newProject")}</Button>
        </Card>
      ) : (
        <div style={{ display: "grid", gridTemplateColumns: narrow ? "1fr" : "1fr 1fr", gap: 14 }}>
          {list.map((p) => (
            <button key={p.id} type="button" onClick={() => navigate("/cases/p/" + p.id)} className="case-row vp-focusable"
              style={{ textAlign: "left", cursor: "pointer", padding: "18px 20px", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-lg)", boxShadow: "var(--sh-sm)", fontFamily: "var(--font-body)" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
                <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 17, color: "var(--ink)", flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.clientName || t("untitled")}</div>
                <CaseChip fg="var(--accent-ink)" bg="var(--accent-soft)">{p.caseCount || 0} {t("casesCount")}</CaseChip>
                <Icon name="arrowRight" size={17} style={{ color: "var(--ink-4)", flex: "0 0 auto" }} />
              </div>
              <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10 }}>
                <CaseChip>{t(p.vertical)}</CaseChip>
                <CaseChip>{t(p.region)}</CaseChip>
                {(p.industry || "general") !== "general" && <CaseChip>{caseIndustryLabel(p.industry, lang)}{p.subIndustry ? ` · ${caseSubIndustryLabel(p.subIndustry, lang)}` : ""}</CaseChip>}
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-4)" }}>
                {t("lastEdited")} {caseRel(p.casesUpdatedAt || p.updatedAt, lang)}{casePersonName(p.updatedBy || p.createdBy) ? ` · ${t("by")} ${casePersonName(p.updatedBy || p.createdBy)}` : ""}
              </div>
            </button>
          ))}
        </div>
      )}

      {creating && <NewProjectModal templates={templates} onClose={() => setCreating(false)} onCreated={(project, fc) => { setCreating(false); navigate("/cases/c/" + fc.id); }} />}
    </div>
  );
}

/* ============================================================
   Project view — one client, its cases as rows
   ============================================================ */
function caseSummary(fc, t, lang) {
  let spend = 0; let kpi = null;
  (fc.blocks || []).forEach((b) => {
    const r = CE.computePlatform({ vertical: fc.vertical, budgetDaily: b.budgetDaily, runtimeDays: b.runtimeDays,
      params: { ...(b.params || {}), aov: (fc.ecom || {}).aov, margin: (fc.ecom || {}).margin } });
    if (r && r.spend) spend += r.spend;
    if (!kpi && r && r.scenarios && r.scenarios.mid) {
      const mid = r.scenarios.mid;
      if (fc.vertical === "ecom") { if (mid.roas !== null && mid.roas !== undefined) kpi = { label: t("roas"), value: CE.fmtNum(mid.roas, lang, 2) }; }
      else kpi = { label: t("cpl"), value: CE.fmtMoney(mid.cpl, fc.currency, lang) };
    }
  });
  return { spend, kpi };
}

function ProjectView({ id, user, navigate }) {
  const { t, lang } = useCT();
  const [project, setProject] = useState(null);
  const [cases, setCases] = useState(null);
  const [templates, setTemplates] = useState([]);
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState(false);
  const narrow = useIsNarrow(820);

  // Defensiv doppelt gefiltert: der Client blendet Archivierte aus, die UI auch.
  const onlyActive = (cs) => (cs || []).filter((c) => !c.archived);
  const reloadCases = () => MesperDB.casesListForecastsByProject(id).then((cs) => setCases(onlyActive(cs)));
  useEffect(() => {
    let on = true;
    Promise.all([MesperDB.casesGetProject(id), MesperDB.casesListForecastsByProject(id), MesperDB.casesListTemplates()])
      .then(([p, cs, tp]) => { if (on) { setProject(p); setCases(onlyActive(cs)); setTemplates(tp); } })
      .catch((e) => on && setErr((e && e.message) || "error"));
    return () => { on = false; };
  }, [id]);

  if (err) return <div style={{ padding: 30, color: "var(--st-declined)", fontSize: 14.5 }}>{t("loadError")}<div style={{ color: "var(--ink-4)", fontSize: 12.5, marginTop: 6 }}>{err}</div></div>;
  if (!project) return <div style={{ padding: 30, color: "var(--ink-4)" }}>…</div>;

  const currency = project.currency;
  const nextCaseNo = () => (cases || []).reduce((m, c) => Math.max(m, c.caseNo || 0), 0) + 1;

  const saveProject = (patch) => { setProject((p) => ({ ...p, ...patch })); };
  const commitProject = (patch) => MesperDB.casesUpdateProject(id, patch).catch(() => {});

  async function newCase() {
    if (busy) return;
    setBusy(true);
    try {
      const tpl = casePickTemplate(templates, "meta", project.vertical, project.region, project.industry, project.subIndustry);
      const no = nextCaseNo();
      const fc = await MesperDB.casesCreateForecast({
        projectId: id, clientName: project.clientName, vertical: project.vertical, region: project.region,
        industry: project.industry, subIndustry: project.subIndustry || null, currency,
        caseNo: no, label: `${t("caseWord")} ${no}`,
        blocks: [{
          platform: "meta", templateId: tpl ? tpl.id : null, templateName: tpl ? tpl.name : null,
          budgetDaily: null, runtimeDays: 30,
          params: tpl ? JSON.parse(JSON.stringify(tpl.params || {})) : { cpcSpread: 0.2, cvr: {}, stages: project.vertical === "leadgen" ? CASE_STAGE_PRESETS(lang)[0].stages.map((s) => ({ ...s })) : undefined },
        }],
      });
      navigate("/cases/c/" + fc.id);
    } catch (e) { setBusy(false); window.alert((e && e.message) || "error"); }
  }

  async function duplicateCase(fc) {
    try { await MesperDB.casesDuplicateForecast(fc.id, nextCaseNo()); await reloadCases(); }
    catch (e) { window.alert((e && e.message) || "error"); }
  }
  async function archiveCase(fc) {
    if (!window.confirm(t("archiveCase"))) return;
    try { await MesperDB.casesUpdateForecast(fc.id, { archived: true }); await reloadCases(); }
    catch (e) { window.alert((e && e.message) || "error"); }
  }

  const rowBtn = { width: 40, height: 40, display: "grid", placeItems: "center", borderRadius: "var(--r-sm)", border: "1px solid var(--line-2)", background: "var(--surface)", color: "var(--ink-4)", cursor: "pointer", flex: "0 0 auto" };
  const stop = (e) => e.stopPropagation();

  return (
    <div className="vp-fade-up" style={{ maxWidth: 1000, margin: "0 auto" }}>
      {/* project header */}
      <div style={{ marginBottom: 20, paddingBottom: 18, borderBottom: "1px solid var(--line)" }}>
        <div style={{ display: "flex", alignItems: "flex-start", gap: 12, flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 260px", minWidth: 0 }}>
            <Eyebrow style={{ marginBottom: 5 }}>{t("project")}</Eyebrow>
            <input value={project.clientName || ""} onChange={(e) => saveProject({ clientName: e.target.value })} onBlur={(e) => commitProject({ clientName: e.target.value })}
              placeholder={t("projectPh")} className="case-title-input"
              style={{ fontFamily: "var(--font-display)", fontWeight: 800, fontSize: narrow ? 24 : 30, color: "var(--ink)", border: "none", background: "transparent", outline: "none", width: "100%", padding: "2px 4px", margin: "-2px -4px", borderRadius: "var(--r-sm)", letterSpacing: "-0.015em" }} />
          </div>
          <Button variant="outline" size="sm" icon="trash"
            onClick={async () => { if (window.confirm(t("archiveProjectQ"))) { await MesperDB.casesUpdateProject(id, { archived: true }); navigate("/cases"); } }}>{t("archive")}</Button>
        </div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 12 }}>
          <CaseChip>{t(project.vertical)}</CaseChip>
          <CaseChip>{t(project.region)} · {currency}</CaseChip>
          {(project.industry || "general") !== "general" && (
            <CaseChip>{caseIndustryLabel(project.industry, lang)}{project.subIndustry ? ` · ${caseSubIndustryLabel(project.subIndustry, lang)}` : ""}</CaseChip>
          )}
        </div>
      </div>

      {/* cases */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginBottom: 12, flexWrap: "wrap" }}>
        <Eyebrow>{t("cases")}</Eyebrow>
        <Button size="sm" icon="plus" onClick={newCase} disabled={busy}>{t("newCase")}</Button>
      </div>

      {cases === null ? (
        <div style={{ color: "var(--ink-4)", padding: 20 }}>…</div>
      ) : cases.length === 0 ? (
        <Card style={{ color: "var(--ink-4)", fontSize: 14, padding: "22px 20px" }}>{t("emptyCases")}</Card>
      ) : (
        <div style={{ display: "grid", gap: 10 }}>
          {cases.map((fc) => {
            const { spend, kpi } = caseSummary(fc, t, lang);
            return (
              <div key={fc.id} role="button" tabIndex={0} onClick={() => navigate("/cases/c/" + fc.id)}
                onKeyDown={(e) => { if (e.key === "Enter") navigate("/cases/c/" + fc.id); }}
                className="case-row vp-focusable"
                style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap", padding: "14px 16px", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-lg)", boxShadow: "var(--sh-sm)", cursor: "pointer" }}>
                <span style={{ flex: "0 0 auto", width: 34, height: 34, borderRadius: "var(--r-sm)", background: "var(--surface-2)", border: "1px solid var(--line-2)", display: "grid", placeItems: "center", fontFamily: "var(--font-display)", fontWeight: 800, fontSize: 14, color: "var(--ink-3)" }}>{fc.caseNo || "?"}</span>
                {/* Label als Text: die ganze Zeile navigiert, umbenannt wird im Editor-Breadcrumb */}
                <div style={{ flex: "1 1 160px", minWidth: 120, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15.5, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                  {fc.label || `${t("caseWord")} ${fc.caseNo || 1}`}
                </div>
                <div style={{ flex: "0 0 auto", textAlign: "right", minWidth: 84 }}>
                  <div style={{ fontSize: 11, fontWeight: 600, color: "var(--ink-4)" }}>{t("budgetTotal")}</div>
                  <div style={{ fontSize: 14, fontWeight: 700, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>{spend > 0 ? fmtMoneyAuto(spend, currency, lang) : "–"}</div>
                </div>
                {kpi && (
                  <div style={{ flex: "0 0 auto", textAlign: "right", minWidth: 72 }}>
                    <div style={{ fontSize: 11, fontWeight: 600, color: "var(--ink-4)" }}>{kpi.label} · {t("scenarioMid")}</div>
                    <div style={{ fontSize: 14, fontWeight: 700, color: "var(--accent-ink)", fontVariantNumeric: "tabular-nums" }}>{kpi.value}</div>
                  </div>
                )}
                {!narrow && <div style={{ flex: "0 0 auto", fontSize: 12, color: "var(--ink-4)", minWidth: 74, textAlign: "right" }}>{t("lastEdited")} {caseRel(fc.updatedAt, lang)}</div>}
                <div style={{ flex: "0 0 auto", display: "flex", gap: 6 }} onClick={stop}>
                  <button type="button" onClick={() => duplicateCase(fc)} title={t("duplicateCase")} className="case-navbtn" style={rowBtn}><Icon name="repeat" size={16} /></button>
                  <button type="button" onClick={() => archiveCase(fc)} title={t("archive")} className="case-navbtn danger" style={rowBtn}><Icon name="trash" size={16} /></button>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ============================================================
   Templates view + editor modal
   ============================================================ */
function TemplateModal({ tpl, onClose, onSaved }) {
  const { t, lang } = useCT();
  const isNew = !tpl.id;
  const [form, setForm] = useState(() => JSON.parse(JSON.stringify({
    name: tpl.name || "", kind: tpl.kind || "company", vertical: tpl.vertical || "leadgen",
    platform: tpl.platform || "meta", region: tpl.region || "dach", industry: tpl.industry || "general",
    subIndustry: tpl.subIndustry || "",
    params: tpl.params || { cpcSpread: 0.2, cvr: {} },
    source: tpl.source || "", sourceDate: tpl.sourceDate || "", confidence: tpl.confidence || "", notes: tpl.notes || "",
  })));
  const [busy, setBusy] = useState(false);
  const set = (patch) => setForm((f) => {
    const next = { ...f, ...patch };
    if ("vertical" in patch && !industriesForVertical(next.vertical).some(([k]) => k === next.industry)) { next.industry = "general"; next.subIndustry = ""; }
    if ("industry" in patch) next.subIndustry = "";
    return next;
  });
  const formSubs = subIndustriesFor(form.industry);
  const setParam = (patch) => setForm((f) => ({ ...f, params: { ...f.params, ...patch } }));
  const curSym = caseCurrency(form.region) === "USD" ? "$" : "€";

  async function save() {
    if (busy || !form.name.trim()) return;
    setBusy(true);
    try {
      const payload = { ...form, name: form.name.trim(), subIndustry: form.subIndustry || null, currency: caseCurrency(form.region), confidence: form.confidence || null };
      const saved = isNew ? await MesperDB.casesCreateTemplate(payload)
        : await MesperDB.casesUpdateTemplate(tpl.id, { name: payload.name, params: payload.params, source: payload.source, sourceDate: payload.sourceDate, confidence: payload.confidence, notes: payload.notes });
      onSaved(saved);
    } catch (e) { setBusy(false); window.alert((e && e.message) || "error"); }
  }

  const chips = !isNew ? (
    <>
      <KindChip kind={form.kind} />
      <CaseChip style={{ fontSize: 11 }}>{caseIndustryLabel(form.industry, lang)}{form.subIndustry ? ` · ${caseSubIndustryLabel(form.subIndustry, lang)}` : ""}</CaseChip>
      <PlatformChip platform={form.platform} size="sm" />
      <CaseChip style={{ fontSize: 11 }}>{t(form.vertical)}</CaseChip>
      <CaseChip style={{ fontSize: 11 }}>{t(form.region)}</CaseChip>
    </>
  ) : null;
  const footer = (
    <>
      {!isNew ? (
        <Button variant="decline" icon="trash" onClick={async () => { if (window.confirm(t("archiveCase"))) { await MesperDB.casesUpdateTemplate(tpl.id, { archived: true }); onSaved(null); } }}>{t("archive")}</Button>
      ) : <span />}
      <div style={{ display: "flex", gap: 10 }}>
        <Button variant="ghost" onClick={onClose}>{t("cancel")}</Button>
        <Button onClick={save} disabled={busy || !form.name.trim()}>{t("save")}</Button>
      </div>
    </>
  );
  return (
    <CaseModalShell title={isNew ? t("newTemplate") : form.name} onClose={onClose} width={660} chips={chips} footer={footer}>
      <CaseFieldGroup label={t("tplIdentity")}>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap", alignItems: "flex-end" }}>
          <label style={{ flex: "1 1 220px" }}>
            <input value={form.name} onChange={(e) => set({ name: e.target.value })} placeholder={t("name")} style={{ ...caseInput, fontSize: 15, padding: "11px 13px" }} />
          </label>
          {isNew && <SegControl size="sm" value={form.kind} onChange={(k) => set({ kind: k })} options={[["company", t("company")], ["benchmark", t("benchmark")]]} />}
        </div>
      </CaseFieldGroup>

      {isNew && (
        <CaseFieldGroup label={t("tplScope")}>
          <div style={{ display: "flex", gap: 18, flexWrap: "wrap", alignItems: "center" }}>
            <SegControl size="sm" value={form.vertical} onChange={(v) => set({ vertical: v })} options={[["leadgen", t("leadgen")], ["ecom", t("ecom")]]} />
            <SegControl size="sm" value={form.platform} onChange={(p) => set({ platform: p })} options={Object.keys(CASE_PLATFORMS).map((p) => [p, CASE_PLATFORMS[p].label])} />
            <SegControl size="sm" value={form.region} onChange={(r) => set({ region: r })} options={[["dach", t("dach")], ["us", t("us")]]} />
            <select value={form.industry} onChange={(e) => set({ industry: e.target.value })} style={{ ...caseInput, width: "auto", minWidth: 180 }}>
              {industriesForVertical(form.vertical).map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
            </select>
            {formSubs.length > 0 && (
              <select value={form.subIndustry} onChange={(e) => set({ subIndustry: e.target.value })} title={t("subIndustry")} style={{ ...caseInput, width: "auto", minWidth: 180 }}>
                <option value="">{t("noSub")}</option>
                {formSubs.map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
              </select>
            )}
          </div>
        </CaseFieldGroup>
      )}

      {/* Benchmark values — set off in an inset card so the core numbers read as a unit */}
      <CaseFieldGroup label={t("tplValues")}>
        <div style={{ background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", padding: "16px 16px 18px" }}>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0,1fr))", gap: 10 }}>
            <NumField fill label={t("cpcLow")} value={form.params.cpcLow} onChange={(v) => setParam({ cpcLow: v })} suffix={curSym} />
            <NumField fill label={t("baseCpc")} value={form.params.baseCpc} onChange={(v) => setParam({ baseCpc: v })} suffix={curSym} />
            <NumField fill label={t("cpcHigh")} value={form.params.cpcHigh} onChange={(v) => setParam({ cpcHigh: v })} suffix={curSym} />
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0,1fr))", gap: 10, marginTop: 14 }}>
            {CE.SCENARIOS.map((sc) => (
              <NumField fill key={sc} label={`${t("cvr")} ${t(sc)}`} value={form.params.cvr && form.params.cvr[sc]} onChange={(v) => setParam({ cvr: { ...(form.params.cvr || {}), [sc]: v } })} pct suffix="%" />
            ))}
          </div>
          {form.vertical === "leadgen" && (
            <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid var(--line-2)" }}>
              <StagesEditor stages={form.params.stages} onChange={(st) => setParam({ stages: st })} lang={lang} />
            </div>
          )}
        </div>
      </CaseFieldGroup>

      <CaseFieldGroup label={t("tplSource")}>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-end" }}>
          <label style={{ flex: "2 1 240px" }}>
            <span style={caseLabel}>{t("sourceLabel")}</span>
            <input value={form.source} onChange={(e) => set({ source: e.target.value })} style={caseInput} placeholder="WordStream, Statista…" />
          </label>
          <label style={{ width: 110 }}>
            <span style={caseLabel}>{t("stand")}</span>
            <input value={form.sourceDate} onChange={(e) => set({ sourceDate: e.target.value })} style={caseInput} placeholder="2025-Q4" />
          </label>
          <label style={{ width: 130 }}>
            <span style={caseLabel}>{t("confidence")}</span>
            <select value={form.confidence} onChange={(e) => set({ confidence: e.target.value })} style={caseInput}>
              <option value="">–</option><option value="high">high</option><option value="medium">medium</option><option value="low">low</option>
            </select>
          </label>
        </div>
      </CaseFieldGroup>
    </CaseModalShell>
  );
}

function TemplatesView({ user }) {
  const { t, lang } = useCT();
  const [list, setList] = useState(null);
  const [err, setErr] = useState("");
  const [editing, setEditing] = useState(null); // template object or {} for new
  const [flt, setFlt] = useState({ industry: "", subIndustry: "", platform: "", vertical: "", region: "" });
  const narrow = useIsNarrow(820);
  const fltSubs = flt.industry ? subIndustriesFor(flt.industry) : [];

  const reload = () => MesperDB.casesListTemplates().then(setList).catch((e) => setErr((e && e.message) || "error"));
  useEffect(() => { reload(); }, []);

  if (err) return <div style={{ padding: 30, color: "var(--st-declined)", fontSize: 14.5 }}>{t("loadError")}<div style={{ color: "var(--ink-4)", fontSize: 12.5, marginTop: 6 }}>{err}</div></div>;

  const groups = [
    { key: "benchmark", label: t("tplBenchmarks") },
    { key: "company", label: t("tplCompany") },
  ];
  const matches = (x) =>
    (!flt.industry || (x.industry || "general") === flt.industry) &&
    (!flt.subIndustry || (x.subIndustry || "") === flt.subIndustry) &&
    (!flt.platform || x.platform === flt.platform) &&
    (!flt.vertical || x.vertical === flt.vertical) &&
    (!flt.region || x.region === flt.region);
  const fltSel = (key, allLabel, options) => (
    <select key={key} value={flt[key]} onChange={(e) => setFlt({ ...flt, [key]: e.target.value })}
      style={{ ...caseInput, width: "auto", fontSize: 13, padding: "7px 9px", color: flt[key] ? "var(--accent-ink)" : "var(--ink-4)", borderColor: flt[key] ? "var(--accent)" : "var(--line-2)", fontWeight: 600 }}>
      <option value="">{allLabel}</option>
      {options.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
    </select>
  );

  const row = (x) => (
    <button key={x.id} type="button" onClick={() => setEditing(x)} className="case-row vp-focusable"
      style={{ display: "flex", alignItems: "center", gap: 10, width: "100%", textAlign: "left", padding: "13px 15px", minHeight: 52, border: "1px solid var(--line)", borderRadius: "var(--r-md)", background: "var(--surface)", boxShadow: "var(--sh-sm)", cursor: "pointer", marginBottom: 8, fontFamily: "var(--font-body)" }}>
      <div style={{ fontWeight: 600, fontSize: 14, color: "var(--ink)", flex: "1 1 200px", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{x.name}</div>
      {!narrow && <CaseChip style={{ fontSize: 11 }}>{caseIndustryLabel(x.industry, lang)}{x.subIndustry ? ` · ${caseSubIndustryLabel(x.subIndustry, lang)}` : ""}</CaseChip>}
      {!narrow && <PlatformChip platform={x.platform} size="sm" />}
      {!narrow && <CaseChip style={{ fontSize: 11 }}>{t(x.vertical)}</CaseChip>}
      {!narrow && <CaseChip style={{ fontSize: 11 }}>{t(x.region)}</CaseChip>}
      <div style={{ fontSize: 12.5, color: "var(--ink-4)", whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" }}>
        CPC {x.params && x.params.baseCpc ? CE.fmtMoney(x.params.baseCpc, x.currency, lang) : "–"}
        {" · "}{t("cvr")} {x.params && x.params.cvr && x.params.cvr.mid ? CE.fmtPct(x.params.cvr.mid, lang) : "–"}
      </div>
      {!narrow && x.sourceDate && <div style={{ fontSize: 12, color: "var(--ink-4)", whiteSpace: "nowrap" }}>{t("stand")} {x.sourceDate}</div>}
      <Icon name="chevRight" size={16} style={{ color: "var(--ink-4)", flex: "0 0 auto" }} />
    </button>
  );

  return (
    <div className="vp-fade-up" style={{ maxWidth: 1000, margin: "0 auto" }}>
      <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 14, marginBottom: 22, flexWrap: "wrap" }}>
        <div>
          <Eyebrow>Mesper Cases</Eyebrow>
          <h1 style={{ fontSize: narrow ? 26 : 32, margin: "6px 0 0" }}>{t("templates")}</h1>
        </div>
        <Button icon="plus" onClick={() => setEditing({})}>{t("newTemplate")}</Button>
      </div>

      {/* filter bar — contained as a toolbar so it reads separate from the list */}
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center", marginBottom: 20, padding: "11px 12px", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", boxShadow: "var(--sh-sm)" }}>
        <Icon name="search" size={16} style={{ color: "var(--ink-4)", flex: "0 0 auto", marginLeft: 2, marginRight: 2 }} />
        <select value={flt.industry} onChange={(e) => setFlt({ ...flt, industry: e.target.value, subIndustry: "" })}
          style={{ ...caseInput, width: "auto", fontSize: 13, padding: "7px 9px", color: flt.industry ? "var(--accent-ink)" : "var(--ink-4)", borderColor: flt.industry ? "var(--accent)" : "var(--line-2)", fontWeight: 600 }}>
          <option value="">{t("allIndustries")}</option>
          {CASE_INDUSTRIES.map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
        </select>
        <select value={flt.subIndustry} disabled={fltSubs.length === 0} onChange={(e) => setFlt({ ...flt, subIndustry: e.target.value })}
          style={{ ...caseInput, width: "auto", fontSize: 13, padding: "7px 9px", color: flt.subIndustry ? "var(--accent-ink)" : "var(--ink-4)", borderColor: flt.subIndustry ? "var(--accent)" : "var(--line-2)", fontWeight: 600, opacity: fltSubs.length === 0 ? 0.5 : 1, cursor: fltSubs.length === 0 ? "not-allowed" : "pointer" }}>
          <option value="">{t("allSubIndustries")}</option>
          {fltSubs.map(([k, l]) => <option key={k} value={k}>{l[lang === "en" ? "en" : "de"]}</option>)}
        </select>
        {fltSel("platform", t("allPlatforms"), Object.entries(CASE_PLATFORMS).map(([k, p]) => [k, p.label]))}
        {fltSel("vertical", t("allVerticals"), [["leadgen", t("leadgen")], ["ecom", t("ecom")]])}
        {fltSel("region", t("allRegions"), [["dach", t("dach")], ["us", t("us")]])}
        <div style={{ flex: 1 }} />
        {(flt.industry || flt.subIndustry || flt.platform || flt.vertical || flt.region) && (
          <button type="button" onClick={() => setFlt({ industry: "", subIndustry: "", platform: "", vertical: "", region: "" })} className="vp-focusable"
            style={{ border: "none", background: "transparent", color: "var(--ink-4)", fontSize: 12.5, fontWeight: 600, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 4, padding: "6px 8px" }}>
            <Icon name="x" size={13} /> {list !== null ? `${list.filter(matches).length}/${list.length}` : ""}
          </button>
        )}
      </div>

      {list === null ? <div style={{ color: "var(--ink-4)", padding: 20 }}>…</div> : groups.map((g) => {
        const items = list.filter((x) => x.kind === g.key && matches(x));
        return (
          <div key={g.key} style={{ marginBottom: 28 }}>
            <Eyebrow style={{ marginBottom: 10 }}>{g.label}</Eyebrow>
            {items.length === 0 ? <div style={{ color: "var(--ink-4)", fontSize: 13.5, padding: "6px 2px" }}>{t("tplEmpty")}</div> : items.map(row)}
          </div>
        );
      })}
      {list !== null && <div style={{ fontSize: 12.5, color: "var(--ink-4)" }}>{t("benchmarkNote")}</div>}
      {editing !== null && <TemplateModal tpl={editing} onClose={() => setEditing(null)} onSaved={() => { setEditing(null); reload(); }} />}
    </div>
  );
}

/* ============================================================
   Export — 16:9 slides (1280×720 px = 13.333×7.5 in @96dpi),
   printed via the browser dialog: text stays vector, exact pages.
   ============================================================ */
const CASE_GRADIENT = "linear-gradient(135deg, #3490EB 0%, #5A58E7 52%, #7D50CA 100%)";
const EXPORT_INK = "#14142B";

function SlideShell({ children, pageNo, pageCount, title, chip, t }) {
  return (
    <div className="case-slide" style={{ width: 1280, height: 720, background: CASE_GRADIENT, position: "relative", overflow: "hidden", fontFamily: "'Montserrat', Arial, sans-serif", WebkitPrintColorAdjust: "exact", printColorAdjust: "exact", display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "34px 52px 0" }}>
        <div style={{ fontSize: 25, fontWeight: 800, color: "#fff", letterSpacing: "-0.01em" }}>{title}</div>
        {chip}
        <div style={{ flex: 1 }} />
      </div>
      <div style={{ flex: 1, padding: "22px 52px 0", display: "flex", gap: 20, minHeight: 0 }}>{children}</div>
      <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "0 52px 26px", marginTop: 16 }}>
        <img src="/logo.png" alt="Mesper" style={{ height: 17, width: "auto", filter: "brightness(0) invert(1)" }} />
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 10, color: "rgba(255,255,255,0.55)", maxWidth: 560, textAlign: "right", lineHeight: 1.35 }}>{t("disclaimer")}</span>
        <span style={{ fontSize: 11.5, fontWeight: 700, color: "rgba(255,255,255,0.8)", flex: "0 0 auto" }}>{pageNo}/{pageCount}</span>
      </div>
    </div>
  );
}
function SlideCard({ children, style }) {
  return <div style={{ background: "#fff", borderRadius: 16, padding: "20px 24px", color: EXPORT_INK, boxShadow: "0 6px 24px rgba(10,14,40,0.18)", overflow: "hidden", ...style }}>{children}</div>;
}
function SlideKpi({ label, value }) {
  // The single Golden-Yellow accent per slide (brand rule) lives here.
  return (
    <div style={{ flex: "0 0 auto" }}>
      <div style={{ fontSize: 11.5, fontWeight: 700, color: "#5C6680", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 4 }}>{label}</div>
      <div style={{ fontSize: 30, fontWeight: 800, color: EXPORT_INK, lineHeight: 1.1, display: "inline-block", borderBottom: "5px solid #FFD700", paddingBottom: 3 }}>{value}</div>
    </div>
  );
}

function CaseExport({ forecast, project, results, templates, user, onClose }) {
  const { t, lang } = useCT();
  const [scale, setScale] = useState(1);
  const wrapRef = useRef(null);

  useEffect(() => {
    document.body.classList.add("case-print");
    const onResize = () => { const w = wrapRef.current ? wrapRef.current.clientWidth : window.innerWidth; setScale(Math.min(1, (w - 8) / 1280)); };
    onResize();
    window.addEventListener("resize", onResize);
    return () => { document.body.classList.remove("case-print"); window.removeEventListener("resize", onResize); };
  }, []);

  const cur = forecast.currency;
  const projectName = (project && project.clientName) || forecast.clientName || t("untitled");
  const caseSubline = (forecast.label && forecast.label.trim()) || (forecast.caseNo ? `${t("caseWord")} ${forecast.caseNo}` : "");
  const industryLabel = (forecast.industry || "general") !== "general" ? caseIndustryLabel(forecast.industry, lang) : null;
  const subIndustryLabel = forecast.subIndustry ? caseSubIndustryLabel(forecast.subIndustry, lang) : null;
  const platformLabels = forecast.blocks.map((b) => (CASE_PLATFORMS[b.platform] || {}).label || b.platform).join(" · ");
  const dateLabel = (() => { try { return new Date().toLocaleDateString(lang === "en" ? "en-US" : "de-DE", { month: "long", year: "numeric" }); } catch (_) { return ""; } })();
  const tplFor = (b) => templates.find((x) => x.id === b.templateId) || null;
  const srcLine = (b) => {
    const tp = tplFor(b);
    if (tp && tp.kind === "benchmark" && tp.source) return `${t("sourceLabel")}: ${tp.source}${tp.sourceDate ? ` (${t("stand")} ${tp.sourceDate})` : ""}`;
    return lang === "en" ? "Based on Mesper account experience" : "Basis: Mesper Erfahrungswerte";
  };

  /* build slides */
  const slides = [];
  // 1 — title
  slides.push((no, count) => (
    <div key="title" className="case-slide" style={{ width: 1280, height: 720, background: CASE_GRADIENT, position: "relative", overflow: "hidden", fontFamily: "'Montserrat', Arial, sans-serif", WebkitPrintColorAdjust: "exact", printColorAdjust: "exact", display: "flex", flexDirection: "column", justifyContent: "space-between", padding: "44px 60px 34px" }}>
      <img src="/logo.png" alt="Mesper" style={{ height: 26, width: "auto", filter: "brightness(0) invert(1)", alignSelf: "flex-start" }} />
      <div>
        <div style={{ fontSize: 15, fontWeight: 700, color: "rgba(255,255,255,0.78)", textTransform: "uppercase", letterSpacing: "0.14em", marginBottom: 14 }}>{t("exportTitle")}</div>
        <div style={{ fontSize: 58, fontWeight: 800, color: "#fff", letterSpacing: "-0.02em", lineHeight: 1.05, marginBottom: caseSubline ? 8 : 18 }}>{projectName}</div>
        {caseSubline && <div style={{ fontSize: 24, fontWeight: 700, color: "rgba(255,255,255,0.92)", marginBottom: 18 }}>{caseSubline}</div>}
        <div style={{ fontSize: 17, fontWeight: 600, color: "rgba(255,255,255,0.88)" }}>
          {platformLabels} · {t(forecast.vertical)}{industryLabel ? ` · ${industryLabel}` : ""}{subIndustryLabel ? ` · ${subIndustryLabel}` : ""} · {t(forecast.region)} · {dateLabel}
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end" }}>
        <span style={{ fontSize: 10.5, color: "rgba(255,255,255,0.55)", maxWidth: 620, textAlign: "right" }}>{t("disclaimer")}</span>
      </div>
    </div>
  ));

  forecast.blocks.forEach((b, i) => {
    const r = results[i];
    if (!r || !r.matrix) return;
    const mid = r.scenarios && r.scenarios.mid;
    const lastStage = b.params && b.params.stages && b.params.stages.length ? b.params.stages[b.params.stages.length - 1].name : null;
    const chip = <span style={{ background: "rgba(255,255,255,0.16)", border: "1px solid rgba(255,255,255,0.35)", color: "#fff", fontSize: 13, fontWeight: 700, padding: "4px 14px", borderRadius: 999 }}>{(CASE_PLATFORMS[b.platform] || {}).label || b.platform}</span>;

    // scenarios slide
    slides.push((no, count) => (
      <SlideShell key={"m" + i} pageNo={no} pageCount={count} t={t} title={t("matrixTitle")} chip={chip}>
        <SlideCard style={{ flex: "0 0 330px", display: "flex", flexDirection: "column", gap: 13 }}>
          <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680" }}>{t("assumptions")}</div>
          {[
            industryLabel ? [t("industry"), industryLabel] : null,
            [t("budgetDay"), CE.fmtMoney(b.budgetDaily, cur, lang)],
            [t("runtime"), `${CE.fmtInt(b.runtimeDays, lang)} ${t("days")}`],
            [t("adSpend"), fmtMoneyAuto(r.spend, cur, lang)],
            ["CPC", `${CE.fmtMoney(r.steps.low, cur, lang)} – ${CE.fmtMoney(r.steps.high, cur, lang)}`],
            [t("cvr"), r.scenarios && r.scenarios.worst && r.scenarios.best ? `${fmtPctAuto(r.scenarios.worst.cvr, lang)} – ${fmtPctAuto(r.scenarios.best.cvr, lang)}` : "–"],
            forecast.vertical === "ecom" && forecast.ecom && forecast.ecom.aov ? [t("aov"), CE.fmtMoney(forecast.ecom.aov, cur, lang)] : null,
            forecast.vertical === "ecom" && forecast.ecom && forecast.ecom.margin ? [t("margin"), CE.fmtPct(forecast.ecom.margin, lang, 0)] : null,
          ].filter(Boolean).map(([l, v], j) => (
            <div key={j} style={{ display: "flex", justifyContent: "space-between", gap: 12, fontSize: 15.5, borderBottom: "1px solid #EDF0F7", paddingBottom: 9 }}>
              <span style={{ color: "#5C6680", fontWeight: 600 }}>{l}</span>
              <span style={{ fontWeight: 700, fontVariantNumeric: "tabular-nums" }}>{v}</span>
            </div>
          ))}
          <div style={{ marginTop: "auto", fontSize: 11, color: "#7F89A2", lineHeight: 1.45 }}>{srcLine(b)}</div>
        </SlideCard>
        <SlideCard style={{ flex: 1, display: "flex", flexDirection: "column" }}>
          <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 12 }}>
            <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680", paddingTop: 8 }}>{t("scenarios")}</div>
            {mid && <SlideKpi label={forecast.vertical === "ecom" ? `${t("cpo")} · ${t("scenarioMid")}` : `${t("cpl")} · ${t("scenarioMid")}`} value={CE.fmtMoney(forecast.vertical === "ecom" ? mid.cpo : mid.cpl, cur, lang)} />}
          </div>
          <MatrixTable result={r} vertical={forecast.vertical} currency={cur} lang={lang} mode="export" />
          <div style={{ fontSize: 10.5, color: "#7F89A2", marginTop: "auto", paddingTop: 8 }}>{t("scenarioNote")}</div>
        </SlideCard>
      </SlideShell>
    ));

    // funnel / econ slide
    if (forecast.vertical === "leadgen" && b.params && b.params.stages && b.params.stages.length > 0 && mid) {
      slides.push((no, count) => (
        <SlideShell key={"f" + i} pageNo={no} pageCount={count} t={t} title={`${t("funnelTitle")} ${lastStage}`} chip={chip}>
          <SlideCard style={{ flex: 1, display: "flex", flexDirection: "column" }}>
            <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 12 }}>
              <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680", paddingTop: 8 }}>{t("funnel")}</div>
              <SlideKpi label={`${t("costPer")} ${lastStage} · ${t("scenarioMid")}`} value={fmtMoneyAuto(mid.finalCostPer, cur, lang)} />
            </div>
            <FunnelTable result={r} stages={b.params.stages} currency={cur} lang={lang} mode="export" />
          </SlideCard>
          <SlideCard style={{ flex: "0 0 300px" }}>
            <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680", marginBottom: 12 }}>{t("readGuide")}</div>
            <div style={{ fontSize: 15, lineHeight: 1.65, color: "#3C455F" }}>{t("funnelGuide")}</div>
          </SlideCard>
        </SlideShell>
      ));
    }
    if (forecast.vertical === "ecom" && mid) {
      slides.push((no, count) => (
        <SlideShell key={"e" + i} pageNo={no} pageCount={count} t={t} title={t("econTitle")} chip={chip}>
          <SlideCard style={{ flex: 1, display: "flex", flexDirection: "column" }}>
            <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 12 }}>
              <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680", paddingTop: 8 }}>{t("scenarios")}</div>
              {mid.roas !== null && <SlideKpi label={`${t("roas")} · ${t("scenarioMid")}`} value={CE.fmtNum(mid.roas, lang, 2)} />}
            </div>
            <EcomTable result={r} currency={cur} lang={lang} mode="export" />
          </SlideCard>
          <SlideCard style={{ flex: "0 0 300px" }}>
            <div style={{ fontSize: 13, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.07em", color: "#5C6680", marginBottom: 12 }}>{t("readGuide")}</div>
            <div style={{ fontSize: 15, lineHeight: 1.65, color: "#3C455F" }}>{t("econGuide")}</div>
          </SlideCard>
        </SlideShell>
      ));
    }
  });

  if (forecast.blocks.length > 1) {
    slides.push((no, count) => (
      <SlideShell key="comp" pageNo={no} pageCount={count} t={t} title={t("compTitle")} chip={null}>
        <SlideCard style={{ flex: 1, alignSelf: "flex-start" }}>
          <ComparisonTable blocks={forecast.blocks} results={results} vertical={forecast.vertical} currency={cur} lang={lang} mode="export" />
        </SlideCard>
      </SlideShell>
    ));
  }

  const count = slides.length;
  function doPrint() {
    try { MesperDB.casesLogExport(forecast.id, forecast.clientName); } catch (_) {}
    window.print();
  }

  return ReactDOM.createPortal(
    <div className="case-export-backdrop" style={{ position: "fixed", inset: 0, zIndex: 90, background: "rgba(12,16,32,0.95)", overflowY: "auto", padding: "70px 24px 40px" }}>
      <div className="case-export-toolbar" style={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 5, display: "flex", alignItems: "center", gap: 12, padding: "12px 24px", background: "rgba(12,16,32,0.92)" }}>
        <span style={{ color: "#fff", fontWeight: 700, fontSize: 15, fontFamily: "var(--font-display)" }}>{t("export")} · {projectName}{caseSubline ? ` · ${caseSubline}` : ""}</span>
        <span style={{ color: "rgba(255,255,255,0.6)", fontSize: 12.5 }}>{count} Slides · 16:9</span>
        <div style={{ flex: 1 }} />
        <Button variant="outline" size="sm" onClick={onClose} style={{ background: "transparent", color: "#fff", borderColor: "rgba(255,255,255,0.35)" }}>{t("close")}</Button>
        <Button size="sm" icon="download" onClick={doPrint}>{t("print")}</Button>
      </div>
      <div ref={wrapRef} style={{ maxWidth: 1280, margin: "0 auto" }}>
        {slides.map((make, i) => (
          <div key={i} className="case-slide-scale" style={{ transform: `scale(${scale})`, transformOrigin: "top left", width: 1280, height: 720 * scale + (i < count - 1 ? 24 : 0), marginBottom: 0 }}>
            {make(i + 1, count)}
          </div>
        ))}
      </div>
    </div>,
    document.body
  );
}

/* ---- scoped styles incl. print rules ---- */
function CaseStyles() {
  return (<style dangerouslySetInnerHTML={{ __html: `
.case-row { transition: box-shadow .15s ease, transform .15s ease; }
.case-row:hover { box-shadow: var(--sh-md); }

/* Inputs & selects — visible focus ring (WCAG 2.4.7), on-brand Royal Blue.
   cases-scope wraps both the main screen and the portaled modals. */
.cases-scope input, .cases-scope select, .cases-scope textarea { transition: border-color .14s, box-shadow .14s; }
.cases-scope input:focus, .cases-scope select:focus, .cases-scope textarea:focus {
  border-color: var(--accent) !important; box-shadow: 0 0 0 3px var(--accent-soft); outline: none;
}
/* Custom chevron on native selects so they match the Montserrat UI */
.cases-scope select {
  -webkit-appearance: none; appearance: none; padding-right: 34px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236F7582' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 11px center;
}
/* Editable case title — signals it's a field on hover/focus */
.case-title-input:hover { background: var(--surface-2) !important; }
.case-title-input:focus { background: var(--surface-2) !important; box-shadow: inset 0 -2px 0 var(--accent); }
.case-title-input::placeholder { color: var(--ink-4); font-weight: 700; }

/* Icon buttons — hover + focus feedback, was fully static */
.case-navbtn { transition: background .14s, border-color .14s, color .14s; }
.case-navbtn:hover { background: var(--surface-2); border-color: var(--line-strong); color: var(--ink-2); }
.case-navbtn.danger:hover { background: var(--st-declined-soft); border-color: var(--st-declined-soft); color: var(--st-declined); }
.case-navbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Mobile: 16px inputs stop iOS focus-zoom; taller fields hit 44px touch target */
@media (max-width: 820px) {
  .cases-scope input, .cases-scope select, .cases-scope textarea { font-size: 16px !important; padding-top: 11px !important; padding-bottom: 11px !important; }
}
@media print {
  @page { size: 13.333in 7.5in; margin: 0; }
  body.case-print > #root { display: none !important; }
  body.case-print .case-export-backdrop { position: static !important; background: none !important; padding: 0 !important; overflow: visible !important; }
  body.case-print .case-export-toolbar { display: none !important; }
  body.case-print .case-slide-scale { transform: none !important; width: 1280px !important; height: 720px !important; margin: 0 !important; }
  body.case-print .case-slide { page-break-after: always; break-after: page; }
  body.case-print .case-slide:last-child { page-break-after: auto; break-after: auto; }
}
` }} />);
}

/* ============================================================
   Root
   ============================================================ */
function CasesApp({ user, onHome, path, navigate }) {
  const [lang, setLangState] = useState(() => { try { return localStorage.getItem("mesper.lang") || "de"; } catch (_) { return "de"; } });
  const setLang = (l) => { setLangState(l); try { localStorage.setItem("mesper.lang", l); } catch (_) {} };
  const t = (k) => (CASES_DICT[lang] && CASES_DICT[lang][k]) || CASES_DICT.en[k] || k;
  const narrow = useIsNarrow(820);

  const sub = (path || "").replace(/^\/cases\/?/, "");
  let view = "home", caseId = null, projectId = null;
  if (sub === "" || sub === "home") view = "home";
  else if (sub === "templates") view = "templates";
  else if (sub.startsWith("p/")) { view = "project"; projectId = sub.slice(2); }
  else if (sub.startsWith("c/")) { view = "case"; caseId = sub.slice(2); }

  const navPill = (id, label, to, activeWhen) => {
    const on = activeWhen ? activeWhen.includes(view) : view === id;
    return (
      <button key={id} type="button" onClick={() => navigate(to)} className="vp-focusable"
        style={{ padding: "7px 14px", borderRadius: "var(--r-pill)", border: "none", cursor: "pointer", fontWeight: 600, fontSize: 13, background: on ? "var(--surface)" : "transparent", color: on ? "var(--ink)" : "var(--ink-3)", boxShadow: on ? "var(--sh-sm)" : "none" }}>
        {label}
      </button>
    );
  };

  return (
    <CaseI18n.Provider value={{ lang, t }}>
      <CaseStyles />
      <div className="cases-scope" style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
        <div style={{ borderBottom: "1px solid var(--line)", background: "var(--surface)", flex: "0 0 auto" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 14, padding: narrow ? "11px 16px" : "13px 28px", minWidth: 0 }}>
            <button type="button" onClick={onHome} className="vp-focusable" title="All tools" style={{ display: "flex", alignItems: "center", gap: 9, background: "transparent", border: "none", cursor: "pointer", flex: "0 0 auto" }}>
              <Icon name="arrowLeft" size={18} style={{ color: "var(--ink-4)" }} />
              <BrandLockup height={22} sub="Cases" />
            </button>
            {/* On wide screens the nav pills sit inline; on narrow they drop to their own row. */}
            {!narrow && (
              <div style={{ display: "inline-flex", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-pill)", padding: 3, marginLeft: 6 }}>
                {navPill("home", t("cases"), "/cases", ["home", "project", "case"])}
                {navPill("templates", t("templates"), "/cases/templates")}
              </div>
            )}
            <div style={{ flex: 1, minWidth: 8 }} />
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div className="lang-toggle" style={{ display: "inline-flex", border: "1px solid var(--line-2)", borderRadius: "var(--r-pill)", overflow: "hidden", flex: "0 0 auto" }}>
                {["de", "en"].map((l) => <button key={l} type="button" onClick={() => setLang(l)} style={{ border: "none", background: lang === l ? "var(--accent)" : "transparent", color: lang === l ? "#fff" : "var(--ink-3)", padding: "8px 12px", fontSize: 12.5, fontWeight: 700, cursor: "pointer" }}>{l.toUpperCase()}</button>)}
              </div>
              {typeof NotificationBell === "function" && <NotificationBell navigate={navigate} />}
              <AccountMenu user={user} onLogout={() => MesperDB.signOut()} />
            </div>
          </div>
          {narrow && (
            <div style={{ padding: "0 16px 10px" }}>
              <div style={{ display: "inline-flex", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-pill)", padding: 3 }}>
                {navPill("home", t("cases"), "/cases", ["home", "project", "case"])}
                {navPill("templates", t("templates"), "/cases/templates")}
              </div>
            </div>
          )}
        </div>
        <main style={{ flex: 1, overflowY: "auto", padding: narrow ? "16px 16px 60px" : "24px 28px 60px" }}>
          {view === "home" && <CasesHome user={user} navigate={navigate} />}
          {view === "templates" && <TemplatesView user={user} />}
          {view === "project" && <ProjectView key={projectId} id={projectId} user={user} navigate={navigate} />}
          {view === "case" && <ForecastEditor key={caseId} id={caseId} user={user} navigate={navigate} />}
        </main>
      </div>
    </CaseI18n.Provider>
  );
}

Object.assign(window, { CasesApp });
