Jump to content

Module:Card: Difference between revisions

From The Petit Planet Wiki
No edit summary
No edit summary
 
(29 intermediate revisions by 4 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local lib = require('Module:Feature')
local lib = require('Module:Feature')
local ne = lib.isNotEmpty
local ne = lib.isNotEmpty
local namespace = require('Module:Namespace_detect')._main
local TemplateData = require('Module:TemplateData')
local getTemplateArgs = require('Module:Parser').getTemplateArgs
local array = require('Module:Array')
local array = require('Module:Array')
local CentralData = require('Module:CentralData')


local allData = {
local allData = CentralData.dataSheets
{'Agent', mw.loadData('Module:CentralData/Agents')},
 
{'W-Engine', mw.loadData('Module:CentralData/W-Engines')},
local CARD_ARGS = {
{'Drive Disc', mw.loadData('Module:CentralData/Drive Discs')},
{
{'Bangboo', mw.loadData('Module:CentralData/Bangboo')},
name = 'name',
{'Enemy', mw.loadData('Module:CentralData/Enemies')},
alias = 1,
{'Resonium', mw.loadData('Module:CentralData/Resonia')},
default = 'Unknown',
{'Item', mw.loadData('Module:CentralData/Items')},
},
{'Companion', mw.loadData('Module:CentralData/Companions')},
{
{'Outfit', mw.loadData('Module:CentralData/Outfits')},
name = 'label',
alias = {2, 'text', 'count', 'amount', 'x'},
},
{
name = 'show_label',
alias = 'show_text',
type = '1',
},
{
name = 'quality',
alias = 'rarity',
default = 'Common',
},
{
name = 'type',
},
{
name = 'split',
},
{
name = 'suffix',
default = '',
},
{
name = 'image',
},
{
name = 'image_size',
alias = {'imageSize', 'size'},
},
{
name = 'image_ext',
alias = {'imageExt', 'ext', 'extension'},
default = 'png',
},
{
name = 'link',
},
{
name = 'link_suffix',
default = '',
},
{
name = 'no_link',
type = '1',
},
{
name = 'caption',
},
{
name = 'show_caption',
type = '1',
},
{
name = 'caption_width',
alias = 'captionWidth',
},
{
name = 'mini',
type = '1',
},
{
name = 'first',
type = '1',
},
{
name = 'up',
alias = 'UP',
type = '1',
},
{
name = 'notice',
},
}
}
local COLON = ':'


function p.main(frame)
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {
local args = require('Module:Arguments').getArgs(frame, {
wrapper = { 'Template:Card' },
wrapper = {'Template:Card'},
parentOnly = false
parentOnly = false
})
})
return p._main(args)
return p._main(args)
end
end


function p._main(args)
local function getDataByName(dataType, dataTable, name)
local item = args[1] or args.name or 'Unknown'
local data = dataTable[name]
local label = args[2] or args.label or args.text or args.count or args.amount or args.x or nil
 
if data then
-- Reset agent-specific data to prevent carry-over from previous cards
return data
local agRank, agAttribute, agSpecialty, bbRank, weRank, weSpecialty, outRank, notice = nil, nil, nil, nil, nil, nil, nil, nil
end
 
-- Caption Params:
local normalizedType = tostring(dataType or ''):lower()
local caption = args.caption or nil
if normalizedType == 'neighbor' or normalizedType == 'neighbors' then
local caption_width = args.caption_width or args.captionWidth or nil
return nil
end
    -- Image File Params:
 
local type, data = p.getTypeAndData(item, args.type)
for quality, names in pairs(dataTable) do
local suffix = args.suffix or ''
if type(names) == 'table' then
local imageSize = args.image_size or args.imageSize or lib.ternary(args.mini == '1', 48, 70)
for _, itemName in ipairs(names) do
local imageExt = args.image_ext or args.imageExt or 'png'
if itemName == name then
return {
-- Link Params:
quality = quality
local link = args.link or lib.ternary(item == 'Unknown', '', item)
}
local link_suffix = args.link_suffix or ''
end
end
-- Rank Param:
local rank = args.rank or args.quality or args.rarity or nil
-- Try to get Rank From Data
if lib.isEmpty(rank) and data then
if data.rank then
rank = data.rank
end
end
end
end
 
