-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- MODUL: UI - IMMERSIVE CHRONICLE OF THE FALLEN
-- Inspiriert von Total RP 3, Immersion, Storyline
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

local addonName, addon = ...
local GDL = _G["GuildDeathLog"]
local UI = {}

-- Bereinigt killerName: filtert "NPC #12345" Fallback-Strings → nil
function UI.CleanKillerName(name)
    if not name or name == "" or name == "Unknown" or name == "Unbekannt" then return nil end
    if name:match("^NPC #%d+$") then return nil end  -- nur numerische ID, kein Name
    return name
end

local CLASS_COLORS = {
    [1]={0.78,0.61,0.43}, [2]={0.96,0.55,0.73}, [3]={0.67,0.83,0.45},
    [4]={1,0.96,0.41}, [5]={1,1,1}, [7]={0,0.44,0.87},
    [8]={0.41,0.8,0.94}, [9]={0.58,0.51,0.79}, [11]={1,0.49,0.04}
}
local CLASS_ICONS = {
    [1]="Interface\\Icons\\ClassIcon_Warrior",   [2]="Interface\\Icons\\ClassIcon_Paladin",
    [3]="Interface\\Icons\\ClassIcon_Hunter",    [4]="Interface\\Icons\\ClassIcon_Rogue",
    [5]="Interface\\Icons\\ClassIcon_Priest",    [7]="Interface\\Icons\\ClassIcon_Shaman",
    [8]="Interface\\Icons\\ClassIcon_Mage",      [9]="Interface\\Icons\\ClassIcon_Warlock",
    [11]="Interface\\Icons\\ClassIcon_Druid",
    -- String-Keys fuer deathData.class (englische Klassen-IDs)
    WARRIOR="Interface\\Icons\\ClassIcon_Warrior",   PALADIN="Interface\\Icons\\ClassIcon_Paladin",
    HUNTER="Interface\\Icons\\ClassIcon_Hunter",     ROGUE="Interface\\Icons\\ClassIcon_Rogue",
    PRIEST="Interface\\Icons\\ClassIcon_Priest",     SHAMAN="Interface\\Icons\\ClassIcon_Shaman",
    MAGE="Interface\\Icons\\ClassIcon_Mage",         WARLOCK="Interface\\Icons\\ClassIcon_Warlock",
    DRUID="Interface\\Icons\\ClassIcon_Druid",       DEATHKNIGHT="Interface\\Icons\\ClassIcon_DeathKnight",
    -- Deutsche Namen (Fallback)
    Krieger="Interface\\Icons\\ClassIcon_Warrior",   Paladin="Interface\\Icons\\ClassIcon_Paladin",
    Jaeger="Interface\\Icons\\ClassIcon_Hunter",     Schurke="Interface\\Icons\\ClassIcon_Rogue",
    Priester="Interface\\Icons\\ClassIcon_Priest",   Schamane="Interface\\Icons\\ClassIcon_Shaman",
    Magier="Interface\\Icons\\ClassIcon_Mage",       Hexenmeister="Interface\\Icons\\ClassIcon_Warlock",
    Druide="Interface\\Icons\\ClassIcon_Druid",      Todesritter="Interface\\Icons\\ClassIcon_DeathKnight",
}

-- Expose for sub-files (UIChapters, UIOverlay, UIToasts, UIWindows)
UI.CLASS_COLORS = CLASS_COLORS
UI.CLASS_ICONS  = CLASS_ICONS

-- ══════════════════════════════════════════════════════════════
-- TINTEN-PALETTE (Pergament-Lesbarkeit)
-- Eine Tinte, vier Abstufungen + Gold-Akzent. Vorher waren ueber
-- 30 verschiedene Brauntoene im Buch verteilt — das hier ist die
-- einzige Quelle. Hex-Strings fuer |cff..., RGB fuer SetTextColor.
-- ══════════════════════════════════════════════════════════════
UI.INK = {
    DARK   = "2A1606",  -- Ueberschriften, Namen, Zahlenwerte
    BODY   = "3D2410",  -- Fliesstext, Kapitel-Titel
    SOFT   = "5E3E1C",  -- Sekundaertext, Labels, Beschreibungen
    FADED  = "7A6448",  -- Meta, Datumsangaben, Inaktives
    GOLD   = "8A5E14",  -- Akzente, Nummern, Hervorhebungen
    GOLDHI = "A8761E",  -- Hover-Zustand
    RED    = "7A2C14",  -- Tod / Negativ (gedaempft, buchtauglich)
}
UI.INK_RGB = {
    DARK   = {0.165, 0.086, 0.024},
    BODY   = {0.239, 0.141, 0.063},
    SOFT   = {0.369, 0.243, 0.110},
    FADED  = {0.478, 0.392, 0.282},
    GOLD   = {0.541, 0.369, 0.078},
    GOLDHI = {0.659, 0.463, 0.118},
    RED    = {0.478, 0.173, 0.078},
}
UI.FONT_HEADER = "Fonts\\MORPHEUS.TTF"
UI.FONT_BODY   = "Fonts\\FRIZQT__.TTF"

UI._overlayQueue = {}
UI._isShowingOverlay = false
UI._achToastQueue  = {}
UI._achToastActive = false

