Jump to content

Module:Recipe: Difference between revisions

From The Petit Planet Wiki
No edit summary
No edit summary
 
(13 intermediate revisions by 2 users not shown)
Line 43: Line 43:
end
end


local function addUseCategory(category, name, sort)
local function addUseCategory(category, name, quality, sort)
  if not name or tostring(name) == '' then
if not name or tostring(name) == '' then
    return
return
  end
end
 
if quality and tostring(quality) ~= '' then
if sort and tostring(sort) ~= '' then
category:wikitext('[[Category:Uses ', quality, '-Quality ', name, '|', sort, ']]')
else
category:wikitext('[[Category:Uses ', quality, '-Quality ', name, ']]')
end
 
return
end


  if sort then
if sort and tostring(sort) ~= '' then
    category:wikitext('[[Category:Uses ', name, '|', sort, ']]')
category:wikitext('[[Category:Uses ', name, '|', sort, ']]')
  else
else
    category:wikitext('[[Category:Uses ', name, ']]')
category:wikitext('[[Category:Uses ', name, ']]')
  end
end
end
end


local function getCurrentPageName(args)
local function getCurrentPageName(args)
   return args[param.sPage] or args.page or mw.title.getCurrentTitle().text
   return args[param.sPage] or args.page or mw.title.getCurrentTitle().text
end
local function getQualityClass(quality)
  if not quality or tostring(quality) == '' then
    return nil
  end
  return 'card-quality-' .. normalizeText(quality)
end
end


Line 67: Line 85:
     name = sName,
     name = sName,
     text = sAmt,
     text = sAmt,
     show_caption = '1',
     show_caption = '1'
    mobile = 'bulleted'
   }
   }


Line 80: Line 97:
     category,
     category,
     sName,
     sName,
    quality,
     padLeft(tostring(sAmt or ''):gsub('%D', ''), 10)
     padLeft(tostring(sAmt or ''):gsub('%D', ''), 10)
   )
   )
Line 91: Line 109:
     text = sText or '',
     text = sText or '',
     show_caption = '1',
     show_caption = '1',
    mobile = 'bulleted',
     mini = '1'
     mini = '1'
   }
   }
Line 119: Line 136:


local function renderHeader(args, output, category)
local function renderHeader(args, output, category)
   local header = output:tag('div'):addClass('recipe_header')
   local header = output:tag('div'):addClass('recipe-header')
   local sType = args[param.sTypeArg]
   local sType = args[param.sTypeArg]


   header:tag('span'):addClass('recipe_header_main')
   header:tag('span'):addClass('recipe-header-main')
    -- Preserved from original module
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sType .. ']]'):done()
    :tag('span'):addClass('mobileHide'):wikitext('<big>' .. sType .. '</big>'):done()
    :tag('span'):addClass('mobileHide'):wikitext('<big>' .. sType .. '</big>'):done()
     :tag('span'):addClass('recipe_header_text'):wikitext('[[' .. sType .. ']]'):done()


   category:wikitext('[[Category:' .. sType .. ']]')
   category:wikitext('[[Category:' .. sType .. ']]')
Line 210: Line 224:
   end
   end


   header:tag('span'):addClass('recipe_header_sub')
   header:tag('span'):addClass('recipe-header-sub')
     :tag('span'):addClass('mobileHide'):wikitext(' for '):done()
     :tag('span'):wikitext(' for '):done()
    :tag('span'):addClass('hidden'):wikitext(' for '):done()
     :tag('span'):addClass('recipe-header-text'):wikitext(sTime):done()
     :tag('span'):addClass('recipe_header_text'):wikitext(sTime):done()
end
end


Line 223: Line 236:
   local sCharName = args[param.sCharArg]
   local sCharName = args[param.sCharArg]


   header:tag('span'):addClass('recipe_header_sub')
   header:tag('span'):addClass('recipe-header-sub')
     :tag('span'):addClass('mobileHide'):wikitext(' with '):done()
    :tag('span'):wikitext('[[File:' .. sCharName .. ' Icon.png|24x24px|link=' .. sCharName .. ']]'):done()
     :tag('span'):addClass('hidden'):wikitext('[[File:' .. sCharName .. ' Icon.png|24x24px|link=' .. sCharName .. ']]'):done()
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sCharName .. ']]'):done()
     :tag('span'):addClass('recipe_header_text'):wikitext('[[' .. sCharName .. ']]'):done()
