local ADDON_NAME, RPAL = ...

local TEX="Interface\\AddOns\\ODYN\\Textures\\"
local STAR=TEX.."Talent\\atlas_stars.tga"
local NEBULA=TEX.."Talent\\atlas_nebula.tga"
local ZODIAC=TEX.."Talent\\atlas_zodiac.tga"
local SPARK=TEX.."Talent\\sparkle.tga"
local NODE=TEX.."Talent\\node_reward.tga"
local PORTRAIT_REWARD=TEX.."Cosmetics\\portrait_open_beta.tga"
local BETA_BADGE=TEX.."Cosmetics\\Badges2\\open_beta_crest.tga"

local INK={0.012,0.014,0.040,0.985}
local INK2={0.024,0.028,0.072,0.965}
local INK3={0.043,0.046,0.105,0.96}
local VIOLET={0.66,0.50,1.00}
local BLUE={0.40,0.66,1.00}
local CYAN={0.47,0.88,1.00}
local PALE={0.93,0.92,1.00}
local MUTED={0.60,0.61,0.75}
local GREEN={0.46,0.95,0.67}
local GOLD={0.94,0.76,0.36}
local BORDER={0.28,0.26,0.55,1}

local FALL_INK={0.045,0.021,0.010,0.992}
local FALL_PANEL={0.090,0.039,0.016,0.965}
local FALL_PANEL2={0.132,0.054,0.019,0.955}
local FALL_ORANGE={0.96,0.47,0.10}
local FALL_GOLD={0.98,0.69,0.25}
local FALL_PALE={0.98,0.91,0.79}
local FALL_MUTED={0.70,0.54,0.39}
local FALL_BORDER={0.54,0.25,0.075,0.94}
local FALL_BANNER=TEX.."parchment_bar.tga"
local FALL_ARCHIVE=TEX.."Interaction\\large02_corner.tga"

local seasonFrame
local levelFrame
local mainStrip
local levelCard
local refreshTicker

local LEVEL_REWARD_ICONS={
    ["Initiate Seal"]=TEX.."Cosmetics\\Badges2\\initiate_seal.tga",
    ["Verdant Mark"]=TEX.."Cosmetics\\Badges2\\verdant_mark.tga",
    ["Gilded"]=TEX.."Cosmetics\\portrait_gilded.tga",
    ["Kaldorei"]=TEX.."Cosmetics\\header_kaldorei.tga",
    ["Azure Sigil"]=TEX.."Cosmetics\\Badges2\\azure_sigil.tga",
    ["Cobalt Arc"]="Interface\\Icons\\Spell_Arcane_Arcane01",
    ["Fireflies"]=SPARK,
    ["Amethyst Crest"]=TEX.."Cosmetics\\Badges2\\amethyst_crest.tga",
    ["Frost Runes"]=TEX.."Cosmetics\\ornament_frost_seal.tga",
    ["Rose Emblem"]=TEX.."Cosmetics\\Badges2\\rose_emblem.tga",
    ["Moonlit"]=TEX.."Cosmetics\\aura_soft_ring.tga",
    ["Sunforged Seal"]=TEX.."Cosmetics\\Badges2\\sunforged_seal.tga",
    ["Ember"]=TEX.."Cosmetics\\header_ember.tga",
    ["Crimson Legend"]=TEX.."Cosmetics\\Badges2\\crimson_legend.tga",
    ["Solar Flare"]="Interface\\Icons\\Spell_Holy_SearingLight",
}

local function IsLite()
    return RPAL.IsLiteKit and RPAL.IsLiteKit()
end

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

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

local function Button(parent,label,w,h)
    local b=CreateFrame("Button",nil,parent,"BackdropTemplate")
    b:SetSize(w or 100,h or 28);MakeBackdrop(b,{0.052,0.055,0.125,0.98},BORDER,10)
    b.label=Text(b,label or "",9,PALE);b.label:SetPoint("CENTER",0,1)
    b:SetScript("OnEnter",function(self)self:SetBackdropColor(0.085,0.080,0.18,0.99);self:SetBackdropBorderColor(VIOLET[1],VIOLET[2],VIOLET[3],0.95)end)
    b:SetScript("OnLeave",function(self)self:SetBackdropColor(0.052,0.055,0.125,0.98);self:SetBackdropBorderColor(BORDER[1],BORDER[2],BORDER[3],1)end)
    if RPAL.AttachButtonMotion then RPAL.AttachButtonMotion(b,0.018) end
    if RPAL.AttachFocusMark then RPAL.AttachFocusMark(b,VIOLET) end
    return b
end

local function ClearHost(host)
    if not host then return end
    local children={host:GetChildren()}
    for _,child in ipairs(children) do child:Hide();child:SetParent(nil) end
end

local function SeasonDateRange(season)
    if not season then return "" end
    local endDisplay=math.max(season.startAt,season.endAt-1)
    return date("%b %d",season.startAt).." — "..date("%b %d, %Y",endDisplay)
end

local function FormatRemaining(seconds,compact)
    seconds=math.max(0,math.floor(tonumber(seconds) or 0))
    local days=math.floor(seconds/86400);seconds=seconds-(days*86400)
    local hours=math.floor(seconds/3600);seconds=seconds-(hours*3600)
    local minutes=math.floor(seconds/60)
    if compact then
        if days>0 then return string.format("%dD %02dH LEFT",days,hours) end
        if hours>0 then return string.format("%dH %02dM LEFT",hours,minutes) end
        return string.format("%dM LEFT",minutes)
    end
    if days>0 then return string.format("%dD %02dH %02dM UNTIL SEASON END",days,hours,minutes) end
    if hours>0 then return string.format("%dH %02dM UNTIL SEASON END",hours,minutes) end
    return string.format("%dM UNTIL SEASON END",minutes)
end

local function RewardTitle(reward)
    if not reward then return "Season Start" end
    return reward.label or reward.name or (reward.amount and (tostring(reward.amount).." Tokens")) or "Season Reward"
end

local function RewardKindLabel(reward)
    if not reward then return "SEASON" end
    if reward.kind=="token" then return "TOKENS" end
    if reward.kind=="packs" then return "PACKS" end
    if reward.kind=="theme" then return "THEME" end
    local labels={portrait="PORTRAIT",level="BADGE",background="ATMOSPHERE",header="HEADER",aura="AURA",effect="EFFECT",ornament="ORNAMENT",toast="TOAST FRAME",tooltip="TOOLTIP",focus="FOCUS MARK"}
    return labels[reward.cosmeticKind] or "COSMETIC"
end

local function LevelKindLabel(kind)
    local labels={level="BADGE",portrait="PORTRAIT",header="HEADER",theme="THEME",effect="EFFECT",ornament="ORNAMENT",aura="AURA",background="BACKGROUND"}
    return labels[tostring(kind or "")] or "REWARD"
end

