local ADDON_NAME, RPAL = ...

local TEX="Interface\\AddOns\\ODYN\\Textures\\Talent\\"
local STAR=TEX.."atlas_stars.tga"
local NEBULA=TEX.."atlas_nebula.tga"
local SPARK=TEX.."sparkle.tga"
local ZODIAC=TEX.."atlas_zodiac.tga"
local TAU=math.pi*2
local NODE_TEXTURE={
    origin=TEX.."node_keystone.tga",
    keystone=TEX.."node_keystone.tga",
    minor=TEX.."node_minor.tga",
    major=TEX.."node_major.tga",
    reward=TEX.."node_reward.tga",
    memoir=TEX.."node_memoir.tga",
}

local INK={0.035,0.028,0.055,0.98}
local INK2={0.055,0.045,0.080,0.96}
local GOLD={0.92,0.72,0.34}
local PALE={0.91,0.88,0.80}
local MUTED={0.58,0.58,0.66}
local LOCKED={0.24,0.25,0.31}

local atlas
local atlasCanvas
local atlasViewport
local nodeFrames={}
local connectionLines={}
local procToast
local resetDialog
local panX,panY=0,0
local atlasZoom=1.0
local ZOOM_MIN,ZOOM_MAX,ZOOM_STEP=0.55,1.45,0.10
local dragging=false
local dragStartX,dragStartY,dragOriginX,dragOriginY

local function MakeBackdrop(frame,bg,border,edgeSize)
    frame:SetBackdrop({
        bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
        tile=false,tileSize=16,edgeSize=edgeSize or 12,
        insets={left=3,right=3,top=3,bottom=3},
    })
    frame:SetBackdropColor((bg or INK)[1],(bg or INK)[2],(bg or INK)[3],(bg or INK)[4] or 1)
    frame:SetBackdropBorderColor((border or GOLD)[1],(border or GOLD)[2],(border or GOLD)[3],(border or GOLD)[4] or 1)
end

local function Text(parent,text,size,color,font)
    local fs=parent:CreateFontString(nil,"OVERLAY")
    local baseSize=size or 10
    local fontPath=font or "Fonts\\FRIZQT__.TTF"
    fs:SetFont(fontPath,RPAL.GetScaledFontSize and RPAL.GetScaledFontSize(baseSize) or baseSize,"OUTLINE")
    if RPAL.RegisterScalableFontObject then RPAL.RegisterScalableFontObject(fs,fontPath,baseSize,"OUTLINE") end
    fs:SetText(text or "")
    local c=color or PALE
    fs:SetTextColor(c[1],c[2],c[3],c[4] or 1)
    fs:SetShadowColor(0,0,0,0.9)
    fs:SetShadowOffset(1,-1)
    return fs
end

local function BranchColor(branch)
    local def=RPAL.TALENT_BRANCHES and RPAL.TALENT_BRANCHES[branch]
    return def and def.color or GOLD
end

local function GetNodeState(node)
    if not node then return "locked" end
    if node.id=="origin" or (RPAL.IsTalentAllocated and RPAL.IsTalentAllocated(node.id)) then return "allocated" end
    if RPAL.CanAllocateTalent then
        local ok=RPAL.CanAllocateTalent(node.id)
        if ok then return "available" end
    end
    return "locked"
end

local function RefreshTalentButton()
    local b=RPAL.TalentButton
    if not b or not b.label then return end
    -- The Adventurer's Desk has a permanent Atlas entry in the left navigation.
    -- Keep the old header button as a compatibility proxy only; it must never
    -- reappear over profile glances when talent points refresh.
    if b._rpalRedundantHeaderButton then
        b:Hide()
        return
    end
    if RPAL.IsLiteKit and RPAL.IsLiteKit() then
        b:Hide()
        return
    end
    b:Show()
    local available=RPAL.GetAvailableTalentPoints and RPAL.GetAvailableTalentPoints() or 0
    if available>0 then
        b.label:SetText("Talent Atlas  |cffffd56a"..tostring(available).."|r")
    else
        b.label:SetText("Talent Atlas")
    end
end
RPAL.RefreshTalentButton=RefreshTalentButton

local function EnsureProcToast()
    if procToast then return procToast end
    local parent=UIParent
    local f=CreateFrame("Frame",nil,parent,"BackdropTemplate")
    procToast=f
    f:SetSize(320,68)
    f:SetPoint("TOP",UIParent,"TOP",0,-155)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(9150)
    MakeBackdrop(f,{0.028,0.020,0.045,0.96},GOLD,14)
    f:Hide()

    local star=f:CreateTexture(nil,"BACKGROUND",nil,-1)
    star:SetTexture(STAR);star:SetAllPoints();star:SetAlpha(0.18)

    local left=f:CreateTexture(nil,"ARTWORK")
    left:SetTexture(SPARK);left:SetSize(40,40);left:SetPoint("LEFT",12,0);left:SetBlendMode("ADD")
    left:SetVertexColor(GOLD[1],GOLD[2],GOLD[3],0.8)

    f.title=Text(f,"",13,GOLD,"Fonts\\FRIZQT__.TTF")
    f.title:SetPoint("TOPLEFT",58,-14)
    f.detail=Text(f,"",10,PALE)
    f.detail:SetPoint("TOPLEFT",58,-38)
    return f
end

local function ShowProcToast(title,detail)
    if RPAL.DMKit and RPAL.DMKit.ShouldSuppressProgressionUI and RPAL.DMKit.ShouldSuppressProgressionUI() then return end
    local f=EnsureProcToast()
    if RPAL.ApplyToastLayout then RPAL.ApplyToastLayout(f) end
    f.title:SetText(title or "Renown Atlas")
    f.detail:SetText(detail or "")
    f._age=0
    f:SetAlpha(0)
    f:Show()
    f:SetScript("OnUpdate",function(self,elapsed)
        self._age=(self._age or 0)+(elapsed or 0)
        local t=self._age
        if t<0.22 then self:SetAlpha(t/0.22)
        elseif t<2.15 then self:SetAlpha(1)
        elseif t<2.55 then self:SetAlpha(1-((t-2.15)/0.40))
        else self:Hide();self:SetScript("OnUpdate",nil) end
    end)
end
RPAL.OnTalentProc=ShowProcToast

local function UpdatePan()
    if not atlasCanvas or not atlasViewport then return end
    -- Panning range expands with zoom so the outer Memoirs remain reachable
    -- even at maximum magnification.
    local z=atlasZoom or 1
    local maxX=math.max(980,(1550*z)-360)
    local maxY=math.max(980,(1550*z)-360)
    panX=math.max(-maxX,math.min(maxX,panX or 0))
    panY=math.max(-maxY,math.min(maxY,panY or 0))
    atlasCanvas:ClearAllPoints()
    atlasCanvas:SetPoint("CENTER",atlasViewport,"CENTER",panX,panY)
end

local function RecenterAtlas()
    panX,panY=0,0
    UpdatePan()
end

local function CursorUI()
    local scale=UIParent:GetEffectiveScale() or 1
    local x,y=GetCursorPosition()
    return x/scale,y/scale
end

local function RefreshZoomReadout()
    if atlas and atlas.zoomText then
        atlas.zoomText:SetText(tostring(math.floor((atlasZoom or 1)*100+0.5)).."%")
    end
end

local function SetAtlasZoom(value,preserveCursor)
    if not atlasCanvas or not atlasViewport then return end
    local old=atlasZoom or 1
    local new=math.max(ZOOM_MIN,math.min(ZOOM_MAX,tonumber(value) or old))
    if math.abs(new-old)<0.001 then return end

    if preserveCursor then
        local cx,cy=CursorUI()
        local vx,vy=atlasViewport:GetCenter()
        if vx and vy then
            local relX,relY=cx-vx,cy-vy
            local worldX=(relX-(panX or 0))/old
            local worldY=(relY-(panY or 0))/old
            panX=relX-(worldX*new)
            panY=relY-(worldY*new)
        end
    end

    atlasZoom=new
    atlasCanvas:SetScale(atlasZoom)
    UpdatePan()
    RefreshZoomReadout()