end
 
local function renderKitchenwareHeader(args, header)
  if type(args[param.sKitchArg]) ~= 'string' then
     return
  end
 
  local sKitchName = args[param.sKitchArg]
 
  header:tag('span'):addClass('recipe-header-sub')
    :tag('span'):wikitext('[[File:' .. sKitchName .. ' Icon.png|24x24px|link=' .. sKitchName .. ']]'):done()
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sKitchName .. ']]'):done()
end
end


Line 248: Line 272:
   end
   end


   local sYield = args[param.sYieldArg] or '1'
   local sYield = args[param.sYieldArg] or nil
   local pageTitle = getCurrentPageName(args)
   local pageTitle = getCurrentPageName(args)


Line 254: Line 278:


   body
   body
     :tag('div'):addClass('recipe_body_icon')
     :tag('div'):addClass('recipe-body-icon')
       :wikitext('➜'):done()
       :wikitext('➜'):done()
     :tag('div'):addClass('recipe_body_yield')
     :tag('div'):addClass('recipe-body-yield')
       :node(yieldCard)
       :node(yieldCard)
end
end
Line 268: Line 292:
   renderCharacter(args, header)
   renderCharacter(args, header)


   local body = output:tag('div'):addClass('recipe_body')
   local body = output:tag('div'):addClass('recipe-body')


   local sSortDelim = param.sSortDelim
   local sSortDelim = param.sSortDelim
Line 276: Line 300:
   end
   end


  -- Process sort parameter.
   for sKey in string.gmatch(args[param.sSortArg] or '', '([^' .. sSortDelim .. ']+)') do
   for sKey in string.gmatch(args[param.sSortArg] or '', '([^' .. sSortDelim .. ']+)') do
     if type(args[sKey]) ~= 'nil' then
     if type(args[sKey]) ~= 'nil' then
Line 284: Line 307:
   end
   end


  -- Process rest of unrestricted parameters.
   for sKey, sVal in pairs(args) do
   for sKey, sVal in pairs(args) do
     if tArgsProcessed[sKey] ~= true and not isRestrictedStandardParam(sKey) then
     if tArgsProcessed[sKey] ~= true and not isRestrictedStandardParam(sKey) then
Line 322: Line 344:
   local ingredient = {
   local ingredient = {
     index = index,
     index = index,
     amount = args[prefix .. '_amount'] or '1',
     amount = args[prefix .. '_amount'] or nil,
     quality = args[prefix .. '_quality'],
     quality = args[prefix .. '_quality'],
     item = args[prefix],
     item = args[prefix],
Line 329: Line 351:
   }
   }


  -- Specific type:
  -- |item_1_type = Fish
   if argExists(args, prefix .. '_type') then
   if argExists(args, prefix .. '_type') then
     table.insert(ingredient.types, args[prefix .. '_type'])
     table.insert(ingredient.types, args[prefix .. '_type'])
   end
   end


  -- Multiple accepted types:
  -- |item_1_type_1 = Freshwater Fish
  -- |item_1_type_2 = Saltwater Fish
   local typeIndex = 1
   local typeIndex = 1


Line 345: Line 362:
   end
   end


  -- Multiple accepted specific items:
  -- |item_1_option_1 = Rice
  -- |item_1_option_2 = Wheat
   local optionIndex = 1
   local optionIndex = 1


Line 367: Line 381:
end
end


local function getKitchenware(args)
--------------------------------------------------------------------------------
   local kitchenware = {}
-- Cooking type split-card helpers
--------------------------------------------------------------------------------
 
local function makeTypeIcon(typeName)
  return '[[File:' .. typeName .. ' Icon.png|74x74px|link=' .. typeName .. ']]'
end
 
local function joinTypeCaption(types)
   local out = {}


   if argExists(args, 'kitchenware') then
   for _, typeName in ipairs(types) do
     table.insert(kitchenware, args.kitchenware)
     table.insert(out, '[[' .. typeName .. ']]')
   end
   end


   local index = 1
   return table.concat(out, ' / ')
end
 