local function CreateStars(frame,accent)
    accent=accent or VIOLET
    local stars=frame:CreateTexture(nil,"BACKGROUND",nil,-4)
    stars:SetTexture(STAR);stars:SetAllPoints();stars:SetAlpha(0.31)
    local nebula=frame:CreateTexture(nil,"BACKGROUND",nil,-3)
    nebula:SetTexture(NEBULA);nebula:SetPoint("TOPLEFT",20,-20);nebula:SetPoint("BOTTOMRIGHT",-20,20)
    nebula:SetVertexColor(accent[1],accent[2],accent[3],0.24);nebula:SetBlendMode("ADD")
    local zodiac=frame:CreateTexture(nil,"BACKGROUND",nil,-2)
    zodiac:SetTexture(ZODIAC);zodiac:SetSize(620,620);zodiac:SetPoint("CENTER",310,20)
    zodiac:SetVertexColor(accent[1],math.min(1,accent[2]+0.10),1.00,0.105);zodiac:SetBlendMode("ADD")
    frame._seasonZodiac=zodiac

    local particles={}
    for i=1,18 do
        local p=frame:CreateTexture(nil,"ARTWORK",nil,0)
        p:SetTexture(SPARK);p:SetBlendMode("ADD")
        local size=4+(i%4)*2;p:SetSize(size,size)
        local useBlue=(i%3==0)
        p:SetVertexColor(useBlue and BLUE[1] or accent[1],useBlue and BLUE[2] or accent[2],1,0.38)
        particles[#particles+1]=p
    end
    frame._seasonParticles=particles
    frame._seasonPhase=0
    frame:SetScript("OnUpdate",function(self,elapsed)
        self._seasonPhase=(self._seasonPhase or 0)+(elapsed or 0)
        local t=self._seasonPhase
        if self._seasonZodiac and self._seasonZodiac.SetRotation then self._seasonZodiac:SetRotation(t*0.012) end
        local w,h=math.max(1,self:GetWidth()),math.max(1,self:GetHeight())
        for i,p in ipairs(self._seasonParticles or {}) do
            local x=((i*83 + t*(5+(i%4)))%(math.max(1,w-80)))-w/2+40
            local y=((i*47 + math.sin(t*0.35+i)*36)%(math.max(1,h-80)))-h/2+40
            p:ClearAllPoints();p:SetPoint("CENTER",self,"CENTER",x,y)
            p:SetAlpha(0.15+((math.sin(t*(0.7+i*0.03)+i)+1)*0.16))
        end
    end)
end

local function HideLegacyProgressionWidgets()
    local UI=RPAL.UI
    if not UI then return end
    for _,widget in ipairs({UI.levelText,UI.levelSubText,UI.renownBarTitle,UI.renownBar,UI.nextLevelText,UI.nextLevelNeed,UI.progressionDivider}) do
        if widget and widget.Hide then widget:Hide() end
    end
end

local function BuildProgressCards()
    local UI=RPAL.UI
    if not UI or not UI.main or not UI.progression then return nil,nil end
    local progression=UI.progression

    if not levelCard then
        local card=CreateFrame("Button",nil,progression,"BackdropTemplate")
        levelCard=card;UI.levelProgressCard=card
        card:SetPoint("TOPLEFT",8,-7);card:SetPoint("BOTTOMLEFT",8,7);card:SetPoint("RIGHT",progression,"CENTER",-4,0)
        card:SetFrameLevel((progression:GetFrameLevel() or 0)+20);card:EnableMouse(true);card:RegisterForClicks("LeftButtonUp")
        MakeBackdrop(card,{0.018,0.040,0.052,0.99},{0.26,0.58,0.68,0.95},10)
        local haze=card:CreateTexture(nil,"BACKGROUND");haze:SetTexture(NEBULA);haze:SetAllPoints();haze:SetVertexColor(0.20,0.62,0.72,0.10);haze:SetBlendMode("ADD")
        card.eyebrow=Text(card,"CHARACTER PROGRESSION",7.6,CYAN);card.eyebrow:SetPoint("TOPLEFT",13,-8)
        card.title=Text(card,"LEVEL 1",15.5,PALE,"Fonts\\FRIZQT__.TTF");card.title:SetPoint("TOPLEFT",13,-23)
        card.next=Text(card,"NEXT: 2",8.2,GOLD);card.next:SetPoint("TOPRIGHT",-14,-9)
        card.open=Text(card,"VIEW LEVEL REWARDS  ›",7.8,CYAN);card.open:SetPoint("TOPRIGHT",-14,-27)
        card.bar=CreateFrame("StatusBar",nil,card);card.bar:SetPoint("BOTTOMLEFT",13,9);card.bar:SetPoint("BOTTOMRIGHT",-13,9);card.bar:SetHeight(14)
        card.bar:SetStatusBarTexture("Interface\\TARGETINGFRAME\\UI-StatusBar");card.bar:SetStatusBarColor(CYAN[1],CYAN[2],CYAN[3],0.94)
        local bg=card.bar:CreateTexture(nil,"BACKGROUND");bg:SetAllPoints();bg:SetTexture("Interface\\Buttons\\WHITE8X8");bg:SetVertexColor(0.035,0.075,0.090,0.98)
        card.barText=Text(card.bar,"",8.2,PALE);card.barText:SetPoint("CENTER",0,0)
        card:SetScript("OnClick",function() if RPAL.OpenLevelRewardsUI then RPAL.OpenLevelRewardsUI() end end)
        card:SetScript("OnEnter",function(self)
            local accent=self._curveCatchupActive and GOLD or CYAN
            self:SetBackdropBorderColor(accent[1],accent[2],accent[3],1)
            self.open:SetTextColor(0.96,0.90,0.66)
        end)
        card:SetScript("OnLeave",function(self)
            local accent=self._curveCatchupActive and GOLD or {0.26,0.58,0.68}
            self:SetBackdropBorderColor(accent[1],accent[2],accent[3],0.95)
            self.open:SetTextColor(unpack(self._curveCatchupActive and GOLD or CYAN))
        end)
        if RPAL.AttachButtonMotion then RPAL.AttachButtonMotion(card,0.022) end
    end

    if not mainStrip then
        local strip=CreateFrame("Button",nil,progression,"BackdropTemplate")
        mainStrip=strip;UI.seasonStrip=strip
        strip:SetPoint("TOPRIGHT",-8,-7);strip:SetPoint("BOTTOMRIGHT",-8,7);strip:SetPoint("LEFT",progression,"CENTER",4,0)
        strip:SetFrameLevel((progression:GetFrameLevel() or 0)+20);strip:EnableMouse(true);strip:RegisterForClicks("LeftButtonUp")
        MakeBackdrop(strip,{0.021,0.023,0.070,0.99},BORDER,10)
        local star=strip:CreateTexture(nil,"BACKGROUND",nil,-1);star:SetTexture(STAR);star:SetAllPoints();star:SetAlpha(0.22)
        local haze=strip:CreateTexture(nil,"BACKGROUND");haze:SetTexture(NEBULA);haze:SetAllPoints();haze:SetVertexColor(0.48,0.32,1.0,0.17);haze:SetBlendMode("ADD")
        strip.eyebrow=Text(strip,"PRESEASON",7.6,VIOLET);strip.eyebrow:SetPoint("TOPLEFT",13,-8)
        strip.title=Text(strip,"Open Beta 2026",13.8,PALE,"Fonts\\FRIZQT__.TTF");strip.title:SetPoint("TOPLEFT",13,-23)
        strip.level=Text(strip,"SEASON LEVEL 1 / 10",8.0,VIOLET);strip.level:SetPoint("TOPRIGHT",-14,-9)
        strip.remaining=Text(strip,"",7.8,MUTED);strip.remaining:SetPoint("TOPRIGHT",-14,-27)
        strip.bar=CreateFrame("StatusBar",nil,strip);strip.bar:SetPoint("BOTTOMLEFT",13,9);strip.bar:SetPoint("BOTTOMRIGHT",-118,9);strip.bar:SetHeight(14)
        strip.bar:SetStatusBarTexture("Interface\\TARGETINGFRAME\\UI-StatusBar");strip.bar:SetStatusBarColor(VIOLET[1],VIOLET[2],VIOLET[3],0.98)
        local bg=strip.bar:CreateTexture(nil,"BACKGROUND");bg:SetAllPoints();bg:SetTexture("Interface\\Buttons\\WHITE8X8");bg:SetVertexColor(0.07,0.08,0.16,0.98)
        strip.barText=Text(strip.bar,"",8.2,PALE);strip.barText:SetPoint("CENTER",0,0)
        strip.open=Text(strip,"OPEN SEASON  ›",7.8,BLUE);strip.open:SetPoint("BOTTOMRIGHT",-14,12)
        strip:SetScript("OnClick",function()
            if RPAL.OpenSeasonsUI then RPAL.OpenSeasonsUI() else print("|cffff6666ODYN:|r Seasons UI is not available.") end
        end)
        strip:SetScript("OnEnter",function(self)local c=self._seasonAccent or VIOLET;self:SetBackdropBorderColor(c[1],c[2],c[3],1);local p=self._seasonPale or {0.72,0.82,1.00};self.open:SetTextColor(p[1],p[2],p[3])end)
        strip:SetScript("OnLeave",function(self)local c=self._seasonBorder or BORDER;self:SetBackdropBorderColor(c[1],c[2],c[3],c[4] or 1);local p=self._seasonOpen or BLUE;self.open:SetTextColor(p[1],p[2],p[3])end)
        if RPAL.AttachButtonMotion then RPAL.AttachButtonMotion(strip,0.022) end
    end

    HideLegacyProgressionWidgets()
    return levelCard,mainStrip
end

local function RefreshMainStrip()
    local level,strip=BuildProgressCards()
    if not level or not strip then return end
    if IsLite() then level:Hide();strip:Hide();return end

    level:Show();strip:Show();HideLegacyProgressionWidgets()
    local current,progress,needed=RPAL.GetCurrentRenownLevel and RPAL.GetCurrentRenownLevel() or 1,0,1
    if RPAL.GetCurrentRenownLevel then current,progress,needed=RPAL.GetCurrentRenownLevel() end
    current=math.max(1,tonumber(current) or 1);progress=math.max(0,tonumber(progress) or 0);needed=math.max(1,tonumber(needed) or 1)
    level.title:SetText("LEVEL "..tostring(current))
    level.next:SetText("NEXT: "..tostring(current+1))
    level.bar:SetMinMaxValues(0,needed);level.bar:SetValue(progress)
    level.barText:SetText(tostring(math.floor(progress)).." / "..tostring(math.floor(needed)).." RENOWN")
    local catchup=RPAL.GetRenownCurveCatchup and RPAL.GetRenownCurveCatchup()
    local remaining=catchup and math.max(0,math.floor(tonumber(catchup.sessionsRemaining) or 0)) or 0
    level._curveCatchupActive=remaining>0
    if level._curveCatchupActive then
        level.open:SetText("+"..tostring(math.floor(tonumber(catchup.percent) or 15)).."% RENOWN  •  "..tostring(remaining).." SESSIONS")
        level.open:SetTextColor(unpack(GOLD))
        level:SetBackdropBorderColor(GOLD[1],GOLD[2],GOLD[3],0.95)
    else
        level.open:SetText("VIEW LEVEL REWARDS  ›")
        level.open:SetTextColor(unpack(CYAN))
        level:SetBackdropBorderColor(0.26,0.58,0.68,0.95)
    end

    local snap=RPAL.GetSeasonSnapshot and RPAL.GetSeasonSnapshot()
    if not snap then strip:Hide();return end
    local isFall=snap.season and snap.season.id=="fall-2026"
    strip._seasonAccent=isFall and FALL_ORANGE or VIOLET;strip._seasonBorder=isFall and FALL_BORDER or BORDER;strip._seasonOpen=isFall and FALL_GOLD or BLUE;strip._seasonPale=isFall and FALL_PALE or {0.72,0.82,1.00}
    if isFall then
        strip:SetBackdropColor(.075,.029,.011,.99);strip:SetBackdropBorderColor(FALL_BORDER[1],FALL_BORDER[2],FALL_BORDER[3],FALL_BORDER[4] or 1);strip.bar:SetStatusBarColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],.98);strip.eyebrow:SetTextColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3]);strip.level:SetTextColor(FALL_GOLD[1],FALL_GOLD[2],FALL_GOLD[3]);strip.open:SetTextColor(FALL_GOLD[1],FALL_GOLD[2],FALL_GOLD[3]);strip.open:SetText("OPEN FALL 2026  ›")
    else
        strip:SetBackdropColor(.021,.023,.070,.99);strip:SetBackdropBorderColor(BORDER[1],BORDER[2],BORDER[3],1);strip.bar:SetStatusBarColor(VIOLET[1],VIOLET[2],VIOLET[3],.98);strip.eyebrow:SetTextColor(VIOLET[1],VIOLET[2],VIOLET[3]);strip.level:SetTextColor(VIOLET[1],VIOLET[2],VIOLET[3]);strip.open:SetTextColor(BLUE[1],BLUE[2],BLUE[3]);strip.open:SetText("OPEN SEASON  ›")
    end
    strip.eyebrow:SetText(snap.season.kind or "SEASON")
    strip.title:SetText(snap.season.name or "ODYN Season")
    strip.level:SetText("SEASON LV "..tostring(snap.level).." / "..tostring(snap.season.maxLevel or 1))
    strip.bar:SetMinMaxValues(0,math.max(1,snap.needed or 1));strip.bar:SetValue(snap.progress or 0)
    if snap.level>=(snap.season.maxLevel or 1) then strip.barText:SetText("SEASON COMPLETE")
    else strip.barText:SetText(tostring(snap.progress or 0).." / "..tostring(snap.needed or 1).." SXP") end
    if snap.active then strip.remaining:SetText(FormatRemaining(snap.remaining,true))
    elseif snap.ended then strip.remaining:SetText("SEASON ARCHIVED")
    else strip.remaining:SetText("COMING SOON") end
