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

Module:Tournament: Difference between revisions

From eSportsAmaze
No edit summary
No edit summary
 
(15 intermediate revisions by the same user not shown)
Line 50: Line 50:


-- Helper: Fetch Winner/RunnerUp from PrizeMoney Table
-- Helper: Fetch Winner/RunnerUp from PrizeMoney Table
-- SUPER ROBUST VERSION:
local function getTeamFromPrizeMoney(pageName, tournamentName, placeType)
-- 1. Checks 'tournament' column (User defined)
     if not cargo then return nil end
-- 2. Checks exact _pageName (Same page)
-- 3. Checks subpages (e.g. Title/Prize_Pool)
local function getTeamFromPrizeMoney(pageName, placeType)
     if not cargo or not pageName then return nil end
      
      
    -- 1. Define Placement Logic (1, 1st, Winner, etc.)
     local placeClause = ""
     local placeClause = ""
     if placeType == "1" then
     if placeType == "1" then
Line 67: Line 62:
     end
     end
      
      
     -- 2. Define Page Logic (The Fix for Nested Pages)
     local pageClause = ""
     -- We use 'LIKE' to find subpages automatically
     if tournamentName and tournamentName ~= "" then
    local pageClause = string.format('(tournament="%s" OR _pageName="%s" OR _pageName LIKE "%s/%%")', pageName, pageName, pageName)
        pageClause = string.format('(tournament="%s" OR _pageName="%s" OR _pageName LIKE "%%%s%%")', tournamentName, pageName, tournamentName)
    else
        pageClause = string.format('(_pageName="%s" OR _pageName LIKE "%s/%%")', pageName, pageName)
    end
      
      
    -- 3. Run Query
     local results = cargo.query('PrizeMoney', 'team', {
     local results = cargo.query('PrizeMoney', 'team', {
         where = pageClause .. ' AND ' .. placeClause,
         where = pageClause .. ' AND ' .. placeClause,
Line 83: Line 80:
end
end


-- Social Icons (File Upload Method)
-- Social Icons
local function getSocials(args)
local function getSocials(args)
     local container = html.create('div'):addClass('fib-socials')
     local container = html.create('div'):addClass('fib-socials')
Line 170: Line 167:
     end
     end
     return tostring(container)
     return tostring(container)
end
-- Helper: Get Tier Class (NEW)
local function getTierClass(tier)
    if not tier then return "tier-misc" end
    local t = tier:lower()
    if string.find(t, "s") and string.find(t, "tier") then return "tier-s" end
    if string.find(t, "a") and string.find(t, "tier") then return "tier-a" end
    if string.find(t, "b") and string.find(t, "tier") then return "tier-b" end
    if string.find(t, "c") and string.find(t, "tier") then return "tier-c" end
    return "tier-misc"
end
end


Line 182: Line 190:
     local dbPageName = mw.title.getCurrentTitle().prefixedText
     local dbPageName = mw.title.getCurrentTitle().prefixedText
      
      
    -- 1. Determine Winner (Manual OR Auto-Fetch "1st")
     local finalWinner = args.winner
     local finalWinner = args.winner
     if not finalWinner or finalWinner == "" then
     if not finalWinner or finalWinner == "" then
         finalWinner = getTeamFromPrizeMoney(dbPageName, "1st")
         finalWinner = getTeamFromPrizeMoney(dbPageName, cleanName, "1")
     end
     end
      
      
    -- 2. Determine Runner Up (Manual OR Auto-Fetch "2nd")
     local finalRunnerUp = args.runner_up
     local finalRunnerUp = args.runner_up
     if not finalRunnerUp or finalRunnerUp == "" then
     if not finalRunnerUp or finalRunnerUp == "" then
         finalRunnerUp = getTeamFromPrizeMoney(dbPageName, "2nd")
         finalRunnerUp = getTeamFromPrizeMoney(dbPageName, cleanName, "2")
     end
     end
      
      
Line 259: Line 265:
     addRow('Dates', formatDateRange(args.start_date, args.end_date))
     addRow('Dates', formatDateRange(args.start_date, args.end_date))
     addRow('Device', args.device)
     addRow('Device', args.device)
   
     if finalWinner then addRow('Winner', "'''[[" .. finalWinner .. "]]'''") end
     if finalWinner then addRow('Winner', "'''[[" .. finalWinner .. "]]'''") end
    if finalRunnerUp then addRow('Runner Up', "'''[[" .. finalRunnerUp .. "]]'''") end
      
      
     root:wikitext(getSocials(args))
     root:wikitext(getSocials(args))
   
     if args.series and args.series_value then root:wikitext(getSeriesNav(args.series, args.series_value)) end
     if args.series and args.series_value then
        root:wikitext(getSeriesNav(args.series, args.series_value))
    end
 
     return tostring(root)
     return tostring(root)
end
end


-- ============================================================
-- ============================================================
-- MAIN 2: TABLE ROW (Sortable Table on Tournaments Page)
-- MAIN 2: TABLE ROW (UPDATED FOR HYBRID DESIGN)
-- ============================================================
-- ============================================================
function p.tableRow(frame)
function p.tableRow(frame)
Line 281: Line 283:
     local endDate = args.end_date or ""
     local endDate = args.end_date or ""
     local prize = args.prize_pool or "0"
     local prize = args.prize_pool or "0"
    local tier = args.tier or "-"
      
      
     -- Auto-Fetch "1st" and "2nd" from Database if not provided
     -- Auto-Fetch Winners
     local winner = args.winner
     local winner = args.winner
     if not winner or winner == "" then
     if not winner or winner == "" then winner = getTeamFromPrizeMoney(page, name, "1") end
        winner = getTeamFromPrizeMoney(page, "1st")
    end
      
      
     local runnerUp = args.runner_up
     local runnerUp = args.runner_up
     if not runnerUp or runnerUp == "" then
     if not runnerUp or runnerUp == "" then runnerUp = getTeamFromPrizeMoney(page, name, "2") end
        runnerUp = getTeamFromPrizeMoney(page, "2nd")
    end
      
      
     local row = html.create('tr')
     local row = html.create('tr')
      
      
     row:tag('td'):attr('data-sort-value', startDate):css('white-space', 'nowrap'):wikitext(formatDateRange(startDate, endDate))
     -- 1. Date (Desktop: Box, Mobile: Label)
     row:tag('td'):css('text-align', 'center'):wikitext(args.tier or '-')
     row:tag('td')
    row:tag('td'):css('font-weight', 'bold'):wikitext('[[' .. page .. '|' .. name .. ']]')
      :attr('data-label', 'Date')
    row:tag('td'):wikitext(args.location or '-')
      :attr('data-sort-value', startDate)
    row:tag('td'):attr('data-sort-value', prize):css('text-align', 'right'):wikitext(formatCurrency(prize))
      :tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
        
        
    -- 2. Tier (Color Coded Pill)
    row:tag('td')
      :attr('data-label', 'Tier')
      :css('text-align', 'center')
      :tag('span'):addClass('tier-badge'):addClass(getTierClass(tier)):wikitext(tier)
     
    -- 3. Tournament Name
    row:tag('td')
      :attr('data-label', 'Tournament')
      :css('font-weight', 'bold'):wikitext('[[' .. page .. '|' .. name .. ']]')
     
    -- 4. Location
    row:tag('td')
      :attr('data-label', 'Location')
      :wikitext(args.location or '-')
   
    -- 5. Prize
    row:tag('td')
      :attr('data-label', 'Prize')
      :attr('data-sort-value', prize)
      :addClass('tr-prize')
      :css('text-align', 'right')
      :wikitext(formatCurrency(prize))
     
    -- 6. Winner (Gold Background on Desktop)
     local winCell = row:tag('td')
     local winCell = row:tag('td')
        :attr('data-label', 'Winner')
        :addClass('cell-winner')
   
     if winner and winner ~= "" then
     if winner and winner ~= "" then
         winCell:wikitext(getSmallTeamLogo(winner) .. '[[' .. winner .. ']]')
         winCell:wikitext(getSmallTeamLogo(winner) .. '[[' .. winner .. ']]')
Line 308: Line 335:
     end
     end
      
      
     local runCell = row:tag('td')
    -- 7. Runner Up
     local runCell = row:tag('td'):attr('data-label', 'Runner Up')
     if runnerUp and runnerUp ~= "" then
     if runnerUp and runnerUp ~= "" then
         runCell:wikitext(getSmallTeamLogo(runnerUp) .. '[[' .. runnerUp .. ']]')
         runCell:wikitext(getSmallTeamLogo(runnerUp) .. '[[' .. runnerUp .. ']]')
Line 319: Line 347:


-- ============================================================
-- ============================================================
-- MAIN 3: LIST ROW
-- MAIN 3: LIST ROW (With Left Strip Logic)
-- ============================================================
-- ============================================================
function p.listRow(frame)
function p.listRow(frame)
Line 325: Line 353:
     local page = args.Page or ""
     local page = args.Page or ""
     local name = getCleanTitle(page)
     local name = getCleanTitle(page)
     local startDate = args.start_date or ""
   
     local endDate = args.end_date or ""
     local startDate = args.start_date or args.date or args.startdate or ""
     local endDate = args.end_date or args.enddate or ""
    local prize = args.prize_pool or args.prizepool or args.prize or "0"
    local tier = args.tier or ""
    local image = args.image or ""
    local imageDark = args.image_dark or ""
      
      
     local winner = args.winner
     local winner = args.winner
     if not winner or winner == "" then
     if not winner or winner == "" then
         winner = getTeamFromPrizeMoney(page, "1st")
         winner = getTeamFromPrizeMoney(page, name, "1")
     end
     end
      
      
    -- CREATE ROW: Removed inline background styles to allow CSS variables to work
     local row = html.create('div'):addClass('tourney-row')
     local row = html.create('div'):addClass('tourney-row')
   
    if tier ~= "" then
        row:addClass('row-' .. getTierClass(tier))
    end
   
    -- Date Box
     row:tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
     row:tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
   
    -- Logo
    row:tag('div'):addClass('tr-event-logo-col'):wikitext(getTourneyLogo(image, imageDark))
   
    -- Info
     local info = row:tag('div'):addClass('tr-info')
     local info = row:tag('div'):addClass('tr-info')
     info:tag('div'):addClass('tr-name'):wikitext('[[' .. page .. '|' .. name .. ']]')
     local nameRow = info:tag('div'):addClass('tr-name-row')
    nameRow:tag('span'):addClass('tr-name'):wikitext('[[' .. page .. '|' .. name .. ']]')
   
    if tier ~= "" then
        local tierClass = getTierClass(tier)
        nameRow:tag('span'):addClass('tier-badge'):addClass(tierClass):css('margin-left','8px'):wikitext(tier)
    end
   
     if args.organizer then info:tag('div'):addClass('tr-org'):wikitext(args.organizer) end
     if args.organizer then info:tag('div'):addClass('tr-org'):wikitext(args.organizer) end
      
      
    -- Winner
     local winDiv = row:tag('div'):addClass('tr-winner mobile-hide')
     local winDiv = row:tag('div'):addClass('tr-winner mobile-hide')
     if winner and winner ~= "" then  
     if winner and winner ~= "" then  
         winDiv:wikitext("🏆 " .. winner)  
         winDiv:wikitext("🏆 " .. winner)  
     else  
     else  
         winDiv:tag('span'):addClass('dim-text'):wikitext('-')  
         winDiv:tag('span'):css('opacity', '0.3'):wikitext('-')  
     end
     end
      
      
    -- Prize
     local prizeDiv = row:tag('div'):addClass('tr-prize')
     local prizeDiv = row:tag('div'):addClass('tr-prize')
     prizeDiv:wikitext(formatCurrency(args.prize_pool))
     prizeDiv:wikitext(formatCurrency(prize))
   
     return tostring(row)
     return tostring(row)
end
end
Line 371: Line 426:
     prizeDiv:wikitext(formatCurrency(args.prize_pool))
     prizeDiv:wikitext(formatCurrency(args.prize_pool))
     return tostring(row)
     return tostring(row)
end
-- ============================================================
-- MAIN 5: PAGINATED QUERY (Connects to listRow)
-- ============================================================
function p.mainQuery(frame)
    local args = frame.args
   
    -- 1. PAGINATION SETUP
    -- Get offset from URL (defaults to 0 if missing)
    local offset = tonumber(args.offset) or 0
    local limit = 50 -- Show 50 tournaments per page
   
    -- 2. BUILD QUERY
    local whereClause = "start_date IS NOT NULL"
   
    -- 3. RUN CARGO QUERY
    -- We select the exact fields required by your p.listRow function
    local results = cargo.query(
        'Tournaments',
        '_pageName, start_date, end_date, tier, prize_pool, winner, organizer, image, image_dark',
        {
            where = whereClause,
            orderBy = 'start_date DESC',
            limit = limit,
            offset = offset
        }
    )
    -- 4. CONTAINER & EMPTY STATE
    local container = html.create('div'):addClass('tournament-list-wrapper')
   
    if not results or #results == 0 then
        if offset > 0 then
            -- User went too far, give a link back to start
            return '<div style="padding:40px; text-align:center; color:#64748b;">No more tournaments found.<br><br><b>[[' .. tostring(mw.uri.fullUrl(mw.title.getCurrentTitle().text)) .. '|Back to Start]]</b></div>'
        else
            return '<div style="padding:40px; text-align:center; color:#64748b;">No tournaments found in database.</div>'
        end
    end
   
    -- 5. RENDER ROWS (Using your Main 3 logic)
    for _, row in ipairs(results) do
        -- We structure the data exactly how p.listRow expects it
        local rowArgs = {
            Page = row._pageName,
            start_date = row.start_date,
            end_date = row.end_date,
            tier = row.tier,
            prize_pool = row.prize_pool,
            winner = row.winner,
            organizer = row.organizer,
            image = row.image,
            image_dark = row.image_dark
        }
        -- Call your existing function
        container:wikitext(p.listRow({ args = rowArgs }))
    end
    -- 6. ADD PAGINATION BUTTONS
    local navDiv = container:tag('div'):addClass('pagination-controls')
   
    -- "Previous" Button (Hide if on first page)
    if offset > 0 then
        local prevOffset = math.max(0, offset - limit)
        local prevUrl = mw.uri.fullUrl(mw.title.getCurrentTitle().text, {offset=prevOffset})
        navDiv:tag('span'):addClass('page-btn')
              :wikitext('[' .. tostring(prevUrl) .. ' « Newer Events]')
    end
   
    -- "Next" Button (Hide if we have fewer results than the limit)
    if #results == limit then
        local nextOffset = offset + limit
        local nextUrl = mw.uri.fullUrl(mw.title.getCurrentTitle().text, {offset=nextOffset})
        navDiv:tag('span'):addClass('page-btn')
              :wikitext('[' .. tostring(nextUrl) .. ' Older Events »]')
    end
    return tostring(container)
end
end


return p
return p

Latest revision as of 09:17, 28 January 2026

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

local p = {}
local html = mw.html
local cargo = mw.ext.cargo
local lang = mw.getContentLanguage()

-- ============================================================
-- HELPER FUNCTIONS
-- ============================================================

local function formatCurrency(amount)
    if not amount or amount == "" or amount == "0" then return "TBD" end
    local currency = "₹" 
    if string.find(amount, "%$") then currency = "$" end
    local n = tostring(amount):gsub(",", ""):gsub("₹", ""):gsub("%$", "")
    if #n <= 3 then return currency .. " " .. n end
    local last3 = n:sub(-3)
    local rest = n:sub(1, -4)
    local formattedRest = rest:reverse():gsub("(%d%d)", "%1,"):reverse()
    if formattedRest:sub(1, 1) == "," then formattedRest = formattedRest:sub(2) end
    return '<span class="pz-prize">' .. currency .. " " .. formattedRest .. "," .. last3 .. '</span>'
end

local function formatDateRange(startStr, endStr)
    if not startStr or startStr == "" then return "TBA" end
    if endStr and endStr ~= "" and endStr ~= startStr then
        local s = lang:formatDate('d M', startStr)
        local e = lang:formatDate('d M, Y', endStr)
        return s .. " – " .. e 
    else
        return lang:formatDate('d M, Y', startStr)
    end
end

local function getCleanTitle(pageLink)
    if not pageLink then return "" end
    local title = mw.title.new(pageLink)
    if title then return title.subpageText end
    return pageLink
end

-- Helper: Small Team Logo
local function getSmallTeamLogo(teamName)
    if not teamName or teamName == "" then return "" end
    local fileName = teamName .. '.png'
    if mw.title.new('File:' .. fileName).exists then
        return '[[File:' .. fileName .. '|25px|link=' .. teamName .. '|class=team-logo-small]] '
    end
    return ''
end

-- Helper: Fetch Winner/RunnerUp from PrizeMoney Table
local function getTeamFromPrizeMoney(pageName, tournamentName, placeType)
    if not cargo then return nil end
    
    local placeClause = ""
    if placeType == "1" then
        placeClause = '(placement="1" OR placement="1st" OR placement="Winner" OR placement="Champion")'
    elseif placeType == "2" then
        placeClause = '(placement="2" OR placement="2nd" OR placement="Runner-up" OR placement="Runner Up")'
    else
        return nil
    end
    
    local pageClause = ""
    if tournamentName and tournamentName ~= "" then
        pageClause = string.format('(tournament="%s" OR _pageName="%s" OR _pageName LIKE "%%%s%%")', tournamentName, pageName, tournamentName)
    else
        pageClause = string.format('(_pageName="%s" OR _pageName LIKE "%s/%%")', pageName, pageName)
    end
    
    local results = cargo.query('PrizeMoney', 'team', {
        where = pageClause .. ' AND ' .. placeClause,
        limit = 1
    })
    
    if results and #results > 0 then
        return results[1].team
    end
    return nil
end

-- Social Icons
local function getSocials(args)
    local container = html.create('div'):addClass('fib-socials')
    local hasSocials = false
    local platforms = {
        {arg='instagram', file='Icon_instagram.png'},
        {arg='twitter',   file='Icon_twitter.png'},
        {arg='youtube',   file='Icon_youtube.png'},
        {arg='discord',   file='Icon_discord.png'},
        {arg='facebook',  file='Icon_facebook.png'},
        {arg='website',   file='Icon_website.png'}
    }
    for _, p in ipairs(platforms) do
        if args[p.arg] and args[p.arg] ~= "" then
            hasSocials = true
            container:wikitext('[[File:' .. p.file .. '|24px|link=' .. args[p.arg] .. '|class=social-img]]')
        end
    end
    if hasSocials then return tostring(container) else return "" end
end

-- Smart Series Navigation
local function getSeriesNav(currentSeries, currentValue)
    if not currentSeries or not currentValue or not cargo then return "" end
    local nav = html.create('div'):addClass('fib-nav')
    
    local prevQuery = cargo.query('Tournaments', '_pageName, series_season', {
        where = string.format('series = "%s" AND series_value < %s', currentSeries, currentValue),
        orderBy = 'series_value DESC', limit = 1
    })
    local prevBtn = nav:tag('div'):addClass('fib-nav-btn prev')
    if prevQuery and #prevQuery > 0 then
        prevBtn:wikitext('[[' .. prevQuery[1]._pageName .. '|« ' .. (prevQuery[1].series_season or "Previous") .. ']]')
    else
        prevBtn:css('opacity', '0.3'):wikitext('« Previous')
    end
    
    local nextQuery = cargo.query('Tournaments', '_pageName, series_season', {
        where = string.format('series = "%s" AND series_value > %s', currentSeries, currentValue),
        orderBy = 'series_value ASC', limit = 1
    })
    local nextBtn = nav:tag('div'):addClass('fib-nav-btn next')
    if nextQuery and #nextQuery > 0 then
        nextBtn:wikitext('[[' .. nextQuery[1]._pageName .. '|' .. (nextQuery[1].series_season or "Next") .. ' »]]')
    else
        nextBtn:css('opacity', '0.3'):wikitext('Next »')
    end
    return tostring(nav)
end

-- Infobox Logo
local function getInfoboxLogo(pageName, image, imageDark)
    local lightFile = (image ~= "" and image) or (pageName .. '.png')
    local darkFile = imageDark
    if not darkFile or darkFile == "" then
        local ext = lightFile:match("^.+(%..+)$") or ".png"
        local name = lightFile:gsub("%..+$", "")
        darkFile = name .. "_dark" .. ext
    end
    local hasLight = mw.title.new('File:' .. lightFile).exists
    local hasDark = mw.title.new('File:' .. darkFile).exists
    local container = html.create('div'):addClass('fib-image')
    local lSpan = container:tag('span'):addClass('logo-lightmode')
    if hasLight then lSpan:wikitext('[[File:' .. lightFile .. '|250px]]') else lSpan:wikitext('[[File:Shield_team.png|150px]]') end
    local dSpan = container:tag('span'):addClass('logo-darkmode')
    if hasDark then dSpan:wikitext('[[File:' .. darkFile .. '|250px]]') elseif hasLight then dSpan:wikitext('[[File:' .. lightFile .. '|250px]]') else dSpan:wikitext('[[File:Shield_team_dark.png|150px]]') end
    return tostring(container)
end

-- List Logo
local function getTourneyLogo(imageFile, darkImageFile)
    local container = html.create('div'):addClass('tr-event-logo')
    if imageFile and imageFile ~= "" then
        local darkFile = darkImageFile
        if not darkFile or darkFile == "" then
            local ext = imageFile:match("^.+(%..+)$") or ".png"
            local name = imageFile:gsub("%..+$", "")
            darkFile = name .. "_dark" .. ext
        end
        local hasDark = mw.title.new('File:' .. darkFile).exists
        container:tag('span'):addClass('logo-lightmode'):wikitext('[[File:' .. imageFile .. '|40px|link=]]')
        local dSpan = container:tag('span'):addClass('logo-darkmode')
        if hasDark then dSpan:wikitext('[[File:' .. darkFile .. '|40px|link=]]') else dSpan:wikitext('[[File:' .. imageFile .. '|40px|link=]]') end
    else
        container:wikitext('<i class="fa-solid fa-trophy" style="color:var(--text-muted); opacity:0.3;"></i>')
    end
    return tostring(container)
end

-- Helper: Get Tier Class (NEW)
local function getTierClass(tier)
    if not tier then return "tier-misc" end
    local t = tier:lower()
    if string.find(t, "s") and string.find(t, "tier") then return "tier-s" end
    if string.find(t, "a") and string.find(t, "tier") then return "tier-a" end
    if string.find(t, "b") and string.find(t, "tier") then return "tier-b" end
    if string.find(t, "c") and string.find(t, "tier") then return "tier-c" end
    return "tier-misc"
end

-- ============================================================
-- MAIN 1: TOURNAMENT INFOBOX
-- ============================================================
function p.infobox(frame)
    local args = frame:getParent().args
    local page = args.name or mw.title.getCurrentTitle().text
    local cleanName = mw.title.getCurrentTitle().subpageText
    local prizeMoney = args.prize_pool or args.prizepool
    local dbPageName = mw.title.getCurrentTitle().prefixedText
    
    local finalWinner = args.winner
    if not finalWinner or finalWinner == "" then
        finalWinner = getTeamFromPrizeMoney(dbPageName, cleanName, "1")
    end
    
    local finalRunnerUp = args.runner_up
    if not finalRunnerUp or finalRunnerUp == "" then
        finalRunnerUp = getTeamFromPrizeMoney(dbPageName, cleanName, "2")
    end
    
    if mw.ext.cargo and mw.ext.cargo.store then
        mw.ext.cargo.store('Tournaments', {
            _pageName = dbPageName,
            name = args.name or cleanName,
            series = args.series,
            series_season = args.series_season,
            series_value = args.series_value,
            organizer = args.organizer,
            sponsor = args.sponsor,
            game = args.game or "BGMI",
            mode = args.mode,
            type = args.type,
            tier = args.tier,
            device = args.device,
            location = args.location,
            venue = args.venue,
            prize_pool = prizeMoney,
            start_date = args.start_date,
            end_date = args.end_date,
            winner = finalWinner,      
            runner_up = finalRunnerUp, 
            image = args.image,
            image_dark = args.image_dark,
            instagram = args.instagram,
            twitter = args.twitter,
            youtube = args.youtube,
            discord = args.discord,
            facebook = args.facebook,
            website = args.website
        })
    end

    local root = html.create('div'):addClass('flat-infobox')
    local header = root:tag('div'):addClass('fib-header')
    header:tag('div'):addClass('fib-title'):wikitext(args.name or cleanName)
    root:wikitext(getInfoboxLogo(page, args.image, args.image_dark))
    
    local grid1 = root:tag('div'):addClass('fib-grid')
    grid1:tag('div'):addClass('fib-cell'):tag('div'):addClass('fib-label-sm'):wikitext('Event Tier'):done():tag('div'):addClass('fib-value-sm'):wikitext(args.tier or 'Unranked'):done()
    grid1:tag('div'):addClass('fib-cell'):tag('div'):addClass('fib-label-sm'):wikitext('Type'):done():tag('div'):addClass('fib-value-sm'):wikitext(args.type or 'Online'):done()
         
    local grid2 = root:tag('div'):addClass('fib-grid')
    grid2:tag('div'):addClass('fib-cell'):tag('div'):addClass('fib-label-sm'):wikitext('Mode'):done():tag('div'):addClass('fib-value-sm'):wikitext(args.mode or 'TBD'):done()
    grid2:tag('div'):addClass('fib-cell'):tag('div'):addClass('fib-label-sm'):wikitext('Location'):done():tag('div'):addClass('fib-value-sm'):wikitext(args.location or 'India'):done()
    
    if prizeMoney then
        root:tag('div'):addClass('fib-prize')
            :tag('div'):addClass('fib-label-sm'):wikitext('Total Prize Pool'):done()
            :tag('div'):addClass('fib-prize-val'):wikitext(formatCurrency(prizeMoney)):done()
    end
    
    local list = root:tag('div'):addClass('fib-list')
    local function addRow(label, value)
        if value and value ~= "" then
            list:tag('div'):addClass('fib-row'):tag('div'):addClass('fib-label'):wikitext(label):done():tag('div'):addClass('fib-data'):wikitext(value):done()
        end
    end
    
    addRow('Series', args.series and '[[' .. args.series .. ']]')
    if args.series_season then addRow('Season', args.series_season) end
    addRow('Organizer', args.organizer)
    addRow('Sponsors', args.sponsor)
    addRow('Venue', args.venue)
    addRow('Dates', formatDateRange(args.start_date, args.end_date))
    addRow('Device', args.device)
    if finalWinner then addRow('Winner', "'''[[" .. finalWinner .. "]]'''") end
    if finalRunnerUp then addRow('Runner Up', "'''[[" .. finalRunnerUp .. "]]'''") end
    
    root:wikitext(getSocials(args))
    if args.series and args.series_value then root:wikitext(getSeriesNav(args.series, args.series_value)) end
    return tostring(root)
end

-- ============================================================
-- MAIN 2: TABLE ROW (UPDATED FOR HYBRID DESIGN)
-- ============================================================
function p.tableRow(frame)
    local args = frame.args
    local page = args.Page or ""
    local name = getCleanTitle(page)
    local startDate = args.start_date or ""
    local endDate = args.end_date or ""
    local prize = args.prize_pool or "0"
    local tier = args.tier or "-"
    
    -- Auto-Fetch Winners
    local winner = args.winner
    if not winner or winner == "" then winner = getTeamFromPrizeMoney(page, name, "1") end
    
    local runnerUp = args.runner_up
    if not runnerUp or runnerUp == "" then runnerUp = getTeamFromPrizeMoney(page, name, "2") end
    
    local row = html.create('tr')
    
    -- 1. Date (Desktop: Box, Mobile: Label)
    row:tag('td')
       :attr('data-label', 'Date')
       :attr('data-sort-value', startDate)
       :tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
       
    -- 2. Tier (Color Coded Pill)
    row:tag('td')
       :attr('data-label', 'Tier')
       :css('text-align', 'center')
       :tag('span'):addClass('tier-badge'):addClass(getTierClass(tier)):wikitext(tier)
       
    -- 3. Tournament Name
    row:tag('td')
       :attr('data-label', 'Tournament')
       :css('font-weight', 'bold'):wikitext('[[' .. page .. '|' .. name .. ']]')
       
    -- 4. Location
    row:tag('td')
       :attr('data-label', 'Location')
       :wikitext(args.location or '-')
    
    -- 5. Prize
    row:tag('td')
       :attr('data-label', 'Prize')
       :attr('data-sort-value', prize)
       :addClass('tr-prize')
       :css('text-align', 'right')
       :wikitext(formatCurrency(prize))
       
    -- 6. Winner (Gold Background on Desktop)
    local winCell = row:tag('td')
        :attr('data-label', 'Winner')
        :addClass('cell-winner')
    
    if winner and winner ~= "" then
        winCell:wikitext(getSmallTeamLogo(winner) .. '[[' .. winner .. ']]')
    else
        winCell:css('text-align', 'center'):wikitext('-')
    end
    
    -- 7. Runner Up
    local runCell = row:tag('td'):attr('data-label', 'Runner Up')
    if runnerUp and runnerUp ~= "" then
        runCell:wikitext(getSmallTeamLogo(runnerUp) .. '[[' .. runnerUp .. ']]')
    else
        runCell:css('text-align', 'center'):wikitext('-')
    end
    
    return tostring(row)
end

-- ============================================================
-- MAIN 3: LIST ROW (With Left Strip Logic)
-- ============================================================
function p.listRow(frame)
    local args = frame.args
    local page = args.Page or ""
    local name = getCleanTitle(page)
    
    local startDate = args.start_date or args.date or args.startdate or ""
    local endDate = args.end_date or args.enddate or ""
    local prize = args.prize_pool or args.prizepool or args.prize or "0"
    local tier = args.tier or ""
    local image = args.image or ""
    local imageDark = args.image_dark or ""
    
    local winner = args.winner
    if not winner or winner == "" then
        winner = getTeamFromPrizeMoney(page, name, "1")
    end
    
    -- CREATE ROW: Removed inline background styles to allow CSS variables to work
    local row = html.create('div'):addClass('tourney-row')
    
    if tier ~= "" then
        row:addClass('row-' .. getTierClass(tier))
    end
    
    -- Date Box
    row:tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
    
    -- Logo
    row:tag('div'):addClass('tr-event-logo-col'):wikitext(getTourneyLogo(image, imageDark))
    
    -- Info
    local info = row:tag('div'):addClass('tr-info')
    local nameRow = info:tag('div'):addClass('tr-name-row')
    nameRow:tag('span'):addClass('tr-name'):wikitext('[[' .. page .. '|' .. name .. ']]')
    
    if tier ~= "" then
        local tierClass = getTierClass(tier) 
        nameRow:tag('span'):addClass('tier-badge'):addClass(tierClass):css('margin-left','8px'):wikitext(tier)
    end
    
    if args.organizer then info:tag('div'):addClass('tr-org'):wikitext(args.organizer) end
    
    -- Winner
    local winDiv = row:tag('div'):addClass('tr-winner mobile-hide')
    if winner and winner ~= "" then 
        winDiv:wikitext("🏆 " .. winner) 
    else 
        winDiv:tag('span'):css('opacity', '0.3'):wikitext('-') 
    end
    
    -- Prize
    local prizeDiv = row:tag('div'):addClass('tr-prize')
    prizeDiv:wikitext(formatCurrency(prize))
    
    return tostring(row)
end

-- ============================================================
-- MAIN 4: LIST ROW MAIN
-- ============================================================
function p.listRowMain(frame)
    local args = frame.args
    local page = args.Page or ""
    local name = getCleanTitle(page)
    local startDate = args.start_date or ""
    local endDate = args.end_date or ""
    local image = args.image or ""
    local imageDark = args.image_dark or ""
    local row = html.create('div'):addClass('tourney-row tr-compact')
    row:tag('div'):addClass('tr-date'):wikitext(formatDateRange(startDate, endDate))
    row:tag('div'):addClass('tr-event-logo-col'):wikitext(getTourneyLogo(image, imageDark))
    local info = row:tag('div'):addClass('tr-info')
    info:tag('div'):addClass('tr-name'):wikitext('[[' .. page .. '|' .. name .. ']]')
    if args.organizer then info:tag('div'):addClass('tr-org'):wikitext(args.organizer) end
    local prizeDiv = row:tag('div'):addClass('tr-prize')
    prizeDiv:wikitext(formatCurrency(args.prize_pool))
    return tostring(row)
end

-- ============================================================
-- MAIN 5: PAGINATED QUERY (Connects to listRow)
-- ============================================================
function p.mainQuery(frame)
    local args = frame.args
    
    -- 1. PAGINATION SETUP
    -- Get offset from URL (defaults to 0 if missing)
    local offset = tonumber(args.offset) or 0
    local limit = 50 -- Show 50 tournaments per page
    
    -- 2. BUILD QUERY
    local whereClause = "start_date IS NOT NULL"
    
    -- 3. RUN CARGO QUERY
    -- We select the exact fields required by your p.listRow function
    local results = cargo.query(
        'Tournaments', 
        '_pageName, start_date, end_date, tier, prize_pool, winner, organizer, image, image_dark', 
        {
            where = whereClause,
            orderBy = 'start_date DESC',
            limit = limit,
            offset = offset 
        }
    )

    -- 4. CONTAINER & EMPTY STATE
    local container = html.create('div'):addClass('tournament-list-wrapper')
    
    if not results or #results == 0 then
        if offset > 0 then
             -- User went too far, give a link back to start
            return '<div style="padding:40px; text-align:center; color:#64748b;">No more tournaments found.<br><br><b>[[' .. tostring(mw.uri.fullUrl(mw.title.getCurrentTitle().text)) .. '|Back to Start]]</b></div>'
        else
            return '<div style="padding:40px; text-align:center; color:#64748b;">No tournaments found in database.</div>'
        end
    end
    
    -- 5. RENDER ROWS (Using your Main 3 logic)
    for _, row in ipairs(results) do
        -- We structure the data exactly how p.listRow expects it
        local rowArgs = {
            Page = row._pageName,
            start_date = row.start_date,
            end_date = row.end_date,
            tier = row.tier,
            prize_pool = row.prize_pool,
            winner = row.winner,
            organizer = row.organizer,
            image = row.image,
            image_dark = row.image_dark
        }
        -- Call your existing function
        container:wikitext(p.listRow({ args = rowArgs }))
    end

    -- 6. ADD PAGINATION BUTTONS
    local navDiv = container:tag('div'):addClass('pagination-controls')
    
    -- "Previous" Button (Hide if on first page)
    if offset > 0 then
        local prevOffset = math.max(0, offset - limit)
        local prevUrl = mw.uri.fullUrl(mw.title.getCurrentTitle().text, {offset=prevOffset})
        navDiv:tag('span'):addClass('page-btn')
              :wikitext('[' .. tostring(prevUrl) .. ' « Newer Events]')
    end
    
    -- "Next" Button (Hide if we have fewer results than the limit)
    if #results == limit then
        local nextOffset = offset + limit
        local nextUrl = mw.uri.fullUrl(mw.title.getCurrentTitle().text, {offset=nextOffset})
        navDiv:tag('span'):addClass('page-btn')
              :wikitext('[' .. tostring(nextUrl) .. ' Older Events »]')
    end

    return tostring(container)
end

return p