local _, ns = ...

------------------------------------------------------------
-- Progression Dashboard: card-based single-page overview
------------------------------------------------------------

-- Layout constants
local FRAME_W, FRAME_H = ns:GetAppFrameSize()
local PAD         = 14
local CARD_GAP    = 10
local SECTION_GAP = 14
local uiScale     = ns:GetUIScale()
local CARD_H      = math.floor(100 * uiScale)
local TITLE_H     = 22
local CONTENT_TOP = -34
local CW          = FRAME_W - PAD * 2                        -- 932
local COLS        = 4
local CARD_W      = math.floor((CW - (COLS - 1) * CARD_GAP) / COLS) -- ~225

-- Crest colour table (hex for text, rgb for borders/stripes)
local CC = {
    Adventurer = { hex = "ff1eff00", r = 0.12, g = 1.00, b = 0.00 },
    Veteran    = { hex = "ff0070dd", r = 0.00, g = 0.44, b = 0.87 },
    Champion   = { hex = "ffa335ee", r = 0.64, g = 0.21, b = 0.93 },
    Hero       = { hex = "ffff8000", r = 1.00, g = 0.50, b = 0.00 },
    Myth       = { hex = "ffff0000", r = 1.00, g = 0.00, b = 0.00 },
}

------------------------------------------------------------
-- Main frame  (same name / namespace hook as before)
------------------------------------------------------------
local f = CreateFrame("Frame", "YippYappProgressionFrame", UIParent, "BackdropTemplate")
f:SetSize(FRAME_W, FRAME_H)
f:SetPoint("CENTER")
f:SetMovable(true)
f:EnableMouse(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", f.StartMoving)
f:SetScript("OnDragStop", f.StopMovingOrSizing)
f:SetClampedToScreen(true)
f:SetFrameStrata("HIGH")
f:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 16,
    insets   = { left = 4, right = 4, top = 4, bottom = 4 },
})
f:SetBackdropColor(0.05, 0.05, 0.05, 0.97)
f:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
ns.SmoothFrame(f)
f:Hide()
ns.ProgressionFrame = f

-- Header
local header = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
header:SetPoint("TOPLEFT", PAD, -10)
header:SetText("|cffaaaaaaProgression \226\128\148 Midnight Season 1|r")

-- ESC to close (skipped when inside the app shell)
f:SetScript("OnShow", function()
    if f.inAppMode then return end
    tinsert(UISpecialFrames, "YippYappProgressionFrame")
end)
f:SetScript("OnHide", function()
    for i = #UISpecialFrames, 1, -1 do
        if UISpecialFrames[i] == "YippYappProgressionFrame" then
            table.remove(UISpecialFrames, i)
            break
        end
    end
end)

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

-- App mode: hide standalone chrome so the app shell provides it
function ns:SetProgressionAppMode(enabled)
    if enabled then
        header:Hide()
        closeBtn:Hide()
        f:SetMovable(false)
        f:EnableMouse(false)
        f:SetBackdrop(nil)
    else
        header:Show()
        closeBtn:Show()
        f:SetMovable(true)
        f:EnableMouse(true)
        f:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 16,
            insets   = { left = 4, right = 4, top = 4, bottom = 4 },
        })
        f:SetBackdropColor(0.05, 0.05, 0.05, 0.97)
        f:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
        ns.SmoothFrame(f)
    end
end

------------------------------------------------------------
-- Y cursor  (increases downward; actual anchor = CONTENT_TOP - Y)
------------------------------------------------------------
local Y = 0