-- If rank is still empty, set it to "C"
return nil
if lib.isEmpty(rank) then
end
rank = 'C'
 
function p.getTypeAndData(name, requestedType)
local img_type = requestedType or 'Item'
local img_data = nil
 
for _, v in ipairs(allData) do
local dataType = v[1]
local dataTable = v[2]
 
if lib.isEmpty(requestedType) or dataType == requestedType then
local data = getDataByName(dataType, dataTable, name)
 
if data then
img_type = dataType
img_data = data
break
end
end
end
end
 
-- If rank contains a semicolon, then split and get first
return img_type, img_data
if string.find(rank, ";") then
end
rank = array._main({
 
rank,
local function normalizeBool(value)
return value == true or value == '1' or value == 1 or value == 'yes'
end
 
local function normalizeClassName(value)
return tostring(value or '')
:lower()
:gsub('%s+', '-')
end
 
local function normalizeQuality(quality)
quality = tostring(quality or '')
 
if quality:find(';', 1, true) then
quality = array._main({
quality,
';',
';',
firstItemOnly = 1
firstItemOnly = 1
})
})
end
end
 
-- Make sure rank is uppercase
if lib.isEmpty(quality) then
rank = rank:upper()
quality = 'Common'
end
-- Modifiers:
 
local isFirst = args.first or false
return normalizeClassName(quality)
local isUp = args.UP or args.up or false
end
 
-- Attribute/specialty
local function stripLinkPrefix(value)
local attribute = args.attr or args.attribute or nil
value = tostring(value or '')
local specialty = args.spec or args.fightSpec or args.fs or nil
 
-- Converts "Luca: Myriad Meadows" to "Myriad Meadows".
if lib.isEmpty(suffix) then
return mw.text.trim(value:gsub('^.-:%s*', '', 1))
local lcType = type:lower()
end
if (lcType == 'agent') then
 
suffix = ' Icon'
local function setCaptionWidth(node, width)
--Grabs Agent Rank, Attribute and Specialty from Data to display icons
if width == 'auto' or lib.isEmpty(width) then
agRank = data['rank']
node:addClass('auto-width')
agAttribute = data['attribute']
else
agSpecialty = data['specialty']
node:css('width', width)
end
--In Full-Size Card-Mode, if there is no specified text, Agents will default to using their full name displayed underneath.
end
if args.mini ~= "1" then
 
if (label == nil) then label = item end
-- Generates the second icon for a split card.
end
local function makeSplitIcon(name, mini)
-- In Mini-Size Card-Mode, if there is no specified text, Agents will use their "shortName" pulled from /data as the displayed text.
name = tostring(name or '')
-- In cases where Agents do not have a "shortName", their full name will be displayed instead.
 
if args.mini == "1" then
local size = mini and 74 or 56
if (label == nil) and data['shortName'] ~= nil then label = data['shortName']
 
elseif (label == nil) and data['shortName'] == nil then label = item
return '[[File:' ..
end
lib.sanitizeFileName(name) ..
end
' Icon.png|' ..
size .. 'x' .. size ..
-- hackish workaround to account for the slight transparent borders on hoyolab agent icons
'px|link=' ..
if (tonumber(imageSize) ~= nil) then
name ..
imageSize = tonumber(imageSize) + 2
']]'
end
end
 
elseif (lcType == 'bangboo') then
function p._processArgs(args)
suffix = ' Icon'
local out = TemplateData.processArgs(args, CARD_ARGS)
--Grabs Bangboo Rank from Data to display icons
 
bbRank = data['rank']
-- Basic identity
out.name = out.name or 'Unknown'
elseif (lcType == 'w-engine') then
out.split = out.split or ''
suffix = ' Icon'
 
--Grabs W-Engine Rank and Specialty from Data to display icons
-- Use only a manually supplied type for lookup.
weRank = data['rank']
local requestedType = out.nonDefaults
weSpecialty = data['specialty']
and out.nonDefaults.type
or nil
elseif (lcType == 'enemy') then
 