local function renderCookingSplitTypeCard(category, body, ingredient)
  local qualityClass = getQualityClass(ingredient.quality or 'Gray')
 
  local container = body:tag('span')
    :addClass('card-container')
    :addClass('recipe-type-split-card')
 
  local wrapper = container:tag('span')
    :addClass('card-wrapper')
 
  local cardBody = wrapper:tag('span')
    :addClass('card-body')
 
  local imageContainer = cardBody:tag('span')
    :addClass('card-image-container')
    :addClass('recipe-type-split-image')
 
  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-a')
    :wikitext(makeTypeIcon(ingredient.types[1]))
    :done()
 
  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-b')
    :wikitext(makeTypeIcon(ingredient.types[2]))
    :done()
   
if qualityClass then
    imageContainer:addClass(qualityClass)
end


   while argExists(args, 'kitchenware_' .. index) do
   if tostring(ingredient.amount or '') ~= '' then
     table.insert(kitchenware, args['kitchenware_' .. index])
     container:tag('span')
    index = index + 1
      :addClass('card-label')
      :wikitext(tostring(ingredient.amount or '1'))
      :done()
   end
   end


   return kitchenware
   container:tag('span')
    :addClass('card-caption')
    :wikitext(joinTypeCaption(ingredient.types))
    :done()
 
  for _, typeName in ipairs(ingredient.types) do
    addUseCategory(category, typeName)
  end
end
end


Line 397: Line 462:
end
end


local function renderCookingTypeCards(category, body, ingredient)
local function renderCookingTypeFallback(category, body, ingredient)
   local wrapper = body:tag('div')
   local wrapper = body:tag('div')
     :addClass('recipe_body_cooking_ingredient')
     :addClass('recipe-body-cooking-ingredient')
     :addClass('recipe_body_cooking_type_cards')
     :addClass('recipe-body-cooking-type-cards')


   for _, typeName in ipairs(ingredient.types) do
   for _, typeName in ipairs(ingredient.types) do
Line 409: Line 474:
       ingredient.quality
       ingredient.quality
     ))
     ))
  end
end
local function renderCookingTypeCards(category, body, ingredient)
  if #ingredient.types == 2 then
    renderCookingSplitTypeCard(category, body, ingredient)
  else
    renderCookingTypeFallback(category, body, ingredient)
   end
   end
end
end
Line 414: Line 487:
local function renderCookingOptionCards(category, body, ingredient)
local function renderCookingOptionCards(category, body, ingredient)
   local wrapper = body:tag('div')
   local wrapper = body:tag('div')
     :addClass('recipe_body_cooking_ingredient')
     :addClass('recipe-body-cooking-ingredient')
     :addClass('recipe_body_cooking_option_cards')
     :addClass('recipe-body-cooking-option-cards')


   for _, itemName in ipairs(ingredient.options) do
   for _, itemName in ipairs(ingredient.options) do
Line 428: Line 501:


local function renderCookingIngredient(category, body, ingredient)
local function renderCookingIngredient(category, body, ingredient)
  -- Specific item:
  -- |item_1 = Rice
   if ingredient.item then
   if ingredient.item then
     renderCookingSpecificItem(category, body, ingredient)
     renderCookingSpecificItem(category, body, ingredient)
Line 435: Line 506:
   end
   end


  -- Multiple accepted specific items:
  -- |item_1_option_1 = Rice
  -- |item_1_option_2 = Wheat
   if #ingredient.options > 0 then
   if #ingredient.options > 0 then
     renderCookingOptionCards(category, body, ingredient)
     renderCookingOptionCards(category, body, ingredient)
Line 443: Line 511:
   end
   end


  -- Type-based ingredient:
  -- |item_1_type = Fish
  -- |item_1_type_1 = Freshwater Fish
  -- |item_1_type_2 = Saltwater Fish
   if #ingredient.types > 0 then
   if #ingredient.types > 0 then
     renderCookingTypeCards(category, body, ingredient)
     renderCookingTypeCards(category, body, ingredient)
     return
     return
   end
   end
