-- ══════════════════════════════════════════════════════════════
-- UI SUB-MODULE: Toast Notifications
-- Achievement, Milestone, Calendar Toasts
-- Nutzt dieselbe UI-Table via addon._UI
-- ══════════════════════════════════════════════════════════════

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

-- Local refs
local GDL_ScaleLerp = UI.ScaleLerp

-- Calendar toast state
UI._calToastQueue = {}
UI._calToastActive = false

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    self.achToastFrame = t
end

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

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

    -- â”€â”€ Outer cinematic dark background â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local bgOuter = t:CreateTexture(nil, "BACKGROUND")
    bgOuter:SetAllPoints()
    bgOuter:SetTexture("Interface\\AchievementFrame\\UI-Achievement-Parchment-Horizontal")
    bgOuter:SetVertexColor(0.96, 0.90, 0.78)

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

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

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

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

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

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

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

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

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

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

    local iconBg = iconPunch:CreateTexture(nil, "BACKGROUND")
    iconBg:SetAllPoints()
    iconBg:SetColorTexture(0.15, 0.105, 0.05, 0.85)

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

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

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

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

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

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

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

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

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

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

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

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

    self.milestoneToastFrame = t
end

function UI:ShowMilestoneToast(milestone, charName, force)
    -- Persistenter Dedup-Guard: jeder Meilenstein-Toast nur EINMAL pro Charakter,
    -- egal welches Modul ihn ausloest (Milestones, SurvivalStreak, ...).
    -- force=true umgeht das (z.B. Vorschau/Test).
    if not force and GuildDeathLogDB then
        local Mile = GDL:GetModule("Milestones")
        local ck = (Mile and Mile.GetCharacterKey and Mile:GetCharacterKey())
                   or UnitGUID("player") or "?"
        local key = ck .. "::" .. tostring(milestone and (milestone.id or milestone.name) or "?")
        GuildDeathLogDB.toastShown = GuildDeathLogDB.toastShown or {}
        if GuildDeathLogDB.toastShown[key] then return end
        GuildDeathLogDB.toastShown[key] = true
    end

    if not self.milestoneToastFrame then self:BuildMilestoneToastFrame() end
    local t = self.milestoneToastFrame

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

    -- â”€â”€ Fill content â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    t.icon:SetTexture(milestone.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
    t.nameStr:SetText(milestone.name or milestone.title or GDL:T("Unbekannt"))
    t.descStr:SetText(milestone.desc or milestone.text or "")
    t.charStr:SetText(charName and ("|cff7A6250" .. charName .. "|r") or "")

    -- â”€â”€ Category color palette â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    local CAT = {
        -- Gedaempfte, pergamenttaugliche Toene (statt grell)
        level      = {0.85, 0.65, 0.10},  -- Gold
        survival   = {0.40, 0.60, 0.34},  -- Moosgruen
        dungeon    = {0.44, 0.56, 0.72},  -- Stahlblau gedaempft
        raid       = {0.76, 0.44, 0.22},  -- Gebrannte Sienna
        kills      = {0.66, 0.36, 0.26},  -- Backstein gedaempft
        profession = {0.58, 0.62, 0.36},  -- Oliv gedaempft
        pvp        = {0.72, 0.34, 0.30},  -- Rot gedaempft
        wealth     = {0.88, 0.72, 0.18},  -- Gelb-Gold
        quests     = {0.56, 0.62, 0.72},  -- Schiefer-Blau
        social     = {0.54, 0.46, 0.66},  -- Staubviolett
        meta       = {0.85, 0.55, 0.10},  -- Warm-Orange
        secret     = {0.56, 0.38, 0.56},  -- Pflaume gedaempft
    }
    local c = CAT[milestone.category] or CAT.level
    t.topBar:SetColorTexture(c[1], c[2], c[3], 1)
    t.leftAccent:SetColorTexture(c[1], c[2], c[3], 0.88)
    t.sealGlow:SetVertexColor(c[1], c[2], c[3])
    t.nameStr:SetTextColor(c[1]*0.46, c[2]*0.40, c[3]*0.30)

    local catLabels = {
        level      = "CHARAKTER-LEVEL",
        survival   = "UEBERLEBEN",
        dungeon    = "DUNGEON-MEILENSTEIN",
        raid       = "RAID-MEILENSTEIN",
        kills      = "KAMPF-MEILENSTEIN",
        profession = "BERUFE-MEILENSTEIN",
        pvp        = "PVP-MEILENSTEIN",
        wealth     = "REICHTUM",
        quests     = "QUEST-MEILENSTEIN",
        social     = "SOZIAL",
        meta       = "MEISTERSCHAFT",
        secret     = "GEHEIMNIS",
    }
    t.badge:SetText("|cff5A3A0C" .. (catLabels[milestone.category] or "MEILENSTEIN") .. "|r")

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

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

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

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

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

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

    PlaySound(888, "Master")
end

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



-- ══════════════════════════════════════════════════════════════
-- CALENDAR TOASTS  (v9.7.0 — Pergament-Design + RSVP im Toast)
-- ══════════════════════════════════════════════════════════════

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

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

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

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

    -- ── Badge & Farbschema ───────────────────────────────────
    if isDelete then
        if isOwn then
            t.badge:SetText("|cffAA4422[" .. GDL:T("Event geloescht") .. "]|r")
        else
            t.badge:SetText("|cffAA4422[" .. GDL:T("Event geloescht von ") .. sender .. "]|r")
        end
        t.headerBar:SetColorTexture(0.45, 0.18, 0.08, 1)
        t.icon:SetDesaturated(true)
        t.icon:SetAlpha(0.55)
        -- RSVP verstecken bei Delete
        t.rsvpFrame:Hide()
        t:SetHeight(88)
    else
        if isOwn then
            t.badge:SetText("|cff8A6420[" .. GDL:T("Neues Gilden-Event erstellt") .. "]|r")
        else
            t.badge:SetText("|cff8A6420[" .. GDL:T("Neues Gilden-Event von ") .. sender .. "]|r")
        end
        t.headerBar:SetColorTexture(0.55, 0.38, 0.12, 1)
        t.icon:SetDesaturated(false)
        t.icon:SetAlpha(1.0)
        -- RSVP anzeigen bei neuen Events (nur kommende Events)
        local now = time()
        if event.timestamp and event.timestamp > now then
            t.rsvpFrame:Show()
            t:SetHeight(118)
            -- RSVP-Buttons aktualisieren
            UI._calToastEventId = event.id
            local Calendar = GDL:GetModule("Calendar")
            local myResp = Calendar and Calendar:GetMyResponse(event.id) or nil
            UI:UpdateCalToastRSVP(myResp)
        else
            t.rsvpFrame:Hide()
            t:SetHeight(88)
        end
    end

    -- ── Event-Name ───────────────────────────────────────────
    t.titleStr:SetText("|cff2A1806" .. (event.name or GDL:T("Unbekanntes Event")) .. "|r")

    -- ── Datum/Uhrzeit ────────────────────────────────────────
    if event.timestamp and event.timestamp > 0 then
        t.descStr:SetText("|cff4A3A28" .. date("%d.%m.%Y  %H:%M", event.timestamp) .. GDL:T(" Uhr") .. "|r")
    else
        t.descStr:SetText("")
    end

    -- ── Position ─────────────────────────────────────────────
    t:ClearAllPoints()
    t:SetPoint("RIGHT", UIParent, "RIGHT", -12, GetScreenHeight() * 0.02)

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

    t:SetAlpha(0)
    t:Show()

    if not t.slideAnim then
        t.slideAnim = t:CreateAnimationGroup()
        local fade = t.slideAnim:CreateAnimation("Alpha")
        fade:SetFromAlpha(0); fade:SetToAlpha(1)
        fade:SetDuration(0.28); fade:SetSmoothing("OUT")
        local slide = t.slideAnim:CreateAnimation("Translation")
        slide:SetOffset(90, 0); slide:SetDuration(0.0)
        local slide2 = t.slideAnim:CreateAnimation("Translation")
        slide2:SetOffset(-90, 0); slide2:SetDuration(0.28); slide2:SetSmoothing("OUT")
        t.slideAnim:SetScript("OnFinished", function() t:SetAlpha(1) end)
    end
    t.slideAnim:Play()

    -- Bell FX
    C_Timer.After(0.30, function()
        if t:IsShown() then
            local AnimFX = GDL:GetModule("AnimFX")
            if AnimFX then AnimFX.CalendarBell(t, data.isDelete) end
        end
    end)

    -- Auto-Hide nach 12 Sekunden (etwas mehr Zeit wegen RSVP-Buttons)
    t.hideTimer = C_Timer.NewTimer(12, function()
        if not t.exitAnim then
            t.exitAnim = t:CreateAnimationGroup()
            local fadeOut = t.exitAnim:CreateAnimation("Alpha")
            fadeOut:SetFromAlpha(1); fadeOut:SetToAlpha(0)
            fadeOut:SetDuration(0.35); fadeOut:SetSmoothing("IN")
            t.exitAnim:SetScript("OnFinished", function()
                t:Hide(); t:SetAlpha(1)
                UI._calToastActive = false
                C_Timer.After(0.1, function() UI:NextCalToast() end)
            end)
        end
        t.exitAnim:Play()
    end)

    -- Klick auf den oberen Bereich → Kalender öffnen
    t.clickArea:SetScript("OnMouseUp", function()
        if t.hideTimer then t.hideTimer:Cancel() end
        t:Hide()
        UI._calToastActive = false
        C_Timer.After(0.1, function() UI:NextCalToast() end)
        local UI2 = GDL:GetModule("UI")
        if UI2 then
            UI2:ShowBook()
            C_Timer.After(0.45, function()
                UI2:RenderChapter("calendar")
            end)
        end
    end)
end

-- RSVP-Button-Zustände aktualisieren
function UI:UpdateCalToastRSVP(currentResp)
    local t = self.calToastFrame
    if not t then return end
    -- Ja
    if currentResp == "yes" then
        t.rsvpYes.bg:SetColorTexture(0.15, 0.45, 0.12, 0.95)
        t.rsvpYes.label:SetText("|cffAAFF88Ja|r")
    else
        t.rsvpYes.bg:SetColorTexture(0.20, 0.20, 0.14, 0.80)
        t.rsvpYes.label:SetText("|cff8A9A70Ja|r")
    end
    -- Vielleicht
    if currentResp == "maybe" then
        t.rsvpMaybe.bg:SetColorTexture(0.45, 0.38, 0.08, 0.95)
        t.rsvpMaybe.label:SetText("|cffFFE870Vllt|r")
    else
        t.rsvpMaybe.bg:SetColorTexture(0.20, 0.20, 0.14, 0.80)
        t.rsvpMaybe.label:SetText("|cff9A9060Vllt|r")
    end
    -- Nein
    if currentResp == "no" then
        t.rsvpNo.bg:SetColorTexture(0.45, 0.10, 0.06, 0.95)
        t.rsvpNo.label:SetText("|cffFF8870Nein|r")
    else
        t.rsvpNo.bg:SetColorTexture(0.20, 0.20, 0.14, 0.80)
        t.rsvpNo.label:SetText("|cff9A6860Nein|r")
    end
end

-- Einen einzelnen RSVP-Button bauen
local function BuildRSVPBtn(parent, w, h)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(w, h)
    btn:SetBackdrop({
        bgFile   = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 8,
        insets   = {left=2, right=2, top=2, bottom=2},
    })
    btn:SetBackdropBorderColor(0.50, 0.40, 0.22, 0.70)

    local bg = btn:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.20, 0.20, 0.14, 0.80)
    btn.bg = bg

    local label = btn:CreateFontString(nil, "OVERLAY")
    label:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    label:SetPoint("CENTER")
    btn.label = label

    return btn