suffix = '' --icon
out.type, out.data = p.getTypeAndData(
enemyType = data and data['type'] or ''
out.name,
enemyTier = data and data['tier'] or ''
requestedType
enemyWeakness = data and data['weakness'] or ''
)
imageSize = lib.ternary(args.mini == '1', 48, 70)
 
-- Base name for assets.
elseif (lcType == 'drive disc') then
-- Removes a trailing disambiguation such as "(Currency)".
suffix = ' Icon'
out.image_name = out.name--:gsub('%s*%([^()]*%)$', '')
 
elseif (lcType == 'npc') then
-- Boolean parameters
suffix = ' Icon'
out.mini = normalizeBool(out.mini)
out.first = normalizeBool(out.first)
elseif (lcType == 'outfit') then
out.up = normalizeBool(out.up)
suffix = ' Icon'
out.no_link = normalizeBool(out.no_link)
--Grabs Outfit Rank from Data to display icons
out.show_label = normalizeBool(out.show_label)
outRank = data['rank']
out.show_caption = normalizeBool(out.show_caption)
end
 
-- Image defaults
out.suffix = out.suffix or ''
out.image_ext = out.image_ext or 'png'
 
if lib.isEmpty(out.image_size) then
out.image_size = out.mini and 48 or 70
end
end


local final_file = args.image or type .. ' ' .. lib.sanitizeFileName(item) .. suffix .. '.' .. imageExt
if lib.isEmpty(out.suffix) then
out.suffix = ' Icon'
local final_link = link .. link_suffix
local file = '[[File:' .. final_file .. '|' .. imageSize .. 'px|alt=' .. item .. '|link=' .. final_link .. ']]'
local result = mw.html.create()
local card_container = result:tag('div')
:addClass('card-container')
:addClass('card-rank-' .. rank)
if args.mini == "1" then
card_container:addClass('mini-card')
end
end
local card_wrapper = card_container:tag('div')
:addClass('card-wrapper')
local card_body = card_wrapper:tag('div')
:addClass('card-body')
local card_link = card_wrapper:tag('div')
:addClass('card-link')
:wikitext('[[' .. final_link .. ']]')
local card_image = card_body:tag('div')
:addClass('card-image-container')
:wikitext(file)


--[[if label and not (args.label == "0" or args.text == "0") then --]]
-- Data-driven defaults
    if (label or args.show_text) and (args.label ~= "0" or args.text ~= "0") then  
if out.data then
if args.show_label == "1" or args.show_text == "1" then
if out.data.name then
label = data and data.name or item
out.defaults.label = out.data.name
end
out.defaults.caption = out.data.name
end
local card_label = card_container:tag('div')
:addClass('card-label')
:wikitext(label)
end


if (caption or args.show_caption) and args.show_caption ~= "0" then
if out.data.title then
if args.show_caption == "1" then
out.defaults.caption = out.data.title
caption = (data and data.name) or item
end
end
local card_caption = card_container:tag('div')
 
:addClass('card-caption')
if out.data.quality then
:wikitext('[[', final_link, '|', caption, ']]')
out.defaults.quality = out.data.quality
 
if caption_width == 'auto' or lib.isEmpty(caption_width) then
if not (out.nonDefaults and out.nonDefaults.quality) then
card_caption:addClass('auto-width')
out.quality = out.data.quality
else
end
card_caption:css('width', caption_width)
end
end
end
end
 
if (isFirst) then
-- Quality / rarity
local card_mod_first = card_wrapper:tag('div')
out.quality = normalizeQuality(out.quality)
:addClass('card-modifier card-modifier-first')
 
:wikitext('[[File:Icon Item First.png|x25px]]')
-- Label handling
if out.show_label then
out.label = out.data
and (out.data.name or out.data.title)
or out.name
end
end
 
if (isUp) then
if out.label == '0' then
local card_mod_up = card_wrapper:tag('div')
out.label = nil
:addClass('card-modifier card-modifier-up')
:wikitext('[[File:Icon Item UP.png|x25px]]')
end
end
 