end
--[[
local function renderKitchenwareSlot(category, body, args)
  local kitchenware = getKitchenware(args)
  if #kitchenware == 0 then
    body:tag('div')
      :addClass('recipe_body_icon')
      :wikitext('➜')
    return
  end
  local wrapper = body:tag('div')
    :addClass('recipe_body_icon')
    :addClass('recipe_body_kitchenware_cards')
  for _, kitchenwareName in ipairs(kitchenware) do
    wrapper:node(addMiniCard(
      category,
      kitchenwareName,
      '',
      nil
    ))
  end
end
--]]
local function renderKitchenWareSlot(args, header)
  if type(args[param.sKitchArg]) ~= 'string' then
    return
  end
  local sKitchName = args[param.sKitchArg]
  header:tag('span'):addClass('recipe_header_sub')
    :tag('span'):addClass('mobileHide'):wikitext(' with '):done()
    :tag('span'):addClass('hidden'):wikitext('[[File:' .. sKitchName .. ' Icon.png|24x24px|link=' .. sKitchName .. ']]'):done()
    :tag('span'):addClass('recipe_header_text'):wikitext('[[' .. sKitchName .. ']]'):done()
end
end


Line 496: Line 522:
   end
   end


   local sYield = args[param.sYieldArg] or '1'
   local sYield = args[param.sYieldArg] or nil
   local pageTitle = getCurrentPageName(args)
   local pageTitle = getCurrentPageName(args)


   local yieldCard = addYieldCard(pageTitle, sYield)
   local yieldCard = addYieldCard(pageTitle, sYield)


   --renderKitchenwareSlot(category, body, args)
   body
  renderKitchenwareSlot(body, args)
    :tag('div'):addClass('recipe-body-icon')
 
      :wikitext('➜'):done()
  body:tag('div')
    :tag('div'):addClass('recipe-body-yield')
    :addClass('recipe_body_yield')
      :node(yieldCard)
    :node(yieldCard)
end
end


Line 514: Line 539:
   renderTime(args, header)
   renderTime(args, header)
   renderCharacter(args, header)
   renderCharacter(args, header)
  renderKitchenwareHeader(args, header)


   local body = output:tag('div')
   local body = output:tag('div')
     :addClass('recipe_body')
     :addClass('recipe-body')
     :addClass('recipe_body_cooking')
     :addClass('recipe-body-cooking')


   local foundIngredient = false
   local foundIngredient = false
Line 557: Line 583:
function p._main(args, frame)
function p._main(args, frame)
   local output = mw.html.create('div')
   local output = mw.html.create('div')
     :addClass('recipe_container')
     :addClass('recipe-container')


  -- Must be local per render.
  -- Keeping this global can cause category leakage between recipe calls.
   local category = mw.html.create()
   local category = mw.html.create()



Latest revision as of 20:10, 13 May 2026

Module documentation

Lua module for {{Recipe}}.

local p = {}

local Card = require('Module:Card')

-- Arguments for template
local param = {
  sCharArg = 'character',
  sKitchArg = 'kitchenware',
  sTypeArg = 'type',
  sSortArg = 'sort',
  sSortDelim = ';',
  sSortDelim2 = '¤',
  sTimeArg = 'time',
  sYieldArg = 'yield',
  sPage = 'page'
}

p.param = param

--------------------------------------------------------------------------------
-- Utility
--------------------------------------------------------------------------------

local function padLeft(str, amt)
  str = tostring(str or '')
  return string.rep('0', math.max(amt - str:len(), 0)) .. str
end

local function pluralize(sOut, plural)
  if plural then
    sOut = sOut .. 's'
  end

  return sOut
end

local function normalizeText(s)
  return mw.ustring.lower(mw.text.trim(tostring(s or '')))
end

local function argExists(args, key)
  return args[key] ~= nil and tostring(args[key]) ~= ''
end

local function addUseCategory(category, name, quality, sort)
	if not name or tostring(name) == '' then
		return
	end

	if quality and tostring(quality) ~= '' then
		if sort and tostring(sort) ~= '' then
			category:wikitext('[[Category:Uses ', quality, '-Quality ', name, '|', sort, ']]')
		else
			category:wikitext('[[Category:Uses ', quality, '-Quality ', name, ']]')
		end

		return
	end

	if sort and tostring(sort) ~= '' then
		category:wikitext('[[Category:Uses ', name, '|', sort, ']]')
	else
		category:wikitext('[[Category:Uses ', name, ']]')
	end
end

local function getCurrentPageName(args)
  return args[param.sPage] or args.page or mw.title.getCurrentTitle().text
end