end
RPAL.RefreshSeasonStrip=RefreshMainStrip
RPAL.RefreshProgressionCards=RefreshMainStrip

local function SyncReplacementGeometry(f,main)
    if not f then return end
    main=main or (RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
    local w=main and main.GetWidth and tonumber(main:GetWidth()) or 1240
    local h=main and main.GetHeight and tonumber(main:GetHeight()) or 760
    if not w or w<900 then w=1240 end
    if not h or h<650 then h=760 end
    f:SetSize(w,h)
    if main and main.GetScale then f:SetScale(main:GetScale() or 1) else f:SetScale(1) end
    f:ClearAllPoints();f:SetPoint("CENTER",main or UIParent,"CENTER",0,0)
end

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


local function FallButton(parent,label,w,h)
    local b=CreateFrame("Button",nil,parent,"BackdropTemplate");b:SetSize(w or 100,h or 28);MakeBackdrop(b,{0.105,0.041,0.014,0.98},FALL_BORDER,10)
    b.label=Text(b,label or "",9,FALL_GOLD);b.label:SetPoint("CENTER",0,1);b._fallActive=false
    local function Apply(self)
        if self._fallActive then
            self:SetBackdropColor(.235,.082,.018,.995);self:SetBackdropBorderColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],1);self.label:SetTextColor(FALL_PALE[1],FALL_PALE[2],FALL_PALE[3],1)
        else
            self:SetBackdropColor(.105,.041,.014,.98);self:SetBackdropBorderColor(FALL_BORDER[1],FALL_BORDER[2],FALL_BORDER[3],FALL_BORDER[4] or 1);self.label:SetTextColor(FALL_GOLD[1],FALL_GOLD[2],FALL_GOLD[3],1)
        end
    end
    function b:SetActive(active)self._fallActive=active and true or false;Apply(self)end
    b:SetScript("OnEnter",function(self)self:SetBackdropColor(.18,.070,.020,.99);self:SetBackdropBorderColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],.98);self.label:SetTextColor(FALL_PALE[1],FALL_PALE[2],FALL_PALE[3],1)end)
    b:SetScript("OnLeave",Apply)
    if RPAL.AttachButtonMotion then RPAL.AttachButtonMotion(b,0.018) end
    return b
end

local function CreateFallAmbience(frame)
    local haze=frame:CreateTexture(nil,"BACKGROUND",nil,-4);haze:SetTexture(NEBULA);haze:SetAllPoints();haze:SetVertexColor(0.62,0.18,0.025,0.17);haze:SetBlendMode("ADD")
    local warm=frame:CreateTexture(nil,"BACKGROUND",nil,-3);warm:SetTexture("Interface\\Buttons\\WHITE8X8");warm:SetPoint("TOPLEFT",5,-5);warm:SetPoint("BOTTOMRIGHT",-5,5);warm:SetVertexColor(0.11,0.035,0.008,0.28)
    local particles={}
    for i=1,14 do
        local t=frame:CreateTexture(nil,"ARTWORK",nil,0);t:SetTexture(i%3==0 and (TEX.."Cosmetics\\ornament_ember_seal.tga") or SPARK);t:SetBlendMode("ADD");local sz=5+(i%4)*2;t:SetSize(sz,sz);t:SetVertexColor(i%2==0 and 1 or .86,i%2==0 and .43 or .62,.08,.22);particles[i]=t
    end
    frame._fallLeaves=particles;frame._fallAge=0;frame._fallTick=0
    frame:SetScript("OnUpdate",function(self,elapsed)
        self._fallTick=(self._fallTick or 0)+(elapsed or 0);if self._fallTick<0.05 then return end
        local dt=self._fallTick;self._fallTick=0;self._fallAge=(self._fallAge or 0)+dt;local t=self._fallAge
        local w,h=math.max(1,self:GetWidth()),math.max(1,self:GetHeight())
        for i,p in ipairs(self._fallLeaves or {}) do
            local age=((i*.113)+(t*(.018+(i%4)*.003)))%1
            local x=(-w*.46)+(((i*.273)%1)*w*.92)+math.sin(t*.55+i)*26
            local y=(h*.52)-(age*h*1.04)
            p:ClearAllPoints();p:SetPoint("CENTER",self,"CENTER",x,y);p:SetAlpha(.08+((math.sin(t*.8+i)+1)*.055));if p.SetRotation then p:SetRotation((t*.12+i*.31)%6.28) end
        end
    end)
end

local SelectSeasonView

local function FormatStatDuration(seconds)
    seconds=math.max(0,math.floor(tonumber(seconds) or 0));local hours=math.floor(seconds/3600);local minutes=math.floor((seconds%3600)/60)
    if hours>0 then return tostring(hours).."h "..tostring(minutes).."m" end
    return tostring(minutes).."m"
end