local GDL_scaleJobs = {}
local GDL_scaleTicker = CreateFrame("Frame")
GDL_scaleTicker:Hide()
GDL_scaleTicker:SetScript("OnUpdate", function(_, dt)
    local any = false
    for i = #GDL_scaleJobs, 1, -1 do
        local j = GDL_scaleJobs[i]
        j.t = j.t + dt
        local p = math.min(j.t / j.dur, 1)
        local s = j.sm == "OUT" and (1-(1-p)^2) or (j.sm == "IN" and p*p or p)
        j.f:SetScale(j.a + (j.b - j.a) * s)
        if j.t >= j.dur then
            j.f:SetScale(j.b)
            table.remove(GDL_scaleJobs, i)
        else
            any = true
        end
    end
    if not any then GDL_scaleTicker:Hide() end
end)
function UI.ScaleLerp(frame, fromS, toS, dur, smoothing)
    frame:SetScale(fromS)
    for i = #GDL_scaleJobs, 1, -1 do
        if GDL_scaleJobs[i].f == frame then table.remove(GDL_scaleJobs, i) end
    end
    GDL_scaleJobs[#GDL_scaleJobs + 1] = { f = frame, a = fromS, b = toS, dur = dur, t = 0, sm = smoothing }
    GDL_scaleTicker:Show()
end

function UI:Initialize()
    C_Timer.After(0.5, function() self:CreateChronicle() end)
end

function UI:GetClassColor(classId) return CLASS_COLORS[classId] or {0.5, 0.5, 0.5} end
function UI:GetClassIcon(classId) return CLASS_ICONS[classId] or "Interface\\Icons\\INV_Misc_QuestionMark" end

function UI.ClassColorHex(classId)
    local c = CLASS_COLORS[classId]
    if not c then return "ff808080" end
    if type(c) == "string" then return c end
    return string.format("ff%02x%02x%02x", math.floor((c[1] or 0.5)*255), math.floor((c[2] or 0.5)*255), math.floor((c[3] or 0.5)*255))
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- DAS BUCH DER GEFALLENEN - ZWEI-SEITEN BUCH-LAYOUT
-- Links: Inhaltsverzeichnis | Rechts: Chronik & Content
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:CreateChronicle()
    local f = CreateFrame("Frame", "GDLChronicle", UIParent)
    f:SetSize(900, 659)
    f:SetPoint("CENTER")
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(100)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:EnableMouseWheel(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    f:SetScript("OnMouseWheel", function(self, delta)
        if IsControlKeyDown() then
            local s = math.max(0.5, math.min(2.0, self:GetScale() + delta * 0.1))
            self:SetScale(s)
            if GuildDeathLogDB then GuildDeathLogDB.bookScale = s end
        end
    end)
    f:Hide()

    local bookBg = f:CreateTexture(nil, "BACKGROUND")
    bookBg:SetAllPoints()
    bookBg:SetTexture("Interface\\AddOns\\GuildDeathLog\\Textures\\GDL_Book7.png")

    -- Schliessen-Button: Wachssiegel (Rabe & Knochen) statt Blizzard-X
    local closeBtn = CreateFrame("Button", nil, f)
    closeBtn:SetSize(42, 42)
    closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -24, -22)
    closeBtn:SetFrameLevel(f:GetFrameLevel() + 20)
    local csTex = closeBtn:CreateTexture(nil, "ARTWORK")
    csTex:SetAllPoints()
    csTex:SetTexture("Interface\\AddOns\\GuildDeathLog\\Textures\\GDL_Seal.png")
    closeBtn:SetScript("OnEnter", function(self)
        csTex:SetVertexColor(1.15, 0.95, 0.95)
        if UI.ScaleLerp then UI.ScaleLerp(self, 1.0, 1.12, 0.12, "OUT") end
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        GameTooltip:SetText("|cffDDB060" .. GDL:T("Schliessen") .. "|r", 1, 0.85, 0.6)
        GameTooltip:AddLine(GDL:T("Das Buch schliessen"), 0.75, 0.65, 0.45)
        GameTooltip:Show()
    end)
    closeBtn:SetScript("OnLeave", function(self)
        csTex:SetVertexColor(1, 1, 1)
        if UI.ScaleLerp then UI.ScaleLerp(self, self:GetScale(), 1.0, 0.12, "OUT") end
        GameTooltip:Hide()
    end)
    closeBtn:SetScript("OnClick", function() UI:HideBook() end)

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- LINKE SEITE: INHALTSVERZEICHNIS
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local leftPage = CreateFrame("Frame", nil, f)
    -- Buch-Artwork GDL_Book7 (vom Nutzer gewaehlt, bereits freigestellt): saubere,
    -- gleichmaessige warme Pergamentseiten, duenner Lederrand + Messing-Ecken,
    -- dezentes Kronen-Wasserzeichen rechts. Aspekt ~1.37. Falz mittig (x=450).
    -- Seiten an die gemessenen Pergamentflaechen angepasst, Ecken ausgespart.
    leftPage:SetPoint("TOPLEFT", 58, -65)
    leftPage:SetSize(346, 527)

    -- Titel (zentriert, kein Icon-Overlay)
    local title = leftPage:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 24, "")
    title:SetPoint("TOP", 0, -16)
    title:SetText("|cff2A1606" .. GDL:T("Das Buch der Gefallenen") .. "|r")
    title:SetShadowOffset(1, -1)
    title:SetShadowColor(0.35, 0.22, 0.08, 0.25)

    -- Dekorative Striche links/rechts vom Titel
    local titLL = leftPage:CreateTexture(nil, "ARTWORK")
    titLL:SetSize(34, 1)
    titLL:SetPoint("RIGHT", title, "LEFT", -6, 1)
    titLL:SetColorTexture(0.54, 0.37, 0.08, 0.60)
    local titLR = leftPage:CreateTexture(nil, "ARTWORK")
    titLR:SetSize(34, 1)
    titLR:SetPoint("LEFT", title, "RIGHT", 6, 1)
    titLR:SetColorTexture(0.54, 0.37, 0.08, 0.60)

    f.guildName = leftPage:CreateFontString(nil, "OVERLAY")
    f.guildName:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    f.guildName:SetPoint("TOP", title, "BOTTOM", 0, -3)
    f.guildName:SetTextColor(0.541, 0.369, 0.078)
    f.guildName:SetShadowOffset(1, -1)
    f.guildName:SetShadowColor(0, 0, 0, 0.10)

    local ornLine = leftPage:CreateTexture(nil, "ARTWORK")
    ornLine:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    ornLine:SetSize(280, 8)
    ornLine:SetPoint("TOP", f.guildName, "BOTTOM", 0, -3)
    ornLine:SetVertexColor(0.60, 0.45, 0.20, 0.70)

    -- ── Kapitel-Eintraege mit Kategorien ────────────────────────────
    local tableOfContents = {
        -- ① Tod & Gedaechtnis ────────────────────────────────────────
        { isCat=true,  label=GDL:T("Tode & Gedaechtnis"),  r=0.75, g=0.18, b=0.15 },
        { num="I",    title=GDL:T("Gedenkhalle"),             icon="Interface\\Icons\\Spell_Holy_SealOfSacrifice",  action="memorial",     desc="Alle Gefallenen – Gedenkstaette und Totenbuch" },
        { num="II",   title=GDL:T("Kondolenzenbuch"),         icon="Interface\\Icons\\INV_Misc_Book_09",            action="condolences",  desc="Abschiedsbotschaften fuer die Gefallenen" },
        { num="III",  title=GDL:T("Todeskarte"),              icon="Interface\\Icons\\INV_Misc_Map_01",             action="guildmap",     desc="Gefahrenzonen und Todesorte auf der Karte" },
        { num="IV",   title=GDL:T("Karte der Gefallenen"),    icon="Interface\\Icons\\INV_Misc_Map02",              action="deathmap",     desc="Heatmap: Wo starben die Gefallenen?" },

        -- ② Ruhm & Fortschritt ───────────────────────────────────────
        { isCat=true,  label=GDL:T("Ruhm & Fortschritt"),    r=0.72, g=0.55, b=0.15 },
        { num="V",    title=GDL:T("Ruhmeshalle"),             icon="Interface\\Icons\\Spell_Holy_AuraOfLight",      action="halloffame",   desc="Ueberlebende Helden der Gilde" },
        { num="VI",   title=GDL:T("Die Ausdauernden"),        icon="Interface\\Icons\\Spell_Nature_Purge",          action="survivals",    desc="Ueberlebens-Streaks der Gildenmitglieder" },
        { num="VII",  title=GDL:T("Meilensteine"),            icon="Interface\\Icons\\Spell_Holy_PowerWordShield",  action="milestones",   desc="Errungene Meilensteine und Fortschritt" },
        { num="VIII", title=GDL:T("Errungenschaften"),        icon="Interface\\Icons\\INV_Misc_Rune_01",            action="achievements", desc="Gilden- und Charakter-Errungenschaften" },

        -- ③ Gilde & Gemeinschaft ─────────────────────────────────────
        { isCat=true,  label=GDL:T("Gilde & Gemeinschaft"),  r=0.28, g=0.48, b=0.82 },
        { num="IX",   title=GDL:T("Abenteurer der Gilde"),   icon="Interface\\Icons\\INV_Misc_GroupNeedMore",      action="adventurers",  desc="Mitglieder-Profile, Berufe, Rassen" },
        { num="X",    title=GDL:T("Titel & Raenge"),         icon="Interface\\Icons\\INV_Crown_01",                action="titles",       desc="Gildentitel und Rang-System" },
        { num="XI",   title=GDL:T("Kalender"),               icon="Interface\\Icons\\INV_Misc_PocketWatch_01",     action="calendar",     desc="Bevorstehende Gilden-Ereignisse" },
        { num="XII",  title=GDL:T("Schwarzes Brett"),        icon="Interface\\Icons\\INV_Misc_Note_06",            action="bulletin",     desc="Gilden-Pinnwand: Ankuendigungen und Notizen" },
        { num="XIII", title=GDL:T("Gilden-Regeln"),          icon="Interface\\Icons\\INV_Misc_Book_11",            action="guildrules",   desc="Regeln und Verhaltenskodex" },

        -- ④ Auswertungen & Wettbewerb ────────────────────────────────
        { isCat=true,  label=GDL:T("Auswertungen & Wettbewerb"), r=0.82, g=0.48, b=0.10 },
        { num="XIV",  title=GDL:T("Statistiken"),            icon="Interface\\Icons\\INV_Misc_Book_07",            action="statistics",   desc="Klassen- und Zonen-Auswertungen der Tode" },
        { num="XV",   title=GDL:T("Gilden-Statistiken"),     icon="Interface\\Icons\\INV_Misc_Note_02",            action="guildstats",   desc="Aktivitaet und Wachstum der Gilde" },
        { num="XVI",  title=GDL:T("Gilden-Wettbewerb"),      icon="Interface\\Icons\\Ability_Warrior_Cleave",      action="guildcompetition", desc="Punktesystem, Leaderboard & Monsterjagd" },
    }

    -- ── Kapitel nach Kategorien gruppieren (einklappbar) ────────────
    local cats = {}
    do
        local cur
        for _, item in ipairs(tableOfContents) do
            if item.isCat then
                cur = { label = item.label, r = item.r, g = item.g, b = item.b }
                cats[#cats + 1] = cur
                cur.entries = {}
            elseif cur then
                cur.entries[#cur.entries + 1] = item
            end
        end
    end

    f.chapterEntries  = {}
    f.chapterByAction = {}
    f.tocCats         = cats
    f.catHeaders      = {}
    local entryIdx = 0

    for ci, cat in ipairs(cats) do
        -- ── Kategorie-Trennzeile (immer offenes Register, nicht klickbar) ──
        -- Alle 16 Kapitel sind dauerhaft sichtbar; die Kategorien sind nur
        -- noch schlanke Buchsektions-Trenner: Akzent, Gold-Versalien, Linie.
        local hdr = CreateFrame("Frame", nil, leftPage)
        hdr:SetSize(340, 15)

        local accent = hdr:CreateTexture(nil, "ARTWORK")
        accent:SetSize(3, 9); accent:SetPoint("LEFT", 1, 0)
        accent:SetColorTexture(cat.r * 0.62, cat.g * 0.48, cat.b * 0.40, 0.95)

        local lbl = hdr:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        lbl:SetPoint("LEFT", 10, 0)
        lbl:SetShadowOffset(1, -1); lbl:SetShadowColor(0, 0, 0, 0.18)
        lbl:SetText("|cff8A5E14" .. string.upper(cat.label) .. "|r")
        hdr.lbl = lbl

        -- Haarlinie vom Label bis zum rechten Rand — klassischer Registersatz
        local rule = hdr:CreateTexture(nil, "ARTWORK")
        rule:SetHeight(1)
        rule:SetPoint("LEFT", lbl, "RIGHT", 8, 0)
        rule:SetPoint("RIGHT", hdr, "RIGHT", -2, 0)
        rule:SetColorTexture(0.54, 0.37, 0.08, 0.28)

        hdr.cat = cat
        hdr.entryFrames = {}
        f.catHeaders[ci] = hdr

        -- ── Kapitel-Eintraege dieser Gruppe ────────────────────────
        for _, ch in ipairs(cat.entries) do
            entryIdx = entryIdx + 1
            local curIdx = entryIdx

            local entry = CreateFrame("Button", nil, leftPage)
            entry:SetSize(340, 22)

            local highlight = entry:CreateTexture(nil, "BACKGROUND")
            highlight:SetAllPoints(); highlight:SetColorTexture(0.6, 0.5, 0.3, 0)
            entry.highlight = highlight

            local numTxt = entry:CreateFontString(nil, "OVERLAY")
            numTxt:SetFont("Fonts\\FRIZQT__.TTF", 14, "")
            numTxt:SetPoint("LEFT", 4, 0)
            numTxt:SetText("|cff8A5E14" .. ch.num .. ".|r")
            numTxt:SetWidth(30); numTxt:SetJustifyH("RIGHT")
            numTxt:SetShadowOffset(1, -1); numTxt:SetShadowColor(0, 0, 0, 0.10)

            local icon = entry:CreateTexture(nil, "ARTWORK")
            icon:SetTexture(ch.icon); icon:SetSize(20, 20)
            icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)  -- schwarzen Icon-Rand wegcroppen
            icon:SetPoint("LEFT", numTxt, "RIGHT", 4, 0)

            local titleTxt = entry:CreateFontString(nil, "OVERLAY")
            titleTxt:SetFont("Fonts\\FRIZQT__.TTF", 14, "")
            titleTxt:SetPoint("LEFT", icon, "RIGHT", 5, 0)
            titleTxt:SetText("|cff3D2410" .. ch.title .. "|r")
            titleTxt:SetShadowOffset(1, -1); titleTxt:SetShadowColor(0, 0, 0, 0.10)
            entry.titleTxt = titleTxt
            entry.numTxt = numTxt

            local activeBarFrame = CreateFrame("Frame", nil, entry)
            activeBarFrame:SetSize(3, 20); activeBarFrame:SetPoint("LEFT", 0, 0); activeBarFrame:SetAlpha(0)
            local activeBarTex = activeBarFrame:CreateTexture(nil, "ARTWORK")
            activeBarTex:SetAllPoints(); activeBarTex:SetColorTexture(0.95, 0.78, 0.35, 1)
            local barFlash = activeBarFrame:CreateAnimationGroup()
            local barFlashA = barFlash:CreateAnimation("Alpha")
            barFlashA:SetFromAlpha(1); barFlashA:SetToAlpha(0.65)
            barFlashA:SetDuration(0.45); barFlashA:SetSmoothing("OUT")
            barFlash:SetScript("OnFinished", function() activeBarFrame:SetAlpha(0.65) end)
            entry.activeBarFrame = activeBarFrame; entry.barFlash = barFlash

            local pulseAG = activeBarFrame:CreateAnimationGroup()
            pulseAG:SetLooping("BOUNCE")
            local pulseA = pulseAG:CreateAnimation("Alpha")
            pulseA:SetFromAlpha(0.35); pulseA:SetToAlpha(1.0)
            pulseA:SetDuration(1.0); pulseA:SetSmoothing("IN_OUT")
            entry.pulseAG = pulseAG

            local glowFrame = CreateFrame("Frame", nil, entry)
            glowFrame:SetAllPoints(); glowFrame:SetAlpha(0)
            local glowTex = glowFrame:CreateTexture(nil, "ARTWORK")
            glowTex:SetAllPoints()
            glowTex:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
            glowTex:SetBlendMode("ADD"); glowTex:SetVertexColor(0.9, 0.75, 0.4, 0.55)
            local hoverInAG = glowFrame:CreateAnimationGroup()
            local hoverInA = hoverInAG:CreateAnimation("Alpha")
            hoverInA:SetFromAlpha(0); hoverInA:SetToAlpha(1)
            hoverInA:SetDuration(0.14); hoverInA:SetSmoothing("OUT")
            hoverInAG:SetScript("OnFinished", function() glowFrame:SetAlpha(1) end)
            local hoverOutAG = glowFrame:CreateAnimationGroup()
            local hoverOutA = hoverOutAG:CreateAnimation("Alpha")
            hoverOutA:SetFromAlpha(1); hoverOutA:SetToAlpha(0)
            hoverOutA:SetDuration(0.2); hoverOutA:SetSmoothing("IN")
            hoverOutAG:SetScript("OnFinished", function() glowFrame:SetAlpha(0) end)
            entry.glowFrame = glowFrame
            entry.hoverInAG = hoverInAG; entry.hoverOutAG = hoverOutAG
            entry.chapterData = ch; entry.chapterIndex = entryIdx

            local curIdx = entryIdx
            entry:SetScript("OnEnter", function(self)
                if UI.activeChapter ~= curIdx then
                    self.highlight:SetColorTexture(0.6, 0.5, 0.3, 0.1)
                    self.titleTxt:SetText("|cffA8761E" .. ch.title .. "|r")
                    if self.hoverOutAG then self.hoverOutAG:Stop() end
                    if self.hoverInAG  then self.hoverInAG:Play()  end
                end
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                GameTooltip:SetText("|cffDDB060" .. ch.num .. ". " .. ch.title .. "|r", 1, 0.86, 0.40, true)
                if ch.desc then GameTooltip:AddLine(GDL:T(ch.desc), 0.75, 0.65, 0.45, true) end
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cff444444Klicken zum Oeffnen|r")
                GameTooltip:Show()
            end)
            entry:SetScript("OnLeave", function(self)
                if UI.activeChapter ~= curIdx then
                    self.highlight:SetColorTexture(0.6, 0.5, 0.3, 0)
                    self.titleTxt:SetText("|cff3D2410" .. ch.title .. "|r")
                    if self.hoverInAG  then self.hoverInAG:Stop()  end
                    if self.hoverOutAG then self.hoverOutAG:Play() end
                end
                GameTooltip:Hide()
            end)
            entry:SetScript("OnClick", function(self)
                UI.ScaleLerp(self, 0.94, 1.0, 0.14, "OUT")
                GameTooltip:Hide()
                UI:HandleChapterClick(curIdx, ch.action)
            end)


            entry.tocCat = ci
            f.chapterEntries[entryIdx]   = entry
            f.chapterByAction[ch.action] = entryIdx
            hdr.entryFrames[#hdr.entryFrames + 1] = entry
        end
    end

    UI:RelayoutTOC(f)

    local footQuoteL = leftPage:CreateFontString(nil, "OVERLAY")
    footQuoteL:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    footQuoteL:SetPoint("BOTTOM", 0, 8)
    footQuoteL:SetWidth(300)
    footQuoteL:SetJustifyH("CENTER")
    footQuoteL:SetShadowOffset(1, -1)
    footQuoteL:SetShadowColor(0, 0, 0, 0.12)
    -- Wechselnde Zitate: bei jedem Oeffnen ein anderes (Buch lebt)
    f._quotes = {
        "Kein Tod ist umsonst, wenn man sich erinnert.",
        GDL:T("Wer in den Herzen seiner Gilde lebt, ist nicht tot."),
        "Jede Seite dieses Buches wurde mit Mut bezahlt.",
        "Die Gefallenen gingen voran — wir tragen ihre Namen weiter.",
        GDL:T("Ein Leben. Ein Versuch. Eine Legende."),
        "Aus Asche und Tinte: die Geschichte unserer Gilde.",
    }
    footQuoteL:SetText("|cff5E3E1C\"" .. f._quotes[1] .. "\"|r")
    f.quote = footQuoteL

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- RECHTE SEITE: DYNAMISCHER CONTENT-BEREICH
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local rightPage = CreateFrame("Frame", nil, f)
    rightPage:SetPoint("TOPRIGHT", -58, -65)
    rightPage:SetSize(346, 527)
    f.rightPage = rightPage

    f.rightTitle = rightPage:CreateFontString(nil, "OVERLAY")
    f.rightTitle:SetFont("Fonts\\MORPHEUS.TTF", 21, "")
    f.rightTitle:SetPoint("TOP", 0, -20)
    f.rightTitle:SetText("|cff2A1606" .. GDL:T("Chronik der Gefallenen") .. "|r")
    f.rightTitle:SetShadowOffset(1, -1)
    f.rightTitle:SetShadowColor(0.35, 0.22, 0.08, 0.25)

    -- Ornament-Linien um den Kapitel-Titel (DialogueUI-Stil)
    local ornLeft = rightPage:CreateTexture(nil, "ARTWORK")
    ornLeft:SetSize(55, 1); ornLeft:SetPoint("TOPLEFT", 10, -22)
    ornLeft:SetColorTexture(0.54, 0.37, 0.08, 0.60)
    local ornRight = rightPage:CreateTexture(nil, "ARTWORK")
    ornRight:SetSize(55, 1); ornRight:SetPoint("TOPRIGHT", -10, -22)
    ornRight:SetColorTexture(0.54, 0.37, 0.08, 0.60)
    -- (Gold-Quadrate an den Enden entfernt: waren 6x6 SetColorTexture-Kaesten.
    --  Die duennen Ornament-Linien reichen als dezenter Akzent.)

    f.rightSubtitle = rightPage:CreateFontString(nil, "OVERLAY")
    f.rightSubtitle:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    f.rightSubtitle:SetPoint("TOP", f.rightTitle, "BOTTOM", 0, -1)
    f.rightSubtitle:SetTextColor(0.369, 0.243, 0.110)
    f.rightSubtitle:SetText("")

    -- Folio: klassische Seitenzahl am unteren Blattrand ("— VII —")
    f.folio = rightPage:CreateFontString(nil, "OVERLAY")
    f.folio:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    f.folio:SetPoint("BOTTOM", 0, 16)
    f.folio:SetTextColor(0.478, 0.392, 0.282)
    f.folio:SetShadowOffset(1, -1)
    f.folio:SetShadowColor(0, 0, 0, 0.10)
    f.folio:SetText("")

    -- Stats-Bar (nur fuer Chronik sichtbar)
    local statsFrame = CreateFrame("Frame", nil, rightPage)
    statsFrame:SetPoint("TOP", f.rightSubtitle, "BOTTOM", 0, -2)
    statsFrame:SetSize(324, 28)
    f.statsFrame = statsFrame

    f.statTotal  = self:CreateStatColumn(statsFrame, "TOPLEFT",  5,   0, "Gefallene", "0", "Gefallene Gesamt",   "Alle verzeichneten Tode der Gilde seit Grundung")
    f.statToday  = self:CreateStatColumn(statsFrame, "TOP",     -55,  0, "Heute",     "0", "Tode Heute",         "Anzahl der Tode innerhalb der letzten 24 Stunden")
    f.statWeek   = self:CreateStatColumn(statsFrame, "TOP",      55,  0, "Woche",     "0", "Tode diese Woche",   "Tode der letzten 7 Tage")
    f.statAvgLvl = self:CreateStatColumn(statsFrame, "TOPRIGHT", -5,  0, "Avg Lvl",  "0", "Durchschnittslevel", "Mittleres Level aller gefallenen Charaktere")

    local divR = rightPage:CreateTexture(nil, "ARTWORK")
    divR:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    divR:SetSize(300, 6)
    divR:SetPoint("TOP", statsFrame, "BOTTOM", 0, 0)
    divR:SetVertexColor(0.35, 0.28, 0.2, 0.3)
    f.divider = divR

    -- Haupt-Scroll-Bereich (wird von allen Kapiteln geteilt)
    local scroll = CreateFrame("ScrollFrame", "GDLBookScroll", rightPage, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 5, -58)
    scroll:SetPoint("BOTTOMRIGHT", -22, 40)
    f.scrollFrame = scroll

    local scrollBar = scroll.ScrollBar or _G["GDLBookScrollScrollBar"]
    if scrollBar then
        -- Klobige Pfeil-Buttons ganz raus (Mausrad scrollt) -> nur schlanker Thumb
        local upBtn   = scrollBar.ScrollUpButton   or _G["GDLBookScrollScrollBarScrollUpButton"]
        local downBtn = scrollBar.ScrollDownButton or _G["GDLBookScrollScrollBarScrollDownButton"]
        for _, b in ipairs({ upBtn, downBtn }) do
            if b then
                b:SetAlpha(0); b:EnableMouse(false); b:Hide()
                b:HookScript("OnShow", function(self) self:Hide() end)
            end
        end
        -- Leiste ohne Pfeile ueber die volle Hoehe, dezent rechts in den Falz/Rand
        scrollBar:SetPoint("TOPLEFT", scroll, "TOPRIGHT", 2, -2)
        scrollBar:SetPoint("BOTTOMLEFT", scroll, "BOTTOMRIGHT", 2, 2)
        -- Track-Hintergrund (blaugraue Blizzard-Textur) ausblenden
        for _, region in ipairs({ scrollBar:GetRegions() }) do
            if region:GetObjectType() == "Texture" then region:SetAlpha(0) end
        end
        -- Warmer, schlanker Thumb
        local thumb = scrollBar:GetThumbTexture()
        if thumb then
            thumb:SetColorTexture(0.50, 0.37, 0.16, 0.80)
            thumb:SetWidth(5)
        end
    end

    local scrollChild = CreateFrame("Frame", nil, scroll)
    scrollChild:SetSize(scroll:GetWidth(), 1)
    scroll:SetScrollChild(scrollChild)
    f.scrollChild = scrollChild

    -- Werkzeug-Reiter (ausklappbar) unten rechts auf der rechten Seite
    local iconSize = 24
    local iconPad = 4
    -- Flyout-Container (haelt die Werkzeug-Icons, klappt nach links aus)
    local iconFlyout = CreateFrame("Frame", nil, rightPage)
    local numIcons = 6
    local flyoutW  = numIcons * (iconSize + iconPad) + 10
    iconFlyout:SetSize(flyoutW, iconSize + 10)
    iconFlyout:SetAlpha(0)
    iconFlyout:Hide()

    -- Dezente warme Pergament-Flaeche statt schwarzem Backdrop
    local foBg = iconFlyout:CreateTexture(nil, "BACKGROUND")
    foBg:SetAllPoints()
    foBg:SetColorTexture(0.86, 0.74, 0.50, 0.16)
    local foTop = iconFlyout:CreateTexture(nil, "BORDER")
    foTop:SetHeight(1); foTop:SetPoint("TOPLEFT"); foTop:SetPoint("TOPRIGHT")
    foTop:SetColorTexture(0.62, 0.46, 0.16, 0.50)

    local toolIcons = {
        { tip = "Aktualisieren", desc = "Daten neu laden und Chronik aktualisieren",
          tex = "Interface\\Buttons\\UI-RotationRight-Button-Up",
          fn = function() local D = GDL:GetModule("Deathlog") if D then D:ScanData() end; UI:RenderChapter("memorial") end },
        { tip = "Sync", desc = "Gildenmitglieder synchronisieren",
          tex = "Interface\\Icons\\Ability_Spy",
          fn = function() local S = GDL:GetModule("Sync") if S then S:RequestFullSync() end end },
        { tip = "Export", desc = "Todes-Protokoll exportieren",
          tex = "Interface\\Icons\\INV_Scroll_01",
          fn = function() local E = GDL:GetModule("Export") if E then E:ShowExportWindow() end end },
        { tip = "Einstellungen", desc = "Guild Chronicle konfigurieren",
          tex = "Interface\\Icons\\Trade_Engineering",
          fn = function() UI:ShowSettings() end },
        { tip = "Debug", desc = "Debug-Konsole oeffnen",
          tex = "Interface\\Icons\\INV_Misc_QuestionMark",
          fn = function() local D = GDL:GetModule("Debug") if D then D:ShowWindow() end end },
        { tip = "Supporter+", desc = "Premium-Features freischalten",
          tex = "Interface\\Icons\\INV_ValentinesCard01",
          fn = function() UI:RenderChapter("supporter") end },
    }

    for i, ti in ipairs(toolIcons) do
        local ib = CreateFrame("Button", nil, iconFlyout)
        ib:SetSize(iconSize, iconSize)
        ib:SetPoint("LEFT", iconFlyout, "LEFT", 6 + (i - 1) * (iconSize + iconPad), 0)

        local ibBg = ib:CreateTexture(nil, "BACKGROUND")
        ibBg:SetAllPoints(); ibBg:SetColorTexture(0.72, 0.54, 0.16, 0)
        ib.icon = ib:CreateTexture(nil, "ARTWORK")
        ib.icon:SetPoint("TOPLEFT", 1, -1); ib.icon:SetPoint("BOTTOMRIGHT", -1, 1)
        ib.icon:SetTexture(ti.tex); ib.icon:SetAlpha(0.92)
        ib.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)  -- schwarzen Icon-Rand wegcroppen
        -- duenner Goldrahmen je Icon (Top/Bottom-Linie), kein dunkler Block
        local ibLine = ib:CreateTexture(nil, "BORDER")
        ibLine:SetHeight(1); ibLine:SetPoint("BOTTOMLEFT"); ibLine:SetPoint("BOTTOMRIGHT")
        ibLine:SetColorTexture(0.60, 0.44, 0.14, 0.30)

        ib:SetScript("OnEnter", function(self)
            self.icon:SetAlpha(1.0)
            ibBg:SetColorTexture(0.78, 0.60, 0.20, 0.30)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:ClearLines()
            GameTooltip:SetText("|cffDDB060" .. ti.tip .. "|r", 1, 0.85, 0.6, true)
            if ti.desc then GameTooltip:AddLine(GDL:T(ti.desc), 0.75, 0.65, 0.45, true) end
            GameTooltip:Show()
        end)
        ib:SetScript("OnLeave", function(self)
            self.icon:SetAlpha(0.92)
            ibBg:SetColorTexture(0.72, 0.54, 0.16, 0)
            GameTooltip:Hide()
        end)
        ib:SetScript("OnMouseDown", function(self) self.icon:SetAlpha(0.80); ibBg:SetColorTexture(0.58, 0.42, 0.10, 0.42) end)
        ib:SetScript("OnMouseUp",   function(self) self.icon:SetAlpha(1.0);  ibBg:SetColorTexture(0.78, 0.60, 0.20, 0.30) end)
        ib:SetScript("OnClick", ti.fn)
    end

    -- Reiter-Knopf (immer sichtbar) — klappt die Werkzeuge nach links aus/ein
    local toggleTab = CreateFrame("Button", nil, rightPage)
    toggleTab:SetSize(24, 24)
    toggleTab:SetPoint("BOTTOMRIGHT", -12, 8)
    iconFlyout:SetPoint("RIGHT", toggleTab, "LEFT", -4, 0)

    local ttBg = toggleTab:CreateTexture(nil, "BACKGROUND")
    ttBg:SetAllPoints(); ttBg:SetColorTexture(0.80, 0.66, 0.42, 0.0)  -- idle transparent, Hover faerbt
    local ttArrow = toggleTab:CreateTexture(nil, "ARTWORK")
    ttArrow:SetPoint("TOPLEFT", 2, -2); ttArrow:SetPoint("BOTTOMRIGHT", -2, 2)
    ttArrow:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Up")
    ttArrow:SetVertexColor(0.52, 0.38, 0.14, 1)

    local fadeIn = iconFlyout:CreateAnimationGroup()
    local fIn = fadeIn:CreateAnimation("Alpha"); fIn:SetFromAlpha(0); fIn:SetToAlpha(1); fIn:SetDuration(0.18)
    fadeIn:SetScript("OnPlay",     function() iconFlyout:Show() end)
    fadeIn:SetScript("OnFinished", function() iconFlyout:SetAlpha(1) end)
    local fadeOut = iconFlyout:CreateAnimationGroup()
    local fOut = fadeOut:CreateAnimation("Alpha"); fOut:SetFromAlpha(1); fOut:SetToAlpha(0); fOut:SetDuration(0.15)
    fadeOut:SetScript("OnFinished", function() iconFlyout:SetAlpha(0); iconFlyout:Hide() end)

    f._toolsOpen = false
    local function setTools(open)
        f._toolsOpen = open
        if open then
            fadeOut:Stop(); fadeIn:Stop(); fadeIn:Play()
            ttArrow:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
        else
            fadeIn:Stop(); fadeOut:Stop()
            if iconFlyout:IsShown() then fadeOut:Play() else iconFlyout:SetAlpha(0); iconFlyout:Hide() end
            ttArrow:SetTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Up")
        end
    end
    toggleTab:SetScript("OnClick", function() setTools(not f._toolsOpen) end)
    toggleTab:SetScript("OnEnter", function(self)
        ttBg:SetColorTexture(0.86, 0.71, 0.46, 0.42)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:ClearLines()
        GameTooltip:SetText("|cffDDB060Werkzeuge|r", 1, 0.85, 0.6, true)
        GameTooltip:AddLine("Aktualisieren, Sync, Export, Einstellungen, Debug, Supporter", 0.75, 0.65, 0.45, true)
        GameTooltip:Show()
    end)
    toggleTab:SetScript("OnLeave", function() ttBg:SetColorTexture(0.80, 0.66, 0.42, 0.22); GameTooltip:Hide() end)
    f.iconFlyout = iconFlyout
    f.toolsToggle = toggleTab

    -- Open-Animation: Alpha Fade-In + Scale via OnUpdate (Classic compat)
    f.openAnim = f:CreateAnimationGroup()
    local fadeIn = f.openAnim:CreateAnimation("Alpha")
    fadeIn:SetFromAlpha(0)
    fadeIn:SetToAlpha(1)
    fadeIn:SetDuration(0.35)
    fadeIn:SetSmoothing("OUT")
    f.openAnim:SetScript("OnFinished", function() f:SetAlpha(1); f:SetScale(GuildDeathLogDB.bookScale or 0.85) end)

    -- Close-Animation: Alpha Fade-Out + Scale via OnUpdate (Classic compat)
    f.closeAnim = f:CreateAnimationGroup()
    local fadeOut = f.closeAnim:CreateAnimation("Alpha")
    fadeOut:SetFromAlpha(1)
    fadeOut:SetToAlpha(0)
    fadeOut:SetDuration(0.18)
    fadeOut:SetSmoothing("IN")
    f.closeAnim:SetScript("OnFinished", function() f:Hide(); f:SetAlpha(1); f:SetScale(GuildDeathLogDB.bookScale or 0.85) end)

    -- Seiten-Übergang: rightPage fade out -> render -> fade in
    local rp = f.rightPage
    f.pageOutAnim = rp:CreateAnimationGroup()
    local pageOutA = f.pageOutAnim:CreateAnimation("Alpha")
    pageOutA:SetFromAlpha(1); pageOutA:SetToAlpha(0)
    pageOutA:SetDuration(0.18); pageOutA:SetSmoothing("IN")
    f.pageOutAnim:SetScript("OnFinished", function()
        rp:SetAlpha(0)
        if f._pendingAction then
            UI:RenderChapter(f._pendingAction)
            f._pendingAction = nil
        end
        f.pageInAnim:Play()
    end)
    f.pageInAnim = rp:CreateAnimationGroup()
    local pageInA = f.pageInAnim:CreateAnimation("Alpha")
    pageInA:SetFromAlpha(0); pageInA:SetToAlpha(1)
    pageInA:SetDuration(0.26); pageInA:SetSmoothing("OUT")
    f.pageInAnim:SetScript("OnFinished", function()
        rp:SetAlpha(1)
        local FX2 = GDL:GetModule("AnimFX")
        if FX2 then
            FX2.ShimmerSweep(rp, 0.95, 0.78, 0.25, 0.55)
            FX2.GlowRing(rp, 0.88, 0.68, 0.22, (rp:GetWidth() or 400) * 0.60, 0.55)
        end
    end)

    f:SetScript("OnShow", function(self)
        local savedScale = GuildDeathLogDB and GuildDeathLogDB.bookScale or 0.85
        self:SetScale(savedScale)
        local lastAction = GuildDeathLogDB and GuildDeathLogDB.lastAction or "memorial"
        -- Index ueber die Action aufloesen statt gespeicherten Zahlenindex:
        -- bei TOC-Umsortierungen zeigt ein alter Index sonst aufs falsche Kapitel.
        local idx = self.chapterByAction[lastAction] or 1
        UI:SetActiveChapter(idx)
        UI:RenderChapter(lastAction)
        if self.quote and self._quotes then
            self.quote:SetText("|cff5E3E1C\"" .. self._quotes[math.random(#self._quotes)] .. "\"|r")
        end
    end)

    self:AddBookAtmosphere(f)
    self.mainFrame = f
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- BUCH-ATMOSPHÄRE: Staub-Motes + Dekoration
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
function UI:RelayoutTOC(frame)
    -- Immer offenes Register: alle Kategorien + alle 16 Kapitel sichtbar.
    -- Budget (Seite 527px): Start 56 + 4 Trenner a 15 + 16 Eintraege a 20
    -- + 4 Zwischenluecken a 4 = 452 — passt mit Luft bis zum Fusszitat.
    local f = frame or self.mainFrame
    if not f or not f.catHeaders then return end

    local y = -60
    for ci, hdr in ipairs(f.catHeaders) do
        hdr:ClearAllPoints()
        hdr:SetPoint("TOPLEFT", 5, y)
        hdr:Show()
        y = y - 18

        for _, entry in ipairs(hdr.entryFrames) do
            entry:ClearAllPoints()
            entry:SetPoint("TOPLEFT", 5, y)
            entry:Show()
            y = y - 22
        end
        y = y - 4
    end
end

function UI:AddBookAtmosphere(f)
    -- Bewusst leer: das neue Buch-Artwork (GDL_Book2) bringt Falz, Schatten,
    -- verbrannte Raender und Vignette bereits mit. Jede zusaetzlich gemalte
    -- Linie wuerde mit dem fotorealistischen Material kollidieren.
end

function UI:CreateStatColumn(parent, point, x, y, label, value, tooltipTitle, tooltipDesc)
    local container = CreateFrame("Frame", nil, parent)
    container:SetSize(75, 30)
    container:SetPoint(point, x, y)

    -- Hover-Hintergrund
    local colBg = container:CreateTexture(nil, "BACKGROUND")
    colBg:SetAllPoints(); colBg:SetColorTexture(0.7, 0.52, 0.14, 0)

    local valueText = container:CreateFontString(nil, "OVERLAY")
    valueText:SetFont("Fonts\\MORPHEUS.TTF", 15, "")
    valueText:SetPoint("TOP", 0, 0)
    valueText:SetTextColor(0.239, 0.141, 0.063)
    valueText:SetShadowOffset(1, -1)
    valueText:SetShadowColor(0, 0, 0, 0.15)
    valueText:SetText(value)

    local labelText = container:CreateFontString(nil, "OVERLAY")
    labelText:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    labelText:SetPoint("TOP", valueText, "BOTTOM", 0, -1)
    labelText:SetTextColor(0.478, 0.392, 0.282)
    labelText:SetText(string.upper(label))

    container.value = valueText
    container.label = labelText

    if tooltipTitle then
        container:EnableMouse(true)
        container:SetScript("OnEnter", function(self)
            colBg:SetColorTexture(0.7, 0.52, 0.14, 0.18)
            valueText:SetTextColor(0.659, 0.463, 0.118)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:ClearLines()
            GameTooltip:SetText("|cffDDB060" .. tooltipTitle .. "|r", 1, 0.85, 0.5, true)
            if tooltipDesc then GameTooltip:AddLine(tooltipDesc, 0.75, 0.65, 0.45, true) end
            GameTooltip:Show()
        end)
        container:SetScript("OnLeave", function(self)
            colBg:SetColorTexture(0.7, 0.52, 0.14, 0)
            valueText:SetTextColor(0.239, 0.141, 0.063)
            GameTooltip:Hide()
        end)
    end

    return container
end

function UI:CreateElegantButton(parent, text, x, y, onClick)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(68, 20)
    btn:SetPoint("BOTTOM", x, y)
    
    btn:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets = { left = 2, right = 2, top = 2, bottom = 2 }
    })
    btn:SetBackdropColor(0.38, 0.28, 0.14, 0.85)
    btn:SetBackdropBorderColor(0.60, 0.46, 0.24, 0.85)
    
    local label = btn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")  -- Etwas kleiner für lange Texte
    label:SetPoint("CENTER", 0, 1)
    label:SetText(text)
    label:SetTextColor(0.88, 0.72, 0.42)
    btn.label = label
    
    btn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.52, 0.38, 0.18, 1.0)
        self:SetBackdropBorderColor(0.80, 0.62, 0.30, 1.0)
        self.label:SetTextColor(1.0, 0.85, 0.52)
    end)
    
    btn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.38, 0.28, 0.14, 0.85)
        self:SetBackdropBorderColor(0.60, 0.46, 0.24, 0.85)
        self.label:SetTextColor(0.88, 0.72, 0.42)
    end)
    
    btn:SetScript("OnMouseDown", function(self)
        self.label:SetPoint("CENTER", 1, 0)
    end)
    
    btn:SetScript("OnMouseUp", function(self)
        self.label:SetPoint("CENTER", 0, 1)
    end)
    
    btn:SetScript("OnClick", onClick)
    
    return btn
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- KAPITEL-NAVIGATION (Zwei-Seiten-Buch)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