------------------------------------------------------------
-- Card factory
------------------------------------------------------------
local function MakeCard(col, yPos, crestType)
    local x = (col - 1) * (CARD_W + CARD_GAP)
    local card = CreateFrame("Frame", nil, f, "BackdropTemplate")
    card:SetSize(CARD_W, CARD_H)
    card:SetPoint("TOPLEFT", f, "TOPLEFT", PAD + x, CONTENT_TOP - yPos)
    card:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    card:SetBackdropColor(0.10, 0.10, 0.10, 0.9)
    ns.SmoothFrame(card)

    local c = crestType and CC[crestType]
    if c then
        card:SetBackdropBorderColor(c.r * 0.6, c.g * 0.6, c.b * 0.6, 0.7)
        local stripe = card:CreateTexture(nil, "OVERLAY")
        stripe:SetSize(3, CARD_H - 10)
        stripe:SetPoint("LEFT", 4, 0)
        stripe:SetColorTexture(c.r, c.g, c.b, 0.85)
        ns.DisableSharpening(stripe)
    else
        card:SetBackdropBorderColor(0.25, 0.25, 0.25, 0.5)
    end
    return card
end

local function CardTitle(card, text, hex)
    local s = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    s:SetPoint("TOPLEFT", 12, -8)
    s:SetText(hex and ("|c" .. hex .. text .. "|r") or ("|cffffffff" .. text .. "|r"))
    ns.ApplyTextShadow(s)
end

local function CardLine(card, row, label, value, hex)
    local yOff = 30 + (row - 1) * 19
    local l = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    l:SetPoint("TOPLEFT", 12, -yOff)
    l:SetTextColor(unpack(ns.COLORS.TEXT_SECONDARY))
    l:SetText(label)

    local v = card:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    v:SetPoint("TOPLEFT", 62, -yOff)
    v:SetWidth(CARD_W - 74)
    v:SetJustifyH("LEFT")
    v:SetWordWrap(false)
    if hex then
        v:SetText("|c" .. hex .. value .. "|r")
    else
        v:SetText(value)
    end
end

local function SectionLabel(yPos, text)
    local s = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    s:SetPoint("TOPLEFT", f, "TOPLEFT", PAD, CONTENT_TOP - yPos)
    s:SetText(text)
    ns.ApplyTextShadow(s)
end

------------------------------------------------------------
-- Section 1: Mythic+   (breakpoints: M0, +4, +9, +12)
------------------------------------------------------------
SectionLabel(Y, "|cff00aaffMythic+|r")
Y = Y + TITLE_H

-- Group M+ keys into ranges
local mpGroups = {
    { from = 2, to = 5,  label = "+2 - +5" },
    { from = 6, to = 8,  label = "+6 - +8" },
    { from = 9, to = 9,  label = "+9" },
    { from = 10, to = 12, label = "+10 - +12" },
}

for i, mg in ipairs(mpGroups) do
    -- Collect entries in this range
    local loLoot, hiLoot, loVault, hiVault = 9999, 0, 9999, 0
    local crestType = nil
    for _, e in ipairs(ns.PROGRESSION.MYTHIC_PLUS) do
        if type(e.key) == "number" and e.key >= mg.from and e.key <= mg.to then
            if e.loot < loLoot then loLoot = e.loot end
            if e.loot > hiLoot then hiLoot = e.loot end
            if e.vault < loVault then loVault = e.vault end
            if e.vault > hiVault then hiVault = e.vault end
            crestType = crestType or e.crestType
        end
    end

    local c   = CC[crestType]
    local hex = c and c.hex or "ffffffff"
    local card = MakeCard(i, Y, crestType)
    CardTitle(card, mg.label, hex)

    -- Format ilvl + track, showing highest track
    local function FormatIlvlRange(lo, hi)
        local hiTrack = ns:GetTrackFromIlvl(hi)
        local tHex = hiTrack and CC[hiTrack] and CC[hiTrack].hex or "ffffffff"
        local short = hiTrack or ""
        if lo == hi then
            return "|cffffffff" .. lo .. "|r |c" .. tHex .. short .. "|r"
        else
            return "|cffffffff" .. lo .. "|r - |cffffffff" .. hi .. "|r |c" .. tHex .. short .. "|r"
        end
    end

    CardLine(card, 1, "Loot", FormatIlvlRange(loLoot, hiLoot))
    CardLine(card, 2, "Vault", FormatIlvlRange(loVault, hiVault))

    CardLine(card, 3, "Crest", crestType, hex)
