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

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

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"
}

local overlayQueue = {}
local isShowingOverlay = false
local achToastQueue  = {}
local 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)
local function GDL_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

local function 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, 520)
    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\\Book")

    -- Schliessen-Button (oben rechts, dezent)
    local closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -22, -22)
    closeBtn:SetScript("OnClick", function() UI:HideBook() end)

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- LINKE SEITE: INHALTSVERZEICHNIS
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local leftPage = CreateFrame("Frame", nil, f)
    leftPage:SetPoint("TOPLEFT", 40, -38)
    leftPage:SetSize(365, 465)

    -- Titel (zentriert, kein Icon-Overlay)
    local title = leftPage:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 18, "")
    title:SetPoint("TOP", 0, -18)
    title:SetText("|cffD4AA5EDas Buch der Gefallenen|r")
    title:SetShadowOffset(1, -1)
    title:SetShadowColor(0, 0, 0, 0.15)

    -- Dekorative Striche links/rechts vom Titel
    local titLL = leftPage:CreateTexture(nil, "ARTWORK")
    titLL:SetSize(34, 1)
    titLL:SetPoint("RIGHT", title, "LEFT", -5, 1)
    titLL:SetColorTexture(0.55, 0.42, 0.18, 0.55)
    local titLR = leftPage:CreateTexture(nil, "ARTWORK")
    titLR:SetSize(34, 1)
    titLR:SetPoint("LEFT", title, "RIGHT", 5, 1)
    titLR:SetColorTexture(0.55, 0.42, 0.18, 0.55)

    f.guildName = leftPage:CreateFontString(nil, "OVERLAY")
    f.guildName:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    f.guildName:SetPoint("TOP", title, "BOTTOM", 0, -2)
    f.guildName:SetTextColor(0.75, 0.55, 0.30)
    f.guildName:SetShadowOffset(1, -1)
    f.guildName:SetShadowColor(0, 0, 0, 0.12)

    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 = {
        { isCat=true,  label="Tode & Ruhm",  r=0.75, g=0.18, b=0.15 },
        { num="I",    title="Chronik der Gefallenen",  icon="Interface\\Icons\\Spell_Shadow_DeathPact",       action="chronicle",    desc="Alle verzeichneten Tode der Gilde" },
        { num="II",   title="Ruhmeshalle",              icon="Interface\\Icons\\Spell_Holy_AuraOfLight",       action="halloffame",   desc="Ueberlebende Helden der Gilde" },
        { num="III",  title="Statistiken",              icon="Interface\\Icons\\INV_Misc_Book_07",             action="statistics",   desc="Klassen- und Zonen-Auswertungen" },
        { isCat=true,  label="Fortschritt",  r=0.25, g=0.60, b=0.20 },
        { num="IV",   title="Meilensteine",             icon="Interface\\Icons\\Spell_Holy_PowerWordShield",   action="milestones",   desc="Errungene Meilensteine und Fortschritt" },
        { num="V",    title="Berufe der Gilde",         icon="Interface\\Icons\\INV_Pick_02",                 action="professions",  desc="Handwerk und Berufe der Mitglieder" },
        { num="VI",   title="Todeskarte",               icon="Interface\\Icons\\INV_Misc_Map_01",             action="guildmap",     desc="Gefahrenzonen auf der Karte" },
        { isCat=true,  label="Gilde",        r=0.28, g=0.48, b=0.82 },
        { num="VII",  title="Titel & Raenge",           icon="Interface\\Icons\\INV_Crown_01",                action="titles",       desc="Gildentitel und Rang-System" },
        { num="VIII", title="Gedenkhalle",              icon="Interface\\Icons\\Spell_Holy_SealOfSacrifice",  action="memorial",     desc="Gedenkstaette fuer Gefallene" },
        { num="IX",   title="Gilden-Statistiken",       icon="Interface\\Icons\\INV_Misc_GroupNeedMore",      action="guildstats",   desc="Aktivitaet und Wachstum der Gilde" },
        { num="X",    title="Gilden-Regeln",            icon="Interface\\Icons\\INV_Misc_Book_11",            action="guildrules",   desc="Regeln und Verhaltenskodex" },
        { num="XI",   title="Kalender",                 icon="Interface\\Icons\\INV_Misc_PocketWatch_01",     action="calendar",     desc="Bevorstehende Gilden-Ereignisse" },
        { isCat=true,  label="Jagd & Kampf", r=0.82, g=0.48, b=0.10 },
        { num="XII",  title="Kill-Statistiken",         icon="Interface\\Icons\\Ability_DualWield",           action="killstats",    desc="Toetungs-Bestenliste und Monsterjagd" },
        { num="XIII", title="Errungenschaften",         icon="Interface\\Icons\\Spell_Holy_Perseverance",     action="achievements", desc="Gilden- und Charakter-Errungenschaften" },
    }

    f.chapterEntries  = {}
    f.chapterByAction = {}
    local chY      = -58
    local entryIdx = 0

    for _, item in ipairs(tableOfContents) do
        if item.isCat then
            chY = chY - 3
            local cl = leftPage:CreateTexture(nil, "ARTWORK")
            cl:SetSize(340, 1); cl:SetPoint("TOPLEFT", 5, chY)
            cl:SetColorTexture(item.r, item.g, item.b, 0.28)
            chY = chY - 2
            local catLbl = leftPage:CreateFontString(nil, "OVERLAY")
            catLbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            catLbl:SetPoint("TOPLEFT", 8, chY)
            catLbl:SetShadowOffset(1, -1); catLbl:SetShadowColor(0, 0, 0, 0.15)
            catLbl:SetText(string.format("|cff%02x%02x%02x%s|r",
                math.floor(item.r*255), math.floor(item.g*255), math.floor(item.b*255), item.label))
            chY = chY - 12
        else
            entryIdx = entryIdx + 1
            local ch = item

            local entry = CreateFrame("Button", nil, leftPage)
            entry:SetSize(345, 22)
            entry:SetPoint("TOPLEFT", 5, chY)

            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", 11, "")
            numTxt:SetPoint("LEFT", 4, 0)
            numTxt:SetText("|cff8A6848" .. ch.num .. ".|r")
            numTxt:SetWidth(34); numTxt:SetJustifyH("RIGHT")
            numTxt:SetShadowOffset(1, -1); numTxt:SetShadowColor(0, 0, 0, 0.12)

            local icon = entry:CreateTexture(nil, "ARTWORK")
            icon:SetTexture(ch.icon); icon:SetSize(18, 18)
            icon:SetPoint("LEFT", numTxt, "RIGHT", 5, 0)

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

            local dots = entry:CreateTexture(nil, "ARTWORK")
            dots:SetPoint("LEFT", titleTxt, "RIGHT", 4, 0)
            dots:SetPoint("RIGHT", entry, "RIGHT", -4, 0)
            dots:SetHeight(1); dots:SetColorTexture(0.45, 0.35, 0.25, 0.2)

            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("|cffF0C060" .. 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(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("|cff583020" .. 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)
                GDL_ScaleLerp(self, 0.94, 1.0, 0.14, "OUT")
                GameTooltip:Hide()
                UI:HandleChapterClick(curIdx, ch.action)
            end)

            f.chapterEntries[entryIdx]   = entry
            f.chapterByAction[ch.action] = entryIdx
            chY = chY - 22
        end
    end

    local footOrnL = leftPage:CreateTexture(nil, "ARTWORK")
    footOrnL:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    footOrnL:SetSize(250, 8)
    footOrnL:SetPoint("BOTTOM", 0, 44)
    footOrnL:SetVertexColor(0.4, 0.3, 0.2, 0.3)

    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)
    footQuoteL:SetText("|cff583020\"Kein Tod ist umsonst, wenn man sich erinnert.\"|r")
    f.quote = footQuoteL

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- RECHTE SEITE: DYNAMISCHER CONTENT-BEREICH
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local rightPage = CreateFrame("Frame", nil, f)
    rightPage:SetPoint("TOPRIGHT", -40, -38)
    rightPage:SetSize(365, 465)
    f.rightPage = rightPage

    f.rightTitle = rightPage:CreateFontString(nil, "OVERLAY")
    f.rightTitle:SetFont("Fonts\\MORPHEUS.TTF", 16, "")
    f.rightTitle:SetPoint("TOP", 0, -20)
    f.rightTitle:SetText("|cffD4AA5EChronik der Gefallenen|r")
    f.rightTitle:SetShadowOffset(1, -1)
    f.rightTitle:SetShadowColor(0, 0, 0, 0.15)

    -- 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.65, 0.50, 0.18, 0.70)
    local ornRight = rightPage:CreateTexture(nil, "ARTWORK")
    ornRight:SetSize(55, 1); ornRight:SetPoint("TOPRIGHT", -10, -22)
    ornRight:SetColorTexture(0.65, 0.50, 0.18, 0.70)
    -- Kleine Rauten-Ornamente an den Enden
    local ornDotL = rightPage:CreateTexture(nil, "ARTWORK")
    ornDotL:SetSize(6, 6); ornDotL:SetPoint("TOPLEFT", 10, -20)
    ornDotL:SetColorTexture(0.82, 0.62, 0.20, 0.85)
    local ornDotR = rightPage:CreateTexture(nil, "ARTWORK")
    ornDotR:SetSize(6, 6); ornDotR:SetPoint("TOPRIGHT", -10, -20)
    ornDotR:SetColorTexture(0.82, 0.62, 0.20, 0.85)

    f.rightSubtitle = rightPage:CreateFontString(nil, "OVERLAY")
    f.rightSubtitle:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    f.rightSubtitle:SetPoint("TOP", f.rightTitle, "BOTTOM", 0, -1)
    f.rightSubtitle:SetTextColor(0.68, 0.52, 0.32)
    f.rightSubtitle:SetText("")

    -- Stats-Bar (nur fuer Chronik sichtbar)
    local statsFrame = CreateFrame("Frame", nil, rightPage)
    statsFrame:SetPoint("TOP", f.rightSubtitle, "BOTTOM", 0, -2)
    statsFrame:SetSize(340, 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
        scrollBar:SetPoint("TOPLEFT", scroll, "TOPRIGHT", 1, -16)
        scrollBar:SetPoint("BOTTOMLEFT", scroll, "BOTTOMRIGHT", 1, 16)
    end

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

    -- Icon-Toolbar (dezent am unteren rechten Rand des Buches)
    local iconSize = 22
    local iconPad = 5
    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("chronicle") 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 = "GuildDeathLog 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, rightPage)
        ib:SetSize(iconSize, iconSize)
        ib:SetPoint("BOTTOMRIGHT", -(5 + (i - 1) * (iconSize + iconPad)), 10)

        -- Hintergrund-Glow
        local ibBg = ib:CreateTexture(nil, "BACKGROUND")
        ibBg:SetAllPoints(); ibBg:SetColorTexture(0.7, 0.52, 0.14, 0)
        -- Icon
        ib.icon = ib:CreateTexture(nil, "ARTWORK")
        ib.icon:SetAllPoints(); ib.icon:SetTexture(ti.tex); ib.icon:SetAlpha(0.68)
        -- Glanz-Overlay
        local ibSheen = ib:CreateTexture(nil, "OVERLAY")
        ibSheen:SetAllPoints()
        ibSheen:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
        ibSheen:SetBlendMode("ADD"); ibSheen:SetVertexColor(0.9, 0.7, 0.2, 0)

        ib:SetScript("OnEnter", function(self)
            self.icon:SetAlpha(1.0)
            ibBg:SetColorTexture(0.7, 0.52, 0.14, 0.28)
            ibSheen:SetVertexColor(0.9, 0.7, 0.2, 0.45)
            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(ti.desc, 0.75, 0.65, 0.45, true) end
            GameTooltip:Show()
        end)
        ib:SetScript("OnLeave", function(self)
            self.icon:SetAlpha(0.68)
            ibBg:SetColorTexture(0.7, 0.52, 0.14, 0)
            ibSheen:SetVertexColor(0.9, 0.7, 0.2, 0)
            GameTooltip:Hide()
        end)
        ib:SetScript("OnMouseDown", function(self) self.icon:SetAlpha(0.85); ibBg:SetColorTexture(0.55, 0.40, 0.10, 0.42) end)
        ib:SetScript("OnMouseUp",   function(self) self.icon:SetAlpha(1.0);  ibBg:SetColorTexture(0.7,  0.52, 0.14, 0.28) end)
        ib:SetScript("OnClick", ti.fn)
    end

    -- 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(1.0) 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(1.0) 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 1.0
        self:SetScale(savedScale)
        local lastIdx    = GuildDeathLogDB and GuildDeathLogDB.lastChapter or 1
        local lastAction = GuildDeathLogDB and GuildDeathLogDB.lastAction  or "chronicle"
        UI:SetActiveChapter(lastIdx)
        UI:RenderChapter(lastAction)
    end)

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

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- BUCH-ATMOSPHÄRE: Staub-Motes + Dekoration
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
function UI:AddBookAtmosphere(f)
    local W, H = 900, 520

    -- â”€â”€ Mittel-Falz Schatten (linke/rechte Seite Trennung) â”€â”€â”€â”€
    local spine = f:CreateTexture(nil, "OVERLAY")
    spine:SetSize(2, H - 20)
    spine:SetPoint("TOP", f, "TOP", 0, -10)
    spine:SetColorTexture(0.22, 0.13, 0.05, 0.13)

    -- â”€â”€ Staubmoten: träge treibende Partikel â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local MOTES = 9
    local motes = {}
    local t0    = GetTime()

    for i = 1, MOTES do
        local m = f:CreateTexture(nil, "ARTWORK")
        m:SetSize(2, 2)
        m:SetColorTexture(0.95, 0.88, 0.60, 0)
        local mx = math.random(55, W - 55)
        local my = -math.random(30, H - 30)
        m:SetPoint("TOPLEFT", f, "TOPLEFT", mx, my)
        motes[i] = {
            tex   = m,
            x     = mx,
            baseY = my,
            speed = 0.6 + math.random() * 1.0,
            swing = math.random(3, 9),
            phase = math.random() * 6.28,
            alpha = 0.12 + math.random() * 0.22,
            offset= math.random() * 10,
            cycle = 9 + math.random() * 5,
        }
    end

    local ticker = CreateFrame("Frame")
    ticker:SetScript("OnUpdate", function()
        if not f:IsShown() then return end
        local t = GetTime() - t0
        for _, mm in ipairs(motes) do
            local age  = (t + mm.offset) % mm.cycle
            local ny   = mm.baseY + age * mm.speed * 6
            local nx   = mm.x + math.sin(age * 0.7 + mm.phase) * mm.swing
            local frac = age / mm.cycle
            local a
            if     frac < 0.18 then a = mm.alpha * (frac / 0.18)
            elseif frac < 0.78 then a = mm.alpha
            else                    a = mm.alpha * ((1 - frac) / 0.22)
            end
            mm.tex:ClearAllPoints()
            mm.tex:SetPoint("TOPLEFT", f, "TOPLEFT", nx, ny)
            mm.tex:SetAlpha(a)
        end
    end)
    f._moteTicker = ticker
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.5, 0.35, 0.15)
    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.35, 0.28, 0.2)
    labelText:SetText(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.72, 0.52, 0.22)
            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.5, 0.35, 0.15)
            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.15, 0.12, 0.08, 0.9)
    btn:SetBackdropBorderColor(0.5, 0.4, 0.3, 0.8)
    
    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.9, 0.8, 0.6)
    btn.label = label
    
    btn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.25, 0.2, 0.12, 1)
        self:SetBackdropBorderColor(0.7, 0.6, 0.4, 1)
        self.label:SetTextColor(1, 0.9, 0.7)
    end)
    
    btn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.15, 0.12, 0.08, 0.9)
        self:SetBackdropBorderColor(0.5, 0.4, 0.3, 0.8)
        self.label:SetTextColor(0.9, 0.8, 0.6)
    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("|cffC8A050" .. entry.chapterData.title .. "|r")
            entry.numTxt:SetText("|cffC8A050" .. 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("|cff583020" .. entry.chapterData.title .. "|r")
            entry.numTxt:SetText("|cff8A6848" .. 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
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

local CHAPTER_TITLES = {
    chronicle   = "Chronik der Gefallenen",
    halloffame  = "Ruhmeshalle",
    statistics  = "Statistiken des Todes",
    milestones  = "Meilensteine & Erfolge",
    professions = "Berufe der Gilde",
    guildmap    = "Abenteurer der Gilde",
    titles      = "Titel & Raenge",
    memorial    = "Gedenkhalle",
    guildstats  = "Gilden-Statistiken",
    guildrules  = "Gilden-Regeln",
    calendar    = "Gilden-Kalender",
    killstats   = "Kill-Statistiken",
    achievements = "Errungenschaften",
    supporter   = "Supporter+",
}

function UI:ClearScrollChild()
    local f = self.mainFrame
    if not f or not f.scrollChild then return end
    for _, c in ipairs({f.scrollChild:GetChildren()}) do c:Hide() end
    local regions = {f.scrollChild:GetRegions()}
    for _, r in ipairs(regions) do r:Hide() end
end

function UI:SetRightPageHeader(action)
    local f = self.mainFrame
    if not f then return end
    f.rightTitle:SetText("|cffD4AA5E" .. (CHAPTER_TITLES[action] or "Kapitel") .. "|r")
    f.rightSubtitle:SetText("")
    local isChronicle = (action == "chronicle")
    if f.statsFrame then
        if isChronicle then f.statsFrame:Show() else f.statsFrame:Hide() end
    end
    if f.divider then
        if isChronicle then f.divider:Show() else f.divider:Hide() end
    end
    f.scrollFrame:SetPoint("TOPLEFT", 5, isChronicle and -86 or -48)
end

function UI:MakeBookLine(parent, y, text, size, color)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    local sz = size or 10
    local fontFile = sz >= 14 and "Fonts\\MORPHEUS.TTF" or "Fonts\\FRIZQT__.TTF"
    fs:SetFont(fontFile, sz, "")
    fs:SetPoint("TOPLEFT", 8, y)
    fs:SetWidth(300)
    fs:SetJustifyH("LEFT")
    fs:SetWordWrap(true)
    local defaultColor = sz >= 14 and "|cffD4AA5E" or "|cff6A4020"
    fs:SetText((color or defaultColor) .. text .. "|r")
    fs:SetShadowOffset(1, -1); fs:SetShadowColor(0, 0, 0, 0.15)
    local textHeight = fs:GetStringHeight() or (sz + 4)
    return fs, y - textHeight - 4
end

function UI:MakeBookRow(parent, y, icon, mainText, subText, rightText)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(310, subText and 32 or 26)
    row:SetPoint("TOPLEFT", 4, y)

    local rowBg = row:CreateTexture(nil, "BACKGROUND")
    rowBg:SetAllPoints()
    rowBg:SetColorTexture(0.05, 0.03, 0.01, 0.30)

    local topRule = row:CreateTexture(nil, "ARTWORK")
    topRule:SetHeight(1); topRule:SetPoint("TOPLEFT"); topRule:SetPoint("TOPRIGHT")
    topRule:SetColorTexture(0.45, 0.32, 0.10, 0.18)

    if icon then
        local ic = row:CreateTexture(nil, "ARTWORK")
        ic:SetTexture(icon)
        ic:SetSize(20, 20)
        ic:SetPoint("LEFT", 4, 0)
    end

    local mt = row:CreateFontString(nil, "OVERLAY")
    mt:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    mt:SetPoint("LEFT", icon and 30 or 6, subText and 6 or 0)
    mt:SetShadowOffset(1, -1); mt:SetShadowColor(0, 0, 0, 0.15)
    mt:SetText(mainText or "")

    if subText then
        local st = row:CreateFontString(nil, "OVERLAY")
        st:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        st:SetPoint("TOPLEFT", mt, "BOTTOMLEFT", 0, -1)
        st:SetShadowOffset(1, -1); st:SetShadowColor(0, 0, 0, 0.15)
        st:SetText("|cff7A5840" .. subText .. "|r")
    end

    if rightText then
        local rt = row:CreateFontString(nil, "OVERLAY")
        rt:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        rt:SetPoint("RIGHT", -6, 0)
        rt:SetShadowOffset(1, -1); rt:SetShadowColor(0, 0, 0, 0.15)
        rt:SetText("|cff785030" .. rightText .. "|r")
    end

    return row, y - (subText and 34 or 28)
end

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

    self:ClearScrollChild()
    self:SetRightPageHeader(action)

    if action == "chronicle" then
        self:UpdateChronicle()
    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 == "memorial" then
        self:RenderMemorial()
    elseif action == "guildstats" then
        self:RenderGuildStats()
    elseif action == "guildrules" then
        self:RenderGuildRules()
    elseif action == "calendar" then
        self:RenderCalendar()
    elseif action == "killstats" then
        self:RenderKillStats()
    elseif action == "achievements" then
        self:RenderAchievements()
    elseif action == "supporter" then
        self:RenderSupporterPage()
    end

    if f.scrollFrame then f.scrollFrame:SetVerticalScroll(0) end
end

-- â•â•â• II. RUHMESHALLE â•â•â•
function UI:RenderHallOfFame()
    local parent = self.mainFrame.scrollChild
    local HoF = GDL:GetModule("HallOfFame")
    if not HoF then return end
    local survivors = HoF:GetSurvivors()
    local y = 0

    -- â”€â”€ Banner â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local banner = CreateFrame("Frame", nil, parent)
    banner:SetSize(330, 32)
    banner:SetPoint("TOPLEFT", 0, y)
    local bannerBg = banner:CreateTexture(nil, "BACKGROUND")
    bannerBg:SetAllPoints()
    bannerBg:SetColorTexture(0.14, 0.10, 0.02, 0.40)
    local bannerIcon = banner:CreateTexture(nil, "ARTWORK")
    bannerIcon:SetTexture("Interface\\Icons\\INV_Shield_06")
    bannerIcon:SetSize(22, 22)
    bannerIcon:SetPoint("LEFT", 6, 0)
    local bannerLbl = banner:CreateFontString(nil, "OVERLAY")
    bannerLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    bannerLbl:SetPoint("LEFT", 34, 1)
    bannerLbl:SetShadowOffset(1, -1)
    bannerLbl:SetShadowColor(0, 0, 0, 0.15)
    bannerLbl:SetText("|cffDDB060Unsterbliche:|r  |cffFFD100" .. #survivors .. "|r  |cff888888Krieger des Lichts|r")
    y = y - 38

    local div = parent:CreateTexture(nil, "ARTWORK")
    div:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    div:SetSize(330, 6)
    div:SetPoint("TOPLEFT", 0, y + 2)
    div:SetVertexColor(0.55, 0.42, 0.14, 0.5)
    y = y - 12

    if #survivors == 0 then
        local empty = parent:CreateFontString(nil, "OVERLAY")
        empty:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        empty:SetPoint("TOPLEFT", 0, y)
        empty:SetText("|cff3A5A30Noch keine Unsterblichen in der Gilde.|r")
        y = y - 30
    else
        for rank, s in ipairs(survivors) do
            y = self:RenderSurvivorRow(parent, y, s, rank)
        end
    end

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

function UI:RenderSurvivorRow(parent, y, s, rank)
    local ROW_H = 46
    local col = CLASS_COLORS[s.class] or CLASS_COLORS[s.classId] or {0.8, 0.7, 0.4}
    local classHex = type(col) == "table"
        and string.format("ff%02x%02x%02x", math.floor(col[1]*255), math.floor(col[2]*255), math.floor(col[3]*255))
        or "ffCCAA66"
    local classIcon = CLASS_ICONS[s.class] or CLASS_ICONS[s.classId] or "Interface\\Icons\\INV_Misc_QuestionMark"

    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(326, ROW_H)
    row:SetPoint("TOPLEFT", 4, y)

    -- Hintergrund
    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.18, 0.13, 0.04, rank % 2 == 0 and 0.30 or 0.18)

    -- Trennlinie
    local sep = row:CreateTexture(nil, "BORDER")
    sep:SetSize(316, 1)
    sep:SetPoint("BOTTOMLEFT", 5, 0)
    sep:SetColorTexture(0.45, 0.35, 0.12, 0.30)

    -- Rang-Badge links
    local rankBg = row:CreateTexture(nil, "ARTWORK")
    rankBg:SetSize(22, 22)
    rankBg:SetPoint("LEFT", 4, 0)
    if rank == 1 then
        rankBg:SetColorTexture(0.72, 0.58, 0.08, 0.70)
    elseif rank == 2 then
        rankBg:SetColorTexture(0.55, 0.55, 0.58, 0.60)
    elseif rank == 3 then
        rankBg:SetColorTexture(0.50, 0.28, 0.10, 0.60)
    else
        rankBg:SetColorTexture(0.20, 0.16, 0.08, 0.45)
    end
    local rankStr = row:CreateFontString(nil, "OVERLAY")
    rankStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    rankStr:SetPoint("CENTER", rankBg, "CENTER", 0, 0)
    rankStr:SetShadowOffset(1, -1)
    rankStr:SetShadowColor(0, 0, 0, 0.15)
    rankStr:SetText(rank == 1 and "|cffFFD100#1|r" or rank == 2 and "|cffC0C0C0#2|r" or rank == 3 and "|cffCD7F32#3|r" or ("|cff888888#" .. rank .. "|r"))

    -- Klassen-Icon
    local iconBorder = row:CreateTexture(nil, "ARTWORK")
    iconBorder:SetSize(34, 34)
    iconBorder:SetPoint("LEFT", 30, 0)
    iconBorder:SetColorTexture(col[1]*0.3, col[2]*0.3, col[3]*0.3, 0.60)
    local ic = row:CreateTexture(nil, "OVERLAY")
    ic:SetSize(30, 30)
    ic:SetPoint("LEFT", 32, 0)
    ic:SetTexture(classIcon)

    -- Name
    local nameStr = row:CreateFontString(nil, "OVERLAY")
    nameStr:SetFont("Fonts\\MORPHEUS.TTF", 11, "")
    nameStr:SetPoint("TOPLEFT", 70, -6)
    nameStr:SetShadowOffset(1, -1)
    nameStr:SetShadowColor(0, 0, 0, 0.15)
    local nr = math.min(1, col[1] * 1.25)
    local ng = math.min(1, col[2] * 1.25)
    local nb = math.min(1, col[3] * 1.25)
    nameStr:SetTextColor(nr, ng, nb)
    nameStr:SetText(s.name or "?")

    -- Level + Klasse
    local CLASS_NAMES_DE = {
        [1]="Krieger",[2]="Paladin",[3]="Jaeger",[4]="Schurke",[5]="Priester",
        [6]="Todesritter",[7]="Schamane",[8]="Magier",[9]="Hexenmeister",[11]="Druide",
        WARRIOR="Krieger",PALADIN="Paladin",HUNTER="Jaeger",ROGUE="Schurke",PRIEST="Priester",
        SHAMAN="Schamane",MAGE="Magier",WARLOCK="Hexenmeister",DRUID="Druide",
    }
    local cName = CLASS_NAMES_DE[s.class] or CLASS_NAMES_DE[s.classId] or (s.class or "Unbekannt")
    local infoStr = row:CreateFontString(nil, "OVERLAY")
    infoStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    infoStr:SetPoint("TOPLEFT", 70, -20)
    infoStr:SetText("|cff785030Lv.|r |cffF0C060" .. (s.level or "?") .. "|r  |cff684828" .. cName .. "|r")
    infoStr:SetShadowOffset(1, -1)
    infoStr:SetShadowColor(0, 0, 0, 0.12)

    -- Spielzeit rechts
    if s.played and s.played ~= "" then
        local playedStr = row:CreateFontString(nil, "OVERLAY")
        playedStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        playedStr:SetPoint("TOPRIGHT", -8, -8)
        playedStr:SetText("|cff88BB55" .. s.played .. "|r")
        playedStr:SetShadowOffset(1, -1)
        playedStr:SetShadowColor(0, 0, 0, 0.12)
    end

    -- "Unsterblich"-Badge
    local badgeBg = row:CreateTexture(nil, "ARTWORK")
    badgeBg:SetSize(62, 14)
    badgeBg:SetPoint("BOTTOMRIGHT", -6, 6)
    badgeBg:SetColorTexture(0.10, 0.18, 0.04, 0.50)
    local badgeStr = row:CreateFontString(nil, "OVERLAY")
    badgeStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    badgeStr:SetPoint("CENTER", badgeBg, "CENTER", 0, 0)
    badgeStr:SetText("|cff88FF44|TInterface\\RAIDFRAME\\ReadyCheck-Ready:10:10|t Lebt noch|r")
    badgeStr:SetShadowOffset(1, -1)
    badgeStr:SetShadowColor(0, 0, 0, 0.15)

    -- Hover
    row:EnableMouse(true)
    row:SetScript("OnEnter", function(self)
        bg:SetColorTexture(0.28, 0.20, 0.06, 0.42)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        GameTooltip:SetText("|c" .. classHex .. (s.name or "?") .. "|r", col[1], col[2], col[3], true)
        GameTooltip:AddLine("|T" .. classIcon .. ":16:16|t  " .. cName .. "  Â·  Level " .. (s.level or "?"), 0.80, 0.70, 0.50)
        if s.played and s.played ~= "" then
            GameTooltip:AddLine("|TInterface\\Icons\\INV_Misc_PocketWatch_01:14:14|t  Spielzeit: |cff88CC44" .. s.played .. "|r", 1, 1, 1)
        end
        if s.zone and s.zone ~= "" then
            GameTooltip:AddLine("|TInterface\\Icons\\INV_Misc_Map01:14:14|t  Zuletzt in: " .. s.zone, 0.50, 0.72, 0.92, true)
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff00FF66|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t Noch am Leben|r")
        GameTooltip:AddLine("|cff888888Rang: #" .. rank .. " der Unsterblichen|r")
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        bg:SetColorTexture(0.18, 0.13, 0.04, rank % 2 == 0 and 0.30 or 0.18)
        GameTooltip:Hide()
    end)

    return y - (ROW_H + 3)
end

-- â•â•â• III. STATISTIKEN â•â•â•
function UI:RenderStatistics()
    local parent = self.mainFrame.scrollChild
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end
    local deaths = guildData.deaths
    local y = 0
    local total = #deaths
    local CLASS_NAMES = {
        [1]="Krieger", [2]="Paladin", [3]="Jaeger", [4]="Schurke", [5]="Priester",
        [6]="Todesritter", [7]="Schamane", [8]="Magier", [9]="Hexenmeister", [11]="Druide",
        WARRIOR="Krieger", PALADIN="Paladin", HUNTER="Jaeger", ROGUE="Schurke", PRIEST="Priester",
        DEATHKNIGHT="Todesritter", SHAMAN="Schamane", MAGE="Magier", WARLOCK="Hexenmeister", DRUID="Druide",
    }
    _, y = self:MakeBookLine(parent, y, "|cff785030Gesamte Tode:|r  |cffCC0000" .. total .. "|r", 12, "|cff785030")
    y = y - 4
    local classCounts, zoneCounts = {}, {}
    local totalLvl = 0
    for _, d in ipairs(deaths) do
        local cId = d.classId or d.class
        if cId then
            classCounts[cId] = (classCounts[cId] or 0) + 1
        end
        local z = d.zone
        if z and z ~= "" then
            zoneCounts[z] = (zoneCounts[z] or 0) + 1
        end
        totalLvl = totalLvl + (d.level or 0)
    end
    _, y = self:MakeBookLine(parent, y, "|cff785030Durchschnittslevel:|r  |cffCC0000" .. (total > 0 and string.format("%.1f", totalLvl / total) or "0") .. "|r", 10, "|cff785030")
    y = y - 10
    local sorted = {}
    for k, v in pairs(classCounts) do
        if CLASS_NAMES[k] then
            table.insert(sorted, {k, v})
        end
    end
    table.sort(sorted, function(a, b) return a[2] > b[2] end)
    local maxCount = sorted[1] and sorted[1][2] or 1
    for _, kv in ipairs(sorted) do
        local cId = kv[1]
        local count = kv[2]
        local className = CLASS_NAMES[cId]
        local cc = ClassColorHex(cId)
        local ccTable = CLASS_COLORS[cId]
        local pct = count / maxCount
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(280, 18)
        row:SetPoint("TOPLEFT", 4, y)
        local ic = row:CreateTexture(nil, "ARTWORK")
        local iconPath = CLASS_ICONS[cId]
        if iconPath then
            ic:SetTexture(iconPath)
            ic:SetSize(14, 14)
            ic:SetPoint("LEFT", 0, 0)
        end
        local nameLabel = row:CreateFontString(nil, "OVERLAY")
        nameLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        nameLabel:SetPoint("LEFT", 18, 0)
        nameLabel:SetWidth(80)
        nameLabel:SetJustifyH("LEFT")
        nameLabel:SetText("|cff785030" .. className .. "|r")
        local bar = row:CreateTexture(nil, "BACKGROUND")
        local barWidth = math.max(6, pct * 100)
        bar:SetSize(barWidth, 12)
        bar:SetPoint("LEFT", 100, 0)
        if ccTable and type(ccTable) == "table" then
            bar:SetColorTexture(ccTable[1] or 0.5, ccTable[2] or 0.5, ccTable[3] or 0.5, 0.5)
        else
            bar:SetColorTexture(0.4, 0.3, 0.2, 0.4)
        end
        local countLabel = row:CreateFontString(nil, "OVERLAY")
        countLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        countLabel:SetPoint("LEFT", 205, 0)
        countLabel:SetText("|cff785030" .. count .. "|r")
        -- Hover-Tooltip
        row:EnableMouse(true)
        row:SetScript("OnEnter", function(self)
            bar:SetAlpha(0.85)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:ClearLines()
            local cIcon = CLASS_ICONS[cId]
            local titleText = cIcon and ("|T"..cIcon..":16:16|t  ") or ""
            GameTooltip:SetText(titleText .. "|c"..cc..className.."|r", 1, 1, 1, true)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffFF6666" .. count .. " Tode|r  |cff888888von " .. total .. " gesamt|r")
            local pctStr = total > 0 and string.format("%.1f", count/total*100) or "0"
            GameTooltip:AddLine("|cff888888Anteil:  " .. pctStr .. " %%|r")
            GameTooltip:Show()
        end)
        row:SetScript("OnLeave", function(self)
            bar:SetAlpha(0.50)
            GameTooltip:Hide()
        end)
        y = y - 18
    end
    y = y - 10
    sorted = {}
    for k, v in pairs(zoneCounts) do table.insert(sorted, {k, v}) end
    table.sort(sorted, function(a, b) return a[2] > b[2] end)
    local maxZone = sorted[1] and sorted[1][2] or 1
    _, y = self:MakeBookLine(parent, y, "|cff785030-- Gefaehrlichste Zonen --|r", 11, "|cff785030")
    y = y - 4
    for i, kv in ipairs(sorted) do
        if i > 8 then break end
        local pct = kv[2] / maxZone
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(280, 18)
        row:SetPoint("TOPLEFT", 4, y)
        local label = row:CreateFontString(nil, "OVERLAY")
        label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        label:SetPoint("LEFT", 0, 0)
        label:SetWidth(100)
        label:SetJustifyH("LEFT")
        label:SetText("|cff8AA8C8" .. kv[1] .. "|r")
        local bar = row:CreateTexture(nil, "BACKGROUND")
        local barWidth = math.max(6, pct * 100)
        bar:SetSize(barWidth, 14)
        bar:SetPoint("LEFT", 100, 0)
        bar:SetColorTexture(0.82, 0.20, 0.12, 0.72)
        local countLabel = row:CreateFontString(nil, "OVERLAY")
        countLabel:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        countLabel:SetPoint("LEFT", 205, 0)
        countLabel:SetText("|cffFF6666" .. kv[2] .. "|r")
        row:EnableMouse(true)
        row:SetScript("OnEnter", function(self)
            bar:SetAlpha(1.0)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:ClearLines()
            GameTooltip:SetText("|TInterface\\Icons\\INV_Misc_Map01:16:16|t  |cffCC8844" .. kv[1] .. "|r", 1, 1, 1, true)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffFF4444" .. kv[2] .. " Tode|r  |cff888888in dieser Zone|r")
            local pctStr = total > 0 and string.format("%.1f", kv[2]/total*100) or "0"
            GameTooltip:AddLine("|cff888888Anteil:  " .. pctStr .. " %% aller Tode|r")
            local danger = math.floor(kv[2]/maxZone * 100)
            local dangerCol = danger >= 80 and "|cffFF2222" or danger >= 50 and "|cffFF8800" or "|cffFFCC00"
            GameTooltip:AddLine("Gefahrenstufe: " .. dangerCol .. danger .. " %%|r")
            GameTooltip:Show()
        end)
        row:SetScript("OnLeave", function(self)
            bar:SetAlpha(0.72)
            GameTooltip:Hide()
        end)
        y = y - 18
    end
    y = y - 10
    _, y = self:MakeBookLine(parent, y, "|cff785030-- Level-Verteilung der Tode --|r", 11, "|cff785030")
    y = y - 4
    local levelBrackets = {
        {1, 9, "1-9"}, {10, 19, "10-19"}, {20, 29, "20-29"}, {30, 39, "30-39"},
        {40, 49, "40-49"}, {50, 59, "50-59"}, {60, 60, "60"},
    }
    local bracketCounts = {}
    local maxBracket = 0
    for _, d in ipairs(deaths) do
        local lvl = d.level or 0
        for _, b in ipairs(levelBrackets) do
            if lvl >= b[1] and lvl <= b[2] then
                bracketCounts[b[3]] = (bracketCounts[b[3]] or 0) + 1
                if bracketCounts[b[3]] > maxBracket then maxBracket = bracketCounts[b[3]] end
                break
            end
        end
    end
    for _, b in ipairs(levelBrackets) do
        local cnt = bracketCounts[b[3]] or 0
        local pct = maxBracket > 0 and (cnt / maxBracket) or 0
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(310, 16)
        row:SetPoint("TOPLEFT", 4, y)
        local lbl = row:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        lbl:SetPoint("LEFT", 0, 0)
        lbl:SetText("|cff785030Lvl " .. b[3] .. "|r")
        local bar = row:CreateTexture(nil, "BACKGROUND")
        local bw = math.max(2, pct * 160)
        bar:SetSize(bw, 12)
        bar:SetPoint("LEFT", 40, 0)
        local heat = cnt / math.max(1, maxBracket)
        bar:SetColorTexture(0.65 + heat * 0.35, 0.25 - heat * 0.15, 0.10, 0.75)
        if cnt > 0 then
            local cl = row:CreateFontString(nil, "OVERLAY")
            cl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
            cl:SetPoint("LEFT", 40 + bw + 4, 0)
            cl:SetText("|cffF0C060" .. cnt .. "|r")
        end
        y = y - 16
    end
    y = y - 10
    _, y = self:MakeBookLine(parent, y, "|cff785030-- Todes-Uhrzeiten --|r", 11, "|cff785030")
    y = y - 4
    local hourCounts = {}
    local maxHour = 0
    for _, d in ipairs(deaths) do
        if d.timestamp then
            local h = tonumber(date("%H", d.timestamp)) or 0
            hourCounts[h] = (hourCounts[h] or 0) + 1
            if hourCounts[h] > maxHour then maxHour = hourCounts[h] end
        end
    end
    local hourRow = CreateFrame("Frame", nil, parent)
    hourRow:SetSize(280, 50)
    hourRow:SetPoint("TOPLEFT", 4, y)
    for h = 0, 23 do
        local cnt = hourCounts[h] or 0
        local pct = maxHour > 0 and (cnt / maxHour) or 0
        local barH = math.max(2, pct * 36)
        local bar = hourRow:CreateTexture(nil, "ARTWORK")
        bar:SetSize(10, barH)
        bar:SetPoint("BOTTOMLEFT", h * 11.5, 0)
        local heat = pct
        bar:SetColorTexture(0.3 + heat * 0.7, 0.2 - heat * 0.15, 0.1, 0.6)
        if cnt > 0 then
            local lbl = hourRow:CreateFontString(nil, "OVERLAY")
            lbl:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
            lbl:SetPoint("BOTTOM", bar, "TOP", 0, 1)
            lbl:SetText("|cff785030" .. cnt .. "|r")
        end
    end
    y = y - 54
    local timeRow = CreateFrame("Frame", nil, parent)
    timeRow:SetSize(280, 10)
    timeRow:SetPoint("TOPLEFT", 4, y)
    for _, h in ipairs({0, 6, 12, 18, 23}) do
        local lbl = timeRow:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
        lbl:SetPoint("LEFT", h * 11.5, 0)
        lbl:SetText("|cff684820" .. h .. "h|r")
    end
    y = y - 12
    y = y - 8
    _, y = self:MakeBookLine(parent, y, "|cff785030-- Monatsvergleich --|r", 11, "|cff785030")
    y = y - 4
    local monthCounts = {}
    local MONTH_NAMES = {"Jan", "Feb", "Marz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}
    for _, d in ipairs(deaths) do
        if d.timestamp then
            local key = date("%Y-%m", d.timestamp)
            local label = MONTH_NAMES[tonumber(date("%m", d.timestamp))] .. " " .. date("%Y", d.timestamp)
            monthCounts[key] = monthCounts[key] or {label = label, count = 0}
            monthCounts[key].count = monthCounts[key].count + 1
        end
    end
    local monthSorted = {}
    for k, v in pairs(monthCounts) do table.insert(monthSorted, {k, v}) end
    table.sort(monthSorted, function(a, b) return a[1] > b[1] end)
    local maxMonth = 0
    for _, m in ipairs(monthSorted) do if m[2].count > maxMonth then maxMonth = m[2].count end end
    for i, m in ipairs(monthSorted) do
        if i > 6 then break end
        local pct = maxMonth > 0 and (m[2].count / maxMonth) or 0
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(280, 14)
        row:SetPoint("TOPLEFT", 4, y)
        local lbl = row:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        lbl:SetPoint("LEFT", 0, 0)
        lbl:SetWidth(55)
        lbl:SetJustifyH("LEFT")
        lbl:SetText("|cff785030" .. m[2].label .. "|r")
        local bar = row:CreateTexture(nil, "BACKGROUND")
        local bw = math.max(4, pct * 120)
        bar:SetSize(bw, 9)
        bar:SetPoint("LEFT", 58, 0)
        bar:SetColorTexture(0.5, 0.1, 0.1, 0.4)
        local cl = row:CreateFontString(nil, "OVERLAY")
        cl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        cl:SetPoint("LEFT", 185, 0)
        cl:SetText("|cffCC0000" .. m[2].count .. " Tode|r")
        y = y - 14
    end
    parent:SetHeight(math.abs(y) + 20)
end

function UI:RenderSupporterLock(parent, y, featureName)
    y = y - 20
    local lockIcon = parent:CreateTexture(nil, "ARTWORK")
    lockIcon:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
    lockIcon:SetSize(48, 48)
    lockIcon:SetPoint("TOP", 0, y)
    lockIcon:SetVertexColor(0.6, 0.4, 0.15, 0.8)
    y = y - 56
    local lockLbl = parent:CreateFontString(nil, "OVERLAY")
    lockLbl:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    lockLbl:SetPoint("TOP", 0, y)
    lockLbl:SetText("|cffBB9944Supporter+ Feature|r")
    y = y - 22
    local desc = parent:CreateFontString(nil, "OVERLAY")
    desc:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    desc:SetPoint("TOPLEFT", 20, y)
    desc:SetWidth(280)
    desc:SetJustifyH("CENTER")
    desc:SetText("|cff583830\"" .. featureName .. "\" ist ein kosmetisches Extra fuer Supporter+.\nKein Gameplay-Vorteil - rein optisch.|r")
    y = y - 40
    local hintTxt = parent:CreateFontString(nil, "OVERLAY")
    hintTxt:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    hintTxt:SetPoint("TOPLEFT", 20, y)
    hintTxt:SetWidth(280)
    hintTxt:SetJustifyH("CENTER")
    hintTxt:SetText("|cff8A7A68Oeffne Kapitel XIV 'Supporter+' um einen Code einzuloesen.|r")
    y = y - 30
    local goBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    goBtn:SetSize(180, 24)
    goBtn:SetPoint("TOP", 0, y)
    goBtn:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets = {left = 2, right = 2, top = 2, bottom = 2},
    })
    goBtn:SetBackdropColor(0.2, 0.15, 0.05, 0.95)
    goBtn:SetBackdropBorderColor(0.7, 0.55, 0.2, 1)
    local btnLbl = goBtn:CreateFontString(nil, "OVERLAY")
    btnLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    btnLbl:SetPoint("CENTER", 0, 1)
    btnLbl:SetText("|cffFFD100Zu Supporter+|r")
    goBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.3, 0.22, 0.08, 1)
        self:SetBackdropBorderColor(0.9, 0.7, 0.3, 1)
    end)
    goBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.2, 0.15, 0.05, 0.95)
        self:SetBackdropBorderColor(0.7, 0.55, 0.2, 1)
    end)
    goBtn:SetScript("OnClick", function()
        UI:HandleChapterClick(14, "supporter")
    end)
    y = y - 32
    return y