-- Attribute icon, applied automatically for agents or manually specified for anything else
-- Type-specific behavior
if args.mini ~= "1" then -- Only display the icons if mini is not set to "1"
local lcType = tostring(out.type or ''):lower()
if ne(agRank) then
 
card_body:addClass('card-rank-' .. string.lower(agRank))
if lcType == 'neighbor' then
card_body:tag('span')
if lib.isEmpty(out.label) then
:addClass('card-icon-rank')
if out.mini and out.data and out.data.shortName then
:wikitext('[[File:Icon AgentRank ', agRank, '.png|alt=', agRank, '|link=|20px]]')
out.label = out.data.shortName
elseif not out.mini then
out.label = out.name
end
end
end
if ne(bbRank) then
 
card_body:addClass('card-rank-' .. string.lower(bbRank))
if tonumber(out.image_size) then
card_body:tag('span')
out.image_size = tonumber(out.image_size) + 2
:addClass('card-icon-rank')
:wikitext('[[File:Icon Bangboo Rank ', bbRank, '.png|alt=', bbRank, '|link=|20px]]')
end
end
if ne(weRank) then
end
card_body:addClass('card-rank-' .. string.lower(weRank))
 
card_body:tag('span')
if lcType == 'redirect' then
:addClass('card-icon-rank')
out.defaults.link = stripLinkPrefix(out.name)
:wikitext('[[File:Icon AgentRank ', weRank, '.png|alt=', weRank, '|link=|20px]]')
 
if not (out.nonDefaults and out.nonDefaults.link) then
out.link = stripLinkPrefix(out.name)
end
end
if ne(agAttribute) then
end
card_body:addClass('card-attribute-' .. string.lower(agAttribute))
 
card_body:tag('span')
-- Caption defaults
:addClass('card-icon-attribute')
if out.show_caption and lib.isEmpty(out.caption) then
:wikitext('[[File:Icon ', agAttribute, '.png|alt=', agAttribute, '|link=', agAttribute, '|20px]]')
if out.data then
end
out.caption = out.data.title
if ne(agSpecialty) then
or out.data.name
card_body:addClass('card-specialty-' .. string.lower(agSpecialty))
or out.name
card_body:tag('span')
else
:addClass('card-icon-specialty')
out.caption = out.name
:wikitext('[[File:Icon ', agSpecialty, '.png|alt=', agSpecialty, '|link=', agSpecialty, '|20px]]')
end
if ne(weSpecialty) then
card_body:addClass('card-specialty-' .. string.lower(weSpecialty))
card_body:tag('span')
:addClass('card-icon-specialty')
:wikitext('[[File:Icon ', weSpecialty, '.png|alt=', weSpecialty, '|link=', weSpecialty, '|20px]]')
end
if ne(outRank) then
card_body:addClass('card-rank-' .. string.lower(outRank))
card_body:tag('span')
:addClass('card-icon-rank')
:wikitext('[[File:Icon Item Rank ', outRank, '.png|alt=', outRank, '|link=|20px]]')
end
local cinema = args.cinema or args.c or nil
if ne(cinema) then
card_body:tag('span')
:addClass('card-cinema')
:addClass('cinema-' .. cinema)
:wikitext(' C', cinema)
card_body:addClass('card-with-cinema')
end
local phase = args.phase or args.p or nil
if ne(phase) then
card_body:tag('span')
:addClass('card-phase')
:addClass('phase-' .. phase)
:wikitext(' P', phase)
card_body:addClass('card-with-phase')
end
    local notice = args.notice or nil
    if ne(notice) then
    card_body:tag('span')
:addClass('card-notice-label')
:wikitext(notice)
    end
if ne(enemyType) then
card_body:tag('span')
:addClass('card-icon-enemy-type')
:wikitext('[[File:', enemyType, ' Icon.png|alt=', enemyType, '|link=', enemyType, '|28px]]')
end
if ne(enemyWeakness) then
local weaknessContainer = card_body:tag('span')
:addClass('card-enemy-weaknesses')
for weakness in lib.gsplit(enemyWeakness, ';') do
weaknessContainer:wikitext('[[File:Icon ', weakness, '.png|16px|link=', weakness, '|Weak to ', weakness, ']]')
end
end
end
end
end
 