end

local function AdjustAtlasZoom(delta,preserveCursor)
    local step=(tonumber(delta) or 0)>0 and ZOOM_STEP or -ZOOM_STEP
    SetAtlasZoom((atlasZoom or 1)+step,preserveCursor~=false)
end

RPAL.AdjustTalentAtlasZoom=AdjustAtlasZoom
RPAL.SetTalentAtlasZoom=SetAtlasZoom

local function StartPan()
    dragging=true
    dragStartX,dragStartY=CursorUI()
    dragOriginX,dragOriginY=panX,panY
end

local function StopPan()
    dragging=false
end

local function UpdateDrag()
    if not dragging then return end
    local x,y=CursorUI()
    panX=dragOriginX+(x-dragStartX)
    panY=dragOriginY+(y-dragStartY)
    UpdatePan()
end

local function ShowNodeTooltip(button,node)
    if not RPAL.ShowCursorTooltip then return end
    local c=BranchColor(node.branch)
    local branch=RPAL.TALENT_BRANCHES and RPAL.TALENT_BRANCHES[node.branch]
    local state=GetNodeState(node)
    local cost=tonumber(node.cost) or 0
    local footer
    local footerColor=MUTED
    if state=="allocated" then
        if node.id~="origin" and RPAL.CanRefundTalent then
            local refundable,why=RPAL.CanRefundTalent(node.id)
            if refundable then
                footer="LEARNED  •  RIGHT CLICK TO REFUND"
                footerColor={0.48,0.95,0.68}
            else
                footer="LEARNED  •  REFUND LOCKED  •  "..tostring(why or "Another learned node still depends on this path")
                footerColor={0.82,0.64,0.50}
            end
        else
            footer="LEARNED"..(node.reward and "  •  Reward granted" or node.memoir and "  •  Memoir unveiled" or "")
            footerColor={0.48,0.95,0.68}
        end
    elseif state=="available" then
        local settings=RPAL.GetSettings and RPAL.GetSettings() or {}
        footer="LEFT CLICK TO LEARN  •  "..cost.." Talent Point"..(cost==1 and "" or "s")
        footerColor={1.0,0.82,0.42}
    else
        local reason
        if RPAL.CanAllocateTalent then local _,why=RPAL.CanAllocateTalent(node.id);reason=why end
        footer="LOCKED  •  "..tostring(reason or "Follow the connected constellation first")
        footerColor={0.65,0.58,0.62}
    end

    local kind=string.upper(tostring(node.type or "talent"))
    if node.type=="keystone" then kind="BUILD KEYSTONE"
    elseif node.type=="major" then kind="MAJOR TALENT"
    elseif node.type=="minor" then kind="MINOR TALENT"
    elseif node.type=="reward" then kind="COSMETIC REWARD"
    elseif node.type=="memoir" then kind="MEMOIR"
    elseif node.type=="origin" then kind="ORIGIN" end

    RPAL.ShowCursorTooltip({
        eyebrow=kind.."  •  "..string.upper((branch and branch.name) or "RENOWN ATLAS"),
        title=node.title or "Talent",
        body=node.description or "",
        sectionTitle=(node.type=="reward" and "REWARD") or (node.type=="memoir" and "LEGACY") or "WHAT IT DOES",
        section=node.effectText or "",
        detailTitle=(node.type=="major" or node.type=="keystone") and "HOW IT WORKS" or nil,
        detail=(node.type=="major" or node.type=="keystone") and (node.detailText or "") or "",
        footer=footer,
        footerColor=footerColor,
        accent=c,
        width=(node.type=="major" or node.type=="keystone") and 430 or 370,
    })
end

local function HideNodeTooltip()
    if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
end

local function NodeSize(node)
    if node.type=="origin" then return 68 end
    if node.type=="keystone" then return 62 end
    if node.type=="memoir" then return 58 end
    if node.type=="major" then return 48 end
    if node.type=="reward" then return 44 end
    return 30
end