UI.activeChapter = 1

function UI:SetActiveChapter(index)
    self.activeChapter = index
    local f = self.mainFrame
    if not f or not f.chapterEntries then return end

    for i, entry in pairs(f.chapterEntries) do
        if i == index then
            entry.highlight:SetColorTexture(0.75, 0.6, 0.35, 0.18)
            entry.titleTxt:SetText("|cff2A1606" .. entry.chapterData.title .. "|r")
            entry.numTxt:SetText("|cffA8761E" .. entry.chapterData.num .. ".|r")
            if entry.activeBarFrame then
                entry.activeBarFrame:SetAlpha(1)
                if entry.barFlash then entry.barFlash:Play() end
                if entry.pulseAG  then entry.pulseAG:Play()  end
            end
            if entry.hoverInAG  then entry.hoverInAG:Stop()  end
            if entry.hoverOutAG then entry.hoverOutAG:Stop() end
            if entry.glowFrame  then entry.glowFrame:SetAlpha(0) end
        else
            entry.highlight:SetColorTexture(0.6, 0.5, 0.3, 0)
            entry.titleTxt:SetText("|cff3D2410" .. entry.chapterData.title .. "|r")
            entry.numTxt:SetText("|cff8A5E14" .. entry.chapterData.num .. ".|r")
            if entry.activeBarFrame then
                entry.activeBarFrame:SetAlpha(0)
                if entry.barFlash then entry.barFlash:Stop() end
                if entry.pulseAG  then entry.pulseAG:Stop()  end
            end
            if entry.glowFrame then entry.glowFrame:SetAlpha(0) end
        end
    end