local function BuildSeasonFrame()
    if seasonFrame then return seasonFrame end
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
    local f=CreateFrame("Frame","ODYN_SeasonsFrame",UIParent,"BackdropTemplate");seasonFrame=f;SyncReplacementGeometry(f,main)
    f:SetFrameStrata("FULLSCREEN_DIALOG");f:SetFrameLevel(3000);f:SetToplevel(true);f:SetClampedToScreen(true);f:SetClipsChildren(true);f:EnableMouse(true)
    MakeBackdrop(f,FALL_INK,FALL_BORDER,18);f:Hide();CreateFallAmbience(f)
    if RPAL.RegisterEscapeFrame then RPAL.RegisterEscapeFrame(f) end

    local header=CreateFrame("Frame",nil,f,"BackdropTemplate");header:SetPoint("TOPLEFT",14,-14);header:SetPoint("TOPRIGHT",-14,-14);header:SetHeight(98);MakeBackdrop(header,{0.075,0.028,0.012,0.96},{0.67,0.31,0.08,0.88},12)
    local banner=header:CreateTexture(nil,"BACKGROUND",nil,-1);banner:SetTexture(FALL_BANNER);banner:SetPoint("TOPLEFT",4,-4);banner:SetPoint("BOTTOMRIGHT",-4,4);banner:SetAlpha(.16);banner:SetVertexColor(1.00,.72,.30,1)
    local bannerShade=header:CreateTexture(nil,"BACKGROUND",nil,0);bannerShade:SetTexture("Interface\\Buttons\\WHITE8X8");bannerShade:SetPoint("TOPLEFT",4,-4);bannerShade:SetPoint("BOTTOMRIGHT",-4,4);bannerShade:SetVertexColor(.035,.012,.004,.36)
    local crest=header:CreateTexture(nil,"ARTWORK");crest:SetTexture(TEX.."Interaction\\decovolt_round_01.tga");crest:SetSize(58,58);crest:SetPoint("LEFT",12,1);crest:SetVertexColor(1,.63,.18,.86)
    local close=FallButton(header,"RETURN TO LEDGER",132,30);close:SetPoint("TOPRIGHT",-12,-12);close:SetScript("OnClick",function()f:Hide()end)
    f.eyebrow=Text(header,"ODYN SEASONS  •  FALL",8.5,FALL_ORANGE);f.eyebrow:SetPoint("TOPLEFT",76,-14)
    f.title=Text(header,"Fall 2026",29,FALL_PALE,"Fonts\\FRIZQT__.TTF");f.title:SetPoint("TOPLEFT",74,-34)
    f.subtitle=Text(header,"",10.5,FALL_MUTED);f.subtitle:SetPoint("TOPLEFT",77,-72);f.subtitle:SetPoint("RIGHT",header,"RIGHT",-360,0);f.subtitle:SetWordWrap(false)
    f.date=Text(header,"",9.2,FALL_GOLD);f.date:SetPoint("TOPRIGHT",-164,-17);f.date:SetWidth(280);f.date:SetJustifyH("RIGHT")
    f.drop=Text(header,"",8.9,FALL_ORANGE);f.drop:SetPoint("TOPRIGHT",-164,-43);f.drop:SetWidth(330);f.drop:SetJustifyH("RIGHT")

    f.statsTitle=Text(f,"THIS CHARACTER • SEASON RECORD",9.2,FALL_GOLD);f.statsTitle:SetPoint("TOPLEFT",34,-126)
    f.statsHost=CreateFrame("Frame",nil,f);f.statsHost:SetPoint("TOPLEFT",30,-148);f.statsHost:SetPoint("TOPRIGHT",-30,-148);f.statsHost:SetHeight(118)
    f.statCards={}
    local labels={"SESSIONS","RP TIME","CHARACTERS MET","RP DAYS","RENOWN GAINED","EMOTES MADE","EMOTES RECEIVED","LONGEST SESSION"}
    for i,label in ipairs(labels) do
        local row=math.floor((i-1)/4);local col=(i-1)%4
        local card=CreateFrame("Frame",nil,f.statsHost,"BackdropTemplate");card:SetSize(160,50);card:SetPoint("TOPLEFT",f.statsHost,"TOPLEFT",col*167,-row*58)
        MakeBackdrop(card,FALL_PANEL,{0.38,0.16,0.045,0.84},8)
        card.label=Text(card,label,7.5,FALL_MUTED);card.label:SetPoint("TOPLEFT",10,-8)
        card.value=Text(card,"0",15.5,FALL_PALE,"Fonts\\FRIZQT__.TTF");card.value:SetPoint("BOTTOMLEFT",10,7)
        f.statCards[i]=card
    end
    f.statsHost:SetScript("OnSizeChanged",function(self,w)
        local gap=7;local cardW=math.max(120,math.floor((w-(gap*3))/4))
        for i,card in ipairs(f.statCards or {}) do local row=math.floor((i-1)/4);local col=(i-1)%4;card:ClearAllPoints();card:SetSize(cardW,50);card:SetPoint("TOPLEFT",self,"TOPLEFT",col*(cardW+gap),-row*58) end
    end)

    local progress=CreateFrame("Frame",nil,f,"BackdropTemplate");f.progressCard=progress;progress:SetPoint("TOPLEFT",30,-278);progress:SetPoint("TOPRIGHT",-30,-278);progress:SetHeight(86);MakeBackdrop(progress,FALL_PANEL2,{0.67,0.29,0.06,0.90},10)
    progress.level=Text(progress,"SEASON LEVEL 1",17,FALL_PALE);progress.level:SetPoint("TOPLEFT",18,-13)
    progress.xp=Text(progress,"0 TOTAL SXP",9,FALL_MUTED);progress.xp:SetPoint("TOPLEFT",18,-41)
    progress.remaining=Text(progress,"",9.4,FALL_GOLD);progress.remaining:SetPoint("TOPRIGHT",-18,-17);progress.remaining:SetWidth(360);progress.remaining:SetJustifyH("RIGHT")
    progress.bar=CreateFrame("StatusBar",nil,progress);progress.bar:SetPoint("BOTTOMLEFT",18,13);progress.bar:SetPoint("BOTTOMRIGHT",-18,13);progress.bar:SetHeight(14);progress.bar:SetStatusBarTexture("Interface\\TARGETINGFRAME\\UI-StatusBar");progress.bar:SetStatusBarColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],0.98)
    local pbg=progress.bar:CreateTexture(nil,"BACKGROUND");pbg:SetAllPoints();pbg:SetTexture("Interface\\Buttons\\WHITE8X8");pbg:SetVertexColor(0.16,0.055,0.012,0.98)
    progress.barText=Text(progress.bar,"",8.2,FALL_PALE);progress.barText:SetPoint("CENTER")
    local seasonRule=progress:CreateTexture(nil,"ARTWORK")
    seasonRule:SetTexture("Interface\\Buttons\\WHITE8X8")
    seasonRule:SetPoint("TOPRIGHT",-18,-36);seasonRule:SetSize(190,1)
    seasonRule:SetVertexColor(FALL_GOLD[1],FALL_GOLD[2],FALL_GOLD[3],.22)

    f.rewardHeader=Text(f,"FALL REWARD PATH • MONTH I",9.4,FALL_GOLD);f.rewardHeader:SetPoint("TOPLEFT",34,-382)
    f.rewardTab=FallButton(f,"REWARD PATH",116,26);f.rewardTab:SetPoint("TOP",f,"TOP",-94,-376)
    f.dailyTab=FallButton(f,"DAILY",88,26);f.dailyTab:SetPoint("LEFT",f.rewardTab,"RIGHT",7,0)
    f.weeklyTab=FallButton(f,"WEEKLY",88,26);f.weeklyTab:SetPoint("LEFT",f.dailyTab,"RIGHT",7,0)
    f.rewardPageText=Text(f,"",8.1,FALL_MUTED);f.rewardPageText:SetPoint("TOPRIGHT",-104,-382);f.rewardPageText:SetWidth(180);f.rewardPageText:SetJustifyH("RIGHT")
    f.prevReward=FallButton(f,"‹",34,26);f.prevReward:SetPoint("TOPRIGHT",f.rewardPageText,"TOPLEFT",-6,6)
    f.nextReward=FallButton(f,"›",34,26);f.nextReward:SetPoint("TOPRIGHT",-34,-376)
    f.rewardHost=CreateFrame("Frame",nil,f,"BackdropTemplate");f.rewardHost:SetPoint("TOPLEFT",30,-404);f.rewardHost:SetPoint("TOPRIGHT",-30,-404);f.rewardHost:SetPoint("BOTTOM",0,48);f.rewardHost:SetClipsChildren(true);MakeBackdrop(f.rewardHost,{0.055,0.024,0.010,0.72},{0.35,0.14,0.035,0.72},10)
    f.questHost=CreateFrame("Frame",nil,f,"BackdropTemplate");f.questHost:SetAllPoints(f.rewardHost);f.questHost:SetClipsChildren(true);MakeBackdrop(f.questHost,{0.055,0.024,0.010,0.84},{0.42,0.18,0.045,0.84},10);f.questHost:Hide()
    f._rewardPage=1;f._rewardPageTouched=false;f._seasonView="rewards"
    f.rewardTab:SetActive(true)
    f.rewardTab:SetScript("OnClick",function()if f._selectSeasonView then f:_selectSeasonView("rewards") end end)
    f.dailyTab:SetScript("OnClick",function()if f._selectSeasonView then f:_selectSeasonView("daily") end end)
    f.weeklyTab:SetScript("OnClick",function()if f._selectSeasonView then f:_selectSeasonView("weekly") end end)
    f.prevReward:SetScript("OnClick",function()if f._showRewardPage then f._rewardPageTouched=true;f:_showRewardPage((f._rewardPage or 1)-1,-1) end end)
    f.nextReward:SetScript("OnClick",function()if f._showRewardPage then f._rewardPageTouched=true;f:_showRewardPage((f._rewardPage or 1)+1,1) end end)

    f._selectSeasonView=function(self,view)SelectSeasonView(self,view,false)end
    f.footer=Text(f,"FALL 2026 • AUG 30 — NOV 29 • MAPLEFALL • MONTHLY REWARD DROPS",8.1,FALL_MUTED);f.footer:SetPoint("BOTTOM",0,18)
    f:SetScript("OnHide",function(self)local restore=self._restoreLedgerOnHide;local returnFrame=self._returnMainFrame;self._restoreLedgerOnHide=nil;if restore and returnFrame and returnFrame.Show then returnFrame:Show();if RPAL.RefreshBookChrome then pcall(RPAL.RefreshBookChrome) end;if RPAL.Refresh then pcall(RPAL.Refresh) end end end)
    RegisterSpecialFrame("ODYN_SeasonsFrame");return f
end

local function SeasonCheck(parent,size)
    local t=parent:CreateTexture(nil,"OVERLAY");t:SetTexture("Interface\\Buttons\\UI-CheckBox-Check");t:SetSize(size or 18,size or 18);t:SetVertexColor(GREEN[1],GREEN[2],GREEN[3],1);return t
end