if ne(enemyTier) then
-- Link handling
card_body:addClass('card-enemy card-enemy-' .. enemyTier:gsub(' ', ''))
if out.link == nil then
out.link = lib.ternary(
out.name == 'Unknown',
'',
out.name
)
end
 
out.link_suffix = out.link_suffix or ''
out.final_link = out.link .. out.link_suffix
 
if out.no_link then
out.final_link = ''
end
 
-- Main image filename
if ne(out.image) then
out.final_file = out.image
else
out.final_file =
lib.sanitizeFileName(out.image_name) ..
out.suffix ..
'.' ..
out.image_ext
end
end
 
return tostring(result)
-- Main image wikitext
local linkPart = out.no_link
and '|link='
or ('|link=' .. out.final_link)
 
out.image_wikitext = table.concat({
'[[File:',
out.final_file,
'|',
out.image_size,
'px|alt=',
out.data and (out.data.name or out.data.title)
or out.name,
linkPart,
']]'
})
 
return out
end
end


function p.getTypeAndData(name, type)
function p._main(args)
local img_type = type or 'Item'
local a = p._processArgs(args)
local img_data = nil
local isSplit = ne(a.split)
 
for i, v in ipairs(allData) do
-- Outer container
-- Only if the type was either not provided in the card args or if the provided type from card args is equal
local node_container = mw.html.create('div')
if lib.isEmpty(type) or v[1] == type then
:addClass('card-container')
local data = v[2][name]
 
if data then
if isSplit then
img_type = v[1]
node_container:addClass('card-split-card')
img_data = data
end
break
 
end
if a.mini then
node_container:addClass('mini-card')
end
 
-- Wrapper and body
local node_card = node_container:tag('span')
:addClass('card-wrapper')
:tag('span')
:addClass('card-body')
 
-- Galaxy class on body
if not a.mini and ne(a.galaxy) then
node_card:addClass(
'card-galaxy-' .. normalizeClassName(a.galaxy)
)
end
 
-- Main image container
local imageContainer = node_card:tag('span')
:addClass('card-image-container')
:addClass('card-quality-' .. a.quality)
 
-- Split or regular image
if isSplit then
imageContainer:addClass('card-split-image')
 
-- First item: top-left half
imageContainer:tag('span')
:addClass('card-split-half')
:addClass('card-split-half-a')
:wikitext(a.image_wikitext)
 
-- Second item: bottom-right half
imageContainer:tag('span')
:addClass('card-split-half')
:addClass('card-split-half-b')
:wikitext(makeSplitIcon(a.split, a.mini))
else
imageContainer:wikitext(a.image_wikitext)
end
 
-- Notice label
if not a.mini and ne(a.notice) then
node_card:tag('span')
:addClass('card-notice-label')
:wikitext(a.notice)
end
 
-- First modifier
if a.first then
node_card:tag('span')
:addClass('card-modifier')
:addClass('card-modifier-first')
:wikitext(
'[[File:Icon Item First.png|x25px|link=|alt=First]]'
)
end
 
-- UP modifier
if a.up then
node_card:tag('span')
:addClass('card-modifier')
:addClass('card-modifier-up')
:wikitext(
'[[File:Icon Item UP.png|x25px|link=|alt=UP]]'
)
end
 
-- Label below the image
if ne(a.label) then
node_container:tag('span')
:addClass('card-label')
:wikitext(a.label)
end
 
-- Caption
-- Split captions are opt-in and use spans instead of list items.
if a.show_caption and isSplit then
local node_caption = node_container:tag('span')
:addClass('card-caption')
:addClass('card-split-caption')
 
local list = node_caption:tag('span')
:addClass('card-split-caption-list')
 
list:tag('span')
:addClass('card-split-caption-item')
:wikitext('[[' .. a.name .. ']]')
 