end
Y = Y + CARD_H + SECTION_GAP

------------------------------------------------------------
-- Section 2: Raid  (switchable wing view)
------------------------------------------------------------
local RAID_Y = Y
SectionLabel(Y, "|cffa335eeRaid|r")

-- Wing filter definitions
local WING_FILTERS = {
    { id = "all",   label = "All",        filter = nil },
    { id = "void",  label = "Voidspire",  filter = "Void" },
    { id = "dream", label = "Dreamrift",  filter = "Dream" },
    { id = "march", label = "Quel'Danas", filter = "March" },
}
local selectedWing = "all"

-- Wing selector buttons (positioned right of section title)
local wingBtns = {}
local wingX = PAD + 60
for _, wDef in ipairs(WING_FILTERS) do
    local wb = CreateFrame("Button", nil, f, "BackdropTemplate")
    wb:SetSize(70, 18)
    wb:SetPoint("TOPLEFT", f, "TOPLEFT", wingX, CONTENT_TOP - RAID_Y - 1)
    wb:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 8,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    local wbFs = wb:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    wbFs:SetPoint("CENTER")
    wbFs:SetText(wDef.label)
    ns.AddGlowHighlight(wb, 0.06)
    wingBtns[wDef.id] = { btn = wb, def = wDef }
    wingX = wingX + 74
end

Y = Y + TITLE_H

-- Container for raid cards (rebuilt on wing change)
local raidCardContainer = CreateFrame("Frame", nil, f)
raidCardContainer:SetPoint("TOPLEFT", f, "TOPLEFT", 0, CONTENT_TOP - Y)
raidCardContainer:SetSize(FRAME_W, CARD_H)

local raidCards = {}