local FALL_REWARD_ICONS={
    ["Maplefall Corners"]=TEX.."Interaction\\highlighter_corner.tga",
    ["Moonlit Mark"]=TEX.."Interaction\\decovolt_round_02.tga",
    ["Pagefold Note"]=TEX.."Interaction\\pointer_pagefold.tga",
    ["Maplefall Binding"]=TEX.."Interaction\\large01_corner.tga",
    ["Gilded Dispatch"]=TEX.."Interaction\\decovolt_small_01.tga",
    ["5 Hearthstone Packs"]=TEX.."card_bg.tga",
    ["Folio Brackets"]=TEX.."Interaction\\highlighter_brackets.tga",
    ["Maplefall Crest"]=TEX.."Interaction\\large01_header.tga",
    ["Maplefall Pointer"]=TEX.."Interaction\\pointer_maplefall.tga",
    ["Dawnkeeper Seal"]=TEX.."Interaction\\decovolt_round_01.tga",
    ["Crimson Dispatch"]=TEX.."Interaction\\decovolt_small_02.tga",
    ["Heirloom Folio"]=TEX.."Interaction\\large02_corner.tga",
    ["Moonlit Trace"]=TEX.."Interaction\\decovolt_small_03.tga",
    ["Heirloom Crest"]=TEX.."Interaction\\large02_header.tga",
    ["Ribbon Annotation"]=TEX.."Interaction\\pointer_ribbon.tga",
    ["Field Journal"]=TEX.."Interaction\\decovolt_small_03.tga",
    ["Heirloom Compass"]=TEX.."Interaction\\decovolt_small_02.tga",
    ["Heirloom Marginalia"]=TEX.."Interaction\\large01_header.tga",
    ["Heirloom Chronicle"]=TEX.."Interaction\\large02_header.tga",
    ["Maplefall Seal"]=TEX.."Cosmetics\\Badges2\\maplefall_seal.tga",
    ["Northstar Seal"]=TEX.."Cosmetics\\Badges2\\northstar_seal.tga",
    ["Archivist Seal"]=TEX.."Cosmetics\\Badges2\\archivist_seal.tga",
    ["Wayglass Seal"]=TEX.."Cosmetics\\Badges2\\wayglass_seal.tga",
    ["Wolf Seal"]=TEX.."Cosmetics\\Badges2\\wolf_seal.tga",
    ["Maplefall Frame"]=TEX.."Cosmetics\\portrait_maplefall.tga",
    ["Moonlit Frame"]=TEX.."Cosmetics\\portrait_moonlit.tga",
    ["Ember Frame"]=TEX.."Cosmetics\\portrait_ember_season.tga",
    ["Raven Frame"]=TEX.."Cosmetics\\portrait_raven.tga",
    ["Autumn Frame"]=TEX.."Cosmetics\\portrait_autumn.tga",
}

local function RewardIconPath(reward)
    if not reward then return TEX.."IconPack\\question.tga" end
    if reward.kind=="token" then return TEX.."IconPack\\token.tga" end
    return FALL_REWARD_ICONS[reward.name] or TEX.."IconPack\\star.tga"
end

local function BuildRewardPage(f,snap,pageIndex)
    local season=snap.season;local state=snap.state;local maxLevel=season.maxLevel or 20;local perPage=5;local totalPages=math.max(1,math.ceil(maxLevel/perPage));pageIndex=math.max(1,math.min(totalPages,math.floor(tonumber(pageIndex) or 1)))
    local page=CreateFrame("Frame",nil,f.rewardHost)
    page:SetSize(math.max(1,f.rewardHost:GetWidth()),math.max(1,f.rewardHost:GetHeight()))
    page:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",0,0)
    page:EnableMouse(false)
    local startLevel=((pageIndex-1)*perPage)+1;local endLevel=math.min(maxLevel,startLevel+perPage-1)
    local leftPad,rightPad=28,28;local hostW=math.max(900,tonumber(f.rewardHost:GetWidth()) or 1160);local innerW=hostW-leftPad-rightPad
    local timeline=CreateFrame("Frame",nil,page);timeline:SetPoint("TOPLEFT",leftPad,-30);timeline:SetPoint("TOPRIGHT",-rightPad,-30);timeline:SetHeight(52)
    local track=timeline:CreateTexture(nil,"BACKGROUND");track:SetTexture("Interface\\Buttons\\WHITE8X8");track:SetPoint("LEFT",0,0);track:SetPoint("RIGHT",0,0);track:SetHeight(4);track:SetVertexColor(.28,.10,.025,.95)
    local startXP=season.thresholds[startLevel] or 0;local endXP=season.thresholds[endLevel] or math.max(startXP+1,snap.xp);if endXP<=startXP then endXP=startXP+1 end
    local fillPct=math.max(0,math.min(1,((snap.xp or 0)-startXP)/(endXP-startXP)))
    local fill=timeline:CreateTexture(nil,"ARTWORK");fill:SetTexture("Interface\\TARGETINGFRAME\\UI-StatusBar");fill:SetPoint("LEFT",0,0);fill:SetHeight(6);fill:SetWidth(math.max(1,innerW*fillPct));fill:SetVertexColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],.98)
    local marker=timeline:CreateTexture(nil,"OVERLAY");marker:SetTexture(SPARK);marker:SetBlendMode("ADD");marker:SetSize(24,24);marker:SetPoint("CENTER",timeline,"LEFT",innerW*fillPct,0);marker:SetVertexColor(1,.78,.26,.92)

    local count=endLevel-startLevel+1;local cardGap=10;local cardW=math.floor((innerW-((count-1)*cardGap))/count)
    for level=startLevel,endLevel do
        local slot=level-startLevel;local reward=season.rewards and season.rewards[level];local claimed=state.claimedLevels and state.claimedLevels[level];local reached=claimed or (snap.active and snap.level>=level)
        local levelXP=season.thresholds[level] or startXP;local nodeX=count==1 and innerW/2 or (math.max(0,math.min(1,(levelXP-startXP)/(endXP-startXP)))*innerW)
        local node=CreateFrame("Frame",nil,timeline,"BackdropTemplate");node:SetSize(22,22);node:SetPoint("CENTER",timeline,"LEFT",nodeX,0);MakeBackdrop(node,reached and {0.32,.10,.02,.98} or {0.075,.032,.015,.98},reached and FALL_GOLD or {0.30,.13,.04,.92},7)
        local dot=node:CreateTexture(nil,"ARTWORK");dot:SetTexture(SPARK);dot:SetBlendMode("ADD");dot:SetSize(reached and 16 or 10,reached and 16 or 10);dot:SetPoint("CENTER");dot:SetVertexColor(reached and FALL_GOLD[1] or .38,reached and FALL_GOLD[2] or .20,reached and FALL_GOLD[3] or .08,reached and .92 or .42)
        local lv=Text(timeline,"LV "..tostring(level),7.2,reached and FALL_PALE or FALL_MUTED);lv:SetPoint("TOP",node,"BOTTOM",0,-3)

        local card=CreateFrame("Frame",nil,page,"BackdropTemplate");card:SetSize(cardW,178);card:SetPoint("TOPLEFT",leftPad+slot*(cardW+cardGap),-94);MakeBackdrop(card,reached and {0.12,.048,.016,.965} or {0.055,.026,.013,.93},reached and {0.70,.30,.07,.96} or {0.27,.12,.04,.80},10)
        local iconAnchor=CreateFrame("Frame",nil,card);iconAnchor:SetSize(60,60);iconAnchor:SetPoint("TOP",0,-15)
        local icon
        if reward and reward.kind=="cosmetic" and reward.cosmeticKind=="level" and RPAL.RenderLevelBadge then
            RPAL.RenderLevelBadge(card,iconAnchor,reward.name,54,level,reached and .96 or .46,4)
        elseif reward and reward.kind=="cosmetic" and reward.cosmeticKind=="portrait" and RPAL.RenderCombatPortraitCosmetic then
            icon=card:CreateTexture(nil,"ARTWORK");icon:SetSize(48,48);icon:SetPoint("CENTER",iconAnchor);SetPortraitTexture(icon,"player");icon:SetTexCoord(.08,.92,.08,.92);icon:SetAlpha(reached and 1 or .46)
            RPAL.RenderCombatPortraitCosmetic(card,icon,reward.name,FALL_GOLD,58)
        else
            icon=card:CreateTexture(nil,"ARTWORK");icon:SetTexture(RewardIconPath(reward));icon:SetSize(52,52);icon:SetPoint("CENTER",iconAnchor);icon:SetTexCoord(.05,.95,.05,.95);if not reached then pcall(function()icon:SetDesaturated(true)end);icon:SetAlpha(.46) else icon:SetVertexColor(1,.84,.62,1) end
        end
        local kind=Text(card,RewardKindLabel(reward),7.0,reached and FALL_ORANGE or FALL_MUTED);kind:SetPoint("TOP",iconAnchor,"BOTTOM",0,-7)
        local name=Text(card,reward and RewardTitle(reward) or "Season Reward",9.2,reached and FALL_PALE or FALL_MUTED);name:SetPoint("TOP",kind,"BOTTOM",0,-5);name:SetWidth(cardW-16);name:SetJustifyH("CENTER");name:SetWordWrap(true);name:SetHeight(34)
        if claimed then local check=SeasonCheck(card,20);check:SetPoint("TOPRIGHT",-5,-4)
        elseif reached then local ready=Text(card,"UNLOCKED",6.6,GREEN);ready:SetPoint("BOTTOM",0,9)
        else local need=Text(card,tostring(season.thresholds[level] or 0).." SXP",6.7,FALL_MUTED);need:SetPoint("BOTTOM",0,9) end
        if level==20 then local hero=card:CreateTexture(nil,"OVERLAY");hero:SetTexture(SPARK);hero:SetBlendMode("ADD");hero:SetPoint("TOPLEFT",-18,18);hero:SetPoint("BOTTOMRIGHT",18,-18);hero:SetVertexColor(1,.55,.10,reached and .13 or .035) end
    end
    return page,totalPages,startLevel,endLevel
end