end

-- â•â•â• IV. MEILENSTEINE â•â•â•
function UI:RenderMilestones()
    local parent = self.mainFrame.scrollChild
    local Mil = GDL:GetModule("Milestones")
    if not Mil then return end
    local y = 0
    if not GDL:IsSupporterActive() then
        y = self:RenderSupporterLock(parent, y, "Meilensteine")
        parent:SetHeight(math.abs(y) + 20)
        return
    end

    -- â”€â”€ Fortschritts-Banner â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local unlocked, total = Mil:GetMilestoneProgress()
    local pct = total > 0 and math.floor(unlocked / total * 100) or 0
    local banner = CreateFrame("Frame", nil, parent)
    banner:SetSize(330, 32)
    banner:SetPoint("TOPLEFT", 0, y)
    local bannerBg = banner:CreateTexture(nil, "BACKGROUND")
    bannerBg:SetAllPoints()
    bannerBg:SetColorTexture(0.14, 0.08, 0.02, 0.35)
    local bannerIcon = banner:CreateTexture(nil, "ARTWORK")
    bannerIcon:SetTexture("Interface\\Icons\\Spell_Holy_PowerWordShield")
    bannerIcon:SetSize(22, 22)
    bannerIcon:SetPoint("LEFT", 6, 0)
    local bannerLbl = banner:CreateFontString(nil, "OVERLAY")
    bannerLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    bannerLbl:SetPoint("LEFT", 34, 1)
    bannerLbl:SetShadowOffset(1, -1)
    bannerLbl:SetShadowColor(0, 0, 0, 0.15)
    local pctCol = pct == 100 and "|cff00FF66" or (pct >= 50 and "|cffFFD100" or "|cff888888")
    bannerLbl:SetText("|cffDDB060Meilensteine:  |cffFFD100" .. unlocked .. "/" .. total .. "|r  " .. pctCol .. "(" .. pct .. "%%)|r")
    y = y - 38

    local div0 = parent:CreateTexture(nil, "ARTWORK")
    div0:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    div0:SetSize(330, 6)
    div0:SetPoint("TOPLEFT", 0, y + 2)
    div0:SetVertexColor(0.45, 0.30, 0.12, 0.5)
    y = y - 10

    -- Milestone-Lookup
    local milById = {}
    for _, m in ipairs(Mil:GetAllMilestones()) do
        milById[m.id] = m
    end

    local CATS = {
        { title = "Charakter Level",            icon = "Interface\\Icons\\INV_Crown_01",
          r = 0.25, g = 0.15, b = 0.02,
          ids = { "level_10","level_20","level_30","level_40","level_50","level_60" } },
        { title = "Ueberleben",                 icon = "Interface\\Icons\\INV_Misc_PocketWatch_01",
          r = 0.04, g = 0.10, b = 0.24,
          ids = { "played_24h","played_72h","played_168h","played_336h","played_720h","alive_7","alive_30","alive_90","alive_365" } },
        { title = "Dungeons  -  Anfaenger (10-25)", icon = "Interface\\Icons\\INV_Misc_Key_03",
          r = 0.08, g = 0.18, b = 0.06,
          ids = { "boss_rfc","boss_wc","boss_dm","boss_sfk","boss_bfd","boss_stocks" } },
        { title = "Dungeons  -  Mittel (25-45)",  icon = "Interface\\Icons\\INV_Misc_Key_07",
          r = 0.05, g = 0.14, b = 0.10,
          ids = { "boss_gnomer","boss_sm_lib","boss_sm_arm","boss_sm_cath","boss_sm_gy","boss_rfk","boss_rfd","boss_ulda","boss_zf","boss_mara" } },
        { title = "Dungeons  -  Elite (45-60)",   icon = "Interface\\Icons\\INV_Misc_Key_14",
          r = 0.04, g = 0.08, b = 0.16,
          ids = { "boss_st","boss_brd_emp","boss_dm_north","boss_dm_east","boss_dm_west","boss_lbrs","boss_ubrs","boss_scholo","boss_strat_ud","boss_strat_live" } },
        { title = "Raid-Legenden",              icon = "Interface\\Icons\\INV_Misc_Head_Dragon_Black",
          r = 0.22, g = 0.04, b = 0.04,
          ids = { "boss_ony","boss_mc_rag","boss_bwl_nef","boss_zg_hakkar","boss_aq20_ossirian","boss_aq40_cthun","boss_naxx_kt" } },
        { title = "Meisterschaft",              icon = "Interface\\Icons\\INV_Jewelry_Talisman_08",
          r = 0.22, g = 0.16, b = 0.02,
          ids = { "meta_low_dungeons","meta_mid_dungeons","meta_high_dungeons","meta_all_dungeons","meta_all_raids","meta_dungeon_legend","meta_all_secondary","meta_complete_crafter" } },
        { title = "Berufe",                     icon = "Interface\\Icons\\Trade_Engineering",
          r = 0.06, g = 0.16, b = 0.10,
          ids = { "prof_150","prof_225","prof_300","fish_150","fish_300","cook_150","cook_300","firstaid_150","firstaid_300" } },
        { title = "Kill-Statistiken",           icon = "Interface\\Icons\\Ability_DualWield",
          r = 0.22, g = 0.06, b = 0.04,
          ids = { "kills_total_1000","kills_total_5000","kills_total_10000","kills_total_25000","kills_total_50000",
                  "kill_humanoid_25","kill_humanoid_100","kill_humanoid_500","kill_humanoid_2000",
                  "kill_beast_25","kill_beast_100","kill_beast_500","kill_beast_2000",
                  "kill_undead_25","kill_undead_100","kill_undead_500","kill_undead_1000","kill_undead_5000",
                  "kill_demon_25","kill_demon_100","kill_demon_500","kill_demon_1000",
                  "kill_elemental_25","kill_elemental_100","kill_elemental_500",
                  "kill_dragonkin_10","kill_dragonkin_50","kill_dragonkin_250","kill_dragonkin_1000",
                  "kill_giant_10","kill_giant_50","kill_giant_250" } },
        { title = "Reichtum",                   icon = "Interface\\Icons\\INV_Misc_Coin_17",
          r = 0.22, g = 0.16, b = 0.01,
          ids = { "gold_100","gold_500","gold_1000","gold_5000" } },
        { title = "PvP Ruhm",                   icon = "Interface\\Icons\\Ability_Warrior_BattleShout",
          r = 0.20, g = 0.04, b = 0.04,
          ids = { "pvp_10","pvp_50","pvp_100","pvp_500","pvp_1000" } },
        { title = "Soziales & Gilde",           icon = "Interface\\Icons\\INV_Misc_GroupNeedMore",
          r = 0.04, g = 0.12, b = 0.22,
          ids = { "witness_1","witness_10","witness_25","witness_50","witness_100","guild_10","guild_25","guild_50","quest_100","quest_250","quest_500","quest_1000" } },
        { title = "Legenden  -  Geheimnis",       icon = "Interface\\Icons\\INV_Misc_Eye_01",
          r = 0.18, g = 0.02, b = 0.18, isSecret = true,
          ids = { "meta_immortal_veteran","meta_legend_azeroth","meta_true_immortal","meta_godslayer","meta_perfectionist","meta_pvp_master","meta_witness_master","meta_ultimate" } },
    }

    for _, cat in ipairs(CATS) do
        local catUnlocked = 0
        for _, id in ipairs(cat.ids) do
            if Mil:IsMilestoneUnlocked(id) then catUnlocked = catUnlocked + 1 end
        end
        y = self:RenderAchCategoryHeader(parent, y, cat.title, cat.icon, catUnlocked, #cat.ids, cat.r, cat.g, cat.b)
        y = y - 4
        for _, id in ipairs(cat.ids) do
            local m = milById[id]
            if m then
                y = self:RenderMilRow(parent, y, m, Mil:IsMilestoneUnlocked(id), cat.isSecret or false, Mil)
            end
        end
        y = y - 10
    end

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

-- â”€â”€ Meilenstein-Zeile (analog RenderAchRow) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
function UI:RenderMilRow(parent, y, m, isUnlocked, isSecret, Mil)
    local ROW_H = 40
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(326, ROW_H)
    row:SetPoint("TOPLEFT", 4, y)

    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    if isUnlocked then
        bg:SetColorTexture(0.22, 0.14, 0.03, 0.20)
    else
        bg:SetColorTexture(0.04, 0.03, 0.02, 0.10)
    end

    local sep = row:CreateTexture(nil, "BORDER")
    sep:SetSize(316, 1)
    sep:SetPoint("BOTTOMLEFT", 5, 0)
    sep:SetColorTexture(0.28, 0.20, 0.10, 0.22)

    local dispIcon = m.icon
    local dispName = m.name
    local dispDesc = m.desc or ""
    if isSecret and not isUnlocked then
        dispIcon = "Interface\\Icons\\INV_Misc_QuestionMark"
        dispName = "???"
        dispDesc = "Geheimnis  -  Schalte es frei um mehr zu erfahren"
    end

    local iconBorder = row:CreateTexture(nil, "ARTWORK")
    iconBorder:SetSize(28, 28)
    iconBorder:SetPoint("LEFT", 4, 0)
    iconBorder:SetColorTexture(isUnlocked and 0.55 or 0.18, isUnlocked and 0.38 or 0.15, isUnlocked and 0.06 or 0.06, 0.6)
    local ic = row:CreateTexture(nil, "OVERLAY")
    ic:SetTexture(dispIcon)
    ic:SetSize(24, 24)
    ic:SetPoint("LEFT", 6, 0)
    if not isUnlocked then
        ic:SetDesaturated(true)
        ic:SetAlpha(0.35)
    end

    local nameStr = row:CreateFontString(nil, "OVERLAY")
    nameStr:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    nameStr:SetPoint("TOPLEFT", 38, -6)
    nameStr:SetShadowOffset(1, -1)
    nameStr:SetShadowColor(0, 0, 0, 0.15)
    nameStr:SetText(isUnlocked and ("|cffF0C060" .. dispName .. "|r") or ("|cff7A7060" .. dispName .. "|r"))

    local descStr = row:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    descStr:SetPoint("TOPLEFT", 38, -19)
    descStr:SetWidth(192)
    descStr:SetJustifyH("LEFT")
    if isUnlocked then
        descStr:SetTextColor(0.68, 0.55, 0.32)
    else
        descStr:SetTextColor(0.48, 0.40, 0.30)
    end
    descStr:SetText(dispDesc)

    local statusStr = row:CreateFontString(nil, "OVERLAY")
    statusStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    statusStr:SetPoint("RIGHT", -6, 0)
    statusStr:SetShadowOffset(1, -1)
    statusStr:SetShadowColor(0, 0, 0, 0.15)
    if isUnlocked then
        statusStr:SetText("|cff88FF44|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
    else
        statusStr:SetText("|cff664444|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t|r")
    end

    local CAT_NAMES = {
        level="Level", survival="Ueberleben", dungeon="Dungeon", raid="Raid",
        meta="Meisterschaft", profession="Beruf", kills="Kills", wealth="Reichtum",
        pvp="PvP", social="Soziales", quests="Quests", secret="Geheimnis"
    }

    row:EnableMouse(true)
    row:SetScript("OnEnter", function(self)
        bg:SetColorTexture(isUnlocked and 0.28 or 0.10, isUnlocked and 0.18 or 0.06, isUnlocked and 0.04 or 0.03, isUnlocked and 0.38 or 0.22)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        local tc = isUnlocked and "|cffFFD100" or "|cff888888"
        GameTooltip:SetText(tc .. dispName .. "|r", 1, 1, 1, true)
        if dispDesc ~= "" then
            GameTooltip:AddLine(dispDesc, 0.78, 0.65, 0.42, true)
        end
        if m.category then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cff888888Kategorie: " .. (CAT_NAMES[m.category] or m.category) .. "|r")
        end
        GameTooltip:AddLine(" ")
        if isUnlocked then
            local charData = Mil and Mil:GetCharacterData()
            local ui = charData and charData.unlocked and charData.unlocked[m.id]
            if ui and ui.timestamp then
                GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t |cff00FF66Erreicht am " .. date("%d.%m.%Y", ui.timestamp) .. "|r")
                if ui.level then
                    GameTooltip:AddLine("|cff888888Level bei Freischaltung: " .. ui.level .. "|r")
                end
            else
                GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t |cff00FF66Freigeschaltet|r")
            end
        else
            GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t |cffAA4444Noch nicht erreicht|r")
        end
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        bg:SetColorTexture(isUnlocked and 0.22 or 0.04, isUnlocked and 0.14 or 0.03, isUnlocked and 0.03 or 0.02, isUnlocked and 0.20 or 0.10)
        GameTooltip:Hide()
    end)

    return y - (ROW_H + 2)
end

-- â•â•â• V. BERUFE â•â•â•
function UI:RenderProfessions()
    local parent = self.mainFrame.scrollChild
    local Prof = GDL:GetModule("Professions")
    local Guild = GDL:GetModule("Guild")
    if not Prof then return end
    local y = 0
    local data = Prof:GetAllProfessions() or {}
    local allMembers = Guild and Guild:GetAllMembers() or {}
    local shown = {}
    local withProf, withoutProf = 0, 0
    local sortedWithProf = {}
    for playerName, info in pairs(data) do
        local cleanName = strsplit("-", playerName)
        shown[cleanName:lower()] = true
        table.insert(sortedWithProf, {name = cleanName, info = info})
    end
    table.sort(sortedWithProf, function(a, b) return a.name < b.name end)
    if #sortedWithProf > 0 then
        _, y = self:MakeBookLine(parent, y, "|cff785030-- Mitglieder mit Berufsdaten --|r", 10, "|cff785030")
        y = y - 4
        for _, entry in ipairs(sortedWithProf) do
            local info = entry.info
            local profStr = ""
            if info.prof1 and info.prof1.name then
                profStr = info.prof1.name .. " (" .. (info.prof1.skill or "?") .. "/" .. (info.prof1.max or "?") .. ")"
            end
            if info.prof2 and info.prof2.name then
                if profStr ~= "" then profStr = profStr .. ", " end
                profStr = profStr .. info.prof2.name .. " (" .. (info.prof2.skill or "?") .. "/" .. (info.prof2.max or "?") .. ")"
            end
            if profStr == "" then profStr = "Keine Berufe" end
            local cc = ClassColorHex(info.classId)
            _, y = self:MakeBookRow(parent, y, CLASS_ICONS[info.classId], "|c" .. cc .. entry.name .. "|r", "|cff3A2008" .. profStr .. "|r", info.level and ("|cff3A2008Lvl " .. info.level .. "|r") or "")
            withProf = withProf + 1
        end
    end
    local sortedWithout = {}
    for key, memberInfo in pairs(allMembers) do
        if type(memberInfo) == "table" and not shown[key] then
            table.insert(sortedWithout, memberInfo)
        end
    end
    table.sort(sortedWithout, function(a, b) return (a.name or "") < (b.name or "") end)
    if #sortedWithout > 0 then
        y = y - 8
        _, y = self:MakeBookLine(parent, y, "|cff3A2008-- Ohne Addon / Berufsdaten --|r", 10, "|cff3A2008")
        y = y - 4
        for _, m in ipairs(sortedWithout) do
            local cc = ClassColorHex(m.class)
            local classIcon = CLASS_ICONS[m.class]
            local lvlStr = m.level and ("|cff3A2008Lvl " .. m.level .. "|r") or ""
            local zoneStr = m.zone and ("|cff264870" .. m.zone .. "|r") or ""
            _, y = self:MakeBookRow(parent, y, classIcon, "|c" .. cc .. (m.name or "?") .. "|r", "|cff705038Berufe unbekannt (kein Addon)|r", lvlStr)
            withoutProf = withoutProf + 1
        end
    end
    y = y - 8
    _, y = self:MakeBookLine(parent, y, "|cff3A2008" .. withProf .. " mit Berufsdaten  |  " .. withoutProf .. " ohne Addon|r", 8, "|cff3A2008")
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• VI. GILDENKARTE (Alle Mitglieder) â•â•â•
function UI:RenderGuildMap()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local numTotal = GetNumGuildMembers()
    local onlineList, offlineList = {}, {}
    for i = 1, numTotal do
        local name, rank, _, level, class, zone, _, _, online, _, classFileName = GetGuildRosterInfo(i)
        if name then
            local cleanName = strsplit("-", name)
            local entry = {name = cleanName, rank = rank, level = level, class = classFileName or class, zone = zone, online = online}
            if online then
                table.insert(onlineList, entry)
            else
                table.insert(offlineList, entry)
            end
        end
    end
    _, y = self:MakeBookLine(parent, y, "|cff1A7A1A" .. #onlineList .. " Online|r  |  |cff5A3818" .. #offlineList .. " Offline|r  |  |cff3A2008" .. (#onlineList + #offlineList) .. " Gesamt|r", 10, "|cff3A2008")
    y = y - 6
    if #onlineList > 0 then
        _, y = self:MakeBookLine(parent, y, "|cff1A7A1AOnline:|r", 10, "|cff1A7A1A")
        y = y - 2
        for _, m in ipairs(onlineList) do
            local cc = ClassColorHex(m.class)
            _, y = self:MakeBookRow(parent, y, CLASS_ICONS[m.class], "|c" .. cc .. m.name .. "|r", "|cff3A2008Lvl " .. (m.level or "?") .. "  " .. (m.rank or "") .. "|r", "|cff264870" .. (m.zone or "") .. "|r")
        end
    end
    if #offlineList > 0 then
        y = y - 6
        _, y = self:MakeBookLine(parent, y, "|cff5A3818Offline (" .. #offlineList .. "):|r", 10, "|cff5A3818")
        y = y - 2
        for i, m in ipairs(offlineList) do
            if i > 30 then
                _, y = self:MakeBookLine(parent, y, "|cff5A3818... und " .. (#offlineList - 30) .. " weitere|r", 9, "|cff5A3818")
                break
            end
            _, y = self:MakeBookRow(parent, y, nil, "|cff4A3020" .. m.name .. "|r", "|cff4A3020Lvl " .. (m.level or "?") .. " " .. (m.rank or "") .. "|r", "")
        end
    end
    self.mainFrame.rightSubtitle:SetText("|cff3A2008" .. #onlineList .. " online / " .. (#onlineList + #offlineList) .. " gesamt|r")
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• VII. TITEL â•â•â•
function UI:RenderTitles()
    local parent = self.mainFrame.scrollChild
    local Titles = GDL:GetModule("Titles")
    if not Titles then return end
    local y = 0
    if not GDL:IsSupporterActive() then
        y = self:RenderSupporterLock(parent, y, "Titel & Raenge")
        parent:SetHeight(math.abs(y) + 20)
        return
    end

    local allTitles = Titles:GetAllTitles() or {}
    local unlockedTitles = Titles:GetUnlockedTitles() or {}
    local selectedTitle = Titles:GetSelectedTitle()
    local selectedId = selectedTitle and selectedTitle.id

    local unlockedMap = {}
    for _, ut in ipairs(unlockedTitles) do
        unlockedMap[ut.id] = true
    end

    -- ── Aktiver Titel ──────────────────────────────────────────────
    y = self:RenderAchCategoryHeader(parent, y,
        selectedTitle and ("Aktiver Titel: " .. (selectedTitle.name or "?")) or "Kein Titel ausgewaehlt",
        "Interface\\Icons\\INV_Crown_01",
        selectedTitle and 1 or 0, 1,
        0.50, 0.35, 0.05)
    y = y - 2

    if selectedTitle then
        local activeRow = CreateFrame("Frame", nil, parent)
        activeRow:SetSize(326, 42)
        activeRow:SetPoint("TOPLEFT", 4, y)
        local abg = activeRow:CreateTexture(nil, "BACKGROUND")
        abg:SetAllPoints()
        abg:SetColorTexture(0.30, 0.20, 0.02, 0.30)
        local aAccent = activeRow:CreateTexture(nil, "BORDER")
        aAccent:SetSize(3, 36); aAccent:SetPoint("LEFT", 0, 0)
        aAccent:SetColorTexture(1.0, 0.82, 0.10, 1.0)
        local aGlow = activeRow:CreateTexture(nil, "ARTWORK")
        aGlow:SetSize(22, 22); aGlow:SetPoint("LEFT", 8, 0)
        aGlow:SetColorTexture(1.0, 0.85, 0.0, 0.20)
        aGlow:SetBlendMode("ADD")
        local aIcon = activeRow:CreateTexture(nil, "OVERLAY")
        aIcon:SetTexture("Interface\\Icons\\INV_Crown_01")
        aIcon:SetSize(22, 22); aIcon:SetPoint("LEFT", 7, 0)
        local aStar = activeRow:CreateFontString(nil, "OVERLAY")
        aStar:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        aStar:SetPoint("TOPLEFT", 36, -5)
        aStar:SetText("|cffFFD100Aktuell aktiv|r")
        aStar:SetShadowOffset(1, -1); aStar:SetShadowColor(0, 0, 0, 0.20)
        local aName = activeRow:CreateFontString(nil, "OVERLAY")
        aName:SetFont("Fonts\\MORPHEUS.TTF", 13, "")
        aName:SetPoint("TOPLEFT", 36, -17)
        aName:SetText("|cffFFD100" .. (selectedTitle.name or "?") .. "|r")
        aName:SetShadowOffset(1, -1); aName:SetShadowColor(0, 0, 0, 0.20)
        local aDesc = activeRow:CreateFontString(nil, "OVERLAY")
        aDesc:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        aDesc:SetPoint("TOPLEFT", 36, -32)
        aDesc:SetText("|cff9A8058" .. (selectedTitle.desc or selectedTitle.requirement or "") .. "|r")
        aDesc:SetShadowOffset(1, -1); aDesc:SetShadowColor(0, 0, 0, 0.15)
        local aSep = activeRow:CreateTexture(nil, "BORDER")
        aSep:SetSize(316, 1); aSep:SetPoint("BOTTOMLEFT", 5, 0)
        aSep:SetColorTexture(0.70, 0.55, 0.12, 0.40)
        y = y - 46
    end

    y = y - 8

    -- ── Freigeschaltete Titel ───────────────────────────────────────
    local unlockedCount = #unlockedTitles
    local totalCount = #allTitles
    y = self:RenderAchCategoryHeader(parent, y,
        "Freigeschaltete Titel",
        "Interface\\RAIDFRAME\\ReadyCheck-Ready",
        unlockedCount, totalCount,
        0.04, 0.22, 0.06)
    y = y - 2

    local hasUnlocked = false
    for _, t in ipairs(allTitles) do
        if unlockedMap[t.id] then
            hasUnlocked = true
            local isActive = (t.id == selectedId)
            local row = CreateFrame("Button", nil, parent)
            row:SetSize(326, 40)
            row:SetPoint("TOPLEFT", 4, y)
            row:EnableMouse(true)
            row:RegisterForClicks("LeftButtonUp")

            local bg = row:CreateTexture(nil, "BACKGROUND")
            bg:SetAllPoints()
            bg:SetColorTexture(isActive and 0.28 or 0.22, isActive and 0.18 or 0.14, isActive and 0.02 or 0.03, isActive and 0.32 or 0.20)
            row.bg = bg

            local accent = row:CreateTexture(nil, "BORDER")
            accent:SetSize(3, 34); accent:SetPoint("LEFT", 0, 0)
            accent:SetColorTexture(isActive and 1.0 or 0.35, isActive and 0.82 or 0.70, isActive and 0.10 or 0.15, isActive and 1.0 or 0.55)

            local iconBorder = row:CreateTexture(nil, "ARTWORK")
            iconBorder:SetSize(28, 28); iconBorder:SetPoint("LEFT", 8, 0)
            iconBorder:SetColorTexture(0.50, 0.35, 0.05, 0.65)
            local ic = row:CreateTexture(nil, "OVERLAY")
            ic:SetTexture(t.icon or "Interface\\Icons\\INV_Crown_01")
            ic:SetSize(24, 24); ic:SetPoint("LEFT", 10, 0)

            local nameStr = row:CreateFontString(nil, "OVERLAY")
            nameStr:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            nameStr:SetPoint("TOPLEFT", 42, -6)
            nameStr:SetShadowOffset(1, -1); nameStr:SetShadowColor(0, 0, 0, 0.20)
            nameStr:SetText(isActive and ("|cffFFD100" .. t.name .. " |cffFFD100<aktiv>|r") or ("|cff88DDAA" .. t.name .. "|r"))

            local descStr = row:CreateFontString(nil, "OVERLAY")
            descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
            descStr:SetPoint("TOPLEFT", 42, -19)
            descStr:SetWidth(200); descStr:SetJustifyH("LEFT")
            descStr:SetTextColor(0.68, 0.58, 0.35)
            descStr:SetText(t.desc or t.requirement or "")

            local statusStr = row:CreateFontString(nil, "OVERLAY")
            statusStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
            statusStr:SetPoint("RIGHT", -6, 0)
            statusStr:SetShadowOffset(1, -1); statusStr:SetShadowColor(0, 0, 0, 0.15)
            if isActive then
                statusStr:SetText("|cffFFD100|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
            else
                statusStr:SetText("|cff44FF88|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
            end

            local sep = row:CreateTexture(nil, "BORDER")
            sep:SetSize(316, 1); sep:SetPoint("BOTTOMLEFT", 5, 0)
            sep:SetColorTexture(0.28, 0.22, 0.10, 0.22)

            local titleId = t.id
            row:SetScript("OnClick", function()
                if Titles:SelectTitle(titleId) then
                    UI:RenderChapter("titles")
                end
            end)
            row:SetScript("OnEnter", function(self)
                bg:SetColorTexture(0.30, 0.22, 0.04, 0.40)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                GameTooltip:SetText("|cffFFD100" .. t.name .. "|r", 1, 1, 1, true)
                if t.desc or t.requirement then
                    GameTooltip:AddLine(t.desc or t.requirement or "", 0.78, 0.65, 0.42, true)
                end
                GameTooltip:AddLine(" ")
                if isActive then
                    GameTooltip:AddLine("|cffFFD100Aktuell ausgewaehlt|r")
                else
                    GameTooltip:AddLine("|cff44FF88Klicken zum Auswaehlen|r")
                end
                GameTooltip:Show()
            end)
            row:SetScript("OnLeave", function(self)
                bg:SetColorTexture(isActive and 0.28 or 0.22, isActive and 0.18 or 0.14, isActive and 0.02 or 0.03, isActive and 0.32 or 0.20)
                GameTooltip:Hide()
            end)
            y = y - 42
        end
    end
    if not hasUnlocked then
        local noneStr = parent:CreateFontString(nil, "OVERLAY")
        noneStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        noneStr:SetPoint("TOPLEFT", 10, y - 6)
        noneStr:SetText("|cff7A7060Noch keine Titel freigeschaltet.|r")
        y = y - 22
    end

    y = y - 8

    -- ── Gesperrte Titel ────────────────────────────────────────────
    local lockedCount = totalCount - unlockedCount
    y = self:RenderAchCategoryHeader(parent, y,
        "Gesperrte Titel",
        "Interface\\RAIDFRAME\\ReadyCheck-NotReady",
        0, lockedCount,
        0.22, 0.04, 0.04)
    y = y - 2

    for _, t in ipairs(allTitles) do
        if not unlockedMap[t.id] then
            local row = CreateFrame("Frame", nil, parent)
            row:SetSize(326, 40)
            row:SetPoint("TOPLEFT", 4, y)

            local bg = row:CreateTexture(nil, "BACKGROUND")
            bg:SetAllPoints()
            bg:SetColorTexture(0.04, 0.03, 0.02, 0.10)

            local iconBorder = row:CreateTexture(nil, "ARTWORK")
            iconBorder:SetSize(28, 28); iconBorder:SetPoint("LEFT", 8, 0)
            iconBorder:SetColorTexture(0.12, 0.08, 0.06, 0.50)
            local ic = row:CreateTexture(nil, "OVERLAY")
            ic:SetTexture(t.icon or "Interface\\Icons\\INV_Crown_01")
            ic:SetSize(24, 24); ic:SetPoint("LEFT", 10, 0)
            ic:SetDesaturated(true); ic:SetAlpha(0.35)

            local nameStr = row:CreateFontString(nil, "OVERLAY")
            nameStr:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            nameStr:SetPoint("TOPLEFT", 42, -6)
            nameStr:SetShadowOffset(1, -1); nameStr:SetShadowColor(0, 0, 0, 0.15)
            nameStr:SetText("|cff7A7060" .. t.name .. "|r")

            local descStr = row:CreateFontString(nil, "OVERLAY")
            descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
            descStr:SetPoint("TOPLEFT", 42, -19)
            descStr:SetWidth(200); descStr:SetJustifyH("LEFT")
            descStr:SetTextColor(0.48, 0.40, 0.30)
            descStr:SetText(t.desc or t.requirement or "")

            local statusStr = row:CreateFontString(nil, "OVERLAY")
            statusStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
            statusStr:SetPoint("RIGHT", -6, 0)
            statusStr:SetShadowOffset(1, -1); statusStr:SetShadowColor(0, 0, 0, 0.15)
            statusStr:SetText("|cff664444|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t|r")

            local sep = row:CreateTexture(nil, "BORDER")
            sep:SetSize(316, 1); sep:SetPoint("BOTTOMLEFT", 5, 0)
            sep:SetColorTexture(0.22, 0.16, 0.08, 0.18)

            row:EnableMouse(true)
            row:SetScript("OnEnter", function(self)
                bg:SetColorTexture(0.08, 0.05, 0.03, 0.22)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                GameTooltip:SetText("|cff888888" .. t.name .. "|r", 1, 1, 1, true)
                if t.desc or t.requirement then
                    GameTooltip:AddLine(t.desc or t.requirement or "", 0.65, 0.55, 0.40, true)
                end
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cffAA4444Noch nicht freigeschaltet|r")
                GameTooltip:Show()
            end)
            row:SetScript("OnLeave", function(self)
                bg:SetColorTexture(0.04, 0.03, 0.02, 0.10)
                GameTooltip:Hide()
            end)
            y = y - 42
        end
    end

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

-- â•â•â• VIII. GEDENKHALLE â•â•â•
function UI:RenderMemorial()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end
    local Memorial = GDL:GetModule("Memorial")
    local LastWords = GDL:GetModule("LastWords")
    local myWords = LastWords and LastWords:GetPresetLastWords() or nil
    local wordsBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    wordsBtn:SetSize(200, 18)
    wordsBtn:SetPoint("TOPLEFT", 4, y)
    wordsBtn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 6, insets = {left=1,right=1,top=1,bottom=1}})
    wordsBtn:SetBackdropColor(0.15, 0.1, 0.06, 0.7)
    wordsBtn:SetBackdropBorderColor(0.4, 0.3, 0.18, 0.5)
    local wLbl = wordsBtn:CreateFontString(nil, "OVERLAY")
    wLbl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    wLbl:SetAllPoints()
    wLbl:SetJustifyH("CENTER")
    wLbl:SetText(myWords and ("|cff6A5A48Meine letzten Worte: \"" .. myWords:sub(1, 30) .. (myWords:len() > 30 and "..." or "") .. "\"|r") or "|cffA07840Meine letzten Worte festlegen...|r")
    wordsBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_LAST_WORDS"] = {
            text = "Deine letzten Worte festlegen\n(Erscheinen im Buch wenn du stirbst):",
            button1 = "Speichern",
            button2 = "Abbrechen",
            hasEditBox = true,
            editBoxWidth = 300,
            maxLetters = 200,
            OnShow = function(self)
                local eb = self.editBox or self.EditBox
                if eb then eb:SetText(myWords or "") end
            end,
            OnAccept = function(self)
                local eb = self.editBox or self.EditBox
                local text = eb and eb:GetText()
                if text and text ~= "" and LastWords then
                    LastWords:SetPresetLastWords(text)
                    UI:RenderChapter("memorial")
                end
            end,
            timeout = 0,
            whileDead = true,
            preferredIndex = 3,
        }
        StaticPopup_Show("GDL_LAST_WORDS")
    end)
    wordsBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Lege deine letzten Worte fest", 0.8, 0.7, 0.5)
        GameTooltip:AddLine("Diese erscheinen im Buch wenn du faellst.", 1, 1, 1, true)
        GameTooltip:Show()
    end)
    wordsBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    y = y - 22
    local deaths = guildData.deaths
    local recent = {}
    for i = #deaths, math.max(1, #deaths - 30), -1 do
        table.insert(recent, deaths[i])
    end
    if #recent == 0 then
        _, y = self:MakeBookLine(parent, y, "|cff684820Keine Gefallenen in der Gedenkhalle.|r", 10, "|cff684820")
    end
    if #recent >= 5 then
        local bloodStain = parent:CreateTexture(nil, "BACKGROUND")
        bloodStain:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
        bloodStain:SetSize(80, 80)
        bloodStain:SetPoint("TOPRIGHT", -10, -40)
        bloodStain:SetVertexColor(0.4, 0.05, 0.02, 0.15)
    end
    if #recent >= 15 then
        local bloodStain2 = parent:CreateTexture(nil, "BACKGROUND")
        bloodStain2:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
        bloodStain2:SetSize(60, 60)
        bloodStain2:SetPoint("BOTTOMLEFT", 20, 30)
        bloodStain2:SetVertexColor(0.35, 0.03, 0.01, 0.12)
    end
    for _, d in ipairs(recent) do
        local cc = ClassColorHex(d.class or d.classId)
        local icon = CLASS_ICONS[d.class or d.classId]
        local name = "|c" .. cc .. (d.name or "Unbekannt") .. "|r"
        local dateStr = d.date or (d.timestamp and date("%d.%m.%Y", d.timestamp) or "")
        local candleFrame = CreateFrame("Frame", nil, parent)
        candleFrame:SetSize(16, 16)
        candleFrame:SetPoint("TOPLEFT", 2, y - 2)
        local candleTex = candleFrame:CreateTexture(nil, "OVERLAY")
        candleTex:SetAllPoints()
        candleTex:SetTexture("Interface\\Minimap\\Tracking\\Innkeeper")
        candleTex:SetSize(14, 14)
        local hour = tonumber(date("%H")) or 12
        local nightAlpha = (hour >= 20 or hour < 6) and 1.0 or 0.6
        candleTex:SetAlpha(nightAlpha)
        _, y = self:MakeBookLine(parent, y, "    " .. name, 11)
        local subInfo = "Level " .. (d.level or "?") .. " | " .. (d.zone or "Unbekannt")
        if d.killerName then subInfo = subInfo .. " | Getoetet von: " .. d.killerName end
        _, y = self:MakeBookLine(parent, y, subInfo, 9, "|cff785838")
        local nachruf = nil
        if Memorial and Memorial.GetMemorialForDeath then
            local result = Memorial:GetMemorialForDeath(d)
            if type(result) == "table" then
                nachruf = result.memorial
            elseif type(result) == "string" then
                nachruf = result
            end
        end
        local customKey = (d.name or "") .. "_" .. (d.timestamp or 0)
        local customNachruf = GuildDeathLogDB and GuildDeathLogDB.customNachrufe and GuildDeathLogDB.customNachrufe[customKey]
        if customNachruf and customNachruf ~= "" then
            nachruf = customNachruf
        end
        if nachruf and nachruf ~= "" then
            _, y = self:MakeBookLine(parent, y, '"' .. nachruf .. '"', 9, "|cff685040")
        else
            local defaultNachruf = (d.name or "Ein Held") .. " fiel in " .. (d.zone or "der Wildnis") .. ". Ruhe in Frieden."
            _, y = self:MakeBookLine(parent, y, '"' .. defaultNachruf .. '"', 9, "|cff685040")
        end
        local editFrame = CreateFrame("Frame", nil, parent)
        editFrame:SetSize(310, 14)
        editFrame:SetPoint("TOPLEFT", 4, y)
        local editBtn = CreateFrame("Button", nil, editFrame)
        editBtn:SetSize(14, 14)
        editBtn:SetPoint("LEFT", 0, 0)
        editBtn:SetNormalTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up")
        editBtn:SetHighlightTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up")
        local deathRef = d
        editBtn:SetScript("OnClick", function()
            local deathKey = (deathRef.name or "") .. "_" .. (deathRef.timestamp or 0)
            StaticPopupDialogs["GDL_EDIT_NACHRUF"] = {
                text = "Nachruf fuer " .. (deathRef.name or "?") .. " schreiben:",
                button1 = "Speichern",
                button2 = "Abbrechen",
                hasEditBox = true,
                editBoxWidth = 300,
                maxLetters = 300,
                OnShow = function(self)
                    local eb = self.editBox or self.EditBox
                    if eb then
                        local existing = GuildDeathLogDB and GuildDeathLogDB.customNachrufe and GuildDeathLogDB.customNachrufe[deathKey]
                        eb:SetText(existing or "")
                    end
                end,
                OnAccept = function(self)
                    local eb = self.editBox or self.EditBox
                    local text = eb and eb:GetText()
                    if text and text ~= "" then
                        GuildDeathLogDB = GuildDeathLogDB or {}
                        GuildDeathLogDB.customNachrufe = GuildDeathLogDB.customNachrufe or {}
                        GuildDeathLogDB.customNachrufe[deathKey] = text
                        UI:RenderChapter("memorial")
                    end
                end,
                timeout = 0,
                whileDead = true,
                preferredIndex = 3,
            }
            StaticPopup_Show("GDL_EDIT_NACHRUF")
        end)
        editBtn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:SetText("Nachruf bearbeiten", 0.8, 0.7, 0.5)
            GameTooltip:Show()
        end)
        editBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        local dateTxt = editFrame:CreateFontString(nil, "OVERLAY")
        dateTxt:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
        dateTxt:SetPoint("LEFT", 18, 0)
        dateTxt:SetText("|cff7A6A58" .. dateStr .. "|r")
        y = y - 14
        local sepLine = parent:CreateTexture(nil, "ARTWORK")
        sepLine:SetSize(270, 1)
        sepLine:SetPoint("TOPLEFT", 20, y)
        sepLine:SetColorTexture(0.55, 0.40, 0.18, 0.22)
        y = y - 10
    end
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• IX. GILDEN-STATISTIKEN â•â•â•
function UI:RenderGuildStats()
    local parent = self.mainFrame.scrollChild
    local GS = GDL:GetModule("GuildStats")
    if not GS then return end
    local y = 0
    local guildData = GDL:GetGuildData()
    local deaths = guildData and guildData.deaths or {}
    if #deaths > 0 then
        _, y = self:MakeBookLine(parent, y, "|cffD4AA5E-- Gilden-Rekorde --|r", 11, "|cffD4AA5E")
        y = y - 4
        local highestLvl, highestName = 0, ""
        local lowestLvl, lowestName = 999, ""
        local deadliestDay, deadliestCount = "", 0
        local dayCount = {}
        local firstDeath, lastDeath = nil, nil
        for _, d in ipairs(deaths) do
            local lvl = d.level or 0
            if lvl > highestLvl then highestLvl = lvl; highestName = d.name or "?" end
            if lvl < lowestLvl and lvl > 0 then lowestLvl = lvl; lowestName = d.name or "?" end
            if d.timestamp then
                local dayKey = date("%d.%m.%Y", d.timestamp)
                dayCount[dayKey] = (dayCount[dayKey] or 0) + 1
                if dayCount[dayKey] > deadliestCount then deadliestCount = dayCount[dayKey]; deadliestDay = dayKey end
                if not firstDeath or d.timestamp < firstDeath.timestamp then firstDeath = d end
                if not lastDeath or d.timestamp > lastDeath.timestamp then lastDeath = d end
            end
        end
        local records = {
            {icon = "Interface\\Icons\\Spell_Holy_BorrowedTime", label = "Hoechster Level-Tod", value = highestName .. " (Lvl " .. highestLvl .. ")"},
            {icon = "Interface\\Icons\\Ability_Rogue_FeignDeath", label = "Niedrigster Level-Tod", value = lowestName .. " (Lvl " .. (lowestLvl < 999 and lowestLvl or "?") .. ")"},
            {icon = "Interface\\Icons\\Spell_Shadow_DeathAndDecay", label = "Toedlichster Tag", value = deadliestDay .. " (" .. deadliestCount .. " Tode)"},
        }
        if firstDeath then
            table.insert(records, {icon = "Interface\\Icons\\INV_Misc_Book_09", label = "Erster Eintrag", value = (firstDeath.name or "?") .. " am " .. date("%d.%m.%y", firstDeath.timestamp)})
        end
        if lastDeath then
            table.insert(records, {icon = "Interface\\Icons\\Spell_Shadow_Requiem", label = "Letzter Eintrag", value = (lastDeath.name or "?") .. " am " .. date("%d.%m.%y", lastDeath.timestamp)})
        end
        for _, r in ipairs(records) do
            _, y = self:MakeBookRow(parent, y, r.icon, "|cffD4AA5E" .. r.label .. "|r", "|cffE8C870" .. r.value .. "|r", "")
        end
        y = y - 10
    end
    local deathsPerChar = GS:GetDeathsPerCharacter()
    _, y = self:MakeBookLine(parent, y, "|cffD4AA5E-- Tode pro Charakter (" .. #deathsPerChar .. ") --|r", 11, "|cffD4AA5E")
    y = y - 4
    for _, d in ipairs(deathsPerChar) do
        local lastInfo = ""
        if d.deaths and #d.deaths > 0 then
            local last = d.deaths[#d.deaths]
            if last.timestamp then
                lastInfo = date("%d.%m.%y", last.timestamp)
            end
            if last.zone then
                lastInfo = lastInfo ~= "" and (lastInfo .. " - " .. last.zone) or last.zone
            end
        end
        _, y = self:MakeBookRow(parent, y, nil, "|cffE8C870" .. d.name .. "|r", "|cffC8A068Tode: " .. d.count .. "|r", lastInfo ~= "" and ("|cffB8A880" .. lastInfo .. "|r") or nil)
    end
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• XIII. ERRUNGENSCHAFTEN â•â•â•
function UI:RenderAchievements()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local Ach = GDL:GetModule("Achievements")
    if not Ach then return end

    -- â”€â”€ Gilden-Stärke Banner â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local totalLevel, alive = Ach:GetGuildPowerLevel()
    local banner = CreateFrame("Frame", nil, parent)
    banner:SetSize(330, 32)
    banner:SetPoint("TOPLEFT", 0, y)
    local bannerBg = banner:CreateTexture(nil, "BACKGROUND")
    bannerBg:SetAllPoints()
    bannerBg:SetColorTexture(0.12, 0.08, 0.03, 0.35)
    local bannerIcon = banner:CreateTexture(nil, "ARTWORK")
    bannerIcon:SetTexture("Interface\\Icons\\Achievement_Arena_5v5_5")
    bannerIcon:SetSize(22, 22)
    bannerIcon:SetPoint("LEFT", 6, 0)
    local bannerLbl = banner:CreateFontString(nil, "OVERLAY")
    bannerLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    bannerLbl:SetPoint("LEFT", 34, 1)
    bannerLbl:SetShadowOffset(1, -1)
    bannerLbl:SetShadowColor(0, 0, 0, 0.15)
    bannerLbl:SetText("|cffDDB060Power-Level:  |cffFFD100" .. totalLevel .. "|r")
    local bannerSub = banner:CreateFontString(nil, "OVERLAY")
    bannerSub:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    bannerSub:SetPoint("RIGHT", -8, 0)
    bannerSub:SetTextColor(0.5, 0.42, 0.3)
    bannerSub:SetText(alive .. " Mitglieder")
    y = y - 38

    -- â”€â”€ Trennlinie â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local div0 = parent:CreateTexture(nil, "ARTWORK")
    div0:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    div0:SetSize(330, 6)
    div0:SetPoint("TOPLEFT", 0, y + 2)
    div0:SetVertexColor(0.4, 0.3, 0.18, 0.5)
    y = y - 10

    -- â”€â”€ Persönliche Errungenschaften â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local unlocked, total = Ach:GetUnlockedCount()
    y = self:RenderAchCategoryHeader(parent, y,
        "Persönliche Errungenschaften",
        "Interface\\Icons\\Achievement_PVP_A_01",
        unlocked, total,
        0.35, 0.22, 0.08)
    y = y - 4
    for _, a in ipairs(Ach:GetAllAchievements()) do
        y = self:RenderAchRow(parent, y, a, Ach:IsUnlocked(a.id), false)
    end
    y = y - 14

    -- â”€â”€ Trennlinie â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local div1 = parent:CreateTexture(nil, "ARTWORK")
    div1:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    div1:SetSize(330, 6)
    div1:SetPoint("TOPLEFT", 0, y + 3)
    div1:SetVertexColor(0.15, 0.35, 0.15, 0.6)
    y = y - 12

    -- â”€â”€ Gilden-Errungenschaften mit Kategorien â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local gUnlocked, gTotal = Ach:GetGuildUnlockedCount()
    local gAllById = {}
    for _, a in ipairs(Ach:GetAllGuildAchievements()) do
        gAllById[a.id] = a
    end

    local GUILD_CATS = {
        {
            title = "Zusammen gekämpft",
            icon  = "Interface\\Icons\\Ability_Warrior_Cleave",
            ids   = {"g_dungeon_3","g_dungeon_10","g_raid_10","g_full_party","g_wipe_back"},
            r, g, b = 0.08, 0.28, 0.10,
        },
        {
            title = "Stärke der Gilde",
            icon  = "Interface\\Icons\\INV_Shield_36",
            ids   = {"g_nodeaths_7d","g_nodeaths_30d","g_10_online","g_50_members","g_5_maxlevel"},
            r, g, b = 0.08, 0.20, 0.32,
        },
        {
            title = "Geschichte & Schicksal",
            icon  = "Interface\\Icons\\INV_Misc_Book_09",
            ids   = {"g_old_30d","g_100_deaths","g_many_classes","g_wipe_3same"},
            r, g, b = 0.28, 0.14, 0.04,
        },
    }

    -- Gilden-Ach Haupt-Header
    y = self:RenderAchCategoryHeader(parent, y,
        "Gilden-Errungenschaften",
        "Interface\\Icons\\INV_Misc_GroupNeedMore",
        gUnlocked, gTotal,
        0.08, 0.30, 0.08)
    y = y - 4

    for _, cat in ipairs(GUILD_CATS) do
        -- Sub-Kategorie-Header
        local catFrame = CreateFrame("Frame", nil, parent)
        catFrame:SetSize(326, 20)
        catFrame:SetPoint("TOPLEFT", 4, y)
        local catBg = catFrame:CreateTexture(nil, "BACKGROUND")
        catBg:SetAllPoints()
        catBg:SetColorTexture(cat.r or 0.08, cat.g or 0.28, cat.b or 0.10, 0.22)
        local catIc = catFrame:CreateTexture(nil, "ARTWORK")
        catIc:SetTexture(cat.icon)
        catIc:SetSize(14, 14)
        catIc:SetPoint("LEFT", 4, 0)
        local catLbl = catFrame:CreateFontString(nil, "OVERLAY")
        catLbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        catLbl:SetPoint("LEFT", 22, 0)
        catLbl:SetShadowOffset(1, -1)
        catLbl:SetShadowColor(0, 0, 0, 0.15)
        catLbl:SetText("|cffAADDAA" .. cat.title .. "|r")

        -- Fortschritt der Kategorie
        local catCount, catTotal = 0, #cat.ids
        for _, id in ipairs(cat.ids) do
            if Ach:IsGuildUnlocked(id) then catCount = catCount + 1 end
        end
        local catProgress = catFrame:CreateFontString(nil, "OVERLAY")
        catProgress:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        catProgress:SetPoint("RIGHT", -4, 0)
        catProgress:SetText("|cff556655" .. catCount .. "/" .. catTotal .. "|r")
        y = y - 22

        for _, id in ipairs(cat.ids) do
            local a = gAllById[id]
            if a then
                y = self:RenderAchRow(parent, y, a, Ach:IsGuildUnlocked(id), true)
            end
        end
        y = y - 8
    end

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

-- â”€â”€ Kategorie-Header â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
function UI:RenderAchCategoryHeader(parent, y, title, icon, unlocked, total, r, g, b)
    local hf = CreateFrame("Frame", nil, parent)
    hf:SetSize(330, 30)
    hf:SetPoint("TOPLEFT", 0, y)

    local hbg = hf:CreateTexture(nil, "BACKGROUND")
    hbg:SetAllPoints()
    hbg:SetColorTexture(r or 0.18, g or 0.12, b or 0.04, 0.50)

    local shimmer = hf:CreateTexture(nil, "BACKGROUND")
    shimmer:SetPoint("TOPLEFT"); shimmer:SetPoint("TOPRIGHT"); shimmer:SetHeight(15)
    shimmer:SetColorTexture((r or 0.28) * 1.5, (g or 0.20) * 1.5, (b or 0.06) * 1.5, 0.10)
    shimmer:SetBlendMode("ADD")

    local topLine = hf:CreateTexture(nil, "BORDER")
    topLine:SetHeight(2); topLine:SetPoint("TOPLEFT"); topLine:SetPoint("TOPRIGHT")
    topLine:SetColorTexture(r or 0.55, g or 0.40, b or 0.12, 0.70)

    local botLine = hf:CreateTexture(nil, "BORDER")
    botLine:SetSize(330, 1); botLine:SetPoint("BOTTOMLEFT")
    botLine:SetColorTexture(r or 0.35, g or 0.25, b or 0.08, 0.40)

    local leftAccent = hf:CreateTexture(nil, "ARTWORK")
    leftAccent:SetSize(3, 24)
    leftAccent:SetPoint("LEFT", 0, 0)
    leftAccent:SetColorTexture(r or 0.70, g or 0.52, b or 0.15, 0.90)

    local hic = hf:CreateTexture(nil, "ARTWORK")
    hic:SetTexture(icon)
    hic:SetSize(20, 20)
    hic:SetPoint("LEFT", 8, 0)

    local htxt = hf:CreateFontString(nil, "OVERLAY")
    htxt:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    htxt:SetPoint("LEFT", 32, 1)
    htxt:SetShadowOffset(1, -1)
    htxt:SetShadowColor(0, 0, 0, 0.15)
    htxt:SetText("|cffF0C060" .. title .. "|r")

    local hcount = hf:CreateFontString(nil, "OVERLAY")
    hcount:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    hcount:SetPoint("RIGHT", -8, 0)
    local pct = total > 0 and math.floor(unlocked/total*100) or 0
    local ccolor = pct == 100 and "|cff44FF88" or (pct >= 50 and "|cffFFD100" or "|cff7A7060")
    hcount:SetText(ccolor .. unlocked .. "/" .. total .. "|r")
    hcount:SetShadowOffset(1, -1); hcount:SetShadowColor(0, 0, 0, 0.15)

    return y - 34
end

-- â”€â”€ Achievement-Zeile (komplett neu, 40px) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
function UI:RenderAchRow(parent, y, a, isUnlocked, isGuild)
    local ROW_H = 40

    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(326, ROW_H)
    row:SetPoint("TOPLEFT", 4, y)

    -- Hintergrund
    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    if isUnlocked then
        if isGuild then
            bg:SetColorTexture(0.05, 0.18, 0.06, 0.22)
        else
            bg:SetColorTexture(0.20, 0.14, 0.03, 0.18)
        end
    else
        bg:SetColorTexture(0.04, 0.03, 0.02, 0.10)
    end

    -- Untere Trennlinie
    local sep = row:CreateTexture(nil, "BORDER")
    sep:SetSize(316, 1)
    sep:SetPoint("BOTTOMLEFT", 5, 0)
    sep:SetColorTexture(0.25, 0.20, 0.12, 0.20)

    -- Icon (24x24) mit dünnem Rahmen-Effekt
    local iconBorder = row:CreateTexture(nil, "ARTWORK")
    iconBorder:SetSize(28, 28)
    iconBorder:SetPoint("LEFT", 4, 0)
    if isUnlocked then
        iconBorder:SetColorTexture(isGuild and 0.08 or 0.55, isGuild and 0.40 or 0.42, isGuild and 0.04 or 0.1, 0.55)
    else
        iconBorder:SetColorTexture(0.10, 0.08, 0.05, 0.50)
    end
    local ic = row:CreateTexture(nil, "OVERLAY")
    ic:SetTexture(a.icon)
    ic:SetSize(24, 24)
    ic:SetPoint("LEFT", 6, 0)
    if not isUnlocked then
        ic:SetDesaturated(true)
        ic:SetAlpha(0.35)
    end

    -- Name
    local nameStr = row:CreateFontString(nil, "OVERLAY")
    nameStr:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    nameStr:SetPoint("TOPLEFT", 38, -6)
    nameStr:SetShadowOffset(1, -1)
    nameStr:SetShadowColor(0, 0, 0, 0.15)
    if isUnlocked then
        local nc = isGuild and "|cff88DDAA" or "|cffF0C060"
        nameStr:SetText(nc .. a.name .. "|r")
    else
        nameStr:SetText("|cff7A7060" .. a.name .. "|r")
    end

    -- Beschreibung
    local descStr = row:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    descStr:SetPoint("TOPLEFT", 38, -19)
    descStr:SetWidth(200)
    descStr:SetJustifyH("LEFT")
    if isUnlocked then
        descStr:SetTextColor(isGuild and 0.45 or 0.68, isGuild and 0.68 or 0.55, isGuild and 0.45 or 0.32)
    else
        descStr:SetTextColor(0.48, 0.42, 0.32)
    end
    descStr:SetText(a.desc or "")

    -- Status-Indikator (kein Kasten, nur Icon)
    local statusStr = row:CreateFontString(nil, "OVERLAY")
    statusStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    statusStr:SetPoint("RIGHT", -6, 0)
    statusStr:SetShadowOffset(1, -1)
    statusStr:SetShadowColor(0, 0, 0, 0.15)
    if isUnlocked then
        local sc = isGuild and "|cff44FF88" or "|cff88FF44"
        statusStr:SetText(sc .. "|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
    else
        statusStr:SetText("|cff664444|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t|r")
    end

    -- Hover: Hintergrund heller + Tooltip
    row:EnableMouse(true)
    row:SetScript("OnEnter", function(self)
        if isUnlocked then
            if isGuild then
                bg:SetColorTexture(0.05, 0.24, 0.07, 0.38)
            else
                bg:SetColorTexture(0.24, 0.16, 0.04, 0.38)
            end
        else
            bg:SetColorTexture(0.08, 0.05, 0.03, 0.22)
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        local tc = isGuild and "|cff55FF99" or "|cffFFD100"
        GameTooltip:SetText(tc .. a.name .. "|r", 1, 1, 1, true)
        if a.desc and a.desc ~= "" then
            GameTooltip:AddLine(a.desc, 0.78, 0.68, 0.48, true)
        end
        if isGuild then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffAADDAA[Gilden-Errungenschaft]|r", 0.4, 0.8, 0.4)
            GameTooltip:AddLine("Nur gemeinsam erreichbar", 0.55, 0.65, 0.55, true)
        end
        GameTooltip:AddLine(" ")
        if isUnlocked then
            GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t |cff00FF66Freigeschaltet|r")
        else
            GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t |cffAA4444Noch nicht erreicht|r")
        end
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        if isUnlocked then
            if isGuild then
                bg:SetColorTexture(0.05, 0.18, 0.06, 0.22)
            else
                bg:SetColorTexture(0.20, 0.14, 0.03, 0.18)
            end
        else
            bg:SetColorTexture(0.04, 0.03, 0.02, 0.10)
        end
        GameTooltip:Hide()
    end)

    return y - (ROW_H + 2)
end



-- â•â•â• XIV. SUPPORTER+ â•â•â•
function UI:RenderSupporterPage()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local isActive = GDL:IsSupporterActive()
    local tierInfo = GDL:GetSupporterTierInfo()
    local expiry = GDL:GetSupporterExpiry()

    local statusRow = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    statusRow:SetSize(300, 40)
    statusRow:SetPoint("TOPLEFT", 4, y)
    statusRow:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets = {left = 2, right = 2, top = 2, bottom = 2},
    })
    if isActive then
        statusRow:SetBackdropColor(0.08, 0.15, 0.06, 0.9)
        statusRow:SetBackdropBorderColor(0.4, 0.7, 0.3, 0.8)
    else
        statusRow:SetBackdropColor(0.15, 0.08, 0.06, 0.9)
        statusRow:SetBackdropBorderColor(0.6, 0.3, 0.2, 0.8)
    end
    local statusIcon = statusRow:CreateTexture(nil, "ARTWORK")
    statusIcon:SetSize(26, 26)
    statusIcon:SetPoint("LEFT", 6, 0)
    statusIcon:SetTexture("Interface\\Icons\\INV_ValentinesCard01")
    if not isActive then statusIcon:SetDesaturated(true) end
    local statusTxt = statusRow:CreateFontString(nil, "OVERLAY")
    statusTxt:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    statusTxt:SetPoint("LEFT", statusIcon, "RIGHT", 8, 0)
    if isActive and tierInfo then
        statusTxt:SetText("|cff00FF00Aktiv|r - " .. tierInfo.color .. tierInfo.label .. "|r  |cff8A7A68bis " .. (expiry or "?") .. "|r")
    else
        statusTxt:SetText("|cffCC3333Nicht aktiv|r  |cff8A7A68Kein Supporter-Code hinterlegt|r")
    end
    y = y - 48

    _, y = self:MakeBookLine(parent, y, "|cff785030-- Was ist Supporter+? --|r", 11, "|cff785030")
    y = y - 4
    _, y = self:MakeBookLine(parent, y, "Dieses Addon ist und bleibt kostenlos.", 10, "|cff785030")
    y = y - 2
    _, y = self:MakeBookLine(parent, y, "Als freiwillige Unterstuetzung kannst du ueber Patreon einen Monatsbeitrag leisten. Als Dank schaltest du rein kosmetische Extras frei:", 9, "|cff684820")
    y = y - 6

    local perks = {
        {icon = "Interface\\Icons\\INV_Crown_01", text = "Eigene Titel auf Nameplates & Tooltips", tier = "B"},
        {icon = "Interface\\Icons\\Achievement_General_StayClassy", text = "Vollstaendiges Meilenstein-System", tier = "B"},
        {icon = "Interface\\Icons\\Spell_Holy_AuraOfLight", text = "Exklusive Rahmen in der Ruhmeshalle", tier = "S"},
        {icon = "Interface\\Icons\\INV_Misc_Map_01", text = "Erweiterte Todeskarten-Filter", tier = "S"},
        {icon = "Interface\\Icons\\Achievement_Arena_5v5_5", text = "Gold-Rang im Gilden-Power-Level", tier = "G"},
    }
    for _, p in ipairs(perks) do
        local pRow = CreateFrame("Frame", nil, parent)
        pRow:SetSize(300, 22)
        pRow:SetPoint("TOPLEFT", 4, y)
        local pIcon = pRow:CreateTexture(nil, "ARTWORK")
        pIcon:SetTexture(p.icon)
        pIcon:SetSize(18, 18)
        pIcon:SetPoint("LEFT", 4, 0)
        local hasAccess = isActive and GDL:HasSupporterTier(p.tier)
        if not hasAccess then pIcon:SetDesaturated(true); pIcon:SetAlpha(0.5) end
        local pTxt = pRow:CreateFontString(nil, "OVERLAY")
        pTxt:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        pTxt:SetPoint("LEFT", 26, 0)
        pTxt:SetShadowOffset(1, -1)
        pTxt:SetShadowColor(0, 0, 0, 0.15)
        local tierLabel = ({B = "Bronze", S = "Silber", G = "Gold"})[p.tier] or p.tier
        local tierColor = ({B = "|cffCD7F32", S = "|cffC0C0C0", G = "|cffFFD700"})[p.tier] or "|cffBB9944"
        local color = hasAccess and "|cff785030" or "|cff665544"
        pTxt:SetText(color .. p.text .. "  " .. tierColor .. "[" .. tierLabel .. "]|r")
        y = y - 22
    end
    y = y - 6

    _, y = self:MakeBookLine(parent, y, "|cff583020Kein Gameplay-Vorteil. Kein Pay-to-Win.|r", 10, "|cff583020")
    y = y - 8

    _, y = self:MakeBookLine(parent, y, "|cff785030-- Supporter-Code einloesen --|r", 11, "|cff785030")
    y = y - 4
    _, y = self:MakeBookLine(parent, y, "Discord-ID (aus der Bot-DM):", 10, "|cff684820")
    y = y - 2

    local discordEB = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    discordEB:SetAutoFocus(false)
    discordEB:SetSize(200, 20)
    discordEB:SetPoint("TOPLEFT", 8, y)
    discordEB:SetMaxLetters(20)
    discordEB:SetNumeric(true)
    discordEB:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    if GuildDeathLogDB and GuildDeathLogDB.discordId then
        discordEB:SetText(GuildDeathLogDB.discordId)
    end
    discordEB:SetScript("OnEditFocusLost", function(self)
        GuildDeathLogDB.discordId = self:GetText()
    end)
    y = y - 26

    _, y = self:MakeBookLine(parent, y, "Supporter-Code:", 10, "|cff684820")
    y = y - 2

    local codeEB = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    codeEB:SetAutoFocus(false)
    codeEB:SetSize(280, 20)
    codeEB:SetPoint("TOPLEFT", 8, y)
    codeEB:SetMaxLetters(40)
    codeEB:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    y = y - 26

    local redeemBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    redeemBtn:SetSize(130, 24)
    redeemBtn:SetPoint("TOPLEFT", 8, y)
    redeemBtn:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets = {left = 2, right = 2, top = 2, bottom = 2},
    })
    redeemBtn:SetBackdropColor(0.2, 0.15, 0.05, 0.95)
    redeemBtn:SetBackdropBorderColor(0.7, 0.55, 0.2, 1)
    local redeemLbl = redeemBtn:CreateFontString(nil, "OVERLAY")
    redeemLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    redeemLbl:SetPoint("CENTER", 0, 1)
    redeemLbl:SetText("|cffFFD100Code einloesen|r")
    redeemBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.3, 0.22, 0.08, 1)
        self:SetBackdropBorderColor(0.9, 0.7, 0.3, 1)
    end)
    redeemBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.2, 0.15, 0.05, 0.95)
        self:SetBackdropBorderColor(0.7, 0.55, 0.2, 1)
    end)
    redeemBtn:SetScript("OnClick", function()
        local code = codeEB:GetText()
        if code and code ~= "" then
            GuildDeathLogDB.discordId = discordEB:GetText()
            local ok, msg = GDL:ValidateSupporterCode(code)
            if ok then
                GDL:Print("|cff00FF00" .. (msg or "Supporter aktiviert!") .. "|r")
            else
                GDL:Print("|cffFF0000" .. (msg or "Ungueltiger Code!") .. "|r")
            end
            UI:RenderChapter("supporter")
        else
            GDL:Print("|cffFF8800Bitte einen Code eingeben!|r")
        end
    end)
    y = y - 30

    _, y = self:MakeBookLine(parent, y, "|cff785030-- Patreon --|r", 11, "|cff785030")
    y = y - 4

    local patreonBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    patreonBtn:SetSize(220, 24)
    patreonBtn:SetPoint("TOPLEFT", 8, y)
    patreonBtn:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets = {left = 2, right = 2, top = 2, bottom = 2},
    })
    patreonBtn:SetBackdropColor(0.15, 0.08, 0.18, 0.95)
    patreonBtn:SetBackdropBorderColor(0.6, 0.3, 0.7, 1)
    local patreonLbl = patreonBtn:CreateFontString(nil, "OVERLAY")
    patreonLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    patreonLbl:SetPoint("CENTER", 0, 1)
    patreonLbl:SetText("|cffFF6B9DPatreon-Link in Chat kopieren|r")
    patreonBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.22, 0.12, 0.25, 1)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText("Patreon Link", 1, 0.42, 0.62)
        GameTooltip:AddLine("Klicke um den Link in den Chat zu kopieren", 1, 1, 1, true)
        GameTooltip:Show()
    end)
    patreonBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(0.15, 0.08, 0.18, 0.95)
        GameTooltip:Hide()
    end)
    patreonBtn:SetScript("OnClick", function()
        GDL:Print("|cffFF6B9D[Patreon]|r https://www.patreon.com/PsYke86")
    end)
    y = y - 32

    _, y = self:MakeBookLine(parent, y, "|cff8A7A68Vielen Dank fuer deine Unterstuetzung!|r", 9, "|cff8A7A68")

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

