Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Bracket: Difference between revisions

From eSportsAmaze
Created page with "local p = {} local html = mw.html function p.main(frame) local args = frame:getParent().args local teamCount = tonumber(args.teams) or 8 -- Default to 8 teams local rounds = math.log(teamCount) / math.log(2) -- Calculate rounds (8 -> 3, 16 -> 4) local container = html.create('div'):addClass('bracket-scroll-wrapper') local wrapper = container:tag('div'):addClass('bracket-wrapper') -- Iterate Rounds (1 to Total Rounds) for r = 1, rounds d..."
 
No edit summary
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- ================================================================
-- Module:Bracket v3.0 (Smart Nodes, Game Links, Info Popups)
-- ================================================================
local p = {}
local p = {}
local html = mw.html
local html = mw.html
local cargo = mw.ext.cargo


function p.main(frame)
local function esc(s) return s and s:gsub("\\", "\\\\"):gsub("'", "\\'") or "" end
     local args = frame:getParent().args
local function clean(s)
     local teamCount = tonumber(args.teams) or 8 -- Default to 8 teams
    if not s then return nil end
     local rounds = math.log(teamCount) / math.log(2) -- Calculate rounds (8 -> 3, 16 -> 4)
    local c = s:gsub("[\r\n]",""):gsub("^%s*(.-)%s*$","%1")
    return c ~= "" and c or nil
end
 
-- Smarter Date Formatter (Handles YYYY-MM-DD and YYYY-MM-DD HH:MM)
local function formatDate(d)
    if not d or d == "" then return "" end
    local year, month, day, hour, min = string.match(d, "(%d%d%d%d)%-(%d%d)%-(%d%d)[T%s]*(%d*)%:*(%d*)")
    if year then
        local months = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}
        local dateStr = tonumber(day) .. "-" .. months[tonumber(month)]
        if hour and hour ~= "" then
            dateStr = dateStr .. " " .. hour .. ":" .. (min ~= "" and min or "00")
        end
        return dateStr
    end
    return d
end
 