local function ShowRewardPage(f,snap,pageIndex,direction,instant)
    if not f or not f.rewardHost or not snap then return end

    -- Finish an interrupted transition before starting another. This prevents
    -- a page from being hidden by a stale OnUpdate when the player clicks fast.
    if f._rewardSlideDriver then
        f._rewardSlideDriver:SetScript("OnUpdate",nil)
        f._rewardSlideDriver=nil
        local active=f._rewardPageFrame
        if active then
            active:ClearAllPoints();active:SetSize(math.max(1,f.rewardHost:GetWidth()),math.max(1,f.rewardHost:GetHeight()))
            active:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",0,0);active:SetAlpha(1);active:Show()
        end
        local stale=f._rewardSlideOld
        if stale and stale~=active then stale:Hide();stale:SetParent(nil) end
        f._rewardSlideOld=nil
    end

    local newPage,totalPages,startLevel,endLevel=BuildRewardPage(f,snap,pageIndex)
    pageIndex=math.max(1,math.min(totalPages,pageIndex));f._rewardPage=pageIndex
    f.rewardPageText:SetText("LEVELS "..tostring(startLevel).."—"..tostring(endLevel).."  •  "..tostring(pageIndex).." / "..tostring(totalPages))
    f.prevReward:SetEnabled(pageIndex>1);f.nextReward:SetEnabled(pageIndex<totalPages)

    local old=f._rewardPageFrame
    f._rewardPageFrame=newPage
    local width=math.max(1,f.rewardHost:GetWidth())
    local height=math.max(1,f.rewardHost:GetHeight())
    newPage:ClearAllPoints();newPage:SetSize(width,height)

    if not old or instant then
        if old then old:Hide();old:SetParent(nil) end
        newPage:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",0,0);newPage:SetAlpha(1);newPage:Show()
        return
    end

    direction=(tonumber(direction) or 1)>=0 and 1 or -1
    old:ClearAllPoints();old:SetSize(width,height);old:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",0,0);old:SetAlpha(1);old:Show()
    newPage:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",direction*width,0);newPage:SetAlpha(1);newPage:Show()

    -- Drive both pages from the host rather than from the incoming page. The
    -- previous implementation could visually drop the old page before the
    -- incoming page had crossed the clip boundary. This is a pure horizontal
    -- slide: no fade-to-black gap, both pages remain fully visible throughout.
    local driver=CreateFrame("Frame",nil,f.rewardHost)
    f._rewardSlideDriver=driver;f._rewardSlideOld=old
    local age=0;local duration=.34
    driver:SetScript("OnUpdate",function(self,elapsed)
        age=age+(elapsed or 0)
        local k=math.min(1,age/duration)
        local eased=1-((1-k)*(1-k)*(1-k))
        local incomingX=direction*width*(1-eased)
        local outgoingX=(-direction)*width*eased
        newPage:ClearAllPoints();newPage:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",incomingX,0)
        old:ClearAllPoints();old:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",outgoingX,0)
        if k>=1 then
            self:SetScript("OnUpdate",nil)
            old:Hide();old:SetParent(nil)
            newPage:ClearAllPoints();newPage:SetSize(width,height);newPage:SetPoint("TOPLEFT",f.rewardHost,"TOPLEFT",0,0);newPage:SetAlpha(1)
            if f._rewardSlideDriver==self then f._rewardSlideDriver=nil end
            if f._rewardSlideOld==old then f._rewardSlideOld=nil end
            self:Hide();self:SetParent(nil)
        end
    end)
end

local function RenderRewardTrack(f,snap)
    f._lastRewardSnap=snap
    if not f._showRewardPage then f._showRewardPage=function(self,page,direction,instant)ShowRewardPage(self,self._lastRewardSnap,page,direction,instant)end end
    local ideal=math.max(1,math.ceil((snap.level or 1)/5));if not f._rewardPageTouched then f._rewardPage=ideal end
    ShowRewardPage(f,snap,f._rewardPage or ideal,1,true)
end

local function QuestRow(parent,quest,y,compact,scope,bucket)
    local height=compact and 42 or 58
    local preview=bucket and bucket.preview
    local row=CreateFrame("Button",nil,parent,"BackdropTemplate");row:SetPoint("TOPLEFT",14,y);row:SetPoint("TOPRIGHT",-14,y);row:SetHeight(height)
    local base=quest.complete and {0.045,0.105,0.070,0.94} or {0.085,0.034,0.013,0.96};local border=quest.complete and {0.28,0.68,0.42,0.88} or {0.42,0.18,0.045,0.90};MakeBackdrop(row,base,border,8)
    local markBox=CreateFrame("Frame",nil,row,"BackdropTemplate");markBox:SetSize(compact and 18 or 21,compact and 18 or 21);markBox:SetPoint("LEFT",10,0);MakeBackdrop(markBox,{.055,.021,.010,.98},quest.complete and GREEN or FALL_ORANGE,6)
    if quest.complete then local mark=SeasonCheck(markBox,compact and 18 or 21);mark:SetPoint("CENTER") else
        local rune=markBox:CreateTexture(nil,"ARTWORK");rune:SetTexture(SPARK);rune:SetBlendMode("ADD");rune:SetSize(12,12);rune:SetPoint("CENTER");rune:SetVertexColor(FALL_GOLD[1],FALL_GOLD[2],FALL_GOLD[3],preview and .38 or .70)
    end
    local title=Text(row,quest.title or "Season Quest",compact and 9.2 or 10.0,quest.complete and GREEN or FALL_PALE);title:SetPoint("TOPLEFT",40,compact and -6 or -9);title:SetPoint("TOPRIGHT",-105,compact and -6 or -9);title:SetJustifyH("LEFT");title:SetWordWrap(false)
    local desc=Text(row,quest.description or "",compact and 8.0 or 8.8,FALL_MUTED);desc:SetPoint("TOPLEFT",40,compact and -23 or -31);desc:SetPoint("TOPRIGHT",-112,compact and -23 or -31);desc:SetJustifyH("LEFT");desc:SetWordWrap(false)
    local progress=math.floor(tonumber(quest.progress) or 0);local target=math.floor(tonumber(quest.target) or 1)
    local right=Text(row,preview and "PREVIEW" or (tostring(progress).." / "..tostring(target)),compact and 7.8 or 8.5,quest.complete and GREEN or FALL_PALE);right:SetPoint("TOPRIGHT",-11,compact and -6 or -10)
    local xp=Text(row,"+"..tostring(quest.xp or 0).." SXP",compact and 7.0 or 7.5,FALL_ORANGE);xp:SetPoint("BOTTOMRIGHT",-11,compact and 5 or 8)
    row:SetScript("OnEnter",function(self)
        self:SetBackdropColor(.13,.049,.015,.99);self:SetBackdropBorderColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],.98)
        if not preview and not quest.complete and (tonumber(bucket and bucket.rerolls) or 0)>0 then if SetCursor then SetCursor("INTERACT_CURSOR") end end
    end)
    row:SetScript("OnLeave",function(self)if ResetCursor then ResetCursor() end;self:SetBackdropColor(base[1],base[2],base[3],base[4] or 1);self:SetBackdropBorderColor(border[1],border[2],border[3],border[4] or 1)end)
    row:SetScript("OnClick",function()
        if preview or quest.complete then return end
        if (tonumber(bucket and bucket.rerolls) or 0)<=0 then print("|cffff7777ODYN Season:|r No "..tostring(scope).." rerolls left.");return end
        local ok,msg=RPAL.RerollSeasonQuest and RPAL.RerollSeasonQuest(scope,quest.id)
        if not ok and msg then print("|cffff7777ODYN Season:|r "..tostring(msg)) end
        if ok and RPAL.RefreshSeasonUI then RPAL.RefreshSeasonUI() end
    end)
end

local function RenderQuestPanel(panel,bucket,title,scope,snap)
    ClearHost(panel)
    local content=CreateFrame("Frame",nil,panel);content:SetAllPoints()
    local preview=bucket and bucket.preview
    local header=Text(content,title.." QUESTS",13,FALL_PALE,"Fonts\\FRIZQT__.TTF");header:SetPoint("TOPLEFT",18,-15)
    local eyebrow=Text(content,preview and "SEASON PREVIEW • OBJECTIVES ACTIVATE WHEN FALL 2026 BEGINS" or (scope=="daily" and "TODAY'S FIELD NOTES" or "THIS WEEK'S ARCHIVE"),7.5,FALL_ORANGE);eyebrow:SetPoint("TOPLEFT",18,-39)
    local rerolls=math.max(0,tonumber(bucket and bucket.rerolls) or 0)
    local resetText
    if preview then
        resetText="Progress and rerolls remain inactive until the Season starts."
    else
        resetText=(scope=="daily" and "Resets daily" or "Resets weekly").."  •  CLICK AN INCOMPLETE QUEST TO REROLL  •  "..tostring(rerolls).." REROLL"..(rerolls==1 and "" or "S").." LEFT"
    end
    local reset=Text(content,resetText,8.2,FALL_MUTED);reset:SetPoint("TOPRIGHT",-18,-19);reset:SetWidth(470);reset:SetJustifyH("RIGHT")
    local rule=content:CreateTexture(nil,"ARTWORK");rule:SetTexture("Interface\\Buttons\\WHITE8X8");rule:SetVertexColor(FALL_ORANGE[1],FALL_ORANGE[2],FALL_ORANGE[3],.30);rule:SetPoint("TOPLEFT",18,-58);rule:SetPoint("TOPRIGHT",-18,-58);rule:SetHeight(1)
    local quests=(bucket and bucket.quests) or {};local compact=scope=="weekly";local y=-72
    for _,quest in ipairs(quests) do QuestRow(content,quest,y,compact,scope,bucket);y=y-(compact and 46 or 63) end
    if #quests==0 then
        local empty=Text(content,"No objectives are available yet.",11,FALL_MUTED);empty:SetPoint("CENTER",0,0)
    end
end

