Module:InfoboxNPC: Difference between revisions
Jump to navigation
Jump to search
mNo edit summary Tag: Reverted |
mNo edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
-- | -- Get CSS variables from your theme | ||
-- | local function get_css_variable(var_name, default) | ||
local STYLE_FLOATING = 'float: right !important; clear: right !important; width: 280px !important; margin: 0 0 16px 16px !important; border: 1px solid | -- These map to your CSS theme variables | ||
local STYLE_HEADER = 'background: | local css_vars = { | ||
local STYLE_DATAROW = 'display: flex !important; justify-content: space-between !important; padding: | -- Borders | ||
local STYLE_DATALABEL = 'font-weight: 600 !important; color: | ['--theme-border-color'] = '#a1e9dc', | ||
local STYLE_DATAVALUE = 'color: | ['--theme-border-color--subtle'] = '#d1f5ee', | ||
-- Backgrounds | |||
['--card-bg-main'] = '#ffffff', | |||
['--card-bg-sub'] = '#e6fffa', | |||
['--card-bg-elevated'] = '#ffffff', | |||
['--theme-page-background-color--secondary'] = '#e8f5f3', | |||
-- Text Colors | |||
['--theme-page-text-color'] = '#1a2a2a', | |||
['--theme-accent-label-color'] = '#ffffff', | |||
['--gray-700'] = '#4a5568', | |||
['--gray-800'] = '#2d3748', | |||
['--gray-900'] = '#1a202c', | |||
['--gray-600'] = '#718096', | |||
['--gray-100'] = '#f7fafc', | |||
['--gray-200'] = '#edf2f7', | |||
['--gray-300'] = '#e2e8f0', | |||
-- Accent Colors | |||
['--teal-600'] = '#2c7a7b', | |||
['--teal-700'] = '#285e61', | |||
['--teal-800'] = '#234e52', | |||
['--teal-900'] = '#1d4044', | |||
['--teal-100'] = '#b2f5ea', | |||
['--teal-50'] = '#e6fffa', | |||
-- Gradients | |||
['--header-gradient'] = 'linear-gradient(135deg, #2c7a7b 0%, #285e61 100%)', -- teal-600 to teal-700 | |||
['--header-gradient-dark'] = 'linear-gradient(135deg, #234e52 0%, #1d4044 100%)', -- teal-800 to teal-900 | |||
-- Shadows | |||
['--shadow-sm'] = '0 1px 3px rgba(0, 0, 0, 0.12)', | |||
['--shadow-md'] = '0 4px 6px rgba(0, 0, 0, 0.1)', | |||
-- Border Radius | |||
['--border-radius-md'] = '8px', | |||
['--border-radius-sm'] = '4px', | |||
-- Transitions | |||
['--transition-fast'] = '150ms cubic-bezier(0.4, 0, 0.2, 1)' | |||
} | |||
-- Return the variable or default | |||
return css_vars[var_name] or default | |||
end | |||
-- Define styles using CSS variables for consistency | |||
local STYLE_FLOATING = string.format( | |||
'float: right !important; clear: right !important; width: 280px !important; margin: 0 0 16px 16px !important; border: 1px solid %s !important; border-radius: %s !important; background: %s !important; font-size: 14px !important; box-shadow: %s !important; transition: box-shadow %s ease, border-color %s ease !important;', | |||
get_css_variable('--theme-border-color', '#a1e9dc'), | |||
get_css_variable('--border-radius-md', '8px'), | |||
get_css_variable('--card-bg-main', '#ffffff'), | |||
get_css_variable('--shadow-md', '0 4px 6px rgba(0, 0, 0, 0.1)'), | |||
get_css_variable('--transition-fast', '150ms'), | |||
get_css_variable('--transition-fast', '150ms') | |||
) | |||
local STYLE_HEADER = string.format( | |||
'background: %s !important; color: %s !important; padding: 12px 15px !important; text-align: center !important; font-weight: 600 !important; font-size: 1.4em !important; letter-spacing: 0.5px !important; border-top-left-radius: 7px !important; border-top-right-radius: 7px !important; position: relative; overflow: hidden;', | |||
get_css_variable('--header-gradient', 'linear-gradient(135deg, #2c7a7b 0%, #285e61 100%)'), | |||
get_css_variable('--theme-accent-label-color', '#ffffff') | |||
) | |||
-- Add a subtle shine effect to header | |||
STYLE_HEADER = STYLE_HEADER .. ' ' .. 'text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);' | |||
-- Data row styles with better theme integration | |||
local STYLE_DATAROW = string.format( | |||
'display: flex !important; justify-content: space-between !important; padding: 10px 12px !important; border-bottom: 1px solid %s !important; transition: background-color %s ease;', | |||
get_css_variable('--theme-border-color--subtle', '#d1f5ee'), | |||
get_css_variable('--transition-fast', '150ms') | |||
) | |||
local STYLE_DATALABEL = string.format( | |||
'font-weight: 600 !important; color: %s !important; width: 35%% !important; text-align: left !important;', | |||
get_css_variable('--teal-700', '#285e61') | |||
) | |||
local STYLE_DATAVALUE = string.format( | |||
'color: %s !important; text-align: right !important; width: 65%% !important; line-height: 1.4; word-break: break-word;', | |||
get_css_variable('--theme-page-text-color', '#1a2a2a') | |||
) | |||
-- Helper function to properly handle wikilinks | -- Helper function to properly handle wikilinks | ||
| Line 27: | Line 109: | ||
end | end | ||
-- Function to generate a single data row | -- Function to generate a single data row with hover effect | ||
local function make_data_row(label, value, is_optional) | local function make_data_row(label, value, is_optional) | ||
if value and value ~= '' then | if value and value ~= '' then | ||
local formatted_value = format_wikitext(value) | local formatted_value = format_wikitext(value) | ||
local hover_style = string.format('npc-data-row:hover { background-color: %s !important; }', | |||
get_css_variable('--teal-50', '#e6fffa')) | |||
return string.format( | return string.format( | ||
'<div style="%s"><span style="%s">%s:</span> <span style="%s">%s</span></div>', | '<div class="npc-data-row" style="%s" onmouseover="this.style.backgroundColor=\'%s\'" onmouseout="this.style.backgroundColor=\'transparent\'">' .. | ||
'<span style="%s">%s:</span> <span style="%s">%s</span>' .. | |||
'</div>', | |||
STYLE_DATAROW, | STYLE_DATAROW, | ||
get_css_variable('--teal-50', 'rgba(230, 255, 250, 0.5)'), | |||
STYLE_DATALABEL, | STYLE_DATALABEL, | ||
label, | label, | ||
| Line 47: | Line 135: | ||
end | end | ||
-- Function to create a section divider | -- Function to create a section divider with theme colors | ||
local function make_section(title) | local function make_section(title) | ||
return string.format( | return string.format( | ||
'<div style="padding: | '<div style="padding: 10px 12px; background-color: %s !important; color: %s !important; font-weight: 600; font-size: 0.95em; border-top: 1px solid %s; border-bottom: 1px solid %s; margin: 0; text-transform: uppercase; letter-spacing: 0.5px; position: relative;">' .. | ||
'<span style="position: absolute; left: 12px; color: %s;">⏰</span>' .. | |||
'<span style="display: block; text-align: center;">%s</span>' .. | |||
'</div>', | |||
get_css_variable('--teal-100', '#b2f5ea'), | |||
get_css_variable('--teal-800', '#234e52'), | |||
get_css_variable('--teal-200', '#81e6d9'), | |||
get_css_variable('--teal-200', '#81e6d9'), | |||
get_css_variable('--teal-600', '#2c7a7b'), | |||
title | title | ||
) | ) | ||
end | |||
-- Function to format schedule time with color coding | |||
local function format_schedule_time(time_text) | |||
if not time_text then return '' end | |||
-- Color code different times of day | |||
local time_color = get_css_variable('--teal-700', '#285e61') -- Default teal | |||
if string.find(time_text:lower(), 'morning') then | |||
time_color = '#ecc94b' -- Yellow for morning | |||
elseif string.find(time_text:lower(), 'afternoon') then | |||
time_color = '#ed8936' -- Orange for afternoon | |||
elseif string.find(time_text:lower(), 'evening') then | |||
time_color = '#9f7aea' -- Purple for evening | |||
elseif string.find(time_text:lower(), 'night') then | |||
time_color = '#4c51bf' -- Indigo for night | |||
end | |||
return string.format('<span style="color: %s; font-weight: 600;">%s</span>', time_color, time_text) | |||
end | |||
-- Function to format schedule location with theme color | |||
local function format_schedule_location(location) | |||
if not location then return '' end | |||
return string.format('<span style="color: %s;">%s</span>', | |||
get_css_variable('--gray-700', '#4a5568'), | |||
location) | |||
end | end | ||
| Line 63: | Line 188: | ||
local html = {} | local html = {} | ||
-- Add CSS for hover effects | |||
table.insert(html, '<style>') | |||
table.insert(html, string.format('.npc-infobox:hover { box-shadow: %s !important; border-color: %s !important; }', | |||
get_css_variable('--shadow-lg', '0 10px 15px rgba(0, 0, 0, 0.1)'), | |||
get_css_variable('--teal-300', '#4fd1c7') | |||
)) | |||
table.insert(html, string.format('.npc-data-row:hover { background-color: %s !important; }', | |||
get_css_variable('--teal-50', 'rgba(230, 255, 250, 0.5)') | |||
)) | |||
table.insert(html, '</style>') | |||
-- Start the main infobox container | -- Start the main infobox container | ||
table.insert(html, string.format('<div class="npc-infobox" style="%s">', STYLE_FLOATING)) | table.insert(html, string.format('<div class="npc-infobox" style="%s">', STYLE_FLOATING)) | ||
-- Header/Title | -- Header/Title with glow effect | ||
table.insert(html, string.format('<div class="npc-header" style="%s">%s</div>', STYLE_HEADER, name)) | table.insert(html, string.format( | ||
'<div class="npc-header" style="%s">' .. | |||
'<div style="position: relative; z-index: 1;">%s</div>' .. | |||
'<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%%, rgba(255, 255, 255, 0.1) 50%%, transparent 70%%); pointer-events: none;"></div>' .. | |||
'</div>', | |||
STYLE_HEADER, name | |||
)) | |||
-- Image Section | -- Image Section | ||
if image and image ~= '' then | if image and image ~= '' then | ||
local image_link = string.format('[[File:%s|250px|alt=%s|class=npc-portrait]]', image, name) | local image_link = string.format('[[File:%s|250px|alt=%s|class=npc-portrait]]', image, name) | ||
table.insert(html, string.format('<div style="padding: 15px !important; text-align: center !important; background-color: | table.insert(html, string.format( | ||
'<div style="padding: 15px !important; text-align: center !important; background-color: %s; border-bottom: 1px solid %s;">%s</div>', | |||
get_css_variable('--card-bg-main', '#ffffff'), | |||
get_css_variable('--theme-border-color--subtle', '#d1f5ee'), | |||
image_link | |||
)) | |||
end | end | ||
-- Start Quick Facts Section | -- Start Quick Facts Section | ||
local has_core_data = false | local has_core_data = false | ||
| Line 93: | Line 239: | ||
if not has_core_data then | if not has_core_data then | ||
has_core_data = true | has_core_data = true | ||
-- Add subtle top border for first data row | |||
local first_row_style = STYLE_DATAROW:gsub('border%-bottom', 'border-top') | |||
table.insert(html, string.format('<style>.npc-data-row:first-of-type { border-top: 1px solid %s !important; }</style>', | |||
get_css_variable('--theme-border-color--subtle', '#d1f5ee'))) | |||
end | end | ||
table.insert(html, make_data_row(field.label, field.value, false)) | table.insert(html, make_data_row(field.label, field.value, false)) | ||
| Line 105: | Line 255: | ||
-- Create schedule items | -- Create schedule items | ||
local schedule_html = '<div style="padding: 0 12px 8px 12px; font-size: 0.9em;">' | local schedule_html = string.format('<div style="padding: 0 12px 8px 12px; font-size: 0.9em; background-color: %s;">', | ||
get_css_variable('--card-bg-main', '#ffffff') | |||
) | |||
local schedules = {} | local schedules = {} | ||
| Line 119: | Line 271: | ||
for i, schedule in ipairs(schedules) do | for i, schedule in ipairs(schedules) do | ||
-- Extract time and location from schedule string | |||
local time_pattern = '%[(.-)%]' | |||
local time = schedule:match(time_pattern) or 'Unknown Time' | |||
local location = schedule:gsub(time_pattern .. '%s*', ''):gsub('^%s*', ''):gsub('%s*$', '') | |||
schedule_html = schedule_html .. string.format( | schedule_html = schedule_html .. string.format( | ||
'<div style="padding: | '<div style="padding: 8px 0; border-bottom: %s; display: flex; align-items: center;">' .. | ||
'<div style="font-weight: 600; | '<div style="flex: 0 0 80px; font-weight: 600; padding-right: 8px; border-right: 1px solid %s;">%s</div>' .. | ||
'<div style=" | '<div style="flex: 1; padding-left: 12px; color: %s;">%s</div>' .. | ||
'</div>', | '</div>', | ||
i == #schedules and 'none' or '1px | i == #schedules and 'none' or '1px solid ' .. get_css_variable('--theme-border-color--subtle', '#e2e8f0'), | ||
get_css_variable('--teal-200', '#81e6d9'), | |||
format_schedule_time(time), | |||
get_css_variable('--gray-700', '#4a5568'), | |||
format_schedule_location(location) | |||
) | ) | ||
end | end | ||
| Line 136: | Line 295: | ||
-- Metadata note (for internal references) - only if exists | -- Metadata note (for internal references) - only if exists | ||
if args.note and args.note ~= '' then | if args.note and args.note ~= '' then | ||
table.insert(html, string.format( | table.insert(html, string.format( | ||
'<div style="padding: 10px 12px; font-size: 0.85em; color: | '<div style="padding: 10px 12px; background-color: %s; color: %s; font-weight: 600; font-size: 0.95em; border-top: 1px solid %s; border-bottom: 1px solid %s; margin: 0; text-transform: uppercase; letter-spacing: 0.5px;">Notes</div>', | ||
get_css_variable('--teal-100', '#b2f5ea'), | |||
get_css_variable('--teal-800', '#234e52'), | |||
get_css_variable('--teal-200', '#81e6d9'), | |||
get_css_variable('--teal-200', '#81e6d9') | |||
)) | |||
table.insert(html, string.format( | |||
'<div style="padding: 10px 12px; font-size: 0.85em; color: %s; font-style: italic; background-color: %s; border-radius: %s; margin: 0 12px 10px 12px; line-height: 1.5; border-left: 3px solid %s;">%s</div>', | |||
get_css_variable('--gray-600', '#718096'), | |||
get_css_variable('--gray-100', '#f7fafc'), | |||
get_css_variable('--border-radius-sm', '4px'), | |||
get_css_variable('--teal-300', '#4fd1c7'), | |||
mw.text.nowiki(args.note) | mw.text.nowiki(args.note) | ||
)) | )) | ||
| Line 145: | Line 314: | ||
-- End container | -- End container | ||
table.insert(html, '</div>') -- End npc-infobox | table.insert(html, '</div>') -- End npc-infobox | ||
-- Add clearfix to prevent layout issues | |||
table.insert(html, '<div style="clear: both;"></div>') | |||
return table.concat(html, '\n') | return table.concat(html, '\n') | ||
Revision as of 03:02, 6 February 2026
Documentation for this module may be created at Module:InfoboxNPC/doc
local p = {}
-- Get CSS variables from your theme
local function get_css_variable(var_name, default)
-- These map to your CSS theme variables
local css_vars = {
-- Borders
['--theme-border-color'] = '#a1e9dc',
['--theme-border-color--subtle'] = '#d1f5ee',
-- Backgrounds
['--card-bg-main'] = '#ffffff',
['--card-bg-sub'] = '#e6fffa',
['--card-bg-elevated'] = '#ffffff',
['--theme-page-background-color--secondary'] = '#e8f5f3',
-- Text Colors
['--theme-page-text-color'] = '#1a2a2a',
['--theme-accent-label-color'] = '#ffffff',
['--gray-700'] = '#4a5568',
['--gray-800'] = '#2d3748',
['--gray-900'] = '#1a202c',
['--gray-600'] = '#718096',
['--gray-100'] = '#f7fafc',
['--gray-200'] = '#edf2f7',
['--gray-300'] = '#e2e8f0',
-- Accent Colors
['--teal-600'] = '#2c7a7b',
['--teal-700'] = '#285e61',
['--teal-800'] = '#234e52',
['--teal-900'] = '#1d4044',
['--teal-100'] = '#b2f5ea',
['--teal-50'] = '#e6fffa',
-- Gradients
['--header-gradient'] = 'linear-gradient(135deg, #2c7a7b 0%, #285e61 100%)', -- teal-600 to teal-700
['--header-gradient-dark'] = 'linear-gradient(135deg, #234e52 0%, #1d4044 100%)', -- teal-800 to teal-900
-- Shadows
['--shadow-sm'] = '0 1px 3px rgba(0, 0, 0, 0.12)',
['--shadow-md'] = '0 4px 6px rgba(0, 0, 0, 0.1)',
-- Border Radius
['--border-radius-md'] = '8px',
['--border-radius-sm'] = '4px',
-- Transitions
['--transition-fast'] = '150ms cubic-bezier(0.4, 0, 0.2, 1)'
}
-- Return the variable or default
return css_vars[var_name] or default
end
-- Define styles using CSS variables for consistency
local STYLE_FLOATING = string.format(
'float: right !important; clear: right !important; width: 280px !important; margin: 0 0 16px 16px !important; border: 1px solid %s !important; border-radius: %s !important; background: %s !important; font-size: 14px !important; box-shadow: %s !important; transition: box-shadow %s ease, border-color %s ease !important;',
get_css_variable('--theme-border-color', '#a1e9dc'),
get_css_variable('--border-radius-md', '8px'),
get_css_variable('--card-bg-main', '#ffffff'),
get_css_variable('--shadow-md', '0 4px 6px rgba(0, 0, 0, 0.1)'),
get_css_variable('--transition-fast', '150ms'),
get_css_variable('--transition-fast', '150ms')
)
local STYLE_HEADER = string.format(
'background: %s !important; color: %s !important; padding: 12px 15px !important; text-align: center !important; font-weight: 600 !important; font-size: 1.4em !important; letter-spacing: 0.5px !important; border-top-left-radius: 7px !important; border-top-right-radius: 7px !important; position: relative; overflow: hidden;',
get_css_variable('--header-gradient', 'linear-gradient(135deg, #2c7a7b 0%, #285e61 100%)'),
get_css_variable('--theme-accent-label-color', '#ffffff')
)
-- Add a subtle shine effect to header
STYLE_HEADER = STYLE_HEADER .. ' ' .. 'text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);'
-- Data row styles with better theme integration
local STYLE_DATAROW = string.format(
'display: flex !important; justify-content: space-between !important; padding: 10px 12px !important; border-bottom: 1px solid %s !important; transition: background-color %s ease;',
get_css_variable('--theme-border-color--subtle', '#d1f5ee'),
get_css_variable('--transition-fast', '150ms')
)
local STYLE_DATALABEL = string.format(
'font-weight: 600 !important; color: %s !important; width: 35%% !important; text-align: left !important;',
get_css_variable('--teal-700', '#285e61')
)
local STYLE_DATAVALUE = string.format(
'color: %s !important; text-align: right !important; width: 65%% !important; line-height: 1.4; word-break: break-word;',
get_css_variable('--theme-page-text-color', '#1a2a2a')
)
-- Helper function to properly handle wikilinks
local function format_wikitext(value)
if not value or value == '' then
return ''
end
local frame = mw.getCurrentFrame()
-- Check if it looks like a wikilink
if string.find(value, '%[%[') and string.find(value, '%]%]') then
-- It's a wikilink, expand it through the parser
return frame:preprocess(value)
else
-- Plain text, escape it
return mw.text.nowiki(value)
end
end
-- Function to generate a single data row with hover effect
local function make_data_row(label, value, is_optional)
if value and value ~= '' then
local formatted_value = format_wikitext(value)
local hover_style = string.format('npc-data-row:hover { background-color: %s !important; }',
get_css_variable('--teal-50', '#e6fffa'))
return string.format(
'<div class="npc-data-row" style="%s" onmouseover="this.style.backgroundColor=\'%s\'" onmouseout="this.style.backgroundColor=\'transparent\'">' ..
'<span style="%s">%s:</span> <span style="%s">%s</span>' ..
'</div>',
STYLE_DATAROW,
get_css_variable('--teal-50', 'rgba(230, 255, 250, 0.5)'),
STYLE_DATALABEL,
label,
STYLE_DATAVALUE,
formatted_value
)
elseif is_optional then
return ''
else
return ''
end
end
-- Function to create a section divider with theme colors
local function make_section(title)
return string.format(
'<div style="padding: 10px 12px; background-color: %s !important; color: %s !important; font-weight: 600; font-size: 0.95em; border-top: 1px solid %s; border-bottom: 1px solid %s; margin: 0; text-transform: uppercase; letter-spacing: 0.5px; position: relative;">' ..
'<span style="position: absolute; left: 12px; color: %s;">⏰</span>' ..
'<span style="display: block; text-align: center;">%s</span>' ..
'</div>',
get_css_variable('--teal-100', '#b2f5ea'),
get_css_variable('--teal-800', '#234e52'),
get_css_variable('--teal-200', '#81e6d9'),
get_css_variable('--teal-200', '#81e6d9'),
get_css_variable('--teal-600', '#2c7a7b'),
title
)
end
-- Function to format schedule time with color coding
local function format_schedule_time(time_text)
if not time_text then return '' end
-- Color code different times of day
local time_color = get_css_variable('--teal-700', '#285e61') -- Default teal
if string.find(time_text:lower(), 'morning') then
time_color = '#ecc94b' -- Yellow for morning
elseif string.find(time_text:lower(), 'afternoon') then
time_color = '#ed8936' -- Orange for afternoon
elseif string.find(time_text:lower(), 'evening') then
time_color = '#9f7aea' -- Purple for evening
elseif string.find(time_text:lower(), 'night') then
time_color = '#4c51bf' -- Indigo for night
end
return string.format('<span style="color: %s; font-weight: 600;">%s</span>', time_color, time_text)
end
-- Function to format schedule location with theme color
local function format_schedule_location(location)
if not location then return '' end
return string.format('<span style="color: %s;">%s</span>',
get_css_variable('--gray-700', '#4a5568'),
location)
end
-- Main function to create the infobox HTML
function p.infobox(frame)
local args = frame:getParent().args
local name = args.name or 'Unknown Character'
local image = args.image
local html = {}
-- Add CSS for hover effects
table.insert(html, '<style>')
table.insert(html, string.format('.npc-infobox:hover { box-shadow: %s !important; border-color: %s !important; }',
get_css_variable('--shadow-lg', '0 10px 15px rgba(0, 0, 0, 0.1)'),
get_css_variable('--teal-300', '#4fd1c7')
))
table.insert(html, string.format('.npc-data-row:hover { background-color: %s !important; }',
get_css_variable('--teal-50', 'rgba(230, 255, 250, 0.5)')
))
table.insert(html, '</style>')
-- Start the main infobox container
table.insert(html, string.format('<div class="npc-infobox" style="%s">', STYLE_FLOATING))
-- Header/Title with glow effect
table.insert(html, string.format(
'<div class="npc-header" style="%s">' ..
'<div style="position: relative; z-index: 1;">%s</div>' ..
'<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%%, rgba(255, 255, 255, 0.1) 50%%, transparent 70%%); pointer-events: none;"></div>' ..
'</div>',
STYLE_HEADER, name
))
-- Image Section
if image and image ~= '' then
local image_link = string.format('[[File:%s|250px|alt=%s|class=npc-portrait]]', image, name)
table.insert(html, string.format(
'<div style="padding: 15px !important; text-align: center !important; background-color: %s; border-bottom: 1px solid %s;">%s</div>',
get_css_variable('--card-bg-main', '#ffffff'),
get_css_variable('--theme-border-color--subtle', '#d1f5ee'),
image_link
))
end
-- Start Quick Facts Section
local has_core_data = false
-- Check which core data fields exist
local core_fields = {
{label = 'Gender', value = args.gender},
{label = 'Age', value = args.age},
{label = 'Race', value = args.race},
{label = 'Role', value = args.role},
{label = 'Location', value = args.location}
}
for _, field in ipairs(core_fields) do
if field.value and field.value ~= '' then
if not has_core_data then
has_core_data = true
-- Add subtle top border for first data row
local first_row_style = STYLE_DATAROW:gsub('border%-bottom', 'border-top')
table.insert(html, string.format('<style>.npc-data-row:first-of-type { border-top: 1px solid %s !important; }</style>',
get_css_variable('--theme-border-color--subtle', '#d1f5ee')))
end
table.insert(html, make_data_row(field.label, field.value, false))
end
end
-- Start Schedule Section (if schedule data exists)
local has_schedule = args.schedule1 or args.schedule2 or args.schedule3
if has_schedule then
-- Add a divider before schedule
table.insert(html, make_section('Daily Schedule'))
-- Create schedule items
local schedule_html = string.format('<div style="padding: 0 12px 8px 12px; font-size: 0.9em; background-color: %s;">',
get_css_variable('--card-bg-main', '#ffffff')
)
local schedules = {}
if args.schedule1 and args.schedule1 ~= '' then
table.insert(schedules, args.schedule1)
end
if args.schedule2 and args.schedule2 ~= '' then
table.insert(schedules, args.schedule2)
end
if args.schedule3 and args.schedule3 ~= '' then
table.insert(schedules, args.schedule3)
end
for i, schedule in ipairs(schedules) do
-- Extract time and location from schedule string
local time_pattern = '%[(.-)%]'
local time = schedule:match(time_pattern) or 'Unknown Time'
local location = schedule:gsub(time_pattern .. '%s*', ''):gsub('^%s*', ''):gsub('%s*$', '')
schedule_html = schedule_html .. string.format(
'<div style="padding: 8px 0; border-bottom: %s; display: flex; align-items: center;">' ..
'<div style="flex: 0 0 80px; font-weight: 600; padding-right: 8px; border-right: 1px solid %s;">%s</div>' ..
'<div style="flex: 1; padding-left: 12px; color: %s;">%s</div>' ..
'</div>',
i == #schedules and 'none' or '1px solid ' .. get_css_variable('--theme-border-color--subtle', '#e2e8f0'),
get_css_variable('--teal-200', '#81e6d9'),
format_schedule_time(time),
get_css_variable('--gray-700', '#4a5568'),
format_schedule_location(location)
)
end
schedule_html = schedule_html .. '</div>'
table.insert(html, schedule_html)
end
-- Metadata note (for internal references) - only if exists
if args.note and args.note ~= '' then
table.insert(html, string.format(
'<div style="padding: 10px 12px; background-color: %s; color: %s; font-weight: 600; font-size: 0.95em; border-top: 1px solid %s; border-bottom: 1px solid %s; margin: 0; text-transform: uppercase; letter-spacing: 0.5px;">Notes</div>',
get_css_variable('--teal-100', '#b2f5ea'),
get_css_variable('--teal-800', '#234e52'),
get_css_variable('--teal-200', '#81e6d9'),
get_css_variable('--teal-200', '#81e6d9')
))
table.insert(html, string.format(
'<div style="padding: 10px 12px; font-size: 0.85em; color: %s; font-style: italic; background-color: %s; border-radius: %s; margin: 0 12px 10px 12px; line-height: 1.5; border-left: 3px solid %s;">%s</div>',
get_css_variable('--gray-600', '#718096'),
get_css_variable('--gray-100', '#f7fafc'),
get_css_variable('--border-radius-sm', '4px'),
get_css_variable('--teal-300', '#4fd1c7'),
mw.text.nowiki(args.note)
))
end
-- End container
table.insert(html, '</div>') -- End npc-infobox
-- Add clearfix to prevent layout issues
table.insert(html, '<div style="clear: both;"></div>')
return table.concat(html, '\n')
end
return p