list:tag('span')
:addClass('card-split-caption-item')
:wikitext('[[' .. a.split .. ']]')
 
setCaptionWidth(node_caption, a.caption_width)
 
elseif a.show_caption and ne(a.caption) then
local node_caption = node_container:tag('span')
:addClass('card-caption')
 
if a.no_link or lib.isEmpty(a.final_link) then
node_caption:wikitext(a.caption)
else
node_caption:wikitext(
'[[' .. a.final_link .. '|' .. a.caption .. ']]'
)
end
end
setCaptionWidth(node_caption, a.caption_width)
end
end
 
return img_type, img_data
return tostring(node_container)
end
 
function p.templateData(frame)
return TemplateData.templateData(CARD_ARGS, {
description = 'Creates an item-style card with image, quality, label, split images, modifiers, and optional data-driven icons.',
format = 'inline'
}, frame)
end
 
function p.syntax(frame)
return TemplateData.syntax(CARD_ARGS, frame)
end
end


return p
return p

Latest revision as of 10:53, 22 October 2026

Module documentation
This Lua module is used primarily on pages in the Main namespace. It can be found on 2,428 pages, or on about 60.61% of those pages.
Edits to this Lua module will be widely noticed. To avoid large-scale disruption and unnecessary server load, any changes to this Lua module should be tested in its sandbox or testcases subpages. The tested changes can then be added to this page in one single edit.
Please consider discussing any changes in the wiki's Discord.

This module implements {{Card}}.

To update, see: Module:CentralData.

local p = {}

local lib = require('Module:Feature')
local ne = lib.isNotEmpty
local TemplateData = require('Module:TemplateData')
local array = require('Module:Array')
local CentralData = require('Module:CentralData')

local allData = CentralData.dataSheets

local CARD_ARGS = {
	{
		name = 'name',
		alias = 1,
		default = 'Unknown',
	},
	{
		name = 'label',
		alias = {2, 'text', 'count', 'amount', 'x'},
	},
	{
		name = 'show_label',
		alias = 'show_text',
		type = '1',
	},
	{
		name = 'quality',
		alias = 'rarity',
		default = 'Common',
	},
	{
		name = 'type',
	},
	{
		name = 'split',
	},
	{
		name = 'suffix',
		default = '',
	},
	{
		name = 'image',
	},
	{
		name = 'image_size',
		alias = {'imageSize', 'size'},
	},
	{
		name = 'image_ext',
		alias = {'imageExt', 'ext', 'extension'},
		default = 'png',
	},
	{
		name = 'link',
	},
	{
		name = 'link_suffix',
		default = '',
	},
	{
		name = 'no_link',
		type = '1',
	},
	{
		name = 'caption',
	},
	{
		name = 'show_caption',
		type = '1',
	},
	{
		name = 'caption_width',
		alias = 'captionWidth',
	},
	{
		name = 'mini',
		type = '1',
	},
	{
		name = 'first',
		type = '1',
	},
	{
		name = 'up',
		alias = 'UP',
		type = '1',
	},
	{
		name = 'notice',
	},
}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrapper = {'Template:Card'},
		parentOnly = false
	})

	return p._main(args)
end

local function getDataByName(dataType, dataTable, name)
	local data = dataTable[name]

	if data then
		return data
	end

	local normalizedType = tostring(dataType or ''):lower()
	if normalizedType == 'neighbor' or normalizedType == 'neighbors' then
		return nil
	end

	for quality, names in pairs(dataTable) do
		if type(names) == 'table' then
			for _, itemName in ipairs(names) do
				if itemName == name then
					return {
						quality = quality
					}
				end
			end
		end
	end

	return nil
end

function p.getTypeAndData(name, requestedType)
	local img_type = requestedType or 'Item'
	local img_data = nil

	for _, v in ipairs(allData) do
		local dataType = v[1]
		local dataTable = v[2]

		if lib.isEmpty(requestedType) or dataType == requestedType then
			local data = getDataByName(dataType, dataTable, name)

			if data then
				img_type = dataType
				img_data = data
				break
			end
		end
	end

	return img_type, img_data
end