SelectSeasonView=function(f,view,instant)
    if not f then return end
    view=(view=="daily" or view=="weekly") and view or "rewards";f._seasonView=view
    if f.rewardTab then f.rewardTab:SetActive(view=="rewards") end
    if f.dailyTab then f.dailyTab:SetActive(view=="daily") end
    if f.weeklyTab then f.weeklyTab:SetActive(view=="weekly") end
    if f.rewardHost then f.rewardHost:SetShown(view=="rewards") end
    if f.questHost then f.questHost:SetShown(view~="rewards") end
    if f.rewardPageText then f.rewardPageText:SetShown(view=="rewards") end
    if f.prevReward then f.prevReward:SetShown(view=="rewards") end
    if f.nextReward then f.nextReward:SetShown(view=="rewards") end
    local snap=RPAL.GetSeasonSnapshot and RPAL.GetSeasonSnapshot()
    if not snap then return end
    if view=="rewards" then
        f.rewardHeader:SetText((snap.season.id=="fall-2026") and "FALL REWARD PATH • MONTH I" or (string.upper(tostring(snap.season.name or "SEASON")).." REWARD PATH"))
        RenderRewardTrack(f,snap)
    else
        local bucket=(view=="weekly") and snap.weekly or snap.daily
        if not snap.active and RPAL.GetSeasonQuestPreview then bucket=RPAL.GetSeasonQuestPreview(view,snap.season and snap.season.id) end
        f.rewardHeader:SetText((view=="weekly" and "WEEKLY FIELD NOTES" or "DAILY FIELD NOTES").." • "..string.upper(tostring(snap.season.name or "SEASON")))
        RenderQuestPanel(f.questHost,bucket,view=="weekly" and "WEEKLY" or "DAILY",view,snap)
    end
end

local function RefreshSeasonFrame()
    local f=seasonFrame;if not f or not f:IsShown() then return end
    if IsLite() then f._restoreLedgerOnHide=true;f:Hide();return end
    local snap=RPAL.GetSeasonSnapshot and RPAL.GetSeasonSnapshot();if not snap then return end
    local season=snap.season;f.eyebrow:SetText("ODYN SEASONS  •  "..tostring(season.kind or "SEASON"));f.title:SetText(season.name or "ODYN Season");f.subtitle:SetText(season.subtitle or "");f.date:SetText(SeasonDateRange(season))
    if snap.active then f.drop:SetText(FormatRemaining(snap.remaining,false)) elseif snap.ended then f.drop:SetText("SEASON ENDED • ARCHIVED") else local starts=FormatRemaining(snap.startsIn,false):gsub("SEASON END","SEASON START");f.drop:SetText(starts) end
    local stats=snap.stats or {};local values={tostring(stats.sessions or 0),FormatStatDuration((stats.rpMinutes or 0)*60),tostring(stats.charactersMet or 0),tostring(stats.rpDays or 0),tostring(stats.renownGained or 0),tostring(stats.emotesOut or 0),tostring(stats.emotesReceived or 0),FormatStatDuration(stats.longestSession or 0)}
    for i,card in ipairs(f.statCards or {}) do if card.value then card.value:SetText(values[i] or "0") end end
    f.progressCard.level:SetText("SEASON LEVEL "..tostring(snap.level).." / "..tostring(season.maxLevel or 1));f.progressCard.xp:SetText(tostring(snap.xp or 0).." TOTAL SXP")
    f.progressCard.bar:SetMinMaxValues(0,math.max(1,snap.needed or 1));f.progressCard.bar:SetValue(snap.progress or 0);f.progressCard.barText:SetText((snap.level>=(season.maxLevel or 1)) and "SEASON COMPLETE" or (tostring(snap.progress or 0).." / "..tostring(snap.needed or 1).." SXP TO NEXT LEVEL"))
    if snap.active then f.progressCard.remaining:SetText("MONTH I • MAPLEFALL") elseif snap.ended then f.progressCard.remaining:SetText("FALL 2026 ARCHIVED") else f.progressCard.remaining:SetText("MAPLEFALL • BEGINS SOON") end
    SelectSeasonView(f,f._seasonView or "rewards",true)
end
RPAL.RefreshSeasonUI=RefreshSeasonFrame

local function BuildLevelFrame()
    if levelFrame then return levelFrame end
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame
    local f=CreateFrame("Frame","ODYN_LevelRewardsFrame",UIParent,"BackdropTemplate");levelFrame=f;SyncReplacementGeometry(f,main)
    f:SetFrameStrata("FULLSCREEN_DIALOG");f:SetFrameLevel(3000);f:SetToplevel(true);f:SetClampedToScreen(true);f:SetClipsChildren(true);f:EnableMouse(true)
    MakeBackdrop(f,{0.008,0.022,0.030,0.992},{0.24,0.62,0.72,1},18);f:Hide();CreateStars(f,CYAN)
    if RPAL.RegisterEscapeFrame then RPAL.RegisterEscapeFrame(f) end

    local header=CreateFrame("Frame",nil,f,"BackdropTemplate");header:SetPoint("TOPLEFT",14,-14);header:SetPoint("TOPRIGHT",-14,-14);header:SetHeight(98);MakeBackdrop(header,{0.012,0.032,0.040,0.96},{0.24,0.62,0.72,0.90},12)
    local close=Button(header,"RETURN TO LEDGER",132,30);close:SetPoint("TOPRIGHT",-12,-12);close:SetScript("OnClick",function()f:Hide()end)
    f.eyebrow=Text(header,"ODYN CHARACTER PROGRESSION",8.5,CYAN);f.eyebrow:SetPoint("TOPLEFT",18,-14)
    f.title=Text(header,"Level Rewards",28,PALE,"Fonts\\FRIZQT__.TTF");f.title:SetPoint("TOPLEFT",17,-34)
    f.subtitle=Text(header,"Permanent progression rewards earned as your character's Renown Level grows.",10.5,MUTED);f.subtitle:SetPoint("TOPLEFT",20,-72);f.subtitle:SetPoint("RIGHT",header,"RIGHT",-360,0);f.subtitle:SetWordWrap(false)
    f.summary=Text(header,"",9.0,CYAN);f.summary:SetPoint("TOPRIGHT",-164,-43);f.summary:SetWidth(330);f.summary:SetJustifyH("RIGHT")

    local progress=CreateFrame("Frame",nil,f,"BackdropTemplate");f.progressCard=progress;progress:SetPoint("TOPLEFT",30,-126);progress:SetPoint("TOPRIGHT",-30,-126);progress:SetHeight(92);MakeBackdrop(progress,{0.014,0.040,0.050,0.96},{0.20,0.58,0.68,0.90},10)
    progress.level=Text(progress,"LEVEL 1",19,PALE);progress.level:SetPoint("TOPLEFT",18,-13)
    progress.total=Text(progress,"0 TOTAL RENOWN",8.8,MUTED);progress.total:SetPoint("TOPLEFT",18,-43)
    progress.next=Text(progress,"NEXT LEVEL: 2",10,CYAN);progress.next:SetPoint("TOPRIGHT",-18,-15);progress.next:SetWidth(330);progress.next:SetJustifyH("RIGHT")
    progress.talents=Text(progress,"",8.3,GOLD);progress.talents:SetPoint("TOPRIGHT",-18,-40);progress.talents:SetWidth(330);progress.talents:SetJustifyH("RIGHT")
    progress.bar=CreateFrame("StatusBar",nil,progress);progress.bar:SetPoint("BOTTOMLEFT",18,13);progress.bar:SetPoint("BOTTOMRIGHT",-18,13);progress.bar:SetHeight(15);progress.bar:SetStatusBarTexture("Interface\\TARGETINGFRAME\\UI-StatusBar");progress.bar:SetStatusBarColor(CYAN[1],CYAN[2],CYAN[3],0.95)
    local bg=progress.bar:CreateTexture(nil,"BACKGROUND");bg:SetAllPoints();bg:SetTexture("Interface\\Buttons\\WHITE8X8");bg:SetVertexColor(0.025,0.070,0.080,0.98)
    progress.barText=Text(progress.bar,"",8.5,PALE);progress.barText:SetPoint("CENTER",0,0)

    local rewardsTitle=Text(f,"LEVEL REWARD MILESTONES",9.5,CYAN);rewardsTitle:SetPoint("TOPLEFT",34,-236)
    f.rewardHost=CreateFrame("Frame",nil,f);f.rewardHost:SetPoint("TOPLEFT",30,-260);f.rewardHost:SetPoint("TOPRIGHT",-30,-260);f.rewardHost:SetPoint("BOTTOM",0,54)
    f.footer=Text(f,"LEVEL REWARDS UNLOCK AUTOMATICALLY • TALENT POINTS FOLLOW YOUR NORMAL LEVEL PROGRESSION",8.2,MUTED);f.footer:SetPoint("BOTTOM",0,18)
    f:SetScript("OnHide",function(self)local restore=self._restoreLedgerOnHide;local returnFrame=self._returnMainFrame;self._restoreLedgerOnHide=nil;if restore and returnFrame and returnFrame.Show then returnFrame:Show();if RPAL.RefreshBookChrome then pcall(RPAL.RefreshBookChrome) end;if RPAL.Refresh then pcall(RPAL.Refresh) end end end)
    RegisterSpecialFrame("ODYN_LevelRewardsFrame")
    return f
end

