Jump to content

Module:Television Series Rating

From Wikipedia, the free encyclopedia
local p = {}

--------------------------------------------------
-- Rating → background + text color (EPISODE CELLS ONLY)
--------------------------------------------------
local function ratingStyle(r)
    if not r then
        return nil, nil
    end

    if r >= 9.6 then
        return "#1C89F4", "#ffffff"
    elseif r >= 9.0 then
        return "#037732", "#ffffff"
    elseif r >= 7.5 then
        return "#00C853", "#000000"
    elseif r >= 6.0 then
        return "#FFD600", "#000000"
    elseif r >= 4.1 then
        return "#E03733", "#ffffff"
    else
        return "#8B00FF", "#ffffff"
    end
end

--------------------------------------------------
-- Collect seasons dynamically
--------------------------------------------------
local function getSeasons(args)
    local seasons = {}
    local i = 1
    while args["season" .. i] do
        seasons[i] = mw.text.split(args["season" .. i], ",")
        i = i + 1
    end
    return seasons
end

--------------------------------------------------
-- Main render
--------------------------------------------------
function p.render(frame)
    local args = frame:getParent() and frame:getParent().args or frame.args
    local seasons = getSeasons(args)

    if #seasons == 0 then
        return "Error: No seasons provided."
    end

    --------------------------------------------------
    -- OUTER WRAPPER (skin-owned)
    --------------------------------------------------
    local wrapper = mw.html.create("div")
        :css({
            ["max-width"] = "100%",
            ["overflow-x"] = "auto",
            ["margin"] = "1em auto",
            ["text-align"] = "center",
            ["font-family"] =
                'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
        })

    --------------------------------------------------
    -- TITLE + OVERALL RATING (skin colors)
    --------------------------------------------------
    if args.title or args.overall_rating then
        local header = wrapper:tag("div")
            :css({ ["margin-bottom"] = "12px" })

        if args.title then
            header:tag("span")
                :css({
                    ["font-size"] = "clamp(1.6em, 3vw, 2.4em)",
                    ["font-weight"] = "800",
                    ["margin-right"] = "12px"
                })
                :wikitext(args.title)
        end

        if args.overall_rating then
            local t = "★ " .. args.overall_rating
            if args.overall_votes then
                t = t .. " (" .. args.overall_votes .. ")"
            end

            header:tag("span")
                :css({
                    ["font-size"] = "clamp(1.1em, 2vw, 1.5em)",
                    ["font-weight"] = "600",
                    ["color"] = "#FFD600"
                })
                :wikitext(t)
        end
    end

    --------------------------------------------------
    -- TABLE (NO BACKGROUND)
    --------------------------------------------------
    local tableRoot = mw.html.create("table")
        :css({
            ["border-collapse"] = "separate",
            ["border-spacing"] = "6px",
            ["margin"] = "0 auto",
            ["text-align"] = "center"
        })

    --------------------------------------------------
    -- HEADER ROW
    --------------------------------------------------
    local headerRow = tableRoot:tag("tr")
    headerRow:tag("th")

    for i = 1, #seasons do
        headerRow:tag("th")
            :css({
                ["padding"] = "10px 16px",
                ["border-radius"] = "6px",
                ["font-weight"] = "700",
                ["font-size"] = "clamp(1.2em, 2vw, 1.9em)"
            })
            :wikitext("S" .. i)
    end

    --------------------------------------------------
    -- ACCUMULATORS
    --------------------------------------------------
    local sum, count = {}, {}
    for i = 1, #seasons do
        sum[i], count[i] = 0, 0
    end

    local maxEpisodes = 0
    for _, eps in ipairs(seasons) do
        if #eps > maxEpisodes then
            maxEpisodes = #eps
        end
    end

    --------------------------------------------------
    -- EPISODE ROWS
    --------------------------------------------------
    for e = 1, maxEpisodes do
        local row = tableRoot:tag("tr")

        row:tag("th")
            :css({
                ["padding"] = "10px 16px",
                ["font-weight"] = "700",
                ["font-size"] = "clamp(1.2em, 2vw, 1.9em)"
            })
            :wikitext("E" .. e)

        for s, eps in ipairs(seasons) do
            local rating = tonumber(eps[e])
            local cell = row:tag("td")

            if rating then
                local bg, fg = ratingStyle(rating)

                cell
                    :css({
                        ["background"] = bg,
                        ["color"] = fg,
                        ["padding"] = "clamp(10px, 1.5vw, 18px)",
                        ["border-radius"] = "8px",
                        ["font-weight"] = "800",
                        ["font-size"] = "clamp(1.6em, 3vw, 2.5em)",
                        ["line-height"] = "1",
                        ["min-width"] = "clamp(42px, 6vw, 60px)"
                    })
                    :wikitext(string.format("%.1f", rating))

                sum[s] = sum[s] + rating
                count[s] = count[s] + 1
            else
                cell:css({ ["opacity"] = "0" })
            end
        end
    end

    --------------------------------------------------
    -- SEASON AVERAGES (PLAIN TEXT, SKIN-CONTROLLED)
    --------------------------------------------------
    local avgRow = tableRoot:tag("tr")
    avgRow:tag("th")

    for s = 1, #seasons do
        local cell = avgRow:tag("td")

        if count[s] > 0 then
            local avg = sum[s] / count[s]

            cell
                :css({
                    ["padding"] = "clamp(8px, 1.5vw, 14px)",
                    ["border-radius"] = "6px",
                    ["font-weight"] = "700",
                    ["font-size"] = "clamp(1.3em, 2.5vw, 2.1em)"
                })
                :wikitext(string.format("%.1f", avg))
        else
            cell:css({ ["opacity"] = "0" })
        end
    end

    wrapper:node(tableRoot)

    --------------------------------------------------
    -- LEGEND (SKIN-CONTROLLED)
    --------------------------------------------------
    local legend = wrapper:tag("div")
        :css({
            ["margin-top"] = "14px",
            ["font-size"] = "clamp(0.9em, 1.5vw, 1.2em)",
            ["font-weight"] = "600",
            ["text-align"] = "center"
        })

    local function legendItem(color, label)
        local item = legend:tag("span")
            :css({ ["margin"] = "0 10px", ["white-space"] = "nowrap" })

        item:tag("span")
            :css({
                ["display"] = "inline-block",
                ["width"] = "16px",
                ["height"] = "16px",
                ["background"] = color,
                ["border-radius"] = "4px",
                ["margin-right"] = "6px",
                ["vertical-align"] = "middle"
            })

        item:tag("span")
            :css({ ["vertical-align"] = "middle" })
            :wikitext(label)
    end

    legendItem("#1C89F4", "PERFECTION")
    legendItem("#037732", "EXCELLENT")
    legendItem("#00C853", "GREAT")
    legendItem("#FFD600", "DECENT")
    legendItem("#E03733", "BAD")
    legendItem("#8B00FF", "GARBAGE")

    return tostring(wrapper)
end

return p