local function CreateConnection(node,parent)
    if not atlasCanvas or not atlasCanvas.CreateLine then return end
    local c=BranchColor(node.branch)
    local glow=atlasCanvas:CreateLine(nil,"BACKGROUND",nil,-3)
    glow:SetThickness(4.5)
    glow:SetStartPoint("CENTER",atlasCanvas,parent.x or 0,parent.y or 0)
    glow:SetEndPoint("CENTER",atlasCanvas,node.x or 0,node.y or 0)
    glow:SetColorTexture(c[1],c[2],c[3],0.045)

    local line=atlasCanvas:CreateLine(nil,"BACKGROUND",nil,-2)
    line:SetThickness(1.35)
    line:SetStartPoint("CENTER",atlasCanvas,parent.x or 0,parent.y or 0)
    line:SetEndPoint("CENTER",atlasCanvas,node.x or 0,node.y or 0)
    line:SetColorTexture(c[1],c[2],c[3],0.20)
    connectionLines[#connectionLines+1]={node=node,parent=parent,line=line,glow=glow}
end

local function CreateNode(node,index)
    local size=NodeSize(node)
    local b=CreateFrame("Button",nil,atlasCanvas)
    b:SetSize(size,size)
    b:SetPoint("CENTER",atlasCanvas,"CENTER",node.x or 0,node.y or 0)
    b:RegisterForClicks("LeftButtonUp","RightButtonUp")
    b:EnableMouseWheel(true)
    b:SetScript("OnMouseWheel",function(_,delta)
        if RPAL.AdjustTalentAtlasZoom then RPAL.AdjustTalentAtlasZoom(delta,true) end
    end)
    b.node=node

    local glow=b:CreateTexture(nil,"BACKGROUND",nil,-1)
    glow:SetTexture(SPARK);glow:SetBlendMode("ADD")
    glow:SetSize(size*1.85,size*1.85);glow:SetPoint("CENTER")
    b.glow=glow

    local art=b:CreateTexture(nil,"ARTWORK")
    art:SetTexture(NODE_TEXTURE[node.type] or NODE_TEXTURE.minor)
    art:SetAllPoints();b.art=art

    if node.type~="minor" then
        local icon=b:CreateTexture(nil,"OVERLAY")
        icon:SetTexture(node.icon or RPAL.ICONS.question)
        local iconSize=node.type=="origin" and 24 or (node.type=="keystone" and 22 or (node.type=="memoir" and 20 or 16))
        icon:SetSize(iconSize,iconSize);icon:SetPoint("CENTER")
        icon:SetAlpha(node.type=="memoir" and 0.58 or 0.68)
        b.icon=icon
    end

    local label=Text(atlasCanvas,node.short or node.title or "",node.type=="minor" and 7 or (node.type=="keystone" and 9 or 8),MUTED)
    label:SetPoint("TOP",b,"BOTTOM",0,-4)
    label:SetWidth(node.type=="minor" and 88 or (node.type=="keystone" and 170 or 145))
    label:SetJustifyH("CENTER")
    label:SetWordWrap(false)
    b.label=label

    if node.type=="keystone" then
        local crown=Text(atlasCanvas,"BUILD KEYSTONE",7,GOLD)
        crown:SetPoint("BOTTOM",b,"TOP",0,5)
        b.crown=crown
    elseif node.type=="memoir" then
        local crown=Text(atlasCanvas,"MEMOIR",7,GOLD)
        crown:SetPoint("BOTTOM",b,"TOP",0,4)
        b.crown=crown
    elseif node.type=="reward" then
        local crown=Text(atlasCanvas,"REWARD",7,GOLD)
        crown:SetPoint("BOTTOM",b,"TOP",0,4)
        b.crown=crown
    end

    b:SetScript("OnEnter",function(self) ShowNodeTooltip(self,node) end)
    b:SetScript("OnLeave",function() HideNodeTooltip() end)
    b:SetScript("OnClick",function(self,button)
        if button=="RightButton" then
            if node.id=="origin" or not (RPAL.IsTalentAllocated and RPAL.IsTalentAllocated(node.id)) then return end
            if RPAL.RefundTalent then
                local ok,reason=RPAL.RefundTalent(node.id)
                if ok then
                    ShowProcToast(node.title,"Talent Point refunded")
                    if RefreshAtlas then RefreshAtlas() end
                    ShowNodeTooltip(self,node)
                else
                    ShowProcToast("Cannot refund this node",reason or "Another learned node still depends on it.")
                end
            end
            return
        end
        if button~="LeftButton" then return end
        if RPAL.IsTalentAllocated and RPAL.IsTalentAllocated(node.id) then return end
        if RPAL.AllocateTalent then
            local ok,reason=RPAL.AllocateTalent(node.id)
            if not ok then
                ShowProcToast("The path is not open",reason or "This node cannot be learned yet.")
            else
                local detail="Talent learned"
                if node.reward then detail="Cosmetic unlocked: "..tostring(node.reward.name)
                elseif node.memoir then detail="Memoir unveiled"
                end
                ShowProcToast(node.title,detail)
                if RefreshAtlas then RefreshAtlas() end
            end
        end
    end)

    nodeFrames[node.id]=b
end

local function CreateBranchCaption(branch,x,y,anchor)
    local def=RPAL.TALENT_BRANCHES[branch]
    if not def then return end
    local c=def.color
    local f=CreateFrame("Frame",nil,atlasCanvas,"BackdropTemplate")
    f:SetSize(250,56)
    f:SetPoint(anchor or "CENTER",atlasCanvas,"CENTER",x,y)
    MakeBackdrop(f,{0.02,0.02,0.04,0.78},{c[1],c[2],c[3],0.42},10)
    local title=Text(f,def.name,12,c,"Fonts\\FRIZQT__.TTF")
    title:SetPoint("TOP",0,-8)
    local desc=Text(f,def.description,7,MUTED)
    desc:SetPoint("TOPLEFT",10,-28);desc:SetPoint("TOPRIGHT",-10,-28);desc:SetJustifyH("CENTER");desc:SetWordWrap(true)
end

local function RefreshAtlas()
    if not atlas then return end
    local available=RPAL.GetAvailableTalentPoints and RPAL.GetAvailableTalentPoints() or 0
    local spent=RPAL.GetSpentTalentPoints and RPAL.GetSpentTalentPoints() or 0
    local total=RPAL.GetTotalTalentPoints and RPAL.GetTotalTalentPoints() or 0
    local level=RPAL.GetCurrentRenownLevel and select(1,RPAL.GetCurrentRenownLevel()) or 1

    if atlas.points then atlas.points:SetText(tostring(available)) end
    if atlas.meta then atlas.meta:SetText("Level "..tostring(level).."  •  "..tostring(total).." earned  •  "..tostring(spent).." spent") end
    if atlas.buildSummary then
        local c=RPAL.GetTalentBranchSpent and RPAL.GetTalentBranchSpent("courtesy") or 0
        local f=RPAL.GetTalentBranchSpent and RPAL.GetTalentBranchSpent("fellowship") or 0
        local l=RPAL.GetTalentBranchSpent and RPAL.GetTalentBranchSpent("chronicle") or 0
        local build=RPAL.GetTalentBuildName and RPAL.GetTalentBuildName() or "Unshaped"
        atlas.buildSummary:SetText("BUILD  |cffffd56a"..tostring(build).."|r    COURTESY "..c.."  •  FELLOWSHIP "..f.."  •  CHRONICLE "..l)
    end
    if atlas.zodiac then
        local palette=RPAL.GetCurrentThemePalette and RPAL.GetCurrentThemePalette()
        local c=(palette and palette.gold) or GOLD
        atlas.zodiac:SetVertexColor(c[1],c[2],c[3])
        for _,star in ipairs(atlas.canvasStars or {}) do
            star:SetVertexColor(c[1],c[2],c[3],1)
            star:SetAlpha(star._rpalBaseAlpha or 0.18)
        end
    end

    for id,b in pairs(nodeFrames) do
        local node=b.node
        local state=GetNodeState(node)
        local c=BranchColor(node.branch)
        if state=="allocated" then
            b.art:SetVertexColor(c[1],c[2],c[3],1)
            b.glow:SetVertexColor(c[1],c[2],c[3],0.72)
            if b.label then b.label:SetTextColor(c[1],c[2],c[3],0.96) end
            if b.icon then b.icon:SetDesaturated(false);b.icon:SetVertexColor(c[1],c[2],c[3],1);b.icon:SetAlpha(0.82) end
        elseif state=="available" then
            b.art:SetVertexColor(c[1],c[2],c[3],0.72)
            b.glow:SetVertexColor(c[1],c[2],c[3],0.42)
            if b.label then b.label:SetTextColor(PALE[1],PALE[2],PALE[3],0.92) end
            if b.icon then b.icon:SetDesaturated(false);b.icon:SetVertexColor(PALE[1],PALE[2],PALE[3],1);b.icon:SetAlpha(0.62) end
        else
            b.art:SetVertexColor(LOCKED[1],LOCKED[2],LOCKED[3],0.72)
            b.glow:SetVertexColor(c[1],c[2],c[3],0.06)
            if b.label then b.label:SetTextColor(MUTED[1],MUTED[2],MUTED[3],0.46) end
            if b.icon then b.icon:SetDesaturated(true);b.icon:SetVertexColor(LOCKED[1],LOCKED[2],LOCKED[3],1);b.icon:SetAlpha(0.24) end
        end
    end

    for _,conn in ipairs(connectionLines) do
        local c=BranchColor(conn.node.branch)
        local parentOn=conn.parent.id=="origin" or (RPAL.IsTalentAllocated and RPAL.IsTalentAllocated(conn.parent.id))
        local childOn=RPAL.IsTalentAllocated and RPAL.IsTalentAllocated(conn.node.id)
        if parentOn and childOn then
            conn.line:SetColorTexture(c[1],c[2],c[3],0.88)
            conn.glow:SetColorTexture(c[1],c[2],c[3],0.24)
        elseif parentOn then
            conn.line:SetColorTexture(c[1],c[2],c[3],0.34)
            conn.glow:SetColorTexture(c[1],c[2],c[3],0.08)
        else
            conn.line:SetColorTexture(0.24,0.24,0.31,0.18)
            conn.glow:SetColorTexture(c[1],c[2],c[3],0.02)
        end
    end
    RefreshTalentButton()
end
RPAL.RefreshTalentAtlas=RefreshAtlas

local function AtlasPulse(elapsed)
    if not atlas or not atlas:IsShown() then return end
    atlas._phase=(atlas._phase or 0)+(elapsed or 0)
    local phase=atlas._phase
    if atlas.zodiac then
        atlas.zodiac:SetRotation((phase*0.026)%TAU)
        atlas.zodiac:SetAlpha(0.105+((math.sin(phase*0.24)+1)*0.0225))
    end
    for _,b in pairs(nodeFrames) do
        local state=GetNodeState(b.node)
        if state=="available" then
            b.glow:SetAlpha(0.48+((math.sin(phase*2.4+(b.node.x or 0)*0.01)+1)*0.20))
        elseif state=="allocated" and b.node.type=="memoir" then
            b.glow:SetAlpha(0.62+((math.sin(phase*1.3)+1)*0.18))
        else
            b.glow:SetAlpha(1)
        end
    end
end

local function EnsureResetDialog(parent)
    if resetDialog then return resetDialog end
    local target=parent or atlas or UIParent

    -- Parent the confirmation to UIParent rather than the Atlas canvas.  This
    -- keeps it above every zoom/pan layer and, more importantly, prevents the
    -- canvas from intercepting clicks intended for the confirmation buttons.
    local shade=CreateFrame("Frame",nil,UIParent,"BackdropTemplate")
    resetDialog=shade
    shade:SetAllPoints(target)
    shade:SetFrameStrata("TOOLTIP")
    shade:SetFrameLevel(16000)
    shade:SetToplevel(true)
    shade:EnableMouse(true)
    shade:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
    shade:SetBackdropColor(0.004,0.004,0.012,0.78)
    shade:Hide()

    local box=CreateFrame("Frame",nil,shade,"BackdropTemplate")
    shade.box=box
    box:SetSize(520,248)
    box:SetPoint("CENTER",shade,"CENTER",0,6)
    box:SetFrameLevel(16010);box:EnableMouse(true)
    MakeBackdrop(box,{0.022,0.017,0.038,0.995},{0.72,0.49,0.28,1},16)

    local neb=box:CreateTexture(nil,"BACKGROUND",nil,-1)
    neb:SetTexture(NEBULA);neb:SetAllPoints();neb:SetAlpha(0.22)
    local zodiac=box:CreateTexture(nil,"BACKGROUND",nil,0)
    zodiac:SetTexture(ZODIAC);zodiac:SetSize(210,210);zodiac:SetPoint("RIGHT",8,0);zodiac:SetBlendMode("ADD");zodiac:SetAlpha(0.08)

    local eyebrow=Text(box,"RENOWN ATLAS",8,GOLD)
    eyebrow:SetPoint("TOPLEFT",24,-20)
    local title=Text(box,"RESET YOUR BUILD?",18,{0.96,0.72,0.42},"Fonts\\FRIZQT__.TTF")
    title:SetPoint("TOPLEFT",24,-42)

    local body=Text(box,"Every active Talent Point will be refunded and all build choices will be cleared. Cosmetic rewards and unveiled Memoirs stay permanently unlocked.",10,PALE)
    body:SetPoint("TOPLEFT",24,-82)
    body:SetPoint("TOPRIGHT",-24,-82)
    body:SetJustifyH("LEFT")
    body:SetWordWrap(true)
    body:SetSpacing(4)

    local warning=Text(box,"This changes talents only — permanent unlocks are safe.",9,{0.73,0.66,0.78})
    warning:SetPoint("BOTTOMLEFT",24,72)

    local cancel=CreateFrame("Button",nil,box,"BackdropTemplate")
    cancel:SetSize(142,34);cancel:SetPoint("BOTTOMRIGHT",-24,22);cancel:SetFrameLevel(16020);cancel:EnableMouse(true);cancel:RegisterForClicks("LeftButtonUp")
    MakeBackdrop(cancel,{0.045,0.038,0.060,0.98},{0.34,0.30,0.43,0.9},10)
    local cancelGlow=cancel:CreateTexture(nil,"BACKGROUND",nil,1);cancelGlow:SetTexture("Interface\\Buttons\\WHITE8X8");cancelGlow:SetPoint("TOPLEFT",3,-3);cancelGlow:SetPoint("BOTTOMRIGHT",-3,3);cancelGlow:SetColorTexture(GOLD[1],GOLD[2],GOLD[3],0);cancel._hoverGlow=cancelGlow
    local cancelText=Text(cancel,"KEEP BUILD",9,PALE);cancelText:SetPoint("CENTER")
    cancel:SetScript("OnEnter",function(self)self:SetBackdropColor(0.066,0.052,0.084,0.99);self:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.98);self._hoverGlow:SetColorTexture(GOLD[1],GOLD[2],GOLD[3],0.11);self:SetScale(1.025)end)
    cancel:SetScript("OnLeave",function(self)self:SetBackdropColor(0.045,0.038,0.060,0.98);self:SetBackdropBorderColor(0.34,0.30,0.43,0.9);self._hoverGlow:SetColorTexture(GOLD[1],GOLD[2],GOLD[3],0);self:SetScale(1)end)
    cancel:SetScript("OnClick",function()shade:Hide()end)

    local confirm=CreateFrame("Button",nil,box,"BackdropTemplate")
    confirm:SetSize(166,34);confirm:SetPoint("RIGHT",cancel,"LEFT",-10,0);confirm:SetFrameLevel(16020);confirm:EnableMouse(true);confirm:RegisterForClicks("LeftButtonUp")
    MakeBackdrop(confirm,{0.105,0.045,0.045,0.99},{0.82,0.42,0.31,1},10)
    local confirmGlow=confirm:CreateTexture(nil,"BACKGROUND",nil,1);confirmGlow:SetTexture("Interface\\Buttons\\WHITE8X8");confirmGlow:SetPoint("TOPLEFT",3,-3);confirmGlow:SetPoint("BOTTOMRIGHT",-3,3);confirmGlow:SetColorTexture(1.0,0.44,0.22,0);confirm._hoverGlow=confirmGlow
    local confirmText=Text(confirm,"RESET ATLAS",9,{1.0,0.83,0.66});confirmText:SetPoint("CENTER")
    confirm:SetScript("OnEnter",function(self)self:SetBackdropColor(0.15,0.055,0.045,0.99);self:SetBackdropBorderColor(1.0,0.62,0.38,1);self._hoverGlow:SetColorTexture(1.0,0.44,0.22,0.13);self:SetScale(1.025)end)
    confirm:SetScript("OnLeave",function(self)self:SetBackdropColor(0.105,0.045,0.045,0.99);self:SetBackdropBorderColor(0.82,0.42,0.31,1);self._hoverGlow:SetColorTexture(1.0,0.44,0.22,0);self:SetScale(1)end)
    confirm:SetScript("OnClick",function()
        shade:Hide()
        if RPAL.RespecTalents then RPAL.RespecTalents() end
    end)

    shade:SetScript("OnShow",function(self)
        self:ClearAllPoints();self:SetAllPoints(atlas or UIParent)
        self:SetFrameStrata("TOOLTIP")
        self:SetFrameLevel(16000)
        box:SetFrameLevel(16010)
        cancel:SetFrameLevel(16020);confirm:SetFrameLevel(16020)
        if self.Raise then self:Raise() end
        if box.Raise then box:Raise() end
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)
    return shade