local function DrawThemePaletteIcon(parent,name,reached)
    -- Use the exact same renderer as Customize > Themes so Level Rewards
    -- never drifts into a different visual language. Locked rewards use the
    -- same swatch in grayscale rather than a separate placeholder graphic.
    if RPAL.DrawCustomizationThemeSwatch then
        return RPAL.DrawCustomizationThemeSwatch(parent,name,48,48,false,not reached)
    end

    -- Defensive fallback for unusual partial-load states.
    local stage=CreateFrame("Frame",nil,parent,"BackdropTemplate")
    stage:SetSize(48,48)
    MakeBackdrop(stage,{.018,.020,.028,.98},reached and CYAN or {0.26,0.30,0.34,1},7)
    local mainColor,panelColor,accentColor=RPAL.CustomizationThemePalette and RPAL.CustomizationThemePalette(name) or nil
    local colors={mainColor or {.05,.06,.08,1},panelColor or {.12,.16,.20,1},accentColor or {.55,.70,.82,1}}
    local innerW=38
    local widths={12,12,12}
    local x=5
    for _,c in ipairs(colors) do
        local r,g,b=tonumber(c[1]) or 0,tonumber(c[2]) or 0,tonumber(c[3]) or 0
        if not reached then local lum=(r*.299)+(g*.587)+(b*.114);r,g,b=lum,lum,lum end
        local sw=stage:CreateTexture(nil,"ARTWORK");sw:SetTexture("Interface\\Buttons\\WHITE8X8");sw:SetSize(widths[1],38);sw:SetPoint("LEFT",x,0);sw:SetVertexColor(r,g,b,reached and 1 or .62);x=x+13
    end
    return stage
end

local function RenderLevelRewards(f,currentLevel)
    local host=f.rewardHost;ClearHost(host);local rewards=RPAL.LEVEL_REWARDS or {};local columns=5;local gapX=8;local gapY=9
    local width=math.max(900,math.floor(host:GetWidth() or 1160));local cardW=math.floor((width-((columns-1)*gapX))/columns);local cardH=116;local nextRewardLevel=nil
    for i,reward in ipairs(rewards) do
        local col=(i-1)%columns;local row=math.floor((i-1)/columns);local reached=currentLevel>=(tonumber(reward.level) or 1)
        if not reached and not nextRewardLevel then nextRewardLevel=tonumber(reward.level) or 1 end
        local card=CreateFrame("Frame",nil,host,"BackdropTemplate");card:SetSize(cardW,cardH);card:SetPoint("TOPLEFT",col*(cardW+gapX),-(row*(cardH+gapY)))
        MakeBackdrop(card,reached and {0.020,0.070,0.078,0.96} or {0.018,0.032,0.044,0.94},reached and {0.24,0.74,0.76,0.92} or {0.18,0.36,0.42,0.78},9)
        local lvl=Text(card,"LEVEL "..tostring(reward.level or 1),7.8,reached and CYAN or MUTED);lvl:SetPoint("TOPLEFT",10,-8)
        local status=Text(card,reached and "UNLOCKED" or "LOCKED",6.8,reached and GREEN or MUTED);status:SetPoint("TOPRIGHT",-9,-9)
        local anchor=CreateFrame("Frame",nil,card);anchor:SetSize(48,48);anchor:SetPoint("LEFT",12,-5)
        if reward.kind=="theme" then
            local palette=DrawThemePaletteIcon(card,reward.name,reached);palette:SetPoint("CENTER",anchor,"CENTER")
        elseif reward.kind=="level" and RPAL.RenderLevelBadge then
            local h=RPAL.RenderLevelBadge(card,anchor,reward.name,45,reward.level,reached and .98 or .62,4);if h and not reached then h:SetAlpha(.67) end
        else
            local icon=anchor:CreateTexture(nil,"ARTWORK");icon:SetAllPoints();icon:SetTexture(LEVEL_REWARD_ICONS[reward.name] or "Interface\\Icons\\INV_Misc_EngGizmos_30");icon:SetTexCoord(.06,.94,.06,.94)
            if reward.kind=="effect" and reward.name=="Fireflies" then icon:SetBlendMode("ADD") end
            if not reached then pcall(function()icon:SetDesaturated(true)end);icon:SetAlpha(.48) end
        end
        local name=Text(card,tostring(reward.name or "Reward"),9.7,reached and PALE or MUTED);name:SetPoint("TOPLEFT",70,-34);name:SetPoint("RIGHT",-8,0);name:SetJustifyH("LEFT");name:SetWordWrap(false)
        local kind=Text(card,LevelKindLabel(reward.kind),7.2,reached and GOLD or MUTED);kind:SetPoint("TOPLEFT",70,-57)
        local detail=Text(card,reached and "Earned on this character." or ("Unlocks at Level "..tostring(reward.level or 1).."."),7.0,MUTED);detail:SetPoint("BOTTOMLEFT",12,10);detail:SetPoint("RIGHT",-10,0);detail:SetJustifyH("LEFT")
    end
    f._nextRewardLevel=nextRewardLevel
end

local function RefreshLevelFrame()
    local f=levelFrame;if not f or not f:IsShown() then return end
    if IsLite() then f._restoreLedgerOnHide=true;f:Hide();return end
    local level,progress,needed=1,0,1;if RPAL.GetCurrentRenownLevel then level,progress,needed=RPAL.GetCurrentRenownLevel() end
    level=math.max(1,math.floor(tonumber(level) or 1));progress=math.max(0,math.floor(tonumber(progress) or 0));needed=math.max(1,math.floor(tonumber(needed) or 1))
    local stats=RPAL.GetStatistics and RPAL.GetStatistics() or {};local total=math.max(0,math.floor(tonumber(stats and stats.score) or 0))
    local talentPoints=RPAL.GetTalentPointsForLevel and RPAL.GetTalentPointsForLevel(level) or 0
    f.progressCard.level:SetText("LEVEL "..tostring(level));f.progressCard.total:SetText(tostring(total).." TOTAL RENOWN");f.progressCard.next:SetText("NEXT LEVEL: "..tostring(level+1));f.progressCard.talents:SetText(tostring(talentPoints).." TALENT POINTS EARNED BY LEVEL")
    f.progressCard.bar:SetMinMaxValues(0,needed);f.progressCard.bar:SetValue(progress);f.progressCard.barText:SetText(tostring(progress).." / "..tostring(needed).." RENOWN")
    RenderLevelRewards(f,level)
    local nextReward=f._nextRewardLevel and ("NEXT COSMETIC MILESTONE • LEVEL "..tostring(f._nextRewardLevel)) or "ALL CURRENT LEVEL REWARDS EARNED"
    f.summary:SetText(nextReward)
end
RPAL.RefreshLevelRewardsUI=RefreshLevelFrame

function RPAL.CloseSeasonsUI(restoreLedger)
    if not seasonFrame then return end;if restoreLedger==false then seasonFrame._restoreLedgerOnHide=false end;seasonFrame:Hide()
end

function RPAL.CloseLevelRewardsUI(restoreLedger)
    if not levelFrame then return end;if restoreLedger==false then levelFrame._restoreLedgerOnHide=false end;levelFrame:Hide()
end

function RPAL.OpenSeasonsUI()
    if IsLite() then print("|cffaa88ffODYN:|r Seasons are not part of Lite Kit.");return false end
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame;local f=BuildSeasonFrame();if not f then return false end
    if levelFrame and levelFrame:IsShown() then levelFrame._restoreLedgerOnHide=false;levelFrame:Hide() end
    SyncReplacementGeometry(f,main);f._returnMainFrame=main;f._returnMainWasShown=main and main.IsShown and main:IsShown() or false;f._restoreLedgerOnHide=f._returnMainWasShown
    f:Show();RefreshSeasonFrame();if f.Raise then f:Raise() end;if main and main.Hide then main:Hide() end;return true
end

function RPAL.OpenLevelRewardsUI()
    if IsLite() then print("|cff88ddffODYN:|r Level progression is not part of Lite Kit.");return false end
    local main=(RPAL.UI and RPAL.UI.main) or _G.RPAL_MainFrame;local f=BuildLevelFrame();if not f then return false end
    if seasonFrame and seasonFrame:IsShown() then seasonFrame._restoreLedgerOnHide=false;seasonFrame:Hide() end
    SyncReplacementGeometry(f,main);f._returnMainFrame=main;f._returnMainWasShown=main and main.IsShown and main:IsShown() or false;f._restoreLedgerOnHide=f._returnMainWasShown
    f:Show();RefreshLevelFrame();if f.Raise then f:Raise() end;if main and main.Hide then main:Hide() end;return true
end

local previousRefresh=RPAL.Refresh
if type(previousRefresh)=="function" and not RPAL._seasonUIRefreshWrapped then
    RPAL._seasonUIRefreshWrapped=true
    RPAL.Refresh=function(...)
        local results={previousRefresh(...)};pcall(RefreshMainStrip)
        if seasonFrame and seasonFrame:IsShown() then pcall(RefreshSeasonFrame) end
        if levelFrame and levelFrame:IsShown() then pcall(RefreshLevelFrame) end
        return unpack(results)
    end
end

local event=CreateFrame("Frame")
event:RegisterEvent("PLAYER_LOGIN")
event:SetScript("OnEvent",function()
    local function ready()
        BuildProgressCards();RefreshMainStrip()
        if RPAL.RefreshBookChrome then pcall(RPAL.RefreshBookChrome) end
        if not refreshTicker and C_Timer and C_Timer.NewTicker then
            refreshTicker=C_Timer.NewTicker(30,function()
                RefreshMainStrip()
                if seasonFrame and seasonFrame:IsShown() then RefreshSeasonFrame() end
                if levelFrame and levelFrame:IsShown() then RefreshLevelFrame() end
            end)
        end
    end
    if C_Timer and C_Timer.After then C_Timer.After(0.35,ready) else ready() end
end)