local function BuildRaidCards()
    -- Clear old cards
    for _, card in ipairs(raidCards) do card:Hide() end
    wipe(raidCards)

    -- Update wing button styles
    for id, wb in pairs(wingBtns) do
        if id == selectedWing then
            wb.btn:SetBackdropColor(0.2, 0.1, 0.25, 1)
            wb.btn:SetBackdropBorderColor(0.64, 0.21, 0.93, 0.8)
        else
            wb.btn:SetBackdropColor(0.1, 0.1, 0.1, 0.6)
            wb.btn:SetBackdropBorderColor(0.3, 0.3, 0.3, 0.5)
        end
    end

    local filter = wingBtns[selectedWing] and wingBtns[selectedWing].def.filter

    for i, diff in ipairs(ns.PROGRESSION.RAID) do
        local c   = CC[diff.crestType]
        local hex = c and c.hex or "ffffffff"

        -- Filter bosses by wing
        local bosses = {}
        for _, b in ipairs(diff.bosses) do
            if not filter or b.range:find(filter) then
                table.insert(bosses, b)
            end
        end

        local x = (i - 1) * (CARD_W + CARD_GAP)
        local card = CreateFrame("Frame", nil, raidCardContainer, "BackdropTemplate")
        card:SetSize(CARD_W, CARD_H)
        card:SetPoint("TOPLEFT", raidCardContainer, "TOPLEFT", PAD + x, 0)
        card:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 10,
            insets   = { left = 2, right = 2, top = 2, bottom = 2 },
        })
        card:SetBackdropColor(0.10, 0.10, 0.10, 0.9)
        card:SetBackdropBorderColor(c and (c.r * 0.6) or 0.25, c and (c.g * 0.6) or 0.25, c and (c.b * 0.6) or 0.25, 0.7)
        ns.SmoothFrame(card)

        if c then
            local stripe = card:CreateTexture(nil, "OVERLAY")
            stripe:SetSize(3, CARD_H - 10)
            stripe:SetPoint("LEFT", 4, 0)
            stripe:SetColorTexture(c.r, c.g, c.b, 0.85)
            ns.DisableSharpening(stripe)
        end

        -- Title
        local titleFs = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        titleFs:SetPoint("TOPLEFT", 12, -8)
        titleFs:SetText("|c" .. hex .. diff.difficulty .. "|r")
        ns.ApplyTextShadow(titleFs)

        if filter then
            -- Detailed: boss rows with colored ilvl and crest info
            local lineY = 26

            -- Short crest names
            local SHORT_CREST = {
                Adventurer = "Adv", Veteran = "Vet", Champion = "Champ",
                Hero = "Hero", Myth = "Myth",
            }
            local CREST_SHORT_BONUS = {
                C = { name = "Champ", hex = CC.Champion and CC.Champion.hex or "ffa335ee" },
                H = { name = "Hero",  hex = CC.Hero and CC.Hero.hex or "ffff8000" },
                M = { name = "Myth",  hex = CC.Myth and CC.Myth.hex or "ffff0000" },
            }

            -- Column header
            local hBoss = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            hBoss:SetPoint("TOPLEFT", 12, -lineY)
            hBoss:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
            hBoss:SetText("Boss")
            local hIlvl = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            hIlvl:SetPoint("TOPLEFT", 48, -lineY)
            hIlvl:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
            hIlvl:SetText("ilvl")
            local hCrest = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            hCrest:SetPoint("TOPLEFT", 82, -lineY)
            hCrest:SetTextColor(unpack(ns.COLORS.TEXT_TERTIARY))
            hCrest:SetText("Crests")
            lineY = lineY + 12

            for _, b in ipairs(bosses) do
                -- Boss number (extract from range like "Void 2-3" -> "2-3")
                local bossNum = b.range:match("%d[%-]?%d?$") or b.range
                local nameFs = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                nameFs:SetPoint("TOPLEFT", 12, -lineY)
                nameFs:SetTextColor(unpack(ns.COLORS.TEXT_SECONDARY))
                nameFs:SetText(bossNum)

                -- Item level (bright)
                local ilvlFs = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                ilvlFs:SetPoint("TOPLEFT", 48, -lineY)
                ilvlFs:SetText("|cffffffff" .. b.ilvl .. "|r")

                -- Crest info (always show crest type, amount if known)
                local crestFs = card:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                crestFs:SetPoint("TOPLEFT", 82, -lineY)
                crestFs:SetWidth(CARD_W - 94)
                crestFs:SetJustifyH("LEFT")
                crestFs:SetWordWrap(false)

                local bCrest = b.crestType or diff.crestType
                local bHex = CC[bCrest] and CC[bCrest].hex or hex
                local shortBase = SHORT_CREST[bCrest] or bCrest

                if b.crestAmount and b.crestAmount ~= "" then
                    local base, bonus, bonusType = b.crestAmount:match("^(%d+)%+(%d+)(%a)$")
                    if base and bonus and bonusType and CREST_SHORT_BONUS[bonusType] then
                        local cs = CREST_SHORT_BONUS[bonusType]
                        crestFs:SetText("|c" .. bHex .. base .. " " .. shortBase .. "|r+|c" .. cs.hex .. bonus .. " " .. cs.name .. "|r")
                    else
                        crestFs:SetText("|c" .. bHex .. b.crestAmount .. " " .. shortBase .. "|r")
                    end
                else
                    crestFs:SetText("|c" .. bHex .. shortBase .. "|r")
                end

                lineY = lineY + 11
            end
        else
            -- Summary: ilvl range + crest + bonus
            local lo, hi = 9999, 0
            local bonusCrest = nil
            for _, b in ipairs(bosses) do
                if b.ilvl < lo then lo = b.ilvl end
                if b.ilvl > hi then hi = b.ilvl end
                if b.crestAmount and b.crestAmount:find("+") then
                    bonusCrest = b.crestAmount
                end
            end
            CardLine(card, 1, "Loot", lo .. " \226\128\147 " .. hi)
            CardLine(card, 2, "Crest", diff.crestType, hex)
            if bonusCrest then
                CardLine(card, 3, "Void 6", bonusCrest)
            else
                CardLine(card, 3, "Bosses", "6 + 1 + 2")
            end
        end

        table.insert(raidCards, card)
    end