end

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

    -- ── Hintergrund: Pergament ───────────────────────────────
    t:SetBackdrop({
        bgFile   = "Interface\\ACHIEVEMENTFRAME\\UI-Achievement-Parchment-Horizontal",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
        edgeSize = 20,
        insets   = {left=4, right=4, top=4, bottom=4},
    })
    t:SetBackdropColor(1, 1, 1, 0.97)
    t:SetBackdropBorderColor(0.65, 0.50, 0.28, 1)

    -- ── Schmaler farbiger Header-Balken oben ─────────────────
    local headerBar = t:CreateTexture(nil, "ARTWORK")
    headerBar:SetPoint("TOPLEFT",  6,  -5)
    headerBar:SetPoint("TOPRIGHT", -6, -5)
    headerBar:SetHeight(3)
    headerBar:SetColorTexture(0.55, 0.38, 0.12, 1)
    t.headerBar = headerBar

    -- ── Icon (mit leichtem Rahmen) ───────────────────────────
    local iconBorder = t:CreateTexture(nil, "BORDER")
    iconBorder:SetSize(42, 42)
    iconBorder:SetPoint("TOPLEFT", 12, -14)
    iconBorder:SetColorTexture(0.35, 0.28, 0.14, 0.60)

    local icon = t:CreateTexture(nil, "ARTWORK")
    icon:SetSize(38, 38)
    icon:SetPoint("TOPLEFT", 14, -16)
    t.icon = icon

    -- ── Klickbarer Bereich (nur obere Zone, ohne RSVP) ───────
    local clickArea = CreateFrame("Button", nil, t)
    clickArea:SetPoint("TOPLEFT",  0,  0)
    clickArea:SetPoint("TOPRIGHT", 0,  0)
    clickArea:SetHeight(88)
    t.clickArea = clickArea

    -- ── Badge (Typ-Zeile) ────────────────────────────────────
    local badge = t:CreateFontString(nil, "OVERLAY")
    badge:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    badge:SetPoint("TOPLEFT", 58, -13)
    badge:SetWidth(290)
    badge:SetJustifyH("LEFT")
    t.badge = badge

    -- ── Event-Name ───────────────────────────────────────────
    local titleStr = t:CreateFontString(nil, "OVERLAY")
    titleStr:SetFont("Fonts\\MORPHEUS.TTF", 13, "")
    titleStr:SetPoint("TOPLEFT", 58, -25)
    titleStr:SetWidth(285)
    titleStr:SetJustifyH("LEFT")
    titleStr:SetShadowOffset(1, -1)
    titleStr:SetShadowColor(0, 0, 0, 0.12)
    t.titleStr = titleStr

    -- ── Datum/Uhrzeit ────────────────────────────────────────
    local descStr = t:CreateFontString(nil, "OVERLAY")
    descStr:SetFont("Fonts\\FRIZQT__.TTF", 9, "")
    descStr:SetPoint("TOPLEFT", 58, -43)
    descStr:SetWidth(285)
    descStr:SetJustifyH("LEFT")
    t.descStr = descStr

    -- ── "Klicken zum Öffnen" Hint ────────────────────────────
    local hint = t:CreateFontString(nil, "OVERLAY")
    hint:SetFont("Fonts\\FRIZQT__.TTF", 7, "")
    hint:SetPoint("BOTTOMRIGHT", -10, 33)
    hint:SetTextColor(0.55, 0.42, 0.22, 0.70)
    hint:SetText("Klicken zum Oeffnen")

    -- ── Trennlinie vor RSVP ──────────────────────────────────
    local divLine = t:CreateTexture(nil, "ARTWORK")
    divLine:SetPoint("BOTTOMLEFT",  8,  32)
    divLine:SetPoint("BOTTOMRIGHT",-8,  32)
    divLine:SetHeight(1)
    divLine:SetColorTexture(0.55, 0.42, 0.22, 0.40)

    -- ── RSVP-Bereich ─────────────────────────────────────────
    local rsvpFrame = CreateFrame("Frame", nil, t)
    rsvpFrame:SetPoint("BOTTOMLEFT",  8, 6)
    rsvpFrame:SetPoint("BOTTOMRIGHT",-8, 6)
    rsvpFrame:SetHeight(24)
    t.rsvpFrame = rsvpFrame

    local rsvpLabel = rsvpFrame:CreateFontString(nil, "OVERLAY")
    rsvpLabel:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    rsvpLabel:SetPoint("LEFT", 0, 0)
    rsvpLabel:SetText("|cff6A5030Teilnahme:|r")

    local btnW, btnH = 72, 20

    local yesBtn = BuildRSVPBtn(rsvpFrame, btnW, btnH)
    yesBtn:SetPoint("LEFT", rsvpLabel, "RIGHT", 6, 0)
    yesBtn:SetScript("OnClick", function()
        local Calendar = GDL:GetModule("Calendar")
        if Calendar and UI._calToastEventId then
            Calendar:RespondToEvent(UI._calToastEventId, "yes")
            UI:UpdateCalToastRSVP("yes")
        end
    end)
    yesBtn:SetScript("OnEnter", function() yesBtn.bg:SetColorTexture(0.18, 0.48, 0.14, 0.95) end)
    yesBtn:SetScript("OnLeave", function()
        local Calendar = GDL:GetModule("Calendar")
        local resp = Calendar and UI._calToastEventId and Calendar:GetMyResponse(UI._calToastEventId) or nil
        yesBtn.bg:SetColorTexture(resp == "yes" and 0.15 or 0.20, resp == "yes" and 0.45 or 0.20, 0.12, resp == "yes" and 0.95 or 0.80)
    end)
    t.rsvpYes = yesBtn

    local maybeBtn = BuildRSVPBtn(rsvpFrame, btnW, btnH)
    maybeBtn:SetPoint("LEFT", yesBtn, "RIGHT", 4, 0)
    maybeBtn:SetScript("OnClick", function()
        local Calendar = GDL:GetModule("Calendar")
        if Calendar and UI._calToastEventId then
            Calendar:RespondToEvent(UI._calToastEventId, "maybe")
            UI:UpdateCalToastRSVP("maybe")
        end
    end)
    maybeBtn:SetScript("OnEnter", function() maybeBtn.bg:SetColorTexture(0.48, 0.40, 0.08, 0.95) end)
    maybeBtn:SetScript("OnLeave", function()
        local Calendar = GDL:GetModule("Calendar")
        local resp = Calendar and UI._calToastEventId and Calendar:GetMyResponse(UI._calToastEventId) or nil
        maybeBtn.bg:SetColorTexture(0.20, 0.20, 0.14, resp == "maybe" and 0.95 or 0.80)
    end)
    t.rsvpMaybe = maybeBtn

    local noBtn = BuildRSVPBtn(rsvpFrame, btnW, btnH)
    noBtn:SetPoint("LEFT", maybeBtn, "RIGHT", 4, 0)
    noBtn:SetScript("OnClick", function()
        local Calendar = GDL:GetModule("Calendar")
        if Calendar and UI._calToastEventId then
            Calendar:RespondToEvent(UI._calToastEventId, "no")
            UI:UpdateCalToastRSVP("no")
        end
    end)
    noBtn:SetScript("OnEnter", function() noBtn.bg:SetColorTexture(0.50, 0.10, 0.06, 0.95) end)
    noBtn:SetScript("OnLeave", function()
        local Calendar = GDL:GetModule("Calendar")
        local resp = Calendar and UI._calToastEventId and Calendar:GetMyResponse(UI._calToastEventId) or nil
        noBtn.bg:SetColorTexture(0.20, 0.20, 0.14, resp == "no" and 0.95 or 0.80)
    end)
    t.rsvpNo = noBtn

    self.calToastFrame = t
end

