Module:InfoboxNPC

From MB Wiki
Revision as of 03:02, 6 February 2026 by Ais (talk | contribs)
Jump to navigation Jump to search

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