local function getQualityClass(quality)
  if not quality or tostring(quality) == '' then
    return nil
  end

  return 'card-quality-' .. normalizeText(quality)
end

--------------------------------------------------------------------------------
-- Card helpers
--------------------------------------------------------------------------------

local function addRecipeCard(category, sAmt, sName, quality)
  local cardArgs = {
    name = sName,
    text = sAmt,
    show_caption = '1'
  }

  if quality then
    cardArgs.quality = quality
  end

  local cardNode = Card._main(cardArgs)

  addUseCategory(
    category,
    sName,
    quality,
    padLeft(tostring(sAmt or ''):gsub('%D', ''), 10)
  )

  return cardNode
end

local function addMiniCard(category, sName, sText, quality)
  local cardArgs = {
    name = sName,
    text = sText or '',
    show_caption = '1',
    mini = '1'
  }

  if quality then
    cardArgs.quality = quality
  end

  local cardNode = Card._main(cardArgs)

  addUseCategory(category, sName)

  return cardNode
end

local function addYieldCard(pageTitle, sYield)
  return Card._main({
    name = pageTitle,
    text = sYield,
    show_caption = '1'
  })
end

--------------------------------------------------------------------------------
-- Header rendering
--------------------------------------------------------------------------------

local function renderHeader(args, output, category)
  local header = output:tag('div'):addClass('recipe-header')
  local sType = args[param.sTypeArg]

  header:tag('span'):addClass('recipe-header-main')
    :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sType .. ']]'):done()

  category:wikitext('[[Category:' .. sType .. ']]')

  return header
end

local function renderTime(args, header)
  if type(args[param.sTimeArg]) ~= 'string' then
    return
  end

  local tTime = {}
  local key = false
  local plural = false

  for n1, d, n2, h, n3, m, n4, s in mw.ustring.gmatch(
    args[param.sTimeArg],
    '(%d+)(d?)(%d*)(h?)(%d*)(m?)(%d*)(s?)'
  ) do
    table.insert(tTime, n1)

    if n1 ~= '1' then
      plural = true
    end

    if d == 'd' then
      table.insert(tTime, pluralize('day', plural))
      plural = false
      key = true
    end

    if n2 ~= '' then
      table.insert(tTime, n2)

      if n2 ~= '1' then
        plural = true
      end
    end

    if h == 'h' then
      table.insert(tTime, pluralize('hour', plural))
      plural = false
      key = true
    end

    if n3 ~= '' then
      table.insert(tTime, n3)

      if n3 ~= '1' then
        plural = true
      end
    end

    if m == 'm' then
      table.insert(tTime, pluralize('minute', plural))
      plural = false
      key = true
    end

    if n4 ~= '' then
      table.insert(tTime, n4)

      if n4 ~= '1' then
        plural = true
      end
    end

    if s == 's' then
      table.insert(tTime, pluralize('second', plural))
      key = true
    end
  end

  if not key then
    table.insert(tTime, pluralize('minute', plural))
  end

  local sTime = ''

  for _, each in ipairs(tTime) do
    sTime = sTime .. each .. ' '
  end

  header:tag('span'):addClass('recipe-header-sub')
    :tag('span'):wikitext(' for '):done()
    :tag('span'):addClass('recipe-header-text'):wikitext(sTime):done()
end

local function renderCharacter(args, header)
  if type(args[param.sCharArg]) ~= 'string' then
    return
  end

  local sCharName = args[param.sCharArg]

  header:tag('span'):addClass('recipe-header-sub')
    :tag('span'):wikitext('[[File:' .. sCharName .. ' Icon.png|24x24px|link=' .. sCharName .. ']]'):done()
    :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sCharName .. ']]'):done()
end

local function renderKitchenwareHeader(args, header)
  if type(args[param.sKitchArg]) ~= 'string' then
    return
  end

  local sKitchName = args[param.sKitchArg]

  header:tag('span'):addClass('recipe-header-sub')
    :tag('span'):wikitext('[[File:' .. sKitchName .. ' Icon.png|24x24px|link=' .. sKitchName .. ']]'):done()
    :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sKitchName .. ']]'):done()
end

--------------------------------------------------------------------------------
-- Standard recipe rendering
--------------------------------------------------------------------------------

local function isRestrictedStandardParam(sKey)
  for _, keyParam in pairs(param) do
    if keyParam == sKey then
      return true
    end
  end

  return false
