import ReactDOM from "react-dom/client";
import React, { useState, useMemo, useRef, useEffect } from "react";
import { Plus, Trash2, Copy, Check, AlertTriangle, Save, FolderOpen, ChevronUp, ChevronDown, Settings, Calculator, Search, RotateCcw, Download, Upload, Bookmark, GitCompare } from "lucide-react";
import * as XLSX from "xlsx";

const TEF_BLUE = "#0019A5", TEF_BLUE_2 = "#0066FF", TEF_CYAN = "#00A9E0", TEF_GRAY = "#333333", TEF_RED = "#c0392b", TEF_BG = "#F4F6FB";
const lbl = "block text-xs font-semibold mb-1";
const ctl = "w-full px-2 py-2 text-sm rounded border border-gray-300 bg-white focus:outline-none";

function Field({ label, children, className = "" }) {
  return <div className={className}>{label && <label className={lbl} style={{ color: TEF_BLUE }}>{label}</label>}{children}</div>;
}

const D_CU = ["Asymmetrische Kupfer-Anbindung inkl. Business-Router", "Bis zu 5 feste IPv4 Adressen kostenfrei inklusive"];
const D_UGG = ["Asymmetrische UGG Glasfaser-Anbindung inkl. Business-Router", "Bis zu 5 feste IPv4 Adressen kostenfrei inklusive"];
const D_FTTH = ["Asymmetrische Telekom Glasfaser-Anbindung inkl. Business-Router", "Bis zu 5 feste IPv4 Adressen kostenfrei inklusive"];
const D_LINE = ["Hochverfügbare, symmetrische Glasfaser-Anbindung inkl. Business-Router", "Dedizierte Anbindung, kein shared Medium", "Bis zu 5 feste IPv4 Adressen kostenfrei inklusive, mehr auf Wunsch ebenfalls kostenfrei"];
const D_BACKUP = ["Automatisiertes Mobilfunk-Backup innerhalb unseres Business Routers", "Übernahme Ihrer IP-Adressen auf die Backup-Anbindung", "Übernahme von bis zu 16 Sprachkanälen auf die Backup-Anbindung", "Automatischer Reconnect über Originär-Anbindung nach Entstörung", "Datenvolumen unbegrenzt"];
const D_EXPRESS = ["Schnellstmögliche Aktivierung Ihrer Anbindung über eine Mobilfunk-Strecke", "Schnellstmögliche Mitteilung und Schaltung Ihrer IP-Adressen"];
const D_MOBILE = ["Mobilfunk-Anbindung für Ihren Standort", "inkl. bis zu 5 festen IPv4 Adressen", "inkl. unbegrenztem Datenvolumen", "inkl. Business-Hardware"];
const D_WAVE = ["Bereitstellung und Installation des Anschlusses und des Endgerätes (Business-Router)", "Business Light: Business-Hotline Mo–Fr 8–18 Uhr, Entstörung innerhalb von 24 Stunden"];
const WAVE_LOS_PRICE = 999;
const WAVE_SUPPORT_PRICE = 150;
const D_BASIS = ["Zugang zur virtuellen Telefonanlage inkl. 5 Lizenzen", "Flatrate in alle dt. Mobilfunk- & Festnetze", "Zugang zum Cloudya-Client via Web, Smartphone, Tablet oder als installierte Applikation"];
const D_SIP = ["Cloud Sprachkanäle powered by NFON", "Erreichbar über jeden Internet-Zugang", "Inkl. Flatrate in alle dt. Mobilfunk- & Festnetze", "Preise für internationale Gespräche: https://www.o2business.de/content/dam/b2bchannels/de/pdfs-o2-business/teams-telefonie-preisliste.pdf"];
const D_MDM = ["Umfassender Schutz für Ihre mobilen Endgeräte", "Management per Remote im Browser", "DSGVO-Konform"];
const D_MDM_MANAGED = [...D_MDM, "inkl. Management durch Telefónica"];

const LZ = [24, 36, 48, 60], DP_LZ = [36, 48, 60];
const BACKUP_PRICE = { vdsl: { m24: 31, m36: 25 }, line: { m24: 48, m36: 41 } };
const EXPRESS_PRICE = { vdsl: 199, line: 399 };
const backupMonthly = (kind, term) => { const b = BACKUP_PRICE[kind]; return b ? (term >= 36 ? b.m36 : b.m24) : 0; };

const KANAL_STEPS = [0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 150, 180, 210, 240, 270, 300, 350, 400, 450, 500, 550, 600];
const RICHTUNGEN = [
  { key: "fest", label: "dt. Festnetz", fair: 2, poolCt: 0.7, fairText: "Flatrate in Richtung dt. Festnetz" },
  { key: "mob", label: "dt. Mobilfunk", fair: 6, poolCt: 4.5, fairText: "Flatrate in Richtung dt. Mobilfunk" },
  { key: "eu", label: "EU Plus", fair: 3.5, poolCt: 2.25, fairText: "500 Minuten je Sprachkanal in die EU Plus Länder inklusive" },
  { key: "world", label: "World Select", fair: 11, poolCt: 8, fairText: "250 Minuten je Sprachkanal in die World Select Länder inklusive" },
];
const blankKanalTarife = () => ({ fest: { mode: "standard", min: 0 }, mob: { mode: "standard", min: 0 }, eu: { mode: "standard", min: 0 }, world: { mode: "standard", min: 0 } });

const MDM_STAFFEL = [{ min: 100, pct: 25 }, { min: 50, pct: 20 }, { min: 25, pct: 15 }, { min: 10, pct: 10 }, { min: 1, pct: 5 }];
const staffelPct = (staffel, qty) => { for (const t of staffel || []) if (qty >= t.min) return t.pct; return 0; };

const officePrice = (p, managed) => managed && p.mgdPrice != null ? p.mgdPrice : p.loPrice;
const officeMax = (p, managed) => managed && p.mgdPrice != null ? p.mgdMax : p.loMax;
const officeNoMgmt = (p, managed) => !!managed && p.mgdPrice == null;

const DP_TABLE_KOMBI = [
  { pct: 20, min: 2628 }, { pct: 21, min: 3132 }, { pct: 22, min: 4140 }, { pct: 23, min: 5148 }, { pct: 24, min: 6156 },
  { pct: 25, min: 7164 }, { pct: 26, min: 8172 }, { pct: 27, min: 9180 }, { pct: 28, min: 10188 }, { pct: 29, min: 11196 },
  { pct: 30, min: 12204 }, { pct: 31, min: 13212 }, { pct: 32, min: 14220 }, { pct: 33, min: 15228 }, { pct: 34, min: 16236 },
  { pct: 35, min: 17244 }, { pct: 36, min: 18252 }, { pct: 37, min: 19260 }, { pct: 38, min: 20268 }, { pct: 39, min: 21276 },
  { pct: 40, min: 22284 }, { pct: 41, min: 22788 }, { pct: 42, min: 23796 }, { pct: 43, min: 24804 }, { pct: 44, min: 25812 },
  { pct: 45, min: 26820 }, { pct: 46, min: 27828 }, { pct: 47, min: 28836 }, { pct: 48, min: 29844 },
];
const DP_TABLE_SOLO = [
  { pct: 15, min: 2628 }, { pct: 16, min: 3132 }, { pct: 17, min: 4140 }, { pct: 18, min: 5148 }, { pct: 19, min: 6156 },
  { pct: 20, min: 7164 }, { pct: 21, min: 8172 }, { pct: 22, min: 9180 }, { pct: 23, min: 10188 }, { pct: 24, min: 11196 },
  { pct: 25, min: 12204 }, { pct: 26, min: 13212 }, { pct: 27, min: 14220 }, { pct: 28, min: 15228 }, { pct: 29, min: 16236 },
  { pct: 30, min: 17244 }, { pct: 31, min: 18252 }, { pct: 32, min: 19260 }, { pct: 33, min: 20268 }, { pct: 34, min: 21276 },
  { pct: 35, min: 22284 }, { pct: 36, min: 22788 }, { pct: 37, min: 23796 }, { pct: 38, min: 24804 }, { pct: 39, min: 25812 },
  { pct: 40, min: 26820 }, { pct: 41, min: 27828 }, { pct: 42, min: 28836 }, { pct: 43, min: 29844 }, { pct: 44, min: 30852 },
  { pct: 45, min: 31860 }, { pct: 46, min: 32868 }, { pct: 47, min: 33876 }, { pct: 48, min: 34884 },
];
const dpMaxPct = (umsatz, kombi) => { const t = kombi ? DP_TABLE_KOMBI : DP_TABLE_SOLO; let p = t[0].pct; for (const r of t) if (umsatz >= r.min) p = r.pct; return p; };

const DB_ALLIP = { label: "All IP Business", products: [
  { inetFlat: true, id: "cu-6", maxKanal: 2, name: "Asymmetrisch bis 6 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 6/1 Mbit/s"], laufzeiten: LZ, m24: 45, m36: 40, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-16", maxKanal: 6, name: "Asymmetrisch bis 16 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 16/2,8 Mbit/s"], laufzeiten: LZ, m24: 45, m36: 40, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-25", maxKanal: 20, name: "Asymmetrisch bis 25 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 25/5 Mbit/s"], laufzeiten: LZ, m24: 45, m36: 40, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-50", maxKanal: 30, name: "Asymmetrisch bis 50 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 50/10 Mbit/s"], laufzeiten: LZ, m24: 45, m36: 40, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-100", maxKanal: 60, name: "Asymmetrisch bis 100 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 100/40 Mbit/s"], laufzeiten: LZ, m24: 45, m36: 40, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-175", maxKanal: 60, name: "Asymmetrisch bis 175 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 175/40 Mbit/s"], laufzeiten: LZ, m24: 65, m36: 60, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "cu-250", maxKanal: 60, name: "Asymmetrisch bis 250 Mbit/s", desc: ["Asymmetrische Kupfer-Anbindung bis 250/40 Mbit/s"], laufzeiten: LZ, m24: 65, m36: 60, o24: 99, o36: 0, standort: true, extrasKind: "vdsl" },
  { inetFlat: true, id: "ugg-100", maxKanal: 8, name: "Asymmetrisch bis 100 Mbit/s (UGG)", desc: ["Asymmetrische UGG Glasfaser-Anbindung bis 100/40 Mbit/s"], laufzeiten: LZ, m24: 60, m36: 50, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ugg-250", maxKanal: 8, name: "Asymmetrisch bis 250 Mbit/s (UGG)", desc: ["Asymmetrische UGG Glasfaser-Anbindung bis 250/125 Mbit/s"], laufzeiten: LZ, m24: 80, m36: 70, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ugg-500", maxKanal: 8, name: "Asymmetrisch bis 500 Mbit/s (UGG)", desc: ["Asymmetrische UGG Glasfaser-Anbindung bis 500/250 Mbit/s"], laufzeiten: LZ, m24: 140, m36: 120, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ugg-1000", maxKanal: 8, name: "Asymmetrisch bis 1.000 Mbit/s (UGG)", desc: ["Asymmetrische UGG Glasfaser-Anbindung bis 1.000/500 Mbit/s"], laufzeiten: LZ, m24: 160, m36: 140, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ftth-150", maxKanal: 8, name: "Asymmetrisch bis 150 Mbit/s FTTH", desc: ["Asymmetrische Telekom Glasfaser-Anbindung bis 150/75 Mbit/s"], laufzeiten: LZ, m24: 65, m36: 60, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ftth-300", maxKanal: 8, name: "Asymmetrisch bis 300 Mbit/s FTTH", desc: ["Asymmetrische Telekom Glasfaser-Anbindung bis 300/150 Mbit/s"], laufzeiten: LZ, m24: 75, m36: 70, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ftth-600", maxKanal: 8, name: "Asymmetrisch bis 600 Mbit/s FTTH", desc: ["Asymmetrische Telekom Glasfaser-Anbindung bis 600/300 Mbit/s"], laufzeiten: LZ, m24: 105, m36: 100, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "ftth-1000", maxKanal: 8, name: "Asymmetrisch bis 1.000 Mbit/s FTTH", desc: ["Asymmetrische Telekom Glasfaser-Anbindung bis 1.000/500 Mbit/s"], laufzeiten: LZ, m24: 115, m36: 110, o24: 99, o36: 0, standort: true },
  { inetFlat: true, id: "line-2", maxKanal: 16, name: "Line 2 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 2/2 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-4", maxKanal: 30, name: "Line 4 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 4/4 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-10", maxKanal: 90, name: "Line 10 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 10/10 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-20", maxKanal: 180, name: "Line 20 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 20/20 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-50", maxKanal: 450, name: "Line 50 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 50/50 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-100", maxKanal: 600, name: "Line 100 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 100/100 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-200", maxKanal: 600, name: "Line 200 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 200/200 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-500", maxKanal: 600, name: "Line 500 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 500/500 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { inetFlat: true, id: "line-1000", maxKanal: 600, name: "Line 1.000 Mbit/s", desc: ["Hochverfügbare, symmetrische Glasfaser-Anbindung bis 1.000/1.000 Mbit/s", "98,5% Verfügbarkeit", "8 Stunden Entstörfrist"], laufzeiten: LZ, priceOpen: true, standort: true, extrasKind: "line" },
  { id: "mobile-main", maxKanal: 16, name: "Mobile Main", desc: ["Mobilfunk-Anbindung für Ihren Standort", "inkl. unbegrenztem Datenvolumen"], laufzeiten: LZ, standort: true, mobile: true },
  { inetFlat: true, id: "wave5000-100", name: "Wave 5.000 bis 100 Mbit/s", desc: ["Richtfunkanbindung bis 100/100 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 950, o36: 9000, standort: true, wave: true },
  { inetFlat: true, id: "wave5000-250", name: "Wave 5.000 bis 250 Mbit/s", desc: ["Richtfunkanbindung bis 250/250 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1050, o36: 9000, standort: true, wave: true },
  { inetFlat: true, id: "wave5000-500", name: "Wave 5.000 bis 500 Mbit/s", desc: ["Richtfunkanbindung bis 500/500 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1150, o36: 9000, standort: true, wave: true },
  { inetFlat: true, id: "wave5000-1000", name: "Wave 5.000 bis 1 Gbit/s", desc: ["Richtfunkanbindung bis 1.000/1.000 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1250, o36: 9000, standort: true, wave: true },
  { inetFlat: true, id: "wave15000-100", name: "Wave 15.000 bis 100 Mbit/s", desc: ["Richtfunkanbindung bis 100/100 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1150, o36: 13000, standort: true, wave: true },
  { inetFlat: true, id: "wave15000-250", name: "Wave 15.000 bis 250 Mbit/s", desc: ["Richtfunkanbindung bis 250/250 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1250, o36: 13000, standort: true, wave: true },
  { inetFlat: true, id: "wave15000-500", name: "Wave 15.000 bis 500 Mbit/s", desc: ["Richtfunkanbindung bis 500/500 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1350, o36: 13000, standort: true, wave: true },
  { inetFlat: true, id: "wave15000-1000", name: "Wave 15.000 bis 1 Gbit/s", desc: ["Richtfunkanbindung bis 1.000/1.000 Mbit/s", "98,5% Verfügbarkeit", "24 Stunden Entstörfrist (optional 8 Stunden gegen Aufpreis)"], laufzeiten: [36], m36: 1450, o36: 13000, standort: true, wave: true },
]};

const BASIC_ROUTER = { dsl: { name: "AVM FRITZ!Box 7590 AX", price: 0 }, cable: { name: "AVM FRITZ!Box 6690", price: 5 }, fiber: { name: "AVM FRITZ!Box 5530 Fiber", price: 0 } };
const euPlusPrice = (term) => (Number(term) >= 36 ? 3 : 3.5);
const worldSelectPrice = (term) => (Number(term) >= 36 ? 10 : 11);
// ===== DTAG Leased Line: Region-Zuordnung (ONKZ 5-stellig) + Preislogik =====
// Kompakt eingebettet: nur Metro + Regio gelistet; alles andere (inkl. nicht gefundener ONKZ) = Country.
const _LL_METRO = "2010020200203002041020510209002102021100212002131021510216102181021910221002280022910230202306023100234002351023610237102381024100242102431024410246102471025100252102541025510256102610026710268102691027100275102831028610291002951029610297102981029910300003301033060332053321033310334403346033500336603371033762337703381033850339403410034210342303437034500346603473035040351003535035370354103544035460355003571035760358803594035960360503610036340364703663036790373503737038100383603839238470387103874038760388103883038860390103904039070390903910039210392303933039350393703941039490397103973039760398403987039940399603998040000415104171041810421004241042510427104281043100432104381044100443104441044510447104491045100452104541046410466104671047100472104751047610477104851048610487104881049100493604971050210505105071051100512105181051910521005221052510531005341053710541005421054310544105461054710548105510055210557105610056210563105641056610567105681056910571005721057610577105821058310584105851059410595105961060210603106041060610611006151061610618106204062100627106281063100635106361063810639106410064510651006531065510656106571065910661006621066310664106661066810673106741067510676106810068510686106871069000704107110071210713107134071410715107158071610719107210072610730307310073810739107410074410745107461075100755107561075710758107610076410767107681077100772107741077610777107831078410785107941079610797108021080410807108081080910810208210082410825108261082710828108291083210836108381084100842108431084410845008461085410855108561085710858108610086210865108681087210874108751087610877108781088070881008856089000907109091091010911009131091410916109171092100924109261093100932109341093510937109391094100946109491095100952109531095510961009631096410965109671097100974109761097710983109843098510992109931099410";
const _LL_REGIO = "20430204502052020530205402056020580206402065020660208002103021040212902132021330213702140021500215202162021630216602171021730217402175021820219202195021960220202203022040220502206022070220802222022230222402225022260222702228022320223302234022350223602237022380224102242022440224602248022510225402261022620226302266022670227102272022730230102303023040230502307023090232302324023250232702330023310233202333023340233502336023390235202353023550236202363023650236602367023680237202373023740237802389023910239202401024020240302404024050240602407024080240902423024290245102454024560246402465025010258102591025920259602602026300263102641026420265102661027210274102761027710281002821028410284202845028710292102931029410330203303033056331003320133203332203328033290333403338033394333973341033420334393361033620336383370133780337903391034000342023420334204342053420634207342413425034291342923429334294342953429734298342993431034347343503443034444344503447034602346103463834640347103475034910349303496035010350253502635027350553520035201352033520435205352063520735208352093521035230352433525035280352903573035780358103583035850359103601036200362013620236203362043620636208362093621036258362803631036371364103643036450364523645836500366103671036750367703681036830368503691036930369503710037220372303731037330374103744037500377103820138202382033820438205382063820738208382093822438231382923829338295383103834038410384303844038453384543845938466385003925039310394303946039500398103991041010410204103041040410504106041070410804109041210412204131041410415204154041610416204168041740417604177041790419104193042020420304206042070420804209042210422204223042240423104242042610429204293042940429604298043020430304305043070430804322043260432904330043310433404340043420434304344043460434704348043490435104357043840438504392043940440404406044210448804526045310453204535045510461004621046510473104791047920479504810048210484104921049310494104961050310503505045050660510105102051030510505108051090513005131051320513605137051380513905141051510516105171051740520105202052030520405205052060520705208052090522205223052240522505231052320523705241052420524405245052460524705261052710530005301053020530305304053050530605307053080530905321053310533305337053440535105353053610536205373053740538105424054250542805451055310554105551055610565105731057320574105751058100591005921059310597106007060230602706034060390604706051060710607406078060810610106102061030610406105061060610706108061090612206123061240612606127061280612906130061310613206134061350613606139061420614406145061460615006152061540615506157061620617106172061730617406175061820618406187061880619006192061950619606198062010620206203062050620606220062210622206224062270623106232062330623406235062360623706238062390624106245062510625206253062560625706258062610632106324063310633206340063410634806359063710642106431064410646106471066910671006721067230678106802068030680406805068060680906821068240682506826068270683106834068360683806841068420684306844068480684906881068930689406897068980702107022070240703107033070340705107071070820708307123071270714207144071450714607150071520715307154071560715707159071620717107181071950720207203072040722107222072240722507231072320723607240072420724307244072450724607247072480724907251072520725307254072550725707271072720727307275072770730207304073050730607307073080730907321073310733607340073440734507346073480735107361073940743107471075310754107602076210763107633076340763607642076450766007661076620766307664076650766607667076680766907683076840768507731078100782107910079310795108031080510810408105081060811008121081220812308131081330814108142081510815308161081650817008171081780819108202082030820508207082080822108226082300823108233082340823608237082380825208293082940831008331083410840408405084060840708424084260844208443084460845208453084540845608457084580845908466085100853108631086710871008731088210886109060091030912009122091230912609127091280912909132091340915109170091810918709191092010920209203092040920509206092080920909221092270922809231092460927009271092730927509276092770927809279092810930209303093050930609307093240933109333093340933709344093490936009364093650936609367093690938609398094010940209403094040940509406094070940809409094210943109441094530947309498095610957109621096610972109810098410987609910099710";
const _chunk5 = (s) => { const set = new Set(); for (let i = 0; i < s.length; i += 5) set.add(s.slice(i, i + 5)); return set; };
const METRO_SET = _chunk5(_LL_METRO), REGIO_SET = _chunk5(_LL_REGIO);
const onkzNorm = (onkz) => { const s = String(onkz || "").replace(/\D/g, "").replace(/^0+/, ""); return s ? (s + "00000").slice(0, 5) : ""; };
const onkzRegion = (onkz) => { const k = onkzNorm(onkz); if (k && METRO_SET.has(k)) return "Metro"; if (k && REGIO_SET.has(k)) return "Regio"; return "Country"; };
const LINE_PRICE = {
  Metro:   { bis20: { m24: 360, m36: 320 }, "50": { m24: 460, m36: 390 }, "100": { m24: 480, m36: 415 }, "200": { m24: 550, m36: 490 }, "500": { m24: 750, m36: 690 }, "1g": { m24: 890, m36: 800 } },
  Regio:   { bis20: { m24: 420, m36: 390 }, "50": { m24: 580, m36: 540 }, "100": { m24: 630, m36: 550 }, "200": { m24: 700, m36: 610 }, "500": { m24: 850, m36: 830 }, "1g": { m24: 990, m36: 950 } },
  Country: { bis20: { m24: 470, m36: 440 }, "50": { m24: 680, m36: 610 }, "100": { m24: 710, m36: 620 }, "200": { m24: 760, m36: 690 }, "500": { m24: 960, m36: 890 }, "1g": { m24: 1060, m36: 1020 } },
};
const LINE_BW = { "line-2": "bis20", "line-4": "bis20", "line-10": "bis20", "line-20": "bis20", "line-50": "50", "line-100": "100", "line-200": "200", "line-500": "500", "line-1000": "1g" };
const lineDtagBase = (p, r) => { const region = onkzRegion(r.onkz); const bw = LINE_BW[p.id] || "bis20"; const tier = Number(r.term) >= 36 ? "m36" : "m24"; const cell = (LINE_PRICE[region] || {})[bw]; const monthly = cell ? cell[tier] : 0; const oneoff = Number(r.term) >= 36 ? 0 : 99; return { monthly, oneoff, region }; };

// ===== Mobile Main: Basis + Geschwindigkeits-Aufpreis + Internet-Flat =====
const MOBILE_BASE = { m24: 46, o24: 99, m36: 42, o36: 0 };
const MOBILE_FLAT = { m24: 11, m36: 10 };
const INET_FLAT = { m24: 11, m36: 10 }; // Internet-Flatrate für All IP Business & Wave – separat bepreist, NICHT rabattiert
const MOBILE_SPEEDS = [
  { key: "25", label: "bis 25 Mbit/s", m24: 44, m36: 37 },
  { key: "300", label: "bis 300 Mbit/s", m24: 80, m36: 71 },
];
const mobileMainBase = (r) => {
  const t36 = Number(r.term) >= 36;
  const speed = MOBILE_SPEEDS.find(s => s.key === r.mobileSpeed) || MOBILE_SPEEDS[0];
  const speedM = (t36 ? speed.m36 : speed.m24) ?? 0;
  const baseM = t36 ? MOBILE_BASE.m36 : MOBILE_BASE.m24;
  const baseO = t36 ? MOBILE_BASE.o36 : MOBILE_BASE.o24;
  const flatM = t36 ? MOBILE_FLAT.m36 : MOBILE_FLAT.m24;
  return { baseM, baseO, speedM, flatM, speed, speedUnpriced: (t36 ? speed.m36 : speed.m24) == null, monthly: baseM + speedM + flatM, oneoff: baseO };
};

// ===== Antenne (für Mobile Main & Mobilfunk-Backup) =====
const ANTENNEN = [
  { key: "outdoor-blitz", label: "Outdoor mit 15 m Kabel und Blitzableiter (Bundle)", oneoff: 399 },
  { key: "outdoor-15", label: "Outdoor mit 15 m Kabel Mobile Main / Mobile Backup", oneoff: 209 },
  { key: "indoor-10", label: "Indoor mit 10 m Kabel", oneoff: 99 },
  { key: "indoor-5", label: "Indoor mit 5 m Kabel", oneoff: 89 },
];
const antennaFor = (key) => ANTENNEN.find(a => a.key === key) || null;

// ===== Editierbare Options- & Tarif-Texte (über Bearbeiten-Reiter anpassbar) =====
// Reihenfolge bestimmt die Anzeige im Editor. type "text" = einzeilig, "lines" = mehrzeilig (Array).
const OPT_SCHEMA = [
  { key: "backup", label: "Mobilfunk-Backup – Beschreibung", type: "lines" },
  { key: "backup_kanal_16", label: "Mobilfunk-Backup – Sprachkanäle (> 16)", type: "text" },
  { key: "backup_kanal_all", label: "Mobilfunk-Backup – Sprachkanäle (≤ 16)", type: "text" },
  { key: "express", label: "Express-Bereitstellung – Beschreibung", type: "lines" },
  { key: "euplus", label: "Fair Use EU Plus – Beschreibung", type: "text" },
  { key: "worldselect", label: "Fair Use World Select – Beschreibung", type: "text" },
  { key: "kanal_fest", label: "Sprachkanal Fair Use – dt. Festnetz", type: "text" },
  { key: "kanal_mob", label: "Sprachkanal Fair Use – dt. Mobilfunk", type: "text" },
  { key: "kanal_eu", label: "Sprachkanal Fair Use – EU Plus", type: "text" },
  { key: "kanal_world", label: "Sprachkanal Fair Use – World Select", type: "text" },
  { key: "teams", label: "Microsoft Teams-Kopplung – Beschreibung", type: "text" },
  { key: "wave_support", label: "Wave – Business Class Support", type: "text" },
  { key: "wave_los_name", label: "Wave – Line-of-Sight-Test (Name)", type: "text" },
  { key: "wave_los_desc", label: "Wave – Line-of-Sight-Test (Beschreibung)", type: "text" },
  { key: "ant_outdoor-blitz", label: "Antenne – Outdoor 15 m + Blitzableiter", type: "text" },
  { key: "ant_outdoor-15", label: "Antenne – Outdoor 15 m", type: "text" },
  { key: "ant_indoor-10", label: "Antenne – Indoor 10 m", type: "text" },
  { key: "ant_indoor-5", label: "Antenne – Indoor 5 m", type: "text" },
  { key: "basic_kanal_incl", label: "All-IP Basic – Sprachkanäle inklusive (2 fest)", type: "text" },
  { key: "basic_kanal_1", label: "All-IP Basic – 1 Kanal (ohne 2. Sprachkanal)", type: "text" },
  { key: "basic_kanal_2", label: "All-IP Basic – 2. Sprachkanal aktiv", type: "text" },
  { key: "basic_router_dsl_1", label: "All IP Basic – Router DSL Option 1 (Name)", type: "text" },
  { key: "basic_router_dsl_2", label: "All IP Basic – Router DSL Option 2 (Name)", type: "text" },
  { key: "basic_router_cable_1", label: "All IP Basic – Router Cable Option 1 (Name)", type: "text" },
  { key: "basic_router_cable_2", label: "All IP Basic – Router Cable Option 2 (Name)", type: "text" },
  { key: "basic_router_fiber_1", label: "All IP Basic – Router Fiber Option 1 (Name)", type: "text" },
  { key: "basic_router_fiber_2", label: "All IP Basic – Router Fiber Option 2 (Name)", type: "text" },
  { key: "basic_router_fiber_3", label: "All IP Basic – Router Fiber Option 3 (Name)", type: "text" },
];
const OPT_DEFAULTS = {
  backup: [...D_BACKUP],
  backup_kanal_16: "Automatisierte Übernahme von 16 Sprachkanälen auf das Mobilfunk-Backup",
  backup_kanal_all: "Automatisierte Übernahme Ihrer Sprachkanäle auf das Mobilfunk-Backup",
  express: [...D_EXPRESS],
  euplus: "Fair Use EU Plus – 500 Minuten in die EU Plus Länder inklusive",
  worldselect: "Fair Use World Select – 250 Minuten in die World Select Länder inklusive",
  kanal_fest: RICHTUNGEN[0].fairText,
  kanal_mob: RICHTUNGEN[1].fairText,
  kanal_eu: RICHTUNGEN[2].fairText,
  kanal_world: RICHTUNGEN[3].fairText,
  teams: "inkl. Microsoft Teams-Integration",
  wave_support: "Upgrade zu Business Class 8 Stunden Support",
  wave_los_name: "„Line of Sight“ Test",
  wave_los_desc: "Einmaliges Einrichtungsentgelt – zwingend vor Inbetriebnahme erforderlich",
  "ant_outdoor-blitz": ANTENNEN[0].label,
  "ant_outdoor-15": ANTENNEN[1].label,
  "ant_indoor-10": ANTENNEN[2].label,
  "ant_indoor-5": ANTENNEN[3].label,
  basic_kanal_incl: "2 Leitungen / 2–10 Rufnummern inklusive",
  basic_kanal_1: "1 Leitung / 1 Rufnummer inklusive",
  basic_kanal_2: "2 Leitungen / 2–10 Rufnummern",
  basic_router_dsl_1: "FRITZ!Box 7690",
  basic_router_dsl_2: "FRITZ!Box 5690 Pro (DSL & Fiber)",
  basic_router_cable_1: "FRITZ!Box 6670",
  basic_router_cable_2: "FRITZ!Box 6690",
  basic_router_fiber_1: "FRITZ!Box 5530",
  basic_router_fiber_2: "FRITZ!Box 5690",
  basic_router_fiber_3: "FRITZ!Box 5690 Pro (DSL & Fiber)",
};
// Router-Auswahl je Anschlussart (All IP Basic) – höchstens einer wählbar, oder keiner (dann eigene Hardware des Kunden)
const BASIC_ROUTER_OPTIONS = { dsl: ["1", "2"], cable: ["1", "2"], fiber: ["1", "2", "3"] };
const cloneVal = (v) => Array.isArray(v) ? [...v] : v;
let OPT = Object.fromEntries(Object.entries(OPT_DEFAULTS).map(([k, v]) => [k, cloneVal(v)]));
const optText = (key) => OPT[key] != null ? OPT[key] : OPT_DEFAULTS[key];
// Editierbare Preise für All-IP-Basic-Optionen (analog zum OPT-Textsystem)
const OPTPRICE_DEFAULTS = {
  basic_kanal2: 3,
  basic_euplus_24: 3.5, basic_euplus_36: 3,
  basic_worldselect_24: 11, basic_worldselect_36: 10,
  basic_router_dsl_1: 3.5, basic_router_dsl_2: 5.0,
  basic_router_cable_1: 3.5, basic_router_cable_2: 5.0,
  basic_router_fiber_1: 0, basic_router_fiber_2: 3.5, basic_router_fiber_3: 5.0,
  sdwan_remote_setup: 75, sdwan_install_per_device: 100, sdwan_ad_oneoff: 790,
  sdwan_project_tier1: 1000, sdwan_project_tier2: 500,
  vpn_internet_o: 229, vpn_citrix_o: 229, vpn_netflow_o: 149, vpn_mpls: 1,
  vpn_antenne_in6: 219, vpn_antenne_in15: 329, vpn_antenne_outmast: 379, vpn_antenne_in25: 39, vpn_antenne_in5: 59,
};
let OPTPRICE = { ...OPTPRICE_DEFAULTS };
const optPrice = (key) => { const v = OPTPRICE[key]; return v != null && v !== "" ? Number(v) : (OPTPRICE_DEFAULTS[key] || 0); };
// Editierbare laufzeitgestaffelte Tabellen (T5/T3-Form { term: betrag }) – Override je Laufzeit-Zelle möglich
const OPTTABLE_DEFAULTS = {
  sdwan_red_fortinet_sdwan: { 12: -50, 24: -47.5, 36: -45, 48: -42.5, 60: -40 },
  sdwan_red_fortinet_atp: { 12: -80, 24: -76, 36: -72, 48: -68, 60: -64 },
  sdwan_red_fortinet_utp: { 12: -90, 24: -85.5, 36: -81, 48: -76.5, 60: -72 },
  sdwan_red_meraki_sdwan: { 12: -60, 24: -57, 36: -54, 48: -51, 60: -48 },
  sdwan_red_meraki_atp: { 12: -90, 24: -85.5, 36: -81, 48: -76.5, 60: -72 },
  sdwan_red_meraki_utp: { 12: -120, 24: -114, 36: -108, 48: -102, 60: -96 },
  sdwan_einr_router: { 12: 600, 24: 300, 36: 150, 48: 75, 60: 0 },
  sdwan_einr_apsw: { 12: 200, 24: 100, 36: 50, 48: 25, 60: 0 },
  vpn_asym_low: { 12: 39.90, 24: 27.90, 36: 29.90 },
  vpn_asym_high: { 12: 54.90, 24: 47.90, 36: 44.90 },
  vpn_asym_oneoff: { 12: 199, 24: 149, 36: 99 },
  vpn_mobile_m: { 12: 34.90, 24: 32.90, 36: 29.90 },
  vpn_mobile_oneoff: { 12: 199, 24: 149, 36: 99 },
  vpn_ipsec_oneoff: { 12: 199, 24: 149, 36: 99 },
  vpn_link_oneoff: { 12: 199, 24: 99, 36: 0 },
  vpn_svc_low: { 12: 40, 24: 35, 36: 30 },
  vpn_svc_high: { 12: 60, 24: 55, 36: 50 },
  vpn_svc_backup: { 12: 7, 24: 6, 36: 5 },
  vpn_datapack_1g: { 12: 7, 24: 6, 36: 5 },
  vpn_datapack_5g: { 12: 40, 24: 30, 36: 20 },
  vpn_datapack_10g: { 12: 50, 24: 40, 36: 30 },
  vpn_datapack_25g: { 12: 60, 24: 50, 36: 40 },
  vpn_datapack_flat: { 12: 70, 24: 60, 36: 50 },
  vpn_qos_low: { 12: 12, 24: 11, 36: 10 },
  vpn_qos_mid: { 12: 120, 24: 110, 36: 100 },
  vpn_qos_high: { 12: 240, 24: 220, 36: 200 },
  vpn_internet_m: { 12: 54.90, 24: 49.41, 36: 43.92 },
  vpn_link_metro_2: { 12: 400, 24: 360, 36: 320 }, vpn_link_metro_4: { 12: 400, 24: 360, 36: 320 }, vpn_link_metro_10: { 12: 400, 24: 360, 36: 320 }, vpn_link_metro_20: { 12: 400, 24: 360, 36: 320 }, vpn_link_metro_50: { 12: 510, 24: 460, 36: 390 }, vpn_link_metro_100: { 12: 540, 24: 480, 36: 415 }, vpn_link_metro_200: { 12: 590, 24: 550, 36: 490 }, vpn_link_metro_500: { 12: 780, 24: 750, 36: 690 }, vpn_link_metro_1000: { 12: 940, 24: 890, 36: 800 },
  vpn_link_regio_2: { 12: 460, 24: 420, 36: 390 }, vpn_link_regio_4: { 12: 460, 24: 420, 36: 390 }, vpn_link_regio_10: { 12: 460, 24: 420, 36: 390 }, vpn_link_regio_20: { 12: 460, 24: 420, 36: 390 }, vpn_link_regio_50: { 12: 640, 24: 580, 36: 540 }, vpn_link_regio_100: { 12: 660, 24: 630, 36: 550 }, vpn_link_regio_200: { 12: 750, 24: 700, 36: 610 }, vpn_link_regio_500: { 12: 870, 24: 850, 36: 830 }, vpn_link_regio_1000: { 12: 1000, 24: 990, 36: 950 },
  vpn_link_country_2: { 12: 510, 24: 470, 36: 440 }, vpn_link_country_4: { 12: 510, 24: 470, 36: 440 }, vpn_link_country_10: { 12: 510, 24: 470, 36: 440 }, vpn_link_country_20: { 12: 510, 24: 470, 36: 440 }, vpn_link_country_50: { 12: 700, 24: 680, 36: 610 }, vpn_link_country_100: { 12: 730, 24: 710, 36: 620 }, vpn_link_country_200: { 12: 800, 24: 760, 36: 690 }, vpn_link_country_500: { 12: 1030, 24: 960, 36: 890 }, vpn_link_country_1000: { 12: 1070, 24: 1060, 36: 1020 },
};
let OPTTABLE = Object.fromEntries(Object.entries(OPTTABLE_DEFAULTS).map(([k, v]) => [k, { ...v }]));
const optTable = (key) => { const base = OPTTABLE_DEFAULTS[key] || {}; const ov = OPTTABLE[key] || {}; return Object.fromEntries(Object.keys(base).map(t => [t, (ov[t] != null && ov[t] !== "") ? Number(ov[t]) : base[t]])); };
const optTableVal = (key, term) => optTable(key)[term] ?? optTable(key)[Object.keys(optTable(key))[0]] ?? 0;
// ===== All IP Pure: einfache Asymmetrisch/FTTH-Anbindungen, kein Backup/Sprachkanäle =====
const PURE_LZ = [24, 36, 48, 60];
const PURE_DESC_ASYM = ["Asymmetrische Kupfer-Anbindung (VDSL)"];
const PURE_DESC_FTTH = ["Asymmetrische Glasfaser-Anbindung (FTTH)"];
const PUREP = (id, name, m24, desc, ftth) => ({ id, name, desc, laufzeiten: PURE_LZ, m24, m36: m24 - 5, o24: 99, o36: 0, qty: true, qtyLabel: "Anzahl", standort: true, pure: true, pureDiscount: !!ftth });
const DB_ALLIPPURE = { label: "All IP Pure", products: [
  PUREP("pure-asym-25", "Asymmetrisch bis 25 Mbit/s", 45, ["Asymmetrische Kupfer-Anbindung bis 25/5 Mbit/s"]),
  PUREP("pure-asym-50", "Asymmetrisch bis 50 Mbit/s", 45, ["Asymmetrische Kupfer-Anbindung bis 50/10 Mbit/s"]),
  PUREP("pure-asym-100", "Asymmetrisch bis 100 Mbit/s", 45, ["Asymmetrische Kupfer-Anbindung bis 100/40 Mbit/s"]),
  PUREP("pure-asym-175", "Asymmetrisch bis 175 Mbit/s", 65, ["Asymmetrische Kupfer-Anbindung bis 175/40 Mbit/s"]),
  PUREP("pure-asym-250", "Asymmetrisch bis 250 Mbit/s", 65, ["Asymmetrische Kupfer-Anbindung bis 250/40 Mbit/s"]),
  PUREP("pure-ftth-150", "Asymmetrisch FTTH bis 150 Mbit/s", 65, ["Asymmetrische Glasfaser-Anbindung bis 150/75 Mbit/s"], true),
  PUREP("pure-ftth-300", "Asymmetrisch FTTH bis 300 Mbit/s", 75, ["Asymmetrische Glasfaser-Anbindung bis 300/150 Mbit/s"], true),
  PUREP("pure-ftth-600", "Asymmetrisch FTTH bis 600 Mbit/s", 105, ["Asymmetrische Glasfaser-Anbindung bis 600/300 Mbit/s"], true),
  PUREP("pure-ftth-1000", "Asymmetrisch FTTH bis 1000 Mbit/s", 115, ["Asymmetrische Glasfaser-Anbindung bis 1.000/500 Mbit/s"], true),
]};
const DB_ALLIPBASIC = { label: "All IP Basic", products: [
  { id: "b-dsl-250", name: "All-IP Basic 250", desc: ["Asymmetrische Kupfer-Anbindung bis 250/40 Mbit/s"], laufzeiten: LZ, m24: 47.5, m36: 42.5, o24: 50, o36: 0, connKind: "dsl", standort: true },
  { id: "b-dsl-100", name: "All-IP Basic 100", desc: ["Asymmetrische Kupfer-Anbindung bis 100/40 Mbit/s"], laufzeiten: LZ, m24: 40, m36: 35, o24: 50, o36: 0, connKind: "dsl", standort: true, zweiterKanal: true },
  { id: "b-dsl-50", name: "All-IP Basic 50", desc: ["Asymmetrische Kupfer-Anbindung bis 50/20 Mbit/s"], laufzeiten: LZ, m24: 35, m36: 30, o24: 50, o36: 0, connKind: "dsl", standort: true, zweiterKanal: true },
  { id: "b-dsl-16", name: "All-IP Basic 16", desc: ["Asymmetrische Kupfer-Anbindung bis 16/2,4 Mbit/s"], laufzeiten: LZ, m24: 35, m36: 30, o24: 50, o36: 0, connKind: "dsl", standort: true, zweiterKanal: true },
  { id: "b-cable-1000", name: "All-IP Basic Cable 1000", desc: ["Asymmetrische COAX-Anbindung bis 1.000/50 Mbit/s"], laufzeiten: LZ, m24: 70, m36: 65, o24: 50, o36: 0, connKind: "cable", standort: true },
  { id: "b-cable-500", name: "All-IP Basic Cable 500", desc: ["Asymmetrische COAX-Anbindung bis 500/50 Mbit/s"], laufzeiten: LZ, m24: 60, m36: 55, o24: 50, o36: 0, connKind: "cable", standort: true },
  { id: "b-cable-300", name: "All-IP Basic Cable 300", desc: ["Asymmetrische COAX-Anbindung bis 300/50 Mbit/s"], laufzeiten: LZ, m24: 50, m36: 45, o24: 50, o36: 0, connKind: "cable", standort: true },
  { id: "b-cable-100", name: "All-IP Basic Cable 100", desc: ["Asymmetrische COAX-Anbindung bis 100/50 Mbit/s"], laufzeiten: LZ, m24: 40, m36: 35, o24: 50, o36: 0, connKind: "cable", standort: true, zweiterKanal: true },
  { id: "b-cable-50", name: "All-IP Basic Cable 50", desc: ["Asymmetrische COAX-Anbindung bis 50/25 Mbit/s"], laufzeiten: LZ, m24: 35, m36: 30, o24: 50, o36: 0, connKind: "cable", standort: true, zweiterKanal: true },
  { id: "b-fiber-1000", name: "All-IP Basic Fiber 1000", desc: ["Asymmetrische Glasfaser-Anbindung bis 1.000/500 Mbit/s"], laufzeiten: LZ, m24: 94.44, m36: 88.89, o24: 50, o36: 0, connKind: "fiber", standort: true },
  { id: "b-fiber-600", name: "All-IP Basic Fiber 600", desc: ["Asymmetrische Glasfaser-Anbindung bis 600/300 Mbit/s"], laufzeiten: LZ, m24: 66.67, m36: 61.11, o24: 50, o36: 0, connKind: "fiber", standort: true },
  { id: "b-fiber-300", name: "All-IP Basic Fiber 300", desc: ["Asymmetrische Glasfaser-Anbindung bis 300/150 Mbit/s"], laufzeiten: LZ, m24: 55.56, m36: 47.22, o24: 50, o36: 0, connKind: "fiber", standort: true },
  { id: "b-fiber-150", name: "All-IP Basic Fiber 150", desc: ["Asymmetrische Glasfaser-Anbindung bis 150/75 Mbit/s"], laufzeiten: LZ, m24: 50, m36: 38.89, o24: 50, o36: 0, connKind: "fiber", standort: true, zweiterKanal: true },
]};

const DP_PRODUCTS = [
  { id: "dp-basis-bus", name: "Basispaket Business (5 Lizenzen)", desc: D_BASIS, laufzeiten: DP_LZ, m36: 59, o36: 99, produkttyp: "basispaket", rabattType: "fix10", abloese: true },
  { id: "dp-basis-bas", name: "Basispaket Basic (5 Lizenzen)", desc: ["Zugang zur virtuellen Telefonanlage inkl. 5 Lizenzen (Basic-Funktionsumfang)"], laufzeiten: DP_LZ, m36: 35, o36: 99, produkttyp: "basispaket", rabattType: "fix10", abloese: true },
  { id: "dp-liz-bus", name: "Zusätzliche Lizenz Business", desc: ["Inhalte identisch zum Basispaket"], laufzeiten: DP_LZ, m36: 14, o36: 29, qty: true, qtyLabel: "Lizenzen", produkttyp: "lizenz", abloese: true, dpTier: "business" },
  { id: "dp-liz-bas", name: "Zusätzliche Lizenz Basic", desc: ["Inhalte identisch zum Basispaket"], laufzeiten: DP_LZ, m36: 9.2, o36: 29, qty: true, qtyLabel: "Lizenzen", produkttyp: "lizenz", abloese: true, dpTier: "basic" },
  { id: "dp-einrichtung", name: "Basiseinrichtung", desc: ["Einmalige Basiseinrichtung der Telefonanlage"], laufzeiten: [], o36: 149, qty: true, qtyLabel: "Anzahl", produkttyp: "einrichtung" },
  { id: "dp-clip", name: "CLIP no Screening", desc: ["Einrichtung CLIP – no screening"], laufzeiten: [], o36: 69, qty: true, qtyLabel: "Anzahl", produkttyp: "rufnummern" },
  { id: "dp-teams", name: "MS Teams Option", desc: [], laufzeiten: [], m36: 1, qty: true, qtyLabel: "Nebenstellen", produkttyp: "option" },
  { id: "dp-block10", name: "Bereitstellung 10er Rufnummernblock", desc: ["Für einen entspannten Parallel-Betrieb Ihrer Telefonanlage"], laufzeiten: [], o36: 19, qty: true, qtyLabel: "Blöcke", produkttyp: "rufnummern" },
  { id: "dp-block100", name: "Bereitstellung 100er Rufnummernblock", desc: ["Für einen entspannten Parallel-Betrieb Ihrer Telefonanlage"], laufzeiten: [], o36: 149, qty: true, qtyLabel: "Blöcke", produkttyp: "rufnummern" },
  { id: "dp-blockverl", name: "Blockverlängerung", desc: ["Virtuelle Verlängerung Ihres Rufnummernblocks nach Bedarf"], laufzeiten: [], o36: 199, qty: true, qtyLabel: "Anzahl", produkttyp: "rufnummern" },
  { id: "dp-blockverk", name: "Blockverkürzung", desc: ["Virtuelle Verlängerung Ihres Rufnummernblocks nach Bedarf"], laufzeiten: [], o36: 199, qty: true, qtyLabel: "Anzahl", produkttyp: "rufnummern" },
  { id: "dp-efax", name: "eFax Nebenstelle", desc: ["eFax Client für Windows"], laufzeiten: [], o36: 10, m36: 1, qty: true, qtyLabel: "Nebenstellen", produkttyp: "option" },
  { id: "dp-crm", name: "CRM Connect Plus", desc: [], laufzeiten: [], m36: 1.8, qty: true, qtyLabel: "Nebenstellen", produkttyp: "option" },
  { id: "dp-ccm", name: "Call Center Monitoring (je Agent)", desc: [], laufzeiten: [], m36: 8, qty: true, qtyLabel: "Agents", qtyMin: 5, produkttyp: "option" },
  { id: "dp-cti-std-win", name: "CTI standard für Windows", desc: [], laufzeiten: [], m36: 1, qty: true, qtyLabel: "Lizenzen", produkttyp: "option" },
  { id: "dp-cti-std-crm", name: "CTI standard CRM für Windows", desc: [], laufzeiten: [], m36: 1.5, qty: true, qtyLabel: "Lizenzen", produkttyp: "option" },
  { id: "dp-cti-std-mac", name: "CTI standard für Mac", desc: [], laufzeiten: [], m36: 1, qty: true, qtyLabel: "Lizenzen", produkttyp: "option" },
  { id: "dp-cti-prem-win", name: "CTI Premium für Windows", desc: ["Premium-Lizenz – aktiviert einmalig CTI Premium Server (299 €)"], laufzeiten: [], m36: 1, qty: true, qtyLabel: "Lizenzen", produkttyp: "option", premiumCti: true },
  { id: "dp-cti-prem-crm", name: "CTI Premium CRM für Windows", desc: ["Premium-Lizenz – aktiviert einmalig CTI Premium Server (299 €)"], laufzeiten: [], m36: 1.8, qty: true, qtyLabel: "Lizenzen", produkttyp: "option", premiumCti: true },
  { id: "dp-cti-standort", name: "CTI Standortlizenz", desc: [], laufzeiten: [], m36: 7.9, qty: true, qtyLabel: "Standorte", produkttyp: "option" },
  { id: "dp-cti-server", name: "CTI Premium Server (Einrichtung)", desc: ["Wird einmalig berechnet, sobald eine CTI-Premium-Lizenz gebucht ist"], laufzeiten: [], o36: 299, produkttyp: "server", isServer: true },
  { id: "dp-ncti", name: "NCTI Pro (estos ProCall)", desc: [], laufzeiten: [], m36: 4.8, qty: true, qtyLabel: "Lizenzen", produkttyp: "option" },
  { id: "dp-intl", name: "International Paket", desc: ["Muss für alle Nebenstellen inkl. Basispaket gebucht werden", "Festnetz Europazone 1 und Weltzone 1 kostenlos"], laufzeiten: [], o36: 9, m36: 5, qty: true, qtyLabel: "Nebenstellen", produkttyp: "option", mengenBezug: "nebenstellen" },
];
// Digital Phone Business / Basic: identischer Katalog, je Bereich nur die passende "Zusätzliche Lizenz"
const DB_DIGITALPHONE_BUSINESS = { label: "Digital Phone Business", products: DP_PRODUCTS.filter(p => p.dpTier !== "basic") };
const DB_DIGITALPHONE_BASIC = { label: "Digital Phone Basic", products: DP_PRODUCTS.filter(p => p.dpTier !== "business") };

const DB_SIPTRUNK = { label: "SIP Trunk Only", products: [
  { id: "sip-basic", name: "SIP-Trunk Basic inkl. Deutschland-Flat", desc: D_SIP, laufzeiten: [], m36: 8, o36: 0, qty: true, qtyLabel: "Sprachkanäle", teamsCoupling: true },
  { id: "sip-premium", name: "SIP-Trunk Premium inkl. Deutschland-Flat", desc: D_SIP, laufzeiten: [], m36: 12.5, o36: 0, qty: true, qtyLabel: "Sprachkanäle", teamsCoupling: true },
  { id: "sip-sep-weitere", name: "── Weitere ──", desc: [], isSeparator: true },
  { id: "sip-efax", name: "eFax", desc: [], laufzeiten: [], m36: 6.8, o36: 19, qty: true, qtyLabel: "Anzahl eFax" },
  { id: "sip-block10", name: "Geografischer 10er-Rufnummernblock", desc: [], laufzeiten: [], o36: 19, qty: true, qtyLabel: "Blöcke" },
  { id: "sip-block30", name: "Geografischer 30er-Rufnummernblock", desc: [], laufzeiten: [], o36: 59, qty: true, qtyLabel: "Blöcke" },
  { id: "sip-block50", name: "Geografischer 50er-Rufnummernblock", desc: [], laufzeiten: [], o36: 99, qty: true, qtyLabel: "Blöcke" },
  { id: "sip-block100", name: "Geografischer 100er-Rufnummernblock", desc: [], laufzeiten: [], o36: 149, qty: true, qtyLabel: "Blöcke" },
  { id: "sip-umzug", name: "Rufnummernumzug", desc: [], laufzeiten: [], o36: 99, qty: true, qtyLabel: "Anzahl" },
  { id: "sip-inbetrieb", name: "Inbetriebnahme SIP-Trunk inkl. Integration Microsoft Teams 3 (max. 180 Minuten)", desc: [], laufzeiten: [], o36: 333 },
]};

const DB_MDM = { label: "Mobile Device Management", products: [
  { id: "mdm-basic", name: "MDM Basic", desc: ["Umfassender Schutz für Ihre mobilen Endgeräte", "Management per Remote im Browser", "DSGVO-Konform"], laufzeiten: [24, 36], m36: 2.5, o36: 0, qty: true, qtyLabel: "Lizenzen", rabattType: "staffelMax", staffel: MDM_STAFFEL, mdmKind: "unmanaged" },
  { id: "mdm-managed", name: "MDM Basic Managed", desc: ["Umfassender Schutz für Ihre mobilen Endgeräte", "DSGVO-Konform", "", "Kompletteinrichtung & Verwaltung der Plattform durch persönlichen MDM-Experten"], laufzeiten: [24, 36], m36: 8, o36: 0, qty: true, qtyLabel: "Lizenzen", rabattType: "staffelMax", staffel: MDM_STAFFEL, mdmKind: "managed" },
  { id: "mdm-training", name: "MDM Basic Training", desc: ["90 Minuten Administrator-Training"], laufzeiten: [24, 36], o36: 333 },
]};

const O = (id, name, loPrice, loMax, mgdPrice, mgdMax) => ({ id, name, desc: [], laufzeiten: [], qty: true, qtyLabel: "Anzahl", office: true, rabattType: "officeMax", produkttyp: "option", loPrice, loMax, mgdPrice: mgdPrice ?? null, mgdMax: mgdMax ?? null });
const DB_OFFICE365 = { label: "Microsoft 365 / Office", products: [
  O("o-bb","Microsoft 365 Business Basic",6.73,30,11.73,25), O("o-bb-eea","Microsoft 365 Business Basic EEA",4.90,30,9.90,25),
  O("o-bs","Microsoft 365 Business Standard",12.74,40,17.74,35), O("o-bs-eea","Microsoft 365 Business Standard EEA",9.81,40,14.81,35),
  O("o-bp","Microsoft 365 Business Premium",20.06,40,25.06,35), O("o-bp-eea","Microsoft 365 Business Premium EEA",17.12,40,22.12,35),
  O("o-phone-std","Microsoft 365 Phone Standard",9.14,45,null,null), O("o-teams-ess","Microsoft Teams Essentials",3.68,20,null,null),
  O("o-exo-1","Exchange Online (Plan 1)",3.68,5,8.68,5), O("o-exo-2","Exchange Online (Plan 2)",7.25,5,12.25,5),
  { id: "o-sep-weitere", name: "── Weitere Lizenzen ──", desc: [], isSeparator: true },
  O("o-ems-e3","Enterprise Mobility + Security E3",10.92,5,15.92,5), O("o-ems-e5","Enterprise Mobility + Security E5",16.38,5,21.38,5),
  O("o-exo-arch-online","Exchange Online Archiving for Exchange Online",2.73,5,null,null), O("o-exo-arch-server","Exchange Online Archiving for Exchange Server",2.73,5,null,null),
  O("o-exo-kiosk","Exchange Online Kiosk",1.82,5,null,null), O("o-exo-protection","Exchange Online Protection",1.01,5,null,null),
  O("o-apps-bus","Microsoft 365 Apps for Business",11.55,5,16.55,5), O("o-apps-ent","Microsoft 365 Apps for Enterprise",16.17,5,21.17,5),
  O("o-copilot-1x","Microsoft 365 Copilot (1xZahlung + keine Provision)",26.78,5,null,null), O("o-copilot-month","Microsoft 365 Copilot - monatlich",29.99,5,null,null),
  O("o-m365-e3","Microsoft 365 E3",39.67,5,44.67,5), O("o-m365-e3-eea","Microsoft 365 E3 EEA (no Teams)",31.88,5,36.88,5),
  O("o-m365-e5","Microsoft 365 E5",62.10,5,67.10,5), O("o-m365-e5-eea","Microsoft 365 E5 EEA (no Teams)",53.25,5,58.25,5),
  O("o-purview","Microsoft 365 Purview Suite",10.92,5,15.92,5), O("o-defender-suite","Microsoft 365 Defender Suite",10.92,5,null,null),
  O("o-m365-f1","Microsoft 365 F1",2.73,5,7.73,5), O("o-m365-f1-eea","Microsoft 365 F1 EEA (no Teams)",2.27,5,7.27,5),
  O("o-m365-f3","Microsoft 365 F3",9.09,5,14.09,5), O("o-m365-f3-eea","Microsoft 365 F3 EEA (no Teams)",8.12,5,13.12,5),
  O("o-def-bus","Microsoft Defender for Business",2.73,5,null,null), O("o-def-bus-srv","Microsoft Defender for Business servers",2.73,5,null,null),
  O("o-def-cloudapps","Microsoft Defender for Cloud Apps",3.15,5,null,null), O("o-def-cloudapps-f1","Microsoft Defender for Cloud Apps F1",2.21,5,null,null),
  O("o-def-ep-f1","Microsoft Defender for Endpoint F1",1.82,5,null,null), O("o-def-ep-f2","Microsoft Defender for Endpoint F2",3.15,5,null,null),
  O("o-def-ep-p1","Microsoft Defender for Endpoint P1",2.73,5,null,null), O("o-def-ep-p2","Microsoft Defender for Endpoint P2",4.73,5,null,null),
  O("o-def-ep-server","Microsoft Defender for Endpoint Server",4.73,5,null,null), O("o-def-id","Microsoft Defender for Identity",5.04,5,null,null),
  O("o-def-id-f1","Microsoft Defender for Identity F1",3.36,5,null,null), O("o-def-o365-f1","Microsoft Defender for Office 365 F1",1.33,5,null,null),
  O("o-def-o365-f2","Microsoft Defender for Office 365 F2",3.05,5,null,null), O("o-def-o365-p1","Microsoft Defender for Office 365 (Plan 1)",1.82,5,null,null),
  O("o-def-o365-p2","Microsoft Defender for Office 365 (Plan 2)",4.52,5,null,null), O("o-entra-id-f2","Microsoft Entra ID F2",6.41,5,11.41,5),
  O("o-entra-id-p1","Microsoft Entra ID P1",6.80,5,11.80,5), O("o-entra-id-p2","Microsoft Entra ID P2",9.14,5,14.14,5),
  O("o-entra-suite","Microsoft Entra Suite",10.92,5,null,null), O("o-intune-epm","Microsoft Intune Endpoint Privilege Management",2.73,5,null,null),
  O("o-intune-p1","Microsoft Intune Plan 1",7.25,5,12.25,5), O("o-intune-p1-device","Microsoft Intune Plan 1 Device",2.42,5,null,null),
  O("o-intune-p1-storage","Microsoft Intune Plan 1 Storage Add-On",3.68,5,null,null), O("o-intune-p2","Microsoft Intune Plan 2",3.68,5,null,null),
  O("o-intune-remote","Microsoft Intune Remote Help",3.15,5,null,null), O("o-intune-suite","Microsoft Intune Suite",9.14,5,null,null),
  O("o-teams-eea","Microsoft Teams EEA",7.77,5,12.77,5), O("o-teams-rooms","Microsoft Teams Rooms Pro",36.44,5,null,null),
  O("o-teams-shared","Microsoft Teams Shared Devices",7.25,5,null,null), O("o-o365-dlp","Office 365 Data Loss Prevention",2.73,5,null,null),
  O("o-o365-e1","Office 365 E1",9.14,5,14.14,5), O("o-o365-e1-eea","Office 365 E1 EEA (no Teams)",6.20,5,11.20,5),
  O("o-o365-e3","Office 365 E3",27.58,5,32.58,5), O("o-o365-e3-eea","Office 365 E3 EEA (no Teams)",19.79,5,24.79,5),
  O("o-o365-e5","Office 365 E5",43.83,5,48.83,5), O("o-o365-e5-eea","Office 365 E5 EEA (no Teams)",35.71,5,40.71,5),
  O("o-o365-storage","Office 365 Extra File Storage",0.21,25,null,null), O("o-o365-f3","Office 365 F3",3.68,5,8.68,5),
  O("o-o365-f3-eea","Office 365 F3 EEA (no Teams)",3.15,5,null,null), O("o-onedrive-p1","OneDrive for business (Plan 1)",4.52,5,null,null),
  O("o-onedrive-p2","OneDrive for business (Plan 2)",9.14,5,null,null), O("o-planner-proj-p3","Planner and Project Plan 3",27.30,5,32.30,5),
  O("o-planner-proj-p5","Planner and Project Plan 5",50.09,5,55.09,5), O("o-planner-p1","Planner Plan 1",9.14,5,null,null),
  O("o-powerapps-prem","Power Apps Premium",18.17,5,null,null), O("o-pa-flow","Power Automate per flow plan",90.93,5,null,null),
  O("o-pa-user","Power Automate per user plan",13.65,5,null,null), O("o-pbi-prem-user","Power BI Premium Per User",21.84,5,null,null),
  O("o-pbi-prem-addon","Power BI Premium Per User Add-On",9.14,5,null,null), O("o-pbi-pro","Power BI Pro",12.71,5,null,null),
  O("o-sp-p1","SharePoint (Plan 1)",4.52,5,null,null), O("o-sp-p2","SharePoint (Plan 2)",9.14,5,null,null),
  O("o-sp-adv","SharePoint advanced management plan 1",2.73,5,null,null), O("o-visio-p1","Visio Plan 1",4.52,5,9.52,5),
  O("o-visio-p2","Visio Plan 2",13.65,5,18.65,5),
]};

// ===== SD-WAN Fortinet =====
const SDWAN_TERMS = [12, 24, 36, 48, 60];
const T5 = (a, b, c, d, e) => ({ 12: a, 24: b, 36: c, 48: d, 60: e });
const SDWAN_LIC_LABEL = { sdwan: "SD-WAN", atp: "Basic Security (ATP)", utp: "Advanced Security (UTP)" };
const SDWAN_LIC_LABEL_MERAKI = { sdwan: "SD-WAN", atp: "Basic Security", utp: "Advanced Security" };
const sdwanLicLabel = (vendor, lic) => (vendor === "meraki" ? SDWAN_LIC_LABEL_MERAKI : SDWAN_LIC_LABEL)[lic] || lic;
const sdwanRedTable = (vendor) => ({ sdwan: optTable(vendor === "meraki" ? "sdwan_red_meraki_sdwan" : "sdwan_red_fortinet_sdwan"), atp: optTable(vendor === "meraki" ? "sdwan_red_meraki_atp" : "sdwan_red_fortinet_atp"), utp: optTable(vendor === "meraki" ? "sdwan_red_meraki_utp" : "sdwan_red_fortinet_utp") });
// Einrichtung SD-WAN-Service (laufzeitabhängig, je Lizenz/Stück) – editierbar über optTable("sdwan_einr_router"/"sdwan_einr_apsw")
// Remote-Setup, Vor-Ort-Installation, AD-Anbindung – editierbar über optPrice("sdwan_remote_setup"/"sdwan_install_per_device"/"sdwan_ad_oneoff")
const sdwanFloor = (p, lic, term) => { const m = p.lic ? p.lic[lic] : p.price; return m ? (m[term] ?? m[12]) : 0; };
const sdwanCeil = (p, lic) => { const m = p.lic ? p.lic[lic] : p.price; return m ? m[12] : 0; };
const sdwanEinr = (kind, term) => optTableVal(kind === "ap_switch" ? "sdwan_einr_apsw" : "sdwan_einr_router", term);
const sdwanProjectCost = (standorte) => standorte >= 11 ? 0 : standorte >= 5 ? optPrice("sdwan_project_tier2") : standorte >= 2 ? optPrice("sdwan_project_tier1") : 0;
const SEP = (id, name) => ({ id, name, isSeparator: true });

// Monatspreis einer SD-WAN-Zeile (inkl. Redundanz). chosen = gewählter Preis (leer => Boden = Preis der Laufzeit).
const sdwanChosen = (p, r) => {
  const lic = p.lic ? (r.sdwanLicense || "sdwan") : null;
  const term = Number(r.term) || 60;
  const floor = sdwanFloor(p, lic, term);
  const raw = r.sdwanPrice === "" || r.sdwanPrice == null ? floor : Number(r.sdwanPrice);
  return isFinite(raw) ? raw : floor;
};
const sdwanRowMonthlyEach = (p, r) => {
  const chosen = sdwanChosen(p, r);
  if (p.sdwanKind === "ad") return 0;
  if (p.sdwanKind === "fg" && p.redundanzAllowed && r.redundanz) {
    const lic = r.sdwanLicense || "sdwan"; const term = Number(r.term) || 60;
    return 2 * chosen + (sdwanRedTable(p.vendor)[lic]?.[term] ?? 0);
  }
  return chosen;
};

const FG = (id, name, lic, redundanzAllowed, vendor = "fortinet") => ({ id, name, sdwan: true, sdwanKind: "fg", einrKind: "router", lic, redundanzAllowed, vendor, qty: true, qtyLabel: "Menge", standort: true });
const VM = (id, name, lic) => ({ id, name, sdwan: true, sdwanKind: "vm", einrKind: "router", lic, vendor: "fortinet", qty: true, qtyLabel: "Menge", standort: true });
// Meraki-Router: physischer Primär-Router, Redundanz erlaubt, Meraki-Preistabellen/-Labels
const MKR = (id, name, lic) => ({ id, name, sdwan: true, sdwanKind: "fg", einrKind: "router", lic, redundanzAllowed: true, vendor: "meraki", qty: true, qtyLabel: "Menge", standort: true });
const SDEV = (id, name, kind, einrKind, price, extra) => ({ id, name, sdwan: true, sdwanKind: kind, einrKind, price, qty: true, qtyLabel: "Menge", standort: true, ...(extra || {}) });

const DB_SDWAN = { label: "SD-WAN Fortinet", products: [
  FG("fg40", "Fortinet FG 40 (S – kleiner Standort)", { sdwan: T5(110, 104.5, 99, 93.5, 88), atp: T5(170, 161.5, 153, 144.5, 136), utp: T5(200, 190, 180, 170, 160) }, false),
  FG("fg60", "Fortinet FG 60 (M – mittlerer Standort)", { sdwan: T5(120, 114, 108, 102, 96), atp: T5(190, 180.5, 171, 161.5, 152), utp: T5(230, 218.5, 207, 195.5, 184) }, true),
  FG("fg80", "Fortinet FG 80 (M – mittlerer Standort)", { sdwan: T5(160, 152, 144, 136, 128), atp: T5(240, 228, 216, 204, 192), utp: T5(300, 285, 270, 255, 240) }, true),
  FG("fg100", "Fortinet FG 100 (L – großer Standort)", { sdwan: T5(280, 266, 252, 238, 224), atp: T5(430, 408.5, 387, 365.5, 344), utp: T5(540, 513, 486, 459, 432) }, true),
  FG("fg200", "Fortinet FG 200 (L – großer Standort)", { sdwan: T5(340, 323, 306, 289, 272), atp: T5(530, 503.5, 477, 450.5, 424), utp: T5(650, 617.5, 585, 552.5, 520) }, true),
  FG("fg400", "Fortinet FG 400 (XL – sehr großer Standort)", { sdwan: T5(500, 475, 450, 425, 400), atp: T5(790, 750.5, 711, 671.5, 632), utp: T5(1000, 950, 900, 850, 800) }, true),
  FG("fg600", "Fortinet FG 600 (XL – sehr großer Standort)", { sdwan: T5(660, 627, 594, 561, 528), atp: T5(990, 940.5, 891, 841.5, 792), utp: T5(1260, 1197, 1134, 1071, 1008) }, true),
  SEP("sep-vm", "──────── Virtual Machine ────────"),
  VM("vm01", "Fortinet Virtual Machine FG VM01", { sdwan: T5(130, 123.5, 117, 110.5, 104), atp: T5(200, 190, 180, 170, 160), utp: T5(260, 247, 234, 221, 208) }),
  VM("vm02", "Fortinet Virtual Machine FG VM02", { sdwan: T5(140, 133, 126, 119, 112), atp: T5(220, 209, 198, 187, 176), utp: T5(280, 266, 252, 238, 224) }),
  VM("vm04", "Fortinet Virtual Machine FG VM04", { sdwan: T5(220, 209, 198, 187, 176), atp: T5(350, 332.5, 315, 297.5, 280), utp: T5(480, 456, 432, 408, 384) }),
  VM("vm08", "Fortinet Virtual Machine FG VM08", { sdwan: T5(530, 503.5, 477, 450.5, 424), atp: T5(860, 817, 774, 731, 688), utp: T5(1180, 1121, 1062, 1003, 944) }),
  VM("vm16", "Fortinet Virtual Machine FG VM16", { sdwan: T5(1080, 1026, 972, 918, 864), atp: T5(1750, 1662.5, 1575, 1487.5, 1400), utp: T5(2390, 2270.5, 2151, 2031.5, 1912) }),
  SEP("sep-fex", "──────── Mobilfunk Extender ────────"),
  SDEV("fex101", "Fortinet FEX 101 (Mobilfunk-WAN-Extender)", "fex", "router", T5(38, 36.1, 34.2, 32.3, 30.4)),
  SDEV("fex511", "Fortinet FEX 511 (Mobilfunk-WAN-Extender)", "fex", "router", T5(70, 66.5, 63, 59.5, 56)),
  SEP("sep-sw", "──────── LAN-Switches ────────"),
  SDEV("fs108", "Fortinet FS 108", "switch", "ap_switch", T5(28, 26.6, 25.2, 23.8, 22.4)),
  SDEV("fs108poe", "Fortinet FS 108 POE", "switch", "ap_switch", T5(32, 30.4, 28.8, 27.2, 25.6)),
  SDEV("fs124", "Fortinet FS 124", "switch", "ap_switch", T5(38, 36.1, 34.2, 32.3, 30.4)),
  SDEV("fs124poe", "Fortinet FS 124 POE", "switch", "ap_switch", T5(56, 53.2, 50.4, 47.6, 44.8)),
  SDEV("fs148", "Fortinet FS 148", "switch", "ap_switch", T5(52, 49.4, 46.8, 44.2, 41.6)),
  SDEV("fs148poe", "Fortinet FS 148 POE", "switch", "ap_switch", T5(72, 68.4, 64.8, 61.2, 57.6)),
  SDEV("fs424", "Fortinet FS 424", "switch", "ap_switch", T5(62, 58.9, 55.8, 52.7, 49.6)),
  SDEV("fs424poe", "Fortinet FS 424 POE", "switch", "ap_switch", T5(90, 85.5, 81, 76.5, 72)),
  SDEV("fs448", "Fortinet FS 448", "switch", "ap_switch", T5(86, 81.7, 77.4, 73.1, 68.8)),
  SDEV("fs448poe", "Fortinet FS 448 POE", "switch", "ap_switch", T5(148, 140.6, 133.2, 125.8, 118.4)),
  SEP("sep-ap", "──────── WLAN-Accesspoints ────────"),
  SDEV("fap231", "Fortinet FAP 231", "ap", "ap_switch", T5(32, 30.4, 28.8, 27.2, 25.6)),
  SDEV("fap431", "Fortinet FAP 431", "ap", "ap_switch", T5(52, 49.4, 46.8, 44.2, 41.6)),
  SDEV("fap831", "Fortinet FAP 831", "ap", "ap_switch", T5(62, 58.9, 55.8, 52.7, 49.6)),
  SEP("sep-remote", "──────── Remote User ────────"),
  SDEV("ru-vpn", "Remote User VPN", "remote", null, T5(98, 93.1, 88.2, 83.3, 78.4), { pack25: true }),
  SDEV("ru-ztna", "Remote User ZTNA", "remote", null, T5(128, 121.6, 115.2, 108.8, 102.4), { pack25: true }),
  SDEV("ru-epp", "Remote User EPP", "remote", null, T5(188, 178.6, 169.2, 159.8, 150.4), { pack25: true }),
  SEP("sep-weitere", "──────── Weitere Leistungen ────────"),
  { id: "sd-ad", name: "Anbindung kundenseitiger Active-Directory-Instanz", sdwan: true, sdwanKind: "ad", qty: true, qtyLabel: "Standorte (Hub)", standort: true },
]};
const DB_SDWAN_MERAKI = { label: "SD-WAN Meraki", products: [
  MKR("mk-z3", "Meraki Z3 (XS – Teleworker)", { sdwan: T5(60, 57, 54, 51, 48) }),
  MKR("mk-mx67", "Meraki MX67 (S – kleiner Standort)", { sdwan: T5(100, 95, 90, 85, 80), atp: T5(130, 123.5, 117, 110.5, 104), utp: T5(170, 161.5, 153, 144.5, 136) }),
  MKR("mk-mx95", "Meraki MX95 (M – mittlerer Standort)", { sdwan: T5(320, 304, 288, 272, 256), atp: T5(470, 446.5, 423, 399.5, 376), utp: T5(570, 541.5, 513, 484.5, 456) }),
  MKR("mk-mx250", "Meraki MX250 (L – großer Standort)", { sdwan: T5(530, 503.5, 477, 450.5, 424), atp: T5(760, 722, 684, 646, 608), utp: T5(1100, 1045, 990, 935, 880) }),
  MKR("mk-mx450", "Meraki MX450 (XL – sehr großer Standort)", { sdwan: T5(1060, 1007, 954, 901, 848), atp: T5(1540, 1463, 1386, 1309, 1232), utp: T5(2260, 2147, 2034, 1921, 1808) }),
  SEP("mk-sep-fex", "──────── Mobilfunk Extender ────────"),
  SDEV("mk-mg21", "Meraki MG21 (Mobilfunk-WAN-Extender)", "fex", "router", T5(35, 33.25, 31.5, 29.75, 28)),
  SEP("mk-sep-sw", "──────── LAN-Switches ────────"),
  SDEV("mk-ms120-8lp", "Meraki MS120-8LP", "switch", "ap_switch", T5(50, 47.5, 45, 42.5, 40)),
  SDEV("mk-ms120-24p", "Meraki MS120-24P", "switch", "ap_switch", T5(100, 95, 90, 85, 80)),
  SDEV("mk-ms120-48lp", "Meraki MS120-48LP", "switch", "ap_switch", T5(150, 142.5, 135, 127.5, 120)),
  SDEV("mk-ms125-24p", "Meraki MS125-24P", "switch", "ap_switch", T5(150, 142.5, 135, 127.5, 120)),
  SDEV("mk-ms125-48lp", "Meraki MS125-48LP", "switch", "ap_switch", T5(170, 161.5, 153, 144.5, 136)),
  SDEV("mk-ms225-24p", "Meraki MS225-24P", "switch", "ap_switch", T5(190, 180.5, 171, 161.5, 152)),
  SDEV("mk-ms225-48lp", "Meraki MS225-48LP", "switch", "ap_switch", T5(270, 256.5, 243, 229.5, 216)),
  SDEV("mk-ms390-24p", "Meraki MS390-24P", "switch", "ap_switch", T5(270, 256.5, 243, 229.5, 216)),
  SDEV("mk-ms390-48p", "Meraki MS390-48P", "switch", "ap_switch", T5(250, 237.5, 225, 212.5, 200)),
  SEP("mk-sep-ap", "──────── WLAN-Accesspoints ────────"),
  SDEV("mk-mr28", "Meraki MR28", "ap", "ap_switch", T5(30, 28.5, 27, 25.5, 24)),
  SDEV("mk-mr44", "Meraki MR44", "ap", "ap_switch", T5(50, 47.5, 45, 42.5, 40)),
  SDEV("mk-mr56", "Meraki MR56", "ap", "ap_switch", T5(60, 57, 54, 51, 48)),
]};
// ===== SD-WAN Smart Connect (nur Hardware – kein Redundanz, keine Auto-Positionen; läuft über die normale
// generische Produktlogik, da Fixpreis über die gesamte Laufzeit 36/48/60 und kein Sonderverhalten nötig ist) =====
const SC_LZ = [36, 48, 60];
const SC_DESC = ["Smart Connect: nur in Kombination mit O2 Business All-IP Access Asymmetrisch am selben Standort", "Mindestlaufzeit 36 Monate · Bereitstellung & Abrechnung getrennt von All-IP"];
const SC = (id, name, m) => ({ id, name, desc: SC_DESC, laufzeiten: SC_LZ, m36: m, o36: 0, qty: true, qtyLabel: "Menge", standort: true });
const DB_SDWAN_SMART_FORTINET = { label: "SD-WAN Smart Connect Fortinet", products: [
  SC("sc-fg40-sdwan", "Fortinet FG 40 – SD-WAN (Smart Connect)", 60),
  SC("sc-fg40-atp", "Fortinet FG 40 – Basic Security (ATP) (Smart Connect)", 100),
  SC("sc-fg40-utp", "Fortinet FG 40 – Advanced Security (UTP) (Smart Connect)", 120),
  SC("sc-fg60-sdwan", "Fortinet FG 60 – SD-WAN (Smart Connect)", 70),
  SC("sc-fg60-atp", "Fortinet FG 60 – Basic Security (ATP) (Smart Connect)", 120),
  SC("sc-fg60-utp", "Fortinet FG 60 – Advanced Security (UTP) (Smart Connect)", 140),
  SC("sc-fg80-sdwan", "Fortinet FG 80 – SD-WAN (Smart Connect)", 100),
  SC("sc-fg80-atp", "Fortinet FG 80 – Basic Security (ATP) (Smart Connect)", 150),
  SC("sc-fg80-utp", "Fortinet FG 80 – Advanced Security (UTP) (Smart Connect)", 180),
  SC("sc-fg100-sdwan", "Fortinet FG 100 – SD-WAN (Smart Connect)", 170),
  SC("sc-fg100-atp", "Fortinet FG 100 – Basic Security (ATP) (Smart Connect)", 270),
  SC("sc-fg100-utp", "Fortinet FG 100 – Advanced Security (UTP) (Smart Connect)", 340),
]};
const DB_SDWAN_SMART_MERAKI = { label: "SD-WAN Smart Connect Meraki", products: [
  SC("sc-mkz3-sdwan", "Meraki Z3 – SD-WAN (Smart Connect)", 35),
  SC("sc-mkmx67-sdwan", "Meraki MX67 – SD-WAN (Smart Connect)", 60),
  SC("sc-mkmx67-atp", "Meraki MX67 – Basic Security (Smart Connect)", 80),
  SC("sc-mkmx67-utp", "Meraki MX67 – Advanced Security (Smart Connect)", 100),
  SC("sc-mkmx95-sdwan", "Meraki MX95 – SD-WAN (Smart Connect)", 200),
  SC("sc-mkmx95-atp", "Meraki MX95 – Basic Security (Smart Connect)", 290),
  SC("sc-mkmx95-utp", "Meraki MX95 – Advanced Security (Smart Connect)", 350),
]};
const DB_VPNCONNECT = { label: "VPN Connect", products: [] };

const BASE_CATALOG = { allip: DB_ALLIP, allippure: DB_ALLIPPURE, allipbasic: DB_ALLIPBASIC, dpbusiness: DB_DIGITALPHONE_BUSINESS, dpbasic: DB_DIGITALPHONE_BASIC, siptrunk: DB_SIPTRUNK, mdm: DB_MDM, office365: DB_OFFICE365, sdwan: DB_SDWAN, sdwanmeraki: DB_SDWAN_MERAKI, sdwansmartfortinet: DB_SDWAN_SMART_FORTINET, sdwansmartmeraki: DB_SDWAN_SMART_MERAKI, vpnconnect: DB_VPNCONNECT, individuell: { label: "Individuell", products: [] }, standort: { label: "Standortangebot", products: [] } };
const AREAS = Object.entries(BASE_CATALOG).map(([key, v]) => ({ key, label: v.label }));
const SUBAREAS = AREAS.filter(a => a.key !== "standort");
// Ortsgebundene Bereiche (mit Standort): kommen bei Umwandlung in ein Standortangebot unter eine Standort-Ebene.
// Nicht ortsgebunden (Digital Phone, SIP, MDM, Office 365) bleiben Top-Level mit reinem Produkt-Header.
const LOCATION_AREAS = new Set(["allip", "allippure", "allipbasic", "sdwan", "sdwanmeraki", "sdwansmartfortinet", "sdwansmartmeraki", "vpnconnect", "individuell"]);
const isLocationArea = (area) => LOCATION_AREAS.has(area);
// Excel-Vorlage (ein Blatt je Bereich) -> flache Gruppen (Zeilen mit Standort getaggt). Keine Preise/Berechnung in Excel.
const sanitizeSheet = (label) => String(label || "").replace(/[/\\?*[\]:]/g, "-").slice(0, 31);
// Router-Text aus der Excel-Spalte auf die passende Option je Anschlussart matchen ("kein"/leer -> keine Auswahl)
const routerTextToKey = (connKind, text) => {
  const t = String(text || "").trim().toLowerCase();
  if (!t || t.includes("kein")) return "";
  for (const k of (BASIC_ROUTER_OPTIONS[connKind] || [])) {
    const name = optText(`basic_router_${connKind}_${k}`);
    if (name && t.includes(String(name).toLowerCase())) return k;
  }
  return "";
};
const parseImportWorkbook = (wb) => {
  const areaByLabel = {}; const prodIdByName = {};
  for (const a of AREAS) { areaByLabel[sanitizeSheet(a.label)] = a.key; const db = CATALOG[a.key]; prodIdByName[a.key] = {}; for (const p of (db?.products || [])) if (!p.isSeparator) prodIdByName[a.key][p.name] = p.id; }
  const TERM = v => { const m = String(v || "").match(/(\d+)/); return m ? Number(m[1]) : null; };
  const YES = v => /^(ja|x|true|1)$/i.test(String(v || "").trim());
  const RAB = v => { const s = String(v || "").trim().toLowerCase(); if (s.includes("pricing")) return "pricing"; const m = s.match(/(\d+)/); return m ? m[1] : "0"; };
  const LIC = v => { const s = String(v || "").toLowerCase(); if (s.includes("advanced") || s.includes("utp")) return "utp"; if (s.includes("basic") || s.includes("atp")) return "atp"; return "sdwan"; };
  const ACC = v => { const s = String(v || "").toLowerCase(); if (s.includes("link")) return "link"; if (s.includes("mobil")) return "mobile"; if (s.includes("ipsec")) return "ipsec"; return "asym"; };
  const groups = []; const areaGroup = {};
  const getAG = (area) => { if (!areaGroup[area]) { const g = newGroup(); g.area = area; g.rows = []; g.subGroups = []; areaGroup[area] = g; groups.push(g); } return areaGroup[area]; };
  let count = 0;
  for (const sheetName of wb.SheetNames) {
    const area = areaByLabel[sheetName]; if (!area) continue;
    const rows = XLSX.utils.sheet_to_json(wb.Sheets[sheetName], { header: 1, defval: "" });
    if (!rows.length) continue;
    const header = rows[0].map(h => String(h || "").trim());
    const col = name => header.indexOf(name);
    const nameMap = prodIdByName[area] || {};
    for (let i = 1; i < rows.length; i++) {
      const row = rows[i]; const get = name => { const c = col(name); return c >= 0 ? row[c] : ""; };
      const standort = String(get("Standort") || "").trim();
      if (area === "vpnconnect") {
        const acc = get("Hauptanschluss"); if (!String(acc || "").trim()) continue;
        const g = newGroup(); g.area = "vpnconnect"; g.rows = []; g.vpnName = standort;
        g.main = newVpnConn(ACC(acc)); const bw = String(get("Bandbreite (Mbit/s)") || "").trim(); if (bw) g.main.bw = bw;
        g.vpnTerm = TERM(get("Laufzeit")) || 36;
        const bk = String(get("Backup") || "").toLowerCase();
        g.backup = bk.includes("mobil") ? newVpnConn("mobile") : bk.includes("asym") ? newVpnConn("asym") : null;
        groups.push(g); count++; continue;
      }
      if (area === "individuell") {
        const bez = String(get("Bezeichnung") || "").trim(); if (!bez) continue;
        const g = getAG("individuell"); const r = newRow();
        r.custName = bez; r.term = TERM(get("Laufzeit")); r.qty = Number(get("Menge")) || 1;
        r.monthly = Number(get("Monatlich (€)")) || 0; r.oneoff = Number(get("Einmalig (€)")) || 0; r.standort = standort;
        g.rows.push(r); count++; continue;
      }
      const pname = String(get("Produkt") || "").trim(); if (!pname) continue;
      const pid = nameMap[pname]; if (!pid) continue;
      const g = getAG(area); const r = newRow();
      r.productId = pid; r.standort = standort;
      if (col("Laufzeit") >= 0) { const t = TERM(get("Laufzeit")); if (t) r.term = t; }
      if (col("Menge") >= 0) r.qty = Number(get("Menge")) || 1;
      if (col("Rabatt") >= 0) { const rawR = String(get("Rabatt") || "").toLowerCase(); const rb = RAB(get("Rabatt")); r.lineRabatt = rb; r.mobileRabatt = rb; r.pureRabatt = rb === "pricing" ? "0" : rb; r.basicRabatt = rawR.includes("kein") || rawR === "" ? "" : rb; }
      if (col("Internet-Flatrate") >= 0) r.internetFlat = YES(get("Internet-Flatrate"));
      if (col("Mobilfunk-Backup") >= 0) r.backup = YES(get("Mobilfunk-Backup"));
      if (col("2. Sprachkanal") >= 0) r.zweiterKanal = YES(get("2. Sprachkanal"));
      if (col("Router") >= 0) { const p2 = getProduct(area, pid); r.routerOpt = p2?.connKind ? routerTextToKey(p2.connKind, get("Router")) : ""; }
      if (col("Fair Use EU Plus") >= 0) r.euPlus = YES(get("Fair Use EU Plus"));
      if (col("Fair Use World Select") >= 0) r.worldSelect = YES(get("Fair Use World Select"));
      if (col("ONKZ (nur Leitung)") >= 0) { const o = String(get("ONKZ (nur Leitung)") || "").trim(); if (o) { r.onkz = o; r.lineProvider = "dtag"; } }
      if (col("Lizenztyp") >= 0) r.sdwanLicense = LIC(get("Lizenztyp"));
      if (col("Redundanz") >= 0) r.redundanz = YES(get("Redundanz"));
      if ((area === "sdwan" || area === "sdwanmeraki") && r.term) g.sdwanTerm = r.term;
      g.rows.push(r); count++;
    }
  }
  const cleaned = groups.filter(g => g.area === "vpnconnect" ? true : g.rows.some(r => r.productId || r.custName));
  return { groups: cleaned, count };
};

// ===== Editierbarer Katalog: Basis bleibt unveränderlich, CATALOG wird aus Overrides aufgebaut =====
const PRICE_FIELDS = ["m24", "m36", "o24", "o36", "loPrice", "mgdPrice"];
const cloneCatalog = () => {
  const out = {};
  for (const [area, db] of Object.entries(BASE_CATALOG)) {
    out[area] = { ...db, products: (db.products || []).map(p => ({ ...p, desc: Array.isArray(p.desc) ? [...p.desc] : p.desc, price: p.price ? { ...p.price } : p.price, lic: p.lic ? Object.fromEntries(Object.entries(p.lic).map(([k, v]) => [k, { ...v }])) : p.lic })) };
  }
  return out;
};
let CATALOG = cloneCatalog();
// Wendet Produkt- und Options-Overrides auf die mutablen Module-Strukturen an.
const applyOverrides = (ov) => {
  const next = cloneCatalog();
  const po = (ov && ov.products) || {};
  for (const [pk, patch] of Object.entries(po)) {
    const sep = pk.indexOf("::"); if (sep < 0) continue;
    const area = pk.slice(0, sep), id = pk.slice(sep + 2);
    const prod = next[area]?.products.find(p => p.id === id);
    if (!prod) continue;
    if (patch.name != null && patch.name !== "") prod.name = patch.name;
    if (Array.isArray(patch.desc)) prod.desc = [...patch.desc];
    for (const f of PRICE_FIELDS) if (patch[f] != null && patch[f] !== "" && f in prod) prod[f] = Number(patch[f]);
    if (patch.price && prod.price) { for (const t of Object.keys(patch.price)) if (patch.price[t] != null && patch.price[t] !== "") prod.price[t] = Number(patch.price[t]); }
    if (patch.lic && prod.lic) { for (const lic of Object.keys(patch.lic)) { if (!prod.lic[lic]) continue; for (const t of Object.keys(patch.lic[lic] || {})) if (patch.lic[lic][t] != null && patch.lic[lic][t] !== "") prod.lic[lic][t] = Number(patch.lic[lic][t]); } }
  }
  // Eigene, im Editor neu angelegte Produkte einmischen (haben kein Original in BASE_CATALOG)
  for (const c of (ov && ov.custom) || []) {
    if (!next[c.area]) continue;
    next[c.area].products.push({ ...c });
  }
  CATALOG = next;
  // Options-Texte
  const oo = (ov && ov.opts) || {};
  OPT = Object.fromEntries(Object.entries(OPT_DEFAULTS).map(([k, v]) => [k, oo[k] != null ? cloneVal(oo[k]) : cloneVal(v)]));
  const op = (ov && ov.optPrices) || {};
  OPTPRICE = Object.fromEntries(Object.entries(OPTPRICE_DEFAULTS).map(([k, v]) => [k, (op[k] != null && op[k] !== "") ? Number(op[k]) : v]));
  const ot = (ov && ov.optTables) || {};
  OPTTABLE = Object.fromEntries(Object.entries(OPTTABLE_DEFAULTS).map(([k, v]) => [k, { ...v, ...(ot[k] || {}) }]));
  // Live-Strukturen, die Texte spiegeln
  RICHTUNGEN[0].fairText = optText("kanal_fest"); RICHTUNGEN[1].fairText = optText("kanal_mob");
  RICHTUNGEN[2].fairText = optText("kanal_eu"); RICHTUNGEN[3].fairText = optText("kanal_world");
  for (const a of ANTENNEN) a.label = optText("ant_" + a.key);
};

const CATALOG_STORE_KEY = "tef-catalog-overrides-v1";
const eur = (n) => new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(isFinite(n) ? n : 0);

let ridc = 1, gidc = 1;
const newRow = () => ({ id: ridc++, productId: "", qty: 1, standort: "", term: null, monthly: 0, oneoff: 0, discountFix: false, discountPct: 0, discountMode: "verrechnet", abloese: 0, teamsCoupling: false, backup: false, express: false, kanal: 0, kanalTarife: blankKanalTarife(), custName: "", custDesc: "", zweiterKanal: false, routerOpt: "", euPlus: false, worldSelect: false, basicRabatt: "", basicMode: "verrechnet", lineProvider: "dtag", onkz: "", lineRabatt: "0", mobileSpeed: "25", mobileRabatt: "0", antenne: "", businessClass: false, internetFlat: false, sdwanLicense: "sdwan", sdwanPrice: "", redundanz: false, pureRabatt: "0" });
const newGroup = () => ({ id: gidc++, area: "", rows: [newRow()], kombi: false, dpPct: 0, dpMode: "verrechnet", managed: false, customArea: "", showDesc: false, subGroups: [], standortName: "", sdwanTerm: 60 });
let cidc = 1;
const newVpnConn = (access = "asym") => ({ id: cidc++, access, bw: access === "asym" ? "100" : access === "link" ? "100" : access === "ipsec" ? "25" : "", onkz: "", provider: "dtag", rabatt: "0", manMonthly: "", manOneoff: "", internet: false, qos: false, antenne: "", dataPack: "1g" });
// Findet eine Gruppe per id – entweder eine Top-Level-Gruppe oder eine Unterbereich-Gruppe innerhalb eines
// Standortangebots – und wendet fn darauf an. Ermöglicht, dass Zeilen-/Bereichsfunktionen unverändert für
// beide Ebenen funktionieren, ohne sie zu duplizieren.
const mapGroupById = (gs, gid, fn) => gs.map(g => {
  if (g.id === gid) return fn(g);
  if (g.area === "standort" && g.subGroups) {
    const idx = g.subGroups.findIndex(sg => sg.id === gid);
    if (idx >= 0) { const subGroups = [...g.subGroups]; subGroups[idx] = fn(subGroups[idx]); return { ...g, subGroups }; }
  }
  return g;
});

const getProduct = (area, id) => (area && id && CATALOG[area]?.products.find(p => p.id === id)) || null;
const defaultTerm = (p) => { const lz = p?.laufzeiten; if (!lz?.length) return null; return lz.includes(36) ? 36 : lz[0]; };
const resolvePrice = (p, term) => {
  if (!p || p.priceOpen) return { monthly: 0, oneoff: 0 };
  const tier = term && term >= 36 ? "36" : "24", alt = tier === "36" ? "24" : "36";
  const pick = (b) => p[b + tier] != null ? p[b + tier] : p[b + alt] != null ? p[b + alt] : 0;
  return { monthly: pick("m"), oneoff: pick("o") };
};
const hasLizenz = (g) => (g.area === "dpbusiness" || g.area === "dpbasic") && g.rows.some(r => getProduct(g.area, r.productId)?.produkttyp === "lizenz");
const dpStats = (g) => {
  let monthlyList = 0, bezahl = 0, found = false;
  for (const r of g.rows) {
    const p = getProduct(g.area, r.productId);
    if (!p) continue;
    if (p.produkttyp === "basispaket" || p.produkttyp === "lizenz") {
      const qty = p.qty ? Math.max(1, Number(r.qty) || 1) : 1;
      monthlyList += resolvePrice(p, r.term).monthly * qty;
      if (!found) { found = true; bezahl = Math.max(0, (Number(r.term) || 0) - (Number(r.abloese) || 0)); }
    }
  }
  const umsatz = monthlyList * bezahl;
  return { monthlyList, bezahl, umsatz, maxPct: dpMaxPct(umsatz, g.kombi) };
};

// Baut alle Angebotszeilen eines SD-WAN-Bereichs (Produkte + Auto-Positionen). gid = Bereichs-ID für die Zeilen.
const buildSdwanLines = (g, gid) => {
  const out = [];
  let installDevices = 0, standorte = 0, einrTotal = 0;
  for (const r of g.rows) {
    const p = getProduct(g.area, r.productId);
    if (!p || !p.sdwan) continue;
    const qty = Math.max(1, Number(r.qty) || 1);
    const term = Number(r.term) || 60;
    const lic = p.lic ? (r.sdwanLicense || "sdwan") : null;
    const meta = [];
    const desc = [...(p.desc || [])];
    let monthly = 0, oneoff = 0, name = p.name;
    if (p.sdwanKind === "ad") {
      oneoff = optPrice("sdwan_ad_oneoff") * qty;
      meta.push("Anzahl: " + qty);
      if (r.standort) meta.push("Standort: " + r.standort);
      out.push({ type: "product", rowId: r.id, name, desc, meta, monthly: 0, oneoff, abloeseNote: null, rabattNote: null, gid, listMonthly: 0 });
      continue;
    }
    const each = sdwanRowMonthlyEach(p, r);
    // Listenpreis = Boden (Bestpreis der Laufzeit). Ersparnis entsteht nur, wenn ein niedrigerer Preis eingetragen wurde.
    const floor = sdwanFloor(p, lic, term);
    const floorEach = (p.sdwanKind === "fg" && p.redundanzAllowed && r.redundanz) ? 2 * floor + (sdwanRedTable(p.vendor)[lic]?.[term] ?? 0) : floor;
    const listEach = Math.max(each, floorEach);
    if (p.sdwanKind === "remote") {
      monthly = each * qty;
      oneoff = optPrice("sdwan_remote_setup") * qty;
      desc.push("je Paket mit 25 Lizenzen");
      meta.push("Laufzeit: " + term + " Monate");
      meta.push(`Anzahl: ${qty} × 25 = ${qty * 25} Lizenzen`);
    } else {
      monthly = each * qty;
      if (lic) name = `${p.name} – ${sdwanLicLabel(p.vendor, lic)}`;
      if (p.sdwanKind === "fg" && p.redundanzAllowed && r.redundanz) desc.push(`inkl. redundantem SD-WAN-Router (Laufzeit-Nachlass ${eur(sdwanRedTable(p.vendor)[lic]?.[term] ?? 0)})`);
      meta.push("Laufzeit: " + term + " Monate");
      if (qty > 1) meta.push("Menge: " + qty);
    }
    if (r.standort) meta.push("Standort: " + r.standort);
    out.push({ type: "product", rowId: r.id, name, desc, meta, monthly, oneoff, abloeseNote: null, rabattNote: null, gid, listMonthly: listEach * qty });

    // Aggregationen für Auto-Positionen
    const redFactor = (p.sdwanKind === "fg" && p.redundanzAllowed && r.redundanz) ? 2 : 1;
    if (p.sdwanKind === "fg") { standorte += qty; installDevices += qty * redFactor; einrTotal += qty * redFactor * sdwanEinr(p.einrKind, term); }
    else if (p.sdwanKind === "vm") { standorte += qty; einrTotal += qty * sdwanEinr(p.einrKind, term); }
    else if (p.sdwanKind === "fex") { installDevices += qty; einrTotal += qty * sdwanEinr(p.einrKind, term); }
    else if (p.sdwanKind === "ap" || p.sdwanKind === "switch") { installDevices += qty; einrTotal += qty * sdwanEinr(p.einrKind, term); }
    // remote & ad: weder Installation noch SD-WAN-Service-Einrichtung
  }
  if (!out.length) return { lines: [], standorte: 0 };
  // Auto-Positionen
  if (installDevices > 0) out.push({ type: "auto", name: "Vor-Ort-Installation durch Service-Techniker", desc: [`${installDevices} Gerät(e) × ${eur(optPrice("sdwan_install_per_device"))}`], monthly: 0, oneoff: optPrice("sdwan_install_per_device") * installDevices, abloeseNote: null, gid });
  if (einrTotal > 0) out.push({ type: "auto", name: "Einrichtung SD-WAN-Service", desc: ["Konfiguration & Aktivierung (laufzeitabhängig, je Lizenz)"], monthly: 0, oneoff: einrTotal, abloeseNote: null, gid });
  const proj = sdwanProjectCost(standorte);
  const projDesc = [`${standorte} Standort(e) im Bereich`];
  out.push({ type: "auto", name: "Projektkosten SD-WAN", desc: projDesc, monthly: 0, oneoff: proj, abloeseNote: null, gid });
  return { lines: out, standorte };
};

// ===== VPN Connect – Preis-Engine (Tabellen 1:1 aus der VPN-Connect-Preisliste; NICHT mit anderen Produkten mischen) =====
const T3 = (a, b, c) => ({ 12: a, 24: b, 36: c });
const VPN_TERMS = [12, 24, 36];
// Preistabellen editierbar über optTable("vpn_*") / optPrice("vpn_*") – siehe OPTTABLE_DEFAULTS/OPTPRICE_DEFAULTS
const VPN_LINK_REGION_KEY = { Metro: "metro", Regio: "regio", Country: "country" };
const VPN_DATAPACK_LABEL = { "1g": "1 GB", "5g": "5 GB", "10g": "10 GB", "25g": "25 GB", "flat": "Flatrate" };
const VPN_ANTENNE_LABEL = { in6: "Indoor Decke bis 6 m", in15: "Indoor Decke bis 15 m", outmast: "Outdoor Mast", in25: "Indoor Decke bis 2,5 m", in5: "Indoor Decke bis 5 m" };
const VPN_ANTENNE = Object.fromEntries(Object.entries(VPN_ANTENNE_LABEL).map(([k, label]) => [k, { label, get o() { return optPrice("vpn_antenne_" + k); } }]));
const VPN_ASYM_BW = ["1","2","6","16","25","50","100","175","250"];
const VPN_LINK_BW = ["2","4","10","20","50","100","200","500","1000"];
const VPN_IPSEC_BW = ["8","16","25","50"];
const VPN_ACCESS_LABEL = { asym: "Asymmetrisch", link: "Link Symmetrisch", mobile: "Mobilfunk", ipsec: "IPSec" };
const VPN_DESC_KEYS = ["asym", "link", "mobile", "ipsec"];
const DEFAULT_VPN_AREA_DESC = ["Hardware inklusive", "Gemanaged durch Telefónica"];
// Produktbereichsbeschreibungen (Standard) – aus Konfiguration fest eingearbeitet, im Editor überschreibbar
const DEFAULT_AREA_DESC = {
  allip: ["inkl. Einrichtung durch Techniker vor Ort", "inkl. Business Hardware", "Bis zu 5 feste IPv4 Adressen kostenfrei"],
  allippure: ["inkl. Installationsservice vor Ort", "inkl. Business-Hardware", "98,5% festgelegte Verfügbarkeit", "24 Stunden Entstörfrist"],
  office365: ["Inklusivleistungen gemäß Microsoft"],
  siptrunk: ["Cloud Sprachkanäle powered by NFON", "Erreichbar über jeden Internet-Zugang", "Inkl. Flatrate in alle dt. Mobilfunk- & Festnetze", "Preise für internationale Gespräche: https://www.o2business.de/content/dam/b2bchannels/de/pdfs-o2-business/teams-telefonie-preisliste.pdf"],
};
const resolveVpnDesc = (ov) => ({ area: (ov && "area" in ov) ? ov.area : DEFAULT_VPN_AREA_DESC, asym: ov?.asym || [], link: ov?.link || [], mobile: ov?.mobile || [], ipsec: ov?.ipsec || [] });

const vpnAccessBase = (conn, term) => {
  const t = Number(term);
  if (conn.access === "asym") return { monthly: optTableVal(Number(conn.bw) >= 175 ? "vpn_asym_high" : "vpn_asym_low", t), oneoff: optTableVal("vpn_asym_oneoff", t) };
  if (conn.access === "mobile") return { monthly: optTableVal("vpn_mobile_m", t), oneoff: optTableVal("vpn_mobile_oneoff", t) };
  if (conn.access === "ipsec") return { monthly: 0, oneoff: optTableVal("vpn_ipsec_oneoff", t) };
  if (conn.access === "link") {
    if (conn.provider === "andere" || conn.rabatt === "pricing") return { monthly: Number(conn.manMonthly) || 0, oneoff: Number(conn.manOneoff) || 0, region: onkzRegion(conn.onkz) };
    const region = onkzRegion(conn.onkz);
    const regionKey = VPN_LINK_REGION_KEY[region];
    const tableKey = regionKey ? `vpn_link_${regionKey}_${conn.bw}` : null;
    const m0 = tableKey ? optTableVal(tableKey, t) : 0;
    const pct = Number(conn.rabatt) || 0;
    return { monthly: m0 * (1 - pct / 100), oneoff: optTableVal("vpn_link_oneoff", t), region, listMonthly: m0 };
  }
  return { monthly: 0, oneoff: 0 };
};
const vpnService = (conn, term, role) => {
  const t = Number(term);
  if (conn.access === "mobile") return { svc: role === "backup" ? optTableVal("vpn_svc_backup", t) : optTableVal("vpn_datapack_" + (conn.dataPack || "1g"), t), mpls: optPrice("vpn_mpls") };
  if (conn.access === "asym" || conn.access === "ipsec") return { svc: optTableVal(Number(conn.bw) <= 16 ? "vpn_svc_low" : "vpn_svc_high", t), mpls: 0 };
  if (conn.access === "link") return { svc: optTableVal("vpn_svc_high", t), mpls: 0 };
  return { svc: 0, mpls: 0 };
};
const vpnQosTier = (conn) => { const n = Number(conn.bw); return n <= 100 ? "low" : n <= 500 ? "mid" : "high"; };
const vpnBackupOptions = (main) => {
  if (!main || !main.access) return [];
  if (main.access === "ipsec") return [{ access: "mobile" }];
  if (main.access === "link") {
    const bw = String(main.bw), ipsecAll = { access: "ipsec", bw: ["8","16","25","50"] }, link = (b) => ({ access: "link", bw: [b] });
    const asym = (arr) => ({ access: "asym", bw: arr });
    if (["1000","500","200"].includes(bw)) return [link(bw), ipsecAll];
    if (bw === "100" || bw === "50") return [link(bw), ipsecAll, asym(["1","2","6","16","25","50","100","175","250"]), { access: "mobile" }];
    if (bw === "20") return [link(bw), { access: "ipsec", bw: ["8","16"] }, asym(["1","2","6","16","25","50","100"]), { access: "mobile" }];
    if (bw === "10") return [link(bw), { access: "ipsec", bw: ["8"] }, asym(["1","2","6","16","25","50"]), { access: "mobile" }];
    if (bw === "4" || bw === "2") return [link(bw), { access: "ipsec", bw: ["8"] }, asym(["1","2","6","16"]), { access: "mobile" }];
  }
  return [];
};

// Baut die Angebotszeilen EINES VPN-Standorts (eine vpnconnect-Gruppe = ein Standort). gid = Gruppen-ID.
const buildVpnLines = (g, vpnDesc) => {
  const vd = vpnDesc || resolveVpnDesc(null);
  const term = Number(g.vpnTerm) || 36, gid = g.id, out = [];
  const bwLabel = (c) => c.access === "mobile" ? "" : ` bis ${c.bw} Mbit/s`;
  const connLines = (c, role) => {
    if (!c || !c.access) return;
    const roleLabel = role === "backup" ? "Backup" : "Hauptanschluss";
    const base = vpnAccessBase(c, term);
    const svc = vpnService(c, term, role);
    const meta = [];
    if (c.access === "link") { meta.push("Region: " + (base.region || onkzRegion(c.onkz))); if (c.onkz) meta.push("ONKZ: " + c.onkz); if (Number(c.rabatt) > 0) meta.push(`Rabatt ${c.rabatt} %`); if (c.rabatt === "pricing" || c.provider === "andere") meta.push("Preis manuell"); }
    // Access-Zeile (inkl. evtl. Rabatt bereits eingerechnet)
    out.push({ type: "product", name: `${roleLabel}: ${VPN_ACCESS_LABEL[c.access]}${bwLabel(c)}`, desc: [...(vd[c.access] || [])], meta, monthly: base.monthly, oneoff: base.oneoff, gid, listMonthly: base.monthly });
    // VPN-Service (immer)
    if (c.access === "mobile") {
      out.push({ type: "product", name: `${roleLabel}: O2 Data-Pack ${role === "backup" ? "Backup" : VPN_DATAPACK_LABEL[c.dataPack] || ""}`.trim(), desc: [], meta: [], monthly: svc.svc, oneoff: 0, gid, listMonthly: svc.svc });
      out.push({ type: "product", name: `${roleLabel}: MPLS Link Option`, desc: [], meta: [], monthly: svc.mpls, oneoff: 0, gid, listMonthly: svc.mpls });
    } else {
      out.push({ type: "product", name: `${roleLabel}: VPN-Service`, desc: [], meta: [], monthly: svc.svc, oneoff: 0, gid, listMonthly: svc.svc });
    }
    // Optionen je Anschluss
    if (c.access === "link" && c.internet) out.push({ type: "product", name: `${roleLabel}: Option Internet`, desc: [], meta: [], monthly: optTableVal("vpn_internet_m", term), oneoff: optPrice("vpn_internet_o"), gid, listMonthly: optTableVal("vpn_internet_m", term) });
    if ((c.access === "asym" || c.access === "link") && c.qos) { const q = optTableVal("vpn_qos_" + vpnQosTier(c), term); out.push({ type: "product", name: `${roleLabel}: Quality of Service`, desc: [], meta: [], monthly: q, oneoff: 0, gid, listMonthly: q }); }
    if (c.access === "mobile" && c.antenne && VPN_ANTENNE[c.antenne]) out.push({ type: "product", name: `${roleLabel}: Antenne ${VPN_ANTENNE[c.antenne].label}`, desc: [], meta: [], monthly: 0, oneoff: VPN_ANTENNE[c.antenne].o, gid, listMonthly: 0 });
  };

  out.push({ type: "cluster", name: (g.vpnName || "").trim() || "VPN-Standort", gid, desc: (vd.area && vd.area.length) ? vd.area : undefined });
  connLines(g.main, "main");
  if (g.backup && g.backup.access) connLines(g.backup, "backup");
  // Standort-Optionen
  const anyQos = (g.main?.qos && (g.main.access === "asym" || g.main.access === "link")) || (g.backup?.qos && (g.backup.access === "asym" || g.backup.access === "link"));
  if (g.backup && g.backup.access && g.backupPlus) out.push({ type: "product", name: "Backup-Variante ++ (Redundanz über zwei Router)", desc: [], meta: [], monthly: Number(g.bpM) || 0, oneoff: Number(g.bpO) || 0, gid, listMonthly: Number(g.bpM) || 0 });
  if (g.netflow) out.push({ type: "product", name: "Option NetFlow", desc: [], meta: [], monthly: 0, oneoff: optPrice("vpn_netflow_o"), gid, listMonthly: 0 });
  if (g.citrix && anyQos) out.push({ type: "product", name: "Option Citrix Priorisierung", desc: [], meta: [], monthly: 0, oneoff: optPrice("vpn_citrix_o"), gid, listMonthly: 0 });
  if (g.advHw) out.push({ type: "product", name: "Option Advanced Hardware", desc: [], meta: [], monthly: Number(g.advHwM) || 0, oneoff: Number(g.advHwO) || 0, gid, listMonthly: Number(g.advHwM) || 0 });
  return out;
};

// ===== Vorher/Nachher-Vergleich: vergleicht eine zuvor gesicherte Baseline mit dem aktuellen Stand einer Gruppe =====
const DIFF_FIELDS = [
  { key: "qty", label: "Menge" },
  { key: "term", label: "Laufzeit", fmt: v => v ? `${v} Mon.` : "—" },
  { key: "monthly", label: "Preis/Monat", fmt: v => eur(Number(v) || 0) },
  { key: "oneoff", label: "Einmalig", fmt: v => eur(Number(v) || 0) },
  { key: "standort", label: "Standort", fmt: v => v || "—" },
  { key: "redundanz", label: "Redundanz", fmt: v => v ? "an" : "aus" },
  { key: "sdwanLicense", label: "Lizenztyp" },
  { key: "sdwanPrice", label: "SD-WAN-Preis", fmt: v => v === "" || v == null ? "Bestpreis" : eur(Number(v)) },
  { key: "lineRabatt", label: "Rabatt", fmt: v => v === "pricing" ? "Pricing" : `${v || 0} %` },
  { key: "mobileRabatt", label: "Rabatt", fmt: v => v === "pricing" ? "Pricing" : `${v || 0} %` },
  { key: "pureRabatt", label: "Rabatt", fmt: v => `${v || 0} %` },
  { key: "basicRabatt", label: "Rabatt", fmt: v => v === "pricing" ? "Pricing" : (v ? `${v} %` : "kein Rabatt") },
  { key: "mobileSpeed", label: "Geschwindigkeit" },
  { key: "antenne", label: "Antenne", fmt: v => v || "keine" },
];
const diffRows = (baseRows, curRows, area) => {
  const out = { added: [], removed: [], changed: [] };
  const baseMap = new Map((baseRows || []).map(r => [r.id, r]));
  const curMap = new Map((curRows || []).map(r => [r.id, r]));
  const nameOf = (r) => getProduct(area, r.productId)?.name || r.custName || "—";
  for (const r of (curRows || [])) if (!baseMap.has(r.id)) out.added.push({ name: nameOf(r) });
  for (const r of (baseRows || [])) if (!curMap.has(r.id)) out.removed.push({ name: nameOf(r) });
  for (const r of (curRows || [])) {
    const b = baseMap.get(r.id); if (!b) continue;
    if (b.productId !== r.productId) { out.changed.push({ name: `${nameOf(b)} → ${nameOf(r)}`, changes: [] }); continue; }
    const changes = [];
    for (const f of DIFF_FIELDS) {
      if (!(f.key in r)) continue;
      const bv = b[f.key], cv = r[f.key];
      if (bv === cv || ((bv == null || bv === "") && (cv == null || cv === ""))) continue;
      const fmt = f.fmt || (v => (v == null || v === "" ? "—" : String(v)));
      changes.push(`${f.label} ${fmt(bv)} → ${fmt(cv)}`);
    }
    if (changes.length) out.changed.push({ name: nameOf(r), changes });
  }
  return out;
};
const buildGroupDiff = (baseline, current) => {
  if (!baseline || !current) return null;
  if (current.area === "standort") {
    const baseSubs = new Map((baseline.subGroups || []).map(sg => [sg.id, sg]));
    const curSubIds = new Set((current.subGroups || []).map(sg => sg.id));
    const subDiffs = [];
    for (const sg of (current.subGroups || [])) {
      const label = CATALOG[sg.area]?.label || sg.area || "Unterbereich";
      const bsg = baseSubs.get(sg.id);
      if (!bsg) { subDiffs.push({ label, addedSub: true }); continue; }
      const d = diffRows(bsg.rows, sg.rows, sg.area);
      if (d.added.length || d.removed.length || d.changed.length) subDiffs.push({ label, ...d });
    }
    for (const sg of (baseline.subGroups || [])) if (!curSubIds.has(sg.id)) subDiffs.push({ label: CATALOG[sg.area]?.label || sg.area, removedSub: true });
    return { kind: "standort", subDiffs };
  }
  if (current.area === "vpnconnect") {
    const changes = [];
    const cmpConn = (b, c, role) => {
      if (!b?.access && !c?.access) return;
      if (!b?.access && c?.access) { changes.push(`${role} hinzugefügt (${VPN_ACCESS_LABEL[c.access] || c.access})`); return; }
      if (b?.access && !c?.access) { changes.push(`${role} entfernt`); return; }
      if (b.access !== c.access) changes.push(`${role}: ${VPN_ACCESS_LABEL[b.access] || b.access} → ${VPN_ACCESS_LABEL[c.access] || c.access}`);
      else if (b.bw !== c.bw) changes.push(`${role}: Bandbreite ${b.bw} → ${c.bw}`);
      if (b.rabatt !== c.rabatt) changes.push(`${role}: Rabatt ${b.rabatt} → ${c.rabatt}`);
    };
    cmpConn(baseline.main, current.main, "Hauptanschluss");
    cmpConn(baseline.backup, current.backup, "Backup");
    if (baseline.vpnTerm !== current.vpnTerm) changes.push(`Laufzeit ${baseline.vpnTerm} → ${current.vpnTerm} Monate`);
    for (const [k, lbl] of [["citrix", "Citrix"], ["netflow", "NetFlow"], ["advHw", "Advanced Hardware"], ["backupPlus", "Backup ++"]]) {
      if (!!baseline[k] !== !!current[k]) changes.push(`${lbl} ${baseline[k] ? "an" : "aus"} → ${current[k] ? "an" : "aus"}`);
    }
    return { kind: "vpn", changes };
  }
  return { kind: "rows", ...diffRows(baseline.rows, current.rows, current.area) };
};

// Excel-Import-Vorlage (eingebettet als Base64, damit der Download direkt aus dem Artefakt funktioniert)
const TEMPLATE_XLSX_B64 = "UEsDBBQAAAAIAC6b11xGx01IlQAAAM0AAAAQAAAAZG9jUHJvcHMvYXBwLnhtbE3PTQvCMAwG4L9SdreZih6kDkQ9ip68zy51hbYpbYT67+0EP255ecgboi6JIia2mEXxLuRtMzLHDUDWI/o+y8qhiqHke64x3YGMsRoPpB8eA8OibdeAhTEMOMzit7Dp1C5GZ3XPlkJ3sjpRJsPiWDQ6sScfq9wcChDneiU+ixNLOZcrBf+LU8sVU57mym/8ZAW/B7oXUEsDBBQAAAAIAC6b11xLHR337gAAACsCAAARAAAAZG9jUHJvcHMvY29yZS54bWzNks9KxDAQh19Fcm+nTbFo6OaieFIQXFC8hWR2N9j8IRlp9+1t624X0QfwmJlfvvkGptNR6JDwOYWIiSzmq9H1PgsdN+xAFAVA1gd0KpdTwk/NXUhO0fRMe4hKf6g9Aq+qFhySMooUzMAirkQmO6OFTqgopBPe6BUfP1O/wIwG7NGhpwx1WQOT88R4HPsOLoAZRphc/i6gWYlL9U/s0gF2So7ZrqlhGMqhWXLTDjW8PT2+LOsW1mdSXuP0K1tBx4gbdp782tzdbx+Y5BVvi6oteLOtbwW/FvzmfXb94XcRdsHYnf3HxmdB2cGvu5BfUEsDBBQAAAAIAC6b11yZXJwjEAYAAJwnAAATAAAAeGwvdGhlbWUvdGhlbWUxLnhtbO1aW3PaOBR+76/QeGf2bQvGNoG2tBNzaXbbtJmE7U4fhRFYjWx5ZJGEf79HNhDLlg3tkk26mzwELOn7zkVH5+g4efPuLmLohoiU8nhg2S/b1ru3L97gVzIkEUEwGaev8MAKpUxetVppAMM4fckTEsPcgosIS3gUy9Zc4FsaLyPW6rTb3VaEaWyhGEdkYH1eLGhA0FRRWm9fILTlHzP4FctUjWWjARNXQSa5iLTy+WzF/NrePmXP6TodMoFuMBtYIH/Ob6fkTlqI4VTCxMBqZz9Wa8fR0kiAgsl9lAW6Sfaj0xUIMg07Op1YznZ89sTtn4zK2nQ0bRrg4/F4OLbL0otwHATgUbuewp30bL+kQQm0o2nQZNj22q6RpqqNU0/T933f65tonAqNW0/Ta3fd046Jxq3QeA2+8U+Hw66JxqvQdOtpJif9rmuk6RZoQkbj63oSFbXlQNMgAFhwdtbM0gOWXin6dZQa2R273UFc8FjuOYkR/sbFBNZp0hmWNEZynZAFDgA3xNFMUHyvQbaK4MKS0lyQ1s8ptVAaCJrIgfVHgiHF3K/99Ze7yaQzep19Os5rlH9pqwGn7bubz5P8c+jkn6eT101CznC8LAnx+yNbYYcnbjsTcjocZ0J8z/b2kaUlMs/v+QrrTjxnH1aWsF3Pz+SejHIju932WH32T0duI9epwLMi15RGJEWfyC265BE4tUkNMhM/CJ2GmGpQHAKkCTGWoYb4tMasEeATfbe+CMjfjYj3q2+aPVehWEnahPgQRhrinHPmc9Fs+welRtH2Vbzco5dYFQGXGN80qjUsxdZ4lcDxrZw8HRMSzZQLBkGGlyQmEqk5fk1IE/4rpdr+nNNA8JQvJPpKkY9psyOndCbN6DMawUavG3WHaNI8ev4F+Zw1ChyRGx0CZxuzRiGEabvwHq8kjpqtwhErQj5iGTYacrUWgbZxqYRgWhLG0XhO0rQR/FmsNZM+YMjszZF1ztaRDhGSXjdCPmLOi5ARvx6GOEqa7aJxWAT9nl7DScHogstm/bh+htUzbCyO90fUF0rkDyanP+kyNAejmlkJvYRWap+qhzQ+qB4yCgXxuR4+5Xp4CjeWxrxQroJ7Af/R2jfCq/iCwDl/Ln3Ppe+59D2h0rc3I31nwdOLW95GblvE+64x2tc0LihjV3LNyMdUr5Mp2DmfwOz9aD6e8e362SSEr5pZLSMWkEuBs0EkuPyLyvAqxAnoZFslCctU02U3ihKeQhtu6VP1SpXX5a+5KLg8W+Tpr6F0PizP+Txf57TNCzNDt3JL6raUvrUmOEr0scxwTh7LDDtnPJIdtnegHTX79l125COlMFOXQ7gaQr4Dbbqd3Do4npiRuQrTUpBvw/npxXga4jnZBLl9mFdt59jR0fvnwVGwo+88lh3HiPKiIe6hhpjPw0OHeXtfmGeVxlA0FG1srCQsRrdguNfxLBTgZGAtoAeDr1EC8lJVYDFbxgMrkKJ8TIxF6HDnl1xf49GS49umZbVuryl3GW0iUjnCaZgTZ6vK3mWxwVUdz1Vb8rC+aj20FU7P/lmtyJ8MEU4WCxJIY5QXpkqi8xlTvucrScRVOL9FM7YSlxi84+bHcU5TuBJ2tg8CMrm7Oal6ZTFnpvLfLQwJLFuIWRLiTV3t1eebnK56Inb6l3fBYPL9cMlHD+U751/0XUOufvbd4/pukztITJx5xREBdEUCI5UcBhYXMuRQ7pKQBhMBzZTJRPACgmSmHICY+gu98gy5KRXOrT45f0Usg4ZOXtIlEhSKsAwFIRdy4+/vk2p3jNf6LIFthFQyZNUXykOJwT0zckPYVCXzrtomC4Xb4lTNuxq+JmBLw3punS0n/9te1D20Fz1G86OZ4B6zh3OberjCRaz/WNYe+TLfOXDbOt4DXuYTLEOkfsF9ioqAEativrqvT/klnDu0e/GBIJv81tuk9t3gDHzUq1qlZCsRP0sHfB+SBmOMW/Q0X48UYq2msa3G2jEMeYBY8wyhZjjfh0WaGjPVi6w5jQpvQdVA5T/b1A1o9g00HJEFXjGZtjaj5E4KPNz+7w2wwsSO4e2LvwFQSwMEFAAAAAgALpvXXB6fLybjAwAAHwkAABgAAAB4bC93b3Jrc2hlZXRzL3NoZWV0MS54bWyNVm1z2jgQ/is7dKbXm0kxNiGhQJghIbnj0lCm9JKZfhP22tYhSz5JjtP8+q5sMFw6eO5DgrTS7vPsqzwpld6aFNHCSyakueqk1uYjzzNhihkzXZWjpJNY6YxZ2urEM7lGFlVKmfCCXu/CyxiXnemkkq30dKIKK7jElQZTZBnTP65RqPKq43f2gq88Sa0TeNNJzhJco/07X2naeY2ViGcoDVcSNMZXnZk/mvl9p1DdeORYmqM1mFSVf2gefSZkcqTXAefcRqmtO15ETkT3UWBonVFGP894g0I428Ts3x1Mp2HhFI/Xe7y7Khzk3oYZvFHiiUc2veoMOxBhzAphv6ryT9y5OHD2QiVM9R/K+q5PcggLY1W20yYKGZf1L3vZxcarFSvUObNsOtGqBO1OyZxbVNQrLQLj0sV9bTWdctKz02+3d3DPxLYQzCoN798NA98fwyLLlbYfH5UWFP2JZwnKKXgh/RFEgxM0OEGFE/yC84tKv1HpVyr9E9TWCuJCbl0yOGr7mxm18DhvjJ6f4FEb9bvwF0Zo4BvbUGpRbshxC8ZiaiF+/y4YBGMNSFoSVlpFxdZuUCMPU/gwEwIWK7gujCshcwbr+cen2fIMHuYPVeT6vfHv3RaSg4bkoJVk0IV1zoRFmMEVrC2TEWWjC7d0D74jFwj/YCMHD1bKcBenNvCLBvyiFbzfhYWkQpVQR2OjeUKbmlATlDP4zIr4FTmtHlAmCIWM4EvuWNCtHDXMtcojVVInFaYkW8FwnFLA2zheNhwvWzmed2FZ6DoUktrCOowEGxSL2Z4olKidM7yqaFdITZELpMzujeyvJVJpd6uN5rChOWylOegCNSVyAj9uskJarFstCMb7LOKBIcrqrDeGVIWpYHUuas4RMzCjcG8UecZ1RI7TiGCW00QG1FTHQrRy/9Rw/9TK/cJxl4ysulzepBjSrHwBiRt01UEOUcJRSpa5qEnXMk1Fsh3BIitJgELCBxWRlddC1zUVblv7xO8dplfv/44V/2jk1TPv/IRrT9TNlicjmHM0uEsRSpvW5SMsbLAsjLFwf7tY3lIlITd1gdeCa8pRmMpCJm8ys+sY1JJnmQXn8yHvrR4f5qh/apDW5N8MIXDP2YgIc2IVMR3Dl+X9dze4S5YKlxWraYLLseuHZ06MvDsSptS4xL3Kr0tmPe+pgJI6BvK1vVH9wxD3+618H1dLuFFS0qsKnEKaUoHDsxupMib0QzfO0TIu1H6CvKX23xYyaE8x9I4eQ/fp8MB0wqWhbo+JYK97SbNX129vvbEqr95HqlhqpWqZ0hcManeBzmOl7H7jntzmm2j6E1BLAwQUAAAACAAum9dcRqtx+OgOAADfXgAAGAAAAHhsL3dvcmtzaGVldHMvc2hlZXQyLnhtbK1cf3PaOBP+Kprc3E1ubtJgA8a9ppkBAjFtaLiQazv9J2NABF/8g9c2SdpP/0o2WDbZleSEmZseEPbRStrn0WolfPYUxQ/JitKUPAd+mHw8WqXp+u/T02S+ooGbvIvWNGR/WUZx4KbsbXx/mqxj6i4yo8A/NRsN6zRwvfDo/Cz7bBKfn0Wb1PdCOolJsgkCN/7Zo3709PHIONp9cOPdr1L+wen52dq9p1Oa/ruexOzdaYGy8AIaJl4UkpguPx51jb9vbYsbZN/46tGnpPSa8K7MouiBvxktPh41uEfUp/OUQ7jsf4+0T32fIzE//rcFPSra5Ibl1zv0YdZ51pmZm9B+5H/zFunq45F9RBZ06W789CZ6cui2Q+3CwQs3dc/P4uiJxLyj52dz/iJr++NR+4iwb3shH6ZpGrO/eqy59HwdR4s71/e99dlpypzhn57Ot7Y9Xdv1JqaAfV/XnnXUmwMAFxoAi/Vsk7A/JAkAMNADQJofalgn3jqNN+EDYH6pYR4sAsDS0bCMlktvTptWG7Af6Ti+eHJDwPaTrm1AY/fBAxA+6yIkjJkpo3rKvpECQFe1gFB/xnKYlMbBXZIuAMsvUksuYX8na3dOPx4xjUpo/EiPzqN1evdAvZDcuDM3Te+MBvn9zuT/NBsEaONa7h2HCxnc3X8uYDxRG/OWKz78DuD8o8aZXpx8634hx9EqpGRK55vYS3/+edeD3LpRwxkmGUehm9I7s7V71bTYKwBuqobrJj+DgKaxxxaSuysvfCDT/AMA7lYNxyeQxnfjaOb5S8buAr8Kd8rUtpBcs5BcE0EuO0lmXkJYf2deegopV08bxGzjKH0UxfdPRhPS48rHEBqQ+GK23IiF/QNbxHtb6SXHbXLl/aIh++9PSIcPiDXEsKajyckt1+Jtt1gU+O/IBd2kbKh8N1ycDH0X0plLDHF8Mc6xII1Gjbx5HCXRMiVMnEWvMJwRhjPcCiMZXpJWgxxPyR+/2aZhfCAPfhaeZJqyTrFvQaP0CXUvk0nyo0mOvwvIW5a48ISGxhDYZ00fd2g7rZhyXSb9KAxZVgQBX6m9rI05xjALyYGUHjMqSTkk3pgZl2xIr7HvI7KMhjooxpASv2YwpjraAwmrbBCpTDubhXY2dWXPkImnNkq7IRFPFKUsnkYDFE/Mtix4mb1KOQ8FNMSAhGxOYhp4m6CGcGKYhXCSsRuy7RaUXDmosURAyWDQhUQUwyoLlMVEdFzoSeClqU9jlYyiTuYCNf5udepK82cdZ0tqmnd8x3Jy3L2d/KmjrTqu11dXDLVI4yB1xYwMWPeuse/DSXBN9H9klNofaEhTXzMEU8yonKxiuorO5C4/lUhrq5DW1iEyyp42ChNGibaiMGVthfNSzPTHJvnjN9O0P6S/fG++ols5LBQEUtfaWFipAAOiQ/cZUk7s+0I5b2PXC73wHpJO1BqWzkyN3BhS4REGVRYj+zXKifq4k5/37fqon3XcLWlnd/HohnO6KLH6X035VPpfks995VDLKIae7YExGcWMTFjoJjW//w9KSngUIW18Tb+m6FDD+paLoo4QSXSxXegiVgiolSz2tFGMjmzDjsJUck4L0kXMNMsUWVoSMyFLN6CgDN5gO8RsL2l0H7tL3nHGaqNB45ObzTLcsOGIw5kfzaFpdTA0hbRhiSEGV9YLvlIdXxVkvo8jrv3NDyqFQ13dKYTZfhXwZx2nrddst5UelzQNIL1a1rAGWrZE1jAjpFg5qfn9m9c4NcWMRhM2HhJdsQpdsQ6SKfW0YUzpZhaFKQtL3535lPsDJl4YRP+KAYQRmc5jSpGUZfAW4yFmXJWYpqbEYGiIxGy3xZC6YEhlopqvVBfUyx1XW1xdvgvkhK5ifY3Rcd16+yZU2QuWBdbfhGKozGFcZjCjCVvc4Li7qdlORQg6hRB0DpIa9LRghre3To4lVQMU66UatGExwBDGU3JL3SAh12t+EA5JwetNh5hpVQjamkKAOiIVAizVwNCqW5M3cPYT6u+WTJemwbZUu6SZk+lk8JzScIGU9XU8tg6ymVJ5XpaBOpupCuPsgnH2QdbMnhZMwbimtOCBYr1kXBNmHIbQY7tmL01S6vssQ88SbaIO/sFh4YYY3P4uQJOaGFyVmpNVxA9AJOUNDKca4m8hJerpNrSnhtk4sa8mEAF1vLNfk+irnCrzrWaiX6Hc+4Jy7+tnu0w+Li8h93taYAXxLCnxUCww8YWIhyG8YIom8w6MN8TwhOkm+LUBC4mYqaBZviIPkoSGqef60BCPMJQijL96cbpxfTJ25yv2AQ/rr+OGAdEJ9ahMJ7MF0knpR5VO9XPaSuwbjSL42cvaCw4e/XpoItNrSOMfR4NyPSj+UYQej8hHGvt5rTy8pzFSX3o7xBCFGIUzPiZ0FrqrgK0GxXlmfo45ClPKVqDsVuZ+WDfJceA+vyMGi4qxF25S+OzUQdsePM9XLnOaXGd/IscT3w2JAYGMUBAFTUyIJihYhSctZN1Ru1JlyqsyvypZSjdSDf2Cq5IsfRytHN5Db5ZnH3CAYxi76Hxg0dk2P8S/sPB+I4CDAsABZsIBhoEoAqwFBhgGVgqwNibEak+2Zdc6mU01nkwRT9pX5Yx3DZ2I0ro0l0eUhQQUBsGPA8kXOqNhtsxDdYrBW4wd1HjAZDBes7GgJNshchL/Jfg8aIIRpbz4BUeUDUaU4rJXHlGoZOncQSuH1FtXd3EnydC/lCRWd748g9Gldasojy5kI4hC9G/Gu66Rib+Bb8S/wdhBjVXRBd9SV16EAaMLPP36hIIV0WVK9ErnSk45uA6wIIqLGYb2nQpRYsDjS+tmxXY9RPI9DKLv+j7pUz7XvP7ppVHssY3K8X+UdO/Zx+AFtoOiOSja/kLZjVnIPHLAZRSTvb+C0ai+WjD4zoKgVontE4paCcuXoleNFXFYbWifM1vKWLlAwfq3I5LsTlaX29yFfPPCRfQEq8qhkBwUSXOCp/xnGNC1/xGKXJngtlFzglVnqtPme2DDWp1fcWho6J8alpMZdIbRY7fyvHDp15rlQ6I5KNr+TH/2ogQqfIxQBDGjU8ZYOBtRnUxl89aypfMmzngMrMJ9xXtg4vvzC9QSpg5bCsGpOQSKg6LsT8kkjtL8l47gvKgPFrJ5IZPrATg3quOCG9OWTYs4CDCwQmg2LS3ZtGCWfEB3J0FapDkQkIMCVWvi3fU6ySRRcu9xhGJVZsiEd4aq4vJNqyWbHVEzNrCCXTY7hqSqdYGalkdVW9cOCOagYMg0idQZnCh1TTObKJRKqnrqTduSTJYpapwmVjPKFU42WagpH9/d4Yqf3bCFpudt5g5qXp2QfrT2/Cglx8bzD3eVVeD/ItkvV7jaPXr8l+Fg2QXFr0xRS6ZYpqiOmVjpJBtm2XHlBWpaDuM8PyLHA3GpD/7F3gHBHBQMnoITEvDbFfzmNTjg6upSNuAvOVEddFFCMrHiwlaIpKOO2X7JRyoixzRJI359IOJbHni0DwDioCDVUYZLPahxeVBb+ytCdUBFycSU/NyBZleiJAOK2fJKfhxmlXzXJxP+2ydwLN9m76D2+8PIL4KQ4zDKDxNgbVAXFrJRVYWqKBaY2IZyqw+SkXVQ272egdUa1LjSFbnMiX2siW2bcsa9k55pOaj1fkd0pki9Lcz6pZoisYcz0a0F31ZSlnyDPw51cLvqpYdN/MgfQDLdeOBFtxGKI3rUnRCzaci6I7Y2JpaNf3MfWcRlU6W8xuqgKNXOXdBltteW9E69uWC9a8l7J3YIJpbN7vVOelPIQVGqvRtCR94j1LjSI1veI5FVm1jKt9cjhVbopbFDQ4di6A0FGrB9JPk3YRP+dfJF0r+mSESbWL61H4/kEu0dirHXO3CpRI3L3flx+6Ur64/I+JpYOpP1x9AlGAqz3yWNCUOxyj0cTGS1kabIrppYarHfQTnHUBjRwUI9VPtfFKsbztjWjif/DxuGFCbUS717htjNHuh0cuHFdJ5G8c+TUcjLKr9kQ1D6WTuWB+wPgZyUKIxiCEj2cJo4kTkrUowmttK/CEgZw9S5RsXVvh9tFtmuWOZkkUA4TXUKgOC/UOFqE5ZoQr0eV5oYhIt15IWpooGOaEC9JiINmLIGbNGAelmCG5hIe/BeNKBeJZAGZD1oFWLvtNRCDTcAnQNUGzFEI2rprDQyWvDLcOlPGbop0Gvq1g5dHkatpmigpipcZ48OAxOSahMt0URNNpebkM60IHSrJqFLTcC3rqoNCVq3atL6RUP7t2+qDQl6t9T0HoRp7JLRhWKUBKNbakYXmFIStwSJW2oSC0yZn23B27aatzkmkOdXMQVN22qasi3+hu0fhczE3iPbct3T7YNIAkYtWWOCtW01a7eN5cEnQxVUbaupWkFlkfjIgk8GLkjaVpO0Cj5liQwbFNJdLE6uQ1kjgqZtNU3LjUjDRXCyrebkFnWbgTrUX8ugBQvbahZuoZWhKGjYVtNwe3F6/7dCVURBwrbuVeybKAqyOqAE1xJEtNREzHGnKzemi23AyZIwSzDSwhhZ0kz+XFJyFeW/n3rkS9v+oWEVXVDQwihYQh/IiGcJ4lkY8SpY0q1RFVrQzsJoV4ZuyrAEuyyMXRWsGm4KilkYxcrQbRmW4JSFcaqCVcNNwSwLY1YZ+pmvHENeS9tKmAxbcMzCOFZOV2Qz1RG86mC8qmDpD0FHsKqDsiqkF2xBo1laMiuel6jOfjqCVB2UVBJwacbTESzrYCzjKPy5WG644NL1X37JkCFLR1twrINxDAWWRXJHEK6DEW4HrFzdO4JiHfQnxtETLyHwzSf4a/IqoOBZB+PZFnCTRoHLVsM1T1V9Zr729x/qW4UWPOtgPAOgN7zco4IWNOtgNMuhe+L8cMLe8lqSBNcWlLMxyqG46szGFrSzMdqV4GWLri1IZqMPbuSL7CRLTTVoawtm2ejj+/YRpVy1BaVsjFIlRHd3zTUosucsDqSEsAW7bIxdX/lxuppbtuCWjXGrBAUmm6elp6PzR7+P3fjeCxPi0yXDa7zjChDnT1PP36TROnt0/CxKGQeylyvqst0g/wL7+zJiCej2DX+Ye/FM+/P/A1BLAwQUAAAACAAum9dc0ZaDvFAYAABxwgAAGAAAAHhsL3dvcmtzaGVldHMvc2hlZXQzLnhtbN3dX3Mb13mA8a/Csp5Oe5GI5/97FEkzsWU7mdqxJ07Smd7BEiRxRBE0CJZJPn1BmuE+SRw/O57epDeSCOwBsHhIivvj2bPPbnf799fvttvDyR8/XFxePz99dzhcPX3y5PrVu+2HzfXPd1fby+M9b3b7D5vD8cP92yfXV/vt5vX9oA8XT/LZWX/yYXN+efri2f1tX+9fPNvdHC7OL7df70+ubz582Oz/9PH2Ynf7/DSd/uWG356/fXe4u+HJi2dXm7fbb7aH3199vT9+9OTxUV6ff9heXp/vLk/22zfPT3+Znv4qnZ3dD7nf5g/n29tr/Pvk+t3u9vP9+esvjs993JWz05O73ft2t3t/d/evX9/ddPd0l9uTP31zdXF+/wJODrurL7ZvDp9sLy6OT5JPTzavDuf/s/36uNnz0293h8Puw939x5d+2ByON73Z7/68vbx/FduL7XHb4wu8+ruNv3+Qhwe92+/vHnbi9HEf714U//2Xffns/s0+vnnfbq63n+wu/uv89eHd89M4PXm9fbO5uTj8dnf7q+3DG9juHu/V7uL6/s+T2++3LcfdeHVzfXw1D4OPr+DD+eX3f2/++PDGrxmQHwbkvxmQ+j8YUB4GlLUD6sOAunZAexjQ1g7oDwP62gHjYcBY+y7Fw4C4r/t9jvuWLzeHzYtn+93tyf5+67tm+fFRHisePy1f3W1x/5ly/4KPt55f3n0RfXPYH+89Pz7g4cU3h83l693+8OzJ4fg0d7c9efUw8uMfH/n1fvf65v0PDfzkxwd+sbl58+ft+Q+NfPnjI7/cXr7d/sCwT3982G83324OP/R0n/34uF9fHrb7y+3hZ59dbA7741fqDzzE5/KKd9+eX7y5uXz/s483r97fXP3AI/zqxx/hq9/853+f/Pvlzf7ki+N7dnP59j/++jGeHD8THj8d8mP1fP+g4/5B776R/t2m5XHTYpvWx02rbdoeN222aX/ctNum43HTYZvG46Zhm87HTadtms6WL6cz3Rhfe0k3XpIlbZaWaEmrpSVb0m5pCZe0XFrSJW2XlnhJ66UlX9J+aQmYtGBeCmYtmJeCWQtmfNFpwbwUzFowLwWzFsxLwawF81Iwa8G8FMxaMC8FsxbMS8GsBctSsGjBshQsWrAsBYt/38Q3Ti1YloJFC5alYNGCZSlYtGBZChYtWJaCRQuWpWDRgnUpWLVgXQpWLViXglUL1qVg9f/78J+fFqxLwaoF61KwasG6FKxasC4FqxasS8GqBdtSsGnBthRsWrAtBZsWbEvBpgXbUrD5zy/4AUYLtqVg04JtKdi0YFsKNi3YloJNC/alYNeCfSnYtWBfCnYt2JeCXQv2pWDXgn0p2P1nUPwQqgX7UrBrwb4U7FqwLwW7FhxLwaEFx1JwaMGxFBxacCwFhxYcS8GhBcdScGjBsRQcfhyBAwktOJaCQwuOpeDQgrEUDC0YS8HQgrEUDC0YS8HQgrEUDC0YS8HQgrEUDC0YS8HwY0EcDGrBWAqGFpxLwakF51JwasG5FJxacC4FpxacS8GpBedScGrBuRScWnAuBacWnEvB6cfzOKBfcUTPQ3o/pj/DQf2ZH9Wf4bD+zI/rz3Bgf+ZH9mc4tD/zY/szHNyf+dH9GQ7vz/z4/gwH+Gd+hH+GQ/wzP8Y/w0H+mVcl1KyRGlKNVyXWrNAacs0KryHYrBAbks0KsyHarFAbss0KtyHcrJAb0o3bTQLeJNebBL5J7jcpk+C8KggnueEkIE5yxUlgnOSOkwA5ySUngXKSW04C5iTXnATOSe45CaCTXHQSSCe56SSgTnLVSYW06lUBO8llJ4F2kttOAu4k150E3knuOwnAk1x4EognufEkIE9y5UlgnuTOkwA9yaUngXqSW0+qJHOvCu5J7j0J4JNcfBLIJ7n5JKBPcvVJYJ/k7pMAP8nlJ4F+kttPAv4k158E/knuPwkAlFyAUuOvQrwqECi5AiUwUHIHSoCg5BKUQEHJLSgBg5JrUAIHJfegBBBKLkIJJJTchBJQKLkKJbBQchdKnb/i8qqgoeQ2lIBDyXUogYeS+1ACECUXogQiSm5ECUiUXIkSmCi5EyVAUXIpSqCi5FaUgEXJtSgN/urSqwKMkotRAhklN6MENEquRglslNyNEuAouRwl0FFyO0rAo+R6lMBHyf0oAZCSC1ICISU3pBT8lbRXBSMld6QESEouSQmUlNySEjApuSYlcFJyT0oApeSilEBKyU0pAZWSq1ICKyV3pQRYSi5LaXKqwYq5Bpxs4LMNYEvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS9ltKcOWsttSTpxE4lVhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpbxiXhAnBq2YGcSpQWvmBnFykFfl9KAV84M4QWjFDCFOEVoxR4iThFbMEuI0oRXzhDhRaMVMIU4VclvKsKXstpRhS9ltKcOWsttSLpz05VVhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21KunMznVWFL2W0pw5ay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUm6cpOlVYUvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS9ltKcOWsttS7px861VhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21IenFTtVWFL2W0pw5ay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUg5OlveqsKXstpRhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpey2lGFL2W0pT54EseIsCJ4G4edBwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelknh6i1eFLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksl87QlrwpbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlsqK89B4ItqKM9F4KtqKc9F4Mtqas9F4OppX5QlpK85I4ylpK85J40lpK85K42lpK85L44lpK85M46lpbksFtlTclgpsqbgtFdhScVsqsKXitlQqTzP0qrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqTSePupVYUvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6or1j3iwkcrVj7i0kcr1j7i4kcrVj/i8kdr1j/iAkhelUsgrVgDiYsgrVgFicsgrVgHiQshrVgJiUshuS1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZbainW2udD2ipW2udT2irW2udj2itW2udz2ivW2ueD2mhW3ueS2V+Wi2ytW3eay2yvW3ebC2ytW3ubS225LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rClvuK6bryw24oru/HSbiuu7caLu624uhsv77bi+m68wNuKK7zxEm9rrvHGi7x5VV7mbcV13nihtxVXeuOl3tyWOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W0pnwKX7D3xA4oB/XPbJ9bvt9vByc9i8ePb6+OcfNhfnx7/Pd5fXJ692N5fHAXfv21/fdXL93X775vnpx/npxw+P/m53+3K/u3q5u718fnr2/Q2/vry6OXy5vb7evN0+3vjpfr/b88bNxcXu9uOLzeX74+s+Pdne3f+788PF8d7fX779t3/NLf/i4nD+dnvyy5vr2827i4dtjk9/fjhsTzY31yevt/uTL86vjx/dHgfk+MW7i+3lcW//dHV8lIvjHcddeLPbf7i52KQX9xte/stHv/woPz3+UeqzJ4/3PXvy13v6j/b8k/z0k3/ePf/ybs+//Kj/hB1/mZ++/L/e8e9f7O273cX29GR3td1vDnc7+Xa/3Ry2+9+921x+tf/0u5vNBfcl/YQX/2l++uk/b7Xf3FX7zU+q9ll++tk/745/dbfjX31UfsKOf56ffv7/fMf/5obrF8+uji/8y83+7fnxe/jF9s3xW/jZz+9+mN2fv333+MFhd3W/N9/uDofdh/t/vttuji/zboPj/W92u8NfPnhyfJbb3f79/f8VL/4XUEsDBBQAAAAIAC6b11z/jW3d2BcAACO/AAAYAAAAeGwvd29ya3NoZWV0cy9zaGVldDQueG1s1d1dcxvHmUDhv8LluvYyRH+/rUiqii3vbqrsWlWcZK9hCRJZpggGhJZxfn1ASuacfPlMpfYmN5II9AAYHJLiPOzpeX6/P/xwd7nbHc/++OH65u7F+eXxePvs4uLuzeXuw/buF/vb3c3pnnf7w4ft8fTh4f3F3e1ht337uNGH64u82fSLD9urm/OXzx9ve314+Xz/8Xh9dbN7fTi7+/jhw/bw45e76/39i/N0/tMNv7l6f3l8uOHi5fPb7fvdd7vj725fH04fXTw9yturD7ubu6v9zdlh9+7F+a/Ss6/TZvO4yeOY31/t7u/w77O7y/39fx2u3n5zeu7TrmzOzx527/v9/oeHu3/99uGmh6e72Z39+N3t9dXjCzg77m+/2b07frW7vj49ST4/2745Xv3f7vVp2Ivz7/fH4/7Dw/2nl37cHk83vTvs/7S7eXwVu+vdaezpBd7+zeBPD/L5QR/2+w+fd+L8aR8fXhT//dO+/Ofjm316877f3u2+2l//79Xb4+WL8zg/e7t7t/14ffzN/v6/d5/fwPbweG/213ePf57dfxpbTrvx5uPd6dV83vj0Cj5c3Xz6e/vHz2/8mg3y5w3yX22Q+j/YoHzeoKzdoH7eoK7doH3e4HHXLz7t++Mb92p73L58ftjfnx0eRz+8Qflpx57estPnwJuHEY9ZXpyfnuZ069XNw2fsd8fD6d6r0wMeX3533N683R+Ozy+Op6d5uO3izectv/z5LV8f9m8//vD3Nvzq5zf8Zvvx3Z92V39vy1c/v+W3u5v3u7+z2dc/v9lvtt9vj3/1dBenN/DpXcxPb1Z+fKDx+EAPX+x/M7Q8DS02tD4NrTa0PQ1tNrQ/De02dDwNHTY0noaGDZ1PQ6cNTZvls3Cjg/Epm3Twkixps7RES1otLdmSdktLuKTl0pIuabu0xEtaLy35kvZLS8CkBfNSMGvBvBTMWjDji04L5qVg1oJ5KZi1YF4KZi2Yl4JZC+alYNaCeSmYtWBeCmYtWJaCRQuWpWDRgmUpWPz7Jr5xasGyFCxasCwFixYsS8GiBctSsGjBshQsWrAsBYsWrEvBqgXrUrBqwboUrFqwLgWr/9+H//y0YF0KVi1Yl4JVC9alYNWCdSlYtWBdClYt2JaCTQu2pWDTgm0p2LRgWwo2LdiWgs1/fsEPMFqwLQWbFmxLwaYF21KwacG2FGxasC8FuxbsS8GuBftSsGvBvhTsWrAvBbsW7EvB7j+D4odQLdiXgl0L9qVg14J9Kdi14FgKDi04loJDC46l4NCCYyk4tOBYCg4tOJaCQwuOpeDw4wgcSGjBsRQcWnAsBYcWjKVgaMFYCoYWjKVgaMFYCoYWjKVgaMFYCoYWjKVgaMFYCoYfC+JgUAvGUjC04FwKTi04l4JTC86l4NSCcyk4teBcCk4tOJeCUwvOpeDUgnMpOLXgXApOP57HAf2KI3oe0vsx/QYH9Rs/qt/gsH7jx/UbHNhv/Mh+g0P7jR/bb3Bwv/Gj+w0O7zd+fL/BAf7Gj/A3OMTf+DH+Bgf5G69KqFkjNaQar0qsWaE15JoVXkOwWSE2JJsVZkO0WaE2ZJsVbkO4WSE3pBu3mwS8Sa43CXyT3G9SJsF5VRBOcsNJQJzkipPAOMkdJwFykktOAuUkt5wEzEmuOQmck9xzEkAnuegkkE5y00lAneSqkwpp1asCdpLLTgLtJLedBNxJrjsJvJPcdxKAJ7nwJBBPcuNJQJ7kypPAPMmdJwF6kktPAvUkt55USeZeFdyT3HsSwCe5+CSQT3LzSUCf5OqTwD7J3ScBfpLLTwL9JLefBPxJrj8J/JPcfxIAKLkApcZfhXhVIFByBUpgoOQOlABBySUogYKSW1ACBiXXoAQOSu5BCSCUXIQSSCi5CSWgUHIVSmCh5C6UOn/F5VVBQ8ltKAGHkutQAg8l96EEIEouRAlElNyIEpAouRIlMFFyJ0qAouRSlEBFya0oAYuSa1Ea/NWlVwUYJRejBDJKbkYJaJRcjRLYKLkbJcBRcjlKoKPkdpSAR8n1KIGPkvtRAiAlF6QEQkpuSCn4K2mvCkZK7kgJkJRckhIoKbklJWBSck1K4KTknpQASslFKYGUkptSAiolV6UEVkruSgmwlFyW0uRUgxVzDTjZwGcbwJay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbelnDiJxKvClrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS3nFvCBODFoxM4hTg9bMDeLkIK/K6UEr5gdxgtCKGUKcIrRijhAnCa2YJcRpQivmCXGi0IqZQpwq5LaUYUvZbSnDlrLbUoYtZbelXDjpy6vClrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS9ltKcOWsttShi1lt6VcOZnPq8KWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21KGLWW3pdw4SdOrwpay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbel3Dn51qvClrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS9ltKcOWsttShi1lt6U8OKnaq8KWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21KGLWW3pRycLO9VYUvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKsKXstpRhS9ltKcOWsttSnjwJYsVZEDwNws+DgC0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LJfH0Fq8KWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JZK5mlLXhW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLZUV56HxRLQVZ6LxVLQV56LxZLQ1Z6PxdDSvyhPSVpyRxlPSVpyTxpPSVpyVxtPSVpyXxhPTVpyZxlPT3JYKbKm4LRXYUnFbKrCl4rZUYEvFbalUnmboVWFLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUmk8fdSrwpaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlRXrHvEhY9WrHzEpY9WrH3ExY9WrH7E5Y/WrH/EBZC8KpdAWrEGEhdBWrEKEpdBWrEOEhdCWrESEpdCcluqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy21Fetsc6HtFSttc6ntFWttc7HtFattc7ntFettc8HtNStuc8ltr8pFt1esus1lt1esu82Ft1esvM2lt92WGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTcljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFLfcV13XhhtxVXduOl3VZc240Xd1txdTde3m3F9d14gbcVV3jjJd7WXOONF3nzqrzM24rrvPFCbyuu9MZLvbktddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsa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qbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSW7HONhfaXrHSNpfaXrHWNhfbXrHaNpfbXrHeNhfcXrPiNpfc9qpcdHvFqttcdnvFuttceHvFyttcetttqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvq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VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZbainW2udD2ipW2udT2irW2udj2itW2udz2ivW2ueD2mhW3ueS2V+Wi2ytW3eay2yvW3ebC2ytW3ubS225LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy11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ttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUluxzjYX2l6x0jaX2l6x1jYX216x2jaX216x3jYX3F6z4jaX3PaqXHR7xarbXHZ7xbrbXHh7xcrbXHrbbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttRXXNeNF3ZbcWU3XtptxbXdeHG3FVd34+XdVlzfjRd4W3GFN17ibc013niRN6/Ky7ytuM4bL/S24kpvvNSb21KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LaQNcevzAN0jc4JfLXtxd7nbHV9vj9uXzN6c//7y9vjr9fbW/uTt7vf9wc9rgIeLP7zq7++Gwe/vi/PP87PNPj365v3912N++2t/fvDjffLzh9ze3H45f7+7utu92Tzd+eTjsD7xxe329v//8envz/el1n5/tHu7/49Xx+nTvn27e/du/5pZ/e328erc7+92Hu/vt5fWnMaenvzoed2fbD3dnb3aHs6+u7k4f3Z82yPHby+vdzWlvf7w9Pcr16Y7TLrzdH95/uN6m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Sw221NyWGmypuS012FJzW2qwpbZinW0utL1ipW0utb1irW0utr1itW0ut71ivW0uuL1mxW0uue1Vuej2ilW3uez2inW3ufD2ipW3ufS221KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKmvuK4bL+y24spuvLTbimu78eJuK67uxsu7rbi+Gy/wtuIKb7zE25prvPEib16Vl3lbcZ03XuhtxZXeeKk3t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W1pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2W0ga49PSBb5C4wT8ve3F/udsd32yP29cv353+/PP2+ur099X+9v7s7f7j7WmDx4g/v+vs/vvD7v2r88/zi88/Pfrl/uHNYX/3Zv9w++p88+MNv7+9+3j8and/v/2we77xt4fD/sAbt9fX+4fPr7e3351e9/nZ7vH+P14dr0/3/un2w7/9a27519fHqw+7s998vH/YXl5/GnN6+qvjcXe2/Xh/9m53OPvy6v700cNpgxy/vrze3Z729oe706Ncn+447cL7/eHm4/U2vX4aePsvn/3us/zi9EfavLx4vu/lxc/39J/t+Rf5xRf/f/f8q8c9/+qz/r/Y8Tf5xZv/6x3/8cU+XO6vd+dn+7vdYXt83MkPh932uDv88XJ7+5+H337/cXvNfUm/+OL/5ob71y/vTk/+1fbw4er0iX29e3/6vN786vE7/OHqw+XzB8f93dMr+nZ/PO5vnv55udue3uPHAaf73+/3x58+uDg9y8P+8N3TF9Dr/wFQSwMEFAAA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S9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpbqinWPuPDRipWPuPTRirWPuPjRitWPuPzRmvWPuACSV+USSCvWQOIiSCtWQeIySCvWQeJCSCtWQuJSSG5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCltmKdbS60vWKlbS61vWKtbS62vWK1bS63vWK9bS64vWbFbS657VW56PaKVbe57PaKdbe58PaKlbe59LbbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqa+4rhsv7Lbiym68tNuKa7vx4m4rru7Gy7utuL4bL/C24gpvvMTbmmu88SJvXpWXeVtxnTde6G3Fld54qTe3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgt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qbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui3V4KBqrwpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlmpysLxXhS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LdXESxBmzIDgNwudBwJaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelVji9xavClprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6VWOW3Jq8KWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpXbGPDRORDtjJhqnop0xF42T0c6ZjcbpaF6VE9LOmJHGKWlnzEnjpLQzZqVxWtoZ89I4Me2MmWmcmua21GBLzW2pwZaa21KDLTW3pQZbam5LrXOaoVeFLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbkttcPqoV4UtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypn7HuERc+OmPlIy59dMbaR1z8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21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6baUsKV0W0rYUrotJWwp3ZYStpRuSwlbSrelhC2l21LCltJtKWFL6ba0YEvLbWnBlpbb0oItLbelBVtabksLtrTclhZsabktLdjScltasKXltrRgS8ttacGWltvSgi0tt6UFW1puSwu2tNyWFmxpuS0t2NJyW1qwpeW2tGBLy21pwZaW29KCLS23pQVbWm5LC7a03JYWbGm5LS3Y0nJbWrCl5ba0YEvLbWnBlpbb0oItLbelBVtabksLtrTclhZsabktLdjScltasKXltrRgS8ttacGWltvSgi0tt6UFW1puSwu2tNyWFmxpuS0t2NJyW1qwpeW2tGBLy21pwZaW29KCLS23pQVbWm5LC7a03JYWbGm5LS3Y0nJbWrCl5ba0YEvLbWnBlpbb0oItLbelBVtabksLtrTclhZsabktLdjScltasKXltrRgS8ttacGWltvSgi0tt6UFW1puSwu2tNyWFmxpuS0t2NJyW1qwpeW2tGBLy21pwZaW29KCLS23pQVbWm5LC7a03JYWbGm5LS3Y0nJbWrCl5ba0YEvLbWnBlpbb0oIt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lipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKW2Yp1tLrS9YqVtLrW9Yq1tLra9YrVtLre9Yr1tLri9ZsVtLrntVbno9opVt7ns9op1t7nw9oqVt7n0tttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypr7iuGy/stuLKbry024pru/Hibiuu7sbLu624vhsv8LbiCm+8xNuaa7zxIm9elZd5W3GdN17obcWV3nipN7elDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbSlgS+G2FLClcFsK2FK4LQVsKdyWArYUbksBWwq3pYAthdtSwJbCbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdltIGuPT4iQ9IHPDPy17cXe52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hS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZbainW2udD2ipW2udT2irW2udj2itW2udz2ivW2ueD2mhW3ueS2V+Wi2ytW3eay2yvW3ebC2ytW3ubS225LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rClvuK6bryw24oru/HSbiuu7caLu624uhsv77bi+m68wNuKK7zxEm9rrvHGi7x5VV7mbcV13nihtxVXeuOl3tyWOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6WALYXbUsCWwm0pYEvhthSwpXBbCthSuC0FbCnclgK2FG5LAVsKt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W0ob4NLjJz4gccA/Lntxd7nbHV9tj9uXz9+cPv5xe311+vNqf3N39nr/4eY04OFt/uu7zu7+dNi9fXH+aX726cdHv9zfvzrsb1/t729enG9+vOHXN7cfjr/Z3d1t3+2ebvz8cNgfeOP2+np//+n19ua70+s+P9s93P/7q+P16d4/3Lz7j3/PLf/y+nj1bnf2qw9399vL64/bnJ7+6njcnW0/3J292R3Ovry6O312fxqQ45eX17ub095+f3t6lOvTHaddeLs/vP9wvU0vHze8+bdP/vuT/Oz0Ic3nF0/3Pb/46z39R3v+WX722b/unv/uYc9/90n9J3b8VX726l93x79+2PGvP+n/xI5/np99/v+94z++2PvL/fXu/Gx/uztsjw87+e6w2x53h99fbm9+e/j8Tx+219yX9E+8+C/ysy/+dav99qHabz8pP7vjf3PD3cvnt6cX/pvt4d3V6VvZ9e7t6TvZ5hcPP9Mdrt5dPn1y3N8+7s23++Nx//7xr5e77ellPmxwuv/tfn/8yycXp2e53x++e/yW+fL/AFBLAwQUAAAACAAum9dcAHblTbQXAADBvQAAGQAAAHhsL3dvcmtzaGVldHMvc2hlZXQxMy54bWzV3V9zG9d5gPGvwrKeXoY4/9/jSJqJrbTN1J564iS9hiVI5JgkGBAq43z6gpTMfZzEfnY6vemNJAJnASweUuT+ePbsi4f94fv7y93uePaXm+vb+5fnl8fj3ecXF/dvLnc32/tf7e92t6d73u0PN9vj6cPD+4v7u8Nu+/Zpo5vri7zZ9Iub7dXt+asXT7d9c3j1Yv/heH11u/vmcHb/4eZme/jhi931/uHleTr/8YbfX72/PD7ecPHqxd32/e7b3fGPd98cTh9dPD/K26ub3e391f727LB79/L8N+nz12mzedrkacyfrnYP9/j32f3l/uHfDldvvzo992lXNudnj7v33X7//ePdv3v7eNPj093uzn749u766ukFnB33d1/t3h2/3F1fn54kn59t3xyv/nv3zWnYy/Pv9sfj/ubx/tNLP26Pp5veHfZ/3d0+vYrd9e409vQC7/5u8McH+fSgj/v95087cf68j48viv/+cV/+9enNPr15323vd1/ur//r6u3x8uV5nJ+93b3bfrg+/n7/8O+7T29ge3y8N/vr+6c/zx4+ji2n3Xjz4f70aj5tfHoFN1e3H//e/uXTG79mg/xpg/w3G6T+MxuUTxuUtRvUTxvUp3fm4648vQ+vt8ftqxeH/cPZ4Wn04/7m59f5/A6ckr55HPH0Lr88Pz3N6dar28dPwG+Ph9O9V6cHPL769ri9fbs/HF9cHE9P83jbxZtPW37xy1t+c9i//fD9P9rwy1/e8Kvth3d/3V39oy1f//KWX+9u3+9+utnF6Y14fjfy807np8cZT4/z+DX4d0PL89BiQ+vz0GpD2/PQZkP789BuQ8fz0GFD43lo2ND5PHTa0LRZPps2OhifekkHL8mSNktLtKTV0pItabe0hEtaLi3pkrZLS7yk9dKSL2m/tARMWjAvBbMWzEvBrAUzvui0YF4KZi2Yl4JZC+alYNaCeSmYtWBeCmYtmJeCWQvmpWDWgmUpWLRgWQoWLViWgsX/38R/nFqwLAWLFixLwaIFy1KwaMGyFCxasCwFixYsS8GiBetSsGrBuhSsWrAuBasWrEvB6t/78M1PC9alYNWCdSlYtWBdClYtWJeCVQvWpWDVgm0p2LRgWwo2LdiWgk0LtqVg04JtKdj85xf8AKMF21KwacG2FGxasC0FmxZsS8GmBftSsGvBvhTsWrAvBbsW7EvBrgX7UrBrwb4U7P4zKH4I1YJ9Kdi1YF8Kdi3Yl4JdC46l4NCCYyk4tOBYCg4tOJaCQwuOpeDQgmMpOLTgWAoOP47AgYQWHEvBoQXHUnBowVgKhhaMpWBowVgKhhaMpWBowVgKhhaMpWBowVgKhhaMpWD4sSAOBrVgLAVDC86l4NSCcyk4teBcCk4tOJeCUwvOpeDUgnMpOLXgXApOLTiXglMLzqXg9ON5HNCvOKLnIb0f029wUL/xo/oNDus3fly/wYH9xo/sNzi03/ix/QYH9xs/ut/g8H7jx/cbHOBv/Ah/g0P8jR/jb3CQv/GqhJo1UkOq8arEmhVaQ65Z4TUEmxViQ7JZYTZEmxVqQ7ZZ4TaEmxVyQ7pxu0nAm+R6k8A3yf0mZRKcVwXhJDecBMRJrjgJjJPccRIgJ7nkJFBOcstJwJzkmpPAOck9JwF0kotOAukkN50E1EmuOqmQVr0qYCe57CTQTnLbScCd5LqTwDvJfScBeJILTwLxJDeeBORJrjwJzJPceRKgJ7n0JFBPcutJlWTuVcE9yb0nAXySi08C+SQ3nwT0Sa4+CeyT3H0S4Ce5/CTQT3L7ScCf5PqTwD/J/ScBgJILUGr8VYhXBQIlV6AEBkruQAkQlFyCEigouQUlYFByDUrgoOQelABCyUUogYSSm1ACCiVXoQQWSu5CqfNXXF4VNJTchhJwKLkOJfBQch9KAKLkQpRARMmNKAGJkitRAhMld6IEKEouRQlUlNyKErAouRalwV9delWAUXIxSiCj5GaUgEbJ1SiBjZK7UQIcJZejBDpKbkcJeJRcjxL4KLkfJQBSckFKIKTkhpSCv5L2qmCk5I6UAEnJJSmBkpJbUgImJdekBE5K7kkJoJRclBJIKbkpJaBSclVKYKXkrpQAS8llKU1ONVgx14CTDXy2AWwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyomTSLwqbCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbelDFvKbksZtpRXzAvixKAVM4M4NWjN3CBODvKqnB60Yn4QJwitmCHEKUIr5ghxktCKWUKcJrRinhAnCq2YKcSpQm5LGbaU3ZYybCm7LWXYUnZbyoWTvrwqbCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKlZP5vCpsKbstZdhSdlvKsKXstpRhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8qNkzS9Kmwpuy1l2FJ2W8qwpey2lGFL2W0pw5ay21KGLWW3pQxbym5LGbaU3ZYybCm7LWXYUnZbyp2Tb70qbCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbstZdhSdlvKg5OqvSpsKbstZdhSdlvKsKXstpRhS9ltKcOWsttShi1lt6UMW8puSxm2lN2WMmwpuy1l2FJ2W8rByfJeFbaU3ZYybCm7LWXYUnZbyrCl7LaUYUvZbSnDlrLbUoYtZbelDFvKbksZtpTdljJsKbst5cmTIFacBcHTIPw8CNhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VBJPb/GqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pZJ625FVhS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJZcR4aT0RbcSYaT0VbcS4aT0ZbczYaT0fzqjwhbcUZaTwlbcU5aTwpbcVZaTwtbcV5aTwxbcWZaTw1zW2pwJaK21KBLRW3pQJbKm5LBbZU3JZK5WmGXhW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LZXG00e9KmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcCWittSgS0Vt6UCWypuSwW2VNyWCmypuC0V2FJxWyqwpeK2VGBLxW2pwJaK21KBLRW3pQJbKm5LBbZU3JYKbKm4LRXYUnFbKrCl4rZUYEvFbanAlorbUoEtFbelAlsqbksFtlTclgpsqbgtFdhScVsqsKXitlRgS8VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUt1xbpHXPhoxcpHXPpoxdpHXPxoxepHXP5ozfpHXADJq3IJpBVrIHERpBWrIHEZpBXrIHEhpBUrIXEpJLelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSW7HONhfaXrHSNpfaXrHWNhfbXrHaNpfbXrHeNhfcXrPiNpfc9qpcdHvFqttcdnvFuttceHvFyttcetttqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221Fdc140XdltxZTde2m3Ftd14cbcVV3fj5d1WXN+NF3hbcYU3XuJtzTXeeJE3r8rLvK24zhsv9LbiSm+81JvbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrb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t7nktlflotsrVt3mstsr1t3mwtsrVt7m0ttuSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpb7ium68sNuKK7vx0m4rru3Gi7utuLobL++24vpuvMDbiiu88RJva67xxou8eVVe5m3Fdd54obcVV3rjpd7cljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelgC2F21LAlsJtKWBL4bYUsKVwWwrYUrgtBWwp3JYCthRuSwFbCrelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFuasKXptjRhS9NtacKWptvShC1Nt6UJW5puSxO2NN2WJmxpui1N2NJ0W5qwpem2NGFL021pwpam29KELU23pQlbmm5LE7Y03ZYmbGm6LU3Y0nRbmrCl6bY0YUvTbWnClqbb0oQtTbelCVuabksTtjTdliZsabotTdjSdFtKG+DS0we+QeIGP1/24v5ytzu+2R63r1++O/35p+311envq/3t/dnb/cfb0waPEX9619n9nw+796/OP88vPv/06Jf7hzeH/d2b/cPtq/PNDzf87vbu4/Gr3f399sPu+cbfHg77A2/cXl/vHz6/3t5+d3rd52e7x/v/cHW8Pt37x9sP//avueVfXx+vPuzOfvPx/mF7ef1pzOnpr47H3dn24/3Zu93h7Mur+9NHD6cNcvz68np3e9rb7+9Oj3J9uuO0C+/3h5uP19v0+mng7b989uVn+cXpj3h58XzXy4uf7ujP7fgX+cUX/393/KvHHf/qs/6/2PE3+cWb/+sd/+HFPlzur3fnZ/u73WF7fNzJD4fd9rg7/OFye/tfh9/++eP2mvuSfvHF/90N969f3p2e/Kvt4cPV6fP6evf+9Gm9+dXjN/jD1YfL5w+O+7unV/Tt/njc3zz983K3Pb3HjwNO97/f748/fnBxepaH/eG7p6+f138DUEsDBBQAAAAIAC6b11yTJv7fvxcAAHq+AAAZAAAAeGwvd29ya3NoZWV0cy9zaGVldDE1LnhtbNXdXXMcx3lA4b+CMKpUcmOgP9+3ZZJVlqXErpKrVJLtXEPkkkQJxCLAMrT967OAaMzxl85W7nxDErs9uzt7ABDzoKfn+cf93Q/373a7w9kf3l/f3L949u5wuP38/Pz+1bvd+8v7n+1vdzfHe97s795fHo4f3r09v7+9212+ftzo/fV5vbiY5+8vr26evXz+eNs3dy+f7z8crq9udt/cnd1/eP/+8u6PX+yu9x9fPCvP/nzDt1dv3x0ebjh/+fz28u3uu93hd7ff3B0/On96lNdX73c391f7m7O73ZsXz35RPv+qXFw8bvI45vdXu4/3+PfZ/bv9x/+6u3r99fG5j7ty8ezsYfe+3+9/eLj7168fbnp4upvd2R+/u72+enwBZ4f97de7N4df7q6vj09Sn51dvjpc/e/um+OwF8++3x8O+/cP9x9f+uHycLzpzd3+T7ubx1exu94dxx5f4O3fDP7xQT496MN+/8+nnXj2tI8PL4r//vO+/Ofjm318876/vN/9cn/931evD+9ePMtnZ693by4/XB++3X/81e7TGzgeHu/V/vr+8c+zjz+ObcfdePXh/vhqPm18fAXvr25+/PvyD5/eeGxQ5j/YoH7aoP7VBv/wGdqnDdqpz9A/bdBP3WB82uBx189/3PfHN+7Ly8Ply+d3+49nd4+jH96g+vQ6n96y4+fAq4cRj1lePDs+zfHWq5uHz9jvDnfHe6+OD3h4+d3h8ub1/u7w/PxwfJqH285ffdryi5/e8leXH26PGx+/hq4/3N//ne1/+dPbf3F84u/vdleH3dm//+b7q8P5/X/8nQf58qcf5OvLD2/+dHyMv7PlV/b0r374cPuX250f39Wnt7Y+vYP18YHi8YEevgP8zdD2NLTZ0P40tNvQ8TR02ND5NHTa0HgaGjY0n4amDV1PQ5cNLRfbp+aFDsbncdHBW7KizcoWrWi1smUr2q1s4YqWK1u6ou3KFq9ovbLlK9qvbAGLFqxbwaoF61awasGKLzotWLeCVQvWrWDVgnUrWLVg3QpWLVi3glUL1q1g1YJ1K1i1YNsKNi3YtoJNC7atYPPvm/jGqQXbVrBpwbYVbFqwbQWbFmxbwaYF21awacG2FWxasG8FuxbsW8GuBftWsGvBvhXs/n8f/vPTgn0r2LVg3wp2Ldi3gl0L9q1g14J9K9i14NgKDi04toJDC46t4NCCYys4tODYCg7/+QU/wGjBsRUcWnBsBYcWHFvBoQXHVnBowbkVnFpwbgWnFpxbwakF51ZwasG5FZxacG4Fp/8Mih9CteDcCk4tOLeCUwvOreDUgrEVDC0YW8HQgrEVDC0YW8HQgrEVDC0YW8HQgrEVDD+OwIGEFoytYGjB2AqGFsytYGrB3AqmFsytYGrB3AqmFsytYGrB3AqmFsytYGrB3AqmHwviYFAL5lYwteDaCi4tuLaCSwuureDSgmsruLTg2gouLbi2gksLrq3g0oJrK7i04NoKLj+exwH9CUf0PKT3Y/oLHNRf+FH9BQ7rL/y4/gIH9hd+ZH+BQ/sLP7a/wMH9hR/dX+Dw/sKP7y9wgH/hR/gXOMS/8GP8CxzkX3hVQs0pUkOq8arEmhO0hlxzgtcQbE4QG5LNCWZDtDlBbcg2J7gN4eYEuSHduN0U4E1xvSngm+J+UyoJzquCcIobTgHiFFecAsYp7jgFkFNccgoop7jlFGBOcc0p4JzinlMAOsVFp4B0iptOAeoUV53SSKteFbBTXHYKaKe47RTgTnHdKeCd4r5TADzFhaeAeIobTwHyFFeeAuYp7jwF0FNcegqop7j1lE4y96rgnuLeUwA+xcWngHyKm08B+hRXnwL2Ke4+BfBTXH4K6Ke4/RTgT3H9KeCf4v5TAEDFBagM/irEqwKBiitQAQMVd6ACCCouQQUUVNyCCjCouAYVcFBxDyoAoeIiVEBCxU2oAIWKq1ABCxV3oTL5Ky6vChoqbkMFOFRchwp4qLgPFQBRcSEqIKLiRlSARMWVqICJijtRARQVl6ICKipuRQVYVFyLSvBXl14VYFRcjArIqLgZFaBRcTUqYKPiblQAR8XlqICOittRAR4V16MCPiruRwWAVFyQCgipuCGV5K+kvSoYqbgjFUBScUkqoKTillSAScU1qYCTintSASgVF6UCUipuSgWoVFyVClipuCsVwFJxWSqLUw1OmGvAyQY+2wC2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LdXCSSReFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqJ8wL4sSgE2YGcWrQKXODODnIq3J60AnzgzhB6IQZQpwidMIcIU4SOmGWEKcJnTBPiBOFTpgpxKlCbksVtlTdlipsqbotVdhSdVuqjZO+vCpsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qdk/m8Kmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqoOTNL0qbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqk5NvvSpsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6rBSdVeFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbot1eRkea8KW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3Zbq4kkQJ5wFwdMg/DwI2FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUCk9v8aqwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbalVnrbkVWFLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUjvhPDSeiHbCmWg8Fe2Ec9F4MtopZ6PxdDSvyhPSTjgjjaeknXBOGk9KO+GsNJ6WdsJ5aTwx7YQz03hqmttSgy01t6UGW2puSw221NyWGmypuS21ztMMvSpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qDp496VdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5b6CeseceGjE1Y+4tJHJ6x9xMWPTlj9iMsfnbL+ERdA8qpcAumENZC4CNIJqyBxGaQT1kHiQkgnrITEpZDcljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx221N2WOmypuy112FJ3W+qwpe621GFL3W2pw5a621KHLXW3pQ5b6m5LHbbU3ZY6bKm7LXXYUndb6rCl7rbUYUvdbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1sasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxpuC0N2NJwWxqwpeG2NGBLw21pwJaG29KALQ23pQFbGm5LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS+OEdba50PYJK21zqe0T1trmYtsnrLbN5bZPWG+bC26fsuI2l9z2qlx0+4RVt7ns9gnrbnPh7RNW3ubS225LA7Y03JYGbGm4LQ3Y0nBbGrCl4bY0YEvDbWnAlobb0oAtDbelAVsabksDtjTclgZsabgtDdjScFsasKXhtjRgS8NtacCWhtvSgC0Nt6UBWxpuSwO2NNyWBmxp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6rZUYUt1xbpHXPhoxcpHXPpoxdpHXPxoxepHXP5ozfpHXADJq3IJpBVrIHERpBWrIHEZpBXrIHEhpBUrIXEpJLelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlipsqbotVdhSdVuqsKXqtlRhS9VtqcKWqttShS1Vt6UKW6puSxW2VN2WKmypui1V2FJ1W6qwpeq2VGFL1W2pwpaq21KFLVW3pQpbqm5LFbZU3ZYqbKm6LVXYUnVbqrCl6rZUYUvVbanClqrbUoUtVbelCluqbksVtlTdlhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSW7HONhfaXrHSNpfaXrHWNhfbXrHaNpfbXrHeNhfcXrPiNpfc9qpcdHvFqttcdnvFuttceHvFyttcetttqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanBlprbUoMtNbelBltqbksNttTclhpsqbktNdhSc1tqsKXmttRgS81tqcGWmttSgy01t6UGW2puSw221NyWGmypuS012FJzW2qwpea21GBLzW2pwZaa21KDLTW3pQZbam5LDbbU3JYabKm5LTXYUnNbarCl5rbUYEvNbanDlrrbUoctdbelDlvqbksdttTdljpsqbstddhSd1vqsKXuttRhS91tqcOWuttShy11t6UOW+puSx22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";

export default function App() {
  const today = new Date().toLocaleDateString("de-DE");
  const FOOTNOTE = "Alle Preise netto zzgl. MwSt. Änderungen und Irrtümer vorbehalten";
  const [customer, setCustomer] = useState("");
  const [groups, setGroups] = useState([newGroup()]);
  const [copied, setCopied] = useState(false);
  const [visibleSums, setVisibleSums] = useState(new Set());
  // Kopier-Auswahl: welche Angebotszeilen NICHT in die PowerPoint-Kopie übernommen werden (Speichern/.json/.jpg bleibt davon unberührt).
  // Standard = alles inklusive (Set ist leer = nichts ausgeschlossen).
  const [excludedLines, setExcludedLines] = useState(new Set());
  const lineKey = (l) => l.rowId != null ? `r${l.rowId}` : `${l.gid ?? "x"}::${l.name}`;
  const isLineIncluded = (l) => !excludedLines.has(lineKey(l));
  const toggleLineIncluded = (l) => setExcludedLines(prev => { const n = new Set(prev); const k = lineKey(l); if (n.has(k)) n.delete(k); else n.add(k); return n; });
  const [showSavings, setShowSavings] = useState(false);
  const [showTCO, setShowTCO] = useState(false);
  const [showAllSums, setShowAllSums] = useState(false);
  const [istKosten, setIstKosten] = useState("");
  const fileRef = useRef(null);
  const cfgRef = useRef(null);
  const rowFieldRefs = useRef({});
  const [focusRowId, setFocusRowId] = useState(null);

  // ===== Editierbarer Katalog =====
  const [tab, setTab] = useState("calc");                 // "calc" | "edit" | "import"
  const [importText, setImportText] = useState("");
  const [importAsStandort, setImportAsStandort] = useState(true);
  const [importMsg, setImportMsg] = useState("");
  const [overrides, setOverrides] = useState({ products: {}, opts: {}, optPrices: {}, optTables: {}, deleted: [], custom: [], areaDesc: {}, vpnDesc: {} });
  const [catVer, setCatVer] = useState(0);                // erzwingt Neuberechnung nach Katalogänderung
  const [editSearch, setEditSearch] = useState("");
  const [editArea, setEditArea] = useState("all");
  const [editChangedOnly, setEditChangedOnly] = useState(false);
  const [editEmptyOnly, setEditEmptyOnly] = useState(false);
  const editPinned = useRef(new Set()); // Produkte, die beim „nur leere"-Filter gerade bearbeitet werden – bleiben sichtbar, bis der Filter neu gesetzt wird
  const [addOpenAreas, setAddOpenAreas] = useState(new Set());
  const [newDrafts, setNewDrafts] = useState({});
  const [cfgStatus, setCfgStatus] = useState("");
  const [lastAutoSave, setLastAutoSave] = useState(null);
  const AUTOSAVE_KEY = "tef-autosave-v1";
  const [baselines, setBaselines] = useState({});
  const [diffOpen, setDiffOpen] = useState({});
  const saveBaseline = (g) => setBaselines(b => ({ ...b, [g.id]: JSON.parse(JSON.stringify(g)) }));
  const clearBaseline = (gid) => { setBaselines(b => { const n = { ...b }; delete n[gid]; return n; }); setDiffOpen(d => ({ ...d, [gid]: false })); };
  const toggleDiff = (gid) => setDiffOpen(d => ({ ...d, [gid]: !d[gid] }));

  const resetOffer = () => {
    if (!window.confirm("Gesamtes Angebot wirklich leeren? Kunde, alle Bereiche und Produkte werden entfernt. Rückgängig nur über den Button 'Letzten Stand wiederherstellen' oder eine zuvor gespeicherte .json-Datei.")) return;
    setGroups([newGroup()]);
    setCustomer("");
    setIstKosten("");
    setBaselines({});
    setDiffOpen({});
    setVisibleSums(new Set());
    setShowAllSums(false);
    setShowTCO(false);
    setShowSavings(false);
    setExcludedLines(new Set());
  };

  // Beim Start: gespeicherte Overrides laden (window.storage, persistent über Sitzungen)
  useEffect(() => {
    (async () => {
      try {
        const res = await window.storage?.get(CATALOG_STORE_KEY, true);
        if (res?.value) {
          const ov = JSON.parse(res.value);
          const norm = { products: ov.products || {}, opts: ov.opts || {}, optPrices: ov.optPrices || {}, optTables: ov.optTables || {}, deleted: ov.deleted || [], custom: ov.custom || [], areaDesc: ov.areaDesc || {}, vpnDesc: ov.vpnDesc || {} };
          applyOverrides(norm); setOverrides(norm); setCatVer(v => v + 1);
        }
      } catch { /* keine gespeicherten Daten / Storage nicht verfügbar */ }
    })();
  }, []);

  // Neue Zeile automatisch fokussieren (nach Klick auf „Produkt hinzufügen" oder Enter im Mengenfeld)
  useEffect(() => {
    if (focusRowId == null) return;
    const el = rowFieldRefs.current[focusRowId];
    if (el) { el.focus(); setFocusRowId(null); }
  }, [focusRowId, groups]);

  // Auto-Save: sichert den aktuellen Stand alle 60 Sekunden im Hintergrund (Schutz vor Datenverlust)
  useEffect(() => {
    const iv = setInterval(() => {
      if (!groups.some(g => g.area)) return; // nichts Sinnvolles zu sichern
      (async () => {
        try {
          await window.storage?.set(AUTOSAVE_KEY, buildState(), false);
          setLastAutoSave(new Date());
        } catch { /* Storage evtl. nicht verfügbar */ }
      })();
    }, 60000);
    return () => clearInterval(iv);
  }, [groups, customer]);

  const restoreAutoSave = async () => {
    try {
      const res = await window.storage?.get(AUTOSAVE_KEY, false);
      if (!res?.value) { alert("Kein automatisch gesicherter Stand gefunden."); return; }
      const data = JSON.parse(res.value);
      const when = data.savedAt ? new Date(data.savedAt).toLocaleString("de-DE") : "unbekannt";
      if (!window.confirm(`Letzten automatisch gesicherten Stand wiederherstellen (gesichert am ${when})? Die aktuelle Eingabe wird ersetzt.`)) return;
      restore(data);
    } catch { alert("Automatische Sicherung konnte nicht geladen werden."); }
  };

  const persistOverrides = async (ov) => {
    applyOverrides(ov); setOverrides(ov); setCatVer(v => v + 1);
    try { await window.storage?.set(CATALOG_STORE_KEY, JSON.stringify(ov), true); } catch { /* ignore */ }
  };
  // Effektiven (= ggf. überschriebenen) Produktwert holen
  const effProduct = (area, id) => CATALOG[area]?.products.find(p => p.id === id) || null;
  const setProductField = (area, id, field, value) => {
    const key = `${area}::${id}`;
    const next = { ...overrides, products: { ...overrides.products, [key]: { ...(overrides.products[key] || {}), [field]: value } } };
    persistOverrides(next);
  };
  const setProductDesc = (area, id, text) => setProductField(area, id, "desc", descLines(text));
  const setSdwanPriceField = (area, id, term, value) => {
    const key = `${area}::${id}`;
    const cur = overrides.products[key] || {};
    const next = { ...overrides, products: { ...overrides.products, [key]: { ...cur, price: { ...(cur.price || {}), [term]: value } } } };
    persistOverrides(next);
  };
  const setSdwanLicField = (area, id, licType, term, value) => {
    const key = `${area}::${id}`;
    const cur = overrides.products[key] || {};
    const curLic = cur.lic || {};
    const next = { ...overrides, products: { ...overrides.products, [key]: { ...cur, lic: { ...curLic, [licType]: { ...(curLic[licType] || {}), [term]: value } } } } };
    persistOverrides(next);
  };
  const resetProduct = (area, id) => {
    const key = `${area}::${id}`; const prods = { ...overrides.products }; delete prods[key];
    persistOverrides({ ...overrides, products: prods });
  };
  const setOptField = (key, value) => persistOverrides({ ...overrides, opts: { ...overrides.opts, [key]: value } });
  const resetOpt = (key) => { const opts = { ...overrides.opts }; delete opts[key]; persistOverrides({ ...overrides, opts }); };
  const setOptPrice = (key, value) => persistOverrides({ ...overrides, optPrices: { ...overrides.optPrices, [key]: value } });
  const resetOptPrice = (key) => { const optPrices = { ...overrides.optPrices }; delete optPrices[key]; persistOverrides({ ...overrides, optPrices }); };
  const setOptTableCell = (key, term, value) => persistOverrides({ ...overrides, optTables: { ...overrides.optTables, [key]: { ...(overrides.optTables?.[key] || {}), [term]: value } } });
  const resetOptTable = (key) => { const optTables = { ...overrides.optTables }; delete optTables[key]; persistOverrides({ ...overrides, optTables }); };

  // Bereichsbeschreibung (global je Bereich, im Editor anpassbar)
  const descLines = (text) => { const arr = (text || "").split("\n"); return arr.every(l => l.trim() === "") ? [] : arr; };
  const setAreaDescText = (area, text) => {
    persistOverrides({ ...overrides, areaDesc: { ...overrides.areaDesc, [area]: descLines(text) } });
  };
  const resetAreaDesc = (area) => { const ad = { ...overrides.areaDesc }; delete ad[area]; persistOverrides({ ...overrides, areaDesc: ad }); };
  // Liefert die im Header anzuzeigende Beschreibung und entfernt – wenn aktiv – die gemeinsamen Zeilen aus den Produktzeilen.
  // Aufgelöste Produktbereichsbeschreibung: manueller Override (nicht leer) > eingearbeiteter Standard > null (=> Automatik/Schnittmenge)
  const areaDescFor = (area) => {
    const manual = overrides.areaDesc?.[area];
    if (manual && manual.length) return manual;
    if (overrides.areaDesc && area in overrides.areaDesc) return null; // bewusst geleert => Automatik
    return DEFAULT_AREA_DESC[area]?.length ? DEFAULT_AREA_DESC[area] : null;
  };
  // Standard (showDesc=false): Bereichsbeschreibung erscheint unter der Produktbeschreibung jeder Zeile.
  // Eingeblendet (showDesc=true): Bereichsbeschreibung wandert in den Bereichs-Header, Produktzeile zeigt nur die eigene Produktbeschreibung.
  const applyAreaHeaderDesc = (g, groupLines) => {
    const productLines = groupLines.filter(l => l.type === "product");
    if (!productLines.length) return null;
    const explicit = areaDescFor(g.area);
    if (g.showDesc) {
      let header = explicit;
      if (!header) { const sets = productLines.map(l => new Set(l.desc || [])); header = (productLines[0].desc || []).filter(line => sets.every(s => s.has(line))); }
      if (!header.length) return null;
      const hs = new Set(header);
      for (const l of productLines) l.desc = (l.desc || []).filter(line => !hs.has(line));
      return header;
    }
    // Standard: Bereichsbeschreibung VOR die eigene Produkt-/Optionsbeschreibung stellen (ohne Dubletten)
    // Reihenfolge: Produktbereichbeschreibung → Produktbeschreibung → Optionsbeschreibung(en)
    if (explicit && explicit.length) {
      for (const l of productLines) { const own = (l.desc || []).filter(line => !explicit.includes(line)); l.desc = [...explicit, ...own]; }
    }
    return null;
  };

  // Basis-Produkte: löschen = nur aus der Auswahl-Liste ausblenden (bereits platzierte Angebotszeilen bleiben unverändert rechenbar)
  const isDeleted = (area, id) => (overrides.deleted || []).includes(`${area}::${id}`);
  const deleteProduct = (area, id, name) => {
    if (!window.confirm(`„${name}" aus der Produktauswahl entfernen?\n\nBereits in Angeboten platzierte Zeilen mit diesem Produkt rechnen unverändert weiter. Über „Wiederherstellen" kannst du es jederzeit zurückholen.`)) return;
    const key = `${area}::${id}`;
    if ((overrides.deleted || []).includes(key)) return;
    persistOverrides({ ...overrides, deleted: [...(overrides.deleted || []), key] });
  };
  const restoreProduct = (area, id) => persistOverrides({ ...overrides, deleted: (overrides.deleted || []).filter(k => k !== `${area}::${id}`) });

  // Eigene Produkte: vollständig im Editor verwaltet (kein Original in BASE_CATALOG)
  const addCustomProduct = (area, draft) => {
    const name = (draft.name || "").trim();
    if (!name) return;
    const id = `custom-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`;
    const desc = (draft.desc || "").split("\n").map(s => s.trim()).filter(Boolean);
    const product = draft.priceOpen
      ? { area, id, name, desc, laufzeiten: [], priceOpen: true, ...(draft.qty ? { qty: true, qtyLabel: (draft.qtyLabel || "Menge").trim() || "Menge" } : {}) }
      : { area, id, name, desc, laufzeiten: LZ, m24: Number(draft.m24) || 0, m36: Number(draft.m36) || 0, o24: Number(draft.o24) || 0, o36: Number(draft.o36) || 0, ...(draft.qty ? { qty: true, qtyLabel: (draft.qtyLabel || "Menge").trim() || "Menge" } : {}) };
    persistOverrides({ ...overrides, custom: [...(overrides.custom || []), product] });
  };
  const deleteCustomProduct = (area, id, name) => {
    if (!window.confirm(`Eigenes Produkt „${name}" endgültig löschen?\n\nBereits in Angeboten platzierte Zeilen mit diesem Produkt rechnen danach nicht mehr korrekt.`)) return;
    persistOverrides({ ...overrides, custom: (overrides.custom || []).filter(c => !(c.area === area && c.id === id)) });
  };
  const setVpnDescField = (key, text) => { persistOverrides({ ...overrides, vpnDesc: { ...overrides.vpnDesc, [key]: descLines(text) } }); };
  const resetVpnDesc = () => { const ov = { ...overrides }; delete ov.vpnDesc; persistOverrides({ ...ov, vpnDesc: {} }); };
  const setCustomField = (area, id, field, value) => persistOverrides({ ...overrides, custom: (overrides.custom || []).map(c => (c.area === area && c.id === id) ? { ...c, [field]: value } : c) });
  const setCustomDesc = (area, id, text) => setCustomField(area, id, "desc", descLines(text));

  // Produkt duplizieren: vollständiger, unabhängiger Klon (inkl. aller Preis-/Logik-Felder) als eigenes Produkt im selben Bereich
  const cloneId = (srcId) => `${srcId}-copy-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
  const duplicateProduct = (area, baseProd) => {
    const ov = overrides.products[`${area}::${baseProd.id}`] || {};
    const merged = { ...baseProd, ...("name" in ov ? { name: ov.name } : {}), ...("desc" in ov ? { desc: ov.desc } : {}) };
    for (const f of PRICE_FIELDS) if (f in ov && ov[f] !== "" && ov[f] != null) merged[f] = Number(ov[f]);
    const clone = JSON.parse(JSON.stringify(merged));
    clone.id = cloneId(baseProd.id); clone.name = `${merged.name} (Kopie)`; clone.area = area;
    persistOverrides({ ...overrides, custom: [...(overrides.custom || []), clone] });
  };
  const duplicateCustomProduct = (area, c) => {
    const clone = JSON.parse(JSON.stringify(c));
    clone.id = cloneId(c.id); clone.name = `${c.name} (Kopie)`; clone.area = area;
    persistOverrides({ ...overrides, custom: [...(overrides.custom || []), clone] });
  };

  // 'Neues Produkt'-Formular (ein Entwurf pro Bereich, lokal bis zum Absenden)
  const blankDraft = { name: "", desc: "", priceOpen: false, m24: "", m36: "", o24: "", o36: "", qty: false, qtyLabel: "" };
  const getDraft = (area) => newDrafts[area] || blankDraft;
  const setDraftField = (area, field, value) => setNewDrafts(d => ({ ...d, [area]: { ...getDraft(area), [field]: value } }));
  const toggleAddOpen = (area) => setAddOpenAreas(prev => { const n = new Set(prev); n.has(area) ? n.delete(area) : n.add(area); return n; });
  const submitNewProduct = (area) => {
    const draft = getDraft(area);
    if (!draft.name?.trim()) return;
    addCustomProduct(area, draft);
    setNewDrafts(d => ({ ...d, [area]: undefined }));
    setAddOpenAreas(prev => { const n = new Set(prev); n.delete(area); return n; });
  };
  const resetAllOverrides = () => { if (window.confirm("Alle Anpassungen zurücksetzen und Originalwerte wiederherstellen? Eigene hinzugefügte Produkte werden dabei ebenfalls gelöscht.")) persistOverrides({ products: {}, opts: {}, optPrices: {}, optTables: {}, deleted: [], custom: [], areaDesc: {}, vpnDesc: {} }); };

  const exportConfig = () => {
    const blob = new Blob([JSON.stringify({ app: "tef-kalkulator-config", version: 1, savedAt: new Date().toISOString(), overrides }, null, 2)], { type: "application/json" });
    triggerDownload(blob, "tef-kalkulator-konfiguration.json");
  };
  const importConfig = (e) => {
    const f = e.target.files?.[0]; if (!f) return;
    const reader = new FileReader();
    reader.onload = () => { try { const d = JSON.parse(String(reader.result)); const ov = d.overrides || d; const norm = { products: ov.products || {}, opts: ov.opts || {}, optPrices: ov.optPrices || {}, optTables: ov.optTables || {}, deleted: ov.deleted || [], custom: ov.custom || [], areaDesc: ov.areaDesc || {}, vpnDesc: ov.vpnDesc || {} }; persistOverrides(norm); setCfgStatus("Konfiguration importiert."); setTimeout(() => setCfgStatus(""), 3000); } catch { setCfgStatus("Datei konnte nicht gelesen werden."); } };
    reader.readAsText(f); e.target.value = "";
  };

  const toggleSum = (gid) => setVisibleSums(prev => { const n = new Set(prev); n.has(gid) ? n.delete(gid) : n.add(gid); return n; });
  const toggleAreaDesc = (gid) => setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, showDesc: !g.showDesc })));
  // VPN Connect: bereichsweite Laufzeit (gilt für ALLE VPN-Gruppen), Connection-Patch, Backup an/aus
  const setVpnTerm = (term) => setGroups(gs => gs.map(g => g.area === "vpnconnect" ? { ...g, vpnTerm: term } : (g.area === "standort" ? { ...g, subGroups: (g.subGroups || []).map(sg => sg.area === "vpnconnect" ? { ...sg, vpnTerm: term } : sg) } : g)));
  // Globale Laufzeit: setzt die Laufzeit aller Produkte/Bereiche (je Produkt auf zulässigen Wert geklemmt)
  const clampTerm = (lz, term) => { if (!lz || !lz.length) return null; if (lz.includes(term)) return term; const below = lz.filter(t => t <= term); return below.length ? Math.max(...below) : Math.min(...lz); };
  const applyTermToGroup = (g, term) => {
    if (g.area === "vpnconnect") return { ...g, vpnTerm: VPN_TERMS.includes(term) ? term : (term > 36 ? 36 : 12) };
    const ng = { ...g };
    if (g.area === "individuell") { ng.rows = (g.rows || []).map(r => ({ ...r, term })); ng.subGroups = (g.subGroups || []).map(sg => applyTermToGroup(sg, term)); return ng; }
    if (g.area === "sdwan" || g.area === "sdwanmeraki") {
      const st = SDWAN_TERMS.includes(term) ? term : (term < 12 ? 12 : 60);
      ng.sdwanTerm = st;
      ng.rows = (g.rows || []).map(r => { const p = getProduct(g.area, r.productId); if (!p || p.sdwanKind === "ad") return r; return { ...r, term: st, sdwanPrice: "" }; });
    } else {
      ng.rows = (g.rows || []).map(r => {
        const p = getProduct(g.area, r.productId);
        const ct = clampTerm(p?.laufzeiten, term);
        if (ct == null) return r;
        const keepManual = p.priceOpen || r.lineRabatt === "pricing" || r.mobileRabatt === "pricing" || r.basicRabatt === "pricing";
        if (keepManual) return { ...r, term: ct };
        const pr = resolvePrice(p, ct);
        return { ...r, term: ct, monthly: pr.monthly, oneoff: pr.oneoff };
      });
    }
    ng.subGroups = (g.subGroups || []).map(sg => applyTermToGroup(sg, term));
    return ng;
  };
  const setAllTerms = (term) => { if (!term) return; setGroups(gs => gs.map(g => applyTermToGroup(g, term))); };

  // ===== Standortangebot: Umwandlung (hin und zurück) =====
  const standortNameRefs = useRef({});
  const isStandortOffer = groups.some(g => g.area === "standort");
  const convertToStandort = () => {
    setGroups(gs => {
      const other = [];
      const siteOrder = [];
      const siteBuckets = {};
      const ensureSite = (s) => { if (!(s in siteBuckets)) { siteBuckets[s] = []; siteOrder.push(s); } return siteBuckets[s]; };
      const clone = (g) => reidGroup(JSON.parse(JSON.stringify(g)));
      for (const g of gs) {
        if (!g.area) continue;
        if (g.area === "standort") { const s = g.standortName || ""; const b = ensureSite(s); for (const sg of (g.subGroups || [])) b.push(clone({ ...sg, standortName: "" })); continue; }
        if (!isLocationArea(g.area)) { other.push(g); continue; }
        if (g.area === "vpnconnect") { ensureSite((g.vpnName || "").trim()).push(clone({ ...g, standortName: "" })); continue; }
        const bySite = {};
        for (const r of (g.rows || [])) { const s = (r.standort || "").trim(); (bySite[s] = bySite[s] || []).push(r); }
        const keys = Object.keys(bySite);
        if (!keys.length) { ensureSite("").push(clone({ ...g, standortName: "" })); continue; }
        for (const s of keys) ensureSite(s).push(clone({ ...g, rows: bySite[s], standortName: "" }));
      }
      const result = siteOrder.map(s => ({ ...newGroup(), area: "standort", rows: [], standortName: s, subGroups: siteBuckets[s] }));
      result.push(...other);
      return result.length ? result : [newGroup()];
    });
    setTimeout(() => { const gid = (groups.find(g => g.area === "standort" && !(g.standortName || "").trim()) || {}).id; const el = (gid != null && standortNameRefs.current[gid]) || Object.values(standortNameRefs.current).find(Boolean); if (el) el.focus(); }, 80);
  };
  const convertToFlat = () => {
    setGroups(gs => {
      const out = [];
      for (const g of gs) {
        if (g.area === "standort") {
          const site = (g.standortName || "").trim();
          for (const sg of (g.subGroups || [])) {
            const ng = { ...sg, standortName: "" };
            if (site) {
              if (ng.area === "vpnconnect") ng.vpnName = ng.vpnName || site;
              else ng.rows = (ng.rows || []).map(r => ({ ...r, standort: r.standort || site }));
            }
            out.push(ng);
          }
        } else out.push(g);
      }
      return out.length ? out : [newGroup()];
    });
  };
  const toggleStandortOffer = () => { if (isStandortOffer) convertToFlat(); else convertToStandort(); };
  // Standort-Header ohne Namen (Pflicht im Standortangebot)
  const emptyStandortGids = isStandortOffer ? groups.filter(g => g.area === "standort" && !(g.standortName || "").trim()).map(g => g.id) : [];
  const focusFirstEmptyStandort = () => { const gid = emptyStandortGids[0]; const el = gid != null ? standortNameRefs.current[gid] : null; if (el) el.focus(); };
  const patchVpnConn = (gid, role, patch) => setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, [role]: { ...g[role], ...patch } })));
  const setVpnAccess = (gid, role, access) => setGroups(gs => mapGroupById(gs, gid, g => {
    const fresh = { ...newVpnConn(access), id: g[role]?.id || newVpnConn().id };
    if (role === "backup") { const opt = vpnBackupOptions(g.main).find(o => o.access === access); if (opt?.bw?.length) fresh.bw = opt.bw[opt.bw.length - 1]; }
    return { ...g, [role]: fresh };
  }));
  const toggleVpnBackup = (gid) => setGroups(gs => mapGroupById(gs, gid, g => {
    if (g.backup) return { ...g, backup: null, backupPlus: false };
    const opts = vpnBackupOptions(g.main);
    if (!opts.length) return g;
    const first = opts[0];
    return { ...g, backup: { ...newVpnConn(first.access), bw: first.bw ? first.bw[first.bw.length - 1] : "" } };
  }));

  const patchGroup = (gid, p) => setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, ...p })));
  // area kann ein Top-Level-Bereich oder ein Unterbereich innerhalb eines Standortangebots sein – mapGroupById findet beide.
  const setArea = (gid, area) => setGroups(gs => {
    const existingVpnTerm = gs.find(x => x.area === "vpnconnect")?.vpnTerm;
    return mapGroupById(gs, gid, g => ({
      ...g, area,
      rows: area === "standort" || area === "vpnconnect" ? [] : [newRow()],
      subGroups: area === "standort" ? [newGroup()] : [],
      standortName: "",
      kombi: false, dpPct: 0, dpMode: "verrechnet", managed: false, customArea: "", showDesc: false,
      ...(area === "vpnconnect" ? {
        vpnTerm: VPN_TERMS.includes(Number(existingVpnTerm)) ? Number(existingVpnTerm) : 36,
        vpnName: "", main: newVpnConn("asym"), backup: null,
        citrix: false, netflow: false, advHw: false, advHwM: "0", advHwO: "0", backupPlus: false, bpM: "0", bpO: "0",
      } : {}),
    }));
  });
  const addGroup = () => setGroups(gs => [...gs, newGroup()]);
  const removeGroup = (gid) => setGroups(gs => gs.length <= 1 ? [newGroup()] : gs.filter(g => g.id !== gid));
  const moveGroupDir = (gid, dir) => setGroups(gs => { const i = gs.findIndex(g => g.id === gid), j = i + dir; if (i < 0 || j < 0 || j >= gs.length) return gs; const arr = [...gs]; [arr[i], arr[j]] = [arr[j], arr[i]]; return arr; });
  const addRow = (gid) => { const nr = newRow(); setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, rows: [...g.rows, nr] }))); setFocusRowId(nr.id); };
  const removeRow = (gid, rid) => setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, rows: g.rows.length <= 1 ? [newRow()] : g.rows.filter(r => r.id !== rid) })));
  const patchRow = (gid, rid, p) => setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, rows: g.rows.map(r => r.id === rid ? { ...r, ...p } : r) })));
  const moveRowDir = (gid, rid, dir) => setGroups(gs => mapGroupById(gs, gid, g => {
    const rows = [...g.rows];
    const i = rows.findIndex(r => r.id === rid), j = i + dir;
    if (i < 0 || j < 0 || j >= rows.length) return g;
    [rows[i], rows[j]] = [rows[j], rows[i]];
    return { ...g, rows };
  }));
  // Duplizieren: Zeile / Bereich / Unterbereich – jeweils mit frisch vergebenen IDs, direkt hinter dem Original eingefügt
  const reidGroup = (g) => ({
    ...g, id: gidc++,
    rows: (g.rows || []).map(r => ({ ...r, id: ridc++ })),
    subGroups: (g.subGroups || []).map(reidGroup),
    ...(g.area === "vpnconnect" ? { main: g.main ? { ...g.main, id: cidc++ } : g.main, backup: g.backup ? { ...g.backup, id: cidc++ } : g.backup } : {}),
  });
  const duplicateRow = (gid, rid) => setGroups(gs => mapGroupById(gs, gid, g => {
    const i = g.rows.findIndex(r => r.id === rid); if (i < 0) return g;
    const clone = { ...JSON.parse(JSON.stringify(g.rows[i])), id: ridc++ };
    const rows = [...g.rows]; rows.splice(i + 1, 0, clone); return { ...g, rows };
  }));
  const duplicateGroup = (gid) => setGroups(gs => {
    const i = gs.findIndex(g => g.id === gid); if (i < 0) return gs;
    const clone = reidGroup(JSON.parse(JSON.stringify(gs[i])));
    const arr = [...gs]; arr.splice(i + 1, 0, clone); return arr;
  });
  const duplicateSubGroup = (parentGid, sgid) => setGroups(gs => gs.map(g => {
    if (g.id !== parentGid) return g;
    const i = (g.subGroups || []).findIndex(sg => sg.id === sgid); if (i < 0) return g;
    const clone = reidGroup(JSON.parse(JSON.stringify(g.subGroups[i])));
    const arr = [...g.subGroups]; arr.splice(i + 1, 0, clone); return { ...g, subGroups: arr };
  }));

  // Standortangebot: Verwaltung der Unterbereiche selbst (Hinzufügen/Entfernen/Sortieren) – setzt am Eltern-Standort an.
  const addSubGroup = (parentGid) => setGroups(gs => gs.map(g => g.id === parentGid ? { ...g, subGroups: [...(g.subGroups || []), newGroup()] } : g));
  const removeSubGroup = (parentGid, sgid) => setGroups(gs => gs.map(g => g.id !== parentGid ? g : { ...g, subGroups: (g.subGroups || []).length <= 1 ? [newGroup()] : g.subGroups.filter(sg => sg.id !== sgid) }));
  const moveSubGroupDir = (parentGid, sgid, dir) => setGroups(gs => gs.map(g => {
    if (g.id !== parentGid) return g;
    const arr = [...(g.subGroups || [])];
    const i = arr.findIndex(sg => sg.id === sgid), j = i + dir;
    if (i < 0 || j < 0 || j >= arr.length) return g;
    [arr[i], arr[j]] = [arr[j], arr[i]];
    return { ...g, subGroups: arr };
  }));

  const findGroupById = (gid) => { for (const g of groups) { if (g.id === gid) return g; if (g.area === "standort") for (const sg of g.subGroups || []) if (sg.id === gid) return sg; } return null; };
  const onProduct = (gid, rid, area, productId) => {
    if (area === "sdwan" || area === "sdwanmeraki") {
      const grp = findGroupById(gid);
      const term = SDWAN_TERMS.includes(Number(grp?.sdwanTerm)) ? Number(grp.sdwanTerm) : 60;
      patchRow(gid, rid, { productId, qty: 1, standort: "", term, sdwanLicense: "sdwan", sdwanPrice: "", redundanz: false });
      return;
    }
    const p = getProduct(area, productId), term = defaultTerm(p), pr = resolvePrice(p, term);
    patchRow(gid, rid, { productId, qty: p?.qtyMin || 1, standort: "", term, monthly: pr.monthly, oneoff: pr.oneoff, discountFix: false, discountPct: 0, discountMode: "verrechnet", abloese: 0, teamsCoupling: false, backup: false, express: false, kanal: 0, kanalTarife: blankKanalTarife(), zweiterKanal: false, routerOpt: "", euPlus: false, worldSelect: false, basicRabatt: "", basicMode: "verrechnet", lineProvider: "dtag", onkz: "", lineRabatt: "0", mobileSpeed: "25", mobileRabatt: "0", antenne: "", businessClass: false, internetFlat: false, pureRabatt: "0" });
  };
  const onRowTerm = (gid, rid, area, term) => {
    setGroups(gs => mapGroupById(gs, gid, g => ({ ...g, rows: g.rows.map(r => {
      if (r.id !== rid) return r;
      const prod = getProduct(area, r.productId);
      if (prod?.priceOpen || r.basicRabatt === "pricing" || r.mobileRabatt === "pricing") return { ...r, term };
      const pr = resolvePrice(prod, term);
      const extra = prod?.pureDiscount && Number(term) < 36 && Number(r.pureRabatt) > 20 ? { pureRabatt: "20" } : {};
      return { ...r, term, monthly: pr.monthly, oneoff: pr.oneoff, ...extra };
    }) })));
  };

  // Standortangebote besitzen selbst keine Zeilen, sondern Unterbereiche – für Berechnungen, die über
  // alle "echten" Bereiche gehen, werden diese hier eingeklappt (jeder Unterbereich verhält sich wie eine normale Gruppe).
  const flattenGroups = (gs) => { const out = []; for (const g of gs) { if (g.area === "standort") out.push(...(g.subGroups || [])); else out.push(g); } return out; };

  const totalNST = useMemo(() => {
    let n = 0;
    for (const g of flattenGroups(groups)) for (const r of (g.rows || [])) {
      const p = getProduct(g.area, r.productId);
      if (p?.produkttyp === "basispaket") n += 5;
      else if (p?.produkttyp === "lizenz") n += Math.max(1, Number(r.qty) || 1);
    }
    return n;
  }, [groups]);

  const offerLines = useMemo(() => {
    // Verarbeitet genau einen "echten" Bereich (Top-Level-Gruppe ODER Unterbereich eines Standortangebots)
    // und hängt seine Zeilen an outLines an. clusterType ist "cluster" für normale Top-Level-Bereiche bzw.
    // "subcluster" für einen Unterbereich innerhalb eines Standortangebots (pgid = ID des Standorts).
    const processGroup = (g, outLines, clusterType, pgid) => {
      let premium = false, server = false;
      if (g.area === "sdwan" || g.area === "sdwanmeraki") {
        const { lines: gl } = buildSdwanLines(g, g.id);
        if (gl.length) {
          const headerDesc = applyAreaHeaderDesc(g, gl);
          outLines.push({ type: clusterType, name: CATALOG[g.area].label, gid: g.id, pgid, desc: headerDesc || undefined });
          outLines.push(...gl);
        }
        return { premium, server };
      }
      if (g.area === "individuell") {
        const groupLines = [];
        for (const r of g.rows) {
          const name = (r.custName || "").trim();
          const qty = Math.max(1, Number(r.qty) || 1);
          const m = (Number(r.monthly) || 0) * qty, o = (Number(r.oneoff) || 0) * qty;
          const desc = (r.custDesc || "").split("\n").map(s => s.trim()).filter(Boolean);
          if (!name && !m && !o && !desc.length) continue;
          const meta = [];
          if (r.term) meta.push("Laufzeit: " + r.term + " Monate");
          if (qty > 1) meta.push("Anzahl: " + qty);
          if (r.standort) meta.push("Standort: " + r.standort);
          groupLines.push({ type: "product", rowId: r.id, name: name || "—", desc, meta, monthly: m, oneoff: o, abloeseNote: null, rabattNote: null, gid: g.id, listMonthly: m });
        }
        if (groupLines.length) {
          const headerDesc = applyAreaHeaderDesc(g, groupLines);
          outLines.push({ type: clusterType, name: (g.customArea || "").trim() || "Individuell", gid: g.id, pgid, desc: headerDesc || undefined });
          outLines.push(...groupLines);
        }
        return { premium, server };
      }
      if (g.area === "allipbasic") {
        const groupLines = [];
        for (const r of g.rows) {
          const p = getProduct(g.area, r.productId);
          if (!p) continue;
          const qty = Math.max(1, Number(r.qty) || 1);
          const pricing = r.basicRabatt === "pricing";
          const base = resolvePrice(p, r.term);
          let prodM = pricing ? (Number(r.monthly) || 0) : base.monthly;
          const baseO = pricing ? (Number(r.oneoff) || 0) : base.oneoff;
          const desc = [...(p.desc || [])];
          if (!p.zweiterKanal) desc.push(optText("basic_kanal_incl"));
          else desc.push(r.zweiterKanal ? optText("basic_kanal_2") : optText("basic_kanal_1"));
          desc.push("Flatrate ins dt. Festnetz", "Flatrate ins dt. Mobilfunknetz", "Taktung 1:1 (sekundengenau)");
          desc.push(p.connKind === "cable" ? "Dynamische IPv6-Adresse inklusive" : "Dynamische IPv4-Adresse inklusive · feste IPv4-Adresse optional ohne Aufpreis");
          if (p.zweiterKanal && r.zweiterKanal) prodM += optPrice("basic_kanal2");
          let pct = 0;
          if (r.basicRabatt === "10") pct = 10; else if (r.basicRabatt === "20") pct = 20; else if (r.basicRabatt === "30") pct = 30;
          let addM = 0;
          { const routerOpts = BASIC_ROUTER_OPTIONS[p.connKind] || []; const chosen = routerOpts.includes(r.routerOpt) ? r.routerOpt : ""; if (chosen) { const rname = optText(`basic_router_${p.connKind}_${chosen}`); const rprice = optPrice(`basic_router_${p.connKind}_${chosen}`); addM += rprice; desc.push(rprice > 0 ? `inkl. ${rname} (+${eur(rprice)}/Monat)` : `inkl. ${rname}`); } else { desc.push("Nutzung Ihrer eigenen Hardware"); } }
          if (r.euPlus) { addM += (Number(r.term) >= 36 ? optPrice("basic_euplus_36") : optPrice("basic_euplus_24")); desc.push(optText("euplus")); }
          if (r.worldSelect) { addM += (Number(r.term) >= 36 ? optPrice("basic_worldselect_36") : optPrice("basic_worldselect_24")); desc.push(optText("worldselect")); }
          const discAmt = prodM * (pct / 100) * qty;
          const mode = r.basicMode || "verrechnet";
          const listFull = (prodM + addM) * qty;
          let monthly, rabattNote = null;
          if (pct > 0 && mode === "zeile") monthly = listFull;
          else { monthly = ((prodM * (1 - pct / 100)) + addM) * qty; if (pct > 0 && mode === "abzug") rabattNote = `inkl. ${eur(discAmt)} Rabatt`; }
          const oneoff = baseO * qty;
          const meta = [];
          if (r.term) meta.push("Laufzeit: " + r.term + " Monate");
          if (qty > 1) meta.push("Anzahl: " + qty);
          if (r.standort) meta.push("Standort: " + r.standort);
          groupLines.push({ type: "product", rowId: r.id, name: p.name, desc, meta, monthly, oneoff, abloeseNote: null, rabattNote, gid: g.id, listMonthly: listFull });
          if (pct > 0 && mode === "zeile") groupLines.push({ type: "discount", name: `Rabatt ${pct} %`, monthly: -discAmt, oneoff: 0, abloeseNote: null, rabattNote: null, gid: g.id });
        }
        if (groupLines.length) {
          const headerDesc = applyAreaHeaderDesc(g, groupLines);
          outLines.push({ type: clusterType, name: CATALOG[g.area].label, gid: g.id, pgid, desc: headerDesc || undefined });
          outLines.push(...groupLines);
        }
        return { premium, server };
      }
      const dp = hasLizenz(g) ? dpStats(g) : null;
      const effPct = dp ? Math.min(Math.max(0, Number(g.dpPct) || 0), dp.maxPct) : 0;
      const groupLines = [];
      let dpAccum = 0;
      for (const r of g.rows) {
        const p = getProduct(g.area, r.productId);
        if (!p) continue;
        if (p.premiumCti) premium = true;
        if (p.isServer) server = true;
        const qty = p.mengenBezug === "nebenstellen" ? totalNST : p.qty ? Math.max(p.qtyMin || 1, Number(r.qty) || 1) : 1;
        let perM = Number(r.monthly) || 0, perO = Number(r.oneoff) || 0;
        let pctDiscPerM = 0; // Prozent-Rabatt je Einheit (Line/Mobile/Pure) – getrennt erfasst, damit die Ersparnis korrekt zählt
        let pctRabattLabel = "";
        // Fixpreis-Produkte (Asymmetrisch, Wave, SIP, Digital Phone, MDM …) live aus dem editierbaren Katalog,
        // damit Preisänderungen im Bearbeiten-Reiter sofort auch auf bereits platzierte Zeilen wirken.
        if (!p.priceOpen && !p.office && !p.mobile && ("m24" in p || "m36" in p)) { const pr = resolvePrice(p, r.term); perM = pr.monthly; perO = pr.oneoff; }
        let lineDesc = [...(p.desc || [])];
        if (p.pure && p.pureDiscount) {
          const maxPct = Number(r.term) >= 36 ? 30 : 20;
          const pct = Math.min(Math.max(0, Number(r.pureRabatt) || 0), maxPct);
          if (pct > 0) { pctDiscPerM += perM * pct / 100; pctRabattLabel = `Rabatt ${pct} %`; }
        }
        if (p.extrasKind === "line" && r.lineProvider === "dtag" && r.lineRabatt !== "pricing") {
          const b = lineDtagBase(p, r);
          let lpct = 0; if (r.lineRabatt === "10") lpct = 10; else if (r.lineRabatt === "20") lpct = 20; else if (r.lineRabatt === "30") lpct = 30;
          perM = b.monthly;
          if (lpct > 0) { pctDiscPerM += b.monthly * lpct / 100; pctRabattLabel = `Rabatt ${lpct} %`; }
          perO = b.oneoff;
        }
        if (p.mobile) {
          const b = mobileMainBase(r);
          if (r.mobileRabatt !== "pricing") {
            let mpct = 0; if (r.mobileRabatt === "10") mpct = 10; else if (r.mobileRabatt === "20") mpct = 20; else if (r.mobileRabatt === "30") mpct = 30;
            perM = b.baseM + b.speedM + b.flatM;
            if (mpct > 0) { pctDiscPerM += (b.baseM + b.speedM) * mpct / 100; pctRabattLabel = `Rabatt ${mpct} %`; }
            perO = b.baseO;
          }
          lineDesc.push(`Geschwindigkeit: ${b.speed.label}`, "Internet-Flat inklusive");
          if (b.speedUnpriced) lineDesc.push("⚠️ Preis für diese Geschwindigkeit noch offen – bitte ergänzen");
          const ant = antennaFor(r.antenne);
          if (ant) { perO += ant.oneoff; lineDesc.push(`inkl. Antenne ${ant.label}`); }
        }
        if (p.wave && r.businessClass) { perM += WAVE_SUPPORT_PRICE; lineDesc.push(optText("wave_support")); }
        if (p.inetFlat && r.internetFlat) { const f = Number(r.term) >= 36 ? INET_FLAT.m36 : INET_FLAT.m24; perM += f; lineDesc.push(`Internet-Flatrate (${eur(f)}/Monat, nicht rabattiert)`); }
        if (p.office) { perM = officePrice(p, g.managed); perO = 0; if (officeNoMgmt(p, g.managed)) lineDesc.push("Kein Management durch Telefónica"); }
        if (p.teamsCoupling && r.teamsCoupling) { perM += 4; lineDesc.push(optText("teams")); }
        if (p.maxKanal && r.kanal > 0) {
          for (const dir of RICHTUNGEN) {
            const t = (r.kanalTarife && r.kanalTarife[dir.key]) || { mode: "standard", min: 0 };
            if (t.mode === "fairuse") { perM += dir.fair * r.kanal; lineDesc.push(dir.fairText); }
            else if (t.mode === "pooling") { const mins = Number(t.min) || 0; perM += mins * dir.poolCt / 100; lineDesc.push(`${mins} Minuten in Richtung ${dir.label}`); }
            else if (dir.key === "fest" || dir.key === "mob") lineDesc.push(`Minutenabrechnung in Richtung ${dir.label}`);
          }
        }
        if (p.extrasKind && r.backup) {
          perM += backupMonthly(p.extrasKind, r.term);
          let bdesc = [...optText("backup")];
          if (p.maxKanal && r.kanal > 0) {
            const kt = r.kanal > 16 ? optText("backup_kanal_16") : optText("backup_kanal_all");
            bdesc = bdesc.map(line => line === optText("backup")[2] ? kt : line);
          }
          lineDesc = lineDesc.concat(bdesc);
          const ant = antennaFor(r.antenne);
          if (ant) { perO += ant.oneoff; lineDesc.push(`inkl. Antenne ${ant.label}`); }
        }
        if (p.extrasKind && r.backup && r.express) { perO += EXPRESS_PRICE[p.extrasKind]; lineDesc = lineDesc.concat(optText("express")); }
        const baseM = perM * qty, baseO = perO * qty;
        let amt = 0, dlabel = "";
        if (p.rabattType === "fix10" && r.discountFix) { amt = 10; dlabel = "Bestandskundenrabatt (−10 €)"; }
        else if (p.rabattType === "staffelMax" && Number(r.discountPct) > 0) { const mx = staffelPct(p.staffel, qty); const pct = Math.min(mx, Number(r.discountPct)); amt = baseM * pct / 100; dlabel = `Mengenrabatt ${pct} %`; }
        else if (p.rabattType === "officeMax" && Number(r.discountPct) > 0) { const mx = officeMax(p, g.managed); const pct = Math.min(mx, Number(r.discountPct)); amt = baseM * pct / 100; dlabel = `Rabatt ${pct} %`; }
        if (pctDiscPerM > 0) { amt += pctDiscPerM * qty; if (!dlabel) dlabel = pctRabattLabel; }
        let dpAmt = 0;
        if (dp && effPct > 0 && p.produkttyp === "lizenz") dpAmt = baseM * effPct / 100;
        const rowReduces = r.discountMode === "verrechnet" || r.discountMode === "abzug";
        const dpReduces = g.dpMode === "verrechnet" || g.dpMode === "abzug";
        const verrLine = (rowReduces ? amt : 0) + (dpReduces ? dpAmt : 0);
        const shownM = baseM - verrLine;
        let rabattAmt = 0;
        if (r.discountMode === "abzug") rabattAmt += amt;
        if (g.dpMode === "abzug") rabattAmt += dpAmt;
        const rabattNote = rabattAmt > 0 ? `inkl. ${eur(rabattAmt)} Rabatt` : null;
        const meta = [];
        if (r.term) meta.push("Laufzeit: " + r.term + " Monate");
        if (p.maxKanal && r.kanal > 0) meta.push("Sprachkanäle: " + r.kanal);
        if (p.mengenBezug === "nebenstellen") meta.push("Nebenstellen: " + qty);
        else if (p.qty) meta.push((p.qtyLabel || "Menge") + ": " + qty);
        if (r.standort) meta.push("Standort: " + r.standort);
        if (p.extrasKind === "line" && r.lineProvider === "dtag" && r.onkz) meta.push("ONKZ: " + r.onkz);
        const abloeseNote = p.abloese && Number(r.abloese) > 0 ? `inkl. ${r.abloese} Monate Basispreisbefreiung` : null;
        groupLines.push({ type: "product", rowId: r.id, name: p.name, desc: lineDesc, meta, monthly: shownM, oneoff: baseO, abloeseNote, rabattNote, gid: g.id, listMonthly: baseM });
        if (p.wave) groupLines.push({ type: "auto", name: optText("wave_los_name"), desc: [optText("wave_los_desc")], monthly: 0, oneoff: WAVE_LOS_PRICE, abloeseNote: null, gid: g.id });
        if (amt > 0 && r.discountMode === "zeile") groupLines.push({ type: "discount", name: dlabel, monthly: -amt, oneoff: 0, abloeseNote: null, gid: g.id });
        if (dpAmt > 0 && g.dpMode === "zeile") dpAccum += dpAmt;
      }
      if (dpAccum > 0) groupLines.push({ type: "discount", name: `${g.kombi ? "Kombi-" : ""}Umsatzrabatt ${effPct} %`, monthly: -dpAccum, oneoff: 0, abloeseNote: null, gid: g.id });
      if (groupLines.length) {
        const headerDesc = applyAreaHeaderDesc(g, groupLines);
        outLines.push({ type: clusterType, name: CATALOG[g.area].label + (g.area === "office365" && g.managed ? " (Managed)" : ""), gid: g.id, pgid, desc: headerDesc || undefined });
        outLines.push(...groupLines);
      }
      return { premium, server };
    };

    const lines = [];
    let hasPremium = false, hasServer = false;
    for (const g of groups) {
      if (!g.area) continue;
      if (g.area === "standort") {
        const subLines = [];
        for (const sg of g.subGroups || []) {
          if (!sg.area) continue;
          if (sg.area === "vpnconnect") {
            const vl = buildVpnLines(sg, resolveVpnDesc(overrides.vpnDesc));
            for (const l of vl) { if (l.type === "cluster") subLines.push({ ...l, type: "subcluster", pgid: g.id }); else subLines.push(l); }
            continue;
          }
          const res = processGroup(sg, subLines, "subcluster", g.id);
          hasPremium = hasPremium || res.premium;
          hasServer = hasServer || res.server;
        }
        if (subLines.length) {
          lines.push({ type: "cluster", name: (g.standortName || "").trim() || "Standortangebot", gid: g.id });
          lines.push(...subLines);
        }
        continue;
      }
      if (g.area === "vpnconnect") { lines.push(...buildVpnLines(g, resolveVpnDesc(overrides.vpnDesc))); continue; }
      const res = processGroup(g, lines, "cluster");
      hasPremium = hasPremium || res.premium;
      hasServer = hasServer || res.server;
    }
    if (hasPremium && !hasServer) lines.push({ type: "auto", name: "CTI Premium Server (Einrichtung)", desc: ["Einmalig bei Buchung von CTI-Premium-Lizenzen"], monthly: 0, oneoff: 299, abloeseNote: null, gid: null });
    return lines;
  }, [groups, totalNST, catVer]);

  const sumM = offerLines.reduce((a, l) => a + (l.monthly || 0), 0);
  const sumO = offerLines.reduce((a, l) => a + (l.oneoff || 0), 0);

  const clusterSumsData = useMemo(() => {
    const d = {}; let curr = null;
    for (const l of offerLines) {
      if (l.type === "cluster" || l.type === "subcluster") { curr = l.gid; d[curr] = { name: l.name, sumM: 0, sumO: 0 }; }
      else if (curr) { d[curr].sumM += l.monthly || 0; d[curr].sumO += l.oneoff || 0; }
    }
    return d;
  }, [offerLines]);

  const standortSums = useMemo(() => {
    const d = {};
    for (const g of groups) {
      if (g.area !== "standort") continue;
      let sm = 0, so = 0;
      for (const sg of g.subGroups || []) { const c = clusterSumsData[sg.id]; if (c) { sm += c.sumM; so += c.sumO; } }
      d[g.id] = { sumM: sm, sumO: so };
    }
    return d;
  }, [groups, clusterSumsData]);

  const savingsData = useMemo(() => {
    let savM = 0;
    for (const l of offerLines) {
      if (l.type === "product") savM += (l.listMonthly || 0) - l.monthly;
      if (l.type === "discount") savM -= l.monthly;
    }
    return { savM: Math.max(0, savM), savO: 0 };
  }, [offerLines]);
  const istM = Number(istKosten) || 0;
  const vsIst = istM > 0;
  const effSavM = vsIst ? Math.max(0, istM - sumM) : savingsData.savM;

  const primaryTerm = useMemo(() => {
    const counts = {};
    const vote = (t, w = 1) => { if (t) counts[t] = (counts[t] || 0) + w; };
    for (const g of flattenGroups(groups)) {
      if (g.area === "vpnconnect") { const w = (g.main?.access ? 1 : 0) + (g.backup?.access ? 1 : 0) || 1; vote(g.vpnTerm, w); continue; }
      for (const r of (g.rows || [])) vote(r.term);
    }
    const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
    return sorted.length ? Number(sorted[0][0]) : 0;
  }, [groups]);

  const totalSavings = effSavM * primaryTerm + savingsData.savO;
  const checkableLines = useMemo(() => offerLines.filter(l => l.type === "product" || l.type === "discount" || l.type === "auto"), [offerLines]);
  // Header-gid (Cluster/Subcluster) -> zugehörige kopierbare Zeilen, für die Sammel-Checkbox je Bereich/Unterbereich.
  // Ein Cluster sammelt ALLE Zeilen darunter (auch innerhalb seiner Subcluster); ein Subcluster nur seine eigenen.
  const headerLineGroups = useMemo(() => {
    const map = {}; let curClusterGid = null, curSubclusterGid = null;
    for (const l of offerLines) {
      if (l.type === "cluster") { curClusterGid = l.gid; curSubclusterGid = null; if (!map[curClusterGid]) map[curClusterGid] = []; continue; }
      if (l.type === "subcluster") { curSubclusterGid = l.gid; if (!map[curSubclusterGid]) map[curSubclusterGid] = []; continue; }
      if (l.type !== "product" && l.type !== "discount" && l.type !== "auto") continue;
      if (curClusterGid != null) map[curClusterGid].push(l);
      if (curSubclusterGid != null) map[curSubclusterGid].push(l);
    }
    return map;
  }, [offerLines]);
  // ===== Suche/Filter im Kalkulator: Standort, Bereich/Produkt oder Option/Tarif =====
  const [searchQuery, setSearchQuery] = useState("");
  const searchMatch = useMemo(() => {
    const q = searchQuery.trim().toLowerCase();
    if (!q) return null; // keine Suche aktiv -> nichts filtern
    const matchedGids = new Set(); const matchedKeys = new Set();
    let clusterGid = null, clusterSelfMatch = false, subclusterGid = null, subclusterSelfMatch = false;
    for (const l of offerLines) {
      if (l.type === "cluster") { clusterGid = l.gid; clusterSelfMatch = (l.name || "").toLowerCase().includes(q); subclusterGid = null; subclusterSelfMatch = false; if (clusterSelfMatch) matchedGids.add(clusterGid); continue; }
      if (l.type === "subcluster") { subclusterGid = l.gid; subclusterSelfMatch = (l.name || "").toLowerCase().includes(q); if (subclusterSelfMatch) { matchedGids.add(subclusterGid); if (clusterGid != null) matchedGids.add(clusterGid); } continue; }
      const text = [l.name, ...(l.desc || []), ...(l.meta || [])].join(" ").toLowerCase();
      if (clusterSelfMatch || subclusterSelfMatch || text.includes(q)) {
        matchedKeys.add(lineKey(l));
        if (clusterGid != null) matchedGids.add(clusterGid);
        if (subclusterGid != null) matchedGids.add(subclusterGid);
        matchedGids.add(l.gid);
      }
    }
    return { matchedGids, matchedKeys };
  }, [offerLines, searchQuery]);
  const searchHitCount = searchMatch ? checkableLines.filter(l => searchMatch.matchedKeys.has(lineKey(l))).length : null;
  const groupVisible = (gid) => !searchMatch || searchMatch.matchedGids.has(gid);
  const lineVisibleInSearch = (l) => !searchMatch || searchMatch.matchedKeys.has(lineKey(l));
  const allLinesChecked = checkableLines.length === 0 || checkableLines.every(l => isLineIncluded(l));
  const toggleAllLines = () => {
    setExcludedLines(prev => {
      const n = new Set(prev);
      for (const l of checkableLines) { const k = lineKey(l); if (allLinesChecked) n.add(k); else n.delete(k); }
      return n;
    });
  };
  // Für Copy/PowerPoint: nur angehakte Zeilen; Bereichs-/Unterbereichs-Header entfallen automatisch, wenn darunter nichts angehakt ist.
  const filteredOfferLinesForCopy = () => {
    const out = []; let pendingCluster = null, pendingSubcluster = null;
    for (const l of offerLines) {
      if (l.type === "cluster") { pendingCluster = l; pendingSubcluster = null; continue; }
      if (l.type === "subcluster") { pendingSubcluster = l; continue; }
      if (!isLineIncluded(l)) continue;
      if (pendingCluster) { out.push(pendingCluster); pendingCluster = null; }
      if (pendingSubcluster) { out.push(pendingSubcluster); pendingSubcluster = null; }
      out.push(l);
    }
    return out;
  };
  // Bereichs-/Unterbereichssummen passend zur Kopier-Auswahl (nur angehakte Zeilen zählen mit)
  const copyClusterSums = (exportLines) => {
    const d = {}; let curr = null;
    for (const l of exportLines) {
      if (l.type === "cluster" || l.type === "subcluster") { curr = l.gid; d[curr] = { name: l.name, sumM: 0, sumO: 0 }; }
      else if (curr) { d[curr].sumM += l.monthly || 0; d[curr].sumO += l.oneoff || 0; }
    }
    return d;
  };

  // Plausibilitäts-Ampel: konkrete Hinweise zu fehlenden/auffälligen Angaben
  const plausibility = useMemo(() => {
    const w = [];
    let totalSdwanHardware = 0;
    const checkRow = (area, label, r, gid) => {
      const p = getProduct(area, r.productId);
      if (!p) return;
      if (p.mobile) { const b = mobileMainBase(r); if (b.speedUnpriced) w.push({ level: "warn", gid, msg: `${label} · ${p.name}: Preis für Geschwindigkeit „${b.speed.label}" ist noch nicht hinterlegt.` }); }
      if (p.id === "dp-ccm" && Number(r.qty) > 0 && Number(r.qty) < (p.qtyMin || 5)) w.push({ level: "info", gid, msg: `${label} · Call Center Monitoring: mind. ${p.qtyMin} Agents – wird automatisch mit ${p.qtyMin} berechnet.` });
      if (p.extrasKind === "line" && r.lineProvider === "dtag" && r.lineRabatt !== "pricing" && !r.onkz) w.push({ level: "warn", gid, msg: `${label} · ${p.name}: ONKZ fehlt – Link-/Leitungspreis kann nicht korrekt ermittelt werden.` });
      if (p.priceOpen && !(Number(r.monthly) > 0) && !(Number(r.oneoff) > 0)) w.push({ level: "info", gid, msg: `${label} · ${p.name}: Position ohne Preis (0 €) – bitte prüfen.` });
    };
    for (const g of groups) {
      if (!g.area) continue;
      const label = CATALOG[g.area]?.label || g.area;
      if (g.area === "sdwan" || g.area === "sdwanmeraki") { totalSdwanHardware += buildSdwanLines(g).standorte; }
      for (const r of (g.rows || [])) checkRow(g.area, label, r, g.id);
      for (const sg of (g.subGroups || [])) {
        const sl = `${label} · ${CATALOG[sg.area]?.label || sg.area}`;
        if (sg.area === "sdwan" || sg.area === "sdwanmeraki") { totalSdwanHardware += buildSdwanLines(sg).standorte; }
        for (const r of (sg.rows || [])) checkRow(sg.area, sl, r, sg.id);
      }
      if (g.area === "vpnconnect") {
        const nm = (g.vpnName || "").trim() || "VPN-Standort";
        const cc = (c, role) => { if (!c || !c.access) return; if (c.access === "link" && c.provider === "dtag" && c.rabatt !== "pricing" && !c.onkz) w.push({ level: "warn", gid: g.id, msg: `${nm} (${role}): ONKZ für Link-Anschluss fehlt.` }); if ((c.rabatt === "pricing" || c.provider === "andere") && !(Number(c.manMonthly) > 0)) w.push({ level: "info", gid: g.id, msg: `${nm} (${role}): manueller Preis ist 0 € – bitte prüfen.` }); };
        cc(g.main, "Hauptanschluss"); cc(g.backup, "Backup");
      }
    }
    // Mindestabnahme SD-WAN: 2 Geräte insgesamt, Fortinet + Meraki zusammengezählt, unabhängig vom Standort
    if (totalSdwanHardware === 1) w.push({ level: "warn", gid: null, msg: `SD-WAN: insgesamt nur 1 Gerät im gesamten Angebot (Fortinet + Meraki zusammengezählt) – mindestens 2 Standorte/Geräte erforderlich.` });
    return w;
  }, [groups, catVer]);

  // ===== Export =====
  const buildHtml = () => {
    const cb = "padding:8px 12px;border:1px solid #cccccc;font-family:Arial,Helvetica,sans-serif;font-size:11pt;vertical-align:top;";
    const th = (t, a = "left") => `<th style="${cb}background:${TEF_BLUE};color:#fff;text-align:${a};font-weight:bold;">${t}</th>`;
    const td = (t, a = "left", fs = "11pt") => `<td style="${cb.replace("11pt", fs)}color:${TEF_GRAY};text-align:${a};">${t}</td>`;
    let body = "", currGid = null;
    const exportLines = filteredOfferLinesForCopy();
    const copySums = copyClusterSums(exportLines);
    for (let i = 0; i < exportLines.length; i++) {
      const l = exportLines[i], next = exportLines[i + 1];
      if (l.type === "cluster") { currGid = l.gid; const cd = l.desc?.length ? `<div style="font-weight:normal;font-size:9.5pt;color:${TEF_GRAY};margin-top:3px;">${l.desc.join("<br>")}</div>` : ""; body += `<tr><td colspan="4" style="${cb}background:#D7E0FF;color:${TEF_BLUE};font-weight:bold;">${l.name}${cd}</td></tr>`; }
      else if (l.type === "subcluster") { currGid = l.gid; const cd = l.desc?.length ? `<div style="font-weight:normal;font-size:9.5pt;color:${TEF_GRAY};margin-top:2px;">${l.desc.join("<br>")}</div>` : ""; body += `<tr><td colspan="4" style="${cb}background:#EAF0FF;color:${TEF_BLUE};font-weight:600;padding-left:24px;">${l.name}${cd}</td></tr>`; }
      else {
        const meta = l.meta?.length ? `<br><span style="font-size:9pt;color:#777;">${l.meta.join(" · ")}</span>` : "";
        const mcell = eur(l.monthly) + [l.abloeseNote, l.rabattNote].filter(Boolean).map(n => `<br><span style="color:${TEF_RED};font-size:8.5pt;">${n}</span>`).join("");
        body += `<tr>${td(l.name + meta)}${td((l.desc || []).join("<br>"), "left", "9.5pt")}${td(mcell, "right")}${td(eur(l.oneoff), "right")}</tr>`;
        if (currGid && (visibleSums.has(currGid) || showAllSums) && (!next || next.type === "cluster" || next.type === "subcluster")) {
          const s = copySums[currGid];
          if (s) { const ss2 = `${cb}background:#E8EDFF;color:${TEF_BLUE};font-weight:600;`; body += `<tr><td colspan="2" style="${ss2}">Summe ${s.name}</td><td style="${ss2}text-align:right;">${eur(s.sumM)}</td><td style="${ss2}text-align:right;">${eur(s.sumO)}</td></tr>`; }
        }
      }
    }
    const ss = `${cb}background:#E6ECFF;color:${TEF_BLUE};font-weight:bold;`;
    const copySumM = exportLines.filter(l => l.type === "product" || l.type === "discount" || l.type === "auto").reduce((a, l) => a + (l.monthly || 0), 0);
    const copySumO = exportLines.filter(l => l.type === "product" || l.type === "discount" || l.type === "auto").reduce((a, l) => a + (l.oneoff || 0), 0);
    body += `<tr><td style="${ss}" colspan="2">Gesamtsumme</td><td style="${ss}text-align:right;">${eur(copySumM)}</td><td style="${ss}text-align:right;">${eur(copySumO)}</td></tr>`;
    if (showTCO && primaryTerm > 0) { const ts = `${cb}background:#EEF2FF;color:${TEF_BLUE};font-weight:600;`; body += `<tr><td style="${ts}" colspan="2">Gesamtkosten über die Laufzeit</td><td style="${ts}text-align:right;" colspan="2">${eur(sumM * primaryTerm + sumO)}</td></tr>`; }
    if (showSavings && effSavM > 0) {
      const rs = `${cb}background:#FFF5F5;color:${TEF_RED};font-weight:600;`;
      const vsLabel = vsIst ? "gegenüber Ihren bisherigen Kosten" : "gegenüber Listenpreis";
      body += `<tr><td style="${rs}" colspan="2">Ihre monatliche Ersparnis ${vsLabel}</td><td style="${rs}text-align:right;" colspan="2">${eur(effSavM)}</td></tr>`;
      if (primaryTerm > 0) body += `<tr><td style="${rs}" colspan="2">Ihre Gesamtersparnis über die Laufzeit</td><td style="${rs}text-align:right;" colspan="2">${eur(totalSavings)}</td></tr>`;
    }
    const titleRow = `<tr><td colspan="4" style="padding:8px 12px;border:none;border-bottom:1px solid #cccccc;background:#ffffff;"><div style="font-size:14pt;font-weight:bold;color:${TEF_BLUE};margin:0 0 2px 0;">Angebot${customer ? " – " + customer : ""}</div><div style="font-size:10pt;font-weight:normal;color:${TEF_GRAY};margin:0;">Stand: ${today}</div></td></tr>`;
    const footRow = `<tr><td colspan="4" style="padding:6px 12px;border:none;border-top:1px solid #cccccc;background:#ffffff;font-family:Arial,Helvetica,sans-serif;font-size:8pt;color:#888888;">${FOOTNOTE}</td></tr>`;
    return `<table style="border-collapse:collapse;font-family:Arial,Helvetica,sans-serif;">${titleRow}<thead><tr>${th("Produkt")}${th("Beschreibung")}${th("Monatlich", "right")}${th("Einmalig", "right")}</tr></thead><tbody>${body}${footRow}</tbody></table>`;
  };

  const buildText = () => {
    const lines = []; let currGid = null;
    const exportLines = filteredOfferLinesForCopy();
    const copySums = copyClusterSums(exportLines);
    for (let i = 0; i < exportLines.length; i++) {
      const l = exportLines[i], next = exportLines[i + 1];
      if (l.type === "cluster") { currGid = l.gid; lines.push(`\n■ ${l.name}`); if (l.desc?.length) for (const d of l.desc) lines.push(`   ${d}`); }
      else if (l.type === "subcluster") { currGid = l.gid; lines.push(`  ▸ ${l.name}`); if (l.desc?.length) for (const d of l.desc) lines.push(`     ${d}`); }
      else {
        const meta = l.meta?.length ? ` (${l.meta.join(" · ")})` : "";
        lines.push(`${l.name}${meta}\t${(l.desc || []).join("; ")}\t${eur(l.monthly)}${[l.abloeseNote, l.rabattNote].filter(Boolean).map(n => ` [${n}]`).join("")}\t${eur(l.oneoff)}`);
        if (currGid && (visibleSums.has(currGid) || showAllSums) && (!next || next.type === "cluster" || next.type === "subcluster")) { const s = copySums[currGid]; if (s) lines.push(`Summe ${s.name}\t\t${eur(s.sumM)}\t${eur(s.sumO)}`); }
      }
    }
    const copySumM = exportLines.filter(l => l.type === "product" || l.type === "discount" || l.type === "auto").reduce((a, l) => a + (l.monthly || 0), 0);
    const copySumO = exportLines.filter(l => l.type === "product" || l.type === "discount" || l.type === "auto").reduce((a, l) => a + (l.oneoff || 0), 0);
    lines.push(`Gesamtsumme\t\t${eur(copySumM)}\t${eur(copySumO)}`);
    if (showTCO && primaryTerm > 0) lines.push(`Gesamtkosten über die Laufzeit\t\t${eur(sumM * primaryTerm + sumO)}`);
    if (showSavings && effSavM > 0) {
      const vsLabel = vsIst ? "ggü. Ihren bisherigen Kosten" : "ggü. Listenpreis";
      lines.push(`Ihre monatliche Ersparnis ${vsLabel}\t\t${eur(effSavM)}`);
      if (primaryTerm > 0) lines.push(`Ihre Gesamtersparnis über die Laufzeit\t\t${eur(totalSavings)}`);
    }
    return [`Angebot${customer ? " – " + customer : ""} (Stand ${today})`, `Produkt\tBeschreibung\tMonatlich\tEinmalig`, ...lines, "", FOOTNOTE].join("\n");
  };

  const copyOffer = async () => {
    try {
      if (navigator.clipboard && window.ClipboardItem) {
        await navigator.clipboard.write([new ClipboardItem({ "text/html": new Blob([buildHtml()], { type: "text/html" }), "text/plain": new Blob([buildText()], { type: "text/plain" }) })]);
      } else await navigator.clipboard.writeText(buildText());
      setCopied(true); setTimeout(() => setCopied(false), 2000);
    } catch { alert("Kopieren nicht möglich – bitte Tabelle manuell markieren."); }
  };

  const buildState = () => JSON.stringify({ app: "tef-kalkulator", version: 10, savedAt: new Date().toISOString(), customer, groups }, null, 2);
  const suggestedName = () => {
    const d = new Date();
    const yy = String(d.getFullYear()).slice(2), mm = String(d.getMonth() + 1).padStart(2, "0"), dd = String(d.getDate()).padStart(2, "0");
    return `${yy}${mm}${dd} ${(customer || "ohne Kunde").replace(/[<>:"/\\|?*]+/g, "").trim()}`;
  };

  const buildJpg = () => new Promise((resolve) => {
    const PAD = 40, SC = 2, COL = [320, 260, 120, 120], TW = COL.reduce((a, b) => a + b, 0), W = TW + PAD * 2;
    const HDR_H = 40, ROW_H = 32, CLUST_H = 28;
    const noteLines = (l) => (l.abloeseNote ? 1 : 0) + (l.rabattNote ? 1 : 0);
    const rowHeight = (l) => Math.max(ROW_H, 14 + (l.desc || []).length * 13, 20 + noteLines(l) * 13);
    const clusterHeight = (l) => CLUST_H + (l.desc?.length ? l.desc.length * 12 + 4 : 0);
    let totalH = PAD * 2 + 56 + HDR_H + ROW_H;
    for (const l of offerLines) totalH += (l.type === "cluster" || l.type === "subcluster") ? clusterHeight(l) : rowHeight(l);
    if (showTCO && primaryTerm > 0) totalH += ROW_H;
    if (showSavings && effSavM > 0) { totalH += ROW_H; if (primaryTerm > 0) totalH += ROW_H; }
    totalH += 22; // Footnote
    const cv = document.createElement("canvas");
    cv.width = W * SC; cv.height = totalH * SC;
    const cx = cv.getContext("2d"); cx.scale(SC, SC);
    cx.fillStyle = "#F4F6FB"; cx.fillRect(0, 0, W, totalH);
    cx.fillStyle = TEF_BLUE; cx.font = "bold 17px Arial,sans-serif"; cx.fillText(`Angebot${customer ? " – " + customer : ""}`, PAD, PAD + 20);
    cx.fillStyle = "#555"; cx.font = "12px Arial,sans-serif"; cx.fillText(`Stand: ${today}`, PAD, PAD + 38);
    let y = PAD + 56;
    const box = (x, yy, w, h, fill) => { cx.fillStyle = fill; cx.fillRect(x, yy, w, h); cx.strokeStyle = "#ccc"; cx.lineWidth = 0.5; cx.strokeRect(x, yy, w, h); };
    const txt = (s, x, yy, col, bold, size = 11, align = "left") => { cx.fillStyle = col; cx.font = `${bold ? "bold " : ""}${size}px Arial,sans-serif`; cx.textAlign = align; cx.fillText(String(s), x, yy); cx.textAlign = "left"; };
    let x = PAD;
    const hdrs = ["Produkt", "Beschreibung", "Monatlich", "Einmalig"], alns = ["left","left","right","right"];
    for (let i = 0; i < 4; i++) { box(x, y, COL[i], HDR_H, TEF_BLUE); txt(hdrs[i], alns[i]==="right"?x+COL[i]-8:x+8, y+26, "#fff", true, 12, alns[i]); x += COL[i]; }
    y += HDR_H;
    let currGid2 = null;
    for (let i = 0; i < offerLines.length; i++) {
      const l = offerLines[i], next = offerLines[i + 1]; x = PAD;
      if (l.type === "cluster") { currGid2 = l.gid; const ch = clusterHeight(l); box(x, y, TW, ch, "#D7E0FF"); txt(l.name, x+8, y+19, TEF_BLUE, true, 12); if (l.desc?.length) { let dy = y + CLUST_H + 4; for (const d of l.desc) { txt(d.length > 90 ? d.slice(0, 90) + "…" : d, x+8, dy, TEF_GRAY, false, 10); dy += 12; } } y += ch; }
      else if (l.type === "subcluster") { currGid2 = l.gid; const ch = clusterHeight(l); box(x, y, TW, ch, "#EAF0FF"); txt(l.name, x+24, y+18, TEF_BLUE, true, 11); if (l.desc?.length) { let dy = y + CLUST_H + 3; for (const d of l.desc) { txt(d.length > 90 ? d.slice(0, 90) + "…" : d, x+24, dy, TEF_GRAY, false, 9.5); dy += 12; } } y += ch; }
      else {
        const desc = l.desc || [], rh = rowHeight(l), bg = l.type==="product"?"#fff":"#f7f7f7", fg = l.type==="product"?TEF_GRAY:"#888";
        box(x,y,COL[0],rh,bg); txt(l.name,x+8,y+16,fg,l.type==="product",11); if(l.meta?.length) txt(l.meta.join(" · "),x+8,y+28,"#999",false,9); x+=COL[0];
        box(x,y,COL[1],rh,bg); let dy=y+13; for(const d of desc){txt(d.length>44?d.slice(0,44)+"…":d,x+8,dy,"#666",false,10); dy+=13;} x+=COL[1];
        box(x,y,COL[2],rh,bg); txt(eur(l.monthly),x+COL[2]-8,y+20,fg,l.type==="product",11,"right");
        { let ny=y+20; if(l.abloeseNote){ny+=13; txt(l.abloeseNote,x+COL[2]-8,ny,TEF_RED,false,8.5,"right");} if(l.rabattNote){ny+=13; txt(l.rabattNote,x+COL[2]-8,ny,TEF_RED,false,8.5,"right");} } x+=COL[2];
        box(x,y,COL[3],rh,bg); txt(eur(l.oneoff),x+COL[3]-8,y+20,fg,l.type==="product",11,"right"); y+=rh;
        if (currGid2 && (visibleSums.has(currGid2) || showAllSums) && (!next || next.type === "cluster" || next.type === "subcluster")) {
          const s = clusterSumsData[currGid2];
          if (s) { x=PAD; box(x,y,COL[0]+COL[1],ROW_H,"#E8EDFF"); txt(`Summe ${s.name}`,x+8,y+21,TEF_BLUE,true,11); x+=COL[0]+COL[1]; box(x,y,COL[2],ROW_H,"#E8EDFF"); txt(eur(s.sumM),x+COL[2]-8,y+21,TEF_BLUE,true,11,"right"); x+=COL[2]; box(x,y,COL[3],ROW_H,"#E8EDFF"); txt(eur(s.sumO),x+COL[3]-8,y+21,TEF_BLUE,true,11,"right"); y+=ROW_H; }
        }
      }
    }
    x=PAD; box(x,y,COL[0]+COL[1],ROW_H,"#E6ECFF"); txt("Gesamtsumme",x+8,y+21,TEF_BLUE,true,12); x+=COL[0]+COL[1]; box(x,y,COL[2],ROW_H,"#E6ECFF"); txt(eur(sumM),x+COL[2]-8,y+21,TEF_BLUE,true,12,"right"); x+=COL[2]; box(x,y,COL[3],ROW_H,"#E6ECFF"); txt(eur(sumO),x+COL[3]-8,y+21,TEF_BLUE,true,12,"right"); y+=ROW_H;
    if (showTCO && primaryTerm > 0) { x=PAD; box(x,y,COL[0]+COL[1],ROW_H,"#EEF2FF"); txt("Gesamtkosten über die Laufzeit",x+8,y+21,TEF_BLUE,true,11); x+=COL[0]+COL[1]; box(x,y,COL[2]+COL[3],ROW_H,"#EEF2FF"); txt(eur(sumM * primaryTerm + sumO),x+COL[2]+COL[3]-8,y+21,TEF_BLUE,true,11,"right"); y+=ROW_H; }
    if (showSavings && effSavM > 0) {
      const vsLabelJ = vsIst ? "Monatliche Ersparnis ggü. bisherigen Kosten" : "Monatliche Ersparnis ggü. Listenpreis";
      x=PAD; box(x,y,COL[0]+COL[1],ROW_H,"#FFF5F5"); txt(vsLabelJ,x+8,y+21,TEF_RED,true,11); x+=COL[0]+COL[1]; box(x,y,COL[2]+COL[3],ROW_H,"#FFF5F5"); txt(eur(effSavM),x+COL[2]+COL[3]-8,y+21,TEF_RED,true,11,"right"); y+=ROW_H;
      if (primaryTerm > 0) { x=PAD; box(x,y,COL[0]+COL[1],ROW_H,"#FFF5F5"); txt(`Gesamtersparnis über die Laufzeit`,x+8,y+21,TEF_RED,true,11); x+=COL[0]+COL[1]; box(x,y,COL[2]+COL[3],ROW_H,"#FFF5F5"); txt(eur(totalSavings),x+COL[2]+COL[3]-8,y+21,TEF_RED,true,11,"right"); y+=ROW_H; }
    }
    y += 6; txt(FOOTNOTE, PAD, y + 10, "#999", false, 9); y += 16;
    cv.toBlob(resolve, "image/jpeg", 0.93);
  });

  const triggerDownload = (blob, filename) => { const url = URL.createObjectURL(blob), a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1500); };
  const saveOffer = async () => {
    const name = suggestedName();
    triggerDownload(new Blob([buildState()], { type: "application/json" }), name + ".json");
    if (offerLines.length > 0) { const jpg = await buildJpg(); setTimeout(() => triggerDownload(jpg, name + ".jpg"), 400); }
  };

  // ===== Excel-Export: aktuelles Angebot zurück in die Import-Vorlagen-Struktur (gleiche Blätter/Spalten, re-importierbar) =====
  const LIC_LABEL_EXPORT = { sdwan: "SD-WAN (ohne Security)", atp: "Basic Security (ATP)", utp: "Advanced Security (UTP)" };
  const ynExport = (v) => (v ? "ja" : "nein");
  const rabattTextBasic = (v) => (v === "pricing" ? "Pricing" : (v ? `${v} %` : "kein Rabatt"));
  const rabattTextLineMobile = (v) => (v === "pricing" ? "Pricing" : `${v || 0} %`);
  const AREA_HEADERS = {
    allip: ["Standort", "Produkt", "Laufzeit", "Menge", "Rabatt", "Internet-Flatrate", "Mobilfunk-Backup", "ONKZ (nur Leitung)"],
    allippure: ["Standort", "Produkt", "Laufzeit", "Menge", "Rabatt"],
    allipbasic: ["Standort", "Produkt", "Laufzeit", "Menge", "Rabatt", "2. Sprachkanal", "Router", "Fair Use EU Plus", "Fair Use World Select"],
    dpbusiness: ["Standort", "Produkt", "Laufzeit", "Menge"],
    dpbasic: ["Standort", "Produkt", "Laufzeit", "Menge"],
    siptrunk: ["Standort", "Produkt", "Laufzeit", "Menge"],
    mdm: ["Standort", "Produkt", "Laufzeit", "Menge"],
    office365: ["Standort", "Produkt", "Menge"],
    sdwan: ["Standort", "Produkt", "Lizenztyp", "Laufzeit", "Menge", "Redundanz"],
    sdwanmeraki: ["Standort", "Produkt", "Lizenztyp", "Laufzeit", "Menge", "Redundanz"],
    sdwansmartfortinet: ["Standort", "Produkt", "Laufzeit", "Menge"],
    sdwansmartmeraki: ["Standort", "Produkt", "Laufzeit", "Menge"],
    vpnconnect: ["Standort", "Hauptanschluss", "Bandbreite (Mbit/s)", "Laufzeit", "Backup"],
    individuell: ["Standort", "Bezeichnung", "Laufzeit", "Menge", "Monatlich (€)", "Einmalig (€)"],
  };
  const AREA_SHEET_ORDER = ["allip", "allippure", "allipbasic", "dpbusiness", "dpbasic", "siptrunk", "mdm", "office365", "sdwan", "sdwanmeraki", "sdwansmartfortinet", "sdwansmartmeraki", "vpnconnect", "individuell"];
  const rowToExportCells = (area, r, standortName) => {
    const p = getProduct(area, r.productId);
    const termTxt = r.term ? `${r.term} Monate` : "";
    switch (area) {
      case "allip": {
        let rabatt = "";
        if (p?.extrasKind === "line") rabatt = rabattTextLineMobile(r.lineRabatt);
        else if (p?.mobile) rabatt = rabattTextLineMobile(r.mobileRabatt);
        return [standortName, p?.name || "", termTxt, r.qty || 1, rabatt, ynExport(r.internetFlat), ynExport(r.backup), r.onkz || ""];
      }
      case "allippure": return [standortName, p?.name || "", termTxt, r.qty || 1, `${r.pureRabatt || 0} %`];
      case "allipbasic": { const opts = BASIC_ROUTER_OPTIONS[p?.connKind] || []; const routerTxt = opts.includes(r.routerOpt) ? optText(`basic_router_${p.connKind}_${r.routerOpt}`) : "kein Router"; return [standortName, p?.name || "", termTxt, r.qty || 1, rabattTextBasic(r.basicRabatt), ynExport(r.zweiterKanal), routerTxt, ynExport(r.euPlus), ynExport(r.worldSelect)]; }
      case "dpbusiness": case "dpbasic": case "siptrunk": case "mdm": return [standortName, p?.name || "", termTxt, r.qty || 1];
      case "office365": return [standortName, p?.name || "", r.qty || 1];
      case "sdwan": case "sdwanmeraki": return [standortName, p?.name || "", LIC_LABEL_EXPORT[r.sdwanLicense || "sdwan"], termTxt, r.qty || 1, ynExport(r.redundanz)];
      case "sdwansmartfortinet": case "sdwansmartmeraki": return [standortName, p?.name || "", termTxt, r.qty || 1];
      case "individuell": return [standortName, r.custName || "", termTxt, r.qty || 1, Number(r.monthly) || 0, Number(r.oneoff) || 0];
      default: return null;
    }
  };
  const vpnToExportRow = (g) => [g.vpnName || "", g.main?.access ? VPN_ACCESS_LABEL[g.main.access] : "", g.main?.bw || "", g.vpnTerm ? `${g.vpnTerm} Monate` : "", g.backup?.access === "mobile" ? "Mobilfunk" : g.backup?.access === "asym" ? "Asymmetrisch" : "keiner"];
  const exportOfferToWorkbook = () => {
    const sheetsData = {}; for (const a of AREA_SHEET_ORDER) sheetsData[a] = [];
    const addRows = (area, rows, fixedStandort) => {
      if (!AREA_HEADERS[area]) return;
      for (const r of rows) {
        if (area === "individuell") { if (!(r.custName || "").trim()) continue; }
        else { if (!getProduct(area, r.productId)) continue; }
        const site = fixedStandort != null ? fixedStandort : (r.standort || "");
        const cells = rowToExportCells(area, r, site);
        if (cells) sheetsData[area].push(cells);
      }
    };
    for (const g of groups) {
      if (!g.area) continue;
      if (g.area === "standort") {
        const site = (g.standortName || "").trim();
        for (const sg of (g.subGroups || [])) {
          if (!sg.area) continue;
          if (sg.area === "vpnconnect") { sheetsData.vpnconnect.push(vpnToExportRow({ ...sg, vpnName: site || sg.vpnName })); continue; }
          addRows(sg.area, sg.rows || [], site);
        }
        continue;
      }
      if (g.area === "vpnconnect") { sheetsData.vpnconnect.push(vpnToExportRow(g)); continue; }
      addRows(g.area, g.rows || [], null);
    }
    const wb = XLSX.utils.book_new();
    for (const area of AREA_SHEET_ORDER) {
      const aoa = [AREA_HEADERS[area], ...sheetsData[area]];
      const ws = XLSX.utils.aoa_to_sheet(aoa);
      ws["!cols"] = AREA_HEADERS[area].map(() => ({ wch: 28 }));
      XLSX.utils.book_append_sheet(wb, ws, sanitizeSheet(CATALOG[area]?.label || area));
    }
    return wb;
  };
  const saveOfferExcel = () => {
    if (offerLines.length === 0) { alert("Das Angebot ist noch leer – es gibt nichts zu exportieren."); return; }
    const wb = exportOfferToWorkbook();
    const buf = XLSX.write(wb, { bookType: "xlsx", type: "array" });
    const blob = new Blob([buf], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a"); a.href = url; a.download = suggestedName() + "-Standorte.xlsx";
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(url), 1500);
  };

  const restore = (data) => {
    if (!data || data.app !== "tef-kalkulator") { alert("Das ist keine gültige Kalkulator-Datei."); return; }
    setCustomer(data.customer || "");
    const rebuildRows = (rows) => (Array.isArray(rows) && rows.length ? rows : [{}]).map(r => ({ ...newRow(), ...r, id: ridc++ }));
    const rebuildGroup = (g) => ({
      id: gidc++,
      area: g.area || "",
      kombi: !!g.kombi,
      dpPct: Number(g.dpPct) || 0,
      dpMode: g.dpMode || "verrechnet",
      managed: !!g.managed,
      customArea: g.customArea || "",
      showDesc: !!g.showDesc,
      standortName: g.standortName || "",
      sdwanTerm: SDWAN_TERMS.includes(Number(g.sdwanTerm)) ? Number(g.sdwanTerm) : 60,
      rows: rebuildRows(g.rows),
      subGroups: Array.isArray(g.subGroups) ? g.subGroups.map(rebuildGroup) : [],
      ...(g.area === "vpnconnect" ? {
        vpnTerm: VPN_TERMS.includes(Number(g.vpnTerm)) ? Number(g.vpnTerm) : 36,
        vpnName: g.vpnName || "",
        main: g.main ? { ...newVpnConn(g.main.access || "asym"), ...g.main, id: cidc++ } : newVpnConn("asym"),
        backup: g.backup && g.backup.access ? { ...newVpnConn(g.backup.access), ...g.backup, id: cidc++ } : null,
        citrix: !!g.citrix, netflow: !!g.netflow, advHw: !!g.advHw, advHwM: g.advHwM ?? "0", advHwO: g.advHwO ?? "0",
        backupPlus: !!g.backupPlus, bpM: g.bpM ?? "0", bpO: g.bpO ?? "0",
      } : {}),
    });
    const gs = (Array.isArray(data.groups) ? data.groups : []).map(rebuildGroup);
    setGroups(gs.length ? gs : [newGroup()]);
  };
  const onLoadFile = (e) => { const f = e.target.files?.[0]; if (!f) return; const reader = new FileReader(); reader.onload = () => { try { restore(JSON.parse(String(reader.result))); } catch { alert("Datei konnte nicht gelesen werden."); } }; reader.readAsText(f); e.target.value = ""; };

  // Sortier-Pfeile (überall einsetzbar)
  const SortArrows = ({ g, r, size = 16 }) => {
    const idx = g.rows.findIndex(x => x.id === r.id);
    return (
      <div className="flex flex-col shrink-0">
        <button onClick={() => moveRowDir(g.id, r.id, -1)} disabled={idx <= 0} className="p-0.5 rounded hover:bg-gray-100 disabled:opacity-25" title="nach oben"><ChevronUp size={size} color="#6b7280" /></button>
        <button onClick={() => moveRowDir(g.id, r.id, 1)} disabled={idx >= g.rows.length - 1} className="p-0.5 rounded hover:bg-gray-100 disabled:opacity-25" title="nach unten"><ChevronDown size={size} color="#6b7280" /></button>
      </div>
    );
  };

  // ===== Produktzeile (SD-WAN Fortinet / Meraki) =====
  const renderSdwanRow = (g, r) => {
    const area = g.area;
    const products = CATALOG[area].products.filter(pp => pp.isSeparator || !isDeleted(area, pp.id) || pp.id === r.productId);
    const p = getProduct(area, r.productId);
    const term = Number(r.term) || 60;
    const isLicensed = !!p?.lic;
    const licKeys = isLicensed ? Object.keys(p.lic) : [];
    const lic = isLicensed ? (licKeys.includes(r.sdwanLicense) ? r.sdwanLicense : licKeys[0]) : null;
    const isAd = p?.sdwanKind === "ad";
    const isRemote = p?.sdwanKind === "remote";
    const canRed = p?.sdwanKind === "fg" && p?.redundanzAllowed;
    const floor = p && !isAd ? sdwanFloor(p, lic, term) : 0;
    const ceil = p && !isAd ? sdwanCeil(p, lic) : 0;
    const eachM = p && !isAd ? sdwanRowMonthlyEach(p, r) : 0;
    const qty = Math.max(1, Number(r.qty) || 1);
    const redMalus = p ? (sdwanRedTable(p.vendor)[lic]?.[term] ?? 0) : 0;
    return (
      <div key={r.id} className="rounded-lg border border-gray-200 bg-white p-3">
        <div className="flex flex-wrap gap-3 items-end">
          <SortArrows g={g} r={r} />
          <button onClick={() => duplicateRow(g.id, r.id)} className="p-2 rounded hover:bg-blue-50 mb-0.5 shrink-0" title="Produkt duplizieren"><Copy size={18} color={TEF_BLUE} /></button><button onClick={() => removeRow(g.id, r.id)} className="p-2 rounded hover:bg-red-50 mb-0.5 shrink-0" title="Produkt entfernen"><Trash2 size={18} color={TEF_RED} /></button>
          <Field label="Produkt" className="grow min-w-[220px]">
            <select ref={el => { if (el) rowFieldRefs.current[r.id] = el; }} className={ctl} value={r.productId} onChange={e => onProduct(g.id, r.id, g.area, e.target.value)}>
              <option value="">— wählen —</option>
              {products.map(pp => pp.isSeparator ? <option key={pp.id} disabled style={{ color: "#aaa" }}>{pp.name}</option> : <option key={pp.id} value={pp.id}>{pp.name}{isDeleted(area, pp.id) ? " (gelöscht)" : ""}</option>)}
            </select>
          </Field>
          {isLicensed && licKeys.length > 1 && <Field label="Lizenztyp" className="w-52">
            <select className={ctl} value={lic} onChange={e => patchRow(g.id, r.id, { sdwanLicense: e.target.value, sdwanPrice: "" })}>
              {licKeys.map(k => <option key={k} value={k}>{sdwanLicLabel(p.vendor, k)}</option>)}
            </select>
          </Field>}
          {!isAd && <Field label="Laufzeit" className="w-40">
            <select className={ctl} value={term} onChange={e => patchRow(g.id, r.id, { term: Number(e.target.value), sdwanPrice: "" })}>
              {SDWAN_TERMS.map(t => <option key={t} value={t}>{t} Monate</option>)}
            </select>
          </Field>}
          <Field label={isRemote ? "25er-Pakete" : p?.qtyLabel || "Menge"} className="w-28"><input type="number" min="1" className={ctl} value={r.qty} onChange={e => patchRow(g.id, r.id, { qty: e.target.value })} onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); addRow(g.id); } }} /></Field>
          {p?.standort && <Field label="Standort (optional)" className="w-48"><input className={ctl} placeholder="z. B. Werk Kirchheim" value={r.standort} onChange={e => patchRow(g.id, r.id, { standort: e.target.value })} /></Field>}
          {!isAd && <Field label="Preis / Monat (€)" className="w-36"><input type="number" step="0.01" className={ctl} placeholder={String(floor)} value={r.sdwanPrice} onChange={e => patchRow(g.id, r.id, { sdwanPrice: e.target.value })} /></Field>}
          {canRed && <label className="flex items-center gap-2 text-sm mb-2" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.redundanz} onChange={e => patchRow(g.id, r.id, { redundanz: e.target.checked })} />Redundanz</label>}
        </div>
        {isLicensed && licKeys.length === 1 && <div className="mt-1 text-[11px] text-gray-500">Lizenztyp: {sdwanLicLabel(p.vendor, lic)} (einzige Variante)</div>}
        {p && !isAd && <div className="mt-2 text-xs text-gray-600">
          Spanne {term} Monate: <b style={{ color: TEF_BLUE }}>{eur(floor)}</b> (max. Nachlass) bis <b style={{ color: TEF_BLUE }}>{eur(ceil)}</b> (12-Monats-Preis). Leer = Bestpreis {eur(floor)}.
          {canRed && r.redundanz && <> · Redundant: 2 × {eur(sdwanChosen(p, r))} {eur(redMalus)} = <b style={{ color: TEF_BLUE }}>{eur(eachM)}</b></>}
          {" "}· Monatlich gesamt: <b style={{ color: TEF_BLUE }}>{eur(eachM * qty)}</b>
        </div>}
        {isAd && <div className="mt-2 text-xs text-gray-600">Einmalig {eur(optPrice("sdwan_ad_oneoff"))} je Standort · gesamt <b style={{ color: TEF_BLUE }}>{eur(optPrice("sdwan_ad_oneoff") * qty)}</b></div>}
        {isRemote && <div className="mt-2 text-xs text-gray-600">{qty} × 25 = <b style={{ color: TEF_BLUE }}>{qty * 25} Lizenzen</b> · zzgl. {eur(optPrice("sdwan_remote_setup"))} Einrichtung je Paket ({eur(optPrice("sdwan_remote_setup") * qty)})</div>}
      </div>
    );
  };

  // ===== Produktzeile (All IP Basic) =====
  const renderBasicRow = (g, r) => {
    const p = getProduct(g.area, r.productId), products = CATALOG[g.area].products.filter(pp => !isDeleted(g.area, pp.id) || pp.id === r.productId);
    const pricing = r.basicRabatt === "pricing";
    const routerOpts = p ? (BASIC_ROUTER_OPTIONS[p.connKind] || []) : [];
    return (
      <div key={r.id} className="rounded-lg border border-gray-200 bg-white p-3">
        <div className="flex flex-wrap gap-3 items-end">
          <SortArrows g={g} r={r} />
          <button onClick={() => duplicateRow(g.id, r.id)} className="p-2 rounded hover:bg-blue-50 mb-0.5 shrink-0" title="Produkt duplizieren"><Copy size={18} color={TEF_BLUE} /></button><button onClick={() => removeRow(g.id, r.id)} className="p-2 rounded hover:bg-red-50 mb-0.5 shrink-0" title="Produkt entfernen"><Trash2 size={18} color={TEF_RED} /></button>
          <Field label="Produkt" className="grow min-w-[200px]">
            <select ref={el => { if (el) rowFieldRefs.current[r.id] = el; }} className={ctl} value={r.productId} onChange={e => onProduct(g.id, r.id, g.area, e.target.value)}>
              <option value="">— wählen —</option>
              {products.map(pp => pp.isSeparator ? <option key={pp.id} disabled style={{ color: "#aaa" }}>{pp.name}</option> : <option key={pp.id} value={pp.id}>{pp.name}{isDeleted(g.area, pp.id) ? " (gelöscht)" : ""}</option>)}
            </select>
          </Field>
          {p && <Field label="Laufzeit" className="w-40"><select className={ctl} value={r.term ?? ""} onChange={e => onRowTerm(g.id, r.id, g.area, Number(e.target.value))}>{p.laufzeiten.map(t => <option key={t} value={t}>{t} Monate</option>)}</select></Field>}
          {p && <Field label="Anzahl" className="w-28"><input type="number" min="1" className={ctl} value={r.qty} onChange={e => patchRow(g.id, r.id, { qty: e.target.value })} onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); addRow(g.id); } }} /></Field>}
          {p && <Field label="Standort (optional)" className="w-48"><input className={ctl} placeholder="z. B. Werk Kirchheim" value={r.standort} onChange={e => patchRow(g.id, r.id, { standort: e.target.value })} /></Field>}
        </div>
        {p && <div className="mt-2 flex flex-wrap gap-x-6 gap-y-2">
          {p.zweiterKanal && <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.zweiterKanal} onChange={e => patchRow(g.id, r.id, { zweiterKanal: e.target.checked })} />Zweiter Sprachkanal <span className="text-xs text-gray-500">(+{eur(optPrice("basic_kanal2"))}/Monat)</span></label>}
          {routerOpts.map(optKey => {
            const rname = optText(`basic_router_${p.connKind}_${optKey}`);
            const rprice = optPrice(`basic_router_${p.connKind}_${optKey}`);
            const checked = r.routerOpt === optKey;
            return (
              <label key={optKey} className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}>
                <input type="checkbox" checked={checked} onChange={e => patchRow(g.id, r.id, { routerOpt: e.target.checked ? optKey : "" })} />
                {rname} <span className="text-xs text-gray-500">{rprice > 0 ? `(+${eur(rprice)}/Monat)` : "(inklusive)"}</span>
              </label>
            );
          })}
          <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.euPlus} onChange={e => patchRow(g.id, r.id, { euPlus: e.target.checked })} />Fair Use EU Plus <span className="text-xs text-gray-500">(+{eur(Number(r.term) >= 36 ? optPrice("basic_euplus_36") : optPrice("basic_euplus_24"))}/Monat)</span></label>
          <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.worldSelect} onChange={e => patchRow(g.id, r.id, { worldSelect: e.target.checked })} />Fair Use World Select <span className="text-xs text-gray-500">(+{eur(Number(r.term) >= 36 ? optPrice("basic_worldselect_36") : optPrice("basic_worldselect_24"))}/Monat)</span></label>
        </div>}
        {p && <div className="mt-2 flex flex-wrap gap-3 items-end">
          <Field label="Rabatt" className="w-36">
            <select className={ctl} value={r.basicRabatt} onChange={e => { const v = e.target.value; if (v === "pricing") { const b = resolvePrice(p, r.term); patchRow(g.id, r.id, { basicRabatt: v, monthly: b.monthly, oneoff: b.oneoff }); } else patchRow(g.id, r.id, { basicRabatt: v }); }}>
              <option value="">kein Rabatt</option>
              <option value="10">10 %</option>
              <option value="20">20 %</option>
              <option value="30">30 %</option>
              <option value="pricing">Pricing</option>
            </select>
          </Field>
          {pricing && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.monthly} onChange={e => patchRow(g.id, r.id, { monthly: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.oneoff} onChange={e => patchRow(g.id, r.id, { oneoff: e.target.value })} /></Field></>}
          {(r.basicRabatt === "10" || r.basicRabatt === "20" || r.basicRabatt === "30") && <Field label="Darstellung" className="w-52"><select className={ctl} value={r.basicMode || "verrechnet"} onChange={e => patchRow(g.id, r.id, { basicMode: e.target.value })}><option value="verrechnet">im Preis verrechnen</option><option value="zeile">als separate Zeile</option><option value="abzug">unter Preis abziehen</option></select></Field>}
        </div>}
        {p && <div className="mt-1 text-[11px] text-gray-500">Prozent-Rabatt gilt nur auf den monatlichen Produktpreis{p.zweiterKanal ? " (inkl. zweitem Sprachkanal)" : ""} – nicht auf Router, EU Plus, World Select.</div>}
      </div>
    );
  };

  // ===== Produktzeile (Individuell) =====
  const renderCustomRow = (g, r) => (
    <div key={r.id} className="rounded-lg border border-gray-200 bg-white p-3">
      <div className="flex flex-wrap gap-3 items-end">
        <SortArrows g={g} r={r} />
        <button onClick={() => duplicateRow(g.id, r.id)} className="p-2 rounded hover:bg-blue-50 mb-0.5 shrink-0" title="Produkt duplizieren"><Copy size={18} color={TEF_BLUE} /></button><button onClick={() => removeRow(g.id, r.id)} className="p-2 rounded hover:bg-red-50 mb-0.5 shrink-0" title="Produkt entfernen"><Trash2 size={18} color={TEF_RED} /></button>
        <Field label="Produkt" className="grow min-w-[200px]"><input ref={el => { if (el) rowFieldRefs.current[r.id] = el; }} className={ctl} placeholder="z. B. Beratungspauschale" value={r.custName} onChange={e => patchRow(g.id, r.id, { custName: e.target.value })} /></Field>
        <Field label="Laufzeit" className="w-40"><select className={ctl} value={r.term ?? ""} onChange={e => patchRow(g.id, r.id, { term: e.target.value === "" ? null : Number(e.target.value) })}><option value="">— ohne —</option>{[12, 24, 36, 48, 60].map(t => <option key={t} value={t}>{t} Monate</option>)}</select></Field>
        <Field label="Anzahl" className="w-28"><input type="number" min="1" className={ctl} value={r.qty} onChange={e => patchRow(g.id, r.id, { qty: e.target.value })} onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); addRow(g.id); } }} /></Field>
        <Field label="Standort (optional)" className="w-48"><input className={ctl} placeholder="z. B. Werk Kirchheim" value={r.standort} onChange={e => patchRow(g.id, r.id, { standort: e.target.value })} /></Field>
        <Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.monthly} onChange={e => patchRow(g.id, r.id, { monthly: e.target.value })} /></Field>
        <Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.oneoff} onChange={e => patchRow(g.id, r.id, { oneoff: e.target.value })} /></Field>
      </div>
      <Field label="Beschreibung (eine Zeile je Stichpunkt)" className="mt-2">
        <textarea className={ctl + " min-h-[64px] resize-y"} placeholder={"z. B.\nPersönlicher Ansprechpartner\nReaktionszeit 4 Stunden"} value={r.custDesc} onChange={e => patchRow(g.id, r.id, { custDesc: e.target.value })} />
      </Field>
    </div>
  );

  // ===== Produktzeile (Kalkulator) =====
  const renderRow = (g, r) => {
    if (g.area === "individuell") return renderCustomRow(g, r);
    if (g.area === "allipbasic") return renderBasicRow(g, r);
    if (g.area === "sdwan" || g.area === "sdwanmeraki") return renderSdwanRow(g, r);
    const p = getProduct(g.area, r.productId), products = CATALOG[g.area].products.filter(pp => !isDeleted(g.area, pp.id) || pp.id === r.productId);
    const hasLz = p?.laufzeiten?.length > 0, isQtyManual = p?.qty && p?.mengenBezug !== "nebenstellen", isOpen = p?.priceOpen, isLine = p?.extrasKind === "line", isMobile = !!p?.mobile;
    const qv = Math.max(1, Number(r.qty) || 1), stMax = p?.rabattType === "staffelMax" ? staffelPct(p.staffel, qv) : 0;
    const oMax = p?.office ? officeMax(p, g.managed) : 0, oPrice = p?.office ? officePrice(p, g.managed) : 0, oNoMgmt = p?.office ? officeNoMgmt(p, g.managed) : false;
    const showDarstellung = (p?.rabattType === "fix10" && r.discountFix) || (p?.rabattType === "staffelMax" && Number(r.discountPct) > 0) || (p?.rabattType === "officeMax" && Number(r.discountPct) > 0) || (isLine && r.lineProvider === "dtag" && ["10", "20", "30"].includes(r.lineRabatt)) || (isMobile && ["10", "20", "30"].includes(r.mobileRabatt)) || (p?.pureDiscount && Number(r.pureRabatt) > 0);
    return (
      <div key={r.id} className="rounded-lg border border-gray-200 bg-white p-3">
        <div className="flex flex-wrap gap-3 items-end">
          <SortArrows g={g} r={r} />
          <button onClick={() => duplicateRow(g.id, r.id)} className="p-2 rounded hover:bg-blue-50 mb-0.5 shrink-0" title="Produkt duplizieren"><Copy size={18} color={TEF_BLUE} /></button><button onClick={() => removeRow(g.id, r.id)} className="p-2 rounded hover:bg-red-50 mb-0.5 shrink-0" title="Produkt entfernen"><Trash2 size={18} color={TEF_RED} /></button>
          <Field label="Produkt" className="grow min-w-[200px]">
            <select ref={el => { if (el) rowFieldRefs.current[r.id] = el; }} className={ctl} value={r.productId} onChange={e => onProduct(g.id, r.id, g.area, e.target.value)}>
              <option value="">— wählen —</option>
              {products.map(pp => pp.isSeparator ? <option key={pp.id} disabled style={{ color: "#aaa" }}>{pp.name}</option> : <option key={pp.id} value={pp.id}>{pp.name}{isDeleted(g.area, pp.id) ? " (gelöscht)" : ""}</option>)}
            </select>
          </Field>
          {hasLz && <Field label="Laufzeit" className="w-40"><select className={ctl} value={r.term ?? ""} onChange={e => onRowTerm(g.id, r.id, g.area, Number(e.target.value))}>{p.laufzeiten.map(t => <option key={t} value={t}>{t} Monate</option>)}</select></Field>}
          {isQtyManual && <Field label={p.qtyLabel || "Menge"} className="w-28"><input type="number" min={p.qtyMin || 1} className={ctl} value={r.qty} onChange={e => patchRow(g.id, r.id, { qty: e.target.value })} onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); addRow(g.id); } }} /></Field>}
          {isQtyManual && p?.qtyMin > 1 && Number(r.qty) < p.qtyMin && <div className="text-[11px] self-center mb-2" style={{ color: TEF_RED }}>Mindestmenge {p.qtyMin} – wird in der Kalkulation automatisch angesetzt</div>}
          {p?.standort && <Field label="Standort (optional)" className="w-48"><input className={ctl} placeholder="z. B. Werk Kirchheim" value={r.standort} onChange={e => patchRow(g.id, r.id, { standort: e.target.value })} /></Field>}
          {isOpen && !isLine && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.monthly} onChange={e => patchRow(g.id, r.id, { monthly: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.oneoff} onChange={e => patchRow(g.id, r.id, { oneoff: e.target.value })} /></Field></>}
          {isLine && <>
            <Field label="Anbindung" className="w-36"><select className={ctl} value={r.lineProvider} onChange={e => patchRow(g.id, r.id, { lineProvider: e.target.value })}><option value="dtag">DTAG</option><option value="andere">Andere</option></select></Field>
            {r.lineProvider === "dtag" && <Field label="ONKZ" className="w-32"><input className={ctl} placeholder="z. B. 0711" value={r.onkz} onChange={e => patchRow(g.id, r.id, { onkz: e.target.value })} /></Field>}
            {r.lineProvider === "dtag" && <Field label="Rabatt" className="w-36"><select className={ctl} value={r.lineRabatt} onChange={e => { const v = e.target.value; if (v === "pricing") { const b = lineDtagBase(p, r); patchRow(g.id, r.id, { lineRabatt: v, monthly: b.monthly, oneoff: b.oneoff }); } else patchRow(g.id, r.id, { lineRabatt: v }); }}><option value="0">0 %</option><option value="10">10 %</option><option value="20">20 %</option><option value="30">30 %</option><option value="pricing">Pricing</option></select></Field>}
            {(r.lineProvider === "andere" || (r.lineProvider === "dtag" && r.lineRabatt === "pricing")) && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.monthly} onChange={e => patchRow(g.id, r.id, { monthly: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.oneoff} onChange={e => patchRow(g.id, r.id, { oneoff: e.target.value })} /></Field></>}
          </>}
          {isMobile && <>
            <Field label="Geschwindigkeit" className="w-44">
              <select className={ctl} value={r.mobileSpeed} onChange={e => patchRow(g.id, r.id, { mobileSpeed: e.target.value })}>
                {MOBILE_SPEEDS.map(s => <option key={s.key} value={s.key}>{s.label}</option>)}
              </select>
            </Field>
            <Field label="Rabatt" className="w-36">
              <select className={ctl} value={r.mobileRabatt} onChange={e => { const v = e.target.value; if (v === "pricing") { const b = mobileMainBase(r); patchRow(g.id, r.id, { mobileRabatt: v, monthly: b.monthly, oneoff: b.oneoff }); } else patchRow(g.id, r.id, { mobileRabatt: v }); }}>
                <option value="0">0 %</option><option value="10">10 %</option><option value="20">20 %</option><option value="30">30 %</option><option value="pricing">Pricing</option>
              </select>
            </Field>
            {r.mobileRabatt === "pricing" && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.monthly} onChange={e => patchRow(g.id, r.id, { monthly: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={r.oneoff} onChange={e => patchRow(g.id, r.id, { oneoff: e.target.value })} /></Field></>}
            <Field label="Antenne" className="w-64">
              <select className={ctl} value={r.antenne} onChange={e => patchRow(g.id, r.id, { antenne: e.target.value })}>
                <option value="">keine</option>
                {ANTENNEN.map(a => <option key={a.key} value={a.key}>{a.label} (+{eur(a.oneoff)} einmalig)</option>)}
              </select>
            </Field>
          </>}
          {p?.pureDiscount && <Field label="Rabatt" className="w-36">
            <select className={ctl} value={r.pureRabatt} onChange={e => patchRow(g.id, r.id, { pureRabatt: e.target.value })}>
              <option value="0">0 %</option><option value="10">10 %</option><option value="20">20 %</option>
              {Number(r.term) >= 36 && <option value="30">30 %</option>}
            </select>
          </Field>}
        </div>
        {p?.office && <div className="mt-2 text-xs text-gray-600">Preis: <b style={{ color: TEF_BLUE }}>{eur(oPrice)}</b> / Monat{oNoMgmt && <span style={{ color: TEF_RED }}> · Kein Management durch Telefónica</span>}</div>}
        {p?.inetFlat && <div className="mt-2">
          <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.internetFlat} onChange={e => patchRow(g.id, r.id, { internetFlat: e.target.checked })} />Internet-Flatrate <span className="text-xs text-gray-500">(+{eur(Number(r.term) >= 36 ? INET_FLAT.m36 : INET_FLAT.m24)} / Monat · separat, nicht rabattiert)</span></label>
        </div>}
        {p?.extrasKind && <div className="mt-2 flex flex-wrap gap-x-6 gap-y-2">
          <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.backup} onChange={e => { const checked = e.target.checked; patchRow(g.id, r.id, checked ? { backup: true } : { backup: false, express: false }); }} />Mobilfunk-Backup <span className="text-xs text-gray-500">(+{eur(backupMonthly(p.extrasKind, r.term))} / Monat)</span></label>
          {r.backup && <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.express} onChange={e => patchRow(g.id, r.id, { express: e.target.checked })} />Express-Bereitstellung <span className="text-xs text-gray-500">(+{eur(EXPRESS_PRICE[p.extrasKind])} einmalig)</span></label>}
        </div>}
        {p?.extrasKind && r.backup && <div className="mt-2"><Field label="Antenne" className="w-64">
          <select className={ctl} value={r.antenne} onChange={e => patchRow(g.id, r.id, { antenne: e.target.value })}>
            <option value="">keine</option>
            {ANTENNEN.map(a => <option key={a.key} value={a.key}>{a.label} (+{eur(a.oneoff)} einmalig)</option>)}
          </select>
        </Field></div>}
        {p?.wave && <div className="mt-2 flex flex-wrap gap-x-6 gap-y-2">
          <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.businessClass} onChange={e => patchRow(g.id, r.id, { businessClass: e.target.checked })} />Business Class Support (8h) <span className="text-xs text-gray-500">(+{eur(WAVE_SUPPORT_PRICE)} / Monat)</span></label>
        </div>}
        {p?.wave && <div className="mt-2 text-xs text-gray-600">„Line of Sight"-Test wird automatisch mit <b style={{ color: TEF_BLUE }}>{eur(WAVE_LOS_PRICE)}</b> einmalig als eigene Position ergänzt.</div>}
        {p?.maxKanal && <div className="mt-2 rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
          <div className="flex items-end gap-3 flex-wrap">
            <Field label="Sprachkanäle" className="w-36">
              <select className={ctl} value={r.kanal} onChange={e => patchRow(g.id, r.id, { kanal: Number(e.target.value) })}>
                {KANAL_STEPS.filter(s => s <= p.maxKanal).map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </Field>
            <div className="text-[11px] text-gray-500 mb-2">max. {p.maxKanal} Kanäle für diese Anbindung</div>
          </div>
          {r.kanal > 0 && <div className="mt-3 grid grid-cols-1 md:grid-cols-2 gap-3">
            {RICHTUNGEN.map(dir => {
              const t = (r.kanalTarife && r.kanalTarife[dir.key]) || { mode: "standard", min: 0 };
              const setT = (patch) => patchRow(g.id, r.id, { kanalTarife: { ...(r.kanalTarife || blankKanalTarife()), [dir.key]: { ...t, ...patch } } });
              return (
                <div key={dir.key} className="rounded-lg border border-gray-200 bg-white p-2.5">
                  <div className="text-sm font-semibold mb-1.5" style={{ color: TEF_BLUE }}>{dir.label}</div>
                  <select className={ctl} value={t.mode} onChange={e => setT({ mode: e.target.value })}>
                    <option value="standard">Standard (kostenlos)</option>
                    <option value="fairuse">Fair Use</option>
                    <option value="pooling">Pooling</option>
                  </select>
                  {t.mode === "fairuse" && <div className="text-[11px] text-gray-500 mt-1">+{eur(dir.fair)} je Sprachkanal</div>}
                  {t.mode === "pooling" && <div className="mt-2 flex items-end gap-2">
                    <Field label="Minuten" className="w-28"><input type="number" min="0" className={ctl} value={t.min} onChange={e => setT({ min: Number(e.target.value) || 0 })} /></Field>
                    <div className="text-[11px] text-gray-500 mb-2">{dir.poolCt.toLocaleString("de-DE")} ct/Min</div>
                  </div>}
                </div>
              );
            })}
          </div>}
        </div>}
        {p?.mengenBezug === "nebenstellen" && <div className="mt-2 text-xs text-gray-600 bg-blue-50 rounded p-2 inline-block">Nebenstellen automatisch aus Basispaket + Lizenzen: <b style={{ color: TEF_BLUE }}>{totalNST}</b></div>}
        {p?.teamsCoupling && <label className="mt-2 flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!r.teamsCoupling} onChange={e => patchRow(g.id, r.id, { teamsCoupling: e.target.checked })} />Kopplung mit Microsoft Teams gewünscht <span className="text-xs text-gray-500">(+4 € je Sprachkanal)</span></label>}
        {(p?.abloese || p?.rabattType || showDarstellung) && <div className="mt-2 flex flex-wrap gap-3 items-end">
          {p?.abloese && <Field label="Ablöse" className="w-36"><select className={ctl} value={r.abloese} onChange={e => patchRow(g.id, r.id, { abloese: Number(e.target.value) })}><option value={0}>keine</option><option value={3}>3 Monate</option><option value={6}>6 Monate</option></select></Field>}
          {p?.rabattType === "fix10" && <label className="flex items-center gap-2 text-sm mb-2" style={{ color: TEF_GRAY }}><input type="checkbox" checked={r.discountFix} onChange={e => patchRow(g.id, r.id, { discountFix: e.target.checked })} />Bestandskundenrabatt 10 € / Monat</label>}
          {p?.rabattType === "staffelMax" && <Field label={`Rabatt monatlich (0–${stMax} %)`} className="w-48"><input type="number" min="0" max={stMax} className={ctl} value={r.discountPct} onChange={e => patchRow(g.id, r.id, { discountPct: Math.min(stMax, Math.max(0, Number(e.target.value) || 0)) })} /></Field>}
          {p?.rabattType === "officeMax" && <Field label={`Rabatt monatlich (0–${oMax} %)`} className="w-48"><input type="number" min="0" max={oMax} className={ctl} value={r.discountPct} onChange={e => patchRow(g.id, r.id, { discountPct: Math.min(oMax, Math.max(0, Number(e.target.value) || 0)) })} /></Field>}
          {showDarstellung && <Field label="Darstellung" className="w-52"><select className={ctl} value={r.discountMode} onChange={e => patchRow(g.id, r.id, { discountMode: e.target.value })}><option value="verrechnet">im Preis verrechnen</option><option value="zeile">als separate Zeile</option><option value="abzug">unter Preis abziehen</option></select></Field>}
        </div>}
        {p?.produkttyp === "lizenz" && (() => { const dp = dpStats(g); return (
          <div className="mt-2 rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
            <label className="flex items-center gap-2 text-sm font-medium" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!g.kombi} onChange={e => patchGroup(g.id, { kombi: e.target.checked })} />Kombi-Vorteil nutzen</label>
            <div className="text-xs text-gray-600 mt-2">Listenumsatz: <b style={{ color: TEF_BLUE }}>{eur(dp.umsatz)}</b> <span className="text-gray-500">({dp.bezahl} Bezahlmonate)</span> · Max. Rabatt: <b style={{ color: TEF_BLUE }}>{dp.maxPct} %</b></div>
            <div className="flex flex-wrap gap-3 items-end mt-2">
              <Field label={`Rabatt monatlich (0–${dp.maxPct} %)`} className="w-44"><input type="number" min="0" max={dp.maxPct} className={ctl} value={g.dpPct} onChange={e => patchGroup(g.id, { dpPct: Math.min(dp.maxPct, Math.max(0, Number(e.target.value) || 0)) })} /></Field>
              {Number(g.dpPct) > 0 && <Field label="Darstellung" className="w-52"><select className={ctl} value={g.dpMode} onChange={e => patchGroup(g.id, { dpMode: e.target.value })}><option value="verrechnet">im Preis verrechnen</option><option value="zeile">als separate Zeile</option><option value="abzug">unter Preis abziehen</option></select></Field>}
            </div>
            <div className="text-[11px] text-gray-500 mt-1">Gilt nur auf zusätzliche Lizenzen.</div>
          </div>
        ); })()}
        {isOpen && !isLine && <div className="mt-2 inline-flex items-center gap-1 text-amber-600 font-medium text-xs"><AlertTriangle size={13} /> Preis offen – bitte Monats-/Einmalpreis eintragen</div>}
        {isLine && r.lineProvider === "dtag" && r.lineRabatt !== "pricing" && (() => { const b = lineDtagBase(p, r); return <div className="mt-2 text-xs text-gray-600">Preisband: <b style={{ color: TEF_BLUE }}>{b.region}</b>{!r.onkz && <span className="text-gray-400"> (Standard – bitte ONKZ eingeben)</span>} · DTAG-Listenpreis: <b style={{ color: TEF_BLUE }}>{eur(b.monthly)}</b> / Monat{b.oneoff ? <> · {eur(b.oneoff)} einmalig</> : ""}{Number(r.lineRabatt) > 0 && <> · nach {r.lineRabatt}% Rabatt: <b style={{ color: TEF_BLUE }}>{eur(b.monthly * (1 - Number(r.lineRabatt) / 100))}</b> / Monat</>}</div>; })()}
        {isLine && r.lineProvider === "dtag" && r.lineRabatt === "pricing" && <div className="mt-2 text-xs text-gray-600">Preisband: <b style={{ color: TEF_BLUE }}>{onkzRegion(r.onkz)}</b> · Preisfelder mit DTAG-Listenpreis vorbefüllt, frei überschreibbar.</div>}
        {isLine && (r.lineProvider === "andere" || (r.lineProvider === "dtag" && r.lineRabatt === "pricing")) && <div className="mt-2 inline-flex items-center gap-1 text-amber-600 font-medium text-xs"><AlertTriangle size={13} /> Preis offen – bitte Monats-/Einmalpreis eintragen</div>}
        {isMobile && r.mobileRabatt !== "pricing" && (() => { const b = mobileMainBase(r); const pct = Number(r.mobileRabatt) || 0; const disc = (b.baseM + b.speedM) * (1 - pct / 100) + b.flatM; return <div className="mt-2 text-xs text-gray-600">Mobile Main: <b style={{ color: TEF_BLUE }}>{eur(b.baseM)}</b> + {b.speed.label}: <b style={{ color: TEF_BLUE }}>{eur(b.speedM)}</b> + Internet-Flat: <b style={{ color: TEF_BLUE }}>{eur(b.flatM)}</b>{pct > 0 && <> · nach {pct}% Rabatt (auf Mobile Main + Geschwindigkeit): <b style={{ color: TEF_BLUE }}>{eur(disc)}</b> / Monat</>}</div>; })()}
        {isMobile && r.mobileRabatt === "pricing" && <div className="mt-2 text-xs text-gray-600">Preisfelder mit Listenpreis (ohne Rabatt) vorbefüllt, frei überschreibbar.</div>}
        {isMobile && r.mobileRabatt === "pricing" && <div className="mt-2 inline-flex items-center gap-1 text-amber-600 font-medium text-xs"><AlertTriangle size={13} /> Preis offen – bitte Monats-/Einmalpreis eintragen</div>}
        {isMobile && mobileMainBase(r).speedUnpriced && <div className="mt-2 inline-flex items-center gap-1 text-amber-600 font-medium text-xs"><AlertTriangle size={13} /> Preis für {mobileMainBase(r).speed.label} noch nicht hinterlegt</div>}
      </div>
    );
  };

  // helper: find row object by gid/rowId for preview arrows
  const findRow = (gid, rid) => {
    for (const g of groups) {
      if (g.id === gid) return { g, r: g.rows.find(x => x.id === rid) };
      if (g.area === "standort") for (const sg of g.subGroups || []) if (sg.id === gid) return { g: sg, r: sg.rows.find(x => x.id === rid) };
    }
    return null;
  };

  // ===== Unterbereich-Karte innerhalb eines Standortangebots (gleiche Logik wie eine normale Bereichs-Karte) =====
  const renderSubGroupCard = (parentG, sg, sgi, totalSub) => {
    const kinds = new Set();
    for (const r of sg.rows) { const pp = getProduct(sg.area, r.productId); if (pp?.mdmKind) kinds.add(pp.mdmKind); }
    const mdmMix = kinds.has("managed") && kinds.has("unmanaged");
    return (
      <div key={sg.id} className="rounded-lg border-2 border-gray-200 overflow-hidden bg-white">
        <div className="flex flex-col md:flex-row">
          <div className="md:w-40 shrink-0 p-2.5 border-b md:border-b-0 md:border-r border-gray-200" style={{ background: "#F0F4FF" }}>
            <div className="flex items-center justify-between mb-1.5">
              <span className="text-[10px] font-bold inline-flex items-center gap-1" style={{ color: TEF_BLUE }}>
                Unterbereich {sgi + 1}
                {(() => { const n = plausibility.filter(x => x.gid === sg.id).length; const hasWarn = plausibility.some(x => x.gid === sg.id && x.level === "warn"); return n > 0 ? <span title={`${n} Hinweis(e) – siehe Plausibilitäts-Prüfung im Angebot`} className="inline-flex items-center justify-center rounded-full text-white text-[10px] font-bold w-4 h-4" style={{ background: hasWarn ? TEF_RED : TEF_BLUE }}>{n}</span> : null; })()}
              </span>
              <div className="flex items-center gap-0.5">
                <button onClick={() => moveSubGroupDir(parentG.id, sg.id, -1)} disabled={sgi <= 0} className="p-0.5 rounded hover:bg-white disabled:opacity-25" title="Unterbereich nach oben"><ChevronUp size={14} color={TEF_BLUE} /></button>
                <button onClick={() => moveSubGroupDir(parentG.id, sg.id, 1)} disabled={sgi >= totalSub - 1} className="p-0.5 rounded hover:bg-white disabled:opacity-25" title="Unterbereich nach unten"><ChevronDown size={14} color={TEF_BLUE} /></button>
              </div>
            </div>
            <label className={lbl} style={{ color: TEF_BLUE }}>Produktbereich</label>
            <select className={ctl} value={sg.area} onChange={e => setArea(sg.id, e.target.value)}>
              <option value="">— wählen —</option>
              {SUBAREAS.map(a => <option key={a.key} value={a.key}>{a.label}</option>)}
            </select>
            <div className="mt-2 flex items-center gap-2">
              <button onClick={() => duplicateSubGroup(parentG.id, sg.id)} className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Unterbereich duplizieren"><Copy size={12} /> duplizieren</button>
              <button onClick={() => removeSubGroup(parentG.id, sg.id)} className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded hover:bg-red-50" style={{ color: TEF_RED }}><Trash2 size={12} /> entfernen</button>
            </div>
          </div>
          <div className="flex-1 min-w-0 p-3 space-y-2.5 bg-gray-50">
            {mdmMix && <div className="flex items-start gap-2 text-sm rounded p-2 border" style={{ color: TEF_RED, borderColor: TEF_RED, background: "#fdecea" }}><AlertTriangle size={15} className="mt-0.5 shrink-0" />Kein Mix aus Managed und Unmanaged möglich – bitte nur eine Variante wählen.</div>}
            {sg.area === "office365" && <div className="rounded-lg border p-2.5" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
              <label className="flex items-center gap-2 text-sm font-medium" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!sg.managed} onChange={e => patchGroup(sg.id, { managed: e.target.checked })} />Managed (Verwaltung durch Telefónica)</label>
              <div className="text-[11px] text-gray-500 mt-1">Steuert Preise & max. Rabatt aller Lizenzen. Ohne Managed-Preis wird License-Only-Preis genutzt.</div>
            </div>}
            {(sg.area === "sdwan" || sg.area === "sdwanmeraki") && <div className="rounded-lg border p-2.5" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
              <label className={lbl} style={{ color: TEF_BLUE }}>Standard-Laufzeit (Vorauswahl neuer Produkte)</label>
              <select className={ctl} value={sg.sdwanTerm || 60} onChange={e => patchGroup(sg.id, { sdwanTerm: Number(e.target.value) })}>{SDWAN_TERMS.map(t => <option key={t} value={t}>{t} Monate</option>)}</select>
              <div className="text-[11px] text-gray-500 mt-1">Neu hinzugefügte Produkte starten mit dieser Laufzeit. Je Zeile weiterhin einzeln änderbar.</div>
            </div>}
            {sg.area === "individuell" && <div className="rounded-lg border p-2.5" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
              <label className={lbl} style={{ color: TEF_BLUE }}>Produktbereich (frei)</label>
              <input className={ctl} placeholder="z. B. Professional Services" value={sg.customArea || ""} onChange={e => patchGroup(sg.id, { customArea: e.target.value })} />
              <div className="text-[11px] text-gray-500 mt-1">Erscheint im Angebot als Unter-Überschrift dieses Bereichs.</div>
            </div>}
            {!sg.area ? <p className="text-sm text-gray-400">Bitte links einen Produktbereich wählen.</p> : sg.area === "vpnconnect" ? renderVpnEditor(sg) : <>
              {sg.rows.map(r => renderRow(sg, r))}
              <button onClick={() => addRow(sg.id)} style={{ color: TEF_BLUE_2, borderColor: TEF_BLUE_2 }} className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border-2 border-dashed text-sm font-semibold hover:bg-blue-50"><Plus size={15} /> Produkt hinzufügen</button>
            </>}
          </div>
        </div>
      </div>
    );
  };

  // ===== Bearbeiten-Reiter =====
  const PRICE_LABELS = { m24: "Monatl. 24 Mon. (€)", m36: "Monatl. 36+ Mon. (€)", o24: "Einmalig 24 Mon. (€)", o36: "Einmalig 36+ Mon. (€)", loPrice: "Monatlich (€)", mgdPrice: "Monatl. Managed (€)" };
  const editAreas = AREAS.filter(a => (BASE_CATALOG[a.key]?.products || []).length > 0);
  const q = editSearch.trim().toLowerCase();
  const productOverridden = (key) => { const ov = overrides.products[key]; return ov && Object.keys(ov).length > 0; };
  const isDescEmpty = (areaKey, baseProd) => {
    const ov = overrides.products[`${areaKey}::${baseProd.id}`] || {};
    const desc = "desc" in ov ? ov.desc : baseProd.desc;
    return !Array.isArray(desc) || desc.length === 0;
  };

  const renderProductCard = (areaKey, areaLabel, baseProd) => {
    const key = `${areaKey}::${baseProd.id}`;
    const ov = overrides.products[key] || {};
    const nameVal = "name" in ov ? ov.name : baseProd.name;
    const descVal = "desc" in ov ? (ov.desc || []).join("\n") : (Array.isArray(baseProd.desc) ? baseProd.desc : []).join("\n");
    const priceKeys = PRICE_FIELDS.filter(f => f in baseProd);
    const computed = baseProd.priceOpen || baseProd.mobile || baseProd.sdwan;
    const changed = productOverridden(key);
    const deleted = isDeleted(areaKey, baseProd.id);
    if (deleted) {
      return (
        <div key={key} className="rounded-lg border bg-gray-50 p-3 opacity-70">
          <div className="flex items-center justify-between gap-2">
            <div className="text-sm">
              <span className="font-semibold" style={{ color: TEF_GRAY }}>{nameVal}</span>
              <span className="ml-2 text-[10px] font-bold px-2 py-0.5 rounded-full" style={{ color: "#fff", background: TEF_RED }}>gelöscht</span>
              <div className="text-[11px] text-gray-400 mt-0.5">{areaLabel} · {baseProd.id}</div>
            </div>
            <button onClick={() => restoreProduct(areaKey, baseProd.id)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50 shrink-0" style={{ color: TEF_BLUE }}><RotateCcw size={12} /> wiederherstellen</button>
          </div>
        </div>
      );
    }
    return (
      <div key={key} className="rounded-lg border bg-white p-3" style={{ borderColor: changed ? TEF_BLUE_2 : "#e5e7eb" }}>
        <div className="flex items-center justify-between gap-2 mb-2">
          <div className="text-[11px] font-semibold" style={{ color: TEF_BLUE }}>{areaLabel} <span className="text-gray-400 font-normal">· {baseProd.id}</span></div>
          <div className="flex items-center gap-2">
            {changed && <span className="text-[10px] font-bold px-2 py-0.5 rounded-full" style={{ color: "#fff", background: TEF_BLUE_2 }}>geändert</span>}
            {changed && <button onClick={() => resetProduct(areaKey, baseProd.id)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Auf Originalwerte zurücksetzen"><RotateCcw size={12} /> zurücksetzen</button>}
            <button onClick={() => duplicateProduct(areaKey, baseProd)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Als eigene, bearbeitbare Kopie anlegen"><Copy size={12} /> duplizieren</button>
            <button onClick={() => deleteProduct(areaKey, baseProd.id, nameVal)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-red-50" style={{ color: TEF_RED }} title="Aus der Produktauswahl entfernen"><Trash2 size={12} /> löschen</button>
          </div>
        </div>
        <Field label="Produktname"><input className={ctl} value={nameVal} onChange={e => setProductField(areaKey, baseProd.id, "name", e.target.value)} /></Field>
        {priceKeys.length > 0 && <div className="mt-2 grid grid-cols-2 md:grid-cols-4 gap-2">
          {priceKeys.map(f => { const v = f in ov ? ov[f] : baseProd[f]; return (
            <Field key={f} label={PRICE_LABELS[f] || f}><input type="number" step="0.01" className={ctl} value={v ?? ""} onChange={e => setProductField(areaKey, baseProd.id, f, e.target.value)} /></Field>
          ); })}
        </div>}
        {baseProd.sdwan && baseProd.price && <div className="mt-2">
          <div className="text-[11px] font-medium mb-1" style={{ color: TEF_GRAY }}>Preis je Laufzeit (€/Monat – Bestpreis = unterste Grenze für Zeilen im Kalkulator)</div>
          <div className="grid grid-cols-5 gap-2">
            {SDWAN_TERMS.map(t => { const v = (ov.price && ov.price[t] != null) ? ov.price[t] : baseProd.price[t]; return (
              <Field key={t} label={t + " Mon."}><input type="number" step="0.01" className={ctl} value={v ?? ""} onChange={e => setSdwanPriceField(areaKey, baseProd.id, t, e.target.value)} /></Field>
            ); })}
          </div>
        </div>}
        {baseProd.sdwan && baseProd.lic && <div className="mt-2 space-y-2">
          {Object.keys(baseProd.lic).map(licType => (
            <div key={licType}>
              <div className="text-[11px] font-medium mb-1" style={{ color: TEF_GRAY }}>{sdwanLicLabel(baseProd.vendor, licType)} – Preis je Laufzeit (€/Monat)</div>
              <div className="grid grid-cols-5 gap-2">
                {SDWAN_TERMS.map(t => { const v = (ov.lic?.[licType] && ov.lic[licType][t] != null) ? ov.lic[licType][t] : baseProd.lic[licType][t]; return (
                  <Field key={t} label={t + " Mon."}><input type="number" step="0.01" className={ctl} value={v ?? ""} onChange={e => setSdwanLicField(areaKey, baseProd.id, licType, t, e.target.value)} /></Field>
                ); })}
              </div>
            </div>
          ))}
        </div>}
        {computed && !baseProd.sdwan && <div className="mt-2 text-[11px] text-amber-600 inline-flex items-center gap-1"><AlertTriangle size={12} /> Preis wird dynamisch ermittelt (ONKZ / Tarifoptionen) – Name & Beschreibung anpassbar.</div>}
        {baseProd.sdwan && <div className="mt-2 text-[11px] text-amber-600 inline-flex items-center gap-1"><AlertTriangle size={12} /> Bestpreis je Laufzeit. Im Kalkulator kann je Zeile ein höherer Preis eingetragen werden (Ersparnis-Anzeige); niedriger als der Bestpreis ist nicht möglich.</div>}
        <Field label="Beschreibung (eine Zeile je Stichpunkt)" className="mt-2"><textarea className={ctl + " min-h-[60px] resize-y"} value={descVal} onFocus={() => editPinned.current.add(key)} onChange={e => setProductDesc(areaKey, baseProd.id, e.target.value)} placeholder="leer = keine Stichpunkte" /></Field>
      </div>
    );
  };

  const renderCustomProductCard = (areaKey, areaLabel, c) => {
    const descVal = (c.desc || []).join("\n");
    const computed = c.priceOpen || c.mobile || c.sdwan;
    const priceKeys = PRICE_FIELDS.filter(f => f in c);
    return (
      <div key={`custom::${c.id}`} className="rounded-lg border bg-white p-3" style={{ borderColor: TEF_BLUE_2 }}>
        <div className="flex items-center justify-between gap-2 mb-2">
          <div className="text-[11px] font-semibold" style={{ color: TEF_BLUE }}>{areaLabel} <span className="text-gray-400 font-normal">· eigenes Produkt</span></div>
          <div className="flex items-center gap-2">
            <span className="text-[10px] font-bold px-2 py-0.5 rounded-full" style={{ color: "#fff", background: TEF_BLUE_2 }}>eigenes Produkt</span>
            <button onClick={() => duplicateCustomProduct(areaKey, c)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Als eigene, bearbeitbare Kopie anlegen"><Copy size={12} /> duplizieren</button>
            <button onClick={() => deleteCustomProduct(areaKey, c.id, c.name)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-red-50" style={{ color: TEF_RED }} title="Eigenes Produkt löschen"><Trash2 size={12} /> löschen</button>
          </div>
        </div>
        <Field label="Produktname"><input className={ctl} value={c.name} onChange={e => setCustomField(areaKey, c.id, "name", e.target.value)} /></Field>
        {!computed && priceKeys.length > 0 && <div className="mt-2 grid grid-cols-2 md:grid-cols-4 gap-2">
          {priceKeys.map(f => (
            <Field key={f} label={PRICE_LABELS[f] || f}><input type="number" step="0.01" className={ctl} value={c[f] ?? ""} onChange={e => setCustomField(areaKey, c.id, f, e.target.value)} /></Field>
          ))}
        </div>}
        {c.priceOpen && <div className="mt-2 text-[11px] text-amber-600 inline-flex items-center gap-1"><AlertTriangle size={12} /> Preis offen – wird im Kalkulator je Angebotszeile frei eingegeben.</div>}
        {computed && !c.priceOpen && <div className="mt-2 text-[11px] text-amber-600 inline-flex items-center gap-1"><AlertTriangle size={12} /> Preis wird über die Produktlogik berechnet (SD-WAN / Tarifoptionen) – Name & Beschreibung anpassbar.</div>}
        <div className="mt-2 flex items-center gap-4 flex-wrap">
          <label className="flex items-center gap-2 text-xs" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!c.qty} onChange={e => setCustomField(areaKey, c.id, "qty", e.target.checked)} />Mengenfeld anzeigen</label>
          {c.qty && <Field label="Bezeichnung Menge" className="w-40"><input className={ctl} value={c.qtyLabel || ""} onChange={e => setCustomField(areaKey, c.id, "qtyLabel", e.target.value)} /></Field>}
        </div>
        <Field label="Beschreibung (eine Zeile je Stichpunkt)" className="mt-2"><textarea className={ctl + " min-h-[60px] resize-y"} value={descVal} onFocus={() => editPinned.current.add(`custom::${c.id}`)} onChange={e => setCustomDesc(areaKey, c.id, e.target.value)} placeholder="leer = keine Stichpunkte" /></Field>
      </div>
    );
  };

  const renderAddForm = (areaKey) => {
    const open = addOpenAreas.has(areaKey);
    const draft = getDraft(areaKey);
    if (!open) return (
      <button onClick={() => toggleAddOpen(areaKey)} className="rounded-lg border-2 border-dashed flex items-center justify-center gap-2 text-sm font-semibold p-3 hover:bg-blue-50 min-h-[56px]" style={{ color: TEF_BLUE, borderColor: TEF_BLUE_2 }}><Plus size={16} /> Neues Produkt</button>
    );
    return (
      <div className="rounded-lg border-2 p-3" style={{ borderColor: TEF_BLUE_2, background: "#F7FAFF" }}>
        <div className="flex items-center justify-between gap-2 mb-2">
          <div className="text-[11px] font-semibold" style={{ color: TEF_BLUE }}>Neues Produkt</div>
          <button onClick={() => toggleAddOpen(areaKey)} className="text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-100" style={{ color: TEF_BLUE }}>abbrechen</button>
        </div>
        <Field label="Produktname"><input className={ctl} value={draft.name} onChange={e => setDraftField(areaKey, "name", e.target.value)} placeholder="z. B. Standortvernetzung Premium" /></Field>
        <label className="flex items-center gap-2 text-xs mt-2" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!draft.priceOpen} onChange={e => setDraftField(areaKey, "priceOpen", e.target.checked)} />Preis offen (im Kalkulator je Angebotszeile frei eingeben statt fester Preise)</label>
        {!draft.priceOpen && <div className="mt-2 grid grid-cols-2 md:grid-cols-4 gap-2">
          {["m24", "m36", "o24", "o36"].map(f => (
            <Field key={f} label={PRICE_LABELS[f]}><input type="number" step="0.01" className={ctl} value={draft[f]} onChange={e => setDraftField(areaKey, f, e.target.value)} /></Field>
          ))}
        </div>}
        <div className="mt-2 flex items-center gap-4 flex-wrap">
          <label className="flex items-center gap-2 text-xs" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!draft.qty} onChange={e => setDraftField(areaKey, "qty", e.target.checked)} />Mengenfeld anzeigen</label>
          {draft.qty && <Field label="Bezeichnung Menge" className="w-40"><input className={ctl} value={draft.qtyLabel} onChange={e => setDraftField(areaKey, "qtyLabel", e.target.value)} placeholder="z. B. Lizenzen" /></Field>}
        </div>
        <Field label="Beschreibung (eine Zeile je Stichpunkt)" className="mt-2"><textarea className={ctl + " min-h-[60px] resize-y"} value={draft.desc} onChange={e => setDraftField(areaKey, "desc", e.target.value)} placeholder="leer = keine Stichpunkte" /></Field>
        <button onClick={() => submitNewProduct(areaKey)} disabled={!draft.name?.trim()} className="mt-3 inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-semibold disabled:opacity-40" style={{ background: TEF_BLUE, color: "#fff" }}><Plus size={16} /> Produkt anlegen</button>
      </div>
    );
  };

  const renderAreaDescCard = (areaKey, areaLabel) => {
    const hasOverride = !!(overrides.areaDesc && areaKey in overrides.areaDesc);
    const effective = hasOverride ? (overrides.areaDesc[areaKey] || []) : (DEFAULT_AREA_DESC[areaKey] || []);
    const set = hasOverride;
    return (
      <div className="rounded-lg border p-3 mb-3" style={{ borderColor: TEF_BLUE_2, background: "#F7FAFF" }}>
        <div className="flex items-center justify-between gap-2 mb-1.5">
          <div className="text-xs font-semibold" style={{ color: TEF_BLUE }}>Bereichsbeschreibung – {areaLabel}</div>
          {set && <button onClick={() => resetAreaDesc(areaKey)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-100" style={{ color: TEF_BLUE }}><RotateCcw size={12} /> auf Standard zurücksetzen</button>}
        </div>
        <textarea className={ctl + " min-h-[60px] resize-y"} value={effective.join("\n")} onChange={e => setAreaDescText(areaKey, e.target.value)} placeholder={"leer = Automatik: gemeinsame Beschreibungszeilen aller Produkte dieses Bereichs werden automatisch erkannt"} />
        <div className="mt-1 text-[11px] text-gray-500">Steht im Angebot standardmäßig unter jeder Produktzeile dieses Bereichs. Über „Produktbeschreibung einblenden" wandert sie in den Bereichs-Header. Eigener Text überschreibt den Standard.</div>
      </div>
    );
  };

  // ===== VPN Connect: ein Anschluss (Haupt oder Backup) =====
  const renderVpnConn = (g, role) => {
    const c = g[role];
    if (!c) return null;
    const term = Number(g.vpnTerm) || 36;
    const isMain = role === "main";
    const accessOpts = isMain ? ["asym", "link", "mobile", "ipsec"] : (vpnBackupOptions(g.main).map(o => o.access));
    const allowedForAccess = isMain ? null : (vpnBackupOptions(g.main).find(o => o.access === c.access));
    const bwList = c.access === "asym" ? (allowedForAccess?.bw || VPN_ASYM_BW) : c.access === "link" ? (allowedForAccess?.bw || VPN_LINK_BW) : c.access === "ipsec" ? (allowedForAccess?.bw || VPN_IPSEC_BW) : [];
    const base = vpnAccessBase(c, term), svc = vpnService(c, term, role);
    const manual = c.access === "link" && (c.provider === "andere" || c.rabatt === "pricing");
    return (
      <div className="rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: isMain ? "#F7FAFF" : "#FFF7F0" }}>
        <div className="text-xs font-bold mb-2" style={{ color: TEF_BLUE }}>{isMain ? "Hauptanschluss" : "Backup-Anschluss"}</div>
        <div className="flex flex-wrap gap-3 items-end">
          <Field label="Anschlussart" className="w-44"><select className={ctl} value={c.access} onChange={e => setVpnAccess(g.id, role, e.target.value)}>{accessOpts.map(a => <option key={a} value={a}>{VPN_ACCESS_LABEL[a]}</option>)}</select></Field>
          {c.access !== "mobile" && <Field label="Bandbreite" className="w-36"><select className={ctl} value={c.bw} onChange={e => patchVpnConn(g.id, role, { bw: e.target.value })}>{bwList.map(b => <option key={b} value={b}>bis {b} Mbit/s</option>)}</select></Field>}
          {c.access === "link" && <>
            <Field label="Anbindung" className="w-32"><select className={ctl} value={c.provider} onChange={e => patchVpnConn(g.id, role, { provider: e.target.value })}><option value="dtag">DTAG</option><option value="andere">Andere</option></select></Field>
            {c.provider === "dtag" && <Field label="ONKZ" className="w-28"><input className={ctl} placeholder="z. B. 0711" value={c.onkz} onChange={e => patchVpnConn(g.id, role, { onkz: e.target.value })} /></Field>}
          </>}
          {c.access === "mobile" && isMain && <Field label="Datentarif" className="w-36"><select className={ctl} value={c.dataPack} onChange={e => patchVpnConn(g.id, role, { dataPack: e.target.value })}>{Object.keys(VPN_DATAPACK_LABEL).map(k => <option key={k} value={k}>{VPN_DATAPACK_LABEL[k]}</option>)}</select></Field>}
          <Field label="Rabatt" className="w-32"><select className={ctl} value={c.rabatt} onChange={e => patchVpnConn(g.id, role, { rabatt: e.target.value })}>
            <option value="0">0 %</option>
            {c.access === "link" && <><option value="10">10 %</option><option value="20">20 %</option><option value="30">30 %</option></>}
            <option value="pricing">Pricing</option>
          </select></Field>
          {manual && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={c.manMonthly} onChange={e => patchVpnConn(g.id, role, { manMonthly: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={c.manOneoff} onChange={e => patchVpnConn(g.id, role, { manOneoff: e.target.value })} /></Field></>}
          {c.access === "mobile" && <Field label="Antenne (optional)" className="w-52"><select className={ctl} value={c.antenne} onChange={e => patchVpnConn(g.id, role, { antenne: e.target.value })}><option value="">— keine —</option>{Object.entries(VPN_ANTENNE).map(([k, v]) => <option key={k} value={k}>{v.label} ({eur(v.o)})</option>)}</select></Field>}
        </div>
        <div className="mt-2 flex items-center gap-4 flex-wrap">
          {c.access === "link" && <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!c.internet} onChange={e => patchVpnConn(g.id, role, { internet: e.target.checked })} />Option Internet</label>}
          {(c.access === "asym" || c.access === "link") && <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!c.qos} onChange={e => patchVpnConn(g.id, role, { qos: e.target.checked })} />Quality of Service</label>}
        </div>
        <div className="mt-2 text-xs text-gray-600">
          Access: <b style={{ color: TEF_BLUE }}>{eur(base.monthly)}</b>/Mon. {base.oneoff ? <>· {eur(base.oneoff)} einmalig </> : ""}
          {c.access === "link" && !manual && <>· Region <b>{base.region}</b>{!c.onkz && <span className="text-gray-400"> (ONKZ eingeben)</span>} </>}
          · {c.access === "mobile" ? <>Data-Pack <b style={{ color: TEF_BLUE }}>{eur(svc.svc)}</b> + MPLS {eur(svc.mpls)}</> : <>VPN-Service <b style={{ color: TEF_BLUE }}>{eur(svc.svc)}</b></>}
          {manual && <span className="text-amber-600"> · Preis manuell</span>}
        </div>
      </div>
    );
  };

  // ===== VPN Connect: kompletter Standort-Editor =====
  const renderVpnEditor = (g) => {
    const backupPossible = vpnBackupOptions(g.main).length > 0;
    const anyQos = (g.main?.qos && (g.main.access === "asym" || g.main.access === "link")) || (g.backup?.qos && (g.backup.access === "asym" || g.backup.access === "link"));
    return (
      <div className="space-y-3">
        <div className="rounded-lg border p-3 flex flex-wrap gap-3 items-end" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
          <Field label="Standortname" className="grow min-w-[200px]"><input className={ctl} placeholder="z. B. Werk Kirchheim" value={g.vpnName || ""} onChange={e => patchGroup(g.id, { vpnName: e.target.value })} /></Field>
          <Field label="Laufzeit (bereichsweit)" className="w-44"><select className={ctl} value={g.vpnTerm || 36} onChange={e => setVpnTerm(Number(e.target.value))}>{VPN_TERMS.map(t => <option key={t} value={t}>{t} Monate</option>)}</select></Field>
        </div>
        {renderVpnConn(g, "main")}
        {g.backup ? <div className="space-y-2">
          {renderVpnConn(g, "backup")}
          <button onClick={() => toggleVpnBackup(g.id)} className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded hover:bg-red-50" style={{ color: TEF_RED }}><Trash2 size={12} /> Backup-Anschluss entfernen</button>
        </div> : backupPossible ? (
          <button onClick={() => toggleVpnBackup(g.id)} style={{ color: TEF_BLUE_2, borderColor: TEF_BLUE_2 }} className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border-2 border-dashed text-sm font-semibold hover:bg-blue-50"><Plus size={15} /> Backup-Anschluss hinzufügen</button>
        ) : <div className="text-xs text-gray-500 inline-flex items-center gap-1"><AlertTriangle size={12} /> Für die gewählte Hauptanschlussart ist laut Backup-Matrix kein Backup vorgesehen.</div>}
        <div className="rounded-lg border p-3" style={{ borderColor: "#e5e7eb" }}>
          <div className="text-xs font-bold mb-2" style={{ color: TEF_BLUE }}>Weitere Optionen (Standort)</div>
          <div className="flex flex-col gap-2">
            {g.backup && g.backup.access && <div className="flex items-center gap-3 flex-wrap">
              <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!g.backupPlus} onChange={e => patchGroup(g.id, { backupPlus: e.target.checked })} />Backup-Variante ++ (Redundanz über zwei Router, kostenfrei)</label>
              {g.backupPlus && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={g.bpM} onChange={e => patchGroup(g.id, { bpM: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={g.bpO} onChange={e => patchGroup(g.id, { bpO: e.target.value })} /></Field></>}
            </div>}
            <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!g.netflow} onChange={e => patchGroup(g.id, { netflow: e.target.checked })} />NetFlow ({eur(optPrice("vpn_netflow_o"))} einmalig)</label>
            <label className={"flex items-center gap-2 text-sm " + (anyQos ? "" : "opacity-40")} style={{ color: TEF_GRAY }}><input type="checkbox" disabled={!anyQos} checked={!!g.citrix && anyQos} onChange={e => patchGroup(g.id, { citrix: e.target.checked })} />Citrix Priorisierung ({eur(optPrice("vpn_citrix_o"))} einmalig){!anyQos && <span className="text-[11px] text-gray-400">– nur mit Quality of Service an einem Anschluss</span>}</label>
            <div className="flex items-center gap-3 flex-wrap">
              <label className="flex items-center gap-2 text-sm" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!g.advHw} onChange={e => patchGroup(g.id, { advHw: e.target.checked })} />Advanced Hardware (Aufpreis manuell)</label>
              {g.advHw && <><Field label="Monatlich (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={g.advHwM} onChange={e => patchGroup(g.id, { advHwM: e.target.value })} /></Field><Field label="Einmalig (€)" className="w-32"><input type="number" step="0.01" className={ctl} value={g.advHwO} onChange={e => patchGroup(g.id, { advHwO: e.target.value })} /></Field></>}
            </div>
          </div>
        </div>
      </div>
    );
  };

  // ===== Standort-Import (CSV/Text, eine Spalte) =====
  const parseImportText = (text) => {
    const lines = (text || "").split(/\r?\n/);
    let names = lines.map(l => (l.split(/[;,\t]/)[0] || "").trim()).filter(Boolean);
    if (names.length && /^(standort|standorte|name|site|sites|filiale|filialen|location|standortname)$/i.test(names[0])) names = names.slice(1);
    return names;
  };
  const importSites = parseImportText(importText);
  const onImportFile = (e) => { const f = e.target.files?.[0]; if (!f) return; const rd = new FileReader(); rd.onload = () => { setImportText(String(rd.result || "")); setImportMsg(""); }; rd.readAsText(f); e.target.value = ""; };
  const [importBusy, setImportBusy] = useState(false);
  const onImportWorkbook = (e) => {
    const f = e.target.files?.[0]; if (!f) return; setImportBusy(true);
    const rd = new FileReader();
    rd.onload = () => {
      try {
        const wb = XLSX.read(rd.result, { type: "array" });
        const { groups: g, count } = parseImportWorkbook(wb);
        if (!g.length) { setImportMsg("Keine ausgefüllten Produktzeilen in der Excel-Vorlage gefunden. Bitte in den Bereichsblättern Produkte auswählen."); setImportBusy(false); return; }
        setGroups(g);
        const sites = new Set(); for (const grp of g) { if (grp.area === "vpnconnect") sites.add((grp.vpnName || "").trim()); else for (const r of (grp.rows || [])) sites.add((r.standort || "").trim()); }
        const nSites = [...sites].filter(Boolean).length;
        setImportMsg(`${count} Position(en) aus ${g.length} Bereich(en) importiert${nSites ? `, ${nSites} Standort(e) erkannt` : ""}. Tipp: Mit der Checkbox „Standortangebot" neben dem Kundennamen in ein Standortangebot umwandeln.`);
        setImportBusy(false); setTab("calc");
      } catch (err) { setImportMsg("Die Datei konnte nicht gelesen werden. Bitte die unveränderte Excel-Vorlage verwenden (.xlsx)."); setImportBusy(false); }
    };
    rd.onerror = () => { setImportMsg("Datei konnte nicht geladen werden."); setImportBusy(false); };
    rd.readAsArrayBuffer(f); e.target.value = "";
  };
  const wbImportRef = useRef(null);
  const downloadTemplate = () => {
    try {
      const bin = atob(TEMPLATE_XLSX_B64);
      const bytes = new Uint8Array(bin.length);
      for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
      const blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a"); a.href = url; a.download = "TEF-Kalkulator-Import-Vorlage.xlsx";
      document.body.appendChild(a); a.click(); document.body.removeChild(a);
      setTimeout(() => URL.revokeObjectURL(url), 1500);
    } catch { alert("Vorlage konnte nicht heruntergeladen werden."); }
  };
  const applySiteName = (g, site) => {
    if (g.area === "vpnconnect") { g.vpnName = site; return; }
    if (g.area === "standort") { g.standortName = site; (g.subGroups || []).forEach(sg => applySiteName(sg, site)); return; }
    (g.rows || []).forEach(r => { r.standort = site; });
  };
  const doImportSites = () => {
    const sites = importSites;
    if (!sites.length) { setImportMsg("Keine Standorte erkannt – bitte eine Liste einfügen oder Datei hochladen (ein Standort je Zeile)."); return; }
    const template = groups.filter(g => g.area);
    let result = [];
    if (importAsStandort) {
      let subTemplate = [];
      for (const g of template) { if (g.area === "standort") subTemplate.push(...(g.subGroups || [])); else subTemplate.push(g); }
      subTemplate = subTemplate.filter(sg => sg.area);
      for (const site of sites) {
        const subs = subTemplate.length
          ? subTemplate.map(t => { const c = reidGroup(JSON.parse(JSON.stringify({ ...t, standortName: "" }))); return c; })
          : [newGroup()];
        result.push({ ...newGroup(), area: "standort", rows: [], standortName: site, subGroups: subs });
      }
    } else {
      if (!template.length) { setImportMsg("Für den Modus Vervielfachen bitte zuerst im Kalkulator ein Produkt anlegen, das je Standort kopiert werden soll."); return; }
      for (const site of sites) for (const t of template) { const c = reidGroup(JSON.parse(JSON.stringify(t))); applySiteName(c, site); result.push(c); }
    }
    if (!result.length) result = [newGroup()];
    setGroups(result);
    setImportMsg(`${sites.length} Standort(e) angelegt${importAsStandort ? " (als Standortangebote)" : " (vervielfacht)"}.`);
    setTab("calc");
  };

  const renderImport = () => {
    const tplCount = groups.filter(g => g.area).length;
    return (
      <div className="bg-white rounded-b-xl shadow-sm p-5 md:p-7">
        <div className="mb-6 rounded-xl border-2 p-5" style={{ borderColor: TEF_BLUE_2, background: "#F7FAFF" }}>
          <h2 style={{ color: TEF_BLUE }} className="font-bold text-lg mb-1">Komplettes Angebot aus Excel-Vorlage importieren</h2>
          <p className="text-sm text-gray-600 mb-4 max-w-3xl">Lade die ausgefüllte <b>Excel-Import-Vorlage</b> hoch. Jedes Tabellenblatt steht für einen Produktbereich; je Zeile wählst du Standort, Produkt, Laufzeit, Menge und Optionen per Dropdown. Der Kalkulator erstellt daraus das fertige Angebot – die Excel enthält bewusst keine Preise/Berechnungen.</p>
          <input ref={wbImportRef} type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" onChange={onImportWorkbook} style={{ display: "none" }} />
          <div className="flex flex-wrap items-center gap-3">
            <button onClick={() => wbImportRef.current?.click()} disabled={importBusy} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-white text-sm font-semibold disabled:opacity-50" style={{ background: TEF_BLUE }}><Upload size={16} /> {importBusy ? "Wird gelesen…" : "Excel-Vorlage hochladen (.xlsx)"}</button>
            <span className="text-xs text-gray-500">Bis zu 1.000 Standorte je Produktbereich. Danach per Checkbox „Standortangebot" umwandelbar.</span>
          </div>
          <p className="text-[11px] text-gray-500 mt-3">Du hast die Vorlage noch nicht? <button onClick={downloadTemplate} className="font-semibold underline hover:no-underline" style={{ color: TEF_BLUE }}>TEF-Kalkulator-Import-Vorlage.xlsx herunterladen</button> – einfach ausfüllen und hier wieder hochladen.</p>
        </div>

        <h2 style={{ color: TEF_BLUE }} className="font-bold text-lg mb-1">Standorte importieren (nur Namensliste)</h2>
        <p className="text-sm text-gray-600 mb-5 max-w-3xl">Alternativ: Lade eine CSV-/Text-Datei mit <b>einer Spalte</b> hoch (ein Standort je Zeile) oder füge die Liste direkt ein. Die im Reiter „Kalkulator" angelegte Konfiguration dient als <b>Vorlage</b> und wird je Standort vervielfältigt.</p>
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
          <div>
            <label style={{ color: TEF_BLUE }} className={lbl}>Datei (CSV / TXT / TSV)</label>
            <input type="file" accept=".csv,.txt,.tsv,text/csv,text/plain" onChange={onImportFile} className="block w-full text-sm mb-3 file:mr-3 file:px-3 file:py-1.5 file:rounded file:border-0 file:text-white file:text-sm file:cursor-pointer" style={{ }} />
            <label style={{ color: TEF_BLUE }} className={lbl}>… oder Liste einfügen (ein Standort je Zeile)</label>
            <textarea className={ctl + " min-h-[160px] resize-y font-mono text-sm"} placeholder={"Werk Kirchheim\nFiliale Stuttgart\nLogistik Ulm"} value={importText} onChange={e => { setImportText(e.target.value); setImportMsg(""); }} />
          </div>
          <div>
            <label style={{ color: TEF_BLUE }} className={lbl}>Erkannte Standorte ({importSites.length})</label>
            <div className="rounded-lg border border-gray-200 p-2 min-h-[160px] max-h-[260px] overflow-auto bg-gray-50">
              {importSites.length === 0 ? <p className="text-sm text-gray-400 p-2">Noch keine Standorte erkannt.</p>
                : <ol className="list-decimal list-inside text-sm space-y-0.5">{importSites.map((s, i) => <li key={i} style={{ color: TEF_GRAY }}>{s}</li>)}</ol>}
            </div>
          </div>
        </div>

        <div className="mt-5 rounded-lg border p-4" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
          <label className="flex items-center gap-2 text-sm font-semibold mb-2" style={{ color: TEF_GRAY }}>
            <input type="checkbox" checked={importAsStandort} onChange={e => setImportAsStandort(e.target.checked)} />
            Als Standortangebote anlegen
          </label>
          {importAsStandort
            ? <p className="text-xs text-gray-600">Je Standort wird ein <b>Standortangebot</b> mit dem Standortnamen angelegt. Die aktuellen Bereiche der Vorlage ({tplCount}) werden als <b>Unterbereiche</b> in jedes Standortangebot kopiert. Ideal für komplexe Vernetzungs-Konstrukte (z. B. je Standort All&nbsp;IP + SD-WAN + VPN-Backup).</p>
            : <p className="text-xs text-gray-600">Die aktuellen Bereiche/Produkte der Vorlage ({tplCount}) werden je Standort <b>kopiert</b>; der Standortname wird in das <b>Standort-Feld</b> jeder Zeile (bzw. den Namen bei VPN) eingetragen. Ideal, um <b>ein</b> Produkt mehrfach auszuweisen (z. B. 6× All&nbsp;IP Business mit Mobilfunk-Backup).</p>}
          <p className="text-[11px] text-amber-700 mt-2"><b>Achtung:</b> Der Import <b>ersetzt</b> die aktuelle Angebotsstruktur durch die vervielfältigte Vorlage.</p>
        </div>

        {tplCount === 0 && <div className="mt-3 text-xs text-amber-700 inline-flex items-center gap-1"><AlertTriangle size={13} /> Aktuell ist keine Vorlage angelegt. {importAsStandort ? "Es werden leere Standortangebote erstellt, die du danach befüllst." : "Bitte zuerst im Kalkulator ein Produkt anlegen."}</div>}

        <div className="mt-5 flex items-center gap-3">
          <button onClick={doImportSites} disabled={importSites.length === 0} style={{ background: TEF_BLUE }} className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-semibold disabled:opacity-40"><Upload size={16} /> {importSites.length || 0} Standort(e) anlegen</button>
          {importMsg && <span className="text-sm" style={{ color: TEF_BLUE }}>{importMsg}</span>}
        </div>
      </div>
    );
  };

  const renderEditor = () => {
    const showOpts = editArea === "all" || editArea === "opts";
    const visibleAreas = editAreas.filter(a => editArea === "all" || editArea === a.key);
    const optMatches = OPT_SCHEMA.filter(o => !q || o.label.toLowerCase().includes(q) || o.key.toLowerCase().includes(q));
    const hasFilter = !!q || editChangedOnly || editEmptyOnly;
    let productCount = 0;
    const areaBlocks = visibleAreas.map(a => {
      const prods = (BASE_CATALOG[a.key].products || []).filter(p => {
        if (p.isSeparator) return false;
        if (editChangedOnly && !productOverridden(`${a.key}::${p.id}`) && !isDeleted(a.key, p.id)) return false;
        if (editEmptyOnly && !isDescEmpty(a.key, p) && !editPinned.current.has(`${a.key}::${p.id}`)) return false;
        if (!q) return true;
        return p.name.toLowerCase().includes(q) || p.id.toLowerCase().includes(q) || a.label.toLowerCase().includes(q);
      });
      const customs = (overrides.custom || []).filter(c => {
        if (c.area !== a.key) return false;
        if (editEmptyOnly && (c.desc || []).length > 0 && !editPinned.current.has(`custom::${c.id}`)) return false;
        if (!q) return true;
        return (c.name || "").toLowerCase().includes(q) || a.label.toLowerCase().includes(q);
      });
      productCount += prods.length + customs.length;
      if (!prods.length && !customs.length && hasFilter) return null;
      return (
        <div key={a.key} className="mb-5">
          <h3 className="text-sm font-bold mb-2 sticky top-0 py-1" style={{ color: TEF_BLUE, background: "#fff" }}>{a.label} <span className="text-gray-400 font-normal">({prods.length + customs.length})</span></h3>
          {!hasFilter && renderAreaDescCard(a.key, a.label)}
          <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
            {prods.map(p => renderProductCard(a.key, a.label, p))}
            {customs.map(c => renderCustomProductCard(a.key, a.label, c))}
            {!hasFilter && renderAddForm(a.key)}
          </div>
        </div>
      );
    });
    const optsChanged = editChangedOnly ? optMatches.filter(o => o.key in overrides.opts) : optMatches;

    return (
      <div className="bg-white rounded-b-xl shadow-sm p-5 md:p-7">
        <div className="flex items-start justify-between gap-3 flex-wrap mb-4">
          <div>
            <h2 style={{ color: TEF_BLUE }} className="font-bold text-lg">Produkte &amp; Texte bearbeiten</h2>
            <p className="text-xs text-gray-500 mt-0.5">Änderungen gelten dauerhaft für diesen Kalkulator und werden automatisch gespeichert. Preisänderungen wirken sofort – auch auf bereits platzierte Produkte.</p>
          </div>
          <div className="flex items-center gap-2 flex-wrap">
            <button onClick={() => cfgRef.current?.click()} className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg border text-xs font-semibold hover:bg-blue-50" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }}><Upload size={14} /> Konfig importieren</button>
            <button onClick={exportConfig} className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg border text-xs font-semibold hover:bg-blue-50" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }}><Download size={14} /> Konfig exportieren</button>
            <button onClick={resetAllOverrides} className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg border text-xs font-semibold hover:bg-red-50" style={{ color: TEF_RED, borderColor: TEF_RED }}><RotateCcw size={14} /> Alles zurücksetzen</button>
          </div>
        </div>
        {cfgStatus && <div className="mb-3 text-xs font-medium" style={{ color: TEF_BLUE }}>{cfgStatus}</div>}

        <div className="flex items-end gap-3 flex-wrap mb-5 p-3 rounded-lg" style={{ background: "#EEF2FF" }}>
          <Field label="Suche" className="grow min-w-[220px]">
            <div className="relative">
              <Search size={15} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" />
              <input className={ctl + " pl-8"} placeholder="Produktname, ID oder Optionstext …" value={editSearch} onChange={e => setEditSearch(e.target.value)} />
            </div>
          </Field>
          <Field label="Bereich" className="w-56">
            <select className={ctl} value={editArea} onChange={e => setEditArea(e.target.value)}>
              <option value="all">Alle Bereiche</option>
              {editAreas.map(a => <option key={a.key} value={a.key}>{a.label}</option>)}
              <option value="vpnconnect">VPN Connect</option>
              <option value="opts">Nur Optionen &amp; Tarife</option>
            </select>
          </Field>
          <label className="flex items-center gap-2 text-sm mb-2" style={{ color: TEF_GRAY }}><input type="checkbox" checked={editChangedOnly} onChange={e => setEditChangedOnly(e.target.checked)} />nur geänderte</label>
          <label className="flex items-center gap-2 text-sm mb-2" style={{ color: TEF_GRAY }}><input type="checkbox" checked={editEmptyOnly} onChange={e => { editPinned.current = new Set(); setEditEmptyOnly(e.target.checked); }} />nur leere</label>
        </div>

        {editArea !== "opts" && editArea !== "vpnconnect" && (productCount === 0
          ? <p className="text-sm text-gray-400 mb-6">Keine Produkte für diese Suche/Filter.</p>
          : areaBlocks)}

        {(editArea === "all" || editArea === "vpnconnect") && !hasFilter && (() => {
          const vd = resolveVpnDesc(overrides.vpnDesc);
          const slot = (key, label) => (
            <div key={key} className="rounded-lg border bg-white p-3" style={{ borderColor: TEF_BLUE_2 }}>
              <div className="text-[11px] font-semibold mb-1" style={{ color: TEF_BLUE }}>{label}</div>
              <textarea className={ctl + " min-h-[60px] resize-y"} value={(vd[key] || []).join("\n")} onChange={e => setVpnDescField(key, e.target.value)} placeholder="eine Zeile je Stichpunkt · leer = keine" />
            </div>
          );
          return (
            <div className="mb-5">
              <h3 className="text-sm font-bold mb-2 pt-2 border-t flex items-center justify-between" style={{ color: TEF_BLUE }}>
                <span>VPN Connect – Beschreibungen</span>
                <button onClick={resetVpnDesc} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><RotateCcw size={12} /> zurücksetzen</button>
              </h3>
              <p className="text-[11px] text-gray-500 mb-2">Bereichsweite Texte erscheinen als Überschrift jedes VPN-Standorts; je Zugangsart erscheinen sie an der jeweiligen Anschlusszeile im Angebot.</p>
              <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
                {slot("area", "Bereichsweit (z. B. Hardware inklusive, Gemanaged durch Telefónica)")}
                {slot("asym", "Asymmetrisch")}
                {slot("link", "Link Symmetrisch")}
                {slot("mobile", "Mobilfunk")}
                {slot("ipsec", "IPSec")}
              </div>
            </div>
          );
        })()}

        {showOpts && optsChanged.length > 0 && <div className="mb-2">
          <h3 className="text-sm font-bold mb-2 pt-2 border-t" style={{ color: TEF_BLUE }}>Optionen &amp; Tarife</h3>
          <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
            {optsChanged.map(o => {
              const isLines = o.type === "lines";
              const cur = o.key in overrides.opts ? overrides.opts[o.key] : OPT_DEFAULTS[o.key];
              const changed = o.key in overrides.opts;
              return (
                <div key={o.key} className="rounded-lg border bg-white p-3" style={{ borderColor: changed ? TEF_BLUE_2 : "#e5e7eb" }}>
                  <div className="flex items-center justify-between gap-2 mb-1.5">
                    <div className="text-xs font-semibold" style={{ color: TEF_BLUE }}>{o.label}</div>
                    <div className="flex items-center gap-2">
                      {changed && <span className="text-[10px] font-bold px-2 py-0.5 rounded-full" style={{ color: "#fff", background: TEF_BLUE_2 }}>geändert</span>}
                      {changed && <button onClick={() => resetOpt(o.key)} className="inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><RotateCcw size={12} /> zurücksetzen</button>}
                    </div>
                  </div>
                  {isLines
                    ? <textarea className={ctl + " min-h-[80px] resize-y"} value={(cur || []).join("\n")} onChange={e => setOptField(o.key, e.target.value.split("\n"))} />
                    : <input className={ctl} value={cur || ""} onChange={e => setOptField(o.key, e.target.value)} />}
                </div>
              );
            })}
          </div>
        </div>}

        {showOpts && (() => {
          const PRICE_SCHEMA = [
            { key: "basic_kanal2", label: "2. Sprachkanal (€/Monat)" },
            { key: "basic_router_dsl_1", label: "Router DSL Option 1 – Aufpreis (€/Monat)" },
            { key: "basic_router_dsl_2", label: "Router DSL Option 2 – Aufpreis (€/Monat)" },
            { key: "basic_router_cable_1", label: "Router Cable Option 1 – Aufpreis (€/Monat)" },
            { key: "basic_router_cable_2", label: "Router Cable Option 2 – Aufpreis (€/Monat)" },
            { key: "basic_router_fiber_1", label: "Router Fiber Option 1 – Aufpreis (€/Monat)" },
            { key: "basic_router_fiber_2", label: "Router Fiber Option 2 – Aufpreis (€/Monat)" },
            { key: "basic_router_fiber_3", label: "Router Fiber Option 3 – Aufpreis (€/Monat)" },
            { key: "basic_euplus_24", label: "Fair Use EU Plus – 24 Mon. (€/Monat)" },
            { key: "basic_euplus_36", label: "Fair Use EU Plus – 36+ Mon. (€/Monat)" },
            { key: "basic_worldselect_24", label: "Fair Use World Select – 24 Mon. (€/Monat)" },
            { key: "basic_worldselect_36", label: "Fair Use World Select – 36+ Mon. (€/Monat)" },
          ].filter(o => !q || o.label.toLowerCase().includes(q) || o.key.toLowerCase().includes(q));
          const shown = editChangedOnly ? PRICE_SCHEMA.filter(o => o.key in (overrides.optPrices || {})) : PRICE_SCHEMA;
          if (!shown.length) return null;
          return (
            <div className="mb-2">
              <h3 className="text-sm font-bold mb-2 pt-2 border-t" style={{ color: TEF_BLUE }}>All IP Basic – Optionspreise</h3>
              <p className="text-[11px] text-gray-500 mb-2">Beschreibungen dieser Optionen findest du oben unter „Optionen &amp; Tarife" (EU Plus / World Select / Router-Namen / Sprachkanal-Texte).</p>
              <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                {shown.map(o => {
                  const changed = o.key in (overrides.optPrices || {});
                  const val = changed ? overrides.optPrices[o.key] : OPTPRICE_DEFAULTS[o.key];
                  return (
                    <div key={o.key} className="rounded-lg border bg-white p-3" style={{ borderColor: changed ? TEF_BLUE_2 : "#e5e7eb" }}>
                      <div className="flex items-center justify-between gap-1 mb-1.5">
                        <div className="text-[11px] font-semibold leading-tight" style={{ color: TEF_BLUE }}>{o.label}</div>
                        {changed && <button onClick={() => resetOptPrice(o.key)} className="shrink-0 inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><RotateCcw size={11} /></button>}
                      </div>
                      <input type="number" step="0.01" className={ctl} value={val ?? ""} onChange={e => setOptPrice(o.key, e.target.value)} />
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })()}

        {(() => {
          const priceCard = (key, label) => {
            if (q && !label.toLowerCase().includes(q) && !key.toLowerCase().includes(q)) return null;
            const changed = key in (overrides.optPrices || {});
            if (editChangedOnly && !changed) return null;
            const val = changed ? overrides.optPrices[key] : OPTPRICE_DEFAULTS[key];
            return (
              <div key={key} className="rounded-lg border bg-white p-3" style={{ borderColor: changed ? TEF_BLUE_2 : "#e5e7eb" }}>
                <div className="flex items-center justify-between gap-1 mb-1.5">
                  <div className="text-[11px] font-semibold leading-tight" style={{ color: TEF_BLUE }}>{label}</div>
                  {changed && <button onClick={() => resetOptPrice(key)} className="shrink-0 inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><RotateCcw size={11} /></button>}
                </div>
                <input type="number" step="0.01" className={ctl} value={val ?? ""} onChange={e => setOptPrice(key, e.target.value)} />
              </div>
            );
          };
          const tableCard = (key, label, terms) => {
            if (q && !label.toLowerCase().includes(q) && !key.toLowerCase().includes(q)) return null;
            const changed = key in (overrides.optTables || {});
            if (editChangedOnly && !changed) return null;
            const tbl = optTable(key);
            return (
              <div key={key} className="rounded-lg border bg-white p-3" style={{ borderColor: changed ? TEF_BLUE_2 : "#e5e7eb" }}>
                <div className="flex items-center justify-between gap-1 mb-1.5">
                  <div className="text-[11px] font-semibold leading-tight" style={{ color: TEF_BLUE }}>{label}</div>
                  {changed && <button onClick={() => resetOptTable(key)} className="shrink-0 inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><RotateCcw size={11} /></button>}
                </div>
                <div className="grid gap-1.5" style={{ gridTemplateColumns: `repeat(${terms.length}, minmax(0,1fr))` }}>
                  {terms.map(t => (
                    <div key={t}>
                      <div className="text-[10px] text-gray-400 mb-0.5">{t} Mon.</div>
                      <input type="number" step="0.01" className={ctl} value={tbl[t] ?? ""} onChange={e => setOptTableCell(key, t, e.target.value)} />
                    </div>
                  ))}
                </div>
              </div>
            );
          };

          // ===== SD-WAN: Pauschalen, Aufschläge, Redundanz-Nachlässe =====
          const sdwanTableCards = [
            tableCard("sdwan_red_fortinet_sdwan", "Fortinet – Redundanz-Nachlass · SD-WAN (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_red_fortinet_atp", "Fortinet – Redundanz-Nachlass · Basic Security (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_red_fortinet_utp", "Fortinet – Redundanz-Nachlass · Advanced Security (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_red_meraki_sdwan", "Meraki – Redundanz-Nachlass · SD-WAN (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_red_meraki_atp", "Meraki – Redundanz-Nachlass · Basic Security (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_red_meraki_utp", "Meraki – Redundanz-Nachlass · Advanced Security (€/Monat)", SDWAN_TERMS),
            tableCard("sdwan_einr_router", "Einrichtung SD-WAN-Service – Router/VM (€ einmalig je Stück)", SDWAN_TERMS),
            tableCard("sdwan_einr_apsw", "Einrichtung SD-WAN-Service – Access Point/Switch (€ einmalig je Stück)", SDWAN_TERMS),
          ].filter(Boolean);
          const sdwanPriceCards = [
            priceCard("sdwan_remote_setup", "Remote-User – Einrichtung je 25er-Paket (€ einmalig)"),
            priceCard("sdwan_install_per_device", "Vor-Ort-Installation je Gerät (€ einmalig)"),
            priceCard("sdwan_ad_oneoff", "AD-Anbindung je Standort (€ einmalig)"),
            priceCard("sdwan_project_tier1", "Projektkosten 2–4 Standorte (€ einmalig)"),
            priceCard("sdwan_project_tier2", "Projektkosten 5–10 Standorte (€ einmalig)"),
          ].filter(Boolean);
          const sdwanSection = (sdwanTableCards.length > 0 || sdwanPriceCards.length > 0) && (
            <div className="mb-2">
              <h3 className="text-sm font-bold mb-2 pt-2 border-t" style={{ color: TEF_BLUE }}>SD-WAN – Pauschalen &amp; Aufschläge (Fortinet + Meraki)</h3>
              <p className="text-[11px] text-gray-500 mb-2">Geräte-Einzelpreise (je Modell/Lizenztyp) bearbeitest du direkt am jeweiligen Produkt unter „SD-WAN Fortinet" / „SD-WAN Meraki" weiter oben.</p>
              {sdwanTableCards.length > 0 && <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">{sdwanTableCards}</div>}
              {sdwanPriceCards.length > 0 && <div className="grid grid-cols-2 md:grid-cols-4 gap-3">{sdwanPriceCards}</div>}
            </div>
          );

          // ===== VPN Connect: kompletter Preiskatalog =====
          const vpnBwLabel = { "1": "bis 1", "2": "bis 2", "4": "bis 4", "6": "bis 6", "10": "bis 10", "16": "bis 16", "20": "bis 20", "25": "bis 25", "50": "bis 50", "100": "bis 100", "175": "bis 175", "200": "bis 200", "250": "bis 250", "500": "bis 500", "1000": "bis 1.000" };
          const vpnAccessCards = [
            tableCard("vpn_asym_low", "Asymmetrisch – niedrige Bandbreite (bis 100 Mbit/s, €/Monat)", VPN_TERMS),
            tableCard("vpn_asym_high", "Asymmetrisch – hohe Bandbreite (175/250 Mbit/s, €/Monat)", VPN_TERMS),
            tableCard("vpn_asym_oneoff", "Asymmetrisch – Einmalkosten (€)", VPN_TERMS),
            tableCard("vpn_mobile_m", "Mobilfunk – Grundpreis (€/Monat)", VPN_TERMS),
            tableCard("vpn_mobile_oneoff", "Mobilfunk – Einmalkosten (€)", VPN_TERMS),
            tableCard("vpn_ipsec_oneoff", "IPSec – Einmalkosten (€)", VPN_TERMS),
            tableCard("vpn_link_oneoff", "Link Symmetrisch – Einmalkosten (€)", VPN_TERMS),
          ].filter(Boolean);
          const vpnLinkCards = (region) => VPN_LINK_BW.map(bw => tableCard(`vpn_link_${region}_${bw}`, `Link ${region[0].toUpperCase() + region.slice(1)} – ${vpnBwLabel[bw] || bw} Mbit/s (€/Monat)`, VPN_TERMS)).filter(Boolean);
          const vpnLinkMetro = vpnLinkCards("metro"), vpnLinkRegio = vpnLinkCards("regio"), vpnLinkCountry = vpnLinkCards("country");
          const vpnSvcCards = [
            tableCard("vpn_svc_low", "Service-Pauschale – niedrige Bandbreite (€/Monat)", VPN_TERMS),
            tableCard("vpn_svc_high", "Service-Pauschale – hohe Bandbreite/Link (€/Monat)", VPN_TERMS),
            tableCard("vpn_svc_backup", "Service-Pauschale – Mobilfunk-Backup (€/Monat)", VPN_TERMS),
          ].filter(Boolean);
          const vpnDatapackCards = [
            tableCard("vpn_datapack_1g", `Datenpaket – ${VPN_DATAPACK_LABEL["1g"]} (€/Monat)`, VPN_TERMS),
            tableCard("vpn_datapack_5g", `Datenpaket – ${VPN_DATAPACK_LABEL["5g"]} (€/Monat)`, VPN_TERMS),
            tableCard("vpn_datapack_10g", `Datenpaket – ${VPN_DATAPACK_LABEL["10g"]} (€/Monat)`, VPN_TERMS),
            tableCard("vpn_datapack_25g", `Datenpaket – ${VPN_DATAPACK_LABEL["25g"]} (€/Monat)`, VPN_TERMS),
            tableCard("vpn_datapack_flat", `Datenpaket – ${VPN_DATAPACK_LABEL["flat"]} (€/Monat)`, VPN_TERMS),
          ].filter(Boolean);
          const vpnQosCards = [
            tableCard("vpn_qos_low", "Quality of Service – niedrig (bis 100 Mbit/s, €/Monat)", VPN_TERMS),
            tableCard("vpn_qos_mid", "Quality of Service – mittel (bis 500 Mbit/s, €/Monat)", VPN_TERMS),
            tableCard("vpn_qos_high", "Quality of Service – hoch (über 500 Mbit/s, €/Monat)", VPN_TERMS),
            tableCard("vpn_internet_m", "Option Internet – Grundpreis (€/Monat)", VPN_TERMS),
          ].filter(Boolean);
          const vpnFlatCards = [
            priceCard("vpn_internet_o", "Option Internet – Einmalkosten (€)"),
            priceCard("vpn_citrix_o", "Option Citrix Priorisierung (€ einmalig)"),
            priceCard("vpn_netflow_o", "Option NetFlow (€ einmalig)"),
            priceCard("vpn_mpls", "MPLS-Aufschlag Mobilfunk-Datenpaket (€/Monat)"),
            priceCard("vpn_antenne_in6", "Antenne – Indoor Decke bis 6 m (€ einmalig)"),
            priceCard("vpn_antenne_in15", "Antenne – Indoor Decke bis 15 m (€ einmalig)"),
            priceCard("vpn_antenne_outmast", "Antenne – Outdoor Mast (€ einmalig)"),
            priceCard("vpn_antenne_in25", "Antenne – Indoor Decke bis 2,5 m (€ einmalig)"),
            priceCard("vpn_antenne_in5", "Antenne – Indoor Decke bis 5 m (€ einmalig)"),
          ].filter(Boolean);
          const vpnAny = vpnAccessCards.length || vpnLinkMetro.length || vpnLinkRegio.length || vpnLinkCountry.length || vpnSvcCards.length || vpnDatapackCards.length || vpnQosCards.length || vpnFlatCards.length;
          const vpnSection = vpnAny && (
            <div className="mb-2">
              <h3 className="text-sm font-bold mb-2 pt-2 border-t" style={{ color: TEF_BLUE }}>VPN Connect – Preise</h3>
              {vpnAccessCards.length > 0 && <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">{vpnAccessCards}</div>}
              {vpnLinkMetro.length > 0 && <><div className="text-[11px] font-semibold mb-1.5" style={{ color: TEF_GRAY }}>Link Symmetrisch – Metro</div><div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">{vpnLinkMetro}</div></>}
              {vpnLinkRegio.length > 0 && <><div className="text-[11px] font-semibold mb-1.5" style={{ color: TEF_GRAY }}>Link Symmetrisch – Regio</div><div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">{vpnLinkRegio}</div></>}
              {vpnLinkCountry.length > 0 && <><div className="text-[11px] font-semibold mb-1.5" style={{ color: TEF_GRAY }}>Link Symmetrisch – Country</div><div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">{vpnLinkCountry}</div></>}
              {vpnSvcCards.length > 0 && <div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">{vpnSvcCards}</div>}
              {vpnDatapackCards.length > 0 && <div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">{vpnDatapackCards}</div>}
              {vpnQosCards.length > 0 && <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">{vpnQosCards}</div>}
              {vpnFlatCards.length > 0 && <div className="grid grid-cols-2 md:grid-cols-4 gap-3">{vpnFlatCards}</div>}
            </div>
          );

          return <>{sdwanSection}{vpnSection}</>;
        })()}
      </div>
    );
  };

  // ===== Bearbeiten-Reiter: Ende der Optionspreis-Sektionen =====
  return (
    <div style={{ background: TEF_BG }} className="min-h-screen p-4 md:p-8">
      <input ref={fileRef} type="file" accept=".json,application/json" onChange={onLoadFile} style={{ display: "none" }} />
      <input ref={cfgRef} type="file" accept=".json,application/json" onChange={importConfig} style={{ display: "none" }} />
      <div className="max-w-6xl mx-auto">
        <div style={{ background: TEF_BLUE }} className="rounded-t-xl px-6 py-5 flex items-center justify-between gap-3 flex-wrap">
          <div className="flex items-center gap-3">
            <div style={{ background: "#fff" }} className="w-10 h-10 rounded-full flex items-center justify-center"><span style={{ color: TEF_BLUE }} className="font-black text-lg">T</span></div>
            <div><div className="text-white font-bold text-lg leading-tight">Telefónica</div><div style={{ color: TEF_CYAN }} className="text-xs font-medium">B2B Angebots-Kalkulator</div></div>
          </div>
          {tab === "calc" && <div className="flex items-center gap-2">
            <button onClick={() => fileRef.current?.click()} className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/15 text-white text-sm font-semibold hover:bg-white/25"><FolderOpen size={16} /> Öffnen</button>
            <button onClick={saveOffer} className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white text-sm font-semibold" style={{ color: TEF_BLUE }}><Save size={16} /> Speichern</button>
            <button onClick={restoreAutoSave} title="Letzten automatisch gesicherten Stand laden (alle 60 Sek. im Hintergrund gesichert)" className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/15 text-white text-sm font-semibold hover:bg-white/25"><RotateCcw size={16} /> Letzten Stand wiederherstellen</button>
            <button onClick={resetOffer} title="Kunde, alle Bereiche und Produkte entfernen" className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/15 text-white text-sm font-semibold hover:bg-red-500/40"><Trash2 size={16} /> Leeren</button>
            {lastAutoSave && <span className="text-[11px] text-white/70 hidden lg:inline">Auto-Save {lastAutoSave.toLocaleTimeString("de-DE")}</span>}
          </div>}
        </div>

        {/* ===== TAB-LEISTE ===== */}
        <div className="bg-white border-b border-gray-200 flex items-stretch">
          <button onClick={() => setTab("calc")} className="inline-flex items-center gap-2 px-5 py-3 text-sm font-semibold border-b-2" style={{ color: tab === "calc" ? TEF_BLUE : "#6b7280", borderColor: tab === "calc" ? TEF_BLUE : "transparent" }}><Calculator size={16} /> Kalkulator</button>
          <button onClick={() => setTab("edit")} className="inline-flex items-center gap-2 px-5 py-3 text-sm font-semibold border-b-2" style={{ color: tab === "edit" ? TEF_BLUE : "#6b7280", borderColor: tab === "edit" ? TEF_BLUE : "transparent" }}><Settings size={16} /> Produkte &amp; Texte bearbeiten</button>
          <button onClick={() => setTab("import")} className="inline-flex items-center gap-2 px-5 py-3 text-sm font-semibold border-b-2" style={{ color: tab === "import" ? TEF_BLUE : "#6b7280", borderColor: tab === "import" ? TEF_BLUE : "transparent" }}><Upload size={16} /> Standorte importieren</button>
        </div>

        {tab === "edit" ? renderEditor() : tab === "import" ? renderImport() : <>
        <div className="bg-white rounded-b-xl shadow-sm p-5 md:p-7">
          <div className="mb-6 flex flex-wrap gap-4 items-end">
            <div className="grow max-w-md min-w-[240px]">
              <label style={{ color: TEF_BLUE }} className={lbl}>Kunde / Angebotsempfänger</label>
              <input className={ctl} placeholder="z. B. Ramsperger Automobile GmbH" value={customer} onChange={e => setCustomer(e.target.value)} />
            </div>
            <div className="w-56">
              <label style={{ color: TEF_BLUE }} className={lbl}>Laufzeit (alle Produkte setzen)</label>
              <select className={ctl} value="" onChange={e => { const v = Number(e.target.value); if (v) setAllTerms(v); }}>
                <option value="">— wählen —</option>
                {[12, 24, 36, 48, 60].map(t => <option key={t} value={t}>{t} Monate für alle</option>)}
              </select>
            </div>
            <div className="w-72 grow max-w-sm">
              <label style={{ color: TEF_BLUE }} className={lbl}>Suchen / Filtern</label>
              <div className="relative">
                <Search size={15} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" />
                <input className={ctl + " pl-8 pr-8"} placeholder="Standort, Produkt oder Option/Tarif …" value={searchQuery} onChange={e => setSearchQuery(e.target.value)} />
                {searchQuery && <button onClick={() => setSearchQuery("")} className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 text-base leading-none" title="Suche leeren">✕</button>}
              </div>
              {searchMatch && <div className="text-[11px] text-gray-500 mt-1">{searchHitCount} Treffer – nicht passende Bereiche sind ausgeblendet</div>}
            </div>
            <label className="flex items-center gap-2 text-sm font-medium mb-2 cursor-pointer select-none" style={{ color: TEF_BLUE }} title="Wandelt das Angebot in ein Standortangebot um – ortsgebundene Bereiche kommen unter eine Standort-Ebene, MDM/Office 365 u. a. bleiben unverändert. Erneutes Deaktivieren macht die Umwandlung rückgängig.">
              <input type="checkbox" checked={isStandortOffer} onChange={toggleStandortOffer} />
              Standortangebot
            </label>
          </div>
          {emptyStandortGids.length > 0 && <div className="mb-4 flex items-center justify-between gap-3 rounded-lg border-2 px-3 py-2 text-sm" style={{ borderColor: TEF_RED, background: "#fdecea", color: TEF_RED }}>
            <span>{emptyStandortGids.length} Standort{emptyStandortGids.length > 1 ? "e" : ""} ohne Namen – bitte eintragen, damit das Angebot eindeutig ist.</span>
            <button onClick={focusFirstEmptyStandort} className="shrink-0 px-2.5 py-1 rounded font-semibold text-white" style={{ background: TEF_RED }}>hinspringen</button>
          </div>}
          <div className="space-y-4">
            {searchMatch && groups.every(g => !groupVisible(g.id)) && (
              <div className="text-center text-sm text-gray-400 py-6 rounded-xl border-2 border-dashed border-gray-200">Keine Treffer für „{searchQuery}" – Standort, Produkt oder Option/Tarif anders eingeben oder Suche leeren.</div>
            )}
            {groups.map((g, gi) => {
              if (!groupVisible(g.id)) return null;
              const kinds = new Set();
              for (const r of g.rows) { const pp = getProduct(g.area, r.productId); if (pp?.mdmKind) kinds.add(pp.mdmKind); }
              const mdmMix = kinds.has("managed") && kinds.has("unmanaged");
              return (
                <div key={g.id} className="rounded-xl border-2 border-gray-200 overflow-hidden">
                  <div className="flex flex-col md:flex-row">
                    <div className="md:w-44 shrink-0 p-3 border-b md:border-b-0 md:border-r border-gray-200" style={{ background: "#EEF2FF" }}>
                      <div className="flex items-center justify-between mb-2">
                        <span className="text-[11px] font-bold inline-flex items-center gap-1" style={{ color: TEF_BLUE }}>
                          Bereich {gi + 1}
                          {(() => { const n = plausibility.filter(x => x.gid === g.id).length; const hasWarn = plausibility.some(x => x.gid === g.id && x.level === "warn"); return n > 0 ? <span title={`${n} Hinweis(e) – siehe Plausibilitäts-Prüfung im Angebot`} className="inline-flex items-center justify-center rounded-full text-white text-[10px] font-bold w-4 h-4" style={{ background: hasWarn ? TEF_RED : TEF_BLUE }}>{n}</span> : null; })()}
                        </span>
                        <div className="flex items-center gap-0.5">
                          <button onClick={() => moveGroupDir(g.id, -1)} disabled={gi <= 0} className="p-1 rounded hover:bg-white disabled:opacity-25" title="Bereich nach oben"><ChevronUp size={16} color={TEF_BLUE} /></button>
                          <button onClick={() => moveGroupDir(g.id, 1)} disabled={gi >= groups.length - 1} className="p-1 rounded hover:bg-white disabled:opacity-25" title="Bereich nach unten"><ChevronDown size={16} color={TEF_BLUE} /></button>
                        </div>
                      </div>
                      <label className={lbl} style={{ color: TEF_BLUE }}>Produktbereich</label>
                      <select className={ctl} value={g.area} onChange={e => setArea(g.id, e.target.value)}>
                        <option value="">— wählen —</option>
                        {AREAS.map(a => <option key={a.key} value={a.key}>{a.label}</option>)}
                      </select>
                      <div className="mt-3 flex items-center gap-2">
                        <button onClick={() => duplicateGroup(g.id)} className="inline-flex items-center gap-1.5 text-xs font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Bereich mit allen Produkten duplizieren"><Copy size={14} /> duplizieren</button>
                        <button onClick={() => removeGroup(g.id)} className="inline-flex items-center gap-1.5 text-xs font-medium px-2 py-1 rounded hover:bg-red-50" style={{ color: TEF_RED }}><Trash2 size={14} /> entfernen</button>
                      </div>
                      <div className="mt-2 flex items-center gap-2 flex-wrap">
                        <button onClick={() => saveBaseline(g)} className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }} title="Aktuellen Stand als Vergleichsbasis sichern (z. B. vor Nachverhandlung)"><Bookmark size={12} /> Status sichern</button>
                        {baselines[g.id] && <>
                          <button onClick={() => toggleDiff(g.id)} className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded hover:bg-blue-50" style={{ color: TEF_BLUE }}><GitCompare size={12} /> {diffOpen[g.id] ? "Vergleich ausblenden" : "Vergleich anzeigen"}</button>
                          <button onClick={() => clearBaseline(g.id)} className="text-[11px] text-gray-400 hover:text-gray-600" title="Gesicherten Vergleichsstand verwerfen">Baseline löschen</button>
                        </>}
                      </div>
                    </div>
                    <div className="flex-1 min-w-0 p-4 space-y-3 bg-gray-50">
                      {mdmMix && <div className="flex items-start gap-2 text-sm rounded p-2.5 border" style={{ color: TEF_RED, borderColor: TEF_RED, background: "#fdecea" }}><AlertTriangle size={16} className="mt-0.5 shrink-0" />Kein Mix aus Managed und Unmanaged möglich – bitte nur eine Variante wählen.</div>}
                      {diffOpen[g.id] && baselines[g.id] && (() => {
                        const d = buildGroupDiff(baselines[g.id], g);
                        if (!d) return null;
                        const nothing = (d.kind === "rows" && !d.added.length && !d.removed.length && !d.changed.length) || (d.kind === "standort" && !d.subDiffs.length) || (d.kind === "vpn" && !d.changes.length);
                        return (
                          <div className="rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#F7FAFF" }}>
                            <div className="text-xs font-bold mb-2 flex items-center gap-1.5" style={{ color: TEF_BLUE }}><GitCompare size={13} /> Vergleich zum gesicherten Stand</div>
                            {nothing && <div className="text-xs text-gray-400">Keine Änderungen seit der letzten Sicherung.</div>}
                            {d.kind === "rows" && <div className="space-y-1">
                              {d.added.map((a, i) => <div key={"a" + i} className="text-xs" style={{ color: "#15803d" }}>+ Hinzugefügt: {a.name}</div>)}
                              {d.removed.map((a, i) => <div key={"r" + i} className="text-xs" style={{ color: TEF_RED }}>− Entfernt: {a.name}</div>)}
                              {d.changed.map((c, i) => <div key={"c" + i} className="text-xs" style={{ color: TEF_GRAY }}>✎ {c.name}{c.changes.length ? ": " + c.changes.join(" · ") : ""}</div>)}
                            </div>}
                            {d.kind === "standort" && <div className="space-y-1.5">
                              {d.subDiffs.map((s, i) => (
                                <div key={i} className="text-xs">
                                  <span className="font-semibold" style={{ color: TEF_BLUE }}>{s.label}:</span>{" "}
                                  {s.addedSub ? <span style={{ color: "#15803d" }}>neuer Unterbereich</span> : s.removedSub ? <span style={{ color: TEF_RED }}>Unterbereich entfernt</span> : <>
                                    {(s.added || []).map((a, j) => <span key={"a" + j} className="mr-2" style={{ color: "#15803d" }}>+ {a.name}</span>)}
                                    {(s.removed || []).map((a, j) => <span key={"r" + j} className="mr-2" style={{ color: TEF_RED }}>− {a.name}</span>)}
                                    {(s.changed || []).map((c, j) => <span key={"c" + j} className="mr-2" style={{ color: TEF_GRAY }}>✎ {c.name}{c.changes.length ? " (" + c.changes.join(" · ") + ")" : ""}</span>)}
                                  </>}
                                </div>
                              ))}
                            </div>}
                            {d.kind === "vpn" && <div className="space-y-1">{d.changes.map((c, i) => <div key={i} className="text-xs" style={{ color: TEF_GRAY }}>✎ {c}</div>)}</div>}
                          </div>
                        );
                      })()}
                      {g.area === "office365" && <div className="rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
                        <label className="flex items-center gap-2 text-sm font-medium" style={{ color: TEF_GRAY }}><input type="checkbox" checked={!!g.managed} onChange={e => patchGroup(g.id, { managed: e.target.checked })} />Managed (Verwaltung durch Telefónica)</label>
                        <div className="text-[11px] text-gray-500 mt-1">Steuert Preise & max. Rabatt aller Lizenzen. Ohne Managed-Preis wird License-Only-Preis genutzt.</div>
                      </div>}
                      {(g.area === "sdwan" || g.area === "sdwanmeraki") && <div className="rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
                        <label className={lbl} style={{ color: TEF_BLUE }}>Standard-Laufzeit (Vorauswahl neuer Produkte)</label>
                        <select className={ctl} value={g.sdwanTerm || 60} onChange={e => patchGroup(g.id, { sdwanTerm: Number(e.target.value) })}>{SDWAN_TERMS.map(t => <option key={t} value={t}>{t} Monate</option>)}</select>
                        <div className="text-[11px] text-gray-500 mt-1">Neu hinzugefügte Produkte starten mit dieser Laufzeit. Je Zeile weiterhin einzeln änderbar.</div>
                      </div>}
                      {g.area === "individuell" && <div className="rounded-lg border p-3" style={{ borderColor: TEF_BLUE_2, background: "#EEF2FF" }}>
                        <label className={lbl} style={{ color: TEF_BLUE }}>Produktbereich (frei)</label>
                        <input className={ctl} placeholder="z. B. Professional Services" value={g.customArea || ""} onChange={e => patchGroup(g.id, { customArea: e.target.value })} />
                        <div className="text-[11px] text-gray-500 mt-1">Erscheint im Angebot als Überschrift dieses Bereichs.</div>
                      </div>}
                      {g.area === "standort" && <div className="rounded-lg border p-3" style={{ borderColor: !(g.standortName || "").trim() ? TEF_RED : TEF_BLUE_2, background: !(g.standortName || "").trim() ? "#fdecea" : "#EEF2FF" }}>
                        <label className={lbl} style={{ color: !(g.standortName || "").trim() ? TEF_RED : TEF_BLUE }}>Standortname {!(g.standortName || "").trim() && <span className="font-normal">– bitte eintragen</span>}</label>
                        <input ref={el => { if (el) standortNameRefs.current[g.id] = el; }} className={ctl} style={!(g.standortName || "").trim() ? { borderColor: TEF_RED, borderWidth: 2 } : undefined} placeholder="z. B. Kirchheim" value={g.standortName || ""} onChange={e => patchGroup(g.id, { standortName: e.target.value })} />
                        <div className="text-[11px] text-gray-500 mt-1">Erscheint im Angebot als gemeinsame Überschrift über allen Unterbereichen.</div>
                      </div>}
                      {g.area === "vpnconnect" ? renderVpnEditor(g) : g.area === "standort" ? <div className="space-y-3">
                        {(g.subGroups || []).filter(sg => groupVisible(sg.id)).map((sg, sgi) => renderSubGroupCard(g, sg, sgi, (g.subGroups || []).length))}
                        <button onClick={() => addSubGroup(g.id)} style={{ color: TEF_BLUE_2, borderColor: TEF_BLUE_2 }} className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border-2 border-dashed text-sm font-semibold hover:bg-blue-50"><Plus size={15} /> Unterbereich hinzufügen</button>
                      </div> : !g.area ? <p className="text-sm text-gray-400">Bitte links zuerst einen Produktbereich wählen.</p> : <>
                        {g.rows.map(r => renderRow(g, r))}
                        <button onClick={() => addRow(g.id)} style={{ color: TEF_BLUE_2, borderColor: TEF_BLUE_2 }} className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border-2 border-dashed text-sm font-semibold hover:bg-blue-50"><Plus size={15} /> Produkt hinzufügen</button>
                      </>}
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
          <button onClick={addGroup} style={{ background: TEF_BLUE }} className="mt-4 inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-semibold hover:opacity-90"><Plus size={16} /> Produktbereich hinzufügen</button>
        </div>

        {/* ===== VORSCHAU ===== */}
        <div className="bg-white rounded-xl shadow-sm p-5 md:p-7 mt-6">
          <div className="flex items-center justify-between mb-4 flex-wrap gap-2">
            <h2 style={{ color: TEF_BLUE }} className="font-bold text-lg">Angebot (so wird es eingefügt)</h2>
            <div className="flex items-center gap-2">
              <button onClick={saveOfferExcel} disabled={offerLines.length === 0} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg border-2 text-sm font-semibold hover:bg-blue-50 disabled:opacity-40" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }} title="Aktuelles Angebot als Excel-Liste exportieren (gleiche Struktur wie die Import-Vorlage, ohne Dropdowns)"><Download size={16} /> Excel Speichern</button>
              <button onClick={saveOffer} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg border-2 text-sm font-semibold hover:bg-blue-50" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }}><Save size={16} /> Speichern</button>
              <button onClick={copyOffer} disabled={offerLines.length === 0} style={{ background: copied ? TEF_CYAN : TEF_BLUE }} className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-semibold disabled:opacity-40">
                {copied ? <Check size={16} /> : <Copy size={16} />}{copied ? "Kopiert!" : "Für PowerPoint kopieren"}
              </button>
            </div>
          </div>
          {plausibility.length > 0 && <div className="mb-4 rounded-lg border p-3" style={{ borderColor: plausibility.some(x => x.level === "warn") ? TEF_RED : TEF_BLUE_2, background: plausibility.some(x => x.level === "warn") ? "#FFF5F5" : "#EEF2FF" }}>
            <div className="flex items-center gap-2 mb-2 text-sm font-bold" style={{ color: plausibility.some(x => x.level === "warn") ? TEF_RED : TEF_BLUE }}><AlertTriangle size={16} /> Plausibilitäts-Prüfung ({plausibility.length})</div>
            <ul className="space-y-1">
              {plausibility.map((x, i) => <li key={i} className="flex items-start gap-2 text-xs" style={{ color: TEF_GRAY }}>
                <span className="mt-0.5 inline-block w-2 h-2 rounded-full shrink-0" style={{ background: x.level === "warn" ? TEF_RED : TEF_BLUE }} />
                <span>{x.msg}</span>
              </li>)}
            </ul>
            <div className="text-[11px] text-gray-400 mt-2">Hinweise erscheinen nicht im Export.</div>
          </div>}
          {offerLines.length === 0 ? <p className="text-sm text-gray-400">Noch kein Produkt ausgewählt.</p> : (
            <div className="overflow-x-auto">
              <p style={{ color: TEF_BLUE }} className="font-bold mb-1">Angebot{customer ? " – " + customer : ""}</p>
              <p className="text-xs text-gray-500 mb-3">Stand: {today} · ▲▼ zum Sortieren · Mülleimer zum Löschen · Checkbox rechts = für PowerPoint-Kopie berücksichtigen (nicht im Export sichtbar)</p>
              <style>{`.tef-offer-table th, .tef-offer-table td { border-color: #d1d5db !important; }`}</style>
              <table className="tef-offer-table w-full border-collapse text-sm">
                <thead>
                  <tr>
                    <th style={{ background: TEF_BLUE, width: 36 }} className="border border-gray-300"></th>
                    <th style={{ background: TEF_BLUE }} className="text-white font-semibold px-3 py-2 border border-gray-300 text-left">Produkt</th>
                    <th style={{ background: TEF_BLUE }} className="text-white font-semibold px-3 py-2 border border-gray-300 text-left">Beschreibung</th>
                    <th style={{ background: TEF_BLUE }} className="text-white font-semibold px-3 py-2 border border-gray-300 text-right">Monatlich</th>
                    <th style={{ background: TEF_BLUE }} className="text-white font-semibold px-3 py-2 border border-gray-300 text-right">Einmalig</th>
                    <th style={{ background: TEF_BLUE, width: 40 }} className="border border-gray-300"></th>
                    <th style={{ background: TEF_BLUE, width: 44 }} className="border border-gray-300 text-center" title="Für PowerPoint-Kopie auswählen (nicht im Export sichtbar)">
                      <input type="checkbox" checked={allLinesChecked} onChange={toggleAllLines} title="Alle Zeilen für die Kopie an-/abwählen" />
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {(() => {
                    const out = []; let currGid = null;
                    const visibleLines = offerLines.filter(l => (l.type === "cluster" || l.type === "subcluster") ? groupVisible(l.gid) : lineVisibleInSearch(l));
                    for (let i = 0; i < visibleLines.length; i++) {
                      const l = visibleLines[i], next = visibleLines[i + 1];
                      if (l.type === "cluster") {
                        currGid = l.gid;
                        const gIdx = groups.findIndex(x => x.id === l.gid);
                        const gArea = groups.find(x => x.id === l.gid)?.area;
                        const isStandort = gArea === "standort";
                        const isVpn = gArea === "vpnconnect";
                        const headerLines = headerLineGroups[l.gid] || [];
                        const headerChecked = headerLines.length > 0 && headerLines.every(x => isLineIncluded(x));
                        const toggleHeaderLines = () => setExcludedLines(prev => { const n = new Set(prev); for (const x of headerLines) { const k = lineKey(x); if (headerChecked) n.add(k); else n.delete(k); } return n; });
                        out.push(
                          <tr key={`cl-${l.gid}`} style={{ background: "#D7E0FF" }}>
                            <td colSpan={6} className="border border-gray-300 font-bold" style={{ color: TEF_BLUE }}>
                              <div className="flex items-center justify-between px-3 py-1.5">
                                <div className="flex items-center gap-1.5">
                                  <span>{l.name}</span>
                                  <button onClick={() => moveGroupDir(l.gid, -1)} disabled={gIdx <= 0} className="p-0.5 rounded hover:bg-white/60 disabled:opacity-25" title="Bereich nach oben"><ChevronUp size={15} color={TEF_BLUE} /></button>
                                  <button onClick={() => moveGroupDir(l.gid, 1)} disabled={gIdx >= groups.length - 1} className="p-0.5 rounded hover:bg-white/60 disabled:opacity-25" title="Bereich nach unten"><ChevronDown size={15} color={TEF_BLUE} /></button>
                                  <button onClick={() => duplicateGroup(l.gid)} className="p-0.5 rounded hover:bg-white/60" title="Bereich duplizieren"><Copy size={15} color={TEF_BLUE} /></button>
                                  <button onClick={() => removeGroup(l.gid)} className="p-0.5 rounded hover:bg-red-50" title="Bereich entfernen"><Trash2 size={15} color={TEF_RED} /></button>
                                </div>
                                {isStandort && <div className="flex items-center gap-2">
                                  {visibleSums.has(l.gid) && standortSums[l.gid] && <span className="text-xs font-bold" style={{ color: TEF_BLUE }}>Standort gesamt: {eur(standortSums[l.gid].sumM)}/Mon. · {eur(standortSums[l.gid].sumO)} einmalig</span>}
                                  <button onClick={() => toggleSum(l.gid)} className="text-xs px-2 py-0.5 rounded border font-medium" style={{ color: TEF_BLUE, borderColor: TEF_BLUE, background: "white" }}>
                                    {visibleSums.has(l.gid) ? "Standortkosten ausblenden" : "Standortkosten einblenden"}
                                  </button>
                                </div>}
                                {!isStandort && <div className="flex items-center gap-1.5">
                                  {!isVpn && <button onClick={() => toggleAreaDesc(l.gid)} className="text-xs px-2 py-0.5 rounded border font-medium" style={{ color: TEF_BLUE, borderColor: TEF_BLUE, background: "white" }}>
                                    {(groups.find(x => x.id === l.gid)?.showDesc) ? "Produktbeschreibung ausblenden" : "Produktbeschreibung einblenden"}
                                  </button>}
                                  <button onClick={() => toggleSum(l.gid)} className="text-xs px-2 py-0.5 rounded border font-medium" style={{ color: TEF_BLUE, borderColor: TEF_BLUE, background: "white" }}>
                                    {visibleSums.has(l.gid) ? "Produktsummen ausblenden" : "Produktsummen einblenden"}
                                  </button>
                                </div>}
                              </div>
                              {l.desc?.length > 0 && <div className="px-3 pb-1.5 -mt-0.5 text-xs font-normal text-gray-600">{l.desc.map((d, di) => <div key={di}>{d}</div>)}</div>}
                            </td>
                            <td className="border border-gray-300 text-center align-middle" style={{ width: 44 }}>
                              {headerLines.length > 0 && <input type="checkbox" checked={headerChecked} onChange={toggleHeaderLines} title="Alle Zeilen dieses Bereichs (inkl. Unterbereiche) für die Kopie an-/abwählen" />}
                            </td>
                          </tr>
                        );
                      } else if (l.type === "subcluster") {
                        currGid = l.gid;
                        const parent = groups.find(x => x.id === l.pgid);
                        const siblings = parent?.subGroups || [];
                        const sIdx = siblings.findIndex(sg => sg.id === l.gid);
                        const sg = siblings[sIdx];
                        const headerLines = headerLineGroups[l.gid] || [];
                        const headerChecked = headerLines.length > 0 && headerLines.every(x => isLineIncluded(x));
                        const toggleHeaderLines = () => setExcludedLines(prev => { const n = new Set(prev); for (const x of headerLines) { const k = lineKey(x); if (headerChecked) n.add(k); else n.delete(k); } return n; });
                        out.push(
                          <tr key={`scl-${l.gid}`} style={{ background: "#EAF0FF" }}>
                            <td colSpan={6} className="border border-gray-300 font-semibold" style={{ color: TEF_BLUE }}>
                              <div className="flex items-center justify-between pl-8 pr-3 py-1.5">
                                <div className="flex items-center gap-1.5">
                                  <span>{l.name}</span>
                                  <button onClick={() => moveSubGroupDir(l.pgid, l.gid, -1)} disabled={sIdx <= 0} className="p-0.5 rounded hover:bg-white/60 disabled:opacity-25" title="Unterbereich nach oben"><ChevronUp size={14} color={TEF_BLUE} /></button>
                                  <button onClick={() => moveSubGroupDir(l.pgid, l.gid, 1)} disabled={sIdx >= siblings.length - 1} className="p-0.5 rounded hover:bg-white/60 disabled:opacity-25" title="Unterbereich nach unten"><ChevronDown size={14} color={TEF_BLUE} /></button>
                                  <button onClick={() => duplicateSubGroup(l.pgid, l.gid)} className="p-0.5 rounded hover:bg-white/60" title="Unterbereich duplizieren"><Copy size={14} color={TEF_BLUE} /></button>
                                  <button onClick={() => removeSubGroup(l.pgid, l.gid)} className="p-0.5 rounded hover:bg-red-50" title="Unterbereich entfernen"><Trash2 size={14} color={TEF_RED} /></button>
                                </div>
                                <div className="flex items-center gap-1.5">
                                  <button onClick={() => toggleAreaDesc(l.gid)} className="text-xs px-2 py-0.5 rounded border font-medium" style={{ color: TEF_BLUE, borderColor: TEF_BLUE, background: "white" }}>
                                    {sg?.showDesc ? "Produktbeschreibung ausblenden" : "Produktbeschreibung einblenden"}
                                  </button>
                                  <button onClick={() => toggleSum(l.gid)} className="text-xs px-2 py-0.5 rounded border font-medium" style={{ color: TEF_BLUE, borderColor: TEF_BLUE, background: "white" }}>
                                    {visibleSums.has(l.gid) ? "Produktsummen ausblenden" : "Produktsummen einblenden"}
                                  </button>
                                </div>
                              </div>
                              {l.desc?.length > 0 && <div className="pl-8 pr-3 pb-1.5 -mt-0.5 text-xs font-normal text-gray-600">{l.desc.map((d, di) => <div key={di}>{d}</div>)}</div>}
                            </td>
                            <td className="border border-gray-300 text-center align-middle" style={{ width: 44 }}>
                              {headerLines.length > 0 && <input type="checkbox" checked={headerChecked} onChange={toggleHeaderLines} title="Alle Zeilen dieses Unterbereichs für die Kopie an-/abwählen" />}
                            </td>
                          </tr>
                        );
                      } else {
                        const isProd = l.type === "product";
                        const fr = isProd ? findRow(l.gid, l.rowId) : null;
                        out.push(
                          <tr key={`${l.type}-${i}`} className={isProd ? "" : "bg-gray-50 italic text-gray-500"}>
                            <td className="border border-gray-300 text-center align-middle" style={{ width: 36 }}>
                              {isProd && fr?.r && <SortArrows g={fr.g} r={fr.r} size={14} />}
                            </td>
                            <td className="px-3 py-2 border border-gray-300 align-top">{l.name}{l.meta?.length > 0 && <div className="text-xs text-gray-400">{l.meta.join(" · ")}</div>}</td>
                            <td className="px-3 py-2 border border-gray-300 align-top text-xs text-gray-600">{(l.desc || []).map((d, di) => <div key={di}>{d}</div>)}</td>
                            <td className="px-3 py-2 border border-gray-300 text-right align-top">{eur(l.monthly)}{l.abloeseNote && <div className="text-xs" style={{ color: TEF_RED }}>{l.abloeseNote}</div>}{l.rabattNote && <div className="text-xs" style={{ color: TEF_RED }}>{l.rabattNote}</div>}</td>
                            <td className="px-3 py-2 border border-gray-300 text-right align-top">{eur(l.oneoff)}</td>
                            <td className="border border-gray-300 text-center align-middle" style={{ width: 40 }}>
                              {isProd && l.rowId != null && <button onClick={() => duplicateRow(l.gid, l.rowId)} className="p-1 rounded hover:bg-blue-50" title="Zeile duplizieren"><Copy size={16} color={TEF_BLUE} /></button>}
                              {isProd && l.rowId != null && <button onClick={() => removeRow(l.gid, l.rowId)} className="p-1 rounded hover:bg-red-50" title="Zeile löschen"><Trash2 size={16} color={TEF_RED} /></button>}
                            </td>
                            <td className="border border-gray-300 text-center align-middle" style={{ width: 44 }}>
                              <input type="checkbox" checked={isLineIncluded(l)} onChange={() => toggleLineIncluded(l)} title="Für PowerPoint-Kopie berücksichtigen" />
                            </td>
                          </tr>
                        );
                        if (currGid && (visibleSums.has(currGid) || showAllSums) && (!next || next.type === "cluster" || next.type === "subcluster")) {
                          const s = clusterSumsData[currGid];
                          if (s) out.push(
                            <tr key={`csum-${currGid}`} style={{ background: "#E8EDFF" }}>
                              <td className="border border-gray-300" style={{ background: "#E8EDFF" }}></td>
                              <td colSpan={2} className="px-3 py-1.5 border border-gray-300 font-semibold text-sm" style={{ color: TEF_BLUE }}>Summe {s.name}</td>
                              <td className="px-3 py-1.5 border border-gray-300 text-right font-semibold text-sm" style={{ color: TEF_BLUE }}>{eur(s.sumM)}</td>
                              <td className="px-3 py-1.5 border border-gray-300 text-right font-semibold text-sm" style={{ color: TEF_BLUE }}>{eur(s.sumO)}</td>
                              <td className="border border-gray-300" style={{ background: "#E8EDFF" }}></td>
                              <td className="border border-gray-300" style={{ background: "#E8EDFF" }}></td>
                            </tr>
                          );
                        }
                      }
                    }
                    return out;
                  })()}

                  <tr style={{ background: "#E6ECFF" }}>
                    <td className="border border-gray-300" style={{ background: "#E6ECFF" }}></td>
                    <td colSpan={2} style={{ color: TEF_BLUE }} className="px-3 py-2 border border-gray-300 font-bold">Gesamtsumme</td>
                    <td style={{ color: TEF_BLUE }} className="px-3 py-2 border border-gray-300 text-right font-bold">{eur(sumM)}</td>
                    <td style={{ color: TEF_BLUE }} className="px-3 py-2 border border-gray-300 text-right font-bold">{eur(sumO)}</td>
                    <td className="border border-gray-300" style={{ background: "#E6ECFF" }}></td>
                    <td className="border border-gray-300" style={{ background: "#E6ECFF" }}></td>
                  </tr>

                  <tr>
                    <td colSpan={7} className="px-2 py-1.5 border border-gray-300 text-right bg-gray-50">
                      <div className="flex flex-wrap gap-2 justify-end">
                        <button onClick={() => setShowAllSums(v => !v)} className="text-xs font-semibold px-3 py-1 rounded border" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }}>{showAllSums ? "Bereichssummen ausblenden" : "Zusammenfassung je Bereich"}</button>
                        <button onClick={() => setShowTCO(v => !v)} className="text-xs font-semibold px-3 py-1 rounded border" style={{ color: TEF_BLUE, borderColor: TEF_BLUE }}>{showTCO ? "Gesamtkosten ausblenden" : "Gesamtkosten (Laufzeit)"}</button>
                        <button onClick={() => setShowSavings(v => !v)} className="text-xs font-semibold px-3 py-1 rounded border" style={{ color: showSavings ? TEF_RED : TEF_BLUE, borderColor: showSavings ? TEF_RED : TEF_BLUE }}>{showSavings ? "Ersparnis ausblenden" : "Ersparnis einblenden"}</button>
                      </div>
                    </td>
                  </tr>

                  {showSavings && <tr style={{ background: "#FFF5F5" }}>
                    <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                    <td colSpan={2} className="px-3 py-2 border border-gray-300 text-sm" style={{ color: TEF_RED }}>Bisherige IST-Kosten des Kunden (€/Monat, optional – sonst Vergleich ggü. Listenpreis)</td>
                    <td colSpan={2} className="px-3 py-1.5 border border-gray-300 text-right">
                      <input type="number" step="0.01" min="0" className={ctl + " text-right max-w-[140px] inline-block"} placeholder="z. B. 450" value={istKosten} onChange={e => setIstKosten(e.target.value)} />
                    </td>
                    <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                    <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                  </tr>}

                  {showTCO && primaryTerm > 0 && <tr style={{ background: "#EEF2FF" }}>
                    <td className="border border-gray-300" style={{ background: "#EEF2FF" }}></td>
                    <td colSpan={2} className="px-3 py-2 border border-gray-300 font-semibold text-sm" style={{ color: TEF_BLUE }}>Gesamtkosten über die Laufzeit (monatlich × Laufzeit + einmalig)</td>
                    <td colSpan={2} className="px-3 py-2 border border-gray-300 text-right font-semibold text-sm" style={{ color: TEF_BLUE }}>{eur(sumM * primaryTerm + sumO)}</td>
                    <td className="border border-gray-300" style={{ background: "#EEF2FF" }}></td>
                    <td className="border border-gray-300" style={{ background: "#EEF2FF" }}></td>
                  </tr>}

                  {showSavings && effSavM > 0 && <>
                    <tr style={{ background: "#FFF5F5" }}>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                      <td colSpan={2} className="px-3 py-2 border border-gray-300 font-semibold text-sm" style={{ color: TEF_RED }}>Ihre monatliche Ersparnis {vsIst ? "gegenüber Ihren bisherigen Kosten" : "gegenüber Listenpreis"}</td>
                      <td colSpan={2} className="px-3 py-2 border border-gray-300 text-right font-semibold text-sm" style={{ color: TEF_RED }}>{eur(effSavM)}</td>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                    </tr>
                    {primaryTerm > 0 && <tr style={{ background: "#FFF5F5" }}>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                      <td colSpan={2} className="px-3 py-2 border border-gray-300 font-semibold text-sm" style={{ color: TEF_RED }}>Ihre Gesamtersparnis über die Laufzeit</td>
                      <td colSpan={2} className="px-3 py-2 border border-gray-300 text-right font-semibold text-sm" style={{ color: TEF_RED }}>{eur(totalSavings)}</td>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                      <td className="border border-gray-300" style={{ background: "#FFF5F5" }}></td>
                    </tr>}
                  </>}
                </tbody>
              </table>
              <p className="text-[11px] text-gray-400 mt-2">{FOOTNOTE}</p>
            </div>
          )}
        </div>
        <p className="text-center text-xs text-gray-400 mt-4">Sortieren & Löschen wirken auch oben im Kalkulator · Checkbox-Spalte & Buttons erscheinen nicht im Export · Speichern als .json + .jpg enthält immer alle Zeilen, unabhängig von der Checkbox-Auswahl</p>
        </>}
      </div>
    </div>
  );
}


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