-- ══════════════════════════════════════════════════════════════
-- UI SUB-MODULE: Book Chapters (Render-Funktionen)
-- Alle RenderXxx() Funktionen fuer die rechte Buchseite
-- Nutzt dieselbe UI-Table via addon._UI
-- ══════════════════════════════════════════════════════════════

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

-- Local refs from shared UI state
local CLASS_COLORS = UI.CLASS_COLORS
local CLASS_ICONS = UI.CLASS_ICONS
local ClassColorHex = UI.ClassColorHex
local CleanKillerName = UI.CleanKillerName

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.32, 0.21, 0.07, 0.16)
    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("|cff3D2410" .. GDL:T("Unsterbliche:") .. "|r  |cff6A4408" .. #survivors .. "|r  |cff3D2410" .. GDL:T("Krieger 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("|cff3D2410" .. GDL:T("Noch 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 "|cffA8761E#1|r" or rank == 2 and "|cff8C8478#2|r" or rank == 3 and "|cff96522A#3|r" or ("|cff7A6448#" .. 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("|cff5E3E1CLv.|r |cff3D2410" .. (s.level or "?") .. "|r  |cff5E3E1C" .. 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("|cff1A5A24" .. 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("|cff3A7030|TInterface\\RAIDFRAME\\ReadyCheck-Ready:10:10|t " .. GDL: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("|cff3A7030|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t Noch am Leben|r")
        GameTooltip:AddLine("|cff888888" .. GDL:T("Rang: #") .. rank .. GDL:T(" 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, "|cff5E3E1C" .. GDL:T("Gesamte Tode:") .. "|r  |cff7A2C14" .. total .. "|r", 12, "|cff5E3E1C")
    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, "|cff5E3E1C" .. GDL:T("Durchschnittslevel:") .. "|r  |cff7A2C14" .. (total > 0 and string.format("%.1f", totalLvl / total) or "0") .. "|r", 10, "|cff5E3E1C")
    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 = GDL:T(CLASS_NAMES[cId])
        local cc = UI.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("|cff2A1606" .. 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("|cff5E3E1C" .. 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 .. " " .. GDL:T("Tode") .. "|r  |cff888888" .. GDL:T("von") .. " " .. total .. " " .. GDL:T("gesamt") .. "|r")
            local pctStr = total > 0 and string.format("%.1f", count/total*100) or "0"
            GameTooltip:AddLine("|cff888888" .. GDL:T("Anteil:") .. "  " .. 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, "|cff5E3E1C-- Gefaehrlichste Zonen --|r", 11, "|cff5E3E1C")
    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(120)
        label:SetJustifyH("LEFT")
        label:SetWordWrap(false)
        label:SetText("|cff2A3E68" .. kv[1] .. "|r")
        local bar = row:CreateTexture(nil, "BACKGROUND")
        local barWidth = math.max(6, pct * 80)
        bar:SetSize(barWidth, 14)
        bar:SetPoint("LEFT", 122, 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", 210, 0)
        countLabel:SetText("|cff7A2010" .. 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] .. " " .. GDL:T("Tode") .. "|r  |cff888888" .. GDL:T("in dieser Zone") .. "|r")
            local pctStr = total > 0 and string.format("%.1f", kv[2]/total*100) or "0"
            GameTooltip:AddLine("|cff888888" .. GDL:T("Anteil:") .. "  " .. pctStr .. " %% " .. GDL:T("aller Tode") .. "|r")
            local danger = math.floor(kv[2]/maxZone * 100)
            local dangerCol = danger >= 80 and "|cffFF2222" or danger >= 50 and "|cff7A2C14" or "|cff8A5E14"
            GameTooltip:AddLine(GDL:T("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, "|cff5E3E1C-- Level-Verteilung der Tode --|r", 11, "|cff5E3E1C")
    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("|cff5E3E1CLvl " .. 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("|cff3D2410" .. cnt .. "|r")
        end
        y = y - 16
    end
    y = y - 10
    -- Haeufigste Todesursachen (nur wenn echte Killer-Namen vorliegen)
    do
        local TRANSP = {
            ["DNL-Channel"]=true, ["DNL"]=true, ["System"]=true, ["Blizzard"]=true,
            ["HCDeathAlerts"]=true, ["Unbekannt"]=true, ["Unknown"]=true, [""]=true,
        }
        local causeCt = {}
        local KT = GDL:GetModule("KillerTracker")
        for _, d in ipairs(deaths) do
            local c = (KT and KT.CauseName and KT:CauseName(d)) or d.killer or d.killerName
            if c and not TRANSP[c] then causeCt[c] = (causeCt[c] or 0) + 1 end
        end
        local arr = {}
        for k, v in pairs(causeCt) do arr[#arr + 1] = { k, v } end
        if #arr > 0 then
            table.sort(arr, function(a, b) return a[2] > b[2] end)
            local maxC = arr[1][2]
            _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Haeufigste Todesursachen --|r", 11, "|cff5E3E1C")
            y = y - 4
            for i, kv in ipairs(arr) do
                if i > 6 then break end
                local row = CreateFrame("Frame", nil, parent)
                row:SetSize(280, 16)
                row:SetPoint("TOPLEFT", 4, y)
                local lbl = row:CreateFontString(nil, "OVERLAY")
                lbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
                lbl:SetPoint("LEFT", 0, 0); lbl:SetWidth(120); lbl:SetJustifyH("LEFT"); lbl:SetWordWrap(false)
                lbl:SetText("|cff5E2A1C" .. tostring(kv[1]) .. "|r")
                local bar = row:CreateTexture(nil, "BACKGROUND")
                bar:SetSize(math.max(6, kv[2] / maxC * 80), 13)
                bar:SetPoint("LEFT", 122, 0)
                bar:SetColorTexture(0.55, 0.18, 0.10, 0.70)
                local ct = row:CreateFontString(nil, "OVERLAY")
                ct:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
                ct:SetPoint("LEFT", 210, 0)
                ct:SetText("|cff7A2010" .. kv[2] .. "|r")
                y = y - 18
            end
            y = y - 10
        end
    end
    _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Todes-Uhrzeiten --|r", 11, "|cff5E3E1C")
    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("|cff5E3E1C" .. 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("|cff5E3E1C" .. h .. "h|r")
    end
    y = y - 12
    y = y - 8
    _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Monatsvergleich --|r", 11, "|cff5E3E1C")
    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("|cff5E3E1C" .. 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("|cff7A2C14" .. m[2].count .. " " .. GDL:T("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("|cff3D2410Supporter+ 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 .. "\" " .. GDL:T("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("|cff7A6448" .. GDL:T("Oeffne 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("|cff8A5E14Zu 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.32, 0.21, 0.07, 0.16)
    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 "|cff1A5A24" or (pct >= 50 and "|cff8A5E14" or "|cff5E3E1C")
    bannerLbl:SetText("|cff3D2410Meilensteine:  |cff8A5E14" .. 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 = GDL:T("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 = GDL:T("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 = GDL:T("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 ("|cff3D2410" .. dispName .. "|r") or ("|cff7A6448" .. 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.28, 0.2, 0.08)
    else
        descStr:SetTextColor(0.369, 0.243, 0.110)
    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("|cff3A7030|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
    else
        statusStr:SetText("|cff7A6448|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 "|cff8A5E14" or "|cff5E3E1C"
        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("|cff888888" .. GDL:T("Kategorie: ") .. GDL:T(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 |cff00FF66" .. GDL:T("Erreicht am ") .. date("%d.%m.%Y", ui.timestamp) .. "|r")
                if ui.level then
                    GameTooltip:AddLine("|cff888888" .. GDL:T("Level bei Freischaltung: ") .. ui.level .. "|r")
                end
            else
                GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t |cff3A7030" .. GDL:T("Freigeschaltet") .. "|r")
            end
        else
            GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t |cffAA4444" .. GDL:T("Noch 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, "|cff5E3E1C" .. GDL:T("-- Mitglieder mit Berufsdaten --") .. "|r", 10, "|cff5E3E1C")
        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 = GDL:T("Keine Berufe") end
            local cc = UI.ClassColorHex(info.classId)
            _, y = self:MakeBookRow(parent, y, CLASS_ICONS[info.classId], "|c" .. cc .. entry.name .. "|r", "|cff2A1606" .. profStr .. "|r", info.level and ("|cff2A1606Lvl " .. 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, "|cff2A1606" .. GDL:T("-- Ohne Addon / Berufsdaten --") .. "|r", 10, "|cff2A1606")
        y = y - 4
        for _, m in ipairs(sortedWithout) do
            local cc = UI.ClassColorHex(m.class)
            local classIcon = CLASS_ICONS[m.class]
            local lvlStr = m.level and ("|cff2A1606Lvl " .. m.level .. "|r") or ""
            local zoneStr = m.zone and ("|cff3A5068" .. m.zone .. "|r") or ""
            _, y = self:MakeBookRow(parent, y, classIcon, "|c" .. cc .. (m.name or "?") .. "|r", "|cff705038" .. GDL:T("Berufe unbekannt (kein Addon)") .. "|r", lvlStr)
            withoutProf = withoutProf + 1
        end
    end
    y = y - 8
    _, y = self:MakeBookLine(parent, y, "|cff2A1606" .. withProf .. " " .. GDL:T("mit Berufsdaten") .. "  |  " .. withoutProf .. " " .. GDL:T("ohne Addon") .. "|r", 8, "|cff2A1606")
    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, "|cff3A7030" .. #onlineList .. " Online|r  |  |cff5E3E1C" .. #offlineList .. " Offline|r  |  |cff2A1606" .. (#onlineList + #offlineList) .. " " .. GDL:T("Gesamt") .. "|r", 10, "|cff2A1606")
    y = y - 6
    if #onlineList > 0 then
        _, y = self:MakeBookLine(parent, y, "|cff3A7030Online:|r", 10, "|cff3A7030")
        y = y - 2
        for _, m in ipairs(onlineList) do
            local cc = UI.ClassColorHex(m.class)
            _, y = self:MakeBookRow(parent, y, CLASS_ICONS[m.class], "|c" .. cc .. m.name .. "|r", "|cff2A1606Lvl " .. (m.level or "?") .. "  " .. (m.rank or "") .. "|r", "|cff3A5068" .. (m.zone or "") .. "|r")
        end
    end
    if #offlineList > 0 then
        y = y - 6
        _, y = self:MakeBookLine(parent, y, "|cff5E3E1COffline (" .. #offlineList .. "):|r", 10, "|cff5E3E1C")
        y = y - 2
        for i, m in ipairs(offlineList) do
            if i > 30 then
                _, y = self:MakeBookLine(parent, y, "|cff5E3E1C" .. string.format(GDL:T("... und %d weitere"), (#offlineList - 30)) .. "|r", 9, "|cff5E3E1C")
                break
            end
            _, y = self:MakeBookRow(parent, y, nil, "|cff3D2410" .. m.name .. "|r", "|cff3D2410Lvl " .. (m.level or "?") .. " " .. (m.rank or "") .. "|r", "")
        end
    end
    self.mainFrame.rightSubtitle:SetText("|cff2A1606" .. #onlineList .. GDL:T(" online / ") .. (#onlineList + #offlineList) .. GDL:T(" 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 (GDL:T("Aktiver Titel: ") .. (selectedTitle.name or "?")) or GDL:T("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("|cff8A5E14" .. GDL:T("Aktuell 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("|cff8A5E14" .. (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("|cff5A3A18" .. (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 ("|cff8A5E14" .. t.name .. " |cff8A5E14" .. GDL:T("<aktiv>") .. "|r") or ("|cff1A5A24" .. 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.28, 0.2, 0.08)
            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("|cff8A5E14|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
            else
                statusStr:SetText("|cff3A7030|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("|cffFFD100" .. GDL:T("Aktuell ausgewaehlt") .. "|r")
                else
                    GameTooltip:AddLine("|cff3A7030" .. GDL:T("Klicken 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("|cff7A6448" .. GDL:T("Noch 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("|cff7A6448" .. 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.369, 0.243, 0.110)
            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("|cff7A6448|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("|cffAA4444" .. GDL:T("Noch 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 â•â•â•

-- ==========================================================================
-- VIII-B. ABENTEURER DER GILDE -- GRM-Style Member Profiles
-- ==========================================================================

local CLASS_STRING_TO_ID = {
    WARRIOR=1,PALADIN=2,HUNTER=3,ROGUE=4,PRIEST=5,
    DEATHKNIGHT=6,SHAMAN=7,MAGE=8,WARLOCK=9,MONK=10,DRUID=11
}
local RACE_NAMES_DE = {
    [1]="Mensch",[2]="Ork",[3]="Zwerg",[4]="Nachtelf",[5]="Untoter",
    [6]="Tauren",[7]="Gnom",[8]="Troll",[10]="Blutelf",[11]="Draenei",
    [22]="Worgen",[24]="Pandaren"
}
local CLASS_NAMES_DE_ADV = {
    WARRIOR="Krieger",PALADIN="Paladin",HUNTER="Jaeger",ROGUE="Schurke",
    PRIEST="Priester",SHAMAN="Schamane",MAGE="Magier",WARLOCK="Hexenmeister",
    DRUID="Druide",DEATHKNIGHT="Todesritter"
}
local STATUS_LABELS_ADV = { [2]="|cff886600AFK|r", [3]="|cff880000Besch.|r" }

local function FormatLastOnline(y, mo, d, h)
    if (y or 0) > 0  then return "|cff444444vor " .. y .. " J.|r" end
    if (mo or 0) > 0 then return "|cff444444vor " .. mo .. " Mo.|r" end
    if (d or 0) > 0  then return "|cff7A6448vor " .. d .. " T.|r" end
    if (h or 0) > 0  then return "|cff7A6448vor " .. h .. " h|r" end
    return "|cff333333Heute|r"
end

-- Baut ein wiederverwendbares Karten-Frame (alle Sub-Widgets EINMAL).
-- Tooltip-/Hover-Closures lesen aus card._* Feldern -> beim Recyceln muss nur
-- der Inhalt aktualisiert werden, nicht der Frame neu erzeugt.
local function AcquireMemberCard(parent)
    local card = table.remove(UI._memberCardPool)
    if not card then
        card = CreateFrame("Frame", nil, parent)
        card.bg = card:CreateTexture(nil, "BACKGROUND")
        card.bg:SetAllPoints()
        card.topLine = card:CreateTexture(nil, "BORDER")
        card.topLine:SetHeight(1); card.topLine:SetPoint("TOPLEFT"); card.topLine:SetPoint("TOPRIGHT")
        card.topLine:SetColorTexture(0.40, 0.28, 0.08, 0.28)
        card.accent = card:CreateTexture(nil, "ARTWORK")
        card.accent:SetSize(3, 50); card.accent:SetPoint("LEFT", 0, 0)
        card.icon = card:CreateTexture(nil, "ARTWORK")
        card.icon:SetSize(36, 36); card.icon:SetPoint("LEFT", 7, 0)
        card.dot = card:CreateTexture(nil, "OVERLAY")
        card.dot:SetSize(7, 7); card.dot:SetPoint("BOTTOMLEFT", 22, 7)
        card.nameFS = card:CreateFontString(nil, "OVERLAY")
        card.nameFS:SetFont("Fonts\\FRIZQT__.TTF", 12, ""); card.nameFS:SetPoint("TOPLEFT", 52, -5)
        card.subFS = card:CreateFontString(nil, "OVERLAY")
        card.subFS:SetFont("Fonts\\FRIZQT__.TTF", 9, ""); card.subFS:SetPoint("TOPLEFT", 52, -20)
        card.rankFS = card:CreateFontString(nil, "OVERLAY")
        card.rankFS:SetFont("Fonts\\FRIZQT__.TTF", 8, ""); card.rankFS:SetPoint("TOPLEFT", 52, -31)
        card.zoneFS = card:CreateFontString(nil, "OVERLAY")
        card.zoneFS:SetFont("Fonts\\FRIZQT__.TTF", 8, ""); card.zoneFS:SetPoint("BOTTOMLEFT", 52, 6)
        card.seenFS = card:CreateFontString(nil, "OVERLAY")
        card.seenFS:SetFont("Fonts\\FRIZQT__.TTF", 9, ""); card.seenFS:SetPoint("TOPRIGHT", -6, -8)
        card.apFS = card:CreateFontString(nil, "OVERLAY")
        card.apFS:SetFont("Fonts\\FRIZQT__.TTF", 8, ""); card.apFS:SetPoint("TOPRIGHT", -6, -21)
        card.profFS = card:CreateFontString(nil, "OVERLAY")
        card.profFS:SetFont("Fonts\\FRIZQT__.TTF", 8, ""); card.profFS:SetPoint("BOTTOMRIGHT", -6, 6)
        card:EnableMouse(true)
        card:SetScript("OnEnter", function(self)
            local clr = self._clr or {0.8, 0.8, 0.8}
            self.bg:SetColorTexture(clr[1]*0.14, clr[2]*0.14, clr[3]*0.14, 0.48)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText("|c" .. (self._cc or "ffffffff") .. (self._mName or "") .. "|r")
            GameTooltip:AddLine(GDL:T("Rang: ") .. (self._mRank or ""), 0.85, 0.75, 0.50)
            if self._mRace and self._mRace ~= "" then
                GameTooltip:AddLine("Rasse: " .. self._mRace, 0.70, 0.80, 0.90)
            end
            if self._mNote and self._mNote ~= "" then
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine(GDL:T("Notiz:"), 0.6, 0.6, 0.6)
                GameTooltip:AddLine(self._mNote, 0.9, 0.9, 0.9, true)
            end
            if self._mONote and self._mONote ~= "" then
                GameTooltip:AddLine("Offiziersnotiz:", 0.6, 0.8, 0.6)
                GameTooltip:AddLine(self._mONote, 0.9, 0.9, 0.9, true)
            end
            GameTooltip:Show()
        end)
        card:SetScript("OnLeave", function(self)
            self.bg:SetColorTexture(self._bgR or 0.07, self._bgG or 0.04, self._bgB or 0.01, self._bgA or 0.20)
            GameTooltip:Hide()
        end)
    end
    card:SetParent(parent)
    UI:TrackPageWidget(card, "_mcInUse")
    return card
end

function UI:RenderMemberCard(parent, y, member)
    local classId   = CLASS_STRING_TO_ID[member.class] or 0
    local clrRGB    = CLASS_COLORS[classId] or {0.8, 0.8, 0.8}
    local cc        = string.format("ff%02X%02X%02X",
                        math.floor(clrRGB[1]*255), math.floor(clrRGB[2]*255), math.floor(clrRGB[3]*255))
    local classIcon = CLASS_ICONS[classId]
    local CARD_H    = 56

    local card = AcquireMemberCard(parent)
    card:SetSize(326, CARD_H)
    card:ClearAllPoints()
    card:SetPoint("TOPLEFT", 4, y)

    -- Hintergrund (Basisfarbe fuer OnLeave merken)
    local bgR, bgG, bgB, bgA
    if member.online then bgR, bgG, bgB, bgA = 0.02, 0.10, 0.08, 0.28
    else                  bgR, bgG, bgB, bgA = 0.07, 0.04, 0.01, 0.20 end
    card.bg:SetColorTexture(bgR, bgG, bgB, bgA)
    card._bgR, card._bgG, card._bgB, card._bgA = bgR, bgG, bgB, bgA
    card._clr, card._cc = clrRGB, cc

    card.accent:SetColorTexture(clrRGB[1], clrRGB[2], clrRGB[3], 0.85)

    if classIcon then card.icon:SetTexture(classIcon); card.icon:Show() else card.icon:Hide() end

    if member.online then
        local s = member.status or 1
        if s == 2 then     card.dot:SetColorTexture(1, 0.85, 0, 1)
        elseif s == 3 then card.dot:SetColorTexture(1, 0.35, 0.35, 1)
        else               card.dot:SetColorTexture(0.20, 1, 0.30, 1) end
    else
        card.dot:SetColorTexture(0.45, 0.45, 0.45, 0.7)
    end

    local statusSuffix = STATUS_LABELS_ADV[member.status] and
                         ("  " .. STATUS_LABELS_ADV[member.status]) or ""
    card.nameFS:SetText("|c" .. cc .. member.name .. "|r" .. statusSuffix)

    local raceName  = (member.raceId and RACE_NAMES_DE[member.raceId]) or (member.race or "")
    local className = CLASS_NAMES_DE_ADV[member.class] or (member.class or "")
    local subParts  = { "Stufe " .. (member.level or "?") }
    if raceName ~= ""  then table.insert(subParts, raceName) end
    if className ~= "" then table.insert(subParts, className) end
    card.subFS:SetText("|cff2A1606" .. table.concat(subParts, "  ·  ") .. "|r")

    card.rankFS:SetText("|cff1A5A24" .. (member.rank or "") .. "|r")

    if (member.zone or "") ~= "" then
        card.zoneFS:SetText("|cff2A1606" .. member.zone .. "|r"); card.zoneFS:Show()
    else
        card.zoneFS:Hide()
    end

    if member.online then
        card.seenFS:SetText("|cff3A7030|TInterface\\RAIDFRAME\\ReadyCheck-Ready:10:10|t Online|r")
    else
        card.seenFS:SetText(FormatLastOnline(
            member.lastOnlineYears, member.lastOnlineMonths,
            member.lastOnlineDays,  member.lastOnlineHours))
    end

    if (member.achievementPoints or 0) > 0 then
        card.apFS:SetText("|cff785810★ " .. member.achievementPoints .. "|r"); card.apFS:Show()
    else
        card.apFS:Hide()
    end

    local Prof = GDL:GetModule("Professions")
    local profParts = {}
    if Prof then
        local pd = Prof:GetPlayerProfession(member.name)
        if pd then
            if pd.prof1 and pd.prof1.name then
                table.insert(profParts, pd.prof1.name .. " " .. (pd.prof1.skill or "?"))
            end
            if pd.prof2 and pd.prof2.name then
                table.insert(profParts, pd.prof2.name .. " " .. (pd.prof2.skill or "?"))
            end
        end
    end
    if #profParts == 0 and (member.clubProf1 or member.clubProf2) then
        local function cname(id)
            if not id or id <= 0 then return nil end
            local ok, n = pcall(GetSpellInfo, id)
            return ok and n or nil
        end
        if member.clubProf1 then
            local n = cname(member.clubProf1.id)
            if n then table.insert(profParts, n .. " " .. (member.clubProf1.rank or "?")) end
        end
        if member.clubProf2 then
            local n = cname(member.clubProf2.id)
            if n then table.insert(profParts, n .. " " .. (member.clubProf2.rank or "?")) end
        end
    end
    if #profParts > 0 then
        card.profFS:SetText("|cff2A1606" .. table.concat(profParts, "  ·  ") .. "|r"); card.profFS:Show()
    else
        card.profFS:Hide()
    end

    -- Tooltip-/Hover-Daten fuer die (einmaligen) Closures
    card._mName  = member.name
    card._mRank  = member.rank
    card._mRace  = raceName
    card._mNote  = member.note
    card._mONote = member.officerNote

    card:Show()
    return y - CARD_H - 3
end

function UI:RenderAdventurers()
    local parent = self.mainFrame.scrollChild
    local Guild  = GDL:GetModule("Guild")
    if not Guild then return end

    local allMembers = Guild:GetAllMembers()
    local y = 0
    local totalCount, onlineCount = 0, 0
    for _, m in pairs(allMembers) do
        if type(m) == "table" and not m._removed then
            totalCount = totalCount + 1
            if m.online then onlineCount = onlineCount + 1 end
        end
    end

    local banner = CreateFrame("Frame", nil, parent)
    banner:SetSize(330, 34)
    banner:SetPoint("TOPLEFT", 0, y)
    local bannerBg = banner:CreateTexture(nil, "BACKGROUND")
    bannerBg:SetAllPoints()
    bannerBg:SetColorTexture(0.32, 0.21, 0.07, 0.16)
    local shimmer = banner:CreateTexture(nil, "BACKGROUND")
    shimmer:SetPoint("TOPLEFT"); shimmer:SetPoint("TOPRIGHT"); shimmer:SetHeight(17)
    shimmer:SetColorTexture(0.65, 0.45, 0.12, 0.08); shimmer:SetBlendMode("ADD")
    local bIc = banner:CreateTexture(nil, "ARTWORK")
    bIc:SetTexture("Interface\\Icons\\INV_Misc_GroupNeedMore")
    bIc:SetSize(22, 22); bIc:SetPoint("LEFT", 6, 0)
    local bLbl = banner:CreateFontString(nil, "OVERLAY")
    bLbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    bLbl:SetPoint("LEFT", 34, 1)
    bLbl:SetShadowOffset(1, -1); bLbl:SetShadowColor(0, 0, 0, 0.15)
    bLbl:SetText(
        "|cff3D2410Abenteurer:  |cff8A5E14" .. totalCount .. " gesamt|r  " ..
        "|cff1A5A24" .. onlineCount .. " online|r  " ..
        "|cff5E3E1C" .. (totalCount - onlineCount) .. " offline|r"
    )
    y = y - 40

    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.20, 0.40, 0.80, 0.45)
    y = y - 12

    local memberList = {}
    for _, m in pairs(allMembers) do
        if type(m) == "table" and not m._removed then
            table.insert(memberList, m)
        end
    end
    table.sort(memberList, function(a, b)
        if a.online ~= b.online then return a.online end
        if (a.rankIndex or 99) ~= (b.rankIndex or 99) then
            return (a.rankIndex or 99) < (b.rankIndex or 99)
        end
        return (a.name or "") < (b.name or "")
    end)

    local rankGroups, rankOrder = {}, {}
    for _, m in ipairs(memberList) do
        local rank = m.rank or "Unbekannt"
        if not rankGroups[rank] then
            rankGroups[rank] = { members={}, rankIndex=m.rankIndex or 99, onlineCount=0 }
            table.insert(rankOrder, rank)
        end
        table.insert(rankGroups[rank].members, m)
        if m.online then rankGroups[rank].onlineCount = rankGroups[rank].onlineCount + 1 end
    end
    table.sort(rankOrder, function(a, b)
        return (rankGroups[a].rankIndex or 99) < (rankGroups[b].rankIndex or 99)
    end)

    for _, rankName in ipairs(rankOrder) do
        local group = rankGroups[rankName]
        local ri = math.min(group.rankIndex or 0, 10)
        local hR = 0.04 + ri * 0.010
        local hG = math.max(0.14 - ri * 0.008, 0.02)
        local hB = math.max(0.30 - ri * 0.018, 0.04)
        y = self:RenderAchCategoryHeader(parent, y,
            rankName, "Interface\\Icons\\INV_Crown_01",
            group.onlineCount, #group.members, hR, hG, hB)
        y = y - 4
        for _, m in ipairs(group.members) do
            y = self:RenderMemberCard(parent, y, m)
        end
        y = y - 10
    end

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

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 Locale    = GDL:GetModule("Locale")

    -- ── Letzten-Worte-Button ──────────────────────────────────────
    local myWords  = LastWords and LastWords:GetPresetLastWords() or nil
    local wordsBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    wordsBtn:SetSize(310, 20)
    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.28, 0.20, 0.11, 0.55)
    wordsBtn:SetBackdropBorderColor(0.58, 0.44, 0.20, 0.75)
    local wLbl = wordsBtn:CreateFontString(nil, "OVERLAY")
    wLbl:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    wLbl:SetAllPoints(); wLbl:SetJustifyH("CENTER")
    wLbl:SetText(myWords
        and ("|cff7A6448Meine letzten Worte: \"" .. myWords:sub(1,35) ..
             (myWords:len() > 35 and "..." or "") .. "\"|r")
        or  "|cffA07840Meine letzten Worte festlegen...|r")
    wordsBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_LAST_WORDS"] = {
            text = GDL:T("Deine letzten Worte festlegen\n(Erscheinen im Buch wenn du stirbst):"),
            button1=GDL:T("Speichern"), button2=GDL:T("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(GDL:T("Lege deine letzten Worte fest"), 0.8, 0.7, 0.5)
        GameTooltip:AddLine(GDL:T("Diese erscheinen im Buch wenn du faellst."), 1, 1, 1, true)
        GameTooltip:Show()
    end)
    wordsBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    y = y - 26

    -- ── Stats-Banner ─────────────────────────────────────────────
    local deaths     = guildData.deaths
    local total      = #deaths
    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
    local today, thisWeek = 0, 0
    for _, d in ipairs(deaths) do
        if (d.timestamp or 0) >= todayStart then today = today + 1 end
        if (d.timestamp or 0) >= weekStart  then thisWeek = thisWeek + 1 end
    end
    _, y = self:MakeBookLine(parent, y,
        "|cff2A1606" .. GDL:T("Gefallene:") .. " |cff7A2C14" .. total .. "|r  |cff2A1606" .. GDL:T("Heute:") .. " |cff7A2C14" .. today .. "|r  |cff2A1606" .. GDL:T("Diese Woche:") .. " |cff7A2C14" .. thisWeek .. "|r", 9, "|cff2A1606")
    y = y - 4

    -- ── Divider ───────────────────────────────────────────────────
    local divTex = parent:CreateTexture(nil, "ARTWORK")
    divTex:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    divTex:SetSize(310, 6)
    divTex:SetPoint("TOPLEFT", 4, y + 2)
    divTex:SetVertexColor(0.55, 0.20, 0.10, 0.55)
    y = y - 10

    -- Dekorative Blutflecken
    if total >= 5 then
        local bs = parent:CreateTexture(nil, "BACKGROUND")
        bs:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
        bs:SetSize(80, 80); bs:SetPoint("TOPRIGHT", -10, -40)
        bs:SetVertexColor(0.4, 0.05, 0.02, 0.15)
    end

    -- ── Tod-Einträge (sortiert: neueste zuerst) ───────────────────
    local sorted = {}
    for _, d in ipairs(deaths) do table.insert(sorted, d) end
    table.sort(sorted, function(a,b) return (a.timestamp or 0) > (b.timestamp or 0) end)

    if total == 0 then
        _, y = self:MakeBookLine(parent, y, "|cff3A5A30Keine Gefallenen verzeichnet. Die Gilde steht stark.|r", 10)
    end

    for idx, d in ipairs(sorted) do
        local cc        = UI.ClassColorHex(d.class or d.classId)
        local col       = {}
        do
            local r, g, b = 1, 1, 1
            local classId = d.class or d.classId
            if CLASS_COLORS and CLASS_COLORS[classId] then
                r, g, b = CLASS_COLORS[classId][1], CLASS_COLORS[classId][2], CLASS_COLORS[classId][3]
            end
            col = {r, g, b}
        end
        local name      = d.name or "Unbekannt"
        local level     = d.level or "?"
        local zone      = d.zone or "Unbekannt"
        local killer    = UI.CleanKillerName(d.killerName or d.killedBy or d.source or "")
        local dateStr   = d.timestamp and date("%d.%m.%Y %H:%M", d.timestamp) or ""
        local classId   = d.class or d.classId

        -- Nachruf ermitteln
        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 = name .. "_" .. (d.timestamp or 0)
        local custom = GuildDeathLogDB and GuildDeathLogDB.customNachrufe and GuildDeathLogDB.customNachrufe[customKey]
        if custom and custom ~= "" then nachruf = custom end
        if not nachruf or nachruf == "" then
            nachruf = name .. " " .. GDL:T("fiel in") .. " " .. zone .. ". " .. GDL:T("Ruhe in Frieden.")
        end

        -- Höhe berechnen (Koordinaten + /played ggf. extra Zeilen)
        local hasLastWords = d.lastWords and d.lastWords ~= ""
        local hasCoords    = d.coordX and d.coordY and
                             (GuildDeathLogDB and GuildDeathLogDB.settings and GuildDeathLogDB.settings.coordsInBook ~= false)
        local hasPlayed    = d.playedTotalFormatted and
                             (GuildDeathLogDB and GuildDeathLogDB.settings and GuildDeathLogDB.settings.playedTimeInBook ~= false)
        local entryH = 64
        if hasLastWords then entryH = entryH + 17 end
        if hasCoords    then entryH = entryH + 17 end
        if hasPlayed    then entryH = entryH + 17 end

        -- Hover-Wrapper-Frame
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(318, entryH)
        row:SetPoint("TOPLEFT", 2, y)
        row:EnableMouse(true)

        -- Hintergrund (hover-fähig)
        local rowBg = row:CreateTexture(nil, "BACKGROUND")
        rowBg:SetAllPoints()
        rowBg:SetColorTexture(0.10, 0.02, 0.01, 0.0)  -- unsichtbar im Ruhezustand

        -- Kerzen-Icon
        local candleTex = row:CreateTexture(nil, "OVERLAY")
        candleTex:SetTexture("Interface\\Minimap\\Tracking\\Innkeeper")
        candleTex:SetSize(16, 16)
        candleTex:SetPoint("TOPLEFT", 2, -3)
        local hour = tonumber(date("%H")) or 12
        candleTex:SetAlpha((hour >= 20 or hour < 6) and 1.0 or 0.55)

        -- Name
        local nameFS = row:CreateFontString(nil, "OVERLAY")
        nameFS:SetFont("Fonts\\FRIZQT__.TTF", 13, "")
        nameFS:SetPoint("TOPLEFT", 20, -3)
        nameFS:SetWidth(190)
        nameFS:SetWordWrap(false)
        nameFS:SetText("|c" .. cc .. name .. "|r")

        -- Klasse + Level + Zone
        local subFS = row:CreateFontString(nil, "OVERLAY")
        subFS:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        subFS:SetPoint("TOPLEFT", 20, -21)
        subFS:SetPoint("TOPRIGHT", -115, -21)
        subFS:SetWordWrap(false)
        local subTxt = GDL:T("Stufe ") .. level .. "  |  " .. zone
        if killer then subTxt = subTxt .. "  |  " .. killer end
        subFS:SetText("|cff2A1606" .. subTxt .. "|r")

        -- Nachruf / Obituary
        local nachFS = row:CreateFontString(nil, "OVERLAY")
        nachFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        nachFS:SetPoint("TOPLEFT", 20, -37)
        nachFS:SetPoint("TOPRIGHT", -32, -37)
        nachFS:SetWordWrap(false)
        local shortNachruf = nachruf:sub(1, 55) .. (nachruf:len() > 55 and "..." or "")
        nachFS:SetText("|cff5E3E1C\"" .. shortNachruf .. "\"|r")

        -- Letzte Worte (wenn vorhanden)
        local lwYoffset = hasCoords and -69 or -53
        if hasLastWords then
            local lwFS = row:CreateFontString(nil, "OVERLAY")
            lwFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            lwFS:SetPoint("TOPLEFT", 20, -53)
            lwFS:SetPoint("TOPRIGHT", -32, -53)
            lwFS:SetWordWrap(false)
            local shortLW = d.lastWords:sub(1, 50) .. (d.lastWords:len() > 50 and "..." or "")
            lwFS:SetText("|cff8A5E14~ " .. shortLW .. " ~|r")
        end

        -- Koordinaten (wenn vorhanden)
        local coordYbase = hasLastWords and -69 or -53
        if hasCoords then
            local coordFS = row:CreateFontString(nil, "OVERLAY")
            coordFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            coordFS:SetTextColor(0.30, 0.48, 0.30)
            coordFS:SetPoint("TOPLEFT", 20, coordYbase)
            coordFS:SetText(string.format("|cff3A6A3A[Pos]|r %.1f, %.1f", d.coordX, d.coordY))
        end

        -- /played (wenn vorhanden)
        local playedYbase = coordYbase - (hasCoords and 16 or 0)
        if hasPlayed then
            local playedFS = row:CreateFontString(nil, "OVERLAY")
            playedFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
            playedFS:SetTextColor(0.38, 0.30, 0.52)
            playedFS:SetPoint("TOPLEFT", 20, playedYbase)
            playedFS:SetText("|cff5A4488[Gespielt]|r " .. d.playedTotalFormatted ..
                (d.playedLevelFormatted and ("  |cff666666Level: " .. d.playedLevelFormatted .. "|r") or ""))
        end

        -- Datum (rechts oben) — genug Platz fuer volles Datum
        local dateFS = row:CreateFontString(nil, "OVERLAY")
        dateFS:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        dateFS:SetPoint("TOPRIGHT", -2, -3)
        dateFS:SetWidth(110)
        dateFS:SetJustifyH("RIGHT")
        dateFS:SetText("|cff7A6448" .. dateStr .. "|r")

        -- Nachruf-Edit-Button
        local editBtn = CreateFrame("Button", nil, row)
        editBtn:SetSize(14, 14)
        editBtn:SetPoint("BOTTOMRIGHT", -2, 4)
        editBtn:SetAlpha(0.0)  -- sichtbar nur bei Hover
        editBtn:SetNormalTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up")
        editBtn:SetHighlightTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up")
        local dRef = d
        editBtn:SetScript("OnClick", function()
            local dKey = (dRef.name or "") .. "_" .. (dRef.timestamp or 0)
            StaticPopupDialogs["GDL_EDIT_NACHRUF"] = {
                text = GDL:T("Nachruf fuer ") .. (dRef.name or "?") .. GDL:T(" schreiben:"),
                button1=GDL:T("Speichern"), button2=GDL:T("Abbrechen"),
                hasEditBox=true, editBoxWidth=300, maxLetters=300,
                OnShow = function(self)
                    local eb = self.editBox or self.EditBox
                    if eb then
                        local ex = GuildDeathLogDB and GuildDeathLogDB.customNachrufe and GuildDeathLogDB.customNachrufe[dKey]
                        eb:SetText(ex 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[dKey] = 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(GDL:T("Nachruf bearbeiten"), 0.8, 0.7, 0.5)
            GameTooltip:Show()
        end)
        editBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)

        -- ── Löschen-Button (links neben Edit, sichtbar bei Hover) ────────────
        local dIdx = idx       -- nur fuer eindeutigen StaticPopup-Key
        local dTs  = d.timestamp  -- Identitaet (Name+Zeit) fuers Loeschen
        local delBtn = CreateFrame("Button", nil, row)
        delBtn:SetSize(14, 14)
        delBtn:SetPoint("BOTTOMRIGHT", editBtn, "BOTTOMLEFT", -3, 0)
        delBtn:SetAlpha(0.0)  -- unsichtbar bis Hover
        local delX = delBtn:CreateFontString(nil, "OVERLAY")
        delX:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
        delX:SetPoint("CENTER", 0, 1)
        delX:SetText("×")
        delX:SetTextColor(0.75, 0.22, 0.18)
        delBtn:SetScript("OnEnter", function(self)
            self:SetAlpha(1.0)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:SetText("|cffFF5555" .. name .. GDL:T(" löschen") .. "|r", 1, 0.3, 0.3)
            if GuildDeathLogDB.adminPassword and GuildDeathLogDB.adminPassword ~= "" then
                GameTooltip:AddLine(GDL:T("Erfordert Admin-Passwort"), 0.6, 0.6, 0.6)
            else
                GameTooltip:AddLine(GDL:T("Klicken zum Löschen"), 0.6, 0.6, 0.6)
            end
            GameTooltip:Show()
        end)
        delBtn:SetScript("OnLeave", function(self)
            self:SetAlpha(0.0)
            GameTooltip:Hide()
        end)
        delBtn:SetScript("OnClick", function()
            GameTooltip:Hide()
            if GuildDeathLogDB.adminPassword and GuildDeathLogDB.adminPassword ~= "" then
                -- Passwort-Dialog (wie im Overlay)
                local UI = GDL:GetModule("UI")
                if UI and UI.ShowPasswordDialog then
                    UI:ShowPasswordDialog(name, dTs)
                end
            else
                -- Direkte Bestätigung ohne Passwort
                StaticPopupDialogs["GDL_CONFIRM_DELETE_" .. dIdx] = {
                    text = "|cffFF5555" .. name .. "|r" .. GDL:T(" aus dem Buch loeschen?\nDies wird an Gildenmitglieder synchronisiert."),
                    button1 = GDL:T("Löschen"), button2 = GDL:L("CANCEL"),
                    OnAccept = function()
                        if GDL:DeleteDeathByIdentity(name, dTs) then
                            local UI = GDL:GetModule("UI")
                            if UI then UI:UpdateChronicle() end
                        end
                    end,
                    timeout = 0, whileDead = true, preferredIndex = 3,
                }
                StaticPopup_Show("GDL_CONFIRM_DELETE_" .. dIdx)
            end
        end)
        -- Delete-Button bei Row-Hover sichtbar machen
        -- Separator-Linie
        local sep = row:CreateTexture(nil, "BORDER")
        sep:SetHeight(1); sep:SetPoint("BOTTOMLEFT", 4, 0); sep:SetPoint("BOTTOMRIGHT", -4, 0)
        sep:SetColorTexture(0.55, 0.40, 0.18, 0.20)

        -- ── HOVER-TOOLTIP ──────────────────────────────────────────
        row:SetScript("OnEnter", function(self)
            rowBg:SetColorTexture(col[1]*0.18, col[2]*0.06, col[3]*0.04, 0.35)
            editBtn:SetAlpha(1.0)
            delBtn:SetAlpha(0.75)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:ClearLines()
            GameTooltip:SetText("|c" .. cc .. name .. "|r", col[1], col[2], col[3])
            local className = CLASS_NAMES_DE_ADV and CLASS_NAMES_DE_ADV[classId] or (classId or "")
            GameTooltip:AddLine(GDL:T("Stufe ") .. level .. "  " .. className, 0.85, 0.75, 0.50)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cff888888Gefallen in:|r  " .. zone, 1, 1, 1)
            if d.coordX and d.coordY then
                GameTooltip:AddLine(string.format("|cff888888Koordinaten:|r  |cff559955%.1f, %.1f|r", d.coordX, d.coordY), 1, 1, 1)
            end
            if killer then
                GameTooltip:AddLine("|cff888888" .. GDL:T("Getoetet von:") .. "|r  " .. killer, 1, 0.3, 0.3)
            end
            if d.killerSpell and d.killerSpell ~= "" then
                GameTooltip:AddLine("|cff888888" .. GDL:T("Faehigkeit:") .. "|r  " .. d.killerSpell, 1, 0.7, 0.7)
            end
            if d.playedTotalFormatted then
                GameTooltip:AddLine("|cff888888Gespielt:|r  |cff8866AA" .. d.playedTotalFormatted .. "|r", 1, 1, 1)
            end
            if d.lastWords and d.lastWords ~= "" then
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cff886644" .. GDL:T("~ Letzte Worte ~") .. "|r")
                GameTooltip:AddLine("\"" .. d.lastWords .. "\"", 0.9, 0.85, 0.7, true)
            end
            if nachruf and nachruf ~= "" then
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cff685040Nachruf:|r")
                GameTooltip:AddLine("\"" .. nachruf .. "\"", 0.8, 0.7, 0.55, true)
            end
            -- Kondolenz im Tooltip
            local _konDB = (GuildDeathLogDB and GuildDeathLogDB.kondolenze) or {}
            local _konKey = name .. "_" .. (d.timestamp or 0)
            local _konTxt = _konDB[_konKey]
            if _konTxt and _konTxt ~= "" then
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cff5A3008Kondolenz:|r")
                GameTooltip:AddLine(_konTxt, 0.75, 0.60, 0.40, true)
            end
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cff666666" .. dateStr .. "|r")
            GameTooltip:Show()
        end)
        row:SetScript("OnLeave", function()
            rowBg:SetColorTexture(0.10, 0.02, 0.01, 0.0)
            editBtn:SetAlpha(0.0)
            delBtn:SetAlpha(0.0)
            GameTooltip:Hide()
        end)

        y = y - entryH - 2
    end

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

-- ── KONDOLENZENBUCH ────────────────────────────────────────────────────────
function UI:RenderCondolences()
    local parent    = self.mainFrame.scrollChild
    local y         = 0
    local guildData = GDL:GetGuildData()
    if not guildData or not guildData.deaths then return end

    GuildDeathLogDB            = GuildDeathLogDB or {}
    GuildDeathLogDB.kondolenze = GuildDeathLogDB.kondolenze or {}
    local konDB = GuildDeathLogDB.kondolenze

    -- ── Seiten-Header ──────────────────────────────────────────────────
    local hdrFS = parent:CreateFontString(nil, "OVERLAY")
    hdrFS:SetFont("Fonts\\MORPHEUS.TTF", 11, "")
    hdrFS:SetPoint("TOPLEFT", 10, y - 2)
    hdrFS:SetPoint("TOPRIGHT", -10, y - 2)
    hdrFS:SetText("|cff4A2D0A" .. GDL:T("Hier ruhen die Worte, die zurueckblieben.") .. "|r")
    hdrFS:SetJustifyH("CENTER")
    y = y - 24

    local divT = parent:CreateTexture(nil, "ARTWORK")
    divT:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    divT:SetSize(300, 5)
    divT:SetPoint("TOPLEFT", 10, y)
    divT:SetVertexColor(0.55, 0.40, 0.18, 0.50)
    y = y - 12

    -- Nur Tode mit Kondolenz-Eintrag oder alle anzeigen
    local deaths  = guildData.deaths
    local sorted  = {}
    for _, d in ipairs(deaths) do table.insert(sorted, d) end
    table.sort(sorted, function(a,b) return (a.timestamp or 0) > (b.timestamp or 0) end)

    if #sorted == 0 then
        _, y = self:MakeBookLine(parent, y, "|cff5A6840Keine Gefallenen verzeichnet.|r", 10)
        parent:SetHeight(math.abs(y) + 20)
        return
    end

    local myName = GDL.playerName or UnitName("player") or "Ich"

    for idx, d in ipairs(sorted) do
        local cc      = UI.ClassColorHex(d.class or d.classId)
        local name    = d.name or "Unbekannt"
        local zone    = d.zone or "Unbekannt"
        local dateStr = d.timestamp and date("%d.%m.%Y", d.timestamp) or ""
        local dKey    = name .. "_" .. (d.timestamp or 0)
        local msg     = konDB[dKey]

        local hasMsg  = msg and msg ~= ""
        local entryH  = hasMsg and 84 or 48

        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(318, entryH)
        row:SetPoint("TOPLEFT", 2, y)
        row:EnableMouse(true)

        -- Alternating background for better readability
        local bg = row:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        if idx % 2 == 0 then
            bg:SetColorTexture(0.12, 0.08, 0.03, 0.12)
        else
            bg:SetColorTexture(0.10, 0.06, 0.02, 0.0)
        end

        -- Kerze-Icon
        local ico = row:CreateTexture(nil, "OVERLAY")
        ico:SetTexture("Interface\\Minimap\\Tracking\\Innkeeper")
        ico:SetSize(16, 16)
        ico:SetPoint("TOPLEFT", 2, -3)
        ico:SetAlpha(hasMsg and 1.0 or 0.40)

        -- Name
        local nameFS = row:CreateFontString(nil, "OVERLAY")
        nameFS:SetFont("Fonts\\FRIZQT__.TTF", 14, "")
        nameFS:SetPoint("TOPLEFT", 20, -3)
        nameFS:SetText("|c" .. cc .. name .. "|r")

        -- Zone + Datum
        local subFS = row:CreateFontString(nil, "OVERLAY")
        subFS:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
        subFS:SetPoint("TOPLEFT", 20, -22)
        subFS:SetText("|cff3D2410" .. zone .. "  |cff3D2808" .. dateStr .. "|r")

        -- Kondolenz-Nachricht (falls vorhanden)
        if hasMsg then
            local line1 = row:CreateTexture(nil, "BORDER")
            line1:SetHeight(1)
            line1:SetPoint("TOPLEFT", 4, -37)
            line1:SetPoint("TOPRIGHT", -30, -37)
            line1:SetColorTexture(0.55, 0.40, 0.18, 0.20)

            local msgFS = row:CreateFontString(nil, "OVERLAY")
            msgFS:SetFont("Fonts\\MORPHEUS.TTF", 13, "")
            msgFS:SetPoint("TOPLEFT", 6, -40)
            msgFS:SetPoint("TOPRIGHT", -32, -40)
            msgFS:SetWordWrap(false)
            local short = msg:len() > 80 and (msg:sub(1,77) .. "...") or msg
            msgFS:SetText("|cff3A2005\"" .. short .. "\"|r")
        end

        -- Schreiben/Bearbeiten-Button
        local btn = CreateFrame("Button", nil, row)
        btn:SetSize(16, 16)
        btn:SetPoint("TOPRIGHT", -2, -3)
        btn:SetNormalTexture(hasMsg
            and "Interface\\Buttons\\UI-GuildButton-PublicNote-Up"
            or  "Interface\\Buttons\\UI-GuildButton-PublicNote-Up")
        btn:SetAlpha(hasMsg and 1.0 or 0.55)
        local dRef, dKeyRef = d, dKey
        btn:SetScript("OnClick", function()
            local existing = konDB[dKeyRef] or ""
            StaticPopupDialogs["GDL_KONDOLENZ"] = {
                text = GDL:T("Kondolenz fuer ") .. "|c" .. cc .. name .. "|r" .. GDL:T(" schreiben:"),
                button1 = GDL:T("Speichern"),
                button2 = GDL:T("Abbrechen"),
                hasEditBox = true, editBoxWidth = 280, maxLetters = 200,
                OnShow = function(self)
                    local eb = self.editBox or self.EditBox
                    if eb then eb:SetText(existing) end
                end,
                OnAccept = function(self)
                    local eb = self.editBox or self.EditBox
                    local text = eb and eb:GetText()
                    if text and text ~= "" then
                        konDB[dKeyRef] = text
                        local Sync = GDL:GetModule("Sync")
                        if Sync and Sync.BroadcastKondolenz then
                            Sync:BroadcastKondolenz(dKeyRef, text)
                        end
                        UI:RenderChapter("condolences")
                    elseif text == "" then
                        konDB[dKeyRef] = nil
                        UI:RenderChapter("condolences")
                    end
                end,
                timeout = 0, whileDead = true, preferredIndex = 3,
            }
            StaticPopup_Show("GDL_KONDOLENZ")
        end)
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:SetText(hasMsg and GDL:T("Kondolenz bearbeiten") or GDL:T("Kondolenz schreiben"), 0.8, 0.7, 0.5)
            GameTooltip:AddLine(hasMsg and GDL:T("Klick zum Aendern oder Loeschen (leer lassen).") or GDL:T("Hinterlasse eine persoenliche Abschiedsbotschaft."), 1, 1, 1, true)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)

        -- Hover
        local isEvenRow = (idx % 2 == 0)
        row:SetScript("OnEnter", function(self)
            bg:SetColorTexture(0.16, 0.10, 0.04, 0.28)
            if hasMsg then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:ClearLines()
                GameTooltip:SetText("|c" .. cc .. name .. "|r")
                GameTooltip:AddLine("Kondolenz:", 0.85, 0.75, 0.50)
                GameTooltip:AddLine(konDB[dKey], 0.95, 0.90, 0.75, true)
                GameTooltip:Show()
            end
        end)
        row:SetScript("OnLeave", function()
            if isEvenRow then
                bg:SetColorTexture(0.12, 0.08, 0.03, 0.12)
            else
                bg:SetColorTexture(0.10, 0.06, 0.02, 0.0)
            end
            GameTooltip:Hide()
        end)

        -- Separator
        local sep = row:CreateTexture(nil, "BORDER")
        sep:SetHeight(1)
        sep:SetPoint("BOTTOMLEFT", 6, 0)
        sep:SetPoint("BOTTOMRIGHT", -6, 0)
        sep:SetColorTexture(0.55, 0.40, 0.18, 0.30)

        y = y - entryH - 2
    end

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

-- â•â•â• XIII. ERRUNGENSCHAFTEN â•â•â•
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, "|cff3D2410-- Gilden-Rekorde --|r", 11, "|cff3D2410")
        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 = GDL:T("Toedlichster Tag"), value = deadliestDay .. " (" .. deadliestCount .. GDL:T(" Tode)")},
        }
        if firstDeath then
            table.insert(records, {icon = "Interface\\Icons\\INV_Misc_Book_09", label = GDL:T("Erster Eintrag"), value = (firstDeath.name or "?") .. GDL:T(" am ") .. date("%d.%m.%y", firstDeath.timestamp)})
        end
        if lastDeath then
            table.insert(records, {icon = "Interface\\Icons\\Spell_Shadow_Requiem", label = GDL:T("Letzter Eintrag"), value = (lastDeath.name or "?") .. GDL:T(" am ") .. date("%d.%m.%y", lastDeath.timestamp)})
        end
        for _, r in ipairs(records) do
            _, y = self:MakeBookRow(parent, y, r.icon, "|cff3D2410" .. r.label .. "|r", "|cff3D2410" .. r.value .. "|r", "")
        end
        y = y - 10
    end
    local deathsPerChar = GS:GetDeathsPerCharacter()
    _, y = self:MakeBookLine(parent, y, "|cff3D2410-- Tode pro Charakter (" .. #deathsPerChar .. ") --|r", 11, "|cff3D2410")
    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, "|cff3D2410" .. d.name .. "|r", "|cff3D2410Tode: " .. d.count .. "|r", lastInfo ~= "" and ("|cff5E3E1C" .. lastInfo .. "|r") or nil)
    end
    parent:SetHeight(math.abs(y) + 20)
end

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.32, 0.21, 0.07, 0.16)
    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("|cff3D2410" .. GDL:T("Power-Level:") .. "  |cff8A5E14" .. totalLevel .. "|r")
    local bannerSub = banner:CreateFontString(nil, "OVERLAY")
    bannerSub:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    bannerSub:SetPoint("RIGHT", -8, 0)
    bannerSub:SetTextColor(0.369, 0.243, 0.110)
    bannerSub:SetText(alive .. GDL:T(" 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,
        GDL:T("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 = GDL:T("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 = 0.08, g = 0.28, b = 0.10,
        },
        {
            title = GDL:T("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 = 0.08, g = 0.20, b = 0.32,
        },
        {
            title = GDL:T("Geschichte & Schicksal"),
            icon  = "Interface\\Icons\\INV_Misc_Book_09",
            ids   = {"g_old_30d","g_100_deaths","g_many_classes","g_wipe_3same"},
            r = 0.28, g = 0.14, b = 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("|cff1A5A24" .. 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("|cff3D2410" .. 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 "|cff1A5A24" or (pct >= 50 and "|cff8A5E14" or "|cff7A6448")
    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 "|cff1A5A24" or "|cff3D2410"
        nameStr:SetText(nc .. GDL:T(a.name) .. "|r")
    else
        nameStr:SetText("|cff7A6448" .. GDL:T(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.369, 0.243, 0.110)
    end
    descStr:SetText(GDL:T(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 "|cff1A5A24" or "|cff1A5A24"
        statusStr:SetText(sc .. "|TInterface\\RAIDFRAME\\ReadyCheck-Ready:12:12|t|r")
    else
        statusStr:SetText("|cff7A6448|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 "|cff1A5A24" or "|cff8A5E14"
        GameTooltip:SetText(tc .. GDL:T(a.name) .. "|r", 1, 1, 1, true)
        if a.desc and a.desc ~= "" then
            GameTooltip:AddLine(GDL:T(a.desc), 0.78, 0.68, 0.48, true)
        end
        if isGuild then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("|cffAADDAA" .. GDL:T("[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 |cff3A7030Freigeschaltet|r")
        else
            GameTooltip:AddLine("|TInterface\\RAIDFRAME\\ReadyCheck-NotReady:12:12|t |cffAA4444" .. GDL:T("Noch 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("|cff1A5A24Aktiv|r - " .. tierInfo.color .. tierInfo.label .. "|r  |cff7A6448bis " .. (expiry or "?") .. "|r")
    else
        statusTxt:SetText("|cff7A2C14" .. GDL:T("Nicht aktiv") .. "|r  |cff7A6448" .. GDL:T("Kein Supporter-Code hinterlegt") .. "|r")
    end
    y = y - 48

    _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Was ist Supporter+? --|r", 11, "|cff5E3E1C")
    y = y - 4
    _, y = self:MakeBookLine(parent, y, "Dieses Addon ist und bleibt kostenlos.", 10, "|cff5E3E1C")
    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, "|cff5E3E1C")
    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 = GDL:T("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 = "|cff4A2A10", S = "|cff5E3E1C", G = "|cff8A5E14"})[p.tier] or "|cff3D2410"
        local color = hasAccess and "|cff5E3E1C" or "|cff665544"
        pTxt:SetText(color .. p.text .. "  " .. tierColor .. "[" .. tierLabel .. "]|r")
        y = y - 22
    end
    y = y - 6

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

    _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Supporter-Code einloesen --|r", 11, "|cff5E3E1C")
    y = y - 4
    _, y = self:MakeBookLine(parent, y, "Discord-ID (aus der Bot-DM):", 10, "|cff5E3E1C")
    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, "|cff5E3E1C")
    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("|cff8A5E14" .. GDL:T("Code 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 GDL:T("Ungueltiger Code!")) .. "|r")
            end
            UI:RenderChapter("supporter")
        else
            GDL:Print("|cffFF8800" .. GDL:T("Bitte einen Code eingeben!") .. "|r")
        end
    end)
    y = y - 30

    _, y = self:MakeBookLine(parent, y, "|cff5E3E1C-- Patreon --|r", 11, "|cff5E3E1C")
    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("|cff5C2A40Patreon-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(GDL:T("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, "|cff7A6448Vielen Dank fuer deine Unterstuetzung!|r", 9, "|cff7A6448")

    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, "|cff5E3E1C")
        end
    else
        _, y = self:MakeBookLine(parent, y, "Keine Gilden-Regeln festgelegt.", 10, "|cff7A6448")
    end
    y = y - 12
    -- Nur GM/Officer darf Regeln bearbeiten
    local GL_mod = GDL:GetModule("GuildLeader")
    local canEditRules = GL_mod and GL_mod.CanEdit and GL_mod:CanEdit()
    if not canEditRules then
        parent:SetHeight(math.abs(y) + 20)
        return
    end
    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.38, 0.28, 0.14, 0.85)
    editBtn:SetBackdropBorderColor(0.60, 0.46, 0.24, 0.85)
    local label = editBtn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    label:SetAllPoints()
    label:SetText("|cff8A5E14" .. GDL:T("Regeln bearbeiten") .. "|r")
    label:SetJustifyH("CENTER")
    editBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_EDIT_RULES"] = {
            text = GDL:T("Gilden-Regeln eingeben:"),
            button1 = GDL:T("Speichern"),
            button2 = GDL:T("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.52, 0.38, 0.18, 1.0) end)
    editBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.38, 0.28, 0.14, 0.85) 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 = "|cff3D2410"
            if e.eventType == "raid" then typeColor = "|cffFF4444"
            elseif e.eventType == "pvp" then typeColor = "|cff7A2C14"
            elseif e.eventType == "social" then typeColor = "|cff1A5A24" 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(GDL:T("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.32, 0.24, 0.12, 0.72)
                    btn:SetBackdropBorderColor(0.52, 0.40, 0.22, 0.70)
                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.32, 0.24, 0.12, 0.72)
                    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, "|cff1A5A24" .. #counts.yes .. " Zusagen|r") end
            if #counts.maybe > 0 then table.insert(parts, "|cff5A4400" .. #counts.maybe .. " Vielleicht|r") end
            if #counts.no > 0 then table.insert(parts, "|cff7A2C14" .. #counts.no .. " Absagen|r") end
            if #parts > 0 then
                _, y = self:MakeBookLine(parent, y, table.concat(parts, "  |  "), 8, "|cff7A6448")
            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, "|cff1A5A24Dabei: " .. table.concat(names, ", ") .. "|r", 7, "|cff1A5A24")
            end
            y = y - 8
        end
    else
        _, y = self:MakeBookLine(parent, y, "Keine Events geplant.", 10, "|cff7A6448")
    end
    y = y - 12
    -- Nur Officer+ darf Events erstellen
    local GL_cal = GDL:GetModule("GuildLeader")
    if not (GL_cal and GL_cal.CanEdit and GL_cal:CanEdit()) then
        parent:SetHeight(math.abs(y) + 20)
        return
    end
    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.38, 0.28, 0.14, 0.85)
    addBtn:SetBackdropBorderColor(0.60, 0.46, 0.24, 0.85)
    local label = addBtn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    label:SetAllPoints()
    label:SetText("|cff8A5E14" .. GDL:T("+ Neues Event") .. "|r")
    label:SetJustifyH("CENTER")
    addBtn:SetScript("OnClick", function()
        local Cal = GDL:GetModule("Calendar")
        if Cal and Cal.ShowAddEventDialog then
            Cal:ShowAddEventDialog()
        end
    end)
    addBtn:SetScript("OnEnter", function(self) self:SetBackdropColor(0.52, 0.38, 0.18, 1.0) end)
    addBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.38, 0.28, 0.14, 0.85) 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 = UI.CleanKillerName(d.killerName or d.killedBy or d.source or "")
        if not k 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, "|cff5E3E1C" .. GDL:T("Top-Killer der Gilde (") .. total .. GDL:T(" Tode gesamt):") .. "|r", 11, "|cff5E3E1C")
    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, "|cff5E3E1C" .. GDL:T("... und ") .. (#sorted - 15) .. GDL:T(" weitere Gegner") .. "|r", 9, "|cff5E3E1C")
                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("|cff7A2C14" .. i .. ".|r")
            else
                rankLabel:SetText("|cff5E3E1C" .. 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("|cff5E3E1C" .. 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.50, 0.22, 0.10, 0.45)
            local countLabel = row:CreateFontString(nil, "OVERLAY")
            countLabel:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
            countLabel:SetPoint("LEFT", 205, 0)
            countLabel:SetText("|cff7A2C14" .. kv[2] .. "|r |cff5E3E1C(" .. pct .. ")|r")
            y = y - 18
        end
    else
        _, y = self:MakeBookLine(parent, y, "|cff5E3E1CNoch keine benannten Gegner erfasst.|r", 10, "|cff5E3E1C")
    end
    if envDeaths > 0 then
        y = y - 14
        local envPct = total > 0 and string.format("%.0f%%", envDeaths / total * 100) or "0%"

        -- Umgebungsschaden-Header mit Icon
        local envHdr = CreateFrame("Frame", nil, parent)
        envHdr:SetSize(310, 28)
        envHdr:SetPoint("TOPLEFT", 4, y)
        local envBg = envHdr:CreateTexture(nil, "BACKGROUND")
        envBg:SetAllPoints()
        envBg:SetColorTexture(0.10, 0.05, 0.02, 0.35)
        local envIcon = envHdr:CreateTexture(nil, "ARTWORK")
        envIcon:SetTexture("Interface\\Icons\\Spell_Fire_Incinerate")
        envIcon:SetSize(20, 20)
        envIcon:SetPoint("LEFT", 4, 0)
        local envLabel = envHdr:CreateFontString(nil, "OVERLAY")
        envLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "")
        envLabel:SetPoint("LEFT", 28, 3)
        envLabel:SetText("|cff7A3810Umgebungsschaden|r")
        envLabel:SetShadowOffset(1, -1); envLabel:SetShadowColor(0, 0, 0, 0.15)
        local envSub = envHdr:CreateFontString(nil, "OVERLAY")
        envSub:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
        envSub:SetPoint("LEFT", 28, -7)
        envSub:SetText("|cff5E3E1C" .. GDL:T("Fallschaden, Ertrinken, Lava, Feuer ...") .. "|r")
        local envCount = envHdr:CreateFontString(nil, "OVERLAY")
        envCount:SetFont("Fonts\\MORPHEUS.TTF", 14, "")
        envCount:SetPoint("RIGHT", -6, 0)
        envCount:SetText("|cff7A2C14" .. envDeaths .. "|r")
        envCount:SetShadowOffset(1, -1); envCount:SetShadowColor(0, 0, 0, 0.15)
        y = y - 32

        -- Visueller Anteil-Balken
        local barRow = CreateFrame("Frame", nil, parent)
        barRow:SetSize(310, 16)
        barRow:SetPoint("TOPLEFT", 4, y)
        local barBg = barRow:CreateTexture(nil, "BACKGROUND")
        barBg:SetSize(200, 10)
        barBg:SetPoint("LEFT", 4, 0)
        barBg:SetColorTexture(0.15, 0.08, 0.04, 0.40)
        local barFill = barRow:CreateTexture(nil, "ARTWORK")
        local fillW = math.max(4, (envDeaths / math.max(1, total)) * 200)
        barFill:SetSize(fillW, 10)
        barFill:SetPoint("LEFT", 4, 0)
        barFill:SetColorTexture(0.52, 0.22, 0.10, 0.65)
        local barPctFS = barRow:CreateFontString(nil, "OVERLAY")
        barPctFS:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        barPctFS:SetPoint("LEFT", 210, 0)
        barPctFS:SetText("|cff7A2C14" .. envPct .. " " .. GDL:T("aller Tode") .. "|r")
        y = y - 20
    end
    parent:SetHeight(math.abs(y) + 20)
end


-- ── SCHWARZES BRETT ────────────────────────────────────────────────────────
function UI:RenderBulletin()
    local parent = self.mainFrame.scrollChild
    local y      = 0

    GuildDeathLogDB              = GuildDeathLogDB or {}
    GuildDeathLogDB.bulletinPosts = GuildDeathLogDB.bulletinPosts or {}
    local rawPosts = GuildDeathLogDB.bulletinPosts

    local myName  = GDL.playerName or UnitName("player") or "?"
    local myGuild = GDL.currentGuildName
    local MAX     = 15

    -- Gilden-Isolation: ungetaggte Alt-Beitraege migrieren, dann nur Beitraege
    -- der AKTUELLEN Gilde anzeigen. Char ohne/andere Gilde -> nichts.
    if myGuild then
        for _, p in ipairs(rawPosts) do
            if not p.guild then p.guild = myGuild end
        end
    end
    local posts = {}
    if myGuild then
        for _, p in ipairs(rawPosts) do
            if p.guild == myGuild then posts[#posts + 1] = p end
        end
    end

    -- ── Header ──────────────────────────────────────────────────────
    local hdrFS = parent:CreateFontString(nil, "OVERLAY")
    hdrFS:SetFont("Fonts\\MORPHEUS.TTF", 12, "")
    hdrFS:SetPoint("TOPLEFT", 10, y - 2)
    hdrFS:SetPoint("TOPRIGHT", -10, y - 2)
    hdrFS:SetText("|cff3D2410" .. GDL:T("Schwarzes Brett") .. "|r")
    hdrFS:SetJustifyH("CENTER")
    y = y - 20

    local subFS = parent:CreateFontString(nil, "OVERLAY")
    subFS:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    subFS:SetPoint("TOPLEFT", 10, y)
    subFS:SetPoint("TOPRIGHT", -10, y)
    subFS:SetText("|cff6A5030" .. GDL:T("Ankuendigungen, Hinweise und Notizen fuer die Gilde") .. "|r")
    subFS:SetJustifyH("CENTER")
    y = y - 18

    local divT = parent:CreateTexture(nil, "ARTWORK")
    divT:SetTexture("Interface\\ACHIEVEMENTFRAME\\UI-Achievement-HorizontalShadow")
    divT:SetSize(300, 5)
    divT:SetPoint("TOPLEFT", 10, y)
    divT:SetVertexColor(0.55, 0.40, 0.18, 0.50)
    y = y - 12

    -- ── Neuer Beitrag ────────────────────────────────────────────────
    local addBtn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    addBtn:SetSize(290, 22)
    addBtn:SetPoint("TOPLEFT", 15, y)
    addBtn:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8x8",
        edgeFile="Interface\\Tooltips\\UI-Tooltip-Border", edgeSize=6,
        insets={left=2,right=2,top=2,bottom=2} })
    addBtn:SetBackdropColor(0.38, 0.28, 0.14, 0.82)
    addBtn:SetBackdropBorderColor(0.55, 0.40, 0.18, 0.60)
    local addLbl = addBtn:CreateFontString(nil, "OVERLAY")
    addLbl:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    addLbl:SetAllPoints(); addLbl:SetJustifyH("CENTER")
    addLbl:SetText("|cff8A5E14" .. GDL:T("+ Neuen Beitrag anpinnen") .. "|r")
    addBtn:SetScript("OnClick", function()
        StaticPopupDialogs["GDL_BULLETIN_NEW"] = {
            text = GDL:T("Beitrag ans Schwarze Brett pinnen\n(max. 180 Zeichen):"),
            button1 = GDL:T("Anpinnen"), button2 = GDL:T("Abbrechen"),
            hasEditBox = true, editBoxWidth = 280, maxLetters = 180,
            OnAccept = function(self)
                local eb = self.editBox or self.EditBox
                local text = eb and eb:GetText()
                if text and text ~= "" then
                    local post = { author=myName, text=text, timestamp=time(),
                                   tag="info", guild=GDL.currentGuildName }
                    table.insert(rawPosts, 1, post)
                    while #rawPosts > MAX do table.remove(rawPosts) end
                    -- Sync an Gilde senden
                    local Sync = GDL:GetModule("Sync")
                    if Sync and Sync.BroadcastBulletin then
                        Sync:BroadcastBulletin(post)
                    end
                    UI:RenderChapter("bulletin")
                end
            end,
            timeout=0, whileDead=true, preferredIndex=3,
        }
        StaticPopup_Show("GDL_BULLETIN_NEW")
    end)
    addBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(0.50, 0.38, 0.18, 0.95)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:SetText(GDL:T("Beitrag anpinnen"), 0.8, 0.7, 0.5)
        GameTooltip:AddLine(GDL:T("Schreibe eine Ankuendigung fuer die Gilde."), 1, 1, 1, true)
        GameTooltip:Show()
    end)
    addBtn:SetScript("OnLeave", function(self) self:SetBackdropColor(0.38, 0.28, 0.14, 0.82) GameTooltip:Hide() end)
    y = y - 28

    -- ── Beitraege ────────────────────────────────────────────────────
    if #posts == 0 then
        _, y = self:MakeBookLine(parent, y, "|cff6A5840Das Brett ist leer. Sei der Erste.|r", 9)
        parent:SetHeight(math.abs(y) + 20)
        return
    end

    -- Tag-Farben
    local TAG_COLOR = { info="|cff4A7A3A", warn="|cffBB5500", search="|cff2255AA", misc="|cff5A4030" }
    local TAG_LABEL = { info="Info", warn="Warnung", search="Suche", misc="Sonstiges" }

    for idx, post in ipairs(posts) do
        local text    = post.text or ""
        local author  = post.author or "?"
        local ts      = post.timestamp and date("%d.%m. %H:%M", post.timestamp) or ""
        local tag     = post.tag or "info"
        local tc      = TAG_COLOR[tag] or TAG_COLOR.info
        local tl      = TAG_LABEL[tag] or "Info"
        local isOwn   = (author == myName)

        -- Zeilenumbruch-Schaetzung (jede 45 Zeichen = 1 Zeile)
        local lines_est = math.ceil(text:len() / 45)
        local entryH  = 30 + (lines_est * 12)

        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(300, entryH)
        row:SetPoint("TOPLEFT", 10, y)
        row:EnableMouse(true)

        -- Hintergrund
        local bg = row:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetColorTexture(0.14, 0.09, 0.04, 0.30)

        -- Randlinie links (Tag-Farbe)
        local accent = row:CreateTexture(nil, "BORDER")
        accent:SetWidth(3)
        accent:SetPoint("TOPLEFT", 0, 0)
        accent:SetPoint("BOTTOMLEFT", 0, 0)
        if tag == "warn" then
            accent:SetColorTexture(0.80, 0.35, 0.05, 0.90)
        elseif tag == "search" then
            accent:SetColorTexture(0.15, 0.40, 0.80, 0.90)
        elseif tag == "misc" then
            accent:SetColorTexture(0.40, 0.30, 0.15, 0.90)
        else
            accent:SetColorTexture(0.25, 0.55, 0.20, 0.90)
        end

        -- Tag-Label + Autor + Datum
        local metaFS = row:CreateFontString(nil, "OVERLAY")
        metaFS:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
        metaFS:SetPoint("TOPLEFT", 7, -3)
        metaFS:SetText(tc .. "[" .. tl .. "]|r  |cff3D2410" .. author .. "|r  |cff7A6448" .. ts .. "|r")

        -- Beitragstext
        local textFS = row:CreateFontString(nil, "OVERLAY")
        textFS:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
        textFS:SetPoint("TOPLEFT", 7, -14)
        textFS:SetPoint("TOPRIGHT", -26, -14)
        textFS:SetWordWrap(true)
        textFS:SetJustifyH("LEFT")
        textFS:SetText("|cff2A1606" .. text .. "|r")

        -- Loeschen-Button (nur eigene Beitraege)
        if isOwn then
            local delBtn = CreateFrame("Button", nil, row)
            delBtn:SetSize(14, 14)
            delBtn:SetPoint("TOPRIGHT", -2, -2)
            delBtn:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
            delBtn:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
            local postRef = post
            delBtn:SetScript("OnClick", function()
                -- aus der ECHTEN Liste per Identitaet entfernen (nicht aus der
                -- gefilterten Anzeige-Kopie -> sonst persistiert es nicht)
                for i = #rawPosts, 1, -1 do
                    if rawPosts[i] == postRef then table.remove(rawPosts, i); break end
                end
                UI:RenderChapter("bulletin")
            end)
            delBtn:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_TOP")
                GameTooltip:SetText("Beitrag loeschen", 1, 0.3, 0.3)
                GameTooltip:Show()
            end)
            delBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        end

        -- Separator
        local sep = row:CreateTexture(nil, "BORDER")
        sep:SetHeight(1)
        sep:SetPoint("BOTTOMLEFT", 4, 0)
        sep:SetPoint("BOTTOMRIGHT", -4, 0)
        sep:SetColorTexture(0.55, 0.40, 0.18, 0.18)

        y = y - entryH - 3
    end

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

