MediaWiki:Gadget-AdventurerPlateMaker.js

From Final Fantasy XIV Online Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * MediaWiki Gadget: Image Category Viewer with Multiple Overlays
 * Displays eleven dropdown menus with images from different categories
 * and overlays the images in order from category 1 (base) to category 11 (top)
 */

(function() {
    'use strict';

    // Configuration - change these to your desired category names
    var categories = [
        { key: 'category6', name: 'Adventurer_Plate_Backings', label: 'Plate Backing', suffix: ' Plate Backing.png' },
        { key: 'category4', name: 'Adventurer_Plate_Base_Plates', label: 'Base Plate', suffix: ' Plate Base Plate.png' },
        { key: 'category5', name: 'Adventurer_Plate_Pattern_Overlays', label: 'Pattern Overlay', suffix: ' Plate Pattern Overlay.png' },
        { key: 'category10', name: 'Adventurer_Plate_Frames', label: 'Plate Frame', suffix: ' Plate Frame.png' },
        { key: 'category1', name: 'Portrait_Backgrounds', label: 'Portrait Background', suffix: ' Portrait Background.png' },
        { key: 'category2', name: 'Portrait_Frames', label: 'Portrait Frame', suffix: ' Portrait Frame.png' },
        { key: 'category3', name: 'Portrait_Decorations', label: 'Portrait Accent', suffix: ' Portrait Decoration.png' },
        { key: 'category9', name: 'Adventurer_Plate_Portrait_Frames', label: 'Plate Portrait Frame', suffix: ' Plate Portrait Frame.png' },
        { key: 'category7', name: 'Adventurer_Plate_Top_Borders', label: 'Top Border', suffix: ' Plate Top Border.png' },
        { key: 'category8', name: 'Adventurer_Plate_Bottom_Borders', label: 'Bottom Border', suffix: ' Plate Bottom Border.png' },
        { key: 'category11', name: 'Adventurer_Plate_Accents', label: 'Accent', suffix: ' Plate Accent.png' }
    ];
    
    // Store current image URLs
    var currentImages = {};
    categories.forEach(function(cat) {
        currentImages[cat.key] = null;
    });
    
    // Load saved selections from localStorage
    function loadSavedSelections() {
        try {
            var saved = localStorage.getItem('adventurer_plate_selections');
            if (saved) {
                return JSON.parse(saved);
            }
        } catch (e) {
            console.error('Error loading saved selections:', e);
        }
        return {};
    }
    
    // Save current selections to localStorage
    function saveSelections(selections) {
        try {
            localStorage.setItem('adventurer_plate_selections', JSON.stringify(selections));
        } catch (e) {
            console.error('Error saving selections:', e);
        }
    }
    
    // Track current selections
    var currentSelections = loadSavedSelections();
    
    // Track portrait orientation
    var portraitOrientation = 'right'; // default
    
    // Load saved orientation
    function loadSavedOrientation() {
        try {
            var saved = localStorage.getItem('adventurer_plate_orientation');
            if (saved) {
                return saved;
            }
        } catch (e) {
            console.error('Error loading saved orientation:', e);
        }
        return 'right';
    }
    
    // Save orientation to localStorage
    function saveOrientation(orientation) {
        try {
            localStorage.setItem('adventurer_plate_orientation', orientation);
        } catch (e) {
            console.error('Error saving orientation:', e);
        }
    }
    
    portraitOrientation = loadSavedOrientation();
    
    // Preset system
    var availablePresets = {
        adventurerPlate: [],
        portrait: []
    };
    
    // Acquisition data storage
    var acquisitionData = {};
    
    // Load presets from JSON file
    function loadPresets() {
        return fetch('https://ffxiv.consolegameswiki.com/wiki/Module:Gadget-AdventurerPlateMaker-PresetData.json?action=raw')
            .then(function(response) {
                if (!response.ok) {
                    throw new Error('Failed to load presets');
                }
                return response.json();
            })
            .then(function(data) {
                availablePresets.adventurerPlate = data.adventurerPlatePresets || [];
                availablePresets.portrait = data.portraitPresets || [];
                console.log('Presets loaded:', availablePresets);
            })
            .catch(function(error) {
                console.error('Error loading presets:', error);
            });
    }
    
    // Load acquisition data from separate JSON file
    function loadAcquisitionData() {
        return fetch('https://ffxiv.consolegameswiki.com/wiki/Module:Gadget-AdventurerPlateMaker-AcquisitionData.json?action=raw')
            .then(function(response) {
                if (!response.ok) {
                    throw new Error('Failed to load acquisition data');
                }
                return response.json();
            })
            .then(function(data) {
                // Convert array to object for easy lookup by name
                if (Array.isArray(data)) {
                    data.forEach(function(item) {
                        acquisitionData[item.name] = item;
                    });
                }
                console.log('Acquisition data loaded:', acquisitionData);
            })
            .catch(function(error) {
                console.error('Error loading acquisition data:', error);
            });
    }
    
    // Parse MediaWiki syntax to HTML
    function parseMediaWikiSyntax(text) {
        if (!text) return '';
        
        // Replace wiki links [[Page]] or [[Page|Display Text]]
        text = text.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, function(match, target, display) {
            return '<a href="/wiki/' + encodeURIComponent(target.replace(/ /g, '_')) + '">' + display + '</a>';
        });
        
        text = text.replace(/\[\[([^\]]+)\]\]/g, function(match, page) {
            return '<a href="/wiki/' + encodeURIComponent(page.replace(/ /g, '_')) + '">' + page + '</a>';
        });
        
        return text;
    }
    
    // Update acquisition display
    function updateAcquisitionDisplay() {
        var acquisitionContainer = document.getElementById('acquisition-display');
        if (!acquisitionContainer) return;
        
        acquisitionContainer.innerHTML = '';
        
        // Collect all selected element names (without "File:" prefix and suffixes)
        var selectedElements = new Set();
        // Include both portrait categories (1-3) and adventurer plate categories (4-11)
        var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
        
        allCategories.forEach(function(categoryKey) {
            if (currentSelections[categoryKey]) {
                var cat = categories.find(function(c) { return c.key === categoryKey; });
                if (cat) {
                    var filename = currentSelections[categoryKey];
                    var displayName = filename.replace('File:', '');
                    
                    // Remove the suffix if present
                    if (cat.suffix && displayName.endsWith(cat.suffix)) {
                        displayName = displayName.substring(0, displayName.length - cat.suffix.length);
                    }
                    
                    selectedElements.add(displayName);
                }
            }
        });
        
        // Display acquisition info for each unique element
        if (selectedElements.size > 0) {
            var hasAcquisitionData = false;
            
            selectedElements.forEach(function(elementName) {
                if (acquisitionData[elementName] && acquisitionData[elementName].acquisition) {
                    hasAcquisitionData = true;
                    
                    var acquisitionItem = document.createElement('div');
                    acquisitionItem.style.cssText = 'margin-bottom: 8px; padding: 8px; background-color: #f0f0f0; border-left: 3px solid #2196F3; border-radius: 3px;';
                    
                    var nameSpan = document.createElement('strong');
                    nameSpan.textContent = elementName + ': ';
                    acquisitionItem.appendChild(nameSpan);
                    
                    // Add framer's kit icon and link if specified
                    if (acquisitionData[elementName]['framers-kit']) {
                        var framersKitIcon = document.createElement('img');
                        framersKitIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/c/c0/Summoner_framers_kit_icon1.png';
                        framersKitIcon.alt = "Framer's Kit";
                        framersKitIcon.title = "Framer's Kit";
                        framersKitIcon.style.cssText = 'height: 20px; width: 20px; vertical-align: middle; margin-right: 4px;';
                        acquisitionItem.appendChild(framersKitIcon);
                        
                        var framersKitLink = document.createElement('a');
                        framersKitLink.href = '/wiki/' + encodeURIComponent((acquisitionData[elementName]['framers-kit'] + " Framer's Kit").replace(/ /g, '_'));
                        framersKitLink.textContent = acquisitionData[elementName]['framers-kit'] + " Framer's Kit";
                        acquisitionItem.appendChild(framersKitLink);
                        
                        acquisitionItem.appendChild(document.createTextNode(' - '));
                    }
                    
                    // Parse MediaWiki syntax and insert as HTML
                    var parsedAcquisition = parseMediaWikiSyntax(acquisitionData[elementName].acquisition);
                    var acquisitionSpan = document.createElement('span');
                    acquisitionSpan.innerHTML = parsedAcquisition;
                    acquisitionItem.appendChild(acquisitionSpan);
                    
                    acquisitionContainer.appendChild(acquisitionItem);
                }
            });
            
            if (!hasAcquisitionData) {
                acquisitionContainer.innerHTML = '<div style="color: #999; font-style: italic;">No acquisition information available for selected elements.</div>';
            }
        }
    }
    
    // Apply a preset
    function applyPreset(preset, dropdowns) {
        // Map preset keys to category keys
        var keyMapping = {
            'basePlate': 'category4',
            'patternOverlay': 'category5',
            'plateBacking': 'category6',
            'topBorder': 'category7',
            'bottomBorder': 'category8',
            'platePortraitFrame': 'category9',
            'plateFrame': 'category10',
            'accent': 'category11',
            'portraitBackground': 'category1',
            'portraitFrame': 'category2',
            'portraitAccent': 'category3'
        };
        
        // First, clear all relevant categories based on preset type
        var categoriesToClear = [];
        if (preset.type === 'adventurerPlate') {
            // Clear all adventurer plate categories (4-11)
            categoriesToClear = ['category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
        } else if (preset.type === 'portrait') {
            // Clear only portrait categories (1-3), NOT category9 (Plate Portrait Frame)
            categoriesToClear = ['category1', 'category2', 'category3'];
        }
        
        categoriesToClear.forEach(function(categoryKey) {
            if (dropdowns[categoryKey]) {
                dropdowns[categoryKey].value = '';
                currentImages[categoryKey] = null;
                delete currentSelections[categoryKey];
            }
        });
        
        // Then apply each preset value (portrait presets should not include platePortraitFrame)
        Object.keys(preset.selections).forEach(function(key) {
            var categoryKey = keyMapping[key];
            var value = preset.selections[key];
            
            // Skip platePortraitFrame if this is a portrait preset
            if (preset.type === 'portrait' && key === 'platePortraitFrame') {
                return;
            }
            
            if (categoryKey && dropdowns[categoryKey]) {
                var dropdown = dropdowns[categoryKey];
                
                if (value === null || value === '(None)') {
                    // Already cleared above, skip
                    return;
                } else {
                    // Find matching option in dropdown
                    var found = false;
                    for (var i = 0; i < dropdown.options.length; i++) {
                        var option = dropdown.options[i];
                        if (option.textContent === value) {
                            dropdown.value = option.value;
                            found = true;
                            break;
                        }
                    }
                    
                    if (found && dropdown.value) {
                        // Trigger load
                        loadImage(dropdown.value, categoryKey);
                        currentSelections[categoryKey] = dropdown.value;
                    }
                }
            }
        });
        
        saveSelections(currentSelections);
        updateCompositeDisplay();
        updateAcquisitionDisplay();
        
        // Keep the preset name displayed in the dropdown (no reset)
        // The dropdown value is already set to the preset name by the change event
    }
    
    // Generate shareable URL with current selections
    function generateShareableUrl() {
        var baseUrl = window.location.origin + window.location.pathname;
        var params = new URLSearchParams();
        
        // Add each selection as a parameter
        Object.keys(currentSelections).forEach(function(categoryKey) {
            var cat = categories.find(function(c) { return c.key === categoryKey; });
            if (cat && currentSelections[categoryKey]) {
                // Get the display name
                var filename = currentSelections[categoryKey];
                var displayName = filename.replace('File:', '');
                if (cat.suffix && displayName.endsWith(cat.suffix)) {
                    displayName = displayName.substring(0, displayName.length - cat.suffix.length);
                }
                
                // Map category key to URL parameter name
                var paramName = '';
                switch(categoryKey) {
                    case 'category1': paramName = 'portraitBackground'; break;
                    case 'category2': paramName = 'portraitFrame'; break;
                    case 'category3': paramName = 'portraitAccent'; break;
                    case 'category4': paramName = 'basePlate'; break;
                    case 'category5': paramName = 'patternOverlay'; break;
                    case 'category6': paramName = 'plateBacking'; break;
                    case 'category7': paramName = 'topBorder'; break;
                    case 'category8': paramName = 'bottomBorder'; break;
                    case 'category9': paramName = 'platePortraitFrame'; break;
                    case 'category10': paramName = 'plateFrame'; break;
                    case 'category11': paramName = 'accent'; break;
                }
                
                if (paramName) {
                    params.set(paramName, displayName);
                }
            }
        });
        
        // Add orientation
        params.set('orientation', portraitOrientation);
        
        return baseUrl + '?' + params.toString();
    }
    
    // Load selections from URL parameters
    function loadFromUrl(dropdowns) {
        var params = new URLSearchParams(window.location.search);
        
        // Check for preset parameter first
        var presetName = params.get('preset');
        if (presetName) {
            // Find and apply the preset
            var allPresets = availablePresets.adventurerPlate.concat(availablePresets.portrait);
            var preset = allPresets.find(function(p) { 
                return p.name.toLowerCase() === presetName.toLowerCase(); 
            });
            
            if (preset) {
                console.log('Loading preset from URL:', presetName);
                applyPreset(preset, dropdowns);
                return;
            }
        }
        
        // Otherwise load individual selections from URL
        var paramMapping = {
            'portraitBackground': 'category1',
            'portraitFrame': 'category2',
            'portraitAccent': 'category3',
            'basePlate': 'category4',
            'patternOverlay': 'category5',
            'plateBacking': 'category6',
            'topBorder': 'category7',
            'bottomBorder': 'category8',
            'platePortraitFrame': 'category9',
            'plateFrame': 'category10',
            'accent': 'category11'
        };
        
        var hasUrlParams = false;
        Object.keys(paramMapping).forEach(function(paramName) {
            var value = params.get(paramName);
            if (value) {
                hasUrlParams = true;
                var categoryKey = paramMapping[paramName];
                var dropdown = dropdowns[categoryKey];
                
                if (dropdown) {
                    // Find matching option
                    for (var i = 0; i < dropdown.options.length; i++) {
                        var option = dropdown.options[i];
                        if (option.textContent === value) {
                            dropdown.value = option.value;
                            if (dropdown.value) {
                                loadImage(dropdown.value, categoryKey);
                                currentSelections[categoryKey] = dropdown.value;
                            }
                            break;
                        }
                    }
                }
            }
        });
        
        // Load orientation from URL
        var orientation = params.get('orientation');
        if (orientation === 'left' || orientation === 'right') {
            portraitOrientation = orientation;
            saveOrientation(orientation);
            
            // Update radio buttons
            var leftRadio = document.getElementById('orientation-left');
            var rightRadio = document.getElementById('orientation-right');
            if (leftRadio && rightRadio) {
                leftRadio.checked = (orientation === 'left');
                rightRadio.checked = (orientation === 'right');
            }
        }
        
        if (hasUrlParams) {
            saveSelections(currentSelections);
            updateCompositeDisplay();
        }
    }
    
    // Populate preset dropdown
    function populatePresetDropdown(dropdown, presetType, dropdowns) {
        dropdown.innerHTML = '';
        
        var presets = presetType === 'adventurerPlate' ? availablePresets.adventurerPlate : availablePresets.portrait;
        
        // Add default option
        var defaultOption = document.createElement('option');
        defaultOption.textContent = '-- Select a preset --';
        defaultOption.value = '';
        dropdown.appendChild(defaultOption);
        
        // Add preset options
        if (presets.length === 0) {
            var noPresetsOption = document.createElement('option');
            noPresetsOption.textContent = 'No presets available';
            noPresetsOption.value = '';
            noPresetsOption.disabled = true;
            dropdown.appendChild(noPresetsOption);
            return;
        }
        
        // Sort presets alphabetically by name
        presets.sort(function(a, b) {
            return a.name.localeCompare(b.name);
        });
        
        presets.forEach(function(preset) {
            var option = document.createElement('option');
            option.value = preset.name;
            option.textContent = preset.name;
            option.presetData = preset; // Store preset data on the option
            dropdown.appendChild(option);
        });
        
        // Add "Custom" option
        var customOption = document.createElement('option');
        customOption.value = 'custom';
        customOption.textContent = 'Custom';
        customOption.disabled = true;
        dropdown.appendChild(customOption);
        
        // Add change event listener
        dropdown.addEventListener('change', function() {
            if (this.value && this.value !== 'custom') {
                // Find the selected preset
                var selectedOption = this.options[this.selectedIndex];
                if (selectedOption.presetData) {
                    applyPreset(selectedOption.presetData, dropdowns);
                    // Don't reset - keep showing the selected preset name
                }
            }
        });
    }
    
    // Create the UI container
    function createUI() {
        var container = document.createElement('div');
        container.id = 'image-category-viewer';
        container.style.cssText = 'margin: 20px;';
        
        // Dropdowns container
        var dropdownContainer = document.createElement('div');
        dropdownContainer.style.cssText = 'margin-bottom: 10px;';
        
        var dropdowns = {};
        
        // Portrait Elements section (in its own gray box)
        var portraitSection = document.createElement('div');
        portraitSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; margin-bottom: 10px; display: inline-block;';
        
        // Portrait header row: Icon and Load Portrait Preset
        var portraitHeaderRow = document.createElement('div');
        portraitHeaderRow.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px; align-items: center;';
        
        // Icon wrapper to match the first column width
        var portraitIconWrapper = document.createElement('div');
        portraitIconWrapper.style.cssText = 'flex: 0 0 200px; display: flex; align-items: center;';
        
        var portraitIcon = document.createElement('img');
        portraitIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/5/5b/Portrait_icon1.png';
        portraitIcon.alt = 'Portrait Elements';
        portraitIcon.style.cssText = 'height: 40px; width: 40px;';
        portraitIconWrapper.appendChild(portraitIcon);
        
        // Label between icon and dropdown
        var portraitPresetLabel = document.createElement('label');
        portraitPresetLabel.textContent = 'Load Portrait Preset:';
        portraitPresetLabel.style.cssText = 'margin-left: 15px; white-space: nowrap; font-weight: bold;';
        portraitIconWrapper.appendChild(portraitPresetLabel);
        
        portraitHeaderRow.appendChild(portraitIconWrapper);
        
        // Load Portrait Preset dropdown (aligned with second column - Portrait Frame)
        var portraitPresetWrapper = document.createElement('div');
        portraitPresetWrapper.style.cssText = 'flex: 0 0 200px;';
        
        var portraitPresetDropdown = document.createElement('select');
        portraitPresetDropdown.id = 'portrait-preset-dropdown';
        portraitPresetDropdown.style.cssText = 'padding: 5px; width: 200px;';
        portraitPresetWrapper.appendChild(portraitPresetDropdown);
        portraitHeaderRow.appendChild(portraitPresetWrapper);
        
        portraitSection.appendChild(portraitHeaderRow);
        
        // Portrait elements row: Background, Frame, Accent, Orientation
        var portraitRow = document.createElement('div');
        portraitRow.style.cssText = 'display: flex; gap: 15px; align-items: flex-end;';
        
        // Portrait Backgrounds, Portrait Frames, Portrait Decorations
        var portraitCategories = [
            categories.find(function(c) { return c.key === 'category1'; }),
            categories.find(function(c) { return c.key === 'category2'; }),
            categories.find(function(c) { return c.key === 'category3'; })
        ];
        
        portraitCategories.forEach(function(cat) {
            var dropdownWrapper = document.createElement('div');
            dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
            
            var label = document.createElement('label');
            label.textContent = cat.label + ': ';
            label.style.marginBottom = '5px';
            dropdownWrapper.appendChild(label);
            
            var dropdown = document.createElement('select');
            dropdown.id = 'image-dropdown-' + cat.key;
            dropdown.style.cssText = 'padding: 5px; width: 200px;';
            
            var defaultOption = document.createElement('option');
            defaultOption.textContent = 'Loading images...';
            defaultOption.value = '';
            dropdown.appendChild(defaultOption);
            
            dropdownWrapper.appendChild(dropdown);
            portraitRow.appendChild(dropdownWrapper);
            dropdowns[cat.key] = dropdown;
        });
        
        // Portrait Orientation selector (after portrait dropdowns)
        var orientationWrapper = document.createElement('div');
        orientationWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 auto;';
        
        var orientationLabel = document.createElement('label');
        orientationLabel.textContent = 'Portrait Orientation:';
        orientationLabel.style.cssText = 'margin-bottom: 5px;';
        orientationWrapper.appendChild(orientationLabel);
        
        var orientationControls = document.createElement('div');
        orientationControls.style.cssText = 'display: flex; gap: 10px; min-height: 30px; align-items: center; box-sizing: border-box;';
        
        var leftRadio = document.createElement('input');
        leftRadio.type = 'radio';
        leftRadio.name = 'portrait-orientation';
        leftRadio.value = 'left';
        leftRadio.id = 'orientation-left';
        leftRadio.checked = portraitOrientation === 'left';
        
        var leftLabel = document.createElement('label');
        leftLabel.htmlFor = 'orientation-left';
        leftLabel.textContent = 'Left';
        leftLabel.style.cursor = 'pointer';
        
        var rightRadio = document.createElement('input');
        rightRadio.type = 'radio';
        rightRadio.name = 'portrait-orientation';
        rightRadio.value = 'right';
        rightRadio.id = 'orientation-right';
        rightRadio.checked = portraitOrientation === 'right';
        
        var rightLabel = document.createElement('label');
        rightLabel.htmlFor = 'orientation-right';
        rightLabel.textContent = 'Right';
        rightLabel.style.cursor = 'pointer';
        
        orientationControls.appendChild(leftRadio);
        orientationControls.appendChild(leftLabel);
        orientationControls.appendChild(rightRadio);
        orientationControls.appendChild(rightLabel);
        orientationWrapper.appendChild(orientationControls);
        
        // Add event listeners
        rightRadio.addEventListener('change', function() {
            if (this.checked) {
                portraitOrientation = 'right';
                saveOrientation('right');
                updateCompositeDisplay();
            }
        });
        
        leftRadio.addEventListener('change', function() {
            if (this.checked) {
                portraitOrientation = 'left';
                saveOrientation('left');
                updateCompositeDisplay();
            }
        });
        
        portraitRow.appendChild(orientationWrapper);
        
        portraitSection.appendChild(portraitRow);
        
        // Adventurer Plate Elements section (in its own gray box)
        var advPlateSection = document.createElement('div');
        advPlateSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: inline-block;';
        
        // Adventurer Plate header row: Icon and Load Plate Preset
        var advPlateHeaderRow = document.createElement('div');
        advPlateHeaderRow.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px; align-items: center;';
        
        // Icon wrapper to match the first column width
        var advPlateIconWrapper = document.createElement('div');
        advPlateIconWrapper.style.cssText = 'flex: 0 0 200px; display: flex; align-items: center;';
        
        var advPlateIcon = document.createElement('img');
        advPlateIcon.src = 'https://ffxiv.consolegameswiki.com/mediawiki/images/f/f4/Adventurer_plate_icon1.png';
        advPlateIcon.alt = 'Adventurer Plate Elements';
        advPlateIcon.style.cssText = 'height: 40px; width: 40px;';
        advPlateIconWrapper.appendChild(advPlateIcon);
        
        // Label between icon and dropdown
        var presetLabel = document.createElement('label');
        presetLabel.textContent = 'Load Plate Preset:';
        presetLabel.style.cssText = 'margin-left: 15px; white-space: nowrap; font-weight: bold;';
        advPlateIconWrapper.appendChild(presetLabel);
        
        advPlateHeaderRow.appendChild(advPlateIconWrapper);
        
        // Load Plate Preset dropdown (aligned with second column - Base Plate)
        var presetWrapper = document.createElement('div');
        presetWrapper.style.cssText = 'flex: 0 0 200px;';
        
        var advPlatePresetDropdown = document.createElement('select');
        advPlatePresetDropdown.id = 'adventurer-plate-preset-dropdown';
        advPlatePresetDropdown.style.cssText = 'padding: 5px; width: 200px;';
        presetWrapper.appendChild(advPlatePresetDropdown);
        advPlateHeaderRow.appendChild(presetWrapper);
        
        advPlateSection.appendChild(advPlateHeaderRow);
        
        // Row 1: Backings, Base Plates, Pattern Overlays, Frames
        var advPlateRow1 = document.createElement('div');
        advPlateRow1.style.cssText = 'display: flex; gap: 15px; margin-bottom: 15px;';
        
        var row1Categories = [
            categories.find(function(c) { return c.key === 'category6'; }),
            categories.find(function(c) { return c.key === 'category4'; }),
            categories.find(function(c) { return c.key === 'category5'; }),
            categories.find(function(c) { return c.key === 'category10'; })
        ];
        
        row1Categories.forEach(function(cat) {
            var dropdownWrapper = document.createElement('div');
            dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
            
            var label = document.createElement('label');
            label.textContent = cat.label + ': ';
            label.style.marginBottom = '5px';
            dropdownWrapper.appendChild(label);
            
            var dropdown = document.createElement('select');
            dropdown.id = 'image-dropdown-' + cat.key;
            dropdown.style.cssText = 'padding: 5px; width: 200px;';
            
            var defaultOption = document.createElement('option');
            defaultOption.textContent = 'Loading images...';
            defaultOption.value = '';
            dropdown.appendChild(defaultOption);
            
            dropdownWrapper.appendChild(dropdown);
            advPlateRow1.appendChild(dropdownWrapper);
            dropdowns[cat.key] = dropdown;
        });
        
        advPlateSection.appendChild(advPlateRow1);
        
        // Row 2: Portrait Frames, Top Borders, Bottom Borders, Accents
        var advPlateRow2 = document.createElement('div');
        advPlateRow2.style.cssText = 'display: flex; gap: 15px;';
        
        var row2Categories = [
            categories.find(function(c) { return c.key === 'category9'; }),
            categories.find(function(c) { return c.key === 'category7'; }),
            categories.find(function(c) { return c.key === 'category8'; }),
            categories.find(function(c) { return c.key === 'category11'; })
        ];
        
        row2Categories.forEach(function(cat) {
            var dropdownWrapper = document.createElement('div');
            dropdownWrapper.style.cssText = 'display: flex; flex-direction: column; flex: 0 0 200px;';
            
            var label = document.createElement('label');
            label.textContent = cat.label + ': ';
            label.style.marginBottom = '5px';
            dropdownWrapper.appendChild(label);
            
            var dropdown = document.createElement('select');
            dropdown.id = 'image-dropdown-' + cat.key;
            dropdown.style.cssText = 'padding: 5px; width: 200px;';
            
            var defaultOption = document.createElement('option');
            defaultOption.textContent = 'Loading images...';
            defaultOption.value = '';
            dropdown.appendChild(defaultOption);
            
            dropdownWrapper.appendChild(dropdown);
            advPlateRow2.appendChild(dropdownWrapper);
            dropdowns[cat.key] = dropdown;
        });
        
        advPlateSection.appendChild(advPlateRow2);
        
        // Create tabber structure
        var tabberWrapper = document.createElement('div');
        tabberWrapper.style.cssText = 'display: inline-block; vertical-align: top;';
        
        // Tab buttons
        var tabButtons = document.createElement('div');
        tabButtons.style.cssText = 'display: flex; gap: 5px; margin-bottom: 10px;';
        
        var portraitTabButton = document.createElement('button');
        portraitTabButton.textContent = 'Portrait';
        portraitTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
        portraitTabButton.id = 'portrait-tab-button';
        
        var plateTabButton = document.createElement('button');
        plateTabButton.textContent = 'Adventurer Plate';
        plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
        plateTabButton.id = 'plate-tab-button';
        
        tabButtons.appendChild(portraitTabButton);
        tabButtons.appendChild(plateTabButton);
        tabberWrapper.appendChild(tabButtons);
        
        // Tab content container
        var tabContent = document.createElement('div');
        tabContent.style.cssText = 'position: relative;';
        
        // Portrait tab content (visible by default)
        portraitSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: block; width: 900px; box-sizing: border-box;';
        portraitSection.id = 'portrait-tab-content';
        
        // Adventurer Plate tab content (hidden by default)
        advPlateSection.style.cssText = 'padding: 15px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 5px; display: none; width: 900px; box-sizing: border-box;';
        advPlateSection.id = 'plate-tab-content';
        
        tabContent.appendChild(portraitSection);
        tabContent.appendChild(advPlateSection);
        tabberWrapper.appendChild(tabContent);
        
        // Tab switching logic
        portraitTabButton.addEventListener('click', function() {
            document.getElementById('portrait-tab-content').style.display = 'block';
            document.getElementById('plate-tab-content').style.display = 'none';
            portraitTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
            plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
        });
        
        plateTabButton.addEventListener('click', function() {
            document.getElementById('portrait-tab-content').style.display = 'none';
            document.getElementById('plate-tab-content').style.display = 'block';
            portraitTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #ccc; color: black; border: none; border-radius: 3px 3px 0 0;';
            plateTabButton.style.cssText = 'padding: 8px 20px; cursor: pointer; background-color: #2196F3; color: white; border: none; border-radius: 3px 3px 0 0; font-weight: bold;';
        });
        
        // Buttons section (vertical, to the right of tabber) - MOVED HERE BEFORE BUTTON DEFINITIONS
        var buttonsSection = document.createElement('div');
        buttonsSection.style.cssText = 'display: inline-flex; flex-direction: column; gap: 10px; margin-left: 20px; margin-top: 41px;';
        
        // Clear All button
        var clearButton = document.createElement('button');
        clearButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/6/64/AdvPlateMaker-clear.png" alt="Clear All" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
        clearButton.title = 'Clear All';
        clearButton.style.cssText = 'padding: 5px; cursor: pointer; background-color: #f44336; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
        clearButton.addEventListener('click', function() {
            // Clear all dropdowns
            Object.keys(dropdowns).forEach(function(key) {
                dropdowns[key].value = '';
            });
            
            // Clear current images
            Object.keys(currentImages).forEach(function(key) {
                currentImages[key] = null;
            });
            
            // Clear saved selections
            currentSelections = {};
            saveSelections({});
            
            // Update display
            updateCompositeDisplay();
        });
        buttonsSection.appendChild(clearButton);
        
        // Randomize button
        var randomizeButton = document.createElement('button');
        randomizeButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/2/2b/AdvPlateMaker-randomizer.png" alt="Randomize" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
        randomizeButton.title = 'Randomize';
        randomizeButton.style.cssText = 'padding: 5px; cursor: pointer; background-color: #4CAF50; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
        randomizeButton.addEventListener('click', function() {
            // Randomize each dropdown (except preset dropdowns)
            Object.keys(dropdowns).forEach(function(key) {
                // Skip the preset dropdowns
                if (key === 'presetDropdown' || key === 'portraitPresetDropdown') {
                    return;
                }
                
                var dropdown = dropdowns[key];
                var options = dropdown.options;
                
                // Get all valid options (exclude the default "-- Select an image --" option)
                var validOptions = [];
                for (var i = 0; i < options.length; i++) {
                    if (options[i].value !== '') {
                        validOptions.push(options[i].value);
                    }
                }
                
                // Select a random option if available
                if (validOptions.length > 0) {
                    var randomIndex = Math.floor(Math.random() * validOptions.length);
                    dropdown.value = validOptions[randomIndex];
                    
                    // Trigger the change event to load the image
                    var event = new Event('change');
                    dropdown.dispatchEvent(event);
                }
            });
            
            // Set preset dropdowns to "Custom" after randomizing
            var presetDropdown = document.getElementById('adventurer-plate-preset-dropdown');
            if (presetDropdown) {
                presetDropdown.value = 'custom';
            }
            var portraitPresetDropdown = document.getElementById('portrait-preset-dropdown');
            if (portraitPresetDropdown) {
                portraitPresetDropdown.value = 'custom';
            }
        });
        buttonsSection.appendChild(randomizeButton);
        
        // Copy Link button
        var copyLinkButton = document.createElement('button');
        copyLinkButton.innerHTML = '<img src="https://ffxiv.consolegameswiki.com/mediawiki/images/9/91/AdvPlateMaker-link.png" alt="Copy Link" style="height: 20px; width: 20px; display: block; margin: 0 auto;">';
        copyLinkButton.title = 'Copy Link';
        copyLinkButton.style.cssText = 'padding: 5px; cursor: pointer; background-color: #FF9800; color: white; border: none; border-radius: 3px; width: 50px; font-size: 16px;';
        copyLinkButton.addEventListener('click', function() {
            var url = generateShareableUrl();
            
            // Copy to clipboard
            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(url).then(function() {
                    copyLinkButton.title = 'Copied!';
                    setTimeout(function() {
                        copyLinkButton.title = 'Copy Link';
                    }, 2000);
                }).catch(function(err) {
                    console.error('Failed to copy URL: ', err);
                });
            } else {
                // Fallback for older browsers
                console.warn('Clipboard API not available');
            }
        });
        buttonsSection.appendChild(copyLinkButton);
        
        // Wrapper for tabber and buttons (side by side)
        var tabberAndButtonsWrapper = document.createElement('div');
        tabberAndButtonsWrapper.style.cssText = 'margin-bottom: 0; display: flex; align-items: flex-start;';
        tabberAndButtonsWrapper.appendChild(tabberWrapper);
        tabberAndButtonsWrapper.appendChild(buttonsSection);
        
        dropdownContainer.appendChild(tabberAndButtonsWrapper);
        
        container.appendChild(dropdownContainer);
        
        // Image container below dropdowns
        var imageContainer = document.createElement('div');
        imageContainer.id = 'selected-image-container';
        imageContainer.style.cssText = 'text-align: center;';
        container.appendChild(imageContainer);
        
        // Acquisition display section (moved below image)
        var acquisitionSection = document.createElement('div');
        acquisitionSection.style.cssText = 'margin-top: 20px; padding: 15px; background-color: #fff; border: 1px solid #ccc; border-radius: 5px;';
        
        var acquisitionTitle = document.createElement('h3');
        acquisitionTitle.textContent = 'Acquisition Information';
        acquisitionTitle.style.cssText = 'margin-top: 0; margin-bottom: 10px;';
        acquisitionSection.appendChild(acquisitionTitle);
        
        var acquisitionDisplay = document.createElement('div');
        acquisitionDisplay.id = 'acquisition-display';
        acquisitionDisplay.style.cssText = 'min-height: 30px;';
        acquisitionSection.appendChild(acquisitionDisplay);
        
        container.appendChild(acquisitionSection);
        
        // Insert the container after the content
        var contentDiv = document.getElementById('mw-content-text');
        if (contentDiv) {
            contentDiv.appendChild(container);
        }
        
        // Store preset dropdown reference for later population
        dropdowns.presetDropdown = advPlatePresetDropdown;
        dropdowns.portraitPresetDropdown = portraitPresetDropdown;
        
        return dropdowns;
    }
    
    // Fetch images from category using MediaWiki API
    function fetchImagesFromCategory(categoryName) {
        var api = new mw.Api();
        
        return api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + categoryName,
            cmtype: 'file',
            cmlimit: 500,
            format: 'json'
        }).then(function(data) {
            if (data.query && data.query.categorymembers) {
                return data.query.categorymembers;
            }
            return [];
        });
    }
    
    // Get image info including URL
    function getImageInfo(filename) {
        var api = new mw.Api();
        
        return api.get({
            action: 'query',
            titles: filename,
            prop: 'imageinfo',
            iiprop: 'url',
            iiurlwidth: 2560,
            format: 'json'
        }).then(function(data) {
            var pages = data.query.pages;
            var pageId = Object.keys(pages)[0];
            
            if (pages[pageId].imageinfo && pages[pageId].imageinfo.length > 0) {
                return pages[pageId].imageinfo[0];
            }
            return null;
        });
    }
    
    // Update the composite display with all images
    function updateCompositeDisplay() {
        var imageContainer = document.getElementById('selected-image-container');
        imageContainer.innerHTML = '';
        
        var hasAnyImage = false;
        categories.forEach(function(cat) {
            if (currentImages[cat.key]) {
                hasAnyImage = true;
            }
        });
        
        // Always show if any image is selected OR if we need to show the default blank backing
        if (!hasAnyImage && !currentImages['category6']) {
            return;
        }
        
        // Calculate responsive width based on container (min 300px, max 1280px)
        var imageContainer = document.getElementById('selected-image-container');
        var containerWidth = imageContainer ? imageContainer.offsetWidth : 1280;
        var targetWidth = Math.min(Math.max(containerWidth - 60, 300), 1280); // Leave 60px total margin
        var scaleFactor = targetWidth / 2560; // Calculate scale factor from original 2560px width
        var targetHeight = 1440 * scaleFactor;
        
        // Create wrapper for overlay effect - scaled responsively
        var wrapper = document.createElement('div');
        wrapper.style.cssText = 'position: relative; display: inline-block; line-height: 0; width: ' + targetWidth + 'px; height: ' + targetHeight + 'px;';
        
        // Add default backing if category6 (Adventurer Plate Backings) is not selected
        if (!currentImages['category6']) {
            var defaultBacking = document.createElement('img');
            defaultBacking.src = 'http://ffxiv.consolegameswiki.com/mediawiki/images/4/44/AP_blank.png';
            defaultBacking.alt = 'Default Backing';
            defaultBacking.style.cssText = 'display: block; width: ' + targetWidth + 'px; height: ' + targetHeight + 'px;';
            wrapper.appendChild(defaultBacking);
        }
        
        // Add images in order (category6 first as base, then overlay subsequent categories)
        categories.forEach(function(cat, index) {
            if (currentImages[cat.key]) {
                var img = document.createElement('img');
                img.src = currentImages[cat.key].thumburl || currentImages[cat.key].url;
                img.alt = cat.label + ' Image';
                
                // Center Base Plates, Pattern Overlays, Frames, Portrait Frames, Portrait categories, Top Borders, Bottom Borders, and Accents on the Backings image
                if (cat.key === 'category4' || cat.key === 'category5' || cat.key === 'category10' || cat.key === 'category9' || cat.key === 'category1' || cat.key === 'category2' || cat.key === 'category3' || cat.key === 'category7' || cat.key === 'category8' || cat.key === 'category11') {
                    // Calculate centering offset dynamically (scaled)
                    var backingWidth = currentImages['category6'] ? currentImages['category6'].loadedWidth * scaleFactor : targetWidth;
                    var backingHeight = currentImages['category6'] ? currentImages['category6'].loadedHeight * scaleFactor : targetHeight;
                    var imgWidth = (currentImages[cat.key].loadedWidth || 1480) * scaleFactor;
                    var imgHeight = (currentImages[cat.key].loadedHeight || 840) * scaleFactor;
                    
                    var leftOffset = (backingWidth - imgWidth) / 2;
                    var topOffset = (backingHeight - imgHeight) / 2;
                    
                    // Apply manual adjustment for Adventurer Plate Frames (shift up 28px, scaled)
                    if (cat.key === 'category10') {
                        topOffset -= 28 * scaleFactor;
                    }
                    
                    // Apply manual adjustment for Adventurer Plate Top Borders (shift up 420px, scaled)
                    if (cat.key === 'category7') {
                        topOffset -= 420 * scaleFactor;
                    }
                    
                    // Apply manual adjustment for Adventurer Plate Bottom Borders (shift down 364px, scaled)
                    if (cat.key === 'category8') {
                        topOffset += 364 * scaleFactor;
                    }
                    
                    // Apply manual adjustment for Portrait categories and Adventurer Plate Portrait Frames/Accents based on orientation
                    var orientationShift = (portraitOrientation === 'right' ? 420 : -420) * scaleFactor;
                    
                    if (cat.key === 'category1' || cat.key === 'category2' || cat.key === 'category3') {
                        leftOffset += orientationShift;
                    }
                    
                    if (cat.key === 'category9') {
                        leftOffset += orientationShift;
                    }
                    
                    // Apply manual adjustment for Adventurer Plate Accents
                    if (cat.key === 'category11') {
                        var accentShift = (portraitOrientation === 'right' ? 676 : -676) * scaleFactor;
                        leftOffset += accentShift;
                        topOffset += 296 * scaleFactor;
                    }
                    
                    img.style.cssText = 'position: absolute; top: ' + topOffset + 'px; left: ' + leftOffset + 'px; width: ' + imgWidth + 'px; height: ' + imgHeight + 'px;';
                } else if (index === 0) {
                    // First image is the base layer (Backings) - scaled
                    var scaledWidth = currentImages[cat.key].loadedWidth * scaleFactor;
                    var scaledHeight = currentImages[cat.key].loadedHeight * scaleFactor;
                    img.style.cssText = 'display: block; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;';
                } else {
                    // All other images overlaid at top-left - scaled
                    var scaledWidth = currentImages[cat.key].loadedWidth * scaleFactor;
                    var scaledHeight = currentImages[cat.key].loadedHeight * scaleFactor;
                    img.style.cssText = 'position: absolute; top: 0; left: 0; width: ' + scaledWidth + 'px; height: ' + scaledHeight + 'px;';
                }
                
                wrapper.appendChild(img);
            }
        });
        
        imageContainer.appendChild(wrapper);
    }
    
    // Load and display image for a category
    function loadImage(filename, categoryKey) {
        getImageInfo(filename).then(function(imageInfo) {
            if (imageInfo) {
                imageInfo.filename = filename.replace('File:', '');
                
                // Store width and height information
                var img = new Image();
                img.onload = function() {
                    imageInfo.loadedWidth = this.width;
                    imageInfo.loadedHeight = this.height;
                    currentImages[categoryKey] = imageInfo;
                    updateCompositeDisplay();
                };
                img.src = imageInfo.thumburl || imageInfo.url;
            } else {
                console.error('Error loading image:', filename);
            }
        }).catch(function(error) {
            console.error('Error fetching image info:', error);
        });
    }
    
    // Populate dropdown with images
    function populateDropdown(dropdown, images, cat) {
        dropdown.innerHTML = '';
        
        if (images.length === 0) {
            var noImagesOption = document.createElement('option');
            noImagesOption.textContent = 'No images found in category';
            noImagesOption.value = '';
            dropdown.appendChild(noImagesOption);
            return;
        }
        
        var defaultOption = document.createElement('option');
        defaultOption.textContent = '-- Select an image --';
        defaultOption.value = '';
        dropdown.appendChild(defaultOption);
        
        images.forEach(function(image) {
            var option = document.createElement('option');
            option.value = image.title;
            var displayName = image.title.replace('File:', '');
            
            // Remove the suffix if present
            if (cat.suffix && displayName.endsWith(cat.suffix)) {
                displayName = displayName.substring(0, displayName.length - cat.suffix.length);
            }
            
            option.textContent = displayName;
            dropdown.appendChild(option);
        });
        
        dropdown.addEventListener('change', function() {
            if (this.value) {
                loadImage(this.value, cat.key);
                currentSelections[cat.key] = this.value;
                saveSelections(currentSelections);
                
                // Check if this is an Adventurer Plate element (categories 4-11)
                var advPlateCategories = ['category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
                if (advPlateCategories.indexOf(cat.key) !== -1) {
                    // Set preset dropdown to "Custom"
                    var presetDropdown = document.getElementById('adventurer-plate-preset-dropdown');
                    if (presetDropdown) {
                        presetDropdown.value = 'custom';
                    }
                }
                
                // Check if this is a Portrait element (categories 1-3)
                var portraitCategories = ['category1', 'category2', 'category3'];
                if (portraitCategories.indexOf(cat.key) !== -1) {
                    // Set portrait preset dropdown to "Custom"
                    var portraitPresetDropdown = document.getElementById('portrait-preset-dropdown');
                    if (portraitPresetDropdown) {
                        portraitPresetDropdown.value = 'custom';
                    }
                }
                
                // Update acquisition display for all categories (portrait and adventurer plate)
                var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
                if (allCategories.indexOf(cat.key) !== -1) {
                    updateAcquisitionDisplay();
                }
            } else {
                currentImages[cat.key] = null;
                delete currentSelections[cat.key];
                saveSelections(currentSelections);
                updateCompositeDisplay();
                
                // Update acquisition display when clearing any category
                var allCategories = ['category1', 'category2', 'category3', 'category4', 'category5', 'category6', 'category7', 'category8', 'category9', 'category10', 'category11'];
                if (allCategories.indexOf(cat.key) !== -1) {
                    updateAcquisitionDisplay();
                }
            }
        });
        
        // Restore saved selection if it exists
        if (currentSelections[cat.key]) {
            dropdown.value = currentSelections[cat.key];
            // Trigger load for saved selection
            if (dropdown.value) {
                loadImage(dropdown.value, cat.key);
            }
        }
    }
    
    // Initialize the gadget
    function init() {
        // Only show on User:Dr Agon/AdvPlateTest
        if (mw.config.get('wgPageName') !== 'User:Dr_Agon/AdvPlateTest') return;
        
        var dropdowns = createUI();
        
        // Load presets and acquisition data
        Promise.all([loadPresets(), loadAcquisitionData()]).then(function() {
            // Populate the preset dropdowns
            populatePresetDropdown(dropdowns.presetDropdown, 'adventurerPlate', dropdowns);
            populatePresetDropdown(dropdowns.portraitPresetDropdown, 'portrait', dropdowns);
            
            // Then load images for all categories
            var loadPromises = categories.map(function(cat) {
                return fetchImagesFromCategory(cat.name).then(function(images) {
                    populateDropdown(dropdowns[cat.key], images, cat);
                }).catch(function(error) {
                    console.error('Error fetching ' + cat.label + ' images:', error);
                    dropdowns[cat.key].innerHTML = '<option>Error loading images</option>';
                });
            });
            
            // After all dropdowns are populated, check for URL parameters
            Promise.all(loadPromises).then(function() {
                setTimeout(function() {
                    loadFromUrl(dropdowns);
                    updateAcquisitionDisplay(); // Update acquisition display after loading from URL
                }, 500); // Small delay to ensure dropdowns are fully populated
            });
        });
    }
    
    // Wait for MediaWiki to be ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            mw.loader.using(['mediawiki.api'], init);
        });
    } else {
        mw.loader.using(['mediawiki.api'], init);
    }
})();