local teamLogoCache = {}
local function getTeamLogo(teamName)
    if not teamName or teamName == "" or teamName == "TBD" then return "" end
    if teamLogoCache[teamName] then return teamLogoCache[teamName] end
   
    local rows = cargo.query("Teams","image,image_dark",{where="name='"..esc(teamName).."'", limit=1})
    local t = (rows and #rows > 0) and rows[1] or false
    if not t then
        teamLogoCache[teamName] = ""
        return ""
    end
   
     local light = t.image or ""
    local dark = (t.image_dark and t.image_dark ~= "") and t.image_dark or light
    if light == "" then
        teamLogoCache[teamName] = ""
        return ""
    end
   
    local htmlStr = "[[File:"..light.."|18px|link=|class=logo-lightmode bk-team-logo]]"
        .. "[[File:"..dark .."|18px|link=|class=logo-darkmode bk-team-logo]]"
    teamLogoCache[teamName] = htmlStr
    return htmlStr
end
 
local function fetchMatch(event, matchID)
    if not event or event == "" then return {} end
    local rows = cargo.query("BracketMatch",
        "team1,score1,team2,score2,winner,bo,match_date,casters,vod,notes,short1,short2",
        { where = "event='"..esc(event).."' AND match_id='"..esc(matchID).."'", limit = 1 })
    if rows and #rows > 0 then return rows[1] end
    return {}
end
 
local function mergeMatchData(cargoData, args, prefix)
     local d = {}
    d.label  = clean(args[prefix.."label"])
    d.team1  = clean(args[prefix.."team1"])  or clean(cargoData.team1)  or "TBD"
    d.team2  = clean(args[prefix.."team2"])  or clean(cargoData.team2)  or "TBD"
    d.short1  = clean(args[prefix.."short1"])  or clean(cargoData.short1)  or d.team1
    d.short2  = clean(args[prefix.."short2"])  or clean(cargoData.short2)  or d.team2
    d.score1  = clean(args[prefix.."score1"])  or clean(cargoData.score1)  or ""
    d.score2  = clean(args[prefix.."score2"])  or clean(cargoData.score2)  or ""
    d.winner  = clean(args[prefix.."win"])    or clean(cargoData.winner)  or ""
    d.bo      = clean(args[prefix.."bo"])      or clean(cargoData.bo)      or ""
    d.date    = clean(args[prefix.."date"])    or clean(cargoData.match_date) or ""
    d.casters = clean(args[prefix.."casters"]) or clean(cargoData.casters) or ""
    d.vod    = clean(args[prefix.."vod"])    or clean(cargoData.vod)    or ""
    d.notes  = clean(args[prefix.."notes"])  or clean(cargoData.notes)  or ""
    return d
end
 
-- ── NEW MODERN MATCH CARD ──
local function renderMatchCard(d, matchID, defaultLabel, winTo, loseTo, target, isDrop, extraClass, game)
    local box = html.create('div'):addClass('bk-match')
        :attr('id', 'match-' .. matchID)
   
    if winTo and winTo ~= "" then box:attr('data-target-win', 'match-' .. winTo) end
    if loseTo and loseTo ~= "" then box:attr('data-target-lose', 'match-' .. loseTo) end
    if target and target ~= "" then box:attr('data-target', 'match-' .. target) end
    if isDrop then box:attr('data-drop', 'true') end
    if extraClass then box:addClass(extraClass) end
 
    local win1 = (d.winner == "1" or d.winner == d.team1) and d.team1 ~= "TBD"
    local win2 = (d.winner == "2" or d.winner == d.team2) and d.team2 ~= "TBD"
 
    -- Top Bar (Entire bar is now the clickable trigger)
    local top = box:tag('div'):addClass('bk-match-top')
      :attr('data-matchid', matchID) -- Ready for your future Match Page logic
      :attr('data-team1', d.team1):attr('data-team2', d.team2)
      :attr('data-score1', d.score1):attr('data-score2', d.score2)
      :attr('data-bo', d.bo):attr('data-date', d.date)
      :attr('data-casters', d.casters):attr('data-vod', d.vod):attr('data-notes', d.notes)
      :attr('title', 'Click for Match Details')
   
    -- The Label
    top:tag('span'):addClass('bk-match-label')
      :wikitext(d.label or defaultLabel or matchID)
   
    -- Meta Info (ONLY Date)
    local metaStr = formatDate(d.date)
    top:tag('span'):addClass('bk-match-meta'):wikitext(metaStr)
 
    -- Teams Wrapper
    local teamsWrap = box:tag('div'):addClass('bk-teams-wrap')
 
    local linkPrefix = ""
    if game and game ~= "" and game ~= "BGMI" then linkPrefix = game .. "/Teams/" end
 
    -- Team 1
    local row1 = teamsWrap:tag('div'):addClass('bk-team')
    if win1 then row1:addClass('bk-win') elseif win2 then row1:addClass('bk-lose') end
    row1:wikitext(getTeamLogo(d.team1))
    local t1Link = d.team1 ~= "TBD" and "[[" .. linkPrefix .. d.team1 .. "|" .. d.short1 .. "]]" or "TBD"
    row1:tag('span'):addClass('bk-team-name'):wikitext(t1Link)
    row1:tag('span'):addClass('bk-score'):wikitext(d.score1 ~= "" and d.score1 or "-")
 
    -- Team 2
    local row2 = teamsWrap:tag('div'):addClass('bk-team')
    if win2 then row2:addClass('bk-win') elseif win1 then row2:addClass('bk-lose') end
    row2:wikitext(getTeamLogo(d.team2))
    local t2Link = d.team2 ~= "TBD" and "[[" .. linkPrefix .. d.team2 .. "|" .. d.short2 .. "]]" or "TBD"
    row2:tag('span'):addClass('bk-team-name'):wikitext(t2Link)
    row2:tag('span'):addClass('bk-score'):wikitext(d.score2 ~= "" and d.score2 or "-")
 
    return box
end
 
-- ================================================================
-- FORMAT: SINGLE ELIMINATION
-- ================================================================
local function renderSingleElim(args, event, teamCount, game)
     local rounds = math.floor(math.log(teamCount) / math.log(2))
    local suffixNames = { "Grand Final", "Semifinals", "Quarterfinals", "Round of 16", "Round of 32" }
      
      
     local container = html.create('div'):addClass('bracket-scroll-wrapper')
     local scroll = html.create('div'):addClass('bk-scroll')
     local wrapper = container:tag('div'):addClass('bracket-wrapper')
     local wrapper = scroll:tag('div'):addClass('bk-wrapper')


    -- Iterate Rounds (1 to Total Rounds)
     for r = 1, rounds do
     for r = 1, rounds do
         local col = wrapper:tag('div'):addClass('bracket-col bracket-col-' .. r)
         local col = wrapper:tag('div'):addClass('bk-col')
         local matchesInRound = teamCount / (2^r)
         local pos = rounds - r + 1
       
         col:tag('div'):addClass('bk-col-header'):wikitext(args["r"..r.."name"] or suffixNames[pos] or ("Round "..r))
        -- Header (e.g., Quarterfinals)
        local headerTxt = "Round " .. r
        if r == rounds then headerTxt = "Grand Finals"
        elseif r == rounds - 1 then headerTxt = "Semifinals"
        elseif r == rounds - 2 then headerTxt = "Quarterfinals"
        end
         col:tag('div'):addClass('bracket-header'):wikitext(headerTxt)


         -- Iterate Matches in this Round
         local matchCount = teamCount / (2^r)
         for m = 1, matchesInRound do
         for m = 1, matchCount do
             local matchID = "R" .. r .. "M" .. m
             local matchID = "R"..r.."M"..m
            local cd = fetchMatch(event, matchID)
            local d  = mergeMatchData(cd, args, matchID)
              
              
             -- Calculate Spacing (The Magic Math)
             local target = ""
            -- This aligns the matches vertically relative to previous round
             local isFinal = (r == rounds)
             local spacer = col:tag('div'):addClass('bracket-spacer')
             if not isFinal then
              
                local nextMatchNum = math.ceil(m / 2)
            -- DATA INPUTS
                target = "R"..(r+1).."M"..nextMatchNum
            local t1 = args[matchID .. 'team1'] or 'TBD'
             end
            local s1 = args[matchID .. 'score1'] or ''
            local t2 = args[matchID .. 'team2'] or 'TBD'
            local s2 = args[matchID .. 'score2'] or ''
             local win = args[matchID .. 'win'] -- '1' or '2' to highlight winner


            -- MATCH CARD
             local card = renderMatchCard(d, matchID, "Match "..m, nil, nil, target, false, isFinal and "bk-final-match" or "", game)
             local matchBox = col:tag('div'):addClass('bracket-match')
             col:node(card)
              
        end
            -- Team 1 Row
    end
            local row1 = matchBox:tag('div'):addClass('bracket-team')
    return tostring(scroll)
            if win == '1' then row1:addClass('bracket-win') end
end
            row1:tag('div'):addClass('bracket-name'):wikitext(t1)
            row1:tag('div'):addClass('bracket-score'):wikitext(s1)


            -- Team 2 Row
-- ================================================================
            local row2 = matchBox:tag('div'):addClass('bracket-team')
-- FORMAT: CUSTOM MANUAL BRACKET
            if win == '2' then row2:addClass('bracket-win') end
-- ================================================================
            row2:tag('div'):addClass('bracket-name'):wikitext(t2)
local function renderCustom(args, event, game)
             row2:tag('div'):addClass('bracket-score'):wikitext(s2)
    local scroll = html.create('div'):addClass('bk-scroll')
              
    local wrapper = scroll:tag('div'):addClass('bk-wrapper')
            -- Add Connector Lines (except for last round)
   
            if r < rounds then
    local cols = tonumber(args.columns) or 2
                 matchBox:addClass('has-connector')
    for c = 1, cols do
        local col = wrapper:tag('div'):addClass('bk-col')
       
        local colName = clean(args["col"..c.."name"])
        if colName and colName:lower() ~= "none" then
             col:tag('div'):addClass('bk-col-header'):wikitext(colName)
        end
       
        local matchesStr = args["col"..c.."_matches"]
        if matchesStr then
             for mID in string.gmatch(matchesStr, '([^,]+)') do
                mID = clean(mID)
                local cd = fetchMatch(event, mID)
                local d  = mergeMatchData(cd, args, mID)
                local winTo = args[mID.."_win_to"]
                local loseTo = args[mID.."_lose_to"]
                local target = args[mID.."_target"]
                local isDrop = args[mID.."_drop"] == "true"
               
                 col:node(renderMatchCard(d, mID, args[mID.."_label"], winTo, loseTo, target, isDrop, "", game))
             end
             end
         end
         end
     end
     end
    return tostring(scroll)
end
-- ================================================================
-- MAIN ENTRY
-- ================================================================
function p.main(frame)
    local args = (frame:getParent() and frame:getParent().args) or frame.args
    local format = (clean(args.format) or "single"):lower()
    local event = clean(args.event) or ""
    local game = clean(args.game) or "" -- Pass the game prefix (e.g. "Honor of Kings")
    local teamCount = tonumber(args.teams) or 8
    local root = html.create('div'):addClass('bk-root')
    local bracketHTML
    if format == "single" then
        bracketHTML = renderSingleElim(args, event, teamCount, game)
    elseif format == "custom" then
        bracketHTML = renderCustom(args, event, game)
    else
        bracketHTML = "<div style='color:red; padding:20px'>Format '"..format.."' is currently being upgraded to v3.0. Use 'single' or 'custom' for now.</div>"
    end
    root:wikitext(bracketHTML)
    -- MODAL POPUP
    local overlay = root:tag('div'):addClass('bk-modal-overlay')
    local modal = overlay:tag('div'):addClass('bk-modal')
    modal:tag('span'):addClass('bk-modal-close'):attr('role','button'):wikitext('&#10005;')
    local mheader = modal:tag('div'):addClass('bk-modal-header')
    local mteams = mheader:tag('div'):addClass('bk-modal-teams')
    mteams:tag('span'):addClass('bk-modal-t1')
    mteams:tag('span'):addClass('bk-modal-vs'):wikitext('vs')
    mteams:tag('span'):addClass('bk-modal-t2')
    local mscore = mheader:tag('div'):addClass('bk-modal-score')
    mscore:tag('span'):addClass('bk-modal-s1')
    mscore:tag('span'):addClass('bk-modal-dash'):wikitext('&#8211;')
    mscore:tag('span'):addClass('bk-modal-s2')
    local mmeta = modal:tag('div'):addClass('bk-modal-meta')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-bo')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-date')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-casters')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-notes')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-vod')


     return tostring(container)
     return tostring(root)