end

local function renderStandardYield(args, body)
  if args[param.sYieldArg] == '0' then
    return
  end

  local sYield = args[param.sYieldArg] or nil
  local pageTitle = getCurrentPageName(args)

  local yieldCard = addYieldCard(pageTitle, sYield)

  body
    :tag('div'):addClass('recipe-body-icon')
      :wikitext('➜'):done()
    :tag('div'):addClass('recipe-body-yield')
      :node(yieldCard)
end

local function renderStandardRecipe(args, frame, output, category)
  local tArgsProcessed = {}

  local header = renderHeader(args, output, category)

  renderTime(args, header)
  renderCharacter(args, header)

  local body = output:tag('div'):addClass('recipe-body')

  local sSortDelim = param.sSortDelim

  if ((args[param.sSortArg] or ''):find(param.sSortDelim2) ~= nil) then
    sSortDelim = param.sSortDelim2
  end

  for sKey in string.gmatch(args[param.sSortArg] or '', '([^' .. sSortDelim .. ']+)') do
    if type(args[sKey]) ~= 'nil' then
      body:node(addRecipeCard(category, args[sKey], sKey))
      tArgsProcessed[sKey] = true
    end
  end

  for sKey, sVal in pairs(args) do
    if tArgsProcessed[sKey] ~= true and not isRestrictedStandardParam(sKey) then
      body:node(addRecipeCard(category, sVal, sKey))
    end
  end

  renderStandardYield(args, body)

  body:done()
end

--------------------------------------------------------------------------------
-- Cooking parsing
--------------------------------------------------------------------------------

local function getMaxCookingItemIndex(args)
  local maxIndex = 0

  for key, _ in pairs(args) do
    local index = tostring(key):match('^item_(%d+)')
    if index then
      index = tonumber(index)

      if index and index > maxIndex then
        maxIndex = index
      end
    end
  end

  return maxIndex
end

local function getCookingIngredient(args, index)
  local prefix = 'item_' .. index

  local ingredient = {
    index = index,
    amount = args[prefix .. '_amount'] or nil,
    quality = args[prefix .. '_quality'],
    item = args[prefix],
    types = {},
    options = {}
  }

  if argExists(args, prefix .. '_type') then
    table.insert(ingredient.types, args[prefix .. '_type'])
  end

  local typeIndex = 1

  while argExists(args, prefix .. '_type_' .. typeIndex) do
    table.insert(ingredient.types, args[prefix .. '_type_' .. typeIndex])
    typeIndex = typeIndex + 1
  end

  local optionIndex = 1

  while argExists(args, prefix .. '_option_' .. optionIndex) do
    table.insert(ingredient.options, args[prefix .. '_option_' .. optionIndex])
    optionIndex = optionIndex + 1
  end

  local hasIngredient =
    argExists(args, prefix)
    or #ingredient.types > 0
    or #ingredient.options > 0

  if not hasIngredient then
    return nil
  end

  return ingredient
end

--------------------------------------------------------------------------------
-- Cooking type split-card helpers
--------------------------------------------------------------------------------

local function makeTypeIcon(typeName)
  return '[[File:' .. typeName .. ' Icon.png|74x74px|link=' .. typeName .. ']]'
end

local function joinTypeCaption(types)
  local out = {}

  for _, typeName in ipairs(types) do
    table.insert(out, '[[' .. typeName .. ']]')
  end

  return table.concat(out, ' / ')
end

local function renderCookingSplitTypeCard(category, body, ingredient)
  local qualityClass = getQualityClass(ingredient.quality or 'Gray')

  local container = body:tag('span')
    :addClass('card-container')
    :addClass('recipe-type-split-card')

  local wrapper = container:tag('span')
    :addClass('card-wrapper')

  local cardBody = wrapper:tag('span')
    :addClass('card-body')

  local imageContainer = cardBody:tag('span')
    :addClass('card-image-container')
    :addClass('recipe-type-split-image')

  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-a')
    :wikitext(makeTypeIcon(ingredient.types[1]))
    :done()

  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-b')
    :wikitext(makeTypeIcon(ingredient.types[2]))
    :done()
    
	if qualityClass then
    	imageContainer:addClass(qualityClass)
	end

  if tostring(ingredient.amount or '') ~= '' then
    container:tag('span')
      :addClass('card-label')
      :wikitext(tostring(ingredient.amount or '1'))
      :done()
  end

  container:tag('span')
    :addClass('card-caption')
    :wikitext(joinTypeCaption(ingredient.types))
    :done()

  for _, typeName in ipairs(ingredient.types) do
    addUseCategory(category, typeName)
  end