end

function UI:HandleChapterClick(index, action)
    self:SetActiveChapter(index)
    GuildDeathLogDB = GuildDeathLogDB or {}
    GuildDeathLogDB.lastChapter = index
    GuildDeathLogDB.lastAction  = action

    -- Staub-Partikel beim Kapitelwechsel (lebendiges Grimoire)
    local FX = GDL:GetModule("AnimFX")
    local f  = self.mainFrame
    if FX and f and f:IsShown() then FX.DustMotes(f, 7, 1.5) end

    if f and f.pageOutAnim and f.rightPage and f.rightPage:GetAlpha() > 0.01 then
        if f.pageInAnim  and f.pageInAnim:IsPlaying()  then f.pageInAnim:Stop()  end
        f.rightPage:SetAlpha(1)
        f._pendingAction = action
        f.pageOutAnim:Stop()
        f.pageOutAnim:Play()
    else
        self:RenderChapter(action)
        if f and f.rightPage then
            f.rightPage:SetAlpha(0)
            if f.pageInAnim then f.pageInAnim:Play() end
        end
    end
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- RENDER-SYSTEM: Alle Kapitel werden in die rechte Buchseite gerendert
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

UI.CHAPTER_TITLES = {
    halloffame   = "Ruhmeshalle",
    statistics   = "Statistiken des Todes",
    milestones   = "Meilensteine & Erfolge",
    professions  = "Berufe der Gilde",
    guildmap     = "Todeskarte",
    titles       = "Titel & Raenge",
    memorial     = "Gedenkhalle",
    guildstats   = "Gilden-Statistiken",
    guildrules   = "Gilden-Regeln",
    calendar     = "Gilden-Kalender",
    guildcompetition = "Gilden-Wettbewerb",
    achievements = "Errungenschaften",
    condolences  = "Kondolenzenbuch",
    bulletin     = "Schwarzes Brett",
    supporter    = "Supporter+",
    survivals    = "Die Ausdauernden",
    deathmap     = "Karte der Gefallenen",
    adventurers  = "Abenteurer der Gilde",
    chronicle    = "Chronik der Gefallenen",
}