local function normalizeBool(value)
	return value == true or value == '1' or value == 1 or value == 'yes'
end

local function normalizeClassName(value)
	return tostring(value or '')
		:lower()
		:gsub('%s+', '-')
end

local function normalizeQuality(quality)
	quality = tostring(quality or '')

	if quality:find(';', 1, true) then
		quality = array._main({
			quality,
			';',
			firstItemOnly = 1
		})
	end

	if lib.isEmpty(quality) then
		quality = 'Common'
	end

	return normalizeClassName(quality)
end

local function stripLinkPrefix(value)
	value = tostring(value or '')

	-- Converts "Luca: Myriad Meadows" to "Myriad Meadows".
	return mw.text.trim(value:gsub('^.-:%s*', '', 1))
end

local function setCaptionWidth(node, width)
	if width == 'auto' or lib.isEmpty(width) then
		node:addClass('auto-width')
	else
		node:css('width', width)
	end
end

-- Generates the second icon for a split card.
local function makeSplitIcon(name, mini)
	name = tostring(name or '')

	local size = mini and 74 or 56

	return '[[File:' ..
		lib.sanitizeFileName(name) ..
		' Icon.png|' ..
		size .. 'x' .. size ..
		'px|link=' ..
		name ..
		']]'
end

function p._processArgs(args)
	local out = TemplateData.processArgs(args, CARD_ARGS)

	-- Basic identity
	out.name = out.name or 'Unknown'
	out.split = out.split or ''

	-- Use only a manually supplied type for lookup.
	local requestedType = out.nonDefaults
		and out.nonDefaults.type
		or nil

	out.type, out.data = p.getTypeAndData(
		out.name,
		requestedType
	)

	-- Base name for assets.
	-- Removes a trailing disambiguation such as "(Currency)".
	out.image_name = out.name--:gsub('%s*%([^()]*%)$', '')

	-- Boolean parameters
	out.mini = normalizeBool(out.mini)
	out.first = normalizeBool(out.first)
	out.up = normalizeBool(out.up)
	out.no_link = normalizeBool(out.no_link)
	out.show_label = normalizeBool(out.show_label)
	out.show_caption = normalizeBool(out.show_caption)

	-- Image defaults
	out.suffix = out.suffix or ''
	out.image_ext = out.image_ext or 'png'

	if lib.isEmpty(out.image_size) then
		out.image_size = out.mini and 48 or 70
	end

	if lib.isEmpty(out.suffix) then
		out.suffix = ' Icon'
	end

	-- Data-driven defaults
	if out.data then
		if out.data.name then
			out.defaults.label = out.data.name
			out.defaults.caption = out.data.name
		end

		if out.data.title then
			out.defaults.caption = out.data.title
		end

		if out.data.quality then
			out.defaults.quality = out.data.quality

			if not (out.nonDefaults and out.nonDefaults.quality) then
				out.quality = out.data.quality
			end
		end
	end

	-- Quality / rarity
	out.quality = normalizeQuality(out.quality)

	-- Label handling
	if out.show_label then
		out.label = out.data
			and (out.data.name or out.data.title)
			or out.name
	end

	if out.label == '0' then
		out.label = nil
	end

	-- Type-specific behavior
	local lcType = tostring(out.type or ''):lower()

	if lcType == 'neighbor' then
		if lib.isEmpty(out.label) then
			if out.mini and out.data and out.data.shortName then
				out.label = out.data.shortName
			elseif not out.mini then
				out.label = out.name
			end
		end

		if tonumber(out.image_size) then
			out.image_size = tonumber(out.image_size) + 2
		end
	end

	if lcType == 'redirect' then
		out.defaults.link = stripLinkPrefix(out.name)

		if not (out.nonDefaults and out.nonDefaults.link) then
			out.link = stripLinkPrefix(out.name)
		end
	end

	-- Caption defaults
	if out.show_caption and lib.isEmpty(out.caption) then
		if out.data then
			out.caption = out.data.title
				or out.data.name
				or out.name
		else
			out.caption = out.name
		end
	end

	-- Link handling
	if out.link == nil then
		out.link = lib.ternary(
			out.name == 'Unknown',
			'',
			out.name
		)
	end

	out.link_suffix = out.link_suffix or ''
	out.final_link = out.link .. out.link_suffix

	if out.no_link then
		out.final_link = ''
	end

	-- Main image filename
	if ne(out.image) then
		out.final_file = out.image
	else
		out.final_file =
			lib.sanitizeFileName(out.image_name) ..
			out.suffix ..
			'.' ..
			out.image_ext
	end

	-- Main image wikitext
	local linkPart = out.no_link
		and '|link='
		or ('|link=' .. out.final_link)

	out.image_wikitext = table.concat({
		'[[File:',
		out.final_file,
		'|',
		out.image_size,
		'px|alt=',
		out.data and (out.data.name or out.data.title)
			or out.name,
		linkPart,
		']]'
	})

	return out