-- â•â•â• X. GILDEN-REGELN â•â•â•
function UI:RenderGuildRules()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local guildName = GDL.currentGuildName
    local rules = guildName and GuildDeathLogDB.guildRules and GuildDeathLogDB.guildRules[guildName]
    if rules and rules ~= "" then
        local lines = {strsplit("\n", rules)}
        for _, line in ipairs(lines) do
            _, y = self:MakeBookLine(parent, y, line, 10, "|cff684820")
        end
    else
        _, y = self:MakeBookLine(parent, y, "Keine Gilden-Regeln festgelegt.", 10, "|cff6A5A48")
    end
    y = y - 12
    local editBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    editBtn:SetSize(140, 22)
    editBtn:SetPoint("TOPLEFT", 8, y)
    editBtn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 8, insets = {left=2,right=2,top=2,bottom=2}})
    editBtn:SetBackdropColor(0.15, 0.12, 0.08, 0.8)
    editBtn:SetBackdropBorderColor(0.4, 0.3, 0.2, 0.6)
    local label = editBtn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    label:SetAllPoints()
    label:SetText("|cffC8A050Regeln bearbeiten|r")
    label:SetJustifyH("CENTER")
    editBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_EDIT_RULES"] = {
            text = "Gilden-Regeln eingeben:",
            button1 = "Speichern",
            button2 = "Abbrechen",
            hasEditBox = true,
            editBoxWidth = 300,
            maxLetters = 1000,
            OnShow = function(self)
                local eb = self.editBox or self.EditBox
                if eb then
                    eb:SetText(rules or "")
                    eb:SetMultiLine(true)
                end
            end,
            OnAccept = function(self)
                local eb = self.editBox or self.EditBox
                local text = eb and eb:GetText()
                if not GuildDeathLogDB.guildRules then GuildDeathLogDB.guildRules = {} end
                GuildDeathLogDB.guildRules[guildName] = text
                local GR = GDL:GetModule("GuildRules")
                if GR and GR.BroadcastRules then GR:BroadcastRules() end
                UI:RenderChapter("guildrules")
            end,
            timeout = 0,
            whileDead = true,
            preferredIndex = 3,
        }
        StaticPopup_Show("GDL_EDIT_RULES")
    end)
    editBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(0.25, 0.2, 0.12, 0.9) end)
    editBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.15, 0.12, 0.08, 0.8) end)
    y = y - 28
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• XI. KALENDER â•â•â•
function UI:RenderCalendar()
    local parent = self.mainFrame.scrollChild
    local Cal = GDL:GetModule("Calendar")
    if not Cal then return end
    local y = 0
    local events = Cal:GetEvents()
    if events and #events > 0 then
        for idx, e in ipairs(events) do
            local typeColor = "|cff583020"
            if e.eventType == "raid" then typeColor = "|cffFF4444"
            elseif e.eventType == "pvp" then typeColor = "|cffFF8800"
            elseif e.eventType == "social" then typeColor = "|cff00CC00" end
            local dateStr = e.timestamp and date("%d.%m.%y %H:%M", e.timestamp) or ""
            local row, newY = self:MakeBookRow(parent, y, nil, typeColor .. (e.name or "Event") .. "|r", e.description or "", dateStr)
            local delBtn = CreateFrame("Button", nil, row)
            delBtn:SetSize(14, 14)
            delBtn:SetPoint("RIGHT", -2, 0)
            delBtn:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
            delBtn:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Highlight")
            delBtn:SetScript("OnClick", function()
                if Cal.DeleteEvent and e.id then
                    Cal:DeleteEvent(e.id)
                    UI:RenderChapter("calendar")
                end
            end)
            delBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_TOP")
                GameTooltip:SetText("Event loeschen", 1, 0.3, 0.3)
                GameTooltip:Show()
            end)
            delBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
            y = newY
            local myResponse = Cal:GetMyResponse(e.id)
            local btnFrame = CreateFrame("Frame", nil, parent)
            btnFrame:SetSize(310, 18)
            btnFrame:SetPoint("TOPLEFT", 24, y)
            local function MakeResponseBtn(parentFrame, xOff, text, color, hoverColor, response)
                local btn = CreateFrame("Button", nil, parentFrame, "BackdropTemplate")
                btn:SetSize(70, 16)
                btn:SetPoint("LEFT", xOff, 0)
                btn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 6, insets = {left=1,right=1,top=1,bottom=1}})
                local isActive = (myResponse == response)
                if isActive then
                    btn:SetBackdropColor(color[1], color[2], color[3], 0.6)
                    btn:SetBackdropBorderColor(color[1], color[2], color[3], 0.9)
                else
                    btn:SetBackdropColor(0.12, 0.1, 0.06, 0.7)
                    btn:SetBackdropBorderColor(0.3, 0.25, 0.18, 0.5)
                end
                local lbl = btn:CreateFontString(nil, "OVERLAY")
                lbl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
                lbl:SetAllPoints()
                lbl:SetJustifyH("CENTER")
                local activeMarker = isActive and " *" or ""
                lbl:SetText(string.format("|cff%02x%02x%02x%s%s|r", color[1]*255, color[2]*255, color[3]*255, text, activeMarker))
                btn:SetScript("OnClick", function()
                    Cal:RespondToEvent(e.id, response)
                    UI:RenderChapter("calendar")
                end)
                btn:SetScript("OnEnter", function(self)
                    self:SetBackdropColor(hoverColor[1], hoverColor[2], hoverColor[3], 0.5)
                end)
                btn:SetScript("OnLeave", function(self)
                    if myResponse == response then
                        self:SetBackdropColor(color[1], color[2], color[3], 0.6)
                    else
                        self:SetBackdropColor(0.12, 0.1, 0.06, 0.7)
                    end
                end)
                return btn
            end
            MakeResponseBtn(btnFrame, 0, "Zusagen", {0, 0.8, 0}, {0, 1, 0}, "yes")
            MakeResponseBtn(btnFrame, 74, "Vielleicht", {0.8, 0.6, 0}, {1, 0.8, 0}, "maybe")
            MakeResponseBtn(btnFrame, 148, "Absagen", {0.8, 0.2, 0.2}, {1, 0.3, 0.3}, "no")
            y = y - 20
            local counts = Cal:GetResponseCounts(e)
            local parts = {}
            if #counts.yes > 0 then table.insert(parts, "|cff00CC00" .. #counts.yes .. " Zusagen|r") end
            if #counts.maybe > 0 then table.insert(parts, "|cffCCA000" .. #counts.maybe .. " Vielleicht|r") end
            if #counts.no > 0 then table.insert(parts, "|cffCC3333" .. #counts.no .. " Absagen|r") end
            if #parts > 0 then
                _, y = self:MakeBookLine(parent, y, table.concat(parts, "  |  "), 8, "|cff6A5A48")
            end
            if #counts.yes > 0 then
                local names = {}
                for _, n in ipairs(counts.yes) do table.insert(names, n) end
                _, y = self:MakeBookLine(parent, y, "|cff00AA00Dabei: " .. table.concat(names, ", ") .. "|r", 7, "|cff00AA00")
            end
            y = y - 8
        end
    else
        _, y = self:MakeBookLine(parent, y, "Keine Events geplant.", 10, "|cff6A5A48")
    end
    y = y - 12
    local addBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    addBtn:SetSize(130, 22)
    addBtn:SetPoint("TOPLEFT", 8, y)
    addBtn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 8, insets = {left=2,right=2,top=2,bottom=2}})
    addBtn:SetBackdropColor(0.15, 0.12, 0.08, 0.8)
    addBtn:SetBackdropBorderColor(0.4, 0.3, 0.2, 0.6)
    local label = addBtn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    label:SetAllPoints()
    label:SetText("|cffC8A050+ Neues Event|r")
    label:SetJustifyH("CENTER")
    addBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_ADD_EVENT"] = {
            text = "Event-Name eingeben:",
            button1 = "Erstellen",
            button2 = "Abbrechen",
            hasEditBox = true,
            editBoxWidth = 250,
            OnAccept = function(self)
                local eb = self.editBox or self.EditBox
                local eventName = eb and eb:GetText()
                if eventName and eventName ~= "" and Cal.AddEvent then
                    Cal:AddEvent({name = eventName, eventType = "social", description = "", timestamp = time() + 86400})
                    UI:RenderChapter("calendar")
                end
            end,
            timeout = 0,
            whileDead = true,
            preferredIndex = 3,
        }
        StaticPopup_Show("GDL_ADD_EVENT")
    end)
    addBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(0.25, 0.2, 0.12, 0.9) end)
    addBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.15, 0.12, 0.08, 0.8) end)
    y = y - 28
    parent:SetHeight(math.abs(y) + 20)