end

--------------------------------------------------------------------------------
-- Cooking display
--------------------------------------------------------------------------------

local function renderCookingSpecificItem(category, body, ingredient)
  body:node(addRecipeCard(
    category,
    ingredient.amount,
    ingredient.item,
    ingredient.quality
  ))
end

local function renderCookingTypeFallback(category, body, ingredient)
  local wrapper = body:tag('div')
    :addClass('recipe-body-cooking-ingredient')
    :addClass('recipe-body-cooking-type-cards')

  for _, typeName in ipairs(ingredient.types) do
    wrapper:node(addMiniCard(
      category,
      typeName,
      ingredient.amount,
      ingredient.quality
    ))
  end
end

local function renderCookingTypeCards(category, body, ingredient)
  if #ingredient.types == 2 then
    renderCookingSplitTypeCard(category, body, ingredient)
  else
    renderCookingTypeFallback(category, body, ingredient)
  end
end

local function renderCookingOptionCards(category, body, ingredient)
  local wrapper = body:tag('div')
    :addClass('recipe-body-cooking-ingredient')
    :addClass('recipe-body-cooking-option-cards')

  for _, itemName in ipairs(ingredient.options) do
    wrapper:node(addMiniCard(
      category,
      itemName,
      ingredient.amount,
      ingredient.quality
    ))
  end
end

local function renderCookingIngredient(category, body, ingredient)
  if ingredient.item then
    renderCookingSpecificItem(category, body, ingredient)
    return
  end

  if #ingredient.options > 0 then
    renderCookingOptionCards(category, body, ingredient)
    return
  end

  if #ingredient.types > 0 then
    renderCookingTypeCards(category, body, ingredient)
    return
  end
end

local function renderCookingYield(args, category, body)
  if args[param.sYieldArg] == '0' then
    return
  end

  local sYield = args[param.sYieldArg] or nil
  local pageTitle = getCurrentPageName(args)

  local yieldCard = addYieldCard(pageTitle, sYield)

  body
    :tag('div'):addClass('recipe-body-icon')
      :wikitext('➜'):done()
    :tag('div'):addClass('recipe-body-yield')
      :node(yieldCard)
end

local function renderCookingRecipe(args, frame, output, category)
  local header = renderHeader(args, output, category)

  renderTime(args, header)
  renderCharacter(args, header)
  renderKitchenwareHeader(args, header)

  local body = output:tag('div')
    :addClass('recipe-body')
    :addClass('recipe-body-cooking')

  local foundIngredient = false
  local maxIndex = getMaxCookingItemIndex(args)

  for index = 1, maxIndex do
    local ingredient = getCookingIngredient(args, index)

    if ingredient then
      foundIngredient = true
      renderCookingIngredient(category, body, ingredient)
    end
  end

  if not foundIngredient then
    body:tag('span')
      :addClass('error')
      :wikitext('Cooking recipe error. No cooking ingredients found.')
  end

  renderCookingYield(args, category, body)

  body:done()
end

--------------------------------------------------------------------------------
-- Entry points
--------------------------------------------------------------------------------

function p.main(frame)
  local args = require('Module:Arguments').getArgs(frame, {
    parentFirst = true,
    wrapper = { 'Template:Recipe' }
  })

  return p._main(args, frame)
end

function p._main(args, frame)
  local output = mw.html.create('div')
    :addClass('recipe-container')

  local category = mw.html.create()

  if type(args[param.sTypeArg]) ~= 'string' then
    output:tag('span')
      :addClass('error')
      :wikitext('Recipe type error. Must include recipe type.')
  else
    local sType = args[param.sTypeArg]

    if normalizeText(sType) == 'cooking' then
      renderCookingRecipe(args, frame, output, category)
    else
      renderStandardRecipe(args, frame, output, category)
    end
  end

  output:node(require('Module:Namespace detect').main({
    ["main"] = category
  }))

  return output
end

return p