end

function p._main(args)
	local a = p._processArgs(args)
	local isSplit = ne(a.split)

	-- Outer container
	local node_container = mw.html.create('div')
		:addClass('card-container')

	if isSplit then
		node_container:addClass('card-split-card')
	end

	if a.mini then
		node_container:addClass('mini-card')
	end

	-- Wrapper and body
	local node_card = node_container:tag('span')
		:addClass('card-wrapper')
		:tag('span')
			:addClass('card-body')

	-- Galaxy class on body
	if not a.mini and ne(a.galaxy) then
		node_card:addClass(
			'card-galaxy-' .. normalizeClassName(a.galaxy)
		)
	end

	-- Main image container
	local imageContainer = node_card:tag('span')
		:addClass('card-image-container')
		:addClass('card-quality-' .. a.quality)

	-- Split or regular image
	if isSplit then
		imageContainer:addClass('card-split-image')

		-- First item: top-left half
		imageContainer:tag('span')
			:addClass('card-split-half')
			:addClass('card-split-half-a')
			:wikitext(a.image_wikitext)

		-- Second item: bottom-right half
		imageContainer:tag('span')
			:addClass('card-split-half')
			:addClass('card-split-half-b')
			:wikitext(makeSplitIcon(a.split, a.mini))
	else
		imageContainer:wikitext(a.image_wikitext)
	end

	-- Notice label
	if not a.mini and ne(a.notice) then
		node_card:tag('span')
			:addClass('card-notice-label')
			:wikitext(a.notice)
	end

	-- First modifier
	if a.first then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-first')
			:wikitext(
				'[[File:Icon Item First.png|x25px|link=|alt=First]]'
			)
	end

	-- UP modifier
	if a.up then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-up')
			:wikitext(
				'[[File:Icon Item UP.png|x25px|link=|alt=UP]]'
			)
	end

	-- Label below the image
	if ne(a.label) then
		node_container:tag('span')
			:addClass('card-label')
			:wikitext(a.label)
	end

	-- Caption
	-- Split captions are opt-in and use spans instead of list items.
	if a.show_caption and isSplit then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')
			:addClass('card-split-caption')

		local list = node_caption:tag('span')
			:addClass('card-split-caption-list')

		list:tag('span')
			:addClass('card-split-caption-item')
			:wikitext('[[' .. a.name .. ']]')

		list:tag('span')
			:addClass('card-split-caption-item')
			:wikitext('[[' .. a.split .. ']]')

		setCaptionWidth(node_caption, a.caption_width)

	elseif a.show_caption and ne(a.caption) then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')

		if a.no_link or lib.isEmpty(a.final_link) then
			node_caption:wikitext(a.caption)
		else
			node_caption:wikitext(
				'[[' .. a.final_link .. '|' .. a.caption .. ']]'
			)
		end

		setCaptionWidth(node_caption, a.caption_width)
	end

	return tostring(node_container)
end

function p.templateData(frame)
	return TemplateData.templateData(CARD_ARGS, {
		description = 'Creates an item-style card with image, quality, label, split images, modifiers, and optional data-driven icons.',
		format = 'inline'
	}, frame)
end

function p.syntax(frame)
	return TemplateData.syntax(CARD_ARGS, frame)
end

return p