end

-- â•â•â• XII. KILL-STATISTIKEN â•â•â•
function UI:RenderKillStats()
    local parent = self.mainFrame.scrollChild
    local y = 0
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end
    local killers = {}
    local envDeaths = 0
    local total = #guildData.deaths
    for _, d in ipairs(guildData.deaths) do
        local k = d.killerName or d.killedBy or d.source or ""
        if k == "" or k == "Unknown" or k == "Unbekannt" then
            envDeaths = envDeaths + 1
        else
            killers[k] = (killers[k] or 0) + 1
        end
    end
    local sorted = {}
    for k, v in pairs(killers) do table.insert(sorted, {k, v}) end
    table.sort(sorted, function(a, b) return a[2] > b[2] end)
    _, y = self:MakeBookLine(parent, y, "|cff785030Top-Killer der Gilde (" .. total .. " Tode gesamt):|r", 11, "|cff785030")
    y = y - 6
    if #sorted > 0 then
        local maxKills = sorted[1][2]
        for i, kv in ipairs(sorted) do
            if i > 15 then
                _, y = self:MakeBookLine(parent, y, "|cff684820... und " .. (#sorted - 15) .. " weitere Gegner|r", 9, "|cff684820")
                break
            end
            local pct = total > 0 and string.format("%.0f%%", kv[2] / total * 100) or "0%"
            local row = CreateFrame("Frame", nil, parent)
            row:SetSize(280, 18)
            row:SetPoint("TOPLEFT", 4, y)
            local rankLabel = row:CreateFontString(nil, "OVERLAY")
            rankLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            rankLabel:SetPoint("LEFT", 0, 0)
            rankLabel:SetWidth(18)
            if i <= 3 then
                rankLabel:SetText("|cffCC0000" .. i .. ".|r")
            else
                rankLabel:SetText("|cff785030" .. i .. ".|r")
            end
            local nameLabel = row:CreateFontString(nil, "OVERLAY")
            nameLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            nameLabel:SetPoint("LEFT", 20, 0)
            nameLabel:SetWidth(100)
            nameLabel:SetJustifyH("LEFT")
            nameLabel:SetText("|cff785030" .. kv[1] .. "|r")
            local bar = row:CreateTexture(nil, "BACKGROUND")
            local barPct = kv[2] / maxKills
            local barWidth = math.max(6, barPct * 80)
            bar:SetSize(barWidth, 10)
            bar:SetPoint("LEFT", 122, 0)
            bar:SetColorTexture(0.6, 0.1, 0.1, 0.4)
            local countLabel = row:CreateFontString(nil, "OVERLAY")
            countLabel:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            countLabel:SetPoint("LEFT", 205, 0)
            countLabel:SetText("|cffCC0000" .. kv[2] .. "|r |cff684820(" .. pct .. ")|r")
            y = y - 18
        end
    else
        _, y = self:MakeBookLine(parent, y, "|cff684820Noch keine benannten Gegner erfasst.|r", 10, "|cff684820")
    end
    if envDeaths > 0 then
        y = y - 10
        local envPct = total > 0 and string.format("%.0f%%", envDeaths / total * 100) or "0%"
        _, y = self:MakeBookLine(parent, y, "|cff785030Umgebungsschaden|r  |cff684820(Fallschaden, Ertrinken, Lava ...)|r", 9, "|cff785030")
        _, y = self:MakeBookLine(parent, y, "|cffCC0000" .. envDeaths .. " Tode|r  |cff684820(" .. envPct .. " aller Tode)|r", 9, "|cff785030")
    end
    parent:SetHeight(math.abs(y) + 20)
end

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

    -- 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 1.0
    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
            if self._breathGlowAG then self._breathGlowAG:Stop()        end
            if self._breathGlow   then self._breathGlow:Hide()          end
            self._emberHandle                       = FX.EmberAmbience(f, 10, 1.0, 0.45, 0.06)
            self._breathGlow, self._breathGlowAG    = FX.BreathGlow(f, 0.92, 0.72, 0.22, 4.0, 0.35)
        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 1.0
    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)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

local function 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

function UI:CreateDaySeparator(parent, timestamp, dayCount)
    -- ── Exakt wie RenderAchCategoryHeader ──────────────────────────
    local hf = CreateFrame("Frame", nil, parent)
    hf:SetHeight(30)

    local r, g, b = 0.32, 0.18, 0.04

    local hbg = hf:CreateTexture(nil, "BACKGROUND")
    hbg:SetAllPoints()
    hbg:SetColorTexture(r, g, b, 0.52)

    local shimmer = hf:CreateTexture(nil, "BACKGROUND")
    shimmer:SetPoint("TOPLEFT"); shimmer:SetPoint("TOPRIGHT"); shimmer:SetHeight(15)
    shimmer:SetColorTexture(r * 1.6, g * 1.6, b * 1.6, 0.10)
    shimmer:SetBlendMode("ADD")

    local topLine = hf:CreateTexture(nil, "BORDER")
    topLine:SetHeight(2)
    topLine:SetPoint("TOPLEFT"); topLine:SetPoint("TOPRIGHT")
    topLine:SetColorTexture(0.80, 0.58, 0.16, 0.78)

    local botLine = hf:CreateTexture(nil, "BORDER")
    botLine:SetHeight(1)
    botLine:SetPoint("BOTTOMLEFT"); botLine:SetPoint("BOTTOMRIGHT")
    botLine:SetColorTexture(0.48, 0.30, 0.08, 0.42)

    local leftAccent = hf:CreateTexture(nil, "ARTWORK")
    leftAccent:SetSize(3, 24)
    leftAccent:SetPoint("LEFT", 0, 0)
    leftAccent:SetColorTexture(0.90, 0.68, 0.16, 0.92)

    local skull = hf:CreateTexture(nil, "ARTWORK")
    skull:SetTexture("Interface\\Icons\\Spell_Shadow_DeathPact")
    skull:SetSize(20, 20)
    skull:SetPoint("LEFT", 8, 0)
    skull:SetAlpha(0.88)

    local WDAYS  = { "So","Mo","Di","Mi","Do","Fr","Sa" }
    local MONTHS = { "Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez" }
    local d = date("*t", timestamp)
    local dLabel = hf:CreateFontString(nil, "OVERLAY")
    dLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    dLabel:SetPoint("LEFT", 32, 1)
    dLabel:SetShadowOffset(1, -1); dLabel:SetShadowColor(0, 0, 0, 0.15)
    dLabel:SetText("|cffF0C060" .. (WDAYS[d.wday] or "") .. ", " .. d.day .. ". " .. (MONTHS[d.month] or "") .. " " .. d.year .. "|r")

    if dayCount and dayCount > 0 then
        local ccolor = dayCount >= 5 and "|cffFF7766" or (dayCount >= 2 and "|cffFFD100" or "|cff7A7060")
        local hcount = hf:CreateFontString(nil, "OVERLAY")
        hcount:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        hcount:SetPoint("RIGHT", -8, 0)
        hcount:SetShadowOffset(1, -1); hcount:SetShadowColor(0, 0, 0, 0.15)
        hcount:SetText(ccolor .. dayCount .. "|r")
    end

    return hf
end

function UI:UpdateChronicle()
    if not self.mainFrame then return end
    if self.activeChapter ~= 1 then return end
    local f = self.mainFrame
    
    -- Clear scroll content (Children + Regions)
    for _, c in ipairs({f.scrollChild:GetChildren()}) do c:Hide() end
    for _, r in ipairs({f.scrollChild:GetRegions()}) do r:Hide() end
    
    -- Guild Name
    if GDL.currentGuildName then
        f.guildName:SetText("< " .. GDL.currentGuildName .. " >")
    else
        f.guildName:SetText("|cff666666" .. GDL:L("NO_GUILD") .. "|r")
    end
    
    -- Daten sammeln
    local guildData = GDL:GetGuildData()
    local deaths = guildData and guildData.deaths or {}
    
    -- Sortierte Liste mit ORIGINAL-INDEX erstellen
    local sortedDeaths = {}
    for originalIndex, d in ipairs(deaths) do 
        d._originalIndex = originalIndex  -- Original-Index speichern!
        table.insert(sortedDeaths, d) 
    end
    table.sort(sortedDeaths, function(a, b) return (a.timestamp or 0) > (b.timestamp or 0) end)
    
    -- Statistiken berechnen
    local count, levelSum, today, thisWeek = #sortedDeaths, 0, 0, 0
    
    -- LOKALE Zeitzone für "heute" verwenden (nicht UTC!)
    local nowDate = date("*t")
    local todayStart = time({year=nowDate.year, month=nowDate.month, day=nowDate.day, hour=0, min=0, sec=0})
    local weekStart = todayStart - (7 * 86400)  -- 7 Tage zurück
    
    for _, d in ipairs(sortedDeaths) do
        levelSum = levelSum + (d.level or 0)
        if (d.timestamp or 0) >= todayStart then today = today + 1 end
        if (d.timestamp or 0) >= weekStart then thisWeek = thisWeek + 1 end
    end
    
    local function AnimateCounter(container, targetVal)
        if type(targetVal) ~= "number" or targetVal == 0 then
            container.value:SetText(targetVal or "0"); return
        end
        local startT = GetTime()
        local dur    = 0.65
        container:SetScript("OnUpdate", function(self, elapsed)
            local p     = math.min(1, (GetTime() - startT) / dur)
            local eased = 1 - (1 - p)^3
            self.value:SetText(math.floor(eased * targetVal + 0.5))
            if p >= 1 then
                self:SetScript("OnUpdate", nil)
                self.value:SetText(targetVal)
            end
        end)
    end
    AnimateCounter(f.statTotal,  count)
    AnimateCounter(f.statToday,  today)
    AnimateCounter(f.statWeek,   thisWeek)
    f.statAvgLvl.value:SetText(count > 0 and string.format("%.1f", levelSum/count) or "-")
    
    -- Zitat
    local Locale = GDL:GetModule("Locale")
    if Locale and Locale.L and Locale.L.BOOK_QUOTES and f.quote then
        f.quote:SetText('"' .. Locale.L.BOOK_QUOTES[math.random(#Locale.L.BOOK_QUOTES)] .. '"')
    end
    
    -- Einträge erstellen (Narcissus-Stil: nach Tagen gruppiert)
    local scrollY  = 0
    local lastDay  = nil

    if count == 0 then
        local empty = f.scrollChild:CreateFontString(nil, "OVERLAY")
        empty:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        empty:SetPoint("TOP", 0, -80)
        local emptyText = GetLocale() == "deDE"
            and "|cff3A5A30Keine Gefallenen verzeichnet.|r\n|cff2A4A20Die Gilde steht stark.|r"
            or  "|cff3A5A30No fallen recorded.|r\n|cff2A4A20The guild stands strong.|r"
        empty:SetText(emptyText)
        empty:SetJustifyH("CENTER")
    else
        for i, d in ipairs(sortedDeaths) do
            if i > 100 then break end

            local ts      = d.timestamp or 0
            local thisDay = ts > 0 and math.floor(ts / 86400) or 0

            if thisDay ~= lastDay then
                local gap      = (i == 1) and 2 or 6
                local dayCnt   = CountDeathsOnDay(sortedDeaths, i)
                local sepTs    = ts > 0 and ts or time()
                local daySep   = self:CreateDaySeparator(f.scrollChild, sepTs, dayCnt)
                daySep:SetPoint("TOPLEFT",  0, scrollY - gap)
                daySep:SetPoint("TOPRIGHT", 0, scrollY - gap)
                scrollY  = scrollY - 30 - gap
                lastDay  = thisDay
            end

            local entry  = self:CreateDeathEntry(f.scrollChild, d, d._originalIndex or i)
            entry:SetPoint("TOPLEFT", 2, scrollY)
            entry:SetPoint("TOPRIGHT", -2, scrollY)
            local entryH = (d.lastWords and d.lastWords ~= "") and 62 or 44
            scrollY = scrollY - entryH - 2
        end
    end

    f.scrollChild:SetHeight(math.max(math.abs(scrollY) + 20, 300))
end

function UI:CreateDeathEntry(parent, death, index)
    -- ── Exakt wie RenderMilRow ──────────────────────────────────────
    local col      = self:GetClassColor(death.classId)
    local classHex = ClassColorHex(death.classId)

    local CLASS_NAMES_DE = {
        [1]="Krieger",[2]="Paladin",[3]="Jaeger",[4]="Schurke",[5]="Priester",
        [6]="Todesritter",[7]="Schamane",[8]="Magier",[9]="Hexenmeister",[11]="Druide",
    }
    local className    = CLASS_NAMES_DE[death.classId] or GDL:GetClassName(death.classId) or "Unbekannt"
    local hasLastWords = death.lastWords and death.lastWords ~= ""
    local killer       = death.killedBy or death.source
    local hasZone      = death.zone and death.zone ~= ""
    local hasKiller    = killer and killer ~= ""
    local ROW_H        = hasLastWords and 62 or 44

    local row = CreateFrame("Button", nil, parent)
    row:SetHeight(ROW_H)

    -- ── Hintergrund (wie MilRow – warmes dunkles Tint) ──────────────
    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(col[1]*0.14, col[2]*0.08, col[3]*0.02, 0.22)
    row.bg = bg

    -- Obere Shimmer-Zone (wie MilRow)
    local topShimmer = row:CreateTexture(nil, "BACKGROUND")
    topShimmer:SetPoint("TOPLEFT"); topShimmer:SetPoint("TOPRIGHT"); topShimmer:SetHeight(14)
    topShimmer:SetColorTexture(col[1]*0.45, col[2]*0.28, col[3]*0.08, 0.07)
    topShimmer:SetBlendMode("ADD")

    -- Untere Trennlinie (wie MilRow)
    local sep = row:CreateTexture(nil, "BORDER")
    sep:SetSize(316, 1)
    sep:SetPoint("BOTTOMLEFT", 5, 0)
    sep:SetColorTexture(0.28, 0.20, 0.10, 0.22)

    -- ── Icon-Backing 28×28 (wie MilRow iconBorder) ─────────────────
    local iconBorder = row:CreateTexture(nil, "ARTWORK")
    iconBorder:SetSize(28, 28)
    iconBorder:SetPoint("LEFT", 4, hasLastWords and 8 or 0)
    iconBorder:SetColorTexture(col[1]*0.55, col[2]*0.38, col[3]*0.10, 0.60)
    row.iconBorder = iconBorder

    -- Klassen-Icon 24×24 (wie MilRow ic)
    local iconPath = self:GetClassIcon(death.classId)
    local ic = row:CreateTexture(nil, "OVERLAY")
    ic:SetTexture(iconPath)
    ic:SetSize(24, 24)
    ic:SetPoint("LEFT", 6, hasLastWords and 8 or 0)
    if not CLASS_ICONS[death.classId] then
        ic:SetDesaturated(true); ic:SetAlpha(0.35)
    end

    -- ── Source-Tag ──────────────────────────────────────────────────
    local srcColor  = death.syncedFrom and "5599CC" or (death.fromDeathlog and "88BB55" or (death.fromBlizzard and "BB9955" or nil))
    local sourceTag = srcColor and (" |cff" .. srcColor .. "*|r") or ""

    -- ── Name: Gold (wie MilRow nameStr unlocked) ────────────────────
    local nameStr = row:CreateFontString(nil, "OVERLAY")
    nameStr:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    nameStr:SetPoint("TOPLEFT", 38, -6)
    nameStr:SetShadowOffset(1, -1); nameStr:SetShadowColor(0, 0, 0, 0.15)
    nameStr:SetText("|cffF0C060" .. death.name .. sourceTag .. "|r")

    -- ── Info-Zeile 1: Level + Klasse (wie MilRow descStr) ───────────
    local descStr = row:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    descStr:SetPoint("TOPLEFT", 38, -19)
    descStr:SetWidth(180); descStr:SetJustifyH("LEFT")
    descStr:SetTextColor(0.68, 0.55, 0.32)
    descStr:SetText("Lv." .. (death.level or "?") .. "  " .. className)

    -- ── Info-Zeile 2: Zone + Toedter (wie MilRow statusStr, links) ──
    local row2Parts = {}
    if hasZone   then table.insert(row2Parts, "|cff5AC8D8" .. death.zone  .. "|r") end
    if hasKiller then table.insert(row2Parts, "|cffFF7A68x " .. killer     .. "|r") end
    if #row2Parts > 0 then
        local info2 = row:CreateFontString(nil, "OVERLAY")
        info2:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        info2:SetPoint("TOPLEFT", 38, -29)
        info2:SetWidth(180); info2:SetJustifyH("LEFT")
        info2:SetShadowOffset(1, -1); info2:SetShadowColor(0, 0, 0, 0.30)
        info2:SetText(table.concat(row2Parts, " |cff6A5C3C- |r"))
    end

    -- ── Datum rechts (wie MilRow statusStr) ─────────────────────────
    local statusStr = row:CreateFontString(nil, "OVERLAY")
    statusStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    statusStr:SetPoint("TOPRIGHT", -6, -7)
    statusStr:SetJustifyH("RIGHT")
    statusStr:SetShadowOffset(1, -1); statusStr:SetShadowColor(0, 0, 0, 0.15)
    if death.timestamp and death.timestamp > 0 then
        statusStr:SetText("|cffD0B878" .. date("%d.%m.%y", death.timestamp) .. "\n|cff888888" .. date("%H:%M", death.timestamp) .. "|r")
    else
        statusStr:SetText("|cff664444|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t|r")
    end

    -- ── Letzte Worte: Bodenstreifen (wie MilRow desc, extra Zeile) ──
    if hasLastWords then
        local lwBg = row:CreateTexture(nil, "BACKGROUND")
        lwBg:SetPoint("BOTTOMLEFT", 4, 1); lwBg:SetPoint("BOTTOMRIGHT", -4, 1)
        lwBg:SetHeight(20)
        lwBg:SetColorTexture(col[1]*0.07, col[2]*0.04, col[3]*0.01, 0.32)

        local lwDiv = row:CreateTexture(nil, "BORDER")
        lwDiv:SetHeight(1)
        lwDiv:SetPoint("BOTTOMLEFT", 4, 21); lwDiv:SetPoint("BOTTOMRIGHT", -4, 21)
        lwDiv:SetColorTexture(0.28, 0.20, 0.10, 0.22)

        local lwText = row:CreateFontString(nil, "OVERLAY")
        lwText:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        lwText:SetPoint("BOTTOMLEFT", 38, 5)
        lwText:SetPoint("BOTTOMRIGHT", -8, 5)
        lwText:SetJustifyH("LEFT")
        lwText:SetTextColor(0.68, 0.55, 0.32)
        lwText:SetText("\"" .. death.lastWords .. "\"")
    end

    -- ── Admin-Loeschen ──────────────────────────────────────────────
    if GuildDeathLogDB.adminPassword then
        local deleteBtn = CreateFrame("Button", nil, row)
        deleteBtn:SetSize(16, 16)
        deleteBtn:SetPoint("BOTTOMRIGHT", -6, 5)
        deleteBtn:SetAlpha(0.18)
        local xText = deleteBtn:CreateFontString(nil, "OVERLAY")
        xText:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
        xText:SetPoint("CENTER", 0, 0)
        xText:SetText("x")
        xText:SetTextColor(0.75, 0.25, 0.25)
        deleteBtn.xText = xText
        deleteBtn:SetScript("OnEnter", function(self)
            self:SetAlpha(1.0); self.xText:SetTextColor(1, 0.35, 0.35)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText("Eintrag loeschen", 1, 0.4, 0.4)
            GameTooltip:AddLine("Erfordert Admin-Passwort", 0.7, 0.7, 0.7)
            GameTooltip:Show()
        end)
        deleteBtn:SetScript("OnLeave", function(self)
            self:SetAlpha(0.18); self.xText:SetTextColor(0.75, 0.25, 0.25)
            GameTooltip:Hide()
        end)
        deleteBtn:SetScript("OnClick", function() UI:ShowPasswordDialog(index, death.name) end)
    end

    -- ── Hover (wie MilRow OnEnter/OnLeave) ─────────────────────────
    row:EnableMouse(true)
    row:SetScript("OnEnter", function(self)
        bg:SetColorTexture(col[1]*0.22, col[2]*0.14, col[3]*0.04, 0.38)
        row.iconBorder:SetColorTexture(col[1]*0.70, col[2]*0.52, col[3]*0.15, 0.85)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        GameTooltip:SetText("|c" .. classHex .. death.name .. "|r", col[1], col[2], col[3], true)
        local cIcon = CLASS_ICONS[death.classId]
        if cIcon then
            GameTooltip:AddLine("|T" .. cIcon .. ":16:16|t  " .. className .. "  Level " .. (death.level or "?"), 0.80, 0.70, 0.50)
        else
            GameTooltip:AddLine("Level " .. (death.level or "?") .. "  " .. className, 0.80, 0.70, 0.50)
        end
        if death.zone and death.zone ~= "" then
            GameTooltip:AddLine("|TInterface\\Icons\\INV_Misc_Map01:14:14|t  " .. death.zone, 0.38, 0.80, 0.88, true)
        end
        if hasKiller then
            GameTooltip:AddLine("|TInterface\\Icons\\Ability_DualWield:14:14|t  Getoetet von: |cffFF7766" .. killer .. "|r", 1, 1, 1, true)
        end
        if death.lastWords and death.lastWords ~= "" then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffCCB490\"" .. death.lastWords .. "\"|r", 1, 1, 1, true)
        end
        GameTooltip:AddLine(" ")
        if death.timestamp and death.timestamp > 0 then
            GameTooltip:AddLine("|cff999988Gefallen am " .. date("%d.%m.%Y um %H:%M Uhr", death.timestamp) .. "|r")
        end
        if death.syncedFrom then
            GameTooltip:AddLine("|cff4488AASync: " .. death.syncedFrom .. "|r")
        elseif death.fromDeathlog then
            GameTooltip:AddLine("|cff88AA44Via Deathlog-Addon|r")
        elseif death.fromBlizzard then
            GameTooltip:AddLine("|cffAA8844Via Blizzard-Kampflog|r")
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff888888Klicken fuer Grabstein-Ansicht|r")
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function(self)
        bg:SetColorTexture(col[1]*0.14, col[2]*0.08, col[3]*0.02, 0.22)
        row.iconBorder:SetColorTexture(col[1]*0.55, col[2]*0.38, col[3]*0.10, 0.60)
        GameTooltip:Hide()
    end)

    row:SetScript("OnClick", function()
        GameTooltip:Hide()
        self:ShowOverlay(death, death.syncedFrom ~= nil)
    end)

    return row
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- PASSWORT-DIALOG
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:ShowPasswordDialog(deathIndex, charName)
    if not self.pwDialog then
        self:CreatePasswordDialog()
    end
    
    self.pwDialog.deathIndex = deathIndex
    self.pwDialog.charName = charName
    self.pwDialog.titleText:SetText("|cffFF6666" .. charName .. "|r loeschen?")
    self.pwDialog.editBox:SetText("")
    self.pwDialog:Show()
    self.pwDialog.editBox:SetFocus()
end

function UI:CreatePasswordDialog()
    local f = CreateFrame("Frame", "GDLPasswordDialog", UIParent, "BackdropTemplate")
    f:SetSize(280, 130)
    f:SetPoint("CENTER", 0, 100)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(200)
    f:EnableMouse(true)
    f:Hide()
    
    f:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 20,
        insets = {left = 4, right = 4, top = 4, bottom = 4}
    })
    
    -- Titel
    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    title:SetPoint("TOP", 0, -15)
    title:SetTextColor(1, 0.85, 0.5)
    f.titleText = title
    
    -- Info-Text
    local info = f:CreateFontString(nil, "OVERLAY")
    info:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    info:SetPoint("TOP", title, "BOTTOM", 0, -5)
    info:SetText("Admin-Passwort eingeben:")
    info:SetTextColor(0.7, 0.7, 0.7)
    
    -- Eingabefeld
    local editBox = CreateFrame("EditBox", nil, f, "InputBoxTemplate")
    editBox:SetSize(200, 20)
    editBox:SetPoint("TOP", info, "BOTTOM", 0, -10)
    editBox:SetAutoFocus(false)
    editBox:SetMaxLetters(50)
    -- Passwort verstecken (Sternchen)
    editBox:SetScript("OnTextChanged", function(self)
        -- Zeige Sternchen statt echtem Text
    end)
    f.editBox = editBox
    
    -- OK Button
    local okBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    okBtn:SetSize(80, 22)
    okBtn:SetPoint("BOTTOMLEFT", 30, 15)
    okBtn:SetText("OK")
    okBtn:SetScript("OnClick", function()
        local input = editBox:GetText()
        local valid, err = GDL:VerifyPassword(input)
        
        if valid then
            GDL:DeleteDeath(f.deathIndex)
            f:Hide()
        else
            GDL:Print("|cffFF0000Falsches Passwort!|r")
            editBox:SetText("")
            editBox:SetFocus()
        end
    end)
    
    -- Abbrechen Button
    local cancelBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    cancelBtn:SetSize(80, 22)
    cancelBtn:SetPoint("BOTTOMRIGHT", -30, 15)
    cancelBtn:SetText("Abbrechen")
    cancelBtn:SetScript("OnClick", function()
        f:Hide()
    end)
    
    -- Enter-Taste = OK
    editBox:SetScript("OnEnterPressed", function()
        okBtn:Click()
    end)
    
    -- Escape = Abbrechen
    editBox:SetScript("OnEscapePressed", function()
        f:Hide()
    end)
    
    self.pwDialog = f
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- ELEGANTES TODES-OVERLAY
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:ShowOverlay(death, isSynced)
    table.insert(overlayQueue, {death = death, synced = isSynced})
    local MB = GDL:GetModule("MinimapButton")
    if MB then MB:Pulse() end
    if not isShowingOverlay then self:ShowNextOverlay() end
end

function UI:ShowNextOverlay()
    if #overlayQueue == 0 then isShowingOverlay = false return end
    isShowingOverlay = true
    self:DisplayOverlay(table.remove(overlayQueue, 1))
end

function UI:DisplayOverlay(data)
    if not self.overlay then self:CreateOverlay() end

    local o     = self.overlay
    local death = data.death
    local col   = self:GetClassColor(death.classId)
    local r, g, b = col[1], col[2], col[3]
    local FX    = GDL:GetModule("AnimFX")

    if o.hideTimer then o.hideTimer:Cancel() end

    -- Vorherige Ambience stoppen
    if FX and o._ambienceHandles then
        FX.StopOverlayAmbience(o._ambienceHandles)
        o._ambienceHandles = nil
    end

    -- Daten setzen (Name per TypewriterText später)
    o.nameText:SetText("")
    o.nameText:SetTextColor(r, g, b)

    -- Col1: Klassen-Icon + Subtext "Stufe X Klasse"
    if o.classIcon then o.classIcon:SetTexture(self:GetClassIcon(death.classId)) end
    o.classText:SetText("Stufe " .. (death.level or "?") .. "  " .. GDL:GetClassName(death.classId))
    o.classText:SetTextColor(math.min(1, r + 0.22), math.min(1, g + 0.22), math.min(1, b + 0.22))

    -- Klassen-Farbakzente
    if o.iconGlow    then o.iconGlow:SetVertexColor(r, g, b, 0.45) end
    if o.iconBacking then o.iconBacking:SetVertexColor(r*0.24, g*0.16, b*0.05, 0.88) end
    if o.rowBg       then o.rowBg:SetVertexColor(r*0.08, g*0.05, b*0.02, 0.55) end
    if o.classGrad   then
        o.classGrad:SetVertexColor(r*0.50, g*0.30, b*0.08, 1)
        o.classGrad:SetAlpha(0.10)
    end
    if o.divider then o.divider:SetVertexColor(r*0.85, g*0.65, b*0.18, 0.50) end
    if o.sep2    then o.sep2:SetVertexColor(r*0.80, g*0.60, b*0.15, 0.50) end

    -- Col2: Killer (Spaltenheader liefert Kontext)
    local killer = death.killerName or death.killer
    local hasKiller = killer and killer ~= "" and killer ~= "Unknown"
    if hasKiller then
        o.killerText:SetText(killer)
    else
        o.killerText:SetText("Unbekannt")
    end
    if o.spellText then
        if death.killerSpell and death.killerSpell ~= "" then
            o.spellText:SetText("via " .. death.killerSpell)
            o.spellText:SetAlpha(0)
            o.spellText:Show()
            if o.spellLabel then o.spellLabel:Show(); o.spellLabel:SetAlpha(0) end
        else
            o.spellText:SetText("")
            o.spellText:Hide()
            if o.spellLabel then o.spellLabel:Hide() end
        end
    end

    -- Col3: Uhrzeit HH:MM:SS
    local hasTimestamp = death.timestamp and death.timestamp > 0
    if hasTimestamp then
        o.timestampText:SetText(date("%H:%M:%S", death.timestamp))
    else
        o.timestampText:SetText("--:--:--")
    end

    -- Col4: Zone
    local zone = death.zone
    o.zoneText:SetText(zone and zone ~= "" and zone or "Unbekannt")

    -- Letzte Worte
    local lastWords = death.lastWords
    local hasLastWords = lastWords and lastWords ~= ""
    if hasLastWords then
        if #lastWords > 90 then lastWords = lastWords:sub(1, 87) .. "..." end
    end

    -- Footer Level/Klasse
    if o.lwLabel then
        if hasLastWords then o.lwLabel:SetAlpha(0); o.lwLabel:Show() else o.lwLabel:Hide() end
    end

    -- Alle Elemente zu Beginn ausgeblendet
    o.nameText:SetAlpha(0);      o.nameText:Show()
    o.classText:SetAlpha(0);     o.classText:Show()
    o.killerText:SetAlpha(0);    o.killerText:Show()
    o.timestampText:SetAlpha(0); o.timestampText:Show()
    o.zoneText:SetAlpha(0);      o.zoneText:Show()
    o.lastWordsText:SetAlpha(0)
    if hasLastWords then o.lastWordsText:Show() else o.lastWordsText:Hide() end

    -- Reveal-Helfer
    local function revealFade(widget, delay, dur)
        dur = dur or 0.35
        C_Timer.After(delay, function()
            if not o:IsShown() then return end
            widget:SetAlpha(0)
            local el = 0
            local ticker = CreateFrame("Frame", nil, UIParent)
            ticker:SetScript("OnUpdate", function(self, dt)
                el = el + dt
                widget:SetAlpha(math.sqrt(math.min(1, el / dur)))
                if el >= dur then
                    widget:SetAlpha(1)
                    self:SetScript("OnUpdate", nil)
                    self:Hide()
                end
            end)
            ticker:Show()
        end)
    end

    -- Tabellenzeile: alle Spalten gleichzeitig bei ~1.0s
    local nameStr = death.name or ""
    revealFade(o.classText,      1.00)
    revealFade(o.killerText,     1.00)
    revealFade(o.timestampText,  1.00)
    revealFade(o.zoneText,       1.00)
    if o.spellText and o.spellText:IsShown() then
        revealFade(o.spellText, 1.10)
        if o.spellLabel and o.spellLabel:IsShown() then revealFade(o.spellLabel, 1.10) end
    end
    if o.lwLabel and hasLastWords then revealFade(o.lwLabel, 1.45) end

    if hasLastWords then
        C_Timer.After(1.60, function()
            if not o:IsShown() then return end
            if FX and FX.TypewriterText then
                o.lastWordsText:SetAlpha(1)
                FX.TypewriterText(o.lastWordsText, '"' .. lastWords .. '"', 22)
            else
                o.lastWordsText:SetAlpha(1)
                o.lastWordsText:SetText('"' .. lastWords .. '"')
            end
        end)
    end

    -- ── OVERLAY VORBEREITEN (Alpha=0, DeathCinematic steuert Erscheinen) ────
    o:SetAlpha(0)
    local clampedScale = math.max(0.50, o.currentScale or 1.0)
    if clampedScale ~= o.currentScale then
        o.currentScale = clampedScale
        GuildDeathLogDB.settings.overlayScale = clampedScale
    end
    o:SetScale(clampedScale)
    o:Show()

    -- ── NAME: Alpha setzen + Typewriter ───────────────────────────────
    C_Timer.After(0.92, function()
        if o:IsShown() then
            o.nameText:SetAlpha(1)
            if FX and FX.TypewriterText then
                FX.TypewriterText(o.nameText, nameStr, 24)
            else
                o.nameText:SetText(nameStr)
            end
        end
    end)

    -- ── CINEMATIC DEATH EFFECTS (Master-Orchestrator) ────────────────────
    if FX then
        FX.DeathCinematic(o, r, g, b)
    end

    -- ── TRAUERSOUND (kein Level-Up / kein Ding!) ─────────────────────
    -- Tiefer Glockenklang oder Knall-Klangg statt Siegesfanfare
    C_Timer.After(0.20, function()
        -- Tiefer Kirchenglocken-ähnlicher WoW-Sound (Classic-kompatibel)
        pcall(function() PlaySound(6594, "Master") end)  -- low church bell
    end)
    C_Timer.After(1.80, function()
        -- Zweiter leiser Nachhall
        pcall(function() PlaySound(6594, "Master") end)
    end)

    -- ── HIDE NACH 12 SEKUNDEN ────────────────────────────────────
    o.hideTimer = C_Timer.NewTimer(12, function()
        if not o.outroAnim then
            o.outroAnim = o:CreateAnimationGroup()
            local outroFade = o.outroAnim:CreateAnimation("Alpha")
            outroFade:SetFromAlpha(1); outroFade:SetToAlpha(0)
            outroFade:SetDuration(1.5); outroFade:SetSmoothing("IN")
            o.outroAnim:SetScript("OnFinished", function()
                o:Hide(); o:SetAlpha(1)
                local FX2 = GDL:GetModule("AnimFX")
                if FX2 then
                    FX2.StopOverlayAmbience(o._ambienceHandles)
                end
                o._ambienceHandles = nil
                C_Timer.After(0.3, function() UI:ShowNextOverlay() end)
            end)
        end
        o.outroAnim:Stop()
        o.outroAnim:Play()
    end)
end
function UI:CreateOverlay()
    -- ============================================================
    -- TODES-POPUP  --  Vertikale Karte (research-basiert)
    -- Kein Table-Layout: alles Word-Wrapped, nichts abgeschnitten
    -- 660x460 px Artwork-Rahmen (GDLDeathFrame_New.png)
    -- ============================================================
    local W, H  = 660, 460
    local CX    = 62     -- linker Innenbereich-Rand
    local CTY   = -132   -- oberer  Innenbereich-Rand (Banner ~130px)
    local CBY   = 56     -- unterer Innenbereich-Rand (px von BOTTOM)
    local CW    = W - CX * 2          -- 536px Innenbreite
    local PAD   = 10                  -- Text-Innenabstand

    local o = CreateFrame("Frame", "GDLDeathOverlay", UIParent)
    o:SetSize(W, H)
    o:SetFrameStrata("FULLSCREEN_DIALOG")
    o:SetFrameLevel(500)
    o:Hide()

    local scale = math.max(0.50, GuildDeathLogDB.settings.overlayScale or 1.0)
    o:SetScale(scale)
    o.currentScale = scale
    GuildDeathLogDB.settings.overlayScale = scale
    local pos = GuildDeathLogDB.settings.overlayPosition
    if pos and pos.point then
        o:SetPoint(pos.point, UIParent, pos.relPoint, pos.x, pos.y)
    else
        o:SetPoint("TOP", 0, -80)
    end

    -- Artwork-Hintergrund
    local bg = o:CreateTexture(nil, "BACKGROUND", nil, -8)
    bg:SetAllPoints()
    bg:SetTexture("Interface\\AddOns\\GuildDeathLog\\Textures\\GDLDeathFrame_New.png")

    -- Klassen-Farbgradient (subtil, ADD-Blend)
    local classGrad = o:CreateTexture(nil, "BACKGROUND", nil, -6)
    classGrad:SetPoint("TOPLEFT",  CX,  CTY)
    classGrad:SetPoint("TOPRIGHT", -CX, CTY)
    classGrad:SetHeight(275)
    classGrad:SetTexture("Interface\\Buttons\\WHITE8x8")
    classGrad:SetBlendMode("ADD")
    classGrad:SetAlpha(0)
    o.classGrad = classGrad

    -- Schliessen-Knopf
    local closeBtn = CreateFrame("Button", nil, o)
    closeBtn:SetSize(22, 22)
    closeBtn:SetPoint("TOPRIGHT", -62, -66)
    closeBtn:SetAlpha(0.55)
    local closeTex = closeBtn:CreateTexture(nil, "OVERLAY")
    closeTex:SetAllPoints()
    closeTex:SetTexture("Interface\\Buttons\\UI-StopButton")
    closeTex:SetVertexColor(0.95, 0.35, 0.35)
    closeBtn:SetScript("OnEnter", function(self) self:SetAlpha(1.0) end)
    closeBtn:SetScript("OnLeave", function(self) self:SetAlpha(0.55) end)
    closeBtn:SetScript("OnClick", function()
        if o.hideTimer then o.hideTimer:Cancel() end
        if o.outroAnim then o.outroAnim:Stop(); o.outroAnim:Play()
        else o:Hide(); C_Timer.After(0.1, function() UI:ShowNextOverlay() end) end
    end)

    -- Separator-Helfer
    local function makeSep(yOff, alpha)
        local s = o:CreateTexture(nil, "ARTWORK")
        s:SetPoint("TOPLEFT",  o, "TOPLEFT",  CX + PAD, yOff)
        s:SetPoint("TOPRIGHT", o, "TOPRIGHT", -CX - PAD, yOff)
        s:SetHeight(1)
        s:SetTexture("Interface\\Buttons\\WHITE8x8")
        s:SetVertexColor(0.82, 0.65, 0.18, alpha or 0.55)
        return s
    end
    makeSep(CTY - 3)           -- Oberkante Innenfeld

    -- ══════════════════════════════════════════════════════════════
    -- BLOCK 1: HEADER  (Name + Klasse/Stufe — zentriert, kein Icon)
    -- ══════════════════════════════════════════════════════════════
    local NAME_Y = CTY - 16    -- 16px unter Innenfeld-Start

    -- Spieler-Name (Morpheus, gross, Klassenfarbe, zentriert)
    o.nameText = o:CreateFontString(nil, "OVERLAY")
    o.nameText:SetFont("Fonts\\MORPHEUS.TTF", 28, "OUTLINE")
    o.nameText:SetPoint("TOP", o, "TOP", 0, NAME_Y)
    o.nameText:SetWidth(CW - PAD * 2)
    o.nameText:SetJustifyH("CENTER")
    o.nameText:SetWordWrap(false)
    o.nameText:SetShadowOffset(0, 0)

    -- Klasse/Level-Subtitle (Klassenfarbe, kleiner, zentriert)
    o.classText = o:CreateFontString(nil, "OVERLAY")
    o.classText:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
    o.classText:SetPoint("TOP", o, "TOP", 0, NAME_Y - 36)
    o.classText:SetWidth(CW - PAD * 2)
    o.classText:SetJustifyH("CENTER")
    o.classText:SetWordWrap(false)
    o.classText:SetShadowOffset(0, 0)

    -- Separator nach Header
    local SEP1_Y = NAME_Y - 62   -- unter classText (36 + ~18pt-Höhe + Luft)
    local sep1 = makeSep(SEP1_Y)
    o.divider = sep1

    -- ══════════════════════════════════════════════════════════════
    -- BLOCK 2: DETAILS  (Killer | Zone | Zeit)
    -- Jede Zeile: Label (gold, 10pt) + Wert (weiss/farbe, 14pt)
    -- Kein fester Abstand -- SetPoint chain von Zeile zu Zeile
    -- ══════════════════════════════════════════════════════════════
    local INFO_X    = CX + PAD
    local INFO_W    = CW - PAD * 2
    local INFO_Y    = SEP1_Y - 12    -- erste Info-Zeile

    -- Killer-Zeile (zwei FontStrings: Label + Wert nebeneinander)
    local function makeInfoRow(labelTxt, yOff)
        local lbl = o:CreateFontString(nil, "ARTWORK")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
        lbl:SetPoint("TOPLEFT", o, "TOPLEFT", INFO_X, yOff)
        lbl:SetText(labelTxt)
        lbl:SetTextColor(1.0, 0.93, 0.55)
        lbl:SetShadowOffset(0, 0)
        lbl:SetWidth(96)
        lbl:SetJustifyH("LEFT")

        local val = o:CreateFontString(nil, "OVERLAY")
        val:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
        val:SetPoint("TOPLEFT", o, "TOPLEFT", INFO_X + 98, yOff - 1)
        val:SetWidth(INFO_W - 100)
        val:SetJustifyH("LEFT")
        val:SetWordWrap(false)
        val:SetTextColor(1, 1, 1)
        val:SetShadowOffset(0, 0)
        val:SetShadowColor(0, 0, 0, 1)
        return lbl, val
    end

    o.killerText    = select(2, makeInfoRow("Getötet von:", INFO_Y))
    o.spellLabel, o.spellText = makeInfoRow("Fähigkeit:",   INFO_Y - 26)
    o.zoneText      = select(2, makeInfoRow("Zone:",        INFO_Y - 52))
    o.timestampText = select(2, makeInfoRow("Uhrzeit:",     INFO_Y - 78))

    -- Separator nach Details
    local SEP2_Y = INFO_Y - 96
    local sep2 = makeSep(SEP2_Y)
    o.sep2 = sep2

    -- ══════════════════════════════════════════════════════════════
    -- BLOCK 3: LETZTE WORTE
    -- ══════════════════════════════════════════════════════════════
    local LW_Y = SEP2_Y - 12

    local lwLabel = o:CreateFontString(nil, "ARTWORK")
    lwLabel:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
    lwLabel:SetPoint("TOPLEFT", o, "TOPLEFT", CX, LW_Y)
    lwLabel:SetWidth(CW)
    lwLabel:SetJustifyH("CENTER")
    lwLabel:SetText("~ Letzte Worte ~")
    lwLabel:SetTextColor(1.0, 0.94, 0.56)
    lwLabel:SetShadowOffset(0, 0)
    lwLabel:SetShadowColor(0, 0, 0, 1)
    o.lwLabel = lwLabel

    o.lastWordsText = o:CreateFontString(nil, "OVERLAY")
    o.lastWordsText:SetFont("Fonts\\FRIZQT__.TTF", 15, "OUTLINE")
    o.lastWordsText:SetPoint("TOPLEFT", o, "TOPLEFT", CX + PAD, LW_Y - 18)
    o.lastWordsText:SetWidth(CW - PAD * 2)
    o.lastWordsText:SetHeight(38)
    o.lastWordsText:SetJustifyH("CENTER")
    o.lastWordsText:SetWordWrap(true)
    o.lastWordsText:SetTextColor(1.0, 0.94, 0.72)
    o.lastWordsText:SetShadowOffset(0, 0)
    o.lastWordsText:SetShadowColor(0, 0, 0, 1)

    -- ══════════════════════════════════════════════════════════════
    -- BLOCK 4: FOOTER  (Alliance/Horde-Icons + Level-Text)
    -- ══════════════════════════════════════════════════════════════
    local alliIcon = o:CreateTexture(nil, "ARTWORK")
    alliIcon:SetSize(38, 38)
    alliIcon:SetPoint("BOTTOMLEFT", o, "BOTTOMLEFT", CX + 2, CBY + 2)
    alliIcon:SetTexture("Interface\\FactionFrame\\PVP-Currency-Alliance")
    alliIcon:SetVertexColor(0.78, 0.84, 1.0, 0.65)

    local hordeIcon = o:CreateTexture(nil, "ARTWORK")
    hordeIcon:SetSize(38, 38)
    hordeIcon:SetPoint("BOTTOMRIGHT", o, "BOTTOMRIGHT", -CX - 2, CBY + 2)
    hordeIcon:SetTexture("Interface\\FactionFrame\\PVP-Currency-Horde")
    hordeIcon:SetVertexColor(1.0, 0.82, 0.75, 0.65)

    -- (levelText entfernt auf Wunsch)

    -- Scale-Label
    local scaleText = o:CreateFontString(nil, "OVERLAY")
    scaleText:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
    scaleText:SetPoint("BOTTOM", 0, -24)
    scaleText:SetTextColor(1, 0.8, 0.3)
    scaleText:Hide()
    o.scaleText = scaleText

    -- ══════════════════════════════════════════════════════════════
    -- DRAG & SCALE  (Shift+Drag = skalieren, Drag = verschieben)
    -- ══════════════════════════════════════════════════════════════
    o:SetMovable(true)
    o:EnableMouse(true)
    o:RegisterForDrag("LeftButton")

    local isDragging = false
    local isScaling  = false
    local startX, startY, startScale

    o:SetScript("OnMouseDown", function(self, button)
        if button == "RightButton" then
            if self.hideTimer then self.hideTimer:Cancel() end
            if self.outroAnim then self.outroAnim:Stop(); self.outroAnim:Play()
            else self:Hide(); C_Timer.After(0.2, function() UI:ShowNextOverlay() end) end
        elseif button == "LeftButton" then
            if IsShiftKeyDown() then
                isScaling  = true
                isDragging = false
                startX, startY = GetCursorPosition()
                startScale = self.currentScale or 1.0
                self:SetScript("OnUpdate", function(self)
                    if not IsMouseButtonDown("LeftButton") then
                        isScaling = false
                        self:SetScript("OnUpdate", nil)
                        GuildDeathLogDB.settings.overlayScale = self.currentScale
                        if self.scaleText then C_Timer.After(1.2, function() if self.scaleText then self.scaleText:Hide() end end) end
                        return
                    end
                    if not isScaling then self:SetScript("OnUpdate", nil); return end
                    local _, curY = GetCursorPosition()
                    local newScale = math.max(0.50, math.min(2.5, startScale + (curY - startY) / 100))
                    self:SetScale(newScale)
                    self.currentScale = newScale
                    if self.scaleText then
                        self.scaleText:SetText(math.floor(newScale * 100) .. "%")
                        self.scaleText:Show()
                    end
                end)
            else
                isDragging = true
                isScaling  = false
                self:StartMoving()
            end
        end
    end)

    o:SetScript("OnMouseUp", function(self, button)
        if button == "LeftButton" then
            if isScaling then
                isScaling = false
                self:SetScript("OnUpdate", nil)
                GuildDeathLogDB.settings.overlayScale = self.currentScale
                if self.scaleText then C_Timer.After(1, function() if self.scaleText then self.scaleText:Hide() end end) end
            end
            if isDragging then
                isDragging = false
                self:StopMovingOrSizing()
                local point, _, relPoint, x, y = self:GetPoint()
                GuildDeathLogDB.settings.overlayPosition = { point=point, relPoint=relPoint, x=x, y=y }
            end
        end
    end)

    self.overlay = o
end


-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- ACHIEVEMENT TOAST (Cinematic Premium Slide-In)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:ShowAchievementToast(ach, isGuild)
    table.insert(achToastQueue, { ach = ach, isGuild = isGuild })
    if not achToastActive then self:NextAchToast() end
end

function UI:NextAchToast()
    if #achToastQueue == 0 then achToastActive = false; return end
    achToastActive = true
    self:DisplayAchievementToast(table.remove(achToastQueue, 1))
end

function UI:DisplayAchievementToast(data)
    if not self.achToastFrame then self:BuildAchToastFrame() end
    local t   = self.achToastFrame
    local ach = data.ach
    local isGuild = data.isGuild

    t.icon:SetTexture(ach.icon)
    t.titleStr:SetText(ach.name)
    t.descStr:SetText(ach.desc or "")

    if isGuild then
        t.badge:SetText("|cffFFD100[Gilden-Errungenschaft]|r")
        t.titleStr:SetTextColor(0.28, 0.95, 0.48)
        t.topBar:SetColorTexture(0.06, 0.44, 0.12, 1)
        t.bg:SetColorTexture(0.03, 0.11, 0.04, 0.94)
        t.glow:SetVertexColor(0.2, 1.0, 0.42)
        t.leftAccent:SetColorTexture(0.15, 0.85, 0.25, 0.85)
    else
        t.badge:SetText("|cffFFD100[Errungenschaft]|r")
        t.titleStr:SetTextColor(1.0, 0.82, 0.0)
        t.topBar:SetColorTexture(0.55, 0.38, 0.04, 1)
        t.bg:SetColorTexture(0.12, 0.09, 0.02, 0.94)
        t.glow:SetVertexColor(1.0, 0.75, 0.10)
        t.leftAccent:SetColorTexture(0.90, 0.62, 0.08, 0.85)
    end

    if t.hideTimer then t.hideTimer:Cancel(); t.hideTimer = nil end
    t:SetScript("OnUpdate", nil)
    t:Show()

    -- Slide-in: TOPRIGHT offset 370 -> -10 (cubic ease-out, 0.42s)
    local slideIn = 0.42
    local slEl = 0
    t:ClearAllPoints()
    t:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 370, -218)
    t:SetScript("OnUpdate", function(self, dt)
        slEl = slEl + dt
        local p  = math.min(slEl / slideIn, 1)
        local ep = 1 - (1 - p) ^ 3
        self:ClearAllPoints()
        self:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 370 - 380 * ep, -218)
        if slEl >= slideIn then
            self:SetScript("OnUpdate", nil)
            self:ClearAllPoints()
            self:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -10, -218)
            -- ─── ACHIEVEMENT FX ──────────────────────────────────────────
            local AnimFX = GDL:GetModule("AnimFX")
            if AnimFX then
                local fr, fg, fb = isGuild and 0.2 or 1.0, isGuild and 1.0 or 0.75, isGuild and 0.42 or 0.10
                AnimFX.AchievementFanfare(t, fr, fg, fb)
            end
        end
    end)

    -- Glow burst
    t.glowFrame:SetAlpha(0)
    t.glowAnim:Stop()
    t.glowAnim:Play()

    -- Icon scale punch
    GDL_ScaleLerp(t.iconPunch, 1.30, 1.0, 0.26, "OUT")

    -- Depletion bar fill tracking
    local tbStart = GetTime()
    local tbDur   = 5.8
    t.timeBar:SetWidth(222)
    local tbTicker = t.tbTicker
    tbTicker.startT = tbStart
    tbTicker.dur    = tbDur
    tbTicker:Show()

    -- Auto-hide after ~6s (slide-out)
    t.hideTimer = C_Timer.NewTimer(6.0, function()
        if not self.achToastFrame then return end
        local tt = self.achToastFrame
        tt:SetScript("OnUpdate", nil)
        tbTicker:Hide()
        local outEl  = 0
        local outDur = 0.34
        tt:SetScript("OnUpdate", function(self, dt)
            outEl = outEl + dt
            local p  = math.min(outEl / outDur, 1)
            local ep = p * p
            self:ClearAllPoints()
            self:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -10 + 380 * ep, -218)
            if outEl >= outDur then
                self:SetScript("OnUpdate", nil)
                self:Hide()
                C_Timer.After(0.22, function() UI:NextAchToast() end)
            end
        end)
    end)