end

-- Wire wing buttons
for id, wb in pairs(wingBtns) do
    wb.btn:SetScript("OnClick", function()
        selectedWing = id
        BuildRaidCards()
    end)
end

BuildRaidCards()
Y = Y + CARD_H + SECTION_GAP

------------------------------------------------------------
-- Section 3: Delves  (breakpoints: T4, T7, T10, T11)
------------------------------------------------------------
SectionLabel(Y, "|cff00ff00Delves|r")
Y = Y + TITLE_H

-- Group delve tiers by crest type into ranges
local delveGroups = {
    { from = 1, to = 4,  crestType = "Adventurer" },
    { from = 5, to = 6,  crestType = "Veteran" },
    { from = 7, to = 10, crestType = "Champion" },
    { from = 11, to = 11, crestType = "Hero" },
}

for i, dg in ipairs(delveGroups) do
    local c   = CC[dg.crestType]
    local hex = c and c.hex or "ffffffff"

    -- Find ilvl range and crest amounts from the data
    local loLoot, hiLoot, loVault, hiVault = 9999, 0, 9999, 0
    local minCrest, maxCrest = 999, 0
    local isGilded = false
    for _, e in ipairs(ns.PROGRESSION.DELVES) do
        if e.tier >= dg.from and e.tier <= dg.to then
            if e.loot then
                if e.loot < loLoot then loLoot = e.loot end
                if e.loot > hiLoot then hiLoot = e.loot end
                if e.vault < loVault then loVault = e.vault end
                if e.vault > hiVault then hiVault = e.vault end
            else
                isGilded = true
            end
            local amt = tonumber(e.crestAmount) or 0
            if amt > 0 then
                if amt < minCrest then minCrest = amt end
                if amt > maxCrest then maxCrest = amt end
            end
        end
    end

    local card = MakeCard(i, Y, dg.crestType)

    -- Title: "Tier 1 - 4" or "Tier 11"
    local titleStr = dg.from == dg.to and ("Tier " .. dg.from) or ("Tier " .. dg.from .. " - " .. dg.to)
    CardTitle(card, titleStr, hex)

    if isGilded then
        CardLine(card, 1, "", "Gilded Stash 4x/week")
        CardLine(card, 2, "Crest", "10 Hero + 5 Myth", hex)
    else
        local lootStr = loLoot == hiLoot and tostring(loLoot) or (loLoot .. " - " .. hiLoot)
        local vaultStr = loVault == hiVault and tostring(loVault) or (loVault .. " - " .. hiVault)
        CardLine(card, 1, "Loot", lootStr)
        CardLine(card, 2, "Vault", vaultStr)

        local crestStr = dg.crestType
        if minCrest > 0 then
            local amtStr = minCrest == maxCrest and tostring(minCrest) or (minCrest .. "-" .. maxCrest)
            crestStr = amtStr .. " " .. dg.crestType
        end
        CardLine(card, 3, "Crest", crestStr, hex)
    end
end
Y = Y + CARD_H + SECTION_GAP

------------------------------------------------------------
-- Section 4: Bottom row — two wider mini-panels
------------------------------------------------------------
local PCOLS  = 2
local PGAP   = 10
local PW     = math.floor((CW - (PCOLS - 1) * PGAP) / PCOLS) -- ~461
local PH     = math.floor(110 * uiScale)
local PLINE  = 18