end
end


return p
return p

Latest revision as of 03:39, 4 April 2026

Documentation for this module may be created at Module:Bracket/doc

-- ================================================================
-- Module:Bracket v3.0 (Smart Nodes, Game Links, Info Popups)
-- ================================================================
local p = {}
local html = mw.html
local cargo = mw.ext.cargo

local function esc(s) return s and s:gsub("\\", "\\\\"):gsub("'", "\\'") or "" end
local function clean(s)
    if not s then return nil end
    local c = s:gsub("[\r\n]",""):gsub("^%s*(.-)%s*$","%1")
    return c ~= "" and c or nil
end

-- Smarter Date Formatter (Handles YYYY-MM-DD and YYYY-MM-DD HH:MM)
local function formatDate(d)
    if not d or d == "" then return "" end
    local year, month, day, hour, min = string.match(d, "(%d%d%d%d)%-(%d%d)%-(%d%d)[T%s]*(%d*)%:*(%d*)")
    if year then
        local months = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}
        local dateStr = tonumber(day) .. "-" .. months[tonumber(month)]
        if hour and hour ~= "" then
            dateStr = dateStr .. " " .. hour .. ":" .. (min ~= "" and min or "00")
        end
        return dateStr
    end
    return d 
end

local teamLogoCache = {}
local function getTeamLogo(teamName)
    if not teamName or teamName == "" or teamName == "TBD" then return "" end
    if teamLogoCache[teamName] then return teamLogoCache[teamName] end
    
    local rows = cargo.query("Teams","image,image_dark",{where="name='"..esc(teamName).."'", limit=1})
    local t = (rows and #rows > 0) and rows[1] or false
    if not t then 
        teamLogoCache[teamName] = ""
        return "" 
    end
    
    local light = t.image or ""
    local dark = (t.image_dark and t.image_dark ~= "") and t.image_dark or light
    if light == "" then 
        teamLogoCache[teamName] = ""
        return "" 
    end
    
    local htmlStr = "[[File:"..light.."|18px|link=|class=logo-lightmode bk-team-logo]]"
        .. "[[File:"..dark .."|18px|link=|class=logo-darkmode bk-team-logo]]"
    teamLogoCache[teamName] = htmlStr
    return htmlStr
end

local function fetchMatch(event, matchID)
    if not event or event == "" then return {} end
    local rows = cargo.query("BracketMatch",
        "team1,score1,team2,score2,winner,bo,match_date,casters,vod,notes,short1,short2",
        { where = "event='"..esc(event).."' AND match_id='"..esc(matchID).."'", limit = 1 })
    if rows and #rows > 0 then return rows[1] end
    return {}
end

local function mergeMatchData(cargoData, args, prefix)
    local d = {}
    d.label   = clean(args[prefix.."label"])
    d.team1   = clean(args[prefix.."team1"])   or clean(cargoData.team1)   or "TBD"
    d.team2   = clean(args[prefix.."team2"])   or clean(cargoData.team2)   or "TBD"
    d.short1  = clean(args[prefix.."short1"])  or clean(cargoData.short1)  or d.team1
    d.short2  = clean(args[prefix.."short2"])  or clean(cargoData.short2)  or d.team2
    d.score1  = clean(args[prefix.."score1"])  or clean(cargoData.score1)  or ""
    d.score2  = clean(args[prefix.."score2"])  or clean(cargoData.score2)  or ""
    d.winner  = clean(args[prefix.."win"])     or clean(cargoData.winner)  or ""
    d.bo      = clean(args[prefix.."bo"])      or clean(cargoData.bo)      or ""
    d.date    = clean(args[prefix.."date"])    or clean(cargoData.match_date) or ""
    d.casters = clean(args[prefix.."casters"]) or clean(cargoData.casters) or ""
    d.vod     = clean(args[prefix.."vod"])     or clean(cargoData.vod)     or ""
    d.notes   = clean(args[prefix.."notes"])   or clean(cargoData.notes)   or ""
    return d
end

-- ── NEW MODERN MATCH CARD ──
local function renderMatchCard(d, matchID, defaultLabel, winTo, loseTo, target, isDrop, extraClass, game)
    local box = html.create('div'):addClass('bk-match')
        :attr('id', 'match-' .. matchID)
    
    if winTo and winTo ~= "" then box:attr('data-target-win', 'match-' .. winTo) end
    if loseTo and loseTo ~= "" then box:attr('data-target-lose', 'match-' .. loseTo) end
    if target and target ~= "" then box:attr('data-target', 'match-' .. target) end
    if isDrop then box:attr('data-drop', 'true') end
    if extraClass then box:addClass(extraClass) end

    local win1 = (d.winner == "1" or d.winner == d.team1) and d.team1 ~= "TBD"
    local win2 = (d.winner == "2" or d.winner == d.team2) and d.team2 ~= "TBD"

    -- Top Bar (Entire bar is now the clickable trigger)
    local top = box:tag('div'):addClass('bk-match-top')
       :attr('data-matchid', matchID) -- Ready for your future Match Page logic
       :attr('data-team1', d.team1):attr('data-team2', d.team2)
       :attr('data-score1', d.score1):attr('data-score2', d.score2)
       :attr('data-bo', d.bo):attr('data-date', d.date)
       :attr('data-casters', d.casters):attr('data-vod', d.vod):attr('data-notes', d.notes)
       :attr('title', 'Click for Match Details')
    
    -- The Label
    top:tag('span'):addClass('bk-match-label')
       :wikitext(d.label or defaultLabel or matchID)
    
    -- Meta Info (ONLY Date)
    local metaStr = formatDate(d.date)
    top:tag('span'):addClass('bk-match-meta'):wikitext(metaStr)

    -- Teams Wrapper
    local teamsWrap = box:tag('div'):addClass('bk-teams-wrap')

    local linkPrefix = ""
    if game and game ~= "" and game ~= "BGMI" then linkPrefix = game .. "/Teams/" end

    -- Team 1
    local row1 = teamsWrap:tag('div'):addClass('bk-team')
    if win1 then row1:addClass('bk-win') elseif win2 then row1:addClass('bk-lose') end
    row1:wikitext(getTeamLogo(d.team1))
    local t1Link = d.team1 ~= "TBD" and "[[" .. linkPrefix .. d.team1 .. "|" .. d.short1 .. "]]" or "TBD"
    row1:tag('span'):addClass('bk-team-name'):wikitext(t1Link)
    row1:tag('span'):addClass('bk-score'):wikitext(d.score1 ~= "" and d.score1 or "-")

    -- Team 2
    local row2 = teamsWrap:tag('div'):addClass('bk-team')
    if win2 then row2:addClass('bk-win') elseif win1 then row2:addClass('bk-lose') end
    row2:wikitext(getTeamLogo(d.team2))
    local t2Link = d.team2 ~= "TBD" and "[[" .. linkPrefix .. d.team2 .. "|" .. d.short2 .. "]]" or "TBD"
    row2:tag('span'):addClass('bk-team-name'):wikitext(t2Link)
    row2:tag('span'):addClass('bk-score'):wikitext(d.score2 ~= "" and d.score2 or "-")

    return box
end

-- ================================================================
-- FORMAT: SINGLE ELIMINATION
-- ================================================================
local function renderSingleElim(args, event, teamCount, game)
    local rounds = math.floor(math.log(teamCount) / math.log(2))
    local suffixNames = { "Grand Final", "Semifinals", "Quarterfinals", "Round of 16", "Round of 32" }
    
    local scroll = html.create('div'):addClass('bk-scroll')
    local wrapper = scroll:tag('div'):addClass('bk-wrapper')

    for r = 1, rounds do
        local col = wrapper:tag('div'):addClass('bk-col')
        local pos = rounds - r + 1
        col:tag('div'):addClass('bk-col-header'):wikitext(args["r"..r.."name"] or suffixNames[pos] or ("Round "..r))

        local matchCount = teamCount / (2^r)
        for m = 1, matchCount do
            local matchID = "R"..r.."M"..m
            local cd = fetchMatch(event, matchID)
            local d  = mergeMatchData(cd, args, matchID)
            
            local target = ""
            local isFinal = (r == rounds)
            if not isFinal then
                local nextMatchNum = math.ceil(m / 2)
                target = "R"..(r+1).."M"..nextMatchNum
            end

            local card = renderMatchCard(d, matchID, "Match "..m, nil, nil, target, false, isFinal and "bk-final-match" or "", game)
            col:node(card)
        end
    end
    return tostring(scroll)
end

-- ================================================================
-- FORMAT: CUSTOM MANUAL BRACKET
-- ================================================================
local function renderCustom(args, event, game)
    local scroll = html.create('div'):addClass('bk-scroll')
    local wrapper = scroll:tag('div'):addClass('bk-wrapper')
    
    local cols = tonumber(args.columns) or 2
    for c = 1, cols do
        local col = wrapper:tag('div'):addClass('bk-col')
        
        local colName = clean(args["col"..c.."name"])
        if colName and colName:lower() ~= "none" then
            col:tag('div'):addClass('bk-col-header'):wikitext(colName)
        end
        
        local matchesStr = args["col"..c.."_matches"]
        if matchesStr then
            for mID in string.gmatch(matchesStr, '([^,]+)') do
                mID = clean(mID)
                local cd = fetchMatch(event, mID)
                local d  = mergeMatchData(cd, args, mID)
                local winTo = args[mID.."_win_to"]
                local loseTo = args[mID.."_lose_to"]
                local target = args[mID.."_target"] 
                local isDrop = args[mID.."_drop"] == "true" 
                
                col:node(renderMatchCard(d, mID, args[mID.."_label"], winTo, loseTo, target, isDrop, "", game))
            end
        end
    end
    return tostring(scroll)
end

-- ================================================================
-- MAIN ENTRY
-- ================================================================
function p.main(frame)
    local args = (frame:getParent() and frame:getParent().args) or frame.args
    local format = (clean(args.format) or "single"):lower()
    local event = clean(args.event) or ""
    local game = clean(args.game) or "" -- Pass the game prefix (e.g. "Honor of Kings")
    local teamCount = tonumber(args.teams) or 8

    local root = html.create('div'):addClass('bk-root')
    local bracketHTML

    if format == "single" then
        bracketHTML = renderSingleElim(args, event, teamCount, game)
    elseif format == "custom" then
        bracketHTML = renderCustom(args, event, game)
    else
        bracketHTML = "<div style='color:red; padding:20px'>Format '"..format.."' is currently being upgraded to v3.0. Use 'single' or 'custom' for now.</div>"
    end

    root:wikitext(bracketHTML)

    -- MODAL POPUP
    local overlay = root:tag('div'):addClass('bk-modal-overlay')
    local modal = overlay:tag('div'):addClass('bk-modal')
    modal:tag('span'):addClass('bk-modal-close'):attr('role','button'):wikitext('&#10005;')
    local mheader = modal:tag('div'):addClass('bk-modal-header')
    local mteams = mheader:tag('div'):addClass('bk-modal-teams')
    mteams:tag('span'):addClass('bk-modal-t1')
    mteams:tag('span'):addClass('bk-modal-vs'):wikitext('vs')
    mteams:tag('span'):addClass('bk-modal-t2')
    local mscore = mheader:tag('div'):addClass('bk-modal-score')
    mscore:tag('span'):addClass('bk-modal-s1')
    mscore:tag('span'):addClass('bk-modal-dash'):wikitext('&#8211;')
    mscore:tag('span'):addClass('bk-modal-s2')
    local mmeta = modal:tag('div'):addClass('bk-modal-meta')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-bo')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-date')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-casters')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-notes')
    mmeta:tag('div'):addClass('bk-modal-row bk-modal-vod')

    return tostring(root)
end

return p