end

function UI:BuildAchToastFrame()
    local W, H = 340, 84
    local t = CreateFrame("Frame", "GDLAchievementToast", UIParent)
    t:SetSize(W, H)
    t:SetFrameStrata("FULLSCREEN_DIALOG")
    t:SetFrameLevel(450)
    t:Hide()

    -- â”€â”€ Outer dark cinematic background â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local bg = t:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.08, 0.05, 0.01, 0.96)
    t.bg = bg

    -- â”€â”€ Inner warm center glow (ADD, text area only) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local bgInner = t:CreateTexture(nil, "BACKGROUND")
    bgInner:SetPoint("TOPLEFT",    70, 0)
    bgInner:SetPoint("BOTTOMRIGHT", -4, 0)
    bgInner:SetColorTexture(0.20, 0.11, 0.02, 0.12)
    bgInner:SetBlendMode("ADD")

    -- â”€â”€ 2px top gold bar (anim colored per type) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local topBar = t:CreateTexture(nil, "BORDER")
    topBar:SetSize(W, 2); topBar:SetPoint("TOPLEFT")
    topBar:SetColorTexture(0.82, 0.56, 0.04, 1)
    t.topBar = topBar

    -- â”€â”€ Gold shimmer strip (ADD, below top bar) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local shimmer = t:CreateTexture(nil, "BORDER")
    shimmer:SetPoint("TOPLEFT",  0, -2)
    shimmer:SetPoint("TOPRIGHT", 0, -2)
    shimmer:SetHeight(14)
    shimmer:SetColorTexture(0.72, 0.50, 0.06, 0.09)
    shimmer:SetBlendMode("ADD")

    -- â”€â”€ Border lines â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local botLine = t:CreateTexture(nil, "BORDER")
    botLine:SetSize(W, 1); botLine:SetPoint("BOTTOMLEFT")
    botLine:SetColorTexture(0.28, 0.20, 0.04, 0.55)
    local lLine = t:CreateTexture(nil, "BORDER")
    lLine:SetSize(1, H); lLine:SetPoint("TOPLEFT")
    lLine:SetColorTexture(0.55, 0.38, 0.06, 0.55)
    local rLine = t:CreateTexture(nil, "BORDER")
    rLine:SetSize(1, H); rLine:SetPoint("TOPRIGHT")
    rLine:SetColorTexture(0.28, 0.20, 0.04, 0.40)

    -- â”€â”€ Left accent bar (4px, category colored) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local leftAccent = t:CreateTexture(nil, "ARTWORK")
    leftAccent:SetSize(4, H - 6)
    leftAccent:SetPoint("TOPLEFT", 3, -3)
    leftAccent:SetColorTexture(0.90, 0.62, 0.08, 0.85)
    t.leftAccent = leftAccent

    -- â”€â”€ Icon-area vertical divider â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local divLine = t:CreateTexture(nil, "BORDER")
    divLine:SetSize(1, H - 14)
    divLine:SetPoint("TOPLEFT", 70, -7)
    divLine:SetColorTexture(0.38, 0.28, 0.06, 0.28)

    -- â”€â”€ Seal glow frame (animated alpha burst on show) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local glowFrame = CreateFrame("Frame", nil, t)
    glowFrame:SetSize(76, 76)
    glowFrame:SetPoint("LEFT", 8, 0)
    glowFrame:SetAlpha(0)
    t.glowFrame = glowFrame

    local glow = glowFrame:CreateTexture(nil, "ARTWORK")
    glow:SetAllPoints()
    glow:SetTexture("Interface\\SpellActivationOverlay\\IconAlert")
    glow:SetBlendMode("ADD")
    glow:SetVertexColor(1, 0.75, 0.10)
    t.glow = glow

    local glowAnim = glowFrame:CreateAnimationGroup()
    glowAnim:SetLooping("NONE")
    local ga1 = glowAnim:CreateAnimation("Alpha")
    ga1:SetFromAlpha(0); ga1:SetToAlpha(0.95)
    ga1:SetDuration(0.08); ga1:SetOrder(1); ga1:SetSmoothing("NONE")
    local ga2 = glowAnim:CreateAnimation("Alpha")
    ga2:SetFromAlpha(0.95); ga2:SetToAlpha(0.12)
    ga2:SetDuration(0.65); ga2:SetOrder(2); ga2:SetSmoothing("OUT")
    glowAnim:SetScript("OnFinished", function() glowFrame:SetAlpha(0.12) end)
    t.glowAnim = glowAnim

    -- â”€â”€ Icon punch frame (60Ã—60 containing 54Ã—54 icon) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local iconPunch = CreateFrame("Frame", nil, t)
    iconPunch:SetSize(60, 60)
    iconPunch:SetPoint("LEFT", 9, 0)
    t.iconPunch = iconPunch

    local iconBg = iconPunch:CreateTexture(nil, "BACKGROUND")
    iconBg:SetAllPoints()
    iconBg:SetColorTexture(0.10, 0.06, 0.02, 0.80)

    local icon = iconPunch:CreateTexture(nil, "ARTWORK")
    icon:SetSize(54, 54)
    icon:SetPoint("CENTER")
    t.icon = icon

    local iconSheen = iconPunch:CreateTexture(nil, "OVERLAY")
    iconSheen:SetSize(54, 14)
    iconSheen:SetPoint("TOP", icon, "TOP", 0, 0)
    iconSheen:SetColorTexture(1, 1, 1, 0.09)

    local iconBd = iconPunch:CreateTexture(nil, "OVERLAY")
    iconBd:SetSize(58, 58)
    iconBd:SetPoint("CENTER")
    iconBd:SetTexture("Interface\\Buttons\\UI-Slot-Background")
    iconBd:SetAlpha(0.18)

    -- â”€â”€ Badge (ERRUNGENSCHAFT / GILDEN-ERRUNGENSCHAFT) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local badge = t:CreateFontString(nil, "OVERLAY")
    badge:SetFont("Fonts\\FRIZQT__.TTF", 7, "OUTLINE")
    badge:SetPoint("TOPLEFT", 78, -8)
    badge:SetShadowOffset(1, -1); badge:SetShadowColor(0, 0, 0, 1)
    t.badge = badge

    -- â”€â”€ Achievement name (MORPHEUS 13pt, no outline = cleaner) â”€â”€â”€
    local titleStr = t:CreateFontString(nil, "OVERLAY")
    titleStr:SetFont("Fonts\\MORPHEUS.TTF", 13, "")
    titleStr:SetPoint("TOPLEFT", 78, -19)
    titleStr:SetWidth(255); titleStr:SetJustifyH("LEFT")
    titleStr:SetShadowOffset(1, -1); titleStr:SetShadowColor(0, 0, 0, 1)
    t.titleStr = titleStr

    -- â”€â”€ Description (FRIZQT 8pt, dim warm gray) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local descStr = t:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    descStr:SetPoint("TOPLEFT", 78, -37)
    descStr:SetWidth(258); descStr:SetJustifyH("LEFT")
    descStr:SetTextColor(0.64, 0.56, 0.38)
    t.descStr = descStr

    -- â”€â”€ Separator + time bar â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local sep = t:CreateTexture(nil, "BORDER")
    sep:SetSize(260, 1)
    sep:SetPoint("TOPLEFT", 78, -55)
    sep:SetColorTexture(0.38, 0.28, 0.08, 0.30)

    local timeBarBg = t:CreateTexture(nil, "BACKGROUND")
    timeBarBg:SetSize(260, 2)
    timeBarBg:SetPoint("TOPLEFT", 78, -60)
    timeBarBg:SetColorTexture(0.18, 0.14, 0.05, 0.50)

    local timeBar = t:CreateTexture(nil, "BORDER")
    timeBar:SetSize(260, 2)
    timeBar:SetPoint("TOPLEFT", 78, -60)
    timeBar:SetColorTexture(0.82, 0.56, 0.08, 0.85)
    t.timeBar = timeBar

    local tbTicker = CreateFrame("Frame")
    tbTicker:Hide()
    tbTicker:SetScript("OnUpdate", function(self)
        local elapsed   = GetTime() - (self.startT or GetTime())
        local remaining = 1 - math.min(elapsed / (self.dur or 6), 1)
        t.timeBar:SetWidth(math.max(260 * remaining, 1))
        if remaining <= 0 then self:Hide() end
    end)
    t.tbTicker = tbTicker

    -- â”€â”€ Right-click to dismiss â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t:EnableMouse(true)
    t:SetScript("OnMouseDown", function(self, btn)
        if btn == "RightButton" then
            if self.hideTimer then self.hideTimer:Cancel(); self.hideTimer = nil end
            tbTicker:Hide()
            self:SetScript("OnUpdate", nil)
            self:Hide()
            C_Timer.After(0.10, function() UI:NextAchToast() end)
        end
    end)

    self.achToastFrame = t
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- MILESTONE TOAST  -  Cinematic Bottom-Center (Narcissus/DialogueUI)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:BuildMilestoneToastFrame()
    local W, H = 480, 90
    local t = CreateFrame("Frame", "GDLMilestoneToast", UIParent)
    t:SetSize(W, H)
    t:SetFrameStrata("FULLSCREEN_DIALOG")
    t:SetFrameLevel(455)
    t:Hide()
    t.W = W

    -- â”€â”€ Outer cinematic dark background â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local bgOuter = t:CreateTexture(nil, "BACKGROUND")
    bgOuter:SetAllPoints()
    bgOuter:SetColorTexture(0.04, 0.02, 0.01, 0.96)

    -- â”€â”€ Inner warm glow (ADD, only text half) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local bgInner = t:CreateTexture(nil, "BACKGROUND")
    bgInner:SetPoint("TOPLEFT",    98, 0)
    bgInner:SetPoint("BOTTOMRIGHT", -8, 0)
    bgInner:SetColorTexture(0.18, 0.10, 0.02, 0.14)
    bgInner:SetBlendMode("ADD")

    -- â”€â”€ 2px top gold line (category colored) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local topBar = t:CreateTexture(nil, "BORDER")
    topBar:SetSize(W, 2); topBar:SetPoint("TOPLEFT")
    topBar:SetColorTexture(0.85, 0.60, 0.05, 1)
    t.topBar = topBar

    -- â”€â”€ Gold shimmer strip below top bar â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local shimmer = t:CreateTexture(nil, "BORDER")
    shimmer:SetPoint("TOPLEFT",  0, -2)
    shimmer:SetPoint("TOPRIGHT", 0, -2)
    shimmer:SetHeight(16)
    shimmer:SetColorTexture(0.70, 0.48, 0.06, 0.09)
    shimmer:SetBlendMode("ADD")

    -- â”€â”€ Bottom subtle border â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local botLine = t:CreateTexture(nil, "BORDER")
    botLine:SetSize(W, 1); botLine:SetPoint("BOTTOMLEFT")
    botLine:SetColorTexture(0.28, 0.18, 0.04, 0.50)

    -- â”€â”€ Left accent bar (5px, category colored) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local leftAccent = t:CreateTexture(nil, "ARTWORK")
    leftAccent:SetSize(5, H - 8)
    leftAccent:SetPoint("TOPLEFT", 4, -4)
    leftAccent:SetColorTexture(0.90, 0.62, 0.08, 0.88)
    t.leftAccent = leftAccent

    -- â”€â”€ Vertical divider icon/text â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local divLine = t:CreateTexture(nil, "BORDER")
    divLine:SetSize(1, H - 16)
    divLine:SetPoint("TOPLEFT", 98, -8)
    divLine:SetColorTexture(0.35, 0.25, 0.08, 0.30)

    -- â”€â”€ Seal glow (alpha burst on show, category colored) â”€â”€â”€â”€â”€â”€â”€â”€
    local sealFrame = CreateFrame("Frame", nil, t)
    sealFrame:SetSize(94, 94)
    sealFrame:SetPoint("LEFT", 10, 0)
    sealFrame:SetAlpha(0)

    local sealGlow = sealFrame:CreateTexture(nil, "ARTWORK")
    sealGlow:SetAllPoints()
    sealGlow:SetTexture("Interface\\SpellActivationOverlay\\IconAlert")
    sealGlow:SetBlendMode("ADD")
    sealGlow:SetVertexColor(0.90, 0.65, 0.10)
    t.sealGlow = sealGlow

    local sealAnim = sealFrame:CreateAnimationGroup()
    sealAnim:SetLooping("NONE")
    local sa1 = sealAnim:CreateAnimation("Alpha")
    sa1:SetFromAlpha(0); sa1:SetToAlpha(1.0)
    sa1:SetDuration(0.07); sa1:SetOrder(1); sa1:SetSmoothing("NONE")
    local sa2 = sealAnim:CreateAnimation("Alpha")
    sa2:SetFromAlpha(1.0); sa2:SetToAlpha(0.16)
    sa2:SetDuration(0.80); sa2:SetOrder(2); sa2:SetSmoothing("OUT")
    sealAnim:SetScript("OnFinished", function() sealFrame:SetAlpha(0.16) end)
    t.sealAnim = sealAnim

    -- â”€â”€ Icon punch frame (70Ã—70 containing 64Ã—64 icon) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local iconPunch = CreateFrame("Frame", nil, t)
    iconPunch:SetSize(74, 74)
    iconPunch:SetPoint("LEFT", 12, 0)
    t.iconPunch = iconPunch

    local iconBg = iconPunch:CreateTexture(nil, "BACKGROUND")
    iconBg:SetAllPoints()
    iconBg:SetColorTexture(0.10, 0.06, 0.02, 0.82)

    local icon = iconPunch:CreateTexture(nil, "ARTWORK")
    icon:SetSize(64, 64)
    icon:SetPoint("CENTER")
    t.icon = icon

    local iconSheen = iconPunch:CreateTexture(nil, "OVERLAY")
    iconSheen:SetSize(64, 18)
    iconSheen:SetPoint("TOP", icon, "TOP", 0, 0)
    iconSheen:SetColorTexture(1, 1, 1, 0.09)

    local iconBd = iconPunch:CreateTexture(nil, "OVERLAY")
    iconBd:SetSize(70, 70)
    iconBd:SetPoint("CENTER")
    iconBd:SetTexture("Interface\\Buttons\\UI-Slot-Background")
    iconBd:SetAlpha(0.18)

    -- â”€â”€ Category badge (ALL CAPS, FRIZQT 7pt OUTLINE) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local badge = t:CreateFontString(nil, "OVERLAY")
    badge:SetFont("Fonts\\FRIZQT__.TTF", 7, "OUTLINE")
    badge:SetPoint("TOPLEFT", 108, -8)
    badge:SetShadowOffset(1, -1); badge:SetShadowColor(0, 0, 0, 1)
    t.badge = badge

    -- â”€â”€ Milestone name (MORPHEUS 15pt, golden) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local nameStr = t:CreateFontString(nil, "OVERLAY")
    nameStr:SetFont("Fonts\\MORPHEUS.TTF", 14, "")
    nameStr:SetPoint("TOPLEFT", 108, -20)
    nameStr:SetWidth(352); nameStr:SetJustifyH("LEFT")
    nameStr:SetShadowOffset(1, -1); nameStr:SetShadowColor(0, 0, 0, 1)
    t.nameStr = nameStr

    -- â”€â”€ Description (FRIZQT 8pt, warm gray) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local descStr = t:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    descStr:SetPoint("TOPLEFT", 108, -40)
    descStr:SetWidth(350); descStr:SetJustifyH("LEFT")
    descStr:SetTextColor(0.60, 0.52, 0.36)
    descStr:SetShadowOffset(1, -1); descStr:SetShadowColor(0, 0, 0, 0.60)
    t.descStr = descStr

    -- â”€â”€ Character name (bottom-right, ghosted) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local charStr = t:CreateFontString(nil, "OVERLAY")
    charStr:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    charStr:SetPoint("BOTTOMRIGHT", -8, 10)
    charStr:SetTextColor(0.44, 0.34, 0.20)
    t.charStr = charStr

    -- â”€â”€ Separator â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local sep = t:CreateTexture(nil, "BORDER")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT",  108, -58)
    sep:SetPoint("TOPRIGHT",  -8, -58)
    sep:SetColorTexture(0.35, 0.25, 0.08, 0.35)

    -- â”€â”€ Time depletion bar (bottom, full width, 3px) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local tBarBg = t:CreateTexture(nil, "BACKGROUND")
    tBarBg:SetHeight(3)
    tBarBg:SetPoint("BOTTOMLEFT",  5, 4)
    tBarBg:SetPoint("BOTTOMRIGHT", -5, 4)
    tBarBg:SetColorTexture(0.15, 0.10, 0.03, 0.55)

    local tBar = t:CreateTexture(nil, "BORDER")
    tBar:SetHeight(3)
    tBar:SetPoint("BOTTOMLEFT", 5, 4)
    tBar:SetWidth(W - 10)
    tBar:SetColorTexture(0.85, 0.60, 0.08, 0.88)
    t.timeBar = tBar

    local tbTicker = CreateFrame("Frame")
    tbTicker:Hide()
    tbTicker:SetScript("OnUpdate", function(self)
        local elapsed   = GetTime() - (self.startT or GetTime())
        local remaining = 1 - math.min(elapsed / (self.dur or 6), 1)
        local bw        = self.barW or (W - 10)
        t.timeBar:SetWidth(math.max(bw * remaining, 1))
        if remaining <= 0 then self:Hide() end
    end)
    t.tbTicker = tbTicker

    -- â”€â”€ Right-click to dismiss â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t:EnableMouse(true)
    t:SetScript("OnMouseDown", function(self, btn)
        if btn == "RightButton" then
            if self.hideTimer then self.hideTimer:Cancel(); self.hideTimer = nil end
            tbTicker:Hide()
            self:SetScript("OnUpdate", nil)
            self:Hide()
        end
    end)

    self.milestoneToastFrame = t