local function MakePanel(panelCol, title, titleHex)
    local x = (panelCol - 1) * (PW + PGAP)
    local p = CreateFrame("Frame", nil, f, "BackdropTemplate")
    p:SetSize(PW, PH)
    p:SetPoint("TOPLEFT", f, "TOPLEFT", PAD + x, CONTENT_TOP - Y)
    p:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets   = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    p:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
    p:SetBackdropBorderColor(0.2, 0.2, 0.2, 0.5)
    ns.SmoothFrame(p)

    local s = p:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    s:SetPoint("TOPLEFT", 10, -8)
    s:SetText("|c" .. titleHex .. title .. "|r")
    ns.ApplyTextShadow(s)
    return p
end

local function PanelLine(panel, row, text)
    local s = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    s:SetPoint("TOPLEFT", 10, -30 - (row - 1) * PLINE)
    s:SetWidth(PW - 20)
    s:SetJustifyH("LEFT")
    s:SetWordWrap(false)
    s:SetTextColor(0.75, 0.75, 0.75)
    s:SetText(text)
end

-- Panel 1 — Prey
local p1 = MakePanel(1, "Prey", "ffffff66")
for i, e in ipairs(ns.PROGRESSION.PREY) do
    local c   = CC[e.crestType]
    local hex = c and c.hex or "ffffffff"
    PanelLine(p1, i,
        e.difficulty .. "  " .. e.loot .. " / " .. e.vault .. "  |c" .. hex .. e.crestType .. "|r")
end

-- Panel 2 — Crafting (Spark tiers)
local p2 = MakePanel(2, "Crafting (Spark)", "ffff8000")
for i, spark in ipairs(ns.PROGRESSION.CRAFTING_SPARK) do
    local lo = spark.qualities[1]
    local hi = spark.qualities[#spark.qualities]
    PanelLine(p2, i,
        "|c" .. spark.color .. spark.label .. "|r  " .. lo .. " \226\128\147 " .. hi)
end

------------------------------------------------------------
-- Side panel: Crest Sources  (docked to right of main frame)
------------------------------------------------------------
local SIDE_W = math.floor(190 * ns:GetUIScale())
local SIDE_PAD = 10

local side = CreateFrame("Frame", nil, f, "BackdropTemplate")
side:SetSize(SIDE_W, FRAME_H)
side:SetPoint("TOPLEFT", f, "TOPRIGHT", -1, 0)
side:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 16,
    insets   = { left = 4, right = 4, top = 4, bottom = 4 },
})
side:SetBackdropColor(0.05, 0.05, 0.05, 0.97)
side:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
ns.SmoothFrame(side)

local sideTitle = side:CreateFontString(nil, "OVERLAY", "GameFontNormal")
sideTitle:SetPoint("TOPLEFT", SIDE_PAD, -12)
sideTitle:SetText("|cffff4444Crest Sources|r")
ns.ApplyTextShadow(sideTitle)

local sY = 36
for _, crest in ipairs(ns.PROGRESSION.CREST_SOURCES) do
    -- Crest type name (coloured, bold)
    local nameFs = side:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameFs:SetPoint("TOPLEFT", SIDE_PAD, -sY)
    nameFs:SetText("|c" .. crest.color .. crest.crestType .. "|r")
    sY = sY + 18

    -- Source lines
    for _, src in ipairs(crest.sources) do
        local srcFs = side:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        srcFs:SetPoint("TOPLEFT", SIDE_PAD + 4, -sY)
        srcFs:SetWidth(SIDE_W - SIDE_PAD * 2 - 4)
        srcFs:SetJustifyH("LEFT")
        srcFs:SetWordWrap(true)
        srcFs:SetTextColor(0.65, 0.65, 0.65)
        srcFs:SetText(src)
        sY = sY + srcFs:GetStringHeight() + 3
    end

    sY = sY + 12
end