-- ── SCROLLCHILD-RECYCLING ────────────────────────────────────
-- Frames/FontStrings sind in WoW nicht garbage-collectbar. Vorher hat
-- JEDER Kapitel-Render komplett neue Widgets erzeugt und alte nur
-- versteckt — Speicher wuchs mit jedem Klick, ClearScrollChild baute
-- dazu pro Aufruf zwei Riesen-Tabellen. Jetzt: MakeBookLine/MakeBookRow
-- holen aus Pools, ClearScrollChild gibt zurueck.
-- Globale FREI-Pools (wiederverwendbare Widgets)
local fsPool  = {}
local rowPool = {}
UI._memberCardPool = {}
UI._achRowPool     = {}

-- ── KAPITEL-CACHING ──────────────────────────────────────────
-- Frueher baute JEDER Kapitelwechsel die komplette Seite neu (bei der Chronik
-- ~15 Frames PRO Tod -> hunderte/tausende Frames pro Klick = FPS-Einbruch).
-- Jetzt hat jedes Kapitel (action) eine eigene Buchseite, die GECACHT wird:
-- Zurueckschalten zeigt nur die fertige Seite wieder. Neu gebaut wird NUR,
-- wenn sich Daten geaendert haben -> UI._contentGen wird dann hochgezaehlt.
UI._contentGen  = UI._contentGen or 1
UI.activeAction = UI.activeAction or nil