end

function UI:ShowMilestoneToast(milestone, charName)
    if not self.milestoneToastFrame then self:BuildMilestoneToastFrame() end
    local t = self.milestoneToastFrame

    if t.hideTimer   then t.hideTimer:Cancel(); t.hideTimer = nil end
    t:SetScript("OnUpdate", nil)
    t.tbTicker:Hide()

    -- â”€â”€ Fill content â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t.icon:SetTexture(milestone.icon)
    t.nameStr:SetText(milestone.name or "")
    t.descStr:SetText(milestone.desc or "")
    t.charStr:SetText(charName and ("|cff7A6250" .. charName .. "|r") or "")

    -- â”€â”€ Category color palette â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local CAT = {
        combat   = {0.90, 0.35, 0.10},
        survival = {0.10, 0.82, 0.42},
        social   = {0.20, 0.58, 1.00},
        legend   = {0.85, 0.65, 0.10},
        death    = {0.72, 0.18, 0.18},
    }
    local c = CAT[milestone.category] or CAT.legend
    t.topBar:SetColorTexture(c[1], c[2], c[3], 1)
    t.leftAccent:SetColorTexture(c[1], c[2], c[3], 0.88)
    t.sealGlow:SetVertexColor(c[1], c[2], c[3])
    t.nameStr:SetTextColor(math.min(1, c[1]*1.15), math.min(1, c[2]*1.15), math.min(1, c[3]*1.15))

    local catLabels = {
        combat   = "KAMPF-MEILENSTEIN",
        survival = "UEBERLEBEN",
        social   = "SOZIAL",
        legend   = "LEGENDAERER MEILENSTEIN",
        death    = "TODES-MEILENSTEIN",
    }
    t.badge:SetText("|cffFFD100" .. (catLabels[milestone.category] or "MEILENSTEIN") .. "|r")

    -- â”€â”€ Reset time bar â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t.timeBar:SetWidth(t.W - 10)

    -- â”€â”€ Animate: float-up from bottom (cubic ease-out) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t:Show()
    t:SetAlpha(0)
    t:ClearAllPoints()

    local startY  = 120
    local endY    = 172
    local slideDur = 0.38
    local slideEl  = 0

    t:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, startY)

    t:SetScript("OnUpdate", function(self, dt)
        slideEl = slideEl + dt
        local p  = math.min(slideEl / slideDur, 1)
        local ep = 1 - (1 - p) ^ 3
        self:SetAlpha(ep)
        self:ClearAllPoints()
        self:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, startY + (endY - startY) * ep)
        if slideEl >= slideDur then
            self:SetScript("OnUpdate", nil)
            self:SetAlpha(1)
            self:ClearAllPoints()
            self:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, endY)
            GDL_ScaleLerp(t.iconPunch, 1.28, 1.0, 0.24, "OUT")
            t.sealAnim:Play()
            t.tbTicker.startT = GetTime()
            t.tbTicker.dur    = 6.2
            t.tbTicker.barW   = t.W - 10
            t.tbTicker:Show()
            -- ─── MILESTONE FX ────────────────────────────────────────────
            local AnimFX = GDL:GetModule("AnimFX")
            if AnimFX then
                AnimFX.MilestoneFanfare(t, c[1], c[2], c[3])
            end
        end
    end)

    -- â”€â”€ Auto-hide: fade + float down â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local UI = self
    t.hideTimer = C_Timer.NewTimer(7.0, function()
        if not UI.milestoneToastFrame then return end
        t.tbTicker:Hide()
        t:SetScript("OnUpdate", nil)
        local outEl  = 0
        local outDur = 0.38
        t:SetScript("OnUpdate", function(self, dt)
            outEl = outEl + dt
            local p  = math.min(outEl / outDur, 1)
            local ep = p * p
            self:SetAlpha(1 - ep)
            self:ClearAllPoints()
            self:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, endY - 24 * ep)
            if outEl >= outDur then
                self:SetScript("OnUpdate", nil)
                self:Hide()
                self:SetAlpha(1)
            end
        end)
    end)

    PlaySound(888, "Master")
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- EINSTELLUNGEN
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•


function UI:CreateSettings()
    local f = CreateFrame("Frame", "GDLEinstellungen", UIParent, "BackdropTemplate")
    f:SetSize(320, 460)
    f:SetPoint("CENTER", 260, -60)
    f:SetFrameStrata("HIGH")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:Hide()

    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        tile = false,
        tileSize = 256,
        edgeSize = 32,
        insets = { left = 6, right = 6, top = 6, bottom = 6 }
    })
    f:SetBackdropColor(1, 1, 1, 1)
    f:SetBackdropBorderColor(0.8, 0.7, 0.5, 1)

    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 18, "")
    title:SetPoint("TOP", 0, -14)
    title:SetText("|cff8B4513Einstellungen|r")

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", 2, 2)

    local function MakeCheck(textLabel, key, y)
        local cb = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate")
        cb:SetPoint("TOPLEFT", 18, y)
        local label = cb.Text or _G[cb:GetName() .. "Text"]
        if label then
            label:SetText(textLabel)
            label:SetTextColor(0.25, 0.18, 0.12)
        end
        cb:SetChecked(GuildDeathLogDB.settings[key] and true or false)
        cb:SetScript("OnClick", function(self)
            GuildDeathLogDB.settings[key] = self:GetChecked() and true or false
        end)
        return cb
    end

    local y = -52
    local dy = -26

    MakeCheck("Gildenchat-Ankuendigung", "announce", y); y = y + dy
    MakeCheck("Sound abspielen", "sound", y); y = y + dy
    MakeCheck("Todes-Popup anzeigen", "overlay", y); y = y + dy
    MakeCheck("Karten-Markierungen (Tode)", "mapMarkers", y); y = y + dy
    MakeCheck("Gilden-Karte (Live-Positionen)", "guildTracker", y); y = y + dy
    MakeCheck("Titel auf Nameplates", "nameplateTitles", y); y = y + dy
    MakeCheck("Meilenstein-Nachrichten", "milestoneAnnounce", y); y = y + dy
    MakeCheck("Meilenstein-Popup", "milestonePopup", y); y = y + dy
    MakeCheck("Blizzard HC-Channel", "useBlizzardChannel", y); y = y + dy
    MakeCheck("Addon-Sync aktiv", "useAddonChannel", y); y = y + dy
    MakeCheck("Debug-Ausgaben", "debugPrint", y); y = y + dy

    -- Popup size slider (Overlay scale)
    local slider = CreateFrame("Slider", "GDLPoupScaleSlider", f, "OptionsSliderTemplate")
    slider:SetPoint("TOPLEFT", 18, -358)
    slider:SetWidth(280)
    slider:SetMinMaxValues(0.5, 2.0)
    slider:SetValueStep(0.05)
    slider:SetObeyStepOnDrag(true)
    slider:SetValue(GuildDeathLogDB.settings.overlayScale or 1.0)

    local low = _G[slider:GetName() .. "Low"]
    local high = _G[slider:GetName() .. "High"]
    local text = _G[slider:GetName() .. "Text"]
    if low then low:SetText("50%") end
    if high then high:SetText("200%") end
    if text then text:SetText("Popup-Groesse / Popup Size:") end

    slider:SetScript("OnValueChanged", function(self, value)
        value = math.max(0.5, math.min(2.0, value))
        GuildDeathLogDB.settings.overlayScale = value
        if UI.overlay then
            UI.overlay:SetScale(value)
            UI.overlay.currentScale = value
        end
    end)

    local function MakeButton(label, width)
        local b = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
        b:SetText(label)
        b:SetWidth(width)
        b:SetHeight(24)
        return b
    end

    local btnTest = MakeButton("Test", 80)
    btnTest:SetPoint("BOTTOMLEFT", 18, 16)
    btnTest:SetScript("OnClick", function()
        local dummy = {
            name = UnitName("player") or "DEINNAME",
            level = UnitLevel("player") or 1,
            classId = select(3, UnitClass("player")) or 1,
            zone = GetZoneText() or "",
            killerName = "Test",
            killer = "Test",
            lastWords = "Nur ein Test...",
        }
        UI:ShowOverlay(dummy, true)
    end)

    local btnReset = MakeButton("Reset Pos", 90)
    btnReset:SetPoint("BOTTOM", 0, 16)
    btnReset:SetScript("OnClick", function()
        GuildDeathLogDB.settings.overlayPosition = nil
        if UI.overlay then
            UI.overlay:ClearAllPoints()
            UI.overlay:SetPoint("TOP", 0, -80)
        end
        GDL:Print("|cffFFD100[GDL]|r Overlay-Position zurueckgesetzt.")
    end)

    local btnOk = MakeButton("OK", 80)
    btnOk:SetPoint("BOTTOMRIGHT", -18, 16)
    btnOk:SetScript("OnClick", function() f:Hide() end)

    self.settingsFrame = f
end


function UI:ShowSettings()
    if not self.settingsFrame then self:CreateSettings() end
    self.settingsFrame:Show()
end

-- Separates the optional supporter UI from the normal settings so the
-- settings window stays clean and doesn't overflow.


-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- RUHMESHALLE / HALL OF FAME (v4.0)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
function UI:ShowHallOfFame()
    if not self.hallOfFameFrame then
        self:CreateHallOfFameWindow()
    end
    self.hallOfFameFrame:Show()
    self:UpdateHallOfFame()
end

function UI:CreateHallOfFameWindow()
    local HoF = GDL:GetModule("HallOfFame")
    if not HoF then return end
    
    local f = CreateFrame("Frame", "GDLHallOfFame", UIParent, "BackdropTemplate")
    f:SetSize(450, 500)
    f:SetPoint("CENTER", UIParent, "CENTER", 520, 0)  -- Rechts-mitte (cascade)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(110)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    
    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 26,
        insets = {left = 5, right = 5, top = 5, bottom = 5}
    })
    f:SetBackdropColor(1, 1, 1, 1)
    f:SetBackdropBorderColor(0.8, 0.7, 0.4, 1)
    
    -- Close Button
    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -5, -5)
    
    -- Content
    self.hallOfFameTab = HoF:CreateTab(f)
    self.hallOfFameTab:Show()
    
    self.hallOfFameFrame = f
end

function UI:UpdateHallOfFame()
    if self.hallOfFameTab and self.hallOfFameTab.Update then
        self.hallOfFameTab.Update()
    end
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- STATISTIKEN / STATISTICS (v4.0)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
function UI:ShowStatistics()
    if not self.statsFrame then
        self:CreateStatisticsWindow()
    end
    self.statsFrame:Show()
    self:UpdateStatistics()
end

function UI:CreateStatisticsWindow()
    local f = CreateFrame("Frame", "GDLStatistics", UIParent, "BackdropTemplate")
    f:SetSize(420, 480)
    f:SetPoint("CENTER", UIParent, "CENTER", 500, -300)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(110)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    
    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 26,
        insets = {left = 5, right = 5, top = 5, bottom = 5}
    })
    
    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -5, -5)
    
    -- Title mit Icon
    local titleIcon = f:CreateTexture(nil, "ARTWORK")
    titleIcon:SetSize(28, 28)
    titleIcon:SetPoint("TOP", -100, -12)
    titleIcon:SetTexture("Interface\\Icons\\INV_Misc_Book_09")
    
    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 16, "")
    title:SetPoint("LEFT", titleIcon, "RIGHT", 8, 0)
    local statsTitle = GetLocale() == "deDE" and "Statistiken" or "Statistics"
    title:SetText("|cff1a0a00" .. statsTitle .. "|r")
    
    -- Scroll für Content
    local scroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 20, -50)
    scroll:SetPoint("BOTTOMRIGHT", -35, 20)
    
    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(scroll:GetWidth(), 1200)
    child:SetHeight(1200)  -- Wird durch content:SetHeight() dynamisch angepasst
    scroll:SetScrollChild(child)
    f.contentFrame = child
    
    self.statsFrame = f
end

function UI:UpdateStatistics()
    local Stats = GDL:GetModule("Statistics")
    if not Stats or not self.statsFrame then return end
    
    local content = self.statsFrame.contentFrame
    -- Clear
    for _, c in ipairs({content:GetChildren()}) do c:Hide() end
    for _, r in ipairs({content:GetRegions()}) do r:Hide() end
    
    local y = 0
    local summary = Stats:GetSummary()
    local zones = Stats:GetDangerousZones(5)
    local killers = Stats:GetDeadliestKillers(5)
    local classes = Stats:GetDeathsByClass()
    
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- ZUSAMMENFASSUNG BOX
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local sumBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
    sumBox:SetSize(360, 95)
    sumBox:SetPoint("TOPLEFT", 0, y)
    sumBox:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 12,
        insets = {left = 2, right = 2, top = 2, bottom = 2}
    })
    sumBox:SetBackdropColor(0.1, 0.08, 0.05, 0.7)
    sumBox:SetBackdropBorderColor(0.6, 0.5, 0.3, 0.9)
    
    local sumIcon = sumBox:CreateTexture(nil, "ARTWORK")
    sumIcon:SetSize(32, 32)
    sumIcon:SetPoint("TOPLEFT", 10, -10)
    sumIcon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
    
    local sumTitle = sumBox:CreateFontString(nil, "OVERLAY")
    sumTitle:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    sumTitle:SetPoint("TOPLEFT", sumIcon, "TOPRIGHT", 8, -2)
    sumTitle:SetText("|cffFFDD88Zusammenfassung / Summary|r")
    
    -- Statistik-Werte in 2 Spalten
    local col1X, col2X = 15, 190
    local row1Y, row2Y, row3Y = -50, -65, -80
    
    local function StatLabel(parent, x, y, label, value)
        local lbl = parent:CreateFontString(nil, "OVERLAY")
        lbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        lbl:SetPoint("TOPLEFT", x, y)
        lbl:SetText("|cffCCBBAA" .. label .. ":|r")
        
        local val = parent:CreateFontString(nil, "OVERLAY")
        val:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        val:SetPoint("TOPLEFT", x + 80, y)
        val:SetText("|cffFFFFFF" .. tostring(value) .. "|r")
    end
    
    StatLabel(sumBox, col1X, row1Y, "Gesamt/Total", summary.total .. " Tode/Deaths")
    StatLabel(sumBox, col2X, row1Y, "Heute/Today", summary.today)
    StatLabel(sumBox, col1X, row2Y, "Woche/Week", summary.thisWeek)
    StatLabel(sumBox, col2X, row2Y, "Avg Level", string.format("%.1f", summary.avgLevel))
    StatLabel(sumBox, col1X, row3Y, "Max Level", summary.highestLevel)
    StatLabel(sumBox, col2X, row3Y, "Min Level", summary.lowestLevel)
    
    y = y - 105
    
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- GEFAEHRLICHSTE ZONEN
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local zoneBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
    zoneBox:SetSize(175, 120)
    zoneBox:SetPoint("TOPLEFT", 0, y)
    zoneBox:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 12,
        insets = {left = 2, right = 2, top = 2, bottom = 2}
    })
    zoneBox:SetBackdropColor(0.15, 0.05, 0.05, 0.6)
    zoneBox:SetBackdropBorderColor(0.6, 0.3, 0.3, 0.9)
    zoneBox:SetClipsChildren(true)
    
    local zoneIcon = zoneBox:CreateTexture(nil, "ARTWORK")
    zoneIcon:SetSize(20, 20)
    zoneIcon:SetPoint("TOPLEFT", 8, -8)
    zoneIcon:SetTexture("Interface\\Icons\\Ability_Hunter_Pathfinding")
    
    local zoneTitle = zoneBox:CreateFontString(nil, "OVERLAY")
    zoneTitle:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    zoneTitle:SetPoint("LEFT", zoneIcon, "RIGHT", 5, 0)
    zoneTitle:SetText("|cffFF9999Zonen / Zones|r")
    
    local zy = -35
    if #zones == 0 then
        local noData = zoneBox:CreateFontString(nil, "OVERLAY")
        noData:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        noData:SetPoint("TOPLEFT", 10, zy)
        noData:SetText("|cff888888Keine Daten / No data|r")
    else
        for i, zone in ipairs(zones) do
            if i > 5 then break end
            local zt = zoneBox:CreateFontString(nil, "OVERLAY")
            zt:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            zt:SetPoint("TOPLEFT", 10, zy)
            local zoneName = (zone.zone or "?"):sub(1, 12)
            zt:SetText(string.format("|cffFFAAAA%d.|r |cffFFFFFF%s|r |cffCCCCCC(%d)|r", i, zoneName, zone.count))
            zy = zy - 14
        end
    end
    
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- TOEDLICHSTE MONSTER
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local killerBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
    killerBox:SetSize(175, 120)
    killerBox:SetPoint("TOPLEFT", 185, y)
    killerBox:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 12,
        insets = {left = 2, right = 2, top = 2, bottom = 2}
    })
    killerBox:SetBackdropColor(0.1, 0.05, 0.15, 0.6)
    killerBox:SetBackdropBorderColor(0.5, 0.3, 0.6, 0.9)
    killerBox:SetClipsChildren(true)
    
    local killerIcon = killerBox:CreateTexture(nil, "ARTWORK")
    killerIcon:SetSize(20, 20)
    killerIcon:SetPoint("TOPLEFT", 8, -8)
    killerIcon:SetTexture("Interface\\Icons\\Ability_Creature_Cursed_02")
    
    local killerTitle = killerBox:CreateFontString(nil, "OVERLAY")
    killerTitle:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    killerTitle:SetPoint("LEFT", killerIcon, "RIGHT", 5, 0)
    killerTitle:SetText("|cffCC99FFMonster / Killers|r")
    
    local ky = -35
    if #killers == 0 then
        local noData = killerBox:CreateFontString(nil, "OVERLAY")
        noData:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        noData:SetPoint("TOPLEFT", 10, ky)
        noData:SetText("|cff888888Keine Daten / No data|r")
    else
        for i, killer in ipairs(killers) do
            if i > 5 then break end
            local kt = killerBox:CreateFontString(nil, "OVERLAY")
            kt:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            kt:SetPoint("TOPLEFT", 10, ky)
            local killerName = (killer.name or "?"):sub(1, 12)
            kt:SetText(string.format("|cffDDBBFF%d.|r |cffFFFFFF%s|r |cffCCCCCC(%d)|r", i, killerName, killer.count))
            ky = ky - 14
        end
    end
    
    y = y - 130
    
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- TODE NACH KLASSE
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local classBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
    classBox:SetSize(360, 145)
    classBox:SetPoint("TOPLEFT", 0, y)
    classBox:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 12,
        insets = {left = 2, right = 2, top = 2, bottom = 2}
    })
    classBox:SetBackdropColor(0.08, 0.08, 0.12, 0.6)
    classBox:SetBackdropBorderColor(0.4, 0.4, 0.6, 0.9)
    classBox:SetClipsChildren(true)
    
    local classIcon = classBox:CreateTexture(nil, "ARTWORK")
    classIcon:SetSize(20, 20)
    classIcon:SetPoint("TOPLEFT", 8, -8)
    classIcon:SetTexture("Interface\\Icons\\INV_Misc_GroupLooking")
    
    local classTitle = classBox:CreateFontString(nil, "OVERLAY")
    classTitle:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    classTitle:SetPoint("LEFT", classIcon, "RIGHT", 5, 0)
    classTitle:SetText("|cff99BBFFKlassen / Classes|r")
    
    -- 2 Spalten fuer Klassen
    local cy = -35
    local classCol = 0
    for i, class in ipairs(classes) do
        local col = self:GetClassColor(class.classId)
        local xOffset = classCol == 0 and 10 or 185
        
        -- Klassen-Icon
        local cIcon = classBox:CreateTexture(nil, "ARTWORK")
        cIcon:SetSize(16, 16)
        cIcon:SetPoint("TOPLEFT", xOffset, cy)
        cIcon:SetTexture(self:GetClassIcon(class.classId))
        
        -- Klassen-Name und Count - helle Klassenfarben
        local ct = classBox:CreateFontString(nil, "OVERLAY")
        ct:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        ct:SetPoint("LEFT", cIcon, "RIGHT", 5, 0)
        -- Farben aufhellen
        local r = math.min(1, col[1] + 0.3)
        local g = math.min(1, col[2] + 0.3)
        local b = math.min(1, col[3] + 0.3)
        ct:SetText(string.format("|cff%02x%02x%02x%s|r: |cffFFFFFF%d|r", 
            r*255, g*255, b*255, 
            (class.name or "?"):sub(1,10), class.count))
        
        classCol = classCol + 1
        if classCol >= 2 then
            classCol = 0
            cy = cy - 22
        end
    end
    
    y = y - 155

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- LEVEL-VERTEILUNG (GetDeathsByLevel)
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local levelData = Stats:GetDeathsByLevel()
    local maxLevelDeaths = 0
    for _, count in ipairs(levelData) do
        if count > maxLevelDeaths then maxLevelDeaths = count end
    end

    if maxLevelDeaths > 0 then
        local lvlBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
        lvlBox:SetSize(360, 130)
        lvlBox:SetPoint("TOPLEFT", 0, y)
        lvlBox:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12, insets = {left=2, right=2, top=2, bottom=2}
        })
        lvlBox:SetBackdropColor(0.05, 0.1, 0.15, 0.6)
        lvlBox:SetBackdropBorderColor(0.4, 0.6, 0.9, 0.9)
        lvlBox:SetClipsChildren(true)

        local lvlIcon = lvlBox:CreateTexture(nil, "ARTWORK")
        lvlIcon:SetSize(20, 20); lvlIcon:SetPoint("TOPLEFT", 8, -8)
        lvlIcon:SetTexture("Interface\\Icons\\INV_Misc_Note_02")
        local lvlTitle = lvlBox:CreateFontString(nil, "OVERLAY")
        lvlTitle:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        lvlTitle:SetPoint("LEFT", lvlIcon, "RIGHT", 5, 0)
        lvlTitle:SetText("|cff88BBFFLevel-Verteilung (Tode pro Level)|r")

        -- Balken-Chart: Level 1-60 in 12 Gruppen Ã  5 Level
        -- Innerer Container: Balken werden NUR in diesem Frame gezeichnet
        -- Position: direkt unter Titel, über den Labels
        local barArea = CreateFrame("Frame", nil, lvlBox)
        barArea:SetPoint("TOPLEFT", lvlBox, "TOPLEFT", 8, -34)   -- 34px unter Box-Top (Titel endet bei ~30)
        barArea:SetPoint("BOTTOMRIGHT", lvlBox, "BOTTOMRIGHT", -8, 16) -- 16px vom Bottom (Platz fuer Labels)
        barArea:SetClipsChildren(true)

        local barW = 26
        local barAreaH = 130 - 34 - 16  -- = 80px verfuegbar
        for group = 1, 12 do
            local groupCount = 0
            local startLvl = (group-1)*5 + 1
            local endLvl = group*5
            for lvl = startLvl, endLvl do
                groupCount = groupCount + (levelData[lvl] or 0)
            end
            if groupCount > 0 then
                local barH = math.max(2, math.floor((groupCount / maxLevelDeaths) * barAreaH))
                local barFrame = CreateFrame("Frame", nil, barArea)
                barFrame:SetSize(barW - 2, barH)
                barFrame:SetPoint("BOTTOMLEFT", barArea, "BOTTOMLEFT", 2 + (group-1)*barW, 0)
                local bar = barFrame:CreateTexture(nil, "ARTWORK")
                bar:SetAllPoints(barFrame)
                -- Farbe: niedrig=grün, hoch=rot
                local r = math.min(1, group/12 * 1.5)
                local g = math.min(1, (1 - group/12) * 1.5)
                bar:SetColorTexture(r, g, 0.2, 0.8)
            end
            -- Label
            local lbl = lvlBox:CreateFontString(nil, "OVERLAY")
            lbl:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
            lbl:SetPoint("BOTTOMLEFT", 8 + (group-1)*barW, 8)
            lbl:SetText(tostring(startLvl))
            lbl:SetTextColor(0.7, 0.7, 0.7)
        end
        y = y - 140
    end

    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    -- STUNDEN-VERTEILUNG (GetDeathsByTime)
    -- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
    local hourData, dayData = Stats:GetDeathsByTime()
    local maxHour = 0
    for h = 0, 23 do if (hourData[h] or 0) > maxHour then maxHour = hourData[h] end end

    if maxHour > 0 then
        local timeBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
        timeBox:SetSize(360, 120)
        timeBox:SetPoint("TOPLEFT", 0, y)
        timeBox:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12, insets = {left=2, right=2, top=2, bottom=2}
        })
        timeBox:SetBackdropColor(0.08, 0.12, 0.08, 0.6)
        timeBox:SetBackdropBorderColor(0.4, 0.8, 0.4, 0.9)
        timeBox:SetClipsChildren(true)

        local tIcon = timeBox:CreateTexture(nil, "ARTWORK")
        tIcon:SetSize(20, 20); tIcon:SetPoint("TOPLEFT", 8, -8)
        tIcon:SetTexture("Interface\\Icons\\INV_Misc_PocketWatch_01")
        local tTitle = timeBox:CreateFontString(nil, "OVERLAY")
        tTitle:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        tTitle:SetPoint("LEFT", tIcon, "RIGHT", 5, 0)
        tTitle:SetText("|cff88FF88Tode nach Uhrzeit (Server)|r")

        local barW2 = math.floor(340/24)
        -- Innerer Container fuer Balken
        local barArea2 = CreateFrame("Frame", nil, timeBox)
        barArea2:SetPoint("TOPLEFT", timeBox, "TOPLEFT", 8, -34)
        barArea2:SetPoint("BOTTOMRIGHT", timeBox, "BOTTOMRIGHT", -8, 16)
        barArea2:SetClipsChildren(true)

        local barAreaH2 = 120 - 34 - 16  -- = 70px
        for h = 0, 23 do
            local count = hourData[h] or 0
            if count > 0 then
                local barH = math.max(2, math.floor((count / maxHour) * barAreaH2))
                local barF = CreateFrame("Frame", nil, barArea2)
                barF:SetSize(barW2 - 1, barH)
                barF:SetPoint("BOTTOMLEFT", barArea2, "BOTTOMLEFT", 2 + h*barW2, 0)
                local bar = barF:CreateTexture(nil, "ARTWORK")
                bar:SetAllPoints(barF)
                -- Nacht=blau, Tag=gelb
                local isDaytime = (h >= 6 and h <= 20)
                bar:SetColorTexture(isDaytime and 0.9 or 0.3, isDaytime and 0.8 or 0.4, isDaytime and 0.2 or 0.9, 0.8)
            end
            if h % 6 == 0 then
                local lbl = timeBox:CreateFontString(nil, "OVERLAY")
                lbl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
                lbl:SetPoint("BOTTOMLEFT", 8 + h*barW2, 6)
                lbl:SetText(string.format("%02d", h))
                lbl:SetTextColor(0.6, 0.6, 0.6)
            end
        end
        y = y - 130
    end

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


-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- KILLSTATS FENSTER (Tötungen nach Kreaturtyp + Meilensteine)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:ShowKillStats()
    if not self.killStatsFrame then
        self:CreateKillStatsWindow()
    end
    self.killStatsFrame:Show()
    self:UpdateKillStats()
end

function UI:CreateKillStatsWindow()
    local f = CreateFrame("Frame", "GDLKillStats", UIParent, "BackdropTemplate")
    f:SetSize(400, 440)
    f:SetPoint("CENTER", UIParent, "CENTER", -500, 200)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(110)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 26, insets = {left=5, right=5, top=5, bottom=5}
    })

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -5, -5)

    local titleIcon = f:CreateTexture(nil, "ARTWORK")
    titleIcon:SetSize(28, 28)
    titleIcon:SetPoint("TOP", -90, -12)
    titleIcon:SetTexture("Interface\\Icons\\Ability_Warrior_Sunder")

    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 15, "")
    title:SetPoint("LEFT", titleIcon, "RIGHT", 8, 0)
    local t = GetLocale() == "deDE" and "Kill-Statistiken" or "Kill Statistics"
    title:SetText("|cff1a0a00" .. t .. "|r")

    local scroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 20, -50)
    scroll:SetPoint("BOTTOMRIGHT", -35, 20)
    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(340, 800)
    scroll:SetScrollChild(child)
    f.contentFrame = child
    self.killStatsFrame = f
end

function UI:UpdateKillStats()
    local KS = GDL:GetModule("KillStats")
    if not KS or not self.killStatsFrame then return end

    local content = self.killStatsFrame.contentFrame
    for _, c in ipairs({content:GetChildren()}) do c:Hide() end
    for _, r in ipairs({content:GetRegions()}) do r:Hide() end

    local charKey = KS:GetCharacterKey()
    local stats = KS:GetStats(charKey)
    local totalKills = KS:GetTotalKills(charKey)
    local y = 0

    -- Header: Gesamtkills
    local totalBox = CreateFrame("Frame", nil, content, "BackdropTemplate")
    totalBox:SetSize(330, 50)
    totalBox:SetPoint("TOPLEFT", 0, y)
    totalBox:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", edgeSize=10, insets={left=2,right=2,top=2,bottom=2}})
    totalBox:SetBackdropColor(0.1, 0.08, 0.05, 0.7)
    totalBox:SetBackdropBorderColor(0.7, 0.6, 0.3, 0.9)

    local charName = UnitName("player") or "?"
    local charRealm = GetRealmName() or ""
    local displayName = charName .. (charRealm ~= "" and ("-" .. charRealm) or "")
    local totalLbl = totalBox:CreateFontString(nil, "OVERLAY")
    totalLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    totalLbl:SetPoint("TOPLEFT", 10, -10)
    totalLbl:SetText("|cffFFDD88" .. displayName .. ":|r |cffFFFFFF" .. totalKills .. " |cffCCCCCCKills gesamt|r")

    local unlockedMilestones = KS:CountUnlockedMilestones()
    local milestoneLbl = totalBox:CreateFontString(nil, "OVERLAY")
    milestoneLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    milestoneLbl:SetPoint("TOPLEFT", 10, -28)
    milestoneLbl:SetText("|cff00FF88" .. (unlockedMilestones or 0) .. " Meilensteine freigeschaltet|r")
    y = y - 60

    -- Kills nach Kreaturtyp
    local typeOrder = {"humanoid","undead","demon","dragonkin","beast","elemental","giant","mechanical","critter"}
    local typeNames = {
        humanoid="Humanoid", undead="Untoter", demon="Dämon",
        dragonkin="Drachkin", beast="Wildtier", elemental="Elementar",
        giant="Riese", mechanical="Mechanisch", critter="Kleintier"
    }
    local typeIcons = {
        humanoid="Interface\\Icons\\INV_Misc_EngGizmos_19",
        undead="Interface\\Icons\\Spell_Shadow_AnimateDead",
        demon="Interface\\Icons\\Ability_Warlock_SummonImp",
        dragonkin="Interface\\Icons\\Ability_Dragonkin",
        beast="Interface\\Icons\\Ability_Hunter_BeastCall",
        elemental="Interface\\Icons\\Spell_Fire_SelfDestruct",
        giant="Interface\\Icons\\INV_Misc_MonsterHead_02",
        mechanical="Interface\\Icons\\INV_Misc_EngGizmos_04",
        critter="Interface\\Icons\\Ability_Hunter_Pet_Moth",
    }

    local headerLbl = content:CreateFontString(nil, "OVERLAY")
    headerLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    headerLbl:SetPoint("TOPLEFT", 5, y)
    headerLbl:SetText("|cffCCBBAATötungen nach Kreaturtyp:|r")
    y = y - 18

    local maxCount = 1
    for _, typeKey in ipairs(typeOrder) do
        local c = KS:GetKillCount(typeKey, charKey)
        if c > maxCount then maxCount = c end
    end

    for _, typeKey in ipairs(typeOrder) do
        local count = KS:GetKillCount(typeKey, charKey)
        if count > 0 then
            local row = CreateFrame("Frame", nil, content)
            row:SetSize(330, 22)
            row:SetPoint("TOPLEFT", 0, y)

            local icon = row:CreateTexture(nil, "ARTWORK")
            icon:SetSize(18, 18)
            icon:SetPoint("LEFT", 0, 0)
            icon:SetTexture(typeIcons[typeKey] or "Interface\\Icons\\INV_Misc_QuestionMark")

            local nameLbl = row:CreateFontString(nil, "OVERLAY")
            nameLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            nameLbl:SetPoint("LEFT", 22, 0)
            nameLbl:SetWidth(90)
            nameLbl:SetText(typeNames[typeKey] or typeKey)
            nameLbl:SetTextColor(0.9, 0.85, 0.7)

            -- Balken
            local barBg = row:CreateTexture(nil, "BACKGROUND")
            barBg:SetSize(150, 12)
            barBg:SetPoint("LEFT", 115, 0)
            barBg:SetColorTexture(0.1, 0.1, 0.1, 0.8)

            local barFill = row:CreateTexture(nil, "ARTWORK")
            local barW = math.max(2, math.floor((count / maxCount) * 150))
            barFill:SetSize(barW, 12)
            barFill:SetPoint("LEFT", 115, 0)
            barFill:SetColorTexture(0.6, 0.3, 0.1, 0.9)

            local countLbl = row:CreateFontString(nil, "OVERLAY")
            countLbl:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            countLbl:SetPoint("LEFT", 270, 0)
            countLbl:SetText("|cffFFFFFF" .. count .. "|r")

            y = y - 24
        end
    end

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

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- ERFOLGE / ACHIEVEMENTS (v4.0)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
function UI:ShowAchievements()
    if not self.achieveFrame then
        self:CreateMilestonesWindow()
    end
    -- Ohne Supporter-Code direkt auf Support-Tab springen,
    -- damit der User den Code eingeben kann
    if not GDL:IsSupporterActive() then
        self.achieveFrame.selectedCategory = "support"
    end
    self.achieveFrame:Show()
    self:UpdateMilestones()
end

function UI:CreateMilestonesWindow()
    local f = CreateFrame("Frame", "GDLMilestones", UIParent, "BackdropTemplate")
    f:SetSize(450, 550)
    f:SetPoint("CENTER", UIParent, "CENTER", 560, -330)  -- Rechts-unten diagonal versetzt (cascade)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(110)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    
    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 26,
        insets = {left = 5, right = 5, top = 5, bottom = 5}
    })
    
    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -5, -5)
    
    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 18, "")
    title:SetPoint("TOP", 0, -15)
    title:SetText("|cff1a0a00" .. GDL:L("BTN_MILESTONES") .. "|r")
    
    -- Charakter-Name Anzeige
    local charInfo = f:CreateFontString(nil, "OVERLAY")
    charInfo:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    charInfo:SetPoint("TOP", title, "BOTTOM", 0, -5)
    charInfo:SetTextColor(0.2, 0.15, 0.1)
    f.charInfo = charInfo

    -- Supporter-Status (nur Info / kosmetisch)
    local supInfo = f:CreateFontString(nil, "OVERLAY")
    -- NOTE: Needs a valid font path or SetText() may error with "Font not set" on Classic.
    supInfo:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", 10, "")
    supInfo:SetPoint("TOP", charInfo, "BOTTOM", 0, -2)
    supInfo:SetTextColor(0.25, 0.2, 0.15)
    f.supInfo = supInfo

    
    -- Kategorie-Namen lokalisiert (für Tooltips)
    local catTooltips = {
        level = GetLocale() == "deDE" and "Level-Meilensteine" or "Level Milestones",
        survival = GetLocale() == "deDE" and "Spielzeit" or "Playtime",
        dungeon = GetLocale() == "deDE" and "Dungeon-Bosse" or "Dungeon Bosses",
        raid = GetLocale() == "deDE" and "Raid-Bosse" or "Raid Bosses",
        profession = GetLocale() == "deDE" and "Berufe" or "Professions",
        kills = GetLocale() == "deDE" and "Kill-Statistiken" or "Kill Stats",
        wealth = GetLocale() == "deDE" and "Gold-Meilensteine" or "Gold Milestones",
        meta = GetLocale() == "deDE" and "Meta-Erfolge" or "Meta Achievements",
        secret = GetLocale() == "deDE" and "Geheime Erfolge" or "Secret Achievements",
        support = GetLocale() == "deDE" and "Unterstützung" or "Support",
    }
    
    -- Kategorie-Buttons - NUR Icons in einer Reihe
    local categories = {
        {id = "level", icon = "Interface\\Icons\\Spell_Holy_WordFortitude", color = {0.2, 0.8, 0.2}},
        {id = "survival", icon = "Interface\\Icons\\Spell_Nature_TimeStop", color = {0.4, 0.6, 0.9}},
        {id = "dungeon", icon = "Interface\\Icons\\INV_Misc_Key_04", color = {0.5, 0.5, 0.9}},
        {id = "raid", icon = "Interface\\Icons\\INV_Misc_Head_Dragon_01", color = {0.9, 0.5, 0.2}},
        {id = "profession", icon = "Interface\\Icons\\Trade_BlackSmithing", color = {0.7, 0.6, 0.3}},
        {id = "kills", icon = "Interface\\Icons\\Ability_DualWield", color = {0.9, 0.3, 0.3}},
        {id = "wealth", icon = "Interface\\Icons\\INV_Misc_Coin_06", color = {0.9, 0.8, 0.2}},
        {id = "meta", icon = "Interface\\Icons\\INV_Misc_AhnQirajTrinket_03", color = {0.6, 0.4, 0.8}},
        {id = "secret", icon = "Interface\\Icons\\INV_Misc_QuestionMark", color = {0.8, 0.2, 0.8}},
        {id = "support", icon = "Interface\\Icons\\INV_ValentinesCard01", color = {0.9, 0.3, 0.5}},
    }
    
    f.categoryButtons = {}
    f.selectedCategory = "level"
    
    -- Icon-only Buttons in einer Reihe
    local iconSize = 32
    local iconSpacing = 6
    local totalWidth = #categories * iconSize + (#categories - 1) * iconSpacing
    local startX = -totalWidth / 2
    
    for i, cat in ipairs(categories) do
        local btn = CreateFrame("Button", nil, f, "BackdropTemplate")
        btn:SetSize(iconSize + 6, iconSize + 6)
        btn:SetPoint("TOP", startX + (i-1) * (iconSize + iconSpacing) + iconSize/2, -52)
        
        btn:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 8,
            insets = {left = 2, right = 2, top = 2, bottom = 2}
        })
        btn:SetBackdropColor(0.05, 0.05, 0.05, 0.9)
        btn:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
        
        local icon = btn:CreateTexture(nil, "ARTWORK")
        icon:SetSize(iconSize - 4, iconSize - 4)
        icon:SetPoint("CENTER", 0, 0)
        icon:SetTexture(cat.icon)
        btn.icon = icon
        btn.color = cat.color
        
        btn.categoryId = cat.id
        btn:SetScript("OnClick", function()
            f.selectedCategory = cat.id
            self:UpdateMilestones()
        end)
        
        btn:SetScript("OnEnter", function(self)
            self:SetBackdropBorderColor(unpack(cat.color))
            self.icon:SetVertexColor(1, 1, 1, 1)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:SetText(catTooltips[cat.id], 1, 1, 1)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function(self)
            if f.selectedCategory == self.categoryId then
                self:SetBackdropBorderColor(unpack(cat.color))
            else
                self:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
                self.icon:SetVertexColor(0.7, 0.7, 0.7, 1)
            end
            GameTooltip:Hide()
        end)
        
        -- Initial: Nicht ausgewählt = leicht ausgegraut
        if cat.id ~= "level" then
            icon:SetVertexColor(0.7, 0.7, 0.7, 1)
        else
            btn:SetBackdropBorderColor(unpack(cat.color))
        end
        
        f.categoryButtons[cat.id] = btn
    end
    
    local scroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 20, -95)
    scroll:SetPoint("BOTTOMRIGHT", -35, 20)
    
    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(scroll:GetWidth(), 1200)
    child:SetHeight(1200)  -- Wird durch content:SetHeight() dynamisch angepasst
    scroll:SetScrollChild(child)
    f.contentFrame = child
    
    self.achieveFrame = f
end

function UI:UpdateMilestones()
    local Milestones = GDL:GetModule("Milestones")
    if not Milestones or not self.achieveFrame then return end
    
    local f = self.achieveFrame
    local content = f.contentFrame
    local category = f.selectedCategory or "level"
    
    -- Charakter-Info aktualisieren
    local charKey = Milestones:GetCharacterKey()
    local charName = UnitName("player")
    local unlocked, total = Milestones:GetMilestoneProgress(charKey)
    local infoText = GetLocale() == "deDE" and "Meilensteine" or "Milestones"
    f.charInfo:SetText("|cffFFD100" .. charName .. "|r |cff3a2a10- " .. unlocked .. "/" .. total .. " " .. infoText .. "|r")

    if f.supInfo then
        if GDL and GDL.IsSupporterActive and GDL:IsSupporterActive() then
            local untilStr = GDL:GetSupporterExpiry() or ""
                    local untilTS = 0
                    if untilStr ~= "" then
                        local y,m,d = untilStr:match("(%d+)-(%d+)-(%d+)")
                        if y then untilTS = time({year=tonumber(y),month=tonumber(m),day=tonumber(d)}) end
                    end
            f.supInfo:SetText("|cff44CC44[OK] Supporter+|r " .. (GetLocale()=="deDE" and "aktiv bis " or "active until ") .. (GDL:GetSupporterExpiry() or "?"))
        else
            f.supInfo:SetText("|cffFF8800Code eingeben >> |cffFFD100<3 Support-Tab|r")
        end
    end

    
    -- Button-Farben aktualisieren (Icon-Only Design)
    for catId, btn in pairs(f.categoryButtons) do
        if catId == category then
            btn:SetBackdropBorderColor(unpack(btn.color))
            btn.icon:SetVertexColor(1, 1, 1, 1)
        else
            btn:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
            btn.icon:SetVertexColor(0.7, 0.7, 0.7, 1)
        end
    end
    
    -- Content leeren
    for _, c in ipairs({content:GetChildren()}) do c:Hide() end
    for _, r in ipairs({content:GetRegions()}) do r:Hide() end

    -- â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    -- GATE: Alle Tabs außer "support" sind ohne Code gesperrt.
    -- Support-Tab ist immer zugänglich (Code-Eingabe).
    -- â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    if category ~= "support" and not GDL:IsSupporterActive() then
        -- Lock-Screen anzeigen statt Content
        if not f.lockScreen then
            local ls = content:CreateFontString(nil, "OVERLAY")
            ls:SetFont("Fonts\\MORPHEUS.TTF", 16, "")
            ls:SetPoint("TOP", 0, -60)
            ls:SetJustifyH("CENTER")
            f.lockScreen = ls
            local ls2 = content:CreateFontString(nil, "OVERLAY")
            ls2:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
            ls2:SetPoint("TOP", ls, "BOTTOM", 0, -20)
            ls2:SetJustifyH("CENTER")
            f.lockScreen2 = ls2
            local lsBtn = CreateFrame("Button", nil, content, "BackdropTemplate")
            lsBtn:SetSize(200, 30)
            lsBtn:SetPoint("TOP", ls2, "BOTTOM", 0, -20)
            lsBtn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", edgeSize=12, insets={left=3,right=3,top=3,bottom=3}})
            lsBtn:SetBackdropColor(0.6, 0.3, 0.1, 0.8)
            lsBtn:SetBackdropBorderColor(1, 0.7, 0.2, 1)
            local lsBtnTxt = lsBtn:CreateFontString(nil, "OVERLAY")
            lsBtnTxt:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
            lsBtnTxt:SetPoint("CENTER")
            lsBtnTxt:SetText(GetLocale()=="deDE" and "Code eingeben >>" or "Enter Code >>")
            lsBtn:SetScript("OnClick", function()
                f.selectedCategory = "support"
                self:UpdateMilestones()
            end)
            lsBtn:SetScript("OnEnter", function(s) s:SetBackdropColor(0.8, 0.4, 0.1, 0.9) end)
            lsBtn:SetScript("OnLeave", function(s) s:SetBackdropColor(0.6, 0.3, 0.1, 0.8) end)
            f.lockScreenBtn = lsBtn
        end
        local isDE = GetLocale() == "deDE"
        f.lockScreen:SetText("|cffFF8C00ðŸ”’ " .. (isDE and "Supporter-Feature" or "Supporter Feature") .. "|r")
        f.lockScreen2:SetText("|cffBBAA88" .. (isDE
            and "Gib deinen Patreon-Code ein um\nMeilensteine & Titel freizuschalten."
            or  "Enter your Patreon code to unlock\nMilestones & Titles.") .. "|r")
        f.lockScreen:Show()
        f.lockScreen2:Show()
        f.lockScreenBtn:Show()
        content:SetHeight(200)
        return
    end
    -- Lock-Screen verstecken wenn Supporter aktiv
    if f.lockScreen then f.lockScreen:Hide() end
    if f.lockScreen2 then f.lockScreen2:Hide() end
    if f.lockScreenBtn then f.lockScreenBtn:Hide() end

-- ==============================================================
-- SUPPORT-SEITE - gleicher Stil wie andere Meilenstein-Tabs
-- ==============================================================
if category == "support" then
    local isDE = GetLocale() == "deDE"

    if not f.supportPage then
        local p = CreateFrame("Frame", nil, content)
        p:SetPoint("TOPLEFT", 0, 0)
        p:SetPoint("BOTTOMRIGHT", 0, 0)
        f.supportPage = p

        -- â”€â”€ Abschnitts-Trennlinie (wie im Meilenstein-Tab-Header) â”€â”€
        local function MakeDivider(parent, yOff)
            local d = parent:CreateTexture(nil, "ARTWORK")
            d:SetTexture("Interface\\Common\\UI-TooltipDivider-Transparent")
            d:SetSize(360, 8)
            d:SetPoint("TOPLEFT", 10, yOff)
            d:SetAlpha(0.6)
            return d
        end

        -- â”€â”€ Abschnitts-Header (wie Kategorie-Header im Meilenstein-Tab) â”€â”€
        local function MakeSectionHeader(parent, text, yOff)
            local lbl = parent:CreateFontString(nil, "OVERLAY")
            lbl:SetFont("Fonts\\FRIZQT__.TTF", 13, "")
            lbl:SetPoint("TOPLEFT", 10, yOff)
            lbl:SetText("|cff1a0a00" .. text .. "|r")
            return lbl
        end

        -- â”€â”€ Fließtext (wie Meilenstein-Beschreibungen) â”€â”€
        local function MakeBodyText(parent, text, yOff, rightPad)
            rightPad = rightPad or 18
            local t = parent:CreateFontString(nil, "OVERLAY")
            t:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
            t:SetPoint("TOPLEFT", 14, yOff)
            t:SetPoint("TOPRIGHT", -rightPad, yOff)
            t:SetJustifyH("LEFT")
            t:SetJustifyV("TOP")
            t:SetTextColor(0.25, 0.18, 0.12)
            t:SetText(text)
            return t
        end

        -- â”€â”€ Status-Box (wie ein freigeschalteter Meilenstein-Row) â”€â”€
        local statusBox = CreateFrame("Frame", nil, p, "BackdropTemplate")
        statusBox:SetSize(362, 44)
        statusBox:SetPoint("TOPLEFT", 8, -8)
        statusBox:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 10,
            insets   = {left=3, right=3, top=3, bottom=3},
        })
        statusBox:SetBackdropColor(0.12, 0.09, 0.06, 0.85)
        statusBox:SetBackdropBorderColor(0.55, 0.40, 0.18, 0.9)

        local statusIcon = statusBox:CreateTexture(nil, "ARTWORK")
        statusIcon:SetSize(30, 30)
        statusIcon:SetPoint("LEFT", 8, 0)
        statusIcon:SetTexture("Interface\\Icons\\INV_ValentinesCard01")
        p.statusIcon = statusIcon

        local statusText = statusBox:CreateFontString(nil, "OVERLAY")
        statusText:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        statusText:SetPoint("LEFT", statusIcon, "RIGHT", 8, 2)
        statusText:SetPoint("RIGHT", statusBox, "RIGHT", -8, 0)
        statusText:SetJustifyH("LEFT")
        statusText:SetTextColor(0.25, 0.18, 0.12)
        p.status = statusText

        -- â”€â”€ Was ist Supporter+? â”€â”€
        MakeDivider(p, -58)
        MakeSectionHeader(p, isDE and "Was ist Supporter+?" or "What is Supporter+?", -70)
        MakeBodyText(p, isDE
            and "Dieses Addon ist und bleibt komplett kostenlos.\n\nAls freiwillige Unterstützung kannst du über Patreon einen Monatsbeitrag leisten. Als Dankeschön schaltest du rein kosmetische Extras frei: eigene Titel auf Nameplates & Tooltips sowie das vollständige Meilenstein-System.\n\nKeine Gameplay-Vorteile. Kein Pay-to-Win."
            or  "This addon is and stays completely free.\n\nAs voluntary support you can contribute monthly via Patreon. As a thank-you, you unlock purely cosmetic extras: custom titles on nameplates & tooltips and the full milestone system.\n\nNo gameplay advantages. No pay-to-win.",
            -84)
        p.body = MakeBodyText(p, "", -84)  -- reference kept for compat

        -- â”€â”€ Code einlösen â”€â”€
        MakeDivider(p, -220)
        MakeSectionHeader(p, isDE and "Supporter-Code einlösen" or "Redeem Supporter Code", -232)

        -- Discord-ID Label + EditBox (ersetzt Patreon-Name)
        local discordLbl = p:CreateFontString(nil, "OVERLAY")
        discordLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        discordLbl:SetPoint("TOPLEFT", 14, -252)
        discordLbl:SetTextColor(0.30, 0.22, 0.14)
        discordLbl:SetText(isDE and "Discord-ID  |cff888888(aus der Bot-DM)|r" or "Discord ID  |cff888888(from Bot DM)|r")

        local discordEB = CreateFrame("EditBox", nil, p, "InputBoxTemplate")
        discordEB:SetAutoFocus(false)
        discordEB:SetSize(340, 22)
        discordEB:SetPoint("TOPLEFT", 14, -268)
        discordEB:SetMaxLetters(20)
        discordEB:SetNumeric(true)
        discordEB:SetScript("OnEscapePressed", function(s) s:ClearFocus() end)
        discordEB:SetScript("OnEnterPressed", function(s)
            local id = s:GetText()
            if id and id ~= "" then
                GuildDeathLogDB.discordId = id
                GDL:Print("|cff00FF00Discord-ID gespeichert:|r " .. id)
            end
            s:ClearFocus()
        end)
        discordEB:SetScript("OnTextChanged", function(s)
            local id = s:GetText()
            if id and id ~= "" then
                GuildDeathLogDB.discordId = id
            end
        end)
        -- Gespeicherte ID laden
        local savedId = GuildDeathLogDB and GuildDeathLogDB.discordId or ""
        if savedId ~= "" then
            discordEB:SetText(savedId)
        end
        p.discordEdit = discordEB

        -- Status-Indikator Discord-ID (klein, diskret)
        local discordStatus = p:CreateFontString(nil, "OVERLAY")
        discordStatus:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        discordStatus:SetPoint("TOPLEFT", 14, -293)
        if savedId ~= "" then
            discordStatus:SetText("|cff00CC44[OK] ID gespeichert|r")
        else
            discordStatus:SetText("|cffFF8800Bitte Discord-ID eingeben|r")
        end
        p.discordStatus = discordStatus

        -- Code Label + EditBox (größer, prominent)
        local codeLbl = p:CreateFontString(nil, "OVERLAY")
        codeLbl:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        codeLbl:SetPoint("TOPLEFT", 14, -300)
        codeLbl:SetTextColor(0.55, 0.38, 0.16)
        codeLbl:SetText(isDE and "Supporter-Code  |cff888888(GDL-YYYYMM-XXXXXXXX-CC)|r" or "Supporter Code  |cff888888(GDL-YYYYMM-XXXXXXXX-CC)|r")

        local codeEB = CreateFrame("EditBox", nil, p, "InputBoxTemplate")
        codeEB:SetAutoFocus(false)
        codeEB:SetSize(340, 22)
        codeEB:SetPoint("TOPLEFT", 14, -318)
        codeEB:SetMaxLetters(64)
        codeEB:SetScript("OnEscapePressed", function(s) s:ClearFocus() end)
        codeEB:SetScript("OnEnterPressed", function(s) s:ClearFocus() end)
        p.codeEdit = codeEB

        -- Einlösen-Button (wie Milestone-Row, goldener Rahmen)
        local redeemBtn = CreateFrame("Button", nil, p, "BackdropTemplate")
        redeemBtn:SetSize(150, 28)
        redeemBtn:SetPoint("TOPLEFT", 14, -350)
        redeemBtn:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12,
            insets   = {left=3, right=3, top=3, bottom=3},
        })
        redeemBtn:SetBackdropColor(0.18, 0.14, 0.08, 0.85)
        redeemBtn:SetBackdropBorderColor(0.75, 0.55, 0.18, 1.0)
        local rtxt = redeemBtn:CreateFontString(nil, "OVERLAY")
        rtxt:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        rtxt:SetPoint("CENTER", 0, 0)
        rtxt:SetText(isDE and "|cffFFD100Einlösen|r" or "|cffFFD100Redeem|r")
        redeemBtn:SetScript("OnEnter", function(self)
            self:SetBackdropColor(0.28, 0.22, 0.12, 0.95)
            self:SetBackdropBorderColor(1.0, 0.75, 0.25, 1.0)
        end)
        redeemBtn:SetScript("OnLeave", function(self)
            self:SetBackdropColor(0.18, 0.14, 0.08, 0.85)
            self:SetBackdropBorderColor(0.75, 0.55, 0.18, 1.0)
        end)
        redeemBtn:SetScript("OnClick", function()
            local code = p.codeEdit and p.codeEdit:GetText() or ""
            code = strtrim(code)
            if code == "" then
                GDL:Print(isDE and "Bitte einen Code eingeben." or "Please enter a code.")
                p.codeEdit:SetFocus()
                return
            end
            if GDL and GDL.RedeemSupporterCode then
                local success = GDL:RedeemSupporterCode(code)
                if success then
                    local untilStr = GDL:GetSupporterExpiry() or ""
                    local untilTS = 0
                    if untilStr ~= "" then
                        local y,m,d = untilStr:match("(%d+)-(%d+)-(%d+)")
                        if y then untilTS = time({year=tonumber(y),month=tonumber(m),day=tonumber(d)}) end
                    end
                    if p.status then
                        p.status:SetTextColor(0.2, 0.65, 0.2)
                        p.status:SetText(
                            "|cff44CC44" .. (isDE and "[OK] Supporter+ aktiv bis " or "[OK] Supporter+ active until ")
                            .. "|r" .. (untilTS > 0 and date("%d.%m.%Y", untilTS) or (untilStr ~= "" and untilStr or "?"))
                            .. "  |cffFFD100" .. (isDE and "Titel & Meilensteine freigeschaltet!" or "Titles & Milestones unlocked!") .. "|r")
                    end
                    if statusBox then
                        statusBox:SetBackdropBorderColor(0.2, 0.7, 0.2, 1.0)
                        statusBox:SetBackdropColor(0.05, 0.18, 0.05, 0.85)
                    end
                    GDL:Print(isDE
                        and "|cff44FF44Danke! Supporter+ aktiviert.|r Meilensteine & Titel freigeschaltet!"
                        or  "|cff44FF44Thank you! Supporter+ activated.|r Milestones & Titles unlocked!")
                    -- Level-Tab öffnen nach Erfolg
                    f.selectedCategory = "level"
                    for catId, btn in pairs(f.categoryButtons) do
                        if catId == "level" then
                            btn:SetBackdropBorderColor(unpack(btn.color))
                            btn.icon:SetVertexColor(1, 1, 1, 1)
                        else
                            btn:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
                            btn.icon:SetVertexColor(0.7, 0.7, 0.7, 1)
                        end
                    end
                else
                    GDL:Print(isDE
                        and "|cffFF4444Ungültiger Code.|r Format: GDL-202602-XXXXXXXX-CC"
                        or  "|cffFF4444Invalid code.|r Format: GDL-202602-XXXXXXXX-CC")
                end
            end
            UI:UpdateMilestones()
        end)
        p.redeemBtn = redeemBtn

        -- Patreon-Link Button (dezenter, lila Akzent wie auf den anderen Tabs)
        local patBtn = CreateFrame("Button", nil, p, "BackdropTemplate")
        patBtn:SetSize(150, 28)
        patBtn:SetPoint("LEFT", redeemBtn, "RIGHT", 10, 0)
        patBtn:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12,
            insets   = {left=3, right=3, top=3, bottom=3},
        })
        patBtn:SetBackdropColor(0.14, 0.10, 0.18, 0.7)
        patBtn:SetBackdropBorderColor(0.50, 0.35, 0.70, 0.9)
        local ptxt = patBtn:CreateFontString(nil, "OVERLAY")
        ptxt:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        ptxt:SetPoint("CENTER", 0, 0)
        ptxt:SetText("|cffCC99FF<3 Patreon|r")
        patBtn:SetScript("OnEnter", function(self)
            self:SetBackdropColor(0.22, 0.16, 0.28, 0.9)
            self:SetBackdropBorderColor(0.70, 0.50, 0.90, 1.0)
        end)
        patBtn:SetScript("OnLeave", function(self)
            self:SetBackdropColor(0.14, 0.10, 0.18, 0.7)
            self:SetBackdropBorderColor(0.50, 0.35, 0.70, 0.9)
        end)
        patBtn:SetScript("OnClick", function()
            GDL:Print((isDE and "Patreon: " or "Patreon: ") .. "patreon.com/PsYke86")
        end)
        p.patBtn = patBtn

        -- Hinweis-Text (klein, wie Meilenstein-Unlock-Datum)
        local note = p:CreateFontString(nil, "OVERLAY")
        note:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        note:SetPoint("TOPLEFT", 14, -388)
        note:SetPoint("TOPRIGHT", -14, -388)
        note:SetJustifyH("LEFT")
        note:SetTextColor(0.40, 0.32, 0.22)
        note:SetText(isDE
            and "|cff806040Hinweis:|r Der Code wird nach Patreon-Support per Discord zugesendet."
            or  "|cff806040Note:|r The code is sent via Discord after Patreon support.")
        p.note = note
    end -- end if not f.supportPage

    -- â”€â”€ Dynamische Updates bei jedem Öffnen â”€â”€
    local isDE = GetLocale() == "deDE"

    -- Patreon-Name laden/speichern
    if GuildDeathLogDB and GuildDeathLogDB.settings and f.supportPage.patreonEdit then
        if not f.supportPage.patreonEdit._loaded then
            f.supportPage.patreonEdit:SetText(GuildDeathLogDB.settings.patreonName or "")
            f.supportPage.patreonEdit._loaded = true
        end
        f.supportPage.patreonEdit:SetScript("OnTextChanged", function(self)
            GuildDeathLogDB.settings.patreonName = self:GetText() or ""
        end)
    end

    -- Status-Feld aktualisieren
    if GDL and GDL.IsSupporterActive and GDL:IsSupporterActive() then
        local untilStr = GDL:GetSupporterExpiry() or ""
        local untilTS = 0
        if untilStr ~= "" then
            local y,m,d = untilStr:match("(%d+)-(%d+)-(%d+)")
            if y then untilTS = time({year=tonumber(y),month=tonumber(m),day=tonumber(d)}) end
        end
        f.supportPage.status:SetTextColor(0.2, 0.65, 0.2)
        f.supportPage.status:SetText(
            "|cff44CC44" .. (isDE and "[OK] Supporter+ aktiv bis " or "[OK] Supporter+ active until ")
            .. "|r" .. (untilTS > 0 and date("%d.%m.%Y", untilTS) or (untilStr ~= "" and untilStr or "?"))
            .. "  |cffFFD100" .. (isDE and "Titel & Meilensteine freigeschaltet!" or "Titles & Milestones unlocked!") .. "|r")
    else
        f.supportPage.status:SetTextColor(0.50, 0.38, 0.20)
        f.supportPage.status:SetText(
            isDE and "|cff806040Code eingeben um Titel & Meilensteine freizuschalten.|r"
                  or "|cff806040Enter code to unlock Titles & Milestones.|r")
        -- Sofort Code-Feld fokussieren
        C_Timer.After(0.05, function()
            if f.supportPage.codeEdit then f.supportPage.codeEdit:SetFocus() end
        end)
    end

    f.supportPage:Show()
    content:SetHeight(420)
    return
end

    local milestones = Milestones:GetMilestonesByCategory(category)
    local charMilestones = Milestones:GetCharacterMilestones(charKey)
    local y = 0
    
    -- Kategorie-Header
    local catUnlocked, catTotal = Milestones:GetCategoryProgress(category, charKey)
    local header = content:CreateFontString(nil, "OVERLAY")
    header:SetFont("Fonts\\FRIZQT__.TTF", 13, "")
    header:SetPoint("TOPLEFT", 5, y)
    
    local categoryNames = {
        level = GetLocale() == "deDE" and "Level-Meilensteine" or "Level Milestones",
        survival = GetLocale() == "deDE" and "Spielzeit-Meilensteine" or "Playtime Milestones",
        dungeon = GetLocale() == "deDE" and "Dungeon-Bosse" or "Dungeon Bosses",
        raid = GetLocale() == "deDE" and "Raid-Bosse" or "Raid Bosses",
        profession = GetLocale() == "deDE" and "Berufe-Meilensteine" or "Profession Milestones",
        kills = GetLocale() == "deDE" and "Kill-Meilensteine" or "Kill Milestones",
        wealth = GetLocale() == "deDE" and "Gold-Meilensteine" or "Gold Milestones",
        meta = GetLocale() == "deDE" and "Meta-Erfolge" or "Meta Achievements",
        secret = GetLocale() == "deDE" and "Geheime Meilensteine" or "Secret Milestones",
        support = GetLocale() == "deDE" and "Unterstützen" or "Support",
    }
    header:SetText("|cff1a0a00" .. (categoryNames[category] or category) .. "|r |cff806040- " .. catUnlocked .. "/" .. catTotal .. "|r")
    y = y - 25
    
    -- Sortiere nach Threshold
    table.sort(milestones, function(a, b)
        return (a.threshold or 0) < (b.threshold or 0)
    end)
    
    for _, m in ipairs(milestones) do
        local isUnlocked = charMilestones[m.id] ~= nil
        local unlockData = charMilestones[m.id]
        
        -- Kill-Meilensteine brauchen mehr Platz für Progress-Leiste
        -- Konstante Zeilenhöhe für alle
        local rowHeight = 55
        
        local row = CreateFrame("Frame", nil, content, "BackdropTemplate")
        row:SetSize(380, rowHeight)
        row:SetPoint("TOPLEFT", 0, y)
        
        row:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 10,
            insets = {left = 2, right = 2, top = 2, bottom = 2}
        })
        
        -- Farbe basierend auf Kategorie und Status
        local catColors = {
            level = {0.2, 0.6, 0.2},
            survival = {0.3, 0.5, 0.7},
            dungeon = {0.4, 0.4, 0.8},
            raid = {0.8, 0.4, 0.2},
            profession = {0.6, 0.5, 0.2},
            kills = {0.7, 0.2, 0.2},
            wealth = {0.8, 0.7, 0.2},
            meta = {0.5, 0.3, 0.7},
            secret = {0.6, 0.1, 0.6},
        }
        local color = catColors[category] or {0.5, 0.5, 0.5}
        
        if isUnlocked then
            row:SetBackdropColor(color[1] * 0.3, color[2] * 0.3, color[3] * 0.3, 0.8)
            row:SetBackdropBorderColor(color[1], color[2], color[3], 0.9)
        else
            row:SetBackdropColor(0.08, 0.08, 0.08, 0.5)
            row:SetBackdropBorderColor(0.2, 0.2, 0.2, 0.5)
        end
        
        -- Icon
        local icon = row:CreateTexture(nil, "ARTWORK")
        icon:SetSize(42, 42)
        icon:SetPoint("LEFT", 6, 0)
        
        -- Geheime Meilensteine: Fragezeichen-Icon wenn nicht freigeschaltet
        if m.secret and not isUnlocked then
            icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
        else
            icon:SetTexture(m.icon)
        end
        
        if not isUnlocked then 
            icon:SetDesaturated(true) 
            icon:SetAlpha(0.65) 
        end
        
        -- Name
        local name = row:CreateFontString(nil, "OVERLAY")
        name:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
        name:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -5)
        name:SetWidth(280)
        if isUnlocked then
            name:SetText("|cff00FF00[+]|r " .. m.name)
        elseif m.secret then
            name:SetText("|cffAA00AA" .. m.name .. "|r")
        else
            name:SetText("|cff666666" .. m.name .. "|r")
        end
        
        -- Beschreibung
        local desc = row:CreateFontString(nil, "OVERLAY")
        desc:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        desc:SetPoint("TOPLEFT", name, "BOTTOMLEFT", 0, -3)
        desc:SetWidth(220)
        
        local descText = m.desc
        if m.secret and not isUnlocked then
            descText = "???"
        end
        desc:SetText("|cff888888" .. descText .. "|r")
        
        -- Fortschritts-Anzeige (Zahlen, keine Balken!) für alle nicht-freigeschalteten mit Threshold
        if not isUnlocked and m.threshold then
            local current = 0
            local threshold = m.threshold
            
            if m.type == "kills" and m.creatureType then
                -- Kreaturtyp-Kills
                local KillStats = GDL:GetModule("KillStats")
                if KillStats then
                    current = KillStats:GetKillCount(m.creatureType)
                end
            elseif m.type == "total_kills" then
                -- Total Kills
                local KillStats = GDL:GetModule("KillStats")
                if KillStats then
                    current = KillStats:GetTotalKills()
                end
            elseif m.type == "gold" then
                -- Gold (in Kupfer)
                current = GetMoney()
            elseif m.type == "playtime" then
                -- Spielzeit (wird separat getrackt)
                local charData = Milestones:GetCharacterData(charKey)
                current = charData.stats and charData.stats.totalPlaytime or 0
            elseif m.type == "level" then
                -- Level
                current = UnitLevel("player")
            elseif m.type == "profession" or m.type == "fishing" or m.type == "cooking" or m.type == "firstaid" then
                -- Berufe über Classic-kompatible API
                local profSkills = Milestones:GetProfessionSkills()
                if m.type == "profession" then
                    current = profSkills.maxMain or 0
                elseif m.type == "fishing" then
                    current = profSkills.fishing or 0
                elseif m.type == "cooking" then
                    current = profSkills.cooking or 0
                elseif m.type == "firstaid" then
                    current = profSkills.firstaid or 0
                end
            end
            
            -- Prüfe ob Meilenstein erreicht aber noch nicht freigeschaltet
            if current >= threshold then
                local charName = UnitName("player")
                local level = UnitLevel("player")
                Milestones:UnlockMilestone(m.id, charKey, charName, level, true)
                
                -- Aktualisiere Anzeige
                row:SetBackdropColor(color[1] * 0.3, color[2] * 0.3, color[3] * 0.3, 0.8)
                row:SetBackdropBorderColor(color[1], color[2], color[3], 0.9)
                icon:SetDesaturated(false)
                icon:SetAlpha(1)
                name:SetText("|cff00FF00[+]|r " .. m.name)
            else
                -- Zeige Fortschritt als Zahlen
                local progressText = row:CreateFontString(nil, "OVERLAY")
                progressText:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
                progressText:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 10, 3)
                
                -- Formatiere die Zahlen je nach Typ
                local currentStr, thresholdStr
                if m.type == "gold" then
                    -- Gold: Kupfer zu Gold konvertieren
                    currentStr = math.floor(current / 10000)
                    thresholdStr = math.floor(threshold / 10000) .. "g"
                    currentStr = currentStr .. "g"
                elseif m.type == "playtime" then
                    -- Spielzeit: Sekunden zu Stunden
                    currentStr = math.floor(current / 3600) .. "h"
                    thresholdStr = math.floor(threshold / 3600) .. "h"
                else
                    currentStr = current
                    thresholdStr = threshold
                end
                
                progressText:SetText("|cffFFFFFF" .. currentStr .. "/" .. thresholdStr .. "|r")
            end
        end
        
        -- Datum wenn freigeschaltet
        if isUnlocked and unlockData then
            local dateText = row:CreateFontString(nil, "OVERLAY")
            dateText:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            dateText:SetPoint("BOTTOMRIGHT", -10, 8)
            local dateStr = unlockData.timestamp and date("%d.%m.%Y", unlockData.timestamp) or "?"
            dateText:SetText("|cff00AA00" .. dateStr .. "|r")
            
            if unlockData.level then
                local lvlText = row:CreateFontString(nil, "OVERLAY")
                lvlText:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
                lvlText:SetPoint("TOPRIGHT", -10, -8)
                lvlText:SetText("|cffAAAA00Lvl " .. unlockData.level .. "|r")
            end
        end
        
        y = y - (rowHeight + 5)
    end
    
    content:SetHeight(math.abs(y) + 30)
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- TITLES WINDOW - Titel-Auswahl Fenster
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

function UI:ToggleTitles()
    if not GDL:RequireSupporter() then return end
    if not self.titlesFrame then
        self:CreateTitlesWindow()
    end
    
    if self.titlesFrame:IsShown() then
        self.titlesFrame:Hide()
    else
        self.titlesFrame:Show()
        self:UpdateTitles()
    end
end

function UI:CreateTitlesWindow()
    local f = CreateFrame("Frame", "GDLTitles", UIParent, "BackdropTemplate")
    f:SetSize(420, 500)
    f:SetPoint("CENTER", UIParent, "CENTER", 550, 150)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(110)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetScript("OnMouseDown", function(self) self:Raise() end)
    
    f:SetBackdrop({
        bgFile = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 26,
        insets = {left = 5, right = 5, top = 5, bottom = 5}
    })
    
    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -5, -5)
    
    -- Titel-Icon oben
    local headerIcon = f:CreateTexture(nil, "ARTWORK")
    headerIcon:SetSize(40, 40)
    headerIcon:SetPoint("TOP", 0, -15)
    headerIcon:SetTexture("Interface\\Icons\\INV_Crown_01")
    
    local title = f:CreateFontString(nil, "OVERLAY")
    title:SetFont("Fonts\\MORPHEUS.TTF", 18, "")
    title:SetPoint("TOP", headerIcon, "BOTTOM", 0, -5)
    title:SetText("|cff1a0a00Titel / Titles|r")
    
    -- Aktueller Titel Anzeige
    local currentFrame = CreateFrame("Frame", nil, f, "BackdropTemplate")
    currentFrame:SetSize(380, 60)
    currentFrame:SetPoint("TOP", title, "BOTTOM", 0, -15)
    currentFrame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 12,
        insets = {left = 3, right = 3, top = 3, bottom = 3}
    })
    currentFrame:SetBackdropColor(0.1, 0.08, 0.05, 0.9)
    currentFrame:SetBackdropBorderColor(0.8, 0.6, 0.2, 1)
    
    local currentLabel = currentFrame:CreateFontString(nil, "OVERLAY")
    currentLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
    currentLabel:SetPoint("TOPLEFT", 10, -8)
    currentLabel:SetText("|cffAAAA00Aktueller Titel:|r")
    
    local currentTitle = currentFrame:CreateFontString(nil, "OVERLAY")
    currentTitle:SetFont("Fonts\\MORPHEUS.TTF", 16, "")
    currentTitle:SetPoint("LEFT", 50, -5)
    -- Initialwert: leer bis UpdateTitles() aufgerufen wird - kein weißes Blank
    currentTitle:SetText("|cff888888-|r")
    f.currentTitleText = currentTitle
    
    local currentIcon = currentFrame:CreateTexture(nil, "ARTWORK")
    currentIcon:SetSize(32, 32)
    currentIcon:SetPoint("LEFT", 10, -5)
    f.currentTitleIcon = currentIcon
    
    -- Progress-Anzeige
    local progressText = f:CreateFontString(nil, "OVERLAY")
    progressText:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    progressText:SetPoint("TOP", currentFrame, "BOTTOM", 0, -10)
    f.progressText = progressText
    
    -- Info-Text
    local infoText = f:CreateFontString(nil, "OVERLAY")
    infoText:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    infoText:SetPoint("TOP", progressText, "BOTTOM", 0, -5)
    local titlesInfo = GetLocale() == "deDE" and "Titel sind für alle Gildenmitglieder mit dem Addon sichtbar!" or "Titles are visible to all guild members with the addon!"
    infoText:SetText("|cff888888" .. titlesInfo .. "|r")
    
    -- Scroll-Bereich für Titel-Liste
    local scroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 15, -175)
    scroll:SetPoint("BOTTOMRIGHT", -35, 15)
    
    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(scroll:GetWidth(), 600)
    scroll:SetScrollChild(child)
    f.contentFrame = child
    
    f:Hide()
    self.titlesFrame = f
end

function UI:UpdateTitles()
    local Titles = GDL:GetModule("Titles")
    if not Titles or not self.titlesFrame then return end
    
    local f = self.titlesFrame
    local content = f.contentFrame
    local isDE = GetLocale() == "deDE"
    
    -- Content leeren
    for _, child in ipairs({content:GetChildren()}) do
        child:Hide()
    end
    
    -- Aktueller Titel - sauberer Fallback, kein leerer String
    local selected = Titles:GetSelectedTitle()
    if selected then
        local color = selected.color or {1, 1, 1}
        local isFemale = (Titles.IsPlayerFemale ~= nil) and Titles:IsPlayerFemale() or false
        local titleName = (isFemale and selected.nameFemale) or selected.name
        f.currentTitleText:SetText(string.format("|cff%02x%02x%02x<%s>|r",
            color[1]*255, color[2]*255, color[3]*255, titleName))
        f.currentTitleIcon:SetTexture(selected.icon)
        f.currentTitleIcon:Show()
    else
        -- Kein Titel vorhanden - neutraler Hinweis, kein leerer String
        local noTitle = isDE and "|cff888888- kein Titel ausgewählt -|r" or "|cff888888- no title selected -|r"
        f.currentTitleText:SetText(noTitle)
        f.currentTitleIcon:SetTexture("")
    end
    
    -- Progress
    local unlocked, total = Titles:GetStats()
    f.progressText:SetText("|cffFFD100" .. unlocked .. "|r / |cff888888" .. total .. " Titel freigeschaltet|r")
    
    -- Titel-Liste
    local allTitles = Titles:GetUnlockedTitles()
    local y = 0
    
    for _, t in ipairs(allTitles) do
        local isSelected = selected and selected.id == t.id
        
        local row = CreateFrame("Button", nil, content, "BackdropTemplate")
        row:SetSize(350, 55)
        row:SetPoint("TOPLEFT", 0, y)
        
        row:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 10,
            insets = {left = 2, right = 2, top = 2, bottom = 2}
        })
        
        local color = t.color or {0.5, 0.5, 0.5}
        
        if isSelected then
            row:SetBackdropColor(color[1] * 0.4, color[2] * 0.4, color[3] * 0.4, 0.9)
            row:SetBackdropBorderColor(0, 1, 0, 1)  -- Grüner Rahmen
        else
            row:SetBackdropColor(0.1, 0.08, 0.05, 0.7)
            row:SetBackdropBorderColor(color[1] * 0.6, color[2] * 0.6, color[3] * 0.6, 0.8)
        end
        
        -- Icon
        local icon = row:CreateTexture(nil, "ARTWORK")
        icon:SetSize(40, 40)
        icon:SetPoint("LEFT", 8, 0)
        icon:SetTexture(t.icon)
        
        -- Titel-Name
        local nameText = row:CreateFontString(nil, "OVERLAY")
        nameText:SetFont("Fonts\\MORPHEUS.TTF", 14, "")
        nameText:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -5)
        nameText:SetText(string.format("|cff%02x%02x%02x<%s>|r", 
            color[1]*255, color[2]*255, color[3]*255, t.name))
        
        -- Beschreibung
        local descText = row:CreateFontString(nil, "OVERLAY")
        descText:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        descText:SetPoint("TOPLEFT", nameText, "BOTTOMLEFT", 0, -3)
        descText:SetText("|cff888888" .. t.desc .. "|r")
        
        -- Aktiv-Marker
        if isSelected then
            local marker = row:CreateFontString(nil, "OVERLAY")
            marker:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            marker:SetPoint("RIGHT", -10, 0)
            marker:SetText("|cff00FF00< AKTIV|r")
        end
        
        -- Hover-Effekte
        row:SetScript("OnEnter", function(self)
            if not isSelected then
                self:SetBackdropColor(color[1] * 0.3, color[2] * 0.3, color[3] * 0.3, 0.9)
            end
            
            local isDE = GetLocale() == "deDE"
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:ClearLines()
            GameTooltip:AddLine(t.name, color[1], color[2], color[3])
            GameTooltip:AddLine(t.desc, 1, 1, 1, true)
            GameTooltip:AddLine(" ")
            if t.requirement then
                local reqLabel = isDE and "Benoetigt: " or "Requires: "
                GameTooltip:AddLine(reqLabel .. t.requirement, 0.7, 0.7, 0.7)
            end
            local prioLabel = isDE and "Prioritaet: " or "Priority: "
            GameTooltip:AddLine(prioLabel .. (t.priority or 0), 0.5, 0.5, 0.5)
            GameTooltip:AddLine(" ")
            local clickLabel = isDE and "Klicken zum Auswaehlen" or "Click to select"
            GameTooltip:AddLine(clickLabel, 0, 1, 0)
            GameTooltip:Show()
        end)
        
        row:SetScript("OnLeave", function(self)
            if not isSelected then
                self:SetBackdropColor(0.1, 0.08, 0.05, 0.7)
            end
            GameTooltip:Hide()
        end)
        
        -- Klick zum Auswählen
        row:SetScript("OnClick", function()
            Titles:SetSelectedTitle(t.id)
            self:UpdateTitles()
        end)
        
        y = y - 60
    end
    
    content:SetHeight(math.abs(y) + 20)
end


-- ══════════════════════════════════════════════════════════════
-- CALENDAR EVENT TOAST (Guild Push Notification)
-- ══════════════════════════════════════════════════════════════

local calToastQueue = {}
local calToastActive = false

function UI:ShowCalendarToast(event, senderName, isOwn, isDelete)
    table.insert(calToastQueue, { event = event, sender = senderName, isOwn = isOwn, isDelete = isDelete })
    if not calToastActive then self:NextCalToast() end
end

function UI:NextCalToast()
    if #calToastQueue == 0 then calToastActive = false; return end
    calToastActive = true
    self:DisplayCalendarToast(table.remove(calToastQueue, 1))
end

function UI:DisplayCalendarToast(data)
    if not self.calToastFrame then self:BuildCalendarToastFrame() end
    local t = self.calToastFrame
    local event = data.event
    local sender = data.sender or ""
    local isOwn = data.isOwn

    local isDelete = data.isDelete

    -- Icon
    local EVENT_ICONS = {
        raid    = "Interface\Icons\INV_Helmet_06",
        dungeon = "Interface\Icons\INV_Misc_Key_04",
        pvp     = "Interface\Icons\INV_Sword_04",
        social  = "Interface\Icons\INV_Drink_13",
        other   = "Interface\Icons\INV_Misc_Rune_01",
    }
    t.icon:SetTexture(EVENT_ICONS[event.eventType] or EVENT_ICONS.other)

    -- Badge label + styling (DELETE = rot, CREATE = blau/teal)
    if isDelete then
        if isOwn then
            t.badge:SetText("|cffFF6644[Event geloescht]|r")
        else
            t.badge:SetText("|cffFF6644[Event geloescht von " .. sender .. "]|r")
        end
        t.topBar:SetColorTexture(0.55, 0.12, 0.04, 1)
        t.bg:SetColorTexture(0.18, 0.04, 0.02, 0.96)
        t.leftAccent:SetColorTexture(0.85, 0.25, 0.10, 0.90)
        t.icon:SetDesaturated(true)
        t.icon:SetAlpha(0.60)
    elseif isOwn then
        t.badge:SetText("|cff00CCFF[Neues Gilden-Event erstellt]|r")
        t.topBar:SetColorTexture(0.04, 0.40, 0.70, 1)
        t.bg:SetColorTexture(0.02, 0.10, 0.20, 0.96)
        t.leftAccent:SetColorTexture(0.10, 0.60, 0.90, 0.85)
        t.icon:SetDesaturated(false)
        t.icon:SetAlpha(1.0)
    else
        t.badge:SetText("|cff00CCFF[Neues Gilden-Event von " .. sender .. "]|r")
        t.topBar:SetColorTexture(0.04, 0.40, 0.70, 1)
        t.bg:SetColorTexture(0.02, 0.10, 0.20, 0.96)
        t.leftAccent:SetColorTexture(0.10, 0.60, 0.90, 0.85)
        t.icon:SetDesaturated(false)
        t.icon:SetAlpha(1.0)
    end

    -- Event name
    t.titleStr:SetText(event.name or "Unbekanntes Event")
    t.titleStr:SetTextColor(0.9, 0.95, 1.0)

    -- Date/time
    if event.timestamp and event.timestamp > 0 then
        t.descStr:SetText(date("%d.%m.%Y %H:%M", event.timestamp) .. " Uhr")
    else
        t.descStr:SetText("")
    end

    -- Position right-side, below standard toasts
    local screenW = GetScreenWidth()
    local screenH = GetScreenHeight()
    t:ClearAllPoints()
    t:SetPoint("RIGHT", UIParent, "RIGHT", 0, screenH * 0.02)

    if t.hideTimer then t.hideTimer:Cancel() end
    if t.slideAnim then t.slideAnim:Stop() end
    if t.exitAnim then t.exitAnim:Stop() end

    t:SetAlpha(0)
    t:Show()

    if not t.slideAnim then
        t.slideAnim = t:CreateAnimationGroup()
        local fade = t.slideAnim:CreateAnimation("Alpha")
        fade:SetFromAlpha(0); fade:SetToAlpha(1)
        fade:SetDuration(0.25); fade:SetSmoothing("OUT")
        local slide = t.slideAnim:CreateAnimation("Translation")
        slide:SetOffset(80, 0); slide:SetDuration(0.0)
        local slide2 = t.slideAnim:CreateAnimation("Translation")
        slide2:SetOffset(-80, 0); slide2:SetDuration(0.25); slide2:SetSmoothing("OUT")
        t.slideAnim:SetScript("OnFinished", function() t:SetAlpha(1) end)
    end
    t.slideAnim:Play()
    -- ─── CALENDAR BELL FX ────────────────────────────────────────────────
    C_Timer.After(0.28, function()
        if t:IsShown() then
            local AnimFX = GDL:GetModule("AnimFX")
            if AnimFX then AnimFX.CalendarBell(t, data.isDelete) end
        end
    end)

    t.hideTimer = C_Timer.NewTimer(8, function()
        if not t.exitAnim then
            t.exitAnim = t:CreateAnimationGroup()
            local fadeOut = t.exitAnim:CreateAnimation("Alpha")
            fadeOut:SetFromAlpha(1); fadeOut:SetToAlpha(0)
            fadeOut:SetDuration(0.35); fadeOut:SetSmoothing("IN")
            t.exitAnim:SetScript("OnFinished", function()
                t:Hide()
                t:SetAlpha(1)
                calToastActive = false
                C_Timer.After(0.1, function() UI:NextCalToast() end)
            end)
        end
        t.exitAnim:Play()
    end)

    t:SetScript("OnMouseUp", function()
        if t.hideTimer then t.hideTimer:Cancel() end
        t:Hide()
        calToastActive = false
        C_Timer.After(0.1, function() UI:NextCalToast() end)
        local Calendar = GDL:GetModule("Calendar")
        if Calendar then Calendar:Show() end
    end)
end

function UI:BuildCalendarToastFrame()
    local W, H = 340, 72
    local t = CreateFrame("Frame", "GDLCalendarToast", UIParent)
    t:SetSize(W, H)
    t:SetFrameStrata("FULLSCREEN_DIALOG")
    t:SetFrameLevel(448)
    t:SetMovable(true)
    t:EnableMouse(true)
    t:Hide()

    local bg = t:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.02, 0.08, 0.18, 0.96)
    t.bg = bg

    local topBar = t:CreateTexture(nil, "BORDER")
    topBar:SetSize(W, 2); topBar:SetPoint("TOPLEFT")
    topBar:SetColorTexture(0.04, 0.50, 0.85, 1)
    t.topBar = topBar

    local shimmer = t:CreateTexture(nil, "BORDER")
    shimmer:SetPoint("TOPLEFT", 0, -2); shimmer:SetPoint("TOPRIGHT", 0, -2); shimmer:SetHeight(12)
    shimmer:SetColorTexture(0.10, 0.55, 0.90, 0.08); shimmer:SetBlendMode("ADD")

    local botLine = t:CreateTexture(nil, "BORDER")
    botLine:SetSize(W, 1); botLine:SetPoint("BOTTOMLEFT")
    botLine:SetColorTexture(0.04, 0.25, 0.45, 0.55)
    local lLine = t:CreateTexture(nil, "BORDER")
    lLine:SetSize(1, H); lLine:SetPoint("TOPLEFT")
    lLine:SetColorTexture(0.10, 0.55, 0.80, 0.55)
    local rLine = t:CreateTexture(nil, "BORDER")
    rLine:SetSize(1, H); rLine:SetPoint("TOPRIGHT")
    rLine:SetColorTexture(0.04, 0.25, 0.45, 0.40)

    local leftAccent = t:CreateTexture(nil, "ARTWORK")
    leftAccent:SetSize(4, H - 6); leftAccent:SetPoint("TOPLEFT", 3, -3)
    leftAccent:SetColorTexture(0.10, 0.60, 0.90, 0.85)
    t.leftAccent = leftAccent

    local divLine = t:CreateTexture(nil, "BORDER")
    divLine:SetSize(1, H - 14); divLine:SetPoint("TOPLEFT", 62, -7)
    divLine:SetColorTexture(0.10, 0.45, 0.65, 0.28)

    local iconBg = t:CreateTexture(nil, "BACKGROUND")
    iconBg:SetSize(48, 48); iconBg:SetPoint("LEFT", 9, 0)
    iconBg:SetColorTexture(0.04, 0.12, 0.22, 0.80)

    local icon = t:CreateTexture(nil, "ARTWORK")
    icon:SetSize(44, 44); icon:SetPoint("LEFT", 11, 0)
    t.icon = icon

    local calIconOverlay = t:CreateTexture(nil, "OVERLAY")
    calIconOverlay:SetSize(20, 20); calIconOverlay:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 4, -4)
    calIconOverlay:SetTexture("Interface\Calendar\Holidays\Calendar_GarrisonInvasion")
    calIconOverlay:SetAlpha(0.85)

    local badge = t:CreateFontString(nil, "OVERLAY")
    badge:SetFont("Fonts\\FRIZQT__.TTF", 8, "OUTLINE")
    badge:SetPoint("TOPLEFT", 70, -7)
    badge:SetShadowOffset(1, -1); badge:SetShadowColor(0, 0, 0, 0.20)
    t.badge = badge

    local titleStr = t:CreateFontString(nil, "OVERLAY")
    titleStr:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
    titleStr:SetPoint("TOPLEFT", 70, -18)
    titleStr:SetWidth(260); titleStr:SetJustifyH("LEFT")
    titleStr:SetShadowOffset(1, -1); titleStr:SetShadowColor(0, 0, 0, 0.20)
    t.titleStr = titleStr

    local descStr = t:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
    descStr:SetPoint("TOPLEFT", 70, -33)
    descStr:SetWidth(260); descStr:SetJustifyH("LEFT")
    descStr:SetTextColor(0.65, 0.82, 0.95)
    descStr:SetShadowOffset(1, -1); descStr:SetShadowColor(0, 0, 0, 0.20)
    t.descStr = descStr

    local hint = t:CreateFontString(nil, "OVERLAY")
    hint:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
    hint:SetPoint("BOTTOMRIGHT", -6, 4)
    hint:SetTextColor(0.40, 0.55, 0.68)
    hint:SetText("Klicken zum Oeffnen")

    self.calToastFrame = t
end

GDL:RegisterModule("UI", UI)