end

local function BuildAtlas()
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
    if atlas then
        if main then atlas._returnMainFrame=main end
        return atlas
    end

    -- The atlas is intentionally NOT parented to RPAL_MainFrame.  Keeping it
    -- on UIParent avoids clipping/frame-level/parent-visibility edge cases and
    -- lets it replace the ledger as a true full-screen section of the addon.
    local f=CreateFrame("Frame","RPAL_TalentAtlas",UIParent,"BackdropTemplate")
    atlas=f
    f:SetSize(1280,790)
    f:SetPoint("CENTER",main or UIParent,"CENTER",0,0)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(3000)
    f:SetToplevel(true)
    f:SetClampedToScreen(true)
    f:SetClipsChildren(true)
    f:EnableMouse(true)
    if main and main.GetScale then f:SetScale(main:GetScale() or 1) end
    f:Hide()
    MakeBackdrop(f,{0.008,0.010,0.026,1},{0.34,0.27,0.48,1},18)

    -- Atlas display mode: normal ledger-sized window or UIParent-filling workspace.
    function f:ApplyWindowMode(fullscreen)
        self._fullScreen=fullscreen and true or false
        self:ClearAllPoints()
        if self._fullScreen then
            self:SetScale(1)
            self:SetPoint("TOPLEFT",UIParent,"TOPLEFT",8,-8)
            self:SetPoint("BOTTOMRIGHT",UIParent,"BOTTOMRIGHT",-8,8)
        else
            local ledger=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
            self:SetSize(1280,790)
            if ledger and ledger.GetScale then self:SetScale(ledger:GetScale() or 1) else self:SetScale(1) end
            self:SetPoint("CENTER",ledger or UIParent,"CENTER",0,0)
        end
        if self.fullscreenText then self.fullscreenText:SetText(self._fullScreen and "WINDOWED" or "FULL SCREEN") end
        UpdatePan()
    end

    local bg=f:CreateTexture(nil,"BACKGROUND",nil,-4)
    bg:SetTexture(NEBULA);bg:SetAllPoints();bg:SetAlpha(0.88)
    local neb=f:CreateTexture(nil,"BACKGROUND",nil,-3)
    neb:SetTexture(NEBULA);neb:SetAllPoints();neb:SetBlendMode("ADD");neb:SetAlpha(0.60)

    local vignette=f:CreateTexture(nil,"BACKGROUND",nil,-2)
    vignette:SetTexture("Interface\\Buttons\\WHITE8X8")
    vignette:SetPoint("TOPLEFT",8,-8);vignette:SetPoint("BOTTOMRIGHT",-8,8)
    vignette:SetVertexColor(0.01,0.008,0.02,0.35)

    local top=CreateFrame("Frame",nil,f,"BackdropTemplate")
    top:SetPoint("TOPLEFT",12,-12);top:SetPoint("TOPRIGHT",-12,-12);top:SetHeight(62)
    MakeBackdrop(top,{0.025,0.020,0.045,0.94},{0.46,0.35,0.62,0.78},12)

    local title=Text(top,"RENOWN ATLAS",21,GOLD,"Fonts\\FRIZQT__.TTF")
    title:SetPoint("TOPLEFT",18,-10)
    local sub=Text(top,"Sparse nearest-neighbour paths, deliberate junctions and build-defining masteries",9,MUTED)
    sub:SetPoint("TOPLEFT",20,-37)

    local pointsPlate=CreateFrame("Frame",nil,top,"BackdropTemplate")
    pointsPlate:SetSize(138,42);pointsPlate:SetPoint("RIGHT",-500,0)
    MakeBackdrop(pointsPlate,{0.035,0.027,0.052,0.98},GOLD,11)
    local pl=Text(pointsPlate,"TALENT POINTS",7,MUTED);pl:SetPoint("TOPLEFT",10,-8)
    f.points=Text(pointsPlate,"0",20,GOLD,"Fonts\\FRIZQT__.TTF");f.points:SetPoint("BOTTOMRIGHT",-11,5)

    f.meta=Text(top,"",8,MUTED);f.meta:SetPoint("RIGHT",pointsPlate,"LEFT",-12,0)

    f.buildSummary=Text(f,"BUILD  |cffffd56aUnshaped|r",8,MUTED)
    f.buildSummary:SetPoint("BOTTOMLEFT",22,18)

    local recenter=CreateFrame("Button",nil,top,"BackdropTemplate")
    recenter:SetSize(88,32);recenter:SetPoint("RIGHT",-140,0);MakeBackdrop(recenter,{0.055,0.045,0.075,0.95},{0.34,0.30,0.43,0.85},10)
    local rt=Text(recenter,"Recenter",9,PALE);rt:SetPoint("CENTER")
    recenter:SetScript("OnClick",RecenterAtlas)
    recenter:SetScript("OnEnter",function(self)
        self:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.9)
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Recenter Atlas","Return to the central origin without changing zoom.",GOLD,"RENOWN ATLAS") end
    end)
    recenter:SetScript("OnLeave",function(self)
        self:SetBackdropBorderColor(0.34,0.30,0.43,0.85)
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)

    local zoomPlate=CreateFrame("Frame",nil,top,"BackdropTemplate")
    zoomPlate:SetSize(156,32);zoomPlate:SetPoint("RIGHT",recenter,"LEFT",-8,0)
    MakeBackdrop(zoomPlate,{0.035,0.028,0.055,0.96},{0.30,0.28,0.42,0.78},10)

    local zoomMinus=CreateFrame("Button",nil,zoomPlate,"BackdropTemplate")
    zoomMinus:SetSize(32,24);zoomMinus:SetPoint("LEFT",4,0);MakeBackdrop(zoomMinus,{0.05,0.04,0.07,0.96},{0.34,0.30,0.43,0.75},8)
    local zmt=Text(zoomMinus,"−",15,PALE,"Fonts\\FRIZQT__.TTF");zmt:SetPoint("CENTER",0,1)
    zoomMinus:SetScript("OnClick",function()AdjustAtlasZoom(-1,false)end)
    zoomMinus:SetScript("OnEnter",function(self)
        self:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.95)
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Zoom Out","Reduce the Atlas scale. Mouse wheel down does the same thing.",GOLD,"RENOWN ATLAS") end
    end)
    zoomMinus:SetScript("OnLeave",function(self)
        self:SetBackdropBorderColor(0.34,0.30,0.43,0.75)
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)

    local zoomReset=CreateFrame("Button",nil,zoomPlate)
    zoomReset:SetSize(74,24);zoomReset:SetPoint("CENTER",0,0)
    f.zoomText=Text(zoomReset,"100%",9,GOLD,"Fonts\\FRIZQT__.TTF")
    f.zoomText:SetPoint("CENTER",0,0)
    zoomReset:SetScript("OnClick",function()SetAtlasZoom(1,false)end)
    zoomReset:SetScript("OnEnter",function()
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Reset Zoom","Return the Atlas to 100% scale without changing your current pan position.",GOLD,"RENOWN ATLAS") end
    end)
    zoomReset:SetScript("OnLeave",function()if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end end)

    local zoomPlus=CreateFrame("Button",nil,zoomPlate,"BackdropTemplate")
    zoomPlus:SetSize(32,24);zoomPlus:SetPoint("RIGHT",-4,0);MakeBackdrop(zoomPlus,{0.05,0.04,0.07,0.96},{0.34,0.30,0.43,0.75},8)
    local zpt=Text(zoomPlus,"+",14,PALE,"Fonts\\FRIZQT__.TTF");zpt:SetPoint("CENTER",0,0)
    zoomPlus:SetScript("OnClick",function()AdjustAtlasZoom(1,false)end)
    zoomPlus:SetScript("OnEnter",function(self)
        self:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.95)
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Zoom In","Magnify the Atlas. Mouse wheel up does the same thing.",GOLD,"RENOWN ATLAS") end
    end)
    zoomPlus:SetScript("OnLeave",function(self)
        self:SetBackdropBorderColor(0.34,0.30,0.43,0.75)
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)

    local close=CreateFrame("Button",nil,top,"BackdropTemplate")
    close:SetSize(124,32);close:SetPoint("RIGHT",-10,0);MakeBackdrop(close,{0.075,0.045,0.055,0.97},{0.55,0.32,0.36,0.9},10)
    local ct=Text(close,"Return to Ledger",9,PALE);ct:SetPoint("CENTER")
    close:SetScript("OnClick",function()
        if RPAL.CloseTalentAtlas then RPAL.CloseTalentAtlas(true) else f:Hide() end
    end)
    close:SetScript("OnEnter",function(self)self:SetBackdropBorderColor(0.95,0.55,0.42,1)end)
    close:SetScript("OnLeave",function(self)self:SetBackdropBorderColor(0.55,0.32,0.36,0.9)end)

    local fullscreen=CreateFrame("Button",nil,top,"BackdropTemplate")
    fullscreen:SetSize(96,32);fullscreen:SetPoint("RIGHT",close,"LEFT",-8,0)
    MakeBackdrop(fullscreen,{0.045,0.040,0.075,0.97},{0.42,0.36,0.64,0.9},10)
    f.fullscreenText=Text(fullscreen,"FULL SCREEN",8.5,PALE);f.fullscreenText:SetPoint("CENTER")
    fullscreen:SetScript("OnClick",function()f:ApplyWindowMode(not f._fullScreen)end)
    fullscreen:SetScript("OnEnter",function(self)
        self:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.95)
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Atlas Display","Toggle between the normal Atlas window and a full-screen workspace.",GOLD,"RENOWN ATLAS") end
    end)
    fullscreen:SetScript("OnLeave",function(self)
        self:SetBackdropBorderColor(0.42,0.36,0.64,0.9)
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)

    recenter:ClearAllPoints();recenter:SetPoint("RIGHT",fullscreen,"LEFT",-8,0)

    local viewport=CreateFrame("Frame",nil,f)
    atlasViewport=viewport
    viewport:SetPoint("TOPLEFT",13,-82);viewport:SetPoint("BOTTOMRIGHT",-13,54)
    viewport:SetClipsChildren(true);viewport:EnableMouse(true);viewport:EnableMouseWheel(true)
    viewport:SetScript("OnMouseWheel",function(_,delta)AdjustAtlasZoom(delta,true)end)

    local canvas=CreateFrame("Frame",nil,viewport)
    atlasCanvas=canvas
    canvas:SetSize(3000,3000);canvas:EnableMouse(true);canvas:EnableMouseWheel(true)
    canvas:SetScript("OnMouseWheel",function(_,delta)AdjustAtlasZoom(delta,true)end)
    canvas:SetScale(atlasZoom)
    UpdatePan();RefreshZoomReadout()

    -- Continuous deep-space canvas. The former nebula field was smaller than
    -- the draggable canvas, so panning could expose a hard black rectangle.
    -- A dark base plus soft, offset nebula blooms keeps the field continuous
    -- and more cosmic without competing with nodes or labels.
    local spaceBase=canvas:CreateTexture(nil,"BACKGROUND",nil,-8)
    spaceBase:SetTexture("Interface\\Buttons\\WHITE8X8")
    spaceBase:SetAllPoints()
    spaceBase:SetVertexColor(0.008,0.010,0.026,1)

    local spaceNebula=canvas:CreateTexture(nil,"BACKGROUND",nil,-7)
    spaceNebula:SetTexture(NEBULA)
    spaceNebula:SetAllPoints()
    spaceNebula:SetAlpha(0.42)

    local spaceBloomA=canvas:CreateTexture(nil,"BACKGROUND",nil,-6)
    spaceBloomA:SetTexture(NEBULA);spaceBloomA:SetSize(2140,1760);spaceBloomA:SetPoint("CENTER",canvas,"CENTER",-520,420)
    spaceBloomA:SetBlendMode("ADD");spaceBloomA:SetAlpha(0.13)
    local spaceBloomB=canvas:CreateTexture(nil,"BACKGROUND",nil,-6)
    spaceBloomB:SetTexture(NEBULA);spaceBloomB:SetSize(1980,1640);spaceBloomB:SetPoint("CENTER",canvas,"CENTER",650,-520)
    spaceBloomB:SetBlendMode("ADD");spaceBloomB:SetAlpha(0.10)

    -- A single non-repeating field replaces the former 3x3 STAR tile grid.
    -- The authored spark sprite is placed at deliberate, irregular positions,
    -- so no circular chart or bright cluster visibly repeats while panning.
    local starMap={
        {-1320,1010,10,.18},{-1090,880,7,.14},{-830,1050,14,.20},{-520,910,8,.15},{-180,1080,11,.18},{210,930,7,.13},{520,1060,16,.22},{870,900,9,.15},{1190,1040,12,.19},
        {-1240,620,8,.14},{-980,500,15,.20},{-670,700,6,.12},{-330,560,12,.17},{40,760,8,.13},{350,520,17,.22},{690,710,7,.13},{1010,540,13,.18},{1300,690,8,.14},
        {-1360,180,13,.18},{-1110,-40,7,.12},{-790,210,10,.15},{-470,-80,16,.21},{-140,190,7,.12},{190,-120,11,.16},{510,170,8,.13},{820,-60,15,.20},{1160,220,7,.12},{1340,-150,12,.17},
        {-1230,-430,7,.13},{-950,-650,14,.19},{-650,-390,9,.14},{-350,-720,17,.21},{-30,-470,7,.12},{280,-690,12,.17},{610,-420,8,.14},{910,-720,15,.20},{1210,-470,9,.14},
        {-1320,-980,11,.17},{-1010,-1050,7,.12},{-720,-880,16,.21},{-400,-1080,8,.13},{-80,-900,12,.17},{240,-1060,7,.12},{560,-860,14,.19},{880,-1040,9,.14},{1190,-890,13,.18},
    }
    f.canvasStars={}
    for _,definition in ipairs(starMap) do
        local star=canvas:CreateTexture(nil,"BACKGROUND",nil,-5)
        star:SetTexture(SPARK)
        star:SetSize(definition[3],definition[3])
        star:SetPoint("CENTER",canvas,"CENTER",definition[1],definition[2])
        star:SetBlendMode("ADD")
        star._rpalBaseAlpha=definition[4]
        star:SetAlpha(definition[4])
        f.canvasStars[#f.canvasStars+1]=star
    end
    local haze=canvas:CreateTexture(nil,"BACKGROUND",nil,-5)
    haze:SetTexture(NEBULA);haze:SetSize(2960,2860);haze:SetPoint("CENTER",0,-70);haze:SetBlendMode("ADD");haze:SetAlpha(0.20)

    -- A neutral, open-center zodiac plate sits behind the node paths. Its hue
    -- follows the active theme and the slow rotation keeps the Atlas alive
    -- without competing with node labels or connections.
    local zodiac=canvas:CreateTexture(nil,"BACKGROUND",nil,-4)
    zodiac:SetTexture(ZODIAC)
    zodiac:SetSize(2160,2160)
    zodiac:SetPoint("CENTER",canvas,"CENTER",0,0)
    zodiac:SetBlendMode("ADD")
    zodiac:SetAlpha(0.125)
    f.zodiac=zodiac

    -- The v5 Atlas keeps four subtle guide rings behind a sparse nearest-neighbour path graph.  These guides are subtle
    -- enough to sit behind nodes while making the circular routing legible.
    local function RingSegment(radius,a1,a2,color,alpha,thickness)
        if not canvas.CreateLine then return end
        local r1=math.rad(a1);local r2=math.rad(a2)
        local line=canvas:CreateLine(nil,"BACKGROUND",nil,-3)
        line:SetThickness(thickness or 1.2)
        line:SetStartPoint("CENTER",canvas,math.cos(r1)*radius,math.sin(r1)*radius)
        line:SetEndPoint("CENTER",canvas,math.cos(r2)*radius,math.sin(r2)*radius)
        line:SetColorTexture(color[1],color[2],color[3],alpha or 0.10)
    end
    local function Ring(radius,color,alpha,thickness)
        for deg=0,355,5 do RingSegment(radius,deg,deg+5,color,alpha,thickness) end
    end
    local function Arc(radius,startDeg,endDeg,color,alpha,thickness)
        local step=4
        local d=startDeg
        while d<endDeg do RingSegment(radius,d,math.min(endDeg,d+step),color,alpha,thickness);d=d+step end
    end
    local neutral={0.39,0.36,0.52}
    for _,radius in ipairs({245,470,720,1010}) do Ring(radius,neutral,0.075,1.0) end
    local cc=BranchColor("courtesy");local fc=BranchColor("fellowship");local lc=BranchColor("chronicle");local xc=BranchColor("confluence")
    for _,radius in ipairs({470,720,1010}) do
        Arc(radius,104,198,cc,0.105,1.6)
        Arc(radius,350,360,fc,0.105,1.6);Arc(radius,0,82,fc,0.105,1.6)
        Arc(radius,210,335,lc,0.105,1.6)
    end
    Arc(790,72,108,xc,0.20,2.0);Arc(790,330,354,xc,0.20,2.0);Arc(790,188,208,xc,0.20,2.0)

    CreateBranchCaption("courtesy",-760,840,"CENTER")
    CreateBranchCaption("fellowship",760,840,"CENTER")
    CreateBranchCaption("chronicle",0,-1160,"CENTER")

    connectionLines={}
    for _,id in ipairs(RPAL.TALENT_NODE_ORDER or {}) do
        local node=RPAL.TALENT_NODES[id]
        local links=node and (node.visualParents or node.parents)
        if node and links then
            for _,parentID in ipairs(links) do
                local parent=RPAL.TALENT_NODES[parentID]
                if parent then
                    local dx=(tonumber(node.x) or 0)-(tonumber(parent.x) or 0)
                    local dy=(tonumber(node.y) or 0)-(tonumber(parent.y) or 0)
                    local distance=math.sqrt(dx*dx+dy*dy)
                    if distance<=(tonumber(RPAL.TALENT_LINK_MAX_DISTANCE) or 315) then CreateConnection(node,parent) end
                end
            end
        end
    end

    nodeFrames={}
    for index,id in ipairs(RPAL.TALENT_NODE_ORDER or {}) do
        local node=RPAL.TALENT_NODES[id]
        if node then CreateNode(node,index) end
    end

    local hint=Text(f,"Left click: learn  •  Right click: refund an unneeded endpoint  •  Mouse wheel: zoom  •  Junctions merge paths",8,MUTED)
    hint:SetPoint("BOTTOM",0,18)

    local respec=CreateFrame("Button",nil,f,"BackdropTemplate")
    respec:SetSize(124,28);respec:SetPoint("BOTTOMRIGHT",-20,10)
    MakeBackdrop(respec,{0.055,0.035,0.045,0.97},{0.55,0.32,0.36,0.80},9)
    local rst=Text(respec,"RESET ATLAS",8,PALE);rst:SetPoint("CENTER")
    respec:SetScript("OnEnter",function(self)
        self:SetBackdropBorderColor(0.92,0.58,0.42,1)
        if RPAL.ShowSimpleCursorTooltip then RPAL.ShowSimpleCursorTooltip("Reset Atlas","Refund every active Talent Point and clear build choices. Cosmetic rewards and unveiled Memoirs remain permanently unlocked.",{0.92,0.58,0.42},"RENOWN ATLAS") end
    end)
    respec:SetScript("OnLeave",function(self)
        self:SetBackdropBorderColor(0.55,0.32,0.36,0.80)
        if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
    end)
    respec:SetScript("OnClick",function()
        local dialog=EnsureResetDialog(f)
        if dialog then dialog:Show() elseif RPAL.RespecTalents then RPAL.RespecTalents() end
    end)

    local function DragDown(_,button) if button=="LeftButton" then StartPan() end end
    local function DragUp(_,button) if button=="LeftButton" then StopPan() end end
    viewport:SetScript("OnMouseDown",DragDown);viewport:SetScript("OnMouseUp",DragUp)
    canvas:SetScript("OnMouseDown",DragDown);canvas:SetScript("OnMouseUp",DragUp)
    f:SetScript("OnUpdate",function(_,elapsed)UpdateDrag();AtlasPulse(elapsed)end)
    f:SetScript("OnShow",function()RefreshAtlas();HideNodeTooltip()end)
    f:SetScript("OnHide",function(self)
        dragging=false
        HideNodeTooltip()
        if resetDialog then resetDialog:Hide() end
        self._restoreLedgerOnHide=nil
        -- The main ODYN desk remains visible beneath the Atlas. This avoids the
        -- visually jarring blank frame between closing one surface and opening another.
    end)

    local already=false
    for _,name in ipairs(UISpecialFrames or {}) do if name=="RPAL_TalentAtlas" then already=true break end end
    if not already then table.insert(UISpecialFrames,"RPAL_TalentAtlas") end

    f:Hide()
    RefreshAtlas()
    return f
end

function RPAL.CloseTalentAtlas(restoreLedger)
    if not atlas then return end
    atlas._restoreLedgerOnHide=(restoreLedger~=false) and atlas._returnMainWasShown or false
    atlas:Hide()
end

function RPAL.OpenTalentAtlas()
    if RPAL.IsLiteKit and RPAL.IsLiteKit() then
        if RPAL.ShowSessionStatusToast then
            RPAL.ShowSessionStatusToast("Talent Atlas is disabled in Lite Kit. Re-run Setup from Settings to switch to Full Kit.","PAUSED")
        else
            print("|cffffcc66ODYN:|r Talent Atlas is disabled in Lite Kit. Re-run Setup from Settings to switch to Full Kit.")
        end
        return false
    end
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
    local f=BuildAtlas()
    if not f then
        print("|cffff6666ODYN:|r Talent Atlas could not be created.")
        return false
    end

    f._returnMainFrame=main
    f._returnMainWasShown=main and main.IsShown and main:IsShown() or false
    f._restoreLedgerOnHide=false

    if f.ApplyWindowMode then
        f:ApplyWindowMode(f._fullScreen==true)
    elseif main then
        if main.GetScale then f:SetScale(main:GetScale() or 1) end
        f:ClearAllPoints()
        f:SetPoint("CENTER",main,"CENTER",0,0)
    else
        f:ClearAllPoints()
        f:SetPoint("CENTER",UIParent,"CENTER",0,0)
    end

    if RPAL.ApplyTalentRewards then RPAL.ApplyTalentRewards() end
    RefreshAtlas()

    -- Atlas is an overlay surface; keep the ODYN desk visible behind it so
    -- switching between the two feels continuous instead of flashing empty UI.
    if RPAL.PrepareWindow then RPAL.PrepareWindow(f,nil,0.16) else f:Show() end
    return true
end

function RPAL.ToggleTalentAtlas()
    if atlas and atlas:IsShown() then
        RPAL.CloseTalentAtlas(true)
    else
        RPAL.OpenTalentAtlas()
    end
end

function RPAL.BindTalentAtlasButton(button)
    if not button then return end
    button:RegisterForClicks("LeftButtonUp")
    button:SetScript("OnClick",function()
        RPAL.ToggleTalentAtlas()
    end)
end

if RPAL.TalentButton then RPAL.BindTalentAtlasButton(RPAL.TalentButton) end

SLASH_RPALTALENTATLAS1="/odynatlas"
SLASH_RPALTALENTATLAS2="/ralatlas"
SlashCmdList["RPALTALENTATLAS"]=function()
    if RPAL.ToggleTalentAtlas then RPAL.ToggleTalentAtlas() end
end

RPAL.OnTalentChanged=function(id,node)
    RefreshAtlas()
    RefreshTalentButton()
end

-- Premium memoir presentation used by remote inspect ----------------------
local function ShowMemoirTooltip(anchor,def,accent)
    if not RPAL.ShowCursorTooltip then return end
    accent=accent or GOLD
    local branch=RPAL.TALENT_BRANCHES and RPAL.TALENT_BRANCHES[def.branch]
    RPAL.ShowCursorTooltip({
        eyebrow="MEMOIR  •  "..string.upper((branch and branch.name) or "RENOWN ATLAS"),
        title=def.title or "Memoir",
        body="“"..tostring(def.quote or def.subtitle or "").."”",
        sectionTitle="PUBLIC LEGACY",
        section="Displayed in the Memoirs showcase when another ODYN user inspects this character.",
        accent=accent,
        width=390,
    })
end

function RPAL.RenderMemoirDock(dock,entries,accent,palette)
    if not dock then return end
    accent=accent or GOLD
    palette=type(palette)=="table" and palette or {}
    local dockBg=palette.main or INK
    local cardBg=palette.panel2 or INK2
    local pale=palette.pale or PALE
    local muted=palette.muted or MUTED
    local list={}
    if type(entries)=="table" then
        for key,value in pairs(entries) do
            local id=type(value)=="table" and value.id or (type(key)=="string" and key or value)
            local at=type(value)=="table" and value.unlockedAt or 0
            local def=RPAL.GetMemoirDefinition and RPAL.GetMemoirDefinition(id)
            if def then list[#list+1]={def=def,unlockedAt=tonumber(at) or 0} end
        end
    end
    table.sort(list,function(a,b)
        if a.unlockedAt~=b.unlockedAt then return a.unlockedAt<b.unlockedAt end
        return (a.def.title or "")<(b.def.title or "")
    end)

    -- Inspect data arrives packet-by-packet.  Keep an unchanged Memoir shelf
    -- intact instead of recreating its cards (and their visual state) for every
    -- unrelated statistics or achievement packet.
    local signature={
        tostring(tonumber(dock._expected) or 0),tostring(dock._finished and 1 or 0),
        string.format("%.3f:%.3f:%.3f",accent[1] or 0,accent[2] or 0,accent[3] or 0),
        string.format("%.3f:%.3f:%.3f",dockBg[1] or 0,dockBg[2] or 0,dockBg[3] or 0)
    }
    for _,item in ipairs(list) do
        signature[#signature+1]=tostring(item.def.id or item.def.title or "")..":"..tostring(item.unlockedAt or 0)
    end
    local renderKey=table.concat(signature,"\030")
    if dock._rpalMemoirRenderKey==renderKey then return end
    dock._rpalMemoirRenderKey=renderKey

    if dock._content then dock._content:Hide() end
    local content=CreateFrame("Frame",nil,dock)
    dock._content=content;content:SetAllPoints()
    content:SetFrameLevel((dock.GetFrameLevel and dock:GetFrameLevel() or 1)+2)
    content:Show()

    MakeBackdrop(dock,{dockBg[1],dockBg[2],dockBg[3],0.97},{accent[1],accent[2],accent[3],0.62},13)
    local bg=content:CreateTexture(nil,"BACKGROUND",nil,-1);bg:SetTexture(STAR);bg:SetAllPoints();bg:SetAlpha(0.08)

    local title=Text(content,"MEMOIRS",11,accent,"Fonts\\FRIZQT__.TTF");title:SetPoint("TOPLEFT",12,-9)
    local sub=Text(content,"RENOWN ATLAS LEGACY",7.5,muted);sub:SetPoint("TOPRIGHT",-12,-11)
    local rule=content:CreateTexture(nil,"ARTWORK");rule:SetTexture("Interface\\Buttons\\WHITE8X8");rule:SetHeight(1);rule:SetPoint("TOPLEFT",12,-32);rule:SetPoint("TOPRIGHT",-12,-32);rule:SetVertexColor(accent[1],accent[2],accent[3],0.25)

    if #list==0 then
        local expected=tonumber(dock._expected) or 0
        local finished=dock._finished and true or false
        local message
        if expected>0 and not finished then
            message="Receiving "..tostring(expected).." public Memoir"..(expected==1 and "..." or "s...")
        elseif expected>0 then
            message="Memoir data was announced but has not arrived yet."
        else
            message=finished and "No memoir has been unveiled on this character yet." or "Receiving Memoirs..."
        end
        local empty=Text(content,message,9.5,muted)
        empty:SetPoint("TOPLEFT",12,-52);empty:SetPoint("RIGHT",-12,0)
        empty:SetJustifyH("LEFT")
        return
    end

    local count=math.min(6,#list)
    local columns=count<=4 and 2 or 3
    local availableWidth=math.max(360,(dock.GetWidth and dock:GetWidth() or 438)-24)
    local gap=8
    local cardWidth=math.floor((availableWidth-((columns-1)*gap))/columns)
    for i,item in ipairs(list) do
        if i>6 then break end
        local row=math.floor((i-1)/columns);local col=(i-1)%columns
        local def=item.def
        local bc=BranchColor(def.branch)
        local card=CreateFrame("Button",nil,content,"BackdropTemplate")
        card:SetSize(cardWidth,38);card:SetPoint("TOPLEFT",12+col*(cardWidth+gap),-40-row*44)
        MakeBackdrop(card,{cardBg[1],cardBg[2],cardBg[3],0.94},{bc[1],bc[2],bc[3],0.38},8)
        local accentBar=card:CreateTexture(nil,"ARTWORK");accentBar:SetTexture("Interface\\Buttons\\WHITE8X8");accentBar:SetWidth(3);accentBar:SetPoint("TOPLEFT",1,-3);accentBar:SetPoint("BOTTOMLEFT",1,3);accentBar:SetVertexColor(bc[1],bc[2],bc[3],0.92)
        local med=card:CreateTexture(nil,"ARTWORK");med:SetTexture(RPAL.ICONS.book);med:SetSize(26,26);med:SetPoint("LEFT",7,0);med:SetVertexColor(bc[1],bc[2],bc[3],0.88)
        local nt=Text(card,def.title or "Memoir",8.6,pale,"Fonts\\FRIZQT__.TTF");nt:SetPoint("TOPLEFT",38,-7);nt:SetPoint("RIGHT",-5,0);nt:SetJustifyH("LEFT");nt:SetWordWrap(false)
        local branch=RPAL.TALENT_BRANCHES and RPAL.TALENT_BRANCHES[def.branch]
        local meta=Text(card,string.upper((branch and branch.name) or "RENOWN ATLAS"),6.8,muted);meta:SetPoint("TOPLEFT",38,-23);meta:SetPoint("RIGHT",-5,0);meta:SetWordWrap(false)
        local currentDef=def
        local currentColor={bc[1],bc[2],bc[3],bc[4]}
        card:SetScript("OnEnter",function(self) self:SetBackdropBorderColor(currentColor[1],currentColor[2],currentColor[3],0.95);ShowMemoirTooltip(self,currentDef,currentColor) end)
        card:SetScript("OnLeave",function(self) self:SetBackdropBorderColor(currentColor[1],currentColor[2],currentColor[3],0.38);if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end end)
        card:SetScript("OnClick",function()
            if RPAL.HideCursorTooltip then RPAL.HideCursorTooltip() end
            if RPAL.OpenMemoirDetail then RPAL.OpenMemoirDetail(currentDef,currentColor) end
        end)
    end
end

-- Preserve the existing level-up sound/UI and refresh Atlas point state.
local previousLevelUp=RPAL.OnRenownLevelUp
RPAL.OnRenownLevelUp=function(newLevel,oldLevel,source,rewards)
    if previousLevelUp then previousLevelUp(newLevel,oldLevel,source,rewards) end
    RefreshTalentButton()
    if atlas and atlas:IsShown() then RefreshAtlas() end
end

-- Delay reward reconciliation until Core.InitializeDB has established RPAL.char.
local boot=CreateFrame("Frame")
boot:RegisterEvent("PLAYER_LOGIN")
boot:SetScript("OnEvent",function()
    C_Timer.After(1.2,function()
        if RPAL.GetTalentState then RPAL.GetTalentState() end
        if RPAL.ApplyTalentRewards then RPAL.ApplyTalentRewards() end
        RefreshTalentButton()
    end)
end)