-- In-Use-Listen haengen PRO SEITE am Container (page._fsInUse usw.), damit ein
-- Rebuild EINER Seite nicht die Widgets anderer, gecachter Seiten freigibt.
local function PageList(page, key)
    local t = page[key]
    if not t then t = {}; page[key] = t end
    return t
end
local function ReleaseInto(inUse, pool, clearText)
    for i = #inUse, 1, -1 do
        local w = inUse[i]
        w:Hide()
        if clearText then w:SetText("") end
        pool[#pool + 1] = w
        inUse[i] = nil
    end
end

-- Haengt ein frisch erzeugtes/recyceltes Widget an die AKTIVE Seite.
-- Aus UI.lua UND UIChapters.lua (AcquireMemberCard) nutzbar.
function UI:TrackPageWidget(widget, key)
    local page = self.mainFrame and self.mainFrame.scrollChild
    if not page then return end
    local t = PageList(page, key)
    t[#t + 1] = widget
end

-- Liefert (und erzeugt) die Buchseite fuer ein Kapitel.
function UI:GetChapterPage(action)
    local f = self.mainFrame
    f._pages = f._pages or {}
    local p = f._pages[action]
    if not p then
        p = CreateFrame("Frame", nil, f.scrollFrame)
        p:SetSize(f.scrollFrame:GetWidth(), 1)
        f._pages[action] = p
    end
    return p
end

-- Markiert alle Kapitel als veraltet -> beim naechsten Anzeigen Neuaufbau.
function UI:InvalidateChapters()
    UI._contentGen = (UI._contentGen or 0) + 1
end

function UI:ClearScrollChild()
    local f = self.mainFrame
    local page = f and f.scrollChild
    if not page then return end
    -- Gepoolte Widgets DIESER Seite zurueckgeben
    ReleaseInto(PageList(page, "_fsInUse"),  fsPool, true)
    ReleaseInto(PageList(page, "_rowInUse"), rowPool, false)
    ReleaseInto(PageList(page, "_mcInUse"),  UI._memberCardPool, false)
    ReleaseInto(PageList(page, "_achInUse"), UI._achRowPool, false)
    -- Nicht-gepoolte Direkt-Creates verstecken (nur diese Seite, daher bounded)
    for _, c in ipairs({page:GetChildren()}) do c:Hide() end
    for _, r in ipairs({page:GetRegions()}) do r:Hide() end
end

function UI:SetRightPageHeader(action)
    local f = self.mainFrame
    if not f then return end
    f.rightTitle:SetText("|cff2A1606" .. GDL:T(UI.CHAPTER_TITLES[action] or "Kapitel") .. "|r")
    f.rightSubtitle:SetText("")
    if f.folio then
        local idx   = f.chapterByAction and f.chapterByAction[action]
        local entry = idx and f.chapterEntries and f.chapterEntries[idx]
        local num   = entry and entry.chapterData and entry.chapterData.num
        f.folio:SetText(num and ("—  " .. num .. "  —") or "")
    end
    if f.statsFrame then f.statsFrame:Hide() end
    if f.divider    then f.divider:Hide()    end
    f.scrollFrame:SetPoint("TOPLEFT", 5, -48)
end

function UI:MakeBookLine(parent, y, text, size, color)
    if text and GDL.T then text = GDL:T(text) end   -- EN-Lokalisierung (exakter Treffer)
    local fs = table.remove(fsPool)
    if not fs then
        fs = parent:CreateFontString(nil, "OVERLAY")
    end
    fs:SetParent(parent)
    self:TrackPageWidget(fs, "_fsInUse")

    local baseSz = size or 10
    local sz = math.floor(baseSz * 1.28 + 0.5)   -- Buch-Schrift global groesser (self-reflow)
    local fontFile = baseSz >= 14 and "Fonts\\MORPHEUS.TTF" or "Fonts\\FRIZQT__.TTF"
    fs:SetFont(fontFile, sz, "")
    fs:ClearAllPoints()
    fs:SetPoint("TOPLEFT", 8, y)
    fs:SetWidth(300)
    fs:SetJustifyH("LEFT")
    fs:SetWordWrap(true)
    local defaultColor = sz >= 14 and "|cff2A1606" or "|cff3D2410"
    fs:SetText((color or defaultColor) .. text .. "|r")
    fs:SetShadowOffset(1, -1); fs:SetShadowColor(0, 0, 0, 0.15)
    fs:Show()
    local textHeight = fs:GetStringHeight() or (sz + 4)
    return fs, y - textHeight - 4
end

function UI:MakeBookRow(parent, y, icon, mainText, subText, rightText)
    local row = table.remove(rowPool)
    if not row then
        row = CreateFrame("Frame", nil, parent)
        row.bg = row:CreateTexture(nil, "BACKGROUND")
        row.bg:SetAllPoints()
        row.bg:SetColorTexture(0.30, 0.19, 0.06, 0.09)
        row.rule = row:CreateTexture(nil, "ARTWORK")
        row.rule:SetHeight(1); row.rule:SetPoint("TOPLEFT"); row.rule:SetPoint("TOPRIGHT")
        row.rule:SetColorTexture(0.45, 0.32, 0.10, 0.18)
        row.ic = row:CreateTexture(nil, "ARTWORK")
        row.ic:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        row.ic:SetSize(26, 26); row.ic:SetPoint("LEFT", 4, 0)
        row.mt = row:CreateFontString(nil, "OVERLAY")
        row.mt:SetFont("Fonts\\FRIZQT__.TTF", 13, "")
        row.mt:SetShadowOffset(1, -1); row.mt:SetShadowColor(0, 0, 0, 0.15)
        row.st = row:CreateFontString(nil, "OVERLAY")
        row.st:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        row.st:SetShadowOffset(1, -1); row.st:SetShadowColor(0, 0, 0, 0.15)
        row.rt = row:CreateFontString(nil, "OVERLAY")
        row.rt:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        row.rt:SetPoint("RIGHT", -6, 0)
        row.rt:SetShadowOffset(1, -1); row.rt:SetShadowColor(0, 0, 0, 0.15)
    end
    row:SetParent(parent)
    -- Renderer haengen vereinzelt eigene Buttons an Rows (z.B. Kalender-
    -- Loeschknopf). Beim Recyceln duerfen die nicht in fremden Kapiteln
    -- wieder auftauchen.
    if row:GetNumChildren() > 0 then
        for _, kid in ipairs({ row:GetChildren() }) do kid:Hide() end
    end
    self:TrackPageWidget(row, "_rowInUse")

    row:SetSize(310, subText and 41 or 34)
    row:ClearAllPoints()
    row:SetPoint("TOPLEFT", 4, y)

    if icon then
        row.ic:SetTexture(icon)
        row.ic:Show()
    else
        row.ic:Hide()
    end

    row.mt:ClearAllPoints()
    row.mt:SetPoint("LEFT", icon and 34 or 6, subText and 8 or 0)
    row.mt:SetText(mainText or "")
    row.mt:Show()

    if subText then
        row.st:ClearAllPoints()
        row.st:SetPoint("TOPLEFT", row.mt, "BOTTOMLEFT", 0, -1)
        row.st:SetText("|cff5E3E1C" .. subText .. "|r")
        row.st:Show()
    else
        row.st:Hide()
    end

    if rightText then
        row.rt:SetText("|cff5E3E1C" .. rightText .. "|r")
        row.rt:Show()
    else
        row.rt:Hide()
    end

    row:Show()
    return row, y - (subText and 43 or 36)
end

function UI:RenderChapter(action)
    local f = self.mainFrame
    if not f then return end

    self.activeAction = action
    self:SetRightPageHeader(action)

    -- Buchseite dieses Kapitels aktivieren (vorherige ausblenden)
    local page = self:GetChapterPage(action)
    if f.scrollChild and f.scrollChild ~= page then f.scrollChild:Hide() end
    f.scrollChild = page
    f.scrollFrame:SetScrollChild(page)
    page:Show()

    -- CACHE: nur neu bauen, wenn Daten sich seit dem letzten Build geaendert haben.
    -- Sonst wird die fertige Seite einfach wieder gezeigt (0 neue Frames).
    if page._builtGen == UI._contentGen then
        if f.scrollFrame then f.scrollFrame:SetVerticalScroll(0) end
        return
    end

    self:ClearScrollChild()

    if action == "chronicle" then
        self:RenderMemorial()   -- Chronik zusammengefuehrt mit Gedenkhalle
    elseif action == "halloffame" then
        self:RenderHallOfFame()
    elseif action == "statistics" then
        self:RenderStatistics()
    elseif action == "milestones" then
        self:RenderMilestones()
    elseif action == "professions" then
        self:RenderProfessions()
    elseif action == "guildmap" then
        self:RenderGuildMap()
    elseif action == "titles" then
        self:RenderTitles()
    elseif action == "adventurers" then
        self:RenderAdventurers()
    elseif action == "memorial" then
        self:RenderMemorial()
    elseif action == "condolences" then
        self:RenderCondolences()
    elseif action == "bulletin" then
        self:RenderBulletin()
    elseif action == "guildstats" then
        self:RenderGuildStats()
    elseif action == "guildrules" then
        self:RenderGuildRules()
    elseif action == "calendar" then
        self:RenderCalendar()
    elseif action == "guildcompetition" then
        self:RenderGuildCompetition()
    elseif action == "achievements" then
        self:RenderAchievements()
    elseif action == "supporter" then
        self:RenderSupporterPage()
    elseif action == "survivals" then
        self:RenderSurvivalStreak()
    elseif action == "deathmap" then
        self:RenderDeathmap()
    end

    page._builtGen = UI._contentGen
    if f.scrollFrame then f.scrollFrame:SetVerticalScroll(0) end
end

-- ── SURVIVAL STREAK BUCHSEITE ─────────────────────────────────────────────
function UI:RenderSurvivalStreak()
    local parent = self.mainFrame.scrollChild
    local SS = GDL:GetModule("SurvivalStreak")
    if SS and SS.RenderBookPage then
        SS:RenderBookPage(parent, 0)
        return
    end
    -- Fallback wenn Modul nicht geladen
    local txt = parent:CreateFontString(nil, "OVERLAY")
    txt:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    txt:SetTextColor(0.28, 0.20, 0.10)
    txt:SetPoint("TOPLEFT", 20, -20)
    txt:SetText("|cff5E3E1C" .. GDL:T("SurvivalStreak-Modul wird geladen...") .. "|r")
    parent:SetHeight(40)
end

-- ── KARTE DER GEFALLENEN BUCHSEITE ───────────────────────────────────────────
function UI:RenderDeathmap()
    local parent = self.mainFrame.scrollChild
    local y      = 0

    -- Titel
    local title = parent:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 18, "")
    title:SetTextColor(0.32, 0.22, 0.08)
    title:SetShadowOffset(1, -1); title:SetShadowColor(0,0,0,0.8)
    title:SetPoint("TOPLEFT", 20, y)
    title:SetText(GDL:T("Karte der Gefallenen"))
    y = y - 36

    -- Beschreibung
    local desc = parent:CreateFontString(nil, "OVERLAY")
    desc:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    desc:SetTextColor(0.26, 0.18, 0.08)
    desc:SetPoint("TOPLEFT", 20, y); desc:SetWidth(310)
    desc:SetJustifyH("LEFT"); desc:SetSpacing(3)
    desc:SetText(GDL:T("Oeffne die Weltkarte um Todesorte als farbige Cluster-Pins zu sehen. " ..
                 "Einzelne Tode sind als Skulls markiert (MapMarkers). " ..
                 "Cluster zeigen 2 oder mehr Tode in unmittelbarer Naehe."))
    y = y - desc:GetHeight() - 16

    -- Statistiken aus der GILDEN-DB (Tode liegen unter guilds[name].deaths,
    -- NICHT direkt unter GuildDeathLogDB.deaths -> das war immer nil = leeres Kapitel)
    local gd = GDL:GetGuildData()
    local withCoords, total = 0, 0
    local zoneStats = {}
    if gd and gd.deaths then
        total = #gd.deaths
        for _, d in ipairs(gd.deaths) do
            if d.coordX and d.coordY then
                withCoords = withCoords + 1
                local z = d.zone or "?"
                zoneStats[z] = (zoneStats[z] or 0) + 1
            end
        end
    end

    local stat1 = parent:CreateFontString(nil, "OVERLAY")
    stat1:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    stat1:SetTextColor(0.16, 0.42, 0.16)
    stat1:SetPoint("TOPLEFT", 20, y)
    stat1:SetText(string.format(GDL:T("|cff8A5E14%d|r von |cff8A5E14%d|r Toden haben Koordinaten"), withCoords, total))
    y = y - 22

    -- Top Zonen
    local sorted = {}
    for zone, count in pairs(zoneStats) do table.insert(sorted, {zone=zone, count=count}) end
    table.sort(sorted, function(a,b) return a.count > b.count end)

    if #sorted > 0 then
        local dangerTitle = parent:CreateFontString(nil, "OVERLAY")
        dangerTitle:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
        dangerTitle:SetTextColor(0.45, 0.15, 0.06)
        dangerTitle:SetPoint("TOPLEFT", 20, y - 6)
        dangerTitle:SetText(GDL:T("Gefaehrlichste Gebiete:"))
        y = y - 26

        local colorThresh = {{25,"|cff7A2C14"},{10,"|cff7A3828"},{5,"|cff7A3A08"},{1,"|cff8A5E14"}}
        for i = 1, math.min(8, #sorted) do
            local e = sorted[i]
            local col = "|cff8A5E14"
            for _, ct in ipairs(colorThresh) do
                if e.count >= ct[1] then col = ct[2]; break end
            end
            local row = parent:CreateFontString(nil, "OVERLAY")
            row:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
            row:SetTextColor(0.18, 0.12, 0.05)
            row:SetPoint("TOPLEFT", 30, y)
            row:SetText(string.format(GDL:T("%d. |cff2A1606%-22s|r %s%d Tode|r"), i, e.zone:sub(1,22), col, e.count))
            y = y - 18
        end
    else
        local noData = parent:CreateFontString(nil, "OVERLAY")
        noData:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        noData:SetTextColor(0.5,0.5,0.5)
        noData:SetPoint("TOPLEFT", 20, y)
        noData:SetText("|cff666666" .. GDL:T("Noch keine Koordinaten-Daten vorhanden.") .. "|r")
        y = y - 20
    end

    y = y - 12

    -- Heatmap Toggle Button
    local DH = GDL:GetModule("DeathHeatmap")
    if DH then
        local isOn = DH:IsEnabled()
        local btn  = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        btn:SetSize(200, 26)
        btn:SetPoint("TOPLEFT", 20, y)
        btn:SetText(isOn and GDL:T("Heatmap deaktivieren") or GDL:T("Heatmap aktivieren"))
        btn:SetScript("OnClick", function(self)
            DH:Toggle()
            self:SetText(DH:IsEnabled() and GDL:T("Heatmap deaktivieren") or GDL:T("Heatmap aktivieren"))
        end)
        y = y - 36

        -- Status
        local status = parent:CreateFontString(nil, "OVERLAY")
        status:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        status:SetTextColor(0.25, 0.2, 0.12)
        status:SetPoint("TOPLEFT", 20, y)
        status:SetText(isOn
            and GDL:T("|cff1A5A24Heatmap AKTIV|r — Oeffne die Weltkarte")
            or  GDL:T("|cff5E3E1CHeatmap inaktiv|r"))
        y = y - 20
    end

    y = y - 10
    local sep = parent:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    sep:SetSize(310, 6)
    sep:SetPoint("TOPLEFT", 10, y)
    sep:SetVertexColor(0.5, 0.4, 0.2, 0.35)
    y = y - 18

    -- Hinweis
    local hint = parent:CreateFontString(nil, "OVERLAY")
    hint:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    hint:SetTextColor(0.55, 0.50, 0.40)
    hint:SetPoint("TOPLEFT", 20, y)
    hint:SetWidth(310)
    hint:SetJustifyH("LEFT")
    hint:SetText(GDL:T("Tipp: Koordinaten werden nur mit der DeathNotificationLib (DNL) " ..
                 "oder dem eigenen Tod erfasst. Aeltere Eintraege haben keine Koordinaten."))
    y = y - hint:GetHeight() - 10

    parent:SetHeight(math.abs(y) + 20)
end


-- ══════════════════════════════════════════════════════════════
-- SHOW / HIDE BOOK
-- ══════════════════════════════════════════════════════════════

function UI:ShowBook()
    if not self.mainFrame then return end
    local f = self.mainFrame
    if f:IsShown() then f:Raise(); return end

    -- Frische Daten bei jedem Oeffnen: Cache invalidieren (erster Blick auf ein
    -- Kapitel baut neu auf, danach ist Hin-/Herschalten gratis).
    self:InvalidateChapters()

    -- Stop any running close animation or tween
    if f.closeAnim and f.closeAnim:IsPlaying() then f.closeAnim:Stop() end
    if f.openAnim  and f.openAnim:IsPlaying()  then f.openAnim:Stop()  end
    f:SetScript("OnUpdate", nil)

    local savedScale = (GuildDeathLogDB and GuildDeathLogDB.bookScale) or 0.85
    local savedPos   = GuildDeathLogDB and GuildDeathLogDB.bookPosition
    if savedPos and savedPos.point then
        f:ClearAllPoints()
        f:SetPoint(savedPos.point, UIParent, savedPos.relPoint, savedPos.x, savedPos.y)
    else
        f:ClearAllPoints()
        f:SetPoint("CENTER", UIParent, "CENTER", 0, 40)
    end

    -- ── DRAMATISCHES EINBLENDEN: Winzig → Überschießen → Fertig ──────────
    -- Phase 1  0.00 – 0.42s   scale 0.06 → 1.08  (kubisch ease-out + alpha 0→1)
    -- Phase 2  0.42 – 0.54s   scale 1.08 → 1.00  (einfedern)
    f:SetAlpha(0)
    f:SetScale(savedScale * 0.06)
    f:Show()

    local el = 0
    local P1, P2 = 0.42, 0.12
    local math_min3 = math.min
    f:SetScript("OnUpdate", function(self, dt)
        el = el + dt
        if el <= P1 then
            local p = el / P1
            local e = 1 - (1 - p) * (1 - p) * (1 - p)   -- cubic ease-out
            self:SetScale(savedScale * (0.06 + e * 1.02))
            self:SetAlpha(math_min3(1, e * 1.7))
        elseif el <= P1 + P2 then
            local p = (el - P1) / P2
            self:SetScale(savedScale * (1.08 - p * 0.08))
            self:SetAlpha(1)
        else
            self:SetScript("OnUpdate", nil)
            self:SetScale(savedScale)
            self:SetAlpha(1)
        end
    end)

    -- ── CINEMATIC EFFEKTE ─────────────────────────────────────────────────
    local FX = GDL:GetModule("AnimFX")
    if FX then
        -- t=0.05: BookOpenEffect startet (6-phasige Sequenz)
        C_Timer.After(0.05, function()
            if f:IsShown() then FX.BookOpenEffect(f) end
        end)
        -- t=0.55: Persistente Ambience (Ember + Atem-Leuchten)
        C_Timer.After(0.55, function()
            if not f:IsShown() then return end
            if self._emberHandle  then self._emberHandle.active = false  end
            -- BreathGlow entfernt: der riesige ADD-Starburst hinter dem
            -- fotorealistischen Buch war permanenter GPU-Overdraw und
            -- optisch kaum noch wahrnehmbar. Embers reichen als Ambience.
            self._emberHandle = FX.EmberAmbience(f, 10, 1.0, 0.45, 0.06)
        end)
    end
end

function UI:HideBook()
    if not self.mainFrame then return end
    local f = self.mainFrame
    if not f:IsShown() then return end

    -- Ambience sofort stoppen
    if self._emberHandle   then self._emberHandle.active = false; self._emberHandle   = nil end
    if self._breathGlowAG  then self._breathGlowAG:Stop();       self._breathGlowAG  = nil end
    if self._breathGlow    then self._breathGlow:Hide();          self._breathGlow    = nil end
    if f.openAnim  and f.openAnim:IsPlaying()  then f.openAnim:Stop()  end
    if f.closeAnim and f.closeAnim:IsPlaying() then f.closeAnim:Stop() end
    f:SetScript("OnUpdate", nil)

    -- Flash nach außen beim Schließen
    local FX = GDL:GetModule("AnimFX")
    if FX then
        FX.GlowRing(f, 0.88, 0.68, 0.20, (f:GetWidth() or 800) * 0.90, 0.78)
        FX.ScreenFlash(0.65, 0.48, 0.12, 0.28, 0.14)
    end

    -- Schließ-Tween: kurzes Aufpusten → Zusammenfalten → Hide
    local savedScale = (GuildDeathLogDB and GuildDeathLogDB.bookScale) or 0.85
    local el = 0
    local PUFF  = 0.07   -- kurzes Aufpusten
    local CLOSE = 0.26   -- Zusammenfallen
    f:SetScript("OnUpdate", function(self, dt)
        el = el + dt
        if el <= PUFF then
            local p = el / PUFF
            self:SetScale(savedScale * (1.0 + p * 0.05))
            self:SetAlpha(1)
        elseif el <= PUFF + CLOSE then
            local p = (el - PUFF) / CLOSE
            local e = p * p * p           -- cubic ease-in
            self:SetScale(savedScale * (1.05 - e * 1.01))
            self:SetAlpha(1 - p * p)
        else
            self:SetScript("OnUpdate", nil)
            self:Hide()
            self:SetAlpha(1)
            self:SetScale(savedScale)
        end
    end)
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- CHRONIK: TAG-TRENNER (Narcissus / DialogueUI Immersion)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI.CountDeathsOnDay(sortedDeaths, startIdx)
    if not sortedDeaths[startIdx] then return 0 end
    local refDay = math.floor((sortedDeaths[startIdx].timestamp or 0) / 86400)
    local count  = 0
    for i = startIdx, #sortedDeaths do
        if math.floor((sortedDeaths[i].timestamp or 0) / 86400) == refDay then
            count = count + 1
        else
            break
        end
    end
    return count
end

-- Expose UI table for sub-files (UIChapters, UIOverlay, UIToasts, UIWindows)
addon._UI = UI

GDL:RegisterModule("UI", UI)
