Udio Prompt Saver

Enhanced preset management for Udio, with an integrated collapsible UI, prompt generator, and UI persistence, loading data from external JSON. Features 4-row preset display, search, inter-page drag-n-drop, and position editing.

// ==UserScript==
// @name         Udio Prompt Saver
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Enhanced preset management for Udio, with an integrated collapsible UI, prompt generator, and UI persistence, loading data from external JSON. Features 4-row preset display, search, inter-page drag-n-drop, and position editing.
// @author       Graph1ks with Google AI
// @match        *://www.udio.com/create*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_addElement
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @connect      lyricism.neocities.org
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const UDIO_ACCENT_COLOR = '#e30b5d';
    const UPM_UI_PREFIX = 'upm';
    const USPM_UI_PREFIX = 'uspm';

    const STRONG_AFFINITY_NATION_PROBABILITY = 0.90;
    const MAJOR_NATION_PROBABILITY = 0.85;

    const MANAGER_CONFIGS = {
        prompt: {
            id: 'prompt',
            targetInputSelector: () => { const r = document.querySelector('button[title="Randomize prompt"]'); if (r && r.nextElementSibling && r.nextElementSibling.tagName === 'TEXTAREA') return r.nextElementSibling; if (r && r.parentElement) { const t = r.parentElement.querySelectorAll('textarea'); if (t.length === 1) return t[0]; for (let a of t) if (a.placeholder && a.placeholder.toLowerCase().includes("a song about")) return a; } const p = document.querySelector('textarea[placeholder^="The autobiography of a dollar bill"], textarea[placeholder*="song about"], textarea[placeholder*="Describe Your Song"]'); if (p) return p; UdioIntegration.log("Prompt textarea not found for integrated UI."); return null; },
            applyPreset: applyReactControlledInputPreset,
            itemsPerPageIntegrated: 20,
        },
        styleReduction: {
            id: 'style-reduction',
            targetInputSelector: () => { const advControlsTrigger = Array.from(document.querySelectorAll('button, h3')).find(el => el.textContent?.trim().toLowerCase() === 'advanced controls' && el.hasAttribute('aria-controls')); if (advControlsTrigger) { const regionId = advControlsTrigger.getAttribute('aria-controls'); const region = regionId ? document.getElementById(regionId) : null; if (region && region.getAttribute('data-state') === 'open') { const srInput = region.querySelector('input[cmdk-input][placeholder*="avoid"]'); if (srInput && srInput.offsetParent !== null) return srInput; } } const globalSrInput = document.querySelector('input[cmdk-input][placeholder*="avoid"]'); if (globalSrInput && globalSrInput.offsetParent !== null) { const parentRadixOpen = globalSrInput.closest('div[role="region"][id^="radix-"][data-state="open"]'); if (parentRadixOpen) return globalSrInput; if (globalSrInput.offsetParent !== null) return globalSrInput; } return null; },
            applyPreset: applyReactControlledInputPreset,
            itemsPerPageIntegrated: 20,
        }
    };

    const DATA_SOURCE_URL = "https://lyricism.neocities.org/data/udio_prompt_data.json";
    let promptGenData = { instruments: [], vocals: [], periods: [], productions: [], emotions: [], allNations: [], majorMusicNations: [], otherMusicNations: [], metaGenreCategories: {}, periodToEraMap: {}, allEras: [], strictlyModernGenreMarkers: [], genreEraTags: {}, genreToSubgenresMap: {}, periodToHistoricalRegionsMap: {}, genreNationAffinity: {}, modernElectronicInstruments: [], definitelyOldInstruments: [], classicalEraInstruments: [], romanticEraInstruments: [], modernVocalStyles: [], genres: [] };
    let isDataLoaded = false; let dataLoadAttempted = false;
    async function loadPromptGenData() { if (isDataLoaded || dataLoadAttempted) { if(isDataLoaded && typeof postDataLoadInitialization === 'function') { UdioIntegration.log("Data already loaded or load attempted, (re)running post-data init."); postDataLoadInitialization(); } return; } dataLoadAttempted = true; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: DATA_SOURCE_URL, timeout: 15000, onload: function(response) { if (response.status >= 200 && response.status < 300) { try { const jsonData = JSON.parse(response.responseText); for (const key in jsonData) { if (jsonData.hasOwnProperty(key) && promptGenData.hasOwnProperty(key)) { promptGenData[key] = jsonData[key]; } else if (jsonData.hasOwnProperty(key)) { promptGenData[key] = jsonData[key]; } } if (promptGenData.metaGenreCategories && promptGenData.metaGenreCategories.all && Array.isArray(promptGenData.metaGenreCategories.all)) { promptGenData.genres = [...new Set(promptGenData.metaGenreCategories.all)].sort(); } else if (promptGenData.metaGenreCategories && Object.keys(promptGenData.metaGenreCategories).length > 1) { const allDerivedGenres = Object.values(promptGenData.metaGenreCategories).filter(Array.isArray).flat(); promptGenData.metaGenreCategories.all = [...new Set(allDerivedGenres)]; promptGenData.genres = [...new Set(promptGenData.metaGenreCategories.all)].sort(); } else { promptGenData.genres = []; } const allNationsLoaded = Array.isArray(promptGenData.allNations) ? promptGenData.allNations : []; if (promptGenData.periodToHistoricalRegionsMap) { for (const era in promptGenData.periodToHistoricalRegionsMap) { if (Array.isArray(promptGenData.periodToHistoricalRegionsMap[era]) && promptGenData.periodToHistoricalRegionsMap[era].length === 0 && ['early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad', 'any_broad_modern'].includes(era)) { promptGenData.periodToHistoricalRegionsMap[era] = [...allNationsLoaded]; } } } if (promptGenData.genreNationAffinity) { for (const genre in promptGenData.genreNationAffinity) { if (Array.isArray(promptGenData.genreNationAffinity[genre]) && promptGenData.genreNationAffinity[genre].length === 0 && ["Rock", "Metal", "Pop", "Electronic", "Punk", "Folk", "Experimental", "Ambient", "Noise", "World Music"].includes(genre)) { promptGenData.genreNationAffinity[genre] = [...allNationsLoaded]; } } } isDataLoaded = true; if (typeof postDataLoadInitialization === 'function') postDataLoadInitialization(); resolve(); } catch (e) { dataLoadFailedFallback(); reject(e); } } else { dataLoadFailedFallback(); reject(new Error(`Failed to fetch data: ${response.status}`)); } }, onerror: function(error) { dataLoadFailedFallback(); reject(error); }, ontimeout: function() { dataLoadFailedFallback(); reject(new Error("Data fetch timeout")); } }); }); }
    function dataLoadFailedFallback() { Object.keys(promptGenData).forEach(key => { if (Array.isArray(promptGenData[key])) promptGenData[key] = []; else if (typeof promptGenData[key] === 'object' && promptGenData[key] !== null && key !== 'metaGenreCategories') promptGenData[key] = {}; else if (key === 'metaGenreCategories') promptGenData.metaGenreCategories = {all: []}; }); promptGenData.genres = []; isDataLoaded = true; if (typeof postDataLoadInitialization === 'function') postDataLoadInitialization(); }
    function getRandomElement(arr, count = 1) { if (!arr || arr.length === 0) { return count === 1 ? '' : []; } if (count === 1) { return arr[Math.floor(Math.random() * arr.length)]; } const shuffled = [...arr].sort(() => 0.5 - Math.random()); return shuffled.slice(0, Math.min(count, arr.length)); }
    function getRandomElementExcluding(arr, exclude) { if (!arr || arr.length === 0) return ''; const filteredArr = arr.filter(item => item !== exclude); if (filteredArr.length === 0 && arr.length > 0) return getRandomElement(arr); if (filteredArr.length === 0 && arr.length === 0) return ''; return getRandomElement(filteredArr); }
    function getEraForPeriod(period) { return promptGenData.periodToEraMap[period] || 'contemporary'; }
    function isGenreStrictlyModern(genreName) { if (!genreName) return false; const lowerGenre = genreName.toLowerCase(); if (promptGenData.strictlyModernGenreMarkers && promptGenData.strictlyModernGenreMarkers.some(marker => lowerGenre.includes(marker.toLowerCase()))) { return true; } if ( (promptGenData.metaGenreCategories.Electronic && promptGenData.metaGenreCategories.Electronic.includes(genreName)) || (promptGenData.metaGenreCategories.HipHop && promptGenData.metaGenreCategories.HipHop.includes(genreName)) ) { if ( !genreName.toLowerCase().includes("jazz") && !genreName.toLowerCase().includes("swing") && !genreName.toLowerCase().includes("folk") && !genreName.toLowerCase().includes("classical") && !genreName.toLowerCase().includes("ambient") && !genreName.toLowerCase().includes("lounge") && !genreName.toLowerCase().includes("experimental") && !genreName.toLowerCase().includes("disco") ) { return true; } } return false; }
    function getTypicalErasForGenre(genreName) { if (promptGenData.genreEraTags[genreName]) { const specificEras = promptGenData.genreEraTags[genreName].filter(era => era !== 'any_broad' && era !== 'any_broad_modern'); if (specificEras.length > 0) return specificEras; return promptGenData.genreEraTags[genreName]; } if (isGenreStrictlyModern(genreName)) return ['contemporary', 'late20th', 'future']; const modernDefaults = ['contemporary', 'late20th', 'mid20th', 'early20th']; const historicalDefaults = ['romantic', 'classical', 'baroque', 'renaissance', 'medieval']; if ( (promptGenData.metaGenreCategories.Electronic && promptGenData.metaGenreCategories.Electronic.includes(genreName)) || (promptGenData.metaGenreCategories.HipHop && promptGenData.metaGenreCategories.HipHop.includes(genreName)) || (promptGenData.metaGenreCategories.Pop && promptGenData.metaGenreCategories.Pop.includes(genreName)) || (promptGenData.metaGenreCategories.Metal && promptGenData.metaGenreCategories.Metal.includes(genreName)) || (promptGenData.metaGenreCategories.Punk && promptGenData.metaGenreCategories.Punk.includes(genreName)) ) { return modernDefaults.slice(0,2); } if ( (promptGenData.metaGenreCategories.Rock && promptGenData.metaGenreCategories.Rock.includes(genreName)) || (promptGenData.metaGenreCategories.RnBSoulFunk && promptGenData.metaGenreCategories.RnBSoulFunk.includes(genreName)) || (promptGenData.metaGenreCategories.Jazz && promptGenData.metaGenreCategories.Jazz.includes(genreName)) ) { return modernDefaults; } if ( (promptGenData.metaGenreCategories.ClassicalMusic && promptGenData.metaGenreCategories.ClassicalMusic.includes(genreName)) || genreName.toLowerCase().includes('baroque') || genreName.toLowerCase().includes('classical') || genreName.toLowerCase().includes('romantic') || genreName.toLowerCase().includes('renaissance') || genreName.toLowerCase().includes('medieval')) { if (genreName.toLowerCase().includes('medieval')) return ['medieval']; if (genreName.toLowerCase().includes('renaissance')) return ['renaissance']; if (genreName.toLowerCase().includes('baroque')) return ['baroque']; if (genreName.toLowerCase().includes('classical') && !genreName.toLowerCase().includes('modern') && !genreName.toLowerCase().includes('neo')) return ['classical', 'romantic']; return historicalDefaults; } if ( (promptGenData.metaGenreCategories.Folk && promptGenData.metaGenreCategories.Folk.includes(genreName)) || (promptGenData.metaGenreCategories.BluesCountry && promptGenData.metaGenreCategories.BluesCountry.includes(genreName)) || (promptGenData.metaGenreCategories.WorldLatinReggae && promptGenData.metaGenreCategories.WorldLatinReggae.includes(genreName)) ) { return ['any_broad', ...modernDefaults]; } return ['contemporary', 'late20th']; }
    function selectBestEraFromList(typicalEras, isStrictlyModern, genreName = "") { if (!typicalEras || typicalEras.length === 0) return 'contemporary'; let possibleEras = [...typicalEras]; if (isStrictlyModern) { possibleEras = possibleEras.filter(era => ['contemporary', 'late20th', 'mid20th', 'future', 'any_broad_modern'].includes(era)); if (genreName.toLowerCase().includes("grime") || genreName.toLowerCase().includes("drill") || genreName.toLowerCase().includes("phonk") || genreName.toLowerCase().includes("hyperpop") || genreName.toLowerCase().includes("dubstep") || genreName.toLowerCase().includes("future bass") || genreName.toLowerCase().includes("future garage") || genreName.toLowerCase().includes("future house") ) { possibleEras = possibleEras.filter(era => ['contemporary', 'future'].includes(era)); } else if (genreName.toLowerCase().includes("trap") || genreName.toLowerCase().includes("edm") || genreName.toLowerCase().includes("synthwave") || genreName.toLowerCase().includes("vaporwave") || genreName.toLowerCase().includes("lo-fi hip hop")) { possibleEras = possibleEras.filter(era => ['contemporary', 'late20th', 'future'].includes(era)); } if (possibleEras.length === 0) return 'contemporary'; } else { const historicalSpecific = possibleEras.filter(era => ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)); if (historicalSpecific.length > 0) { return getRandomElement(historicalSpecific); } } if (possibleEras.includes('any_broad_modern') && !isStrictlyModern) { const modernSpecific = possibleEras.filter(era => ['contemporary', 'late20th', 'mid20th', 'early20th'].includes(era)); if(modernSpecific.length > 0) return getRandomElement(modernSpecific); } if (possibleEras.includes('any_broad')) { const nonBroad = possibleEras.filter(era => era !== 'any_broad' && era !== 'any_broad_modern'); if (nonBroad.length > 0) return getRandomElement(nonBroad); return getRandomElement(['contemporary', 'late20th', 'mid20th']); } if (possibleEras.length > 0) { if (possibleEras.includes('contemporary')) return 'contemporary'; if (possibleEras.includes('future')) return 'future'; if (possibleEras.includes('late20th')) return 'late20th'; if (possibleEras.includes('mid20th')) return 'mid20th'; if (possibleEras.includes('early20th')) return 'early20th'; return getRandomElement(possibleEras); } return 'contemporary'; }
    function getSensibleDisplayPeriodForEra(effectiveEra, primaryGenreContext = "") { let suitablePeriods = []; const historicalEras = ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic']; const modernEras = ['early20th', 'mid20th', 'late20th', 'contemporary', 'future']; if (historicalEras.includes(effectiveEra)) { suitablePeriods = promptGenData.periods.filter(p => { const pEra = getEraForPeriod(p); return pEra === effectiveEra && (isNaN(parseInt(p.substring(0, 4))) || p.toLowerCase().includes("century") || p.toLowerCase().includes("age") || p.toLowerCase().includes("period") || p.toLowerCase() === effectiveEra || p.toLowerCase() === effectiveEra + " era" || p.toLowerCase() === effectiveEra + " period"); }); if (primaryGenreContext && getEraForPeriod(primaryGenreContext) === effectiveEra && promptGenData.periods.includes(primaryGenreContext)) { return primaryGenreContext; } if (suitablePeriods.length === 0) { suitablePeriods = promptGenData.periods.filter(p => getEraForPeriod(p) === effectiveEra); } if (suitablePeriods.length === 0) { const eraName = effectiveEra.charAt(0).toUpperCase() + effectiveEra.slice(1); if (promptGenData.periods.includes(eraName)) return eraName; return eraName + " Era"; } } else if (modernEras.includes(effectiveEra)) { suitablePeriods = promptGenData.periods.filter(p => { const pEra = getEraForPeriod(p); return pEra === effectiveEra && /^\d{4}s$/.test(p); }); if (suitablePeriods.length === 0 && (effectiveEra === 'contemporary' || effectiveEra === 'future')) { const directTerm = promptGenData.periods.find(p => p.toLowerCase() === effectiveEra.toLowerCase()); if (directTerm) suitablePeriods.push(directTerm); } if (suitablePeriods.length === 0) { let fallbackDecade = "2020s"; if (effectiveEra === 'late20th') fallbackDecade = getRandomElement(["1980s", "1990s"]); else if (effectiveEra === 'mid20th') fallbackDecade = getRandomElement(["1950s", "1960s", "1970s"]); else if (effectiveEra === 'early20th') fallbackDecade = getRandomElement(["1900s", "1910s", "1920s", "1930s", "1940s"]); else if (effectiveEra === 'contemporary') fallbackDecade = getRandomElement(["2000s", "2010s", "2020s", "Contemporary"]); else if (effectiveEra === 'future') fallbackDecade = getRandomElement(["2030s", "2040s", "Future"]); if (promptGenData.periods.includes(fallbackDecade)) suitablePeriods.push(fallbackDecade); else return fallbackDecade; } } else { suitablePeriods = promptGenData.periods.filter(p => /^\d{4}s$/.test(p) || p === "Contemporary" || p === "Future"); if (suitablePeriods.length === 0) suitablePeriods = ["2020s", "1990s", "1970s"]; } if (suitablePeriods.length > 0) { return getRandomElement(suitablePeriods); } return effectiveEra === 'contemporary' ? "Contemporary" : (effectiveEra.endsWith("s") ? effectiveEra : "2020s"); }
    function isGenreCompatibleWithEra(genreName, era) { const genreIsStrictlyModern = isGenreStrictlyModern(genreName); if (genreIsStrictlyModern && ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) { return false; } const tags = getTypicalErasForGenre(genreName); if (tags) { if (tags.includes(era)) return true; if (tags.includes("any_broad")) return true; if (tags.includes("any_broad_modern") && !['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) return true; return false; } if (['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) { return !genreIsStrictlyModern; } return true; }
    function getGenresForEra(era, selectedGenreCategory = "all", strictCategoryNoFallbackToAll = false) { let baseGenresSource; if (selectedGenreCategory !== "all" && promptGenData.metaGenreCategories[selectedGenreCategory] && promptGenData.metaGenreCategories[selectedGenreCategory].length > 0) { baseGenresSource = promptGenData.metaGenreCategories[selectedGenreCategory]; } else { baseGenresSource = promptGenData.genres; } let suitableGenres = baseGenresSource.filter(genre => { const genreIsStrictlyModern = isGenreStrictlyModern(genre); if (genreIsStrictlyModern && ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) { return false; } const tags = getTypicalErasForGenre(genre); if (tags) { if (tags.includes(era)) return true; if (tags.includes("any_broad")) return true; if (tags.includes("any_broad_modern") && !['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) return true; return false; } if (['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic'].includes(era)) { return false; } return true; }); if (suitableGenres.length === 0) { if (strictCategoryNoFallbackToAll && selectedGenreCategory !== "all") { return []; } if (selectedGenreCategory !== "all") { const allGenresForEra = getGenresForEra(era, "all", true); if (allGenresForEra.length > 0) return allGenresForEra; } if (['contemporary', 'late20th', 'mid20th', 'early20th', 'future', 'any_broad_modern'].includes(era)) { const fallbackSource = (promptGenData.metaGenreCategories[selectedGenreCategory] && promptGenData.metaGenreCategories[selectedGenreCategory].length > 0) ? promptGenData.metaGenreCategories[selectedGenreCategory] : promptGenData.genres; const modernEras = ['contemporary', 'late20th', 'mid20th', 'early20th', 'future', 'any_broad_modern']; return fallbackSource.filter(g => !isGenreStrictlyModern(g) || modernEras.includes(era)); } return []; } return suitableGenres; }
    function getAffinityNationsForPromptGen(primaryGenre, currentEra) { let genreSpecificAffinities = []; if (primaryGenre && promptGenData.genreNationAffinity && promptGenData.genreNationAffinity[primaryGenre]) { genreSpecificAffinities = promptGenData.genreNationAffinity[primaryGenre].filter(n => typeof n === 'string' && (promptGenData.allNations.includes(n) || (promptGenData.periodToHistoricalRegionsMap[currentEra] && promptGenData.periodToHistoricalRegionsMap[currentEra].includes(n)) )); } else if (primaryGenre && promptGenData.genreNationAffinity) { const genreLower = primaryGenre.toLowerCase(); for (const key in promptGenData.genreNationAffinity) { if (genreLower.includes(key.toLowerCase()) && promptGenData.genreNationAffinity[key] && promptGenData.genreNationAffinity[key].length > 0) { genreSpecificAffinities = promptGenData.genreNationAffinity[key].filter(n => typeof n === 'string' && (promptGenData.allNations.includes(n) || (promptGenData.periodToHistoricalRegionsMap[currentEra] && promptGenData.periodToHistoricalRegionsMap[currentEra].includes(n)) )); if (genreSpecificAffinities.length > 0) break; } } } let historicalContextRegionsOrNations = (promptGenData.periodToHistoricalRegionsMap && promptGenData.periodToHistoricalRegionsMap[currentEra]) || promptGenData.allNations; let nationPool = []; if (genreSpecificAffinities.length > 0 && Math.random() < STRONG_AFFINITY_NATION_PROBABILITY) { const affinityInContext = genreSpecificAffinities.filter(n => { if (historicalContextRegionsOrNations === promptGenData.allNations) return true; return historicalContextRegionsOrNations.includes(n) || promptGenData.allNations.includes(n); }); if (affinityInContext.length > 0) { nationPool = affinityInContext; } } if (nationPool.length === 0) { if (genreSpecificAffinities.length > 0) { const affinityInContext = genreSpecificAffinities.filter(n => { if (historicalContextRegionsOrNations === promptGenData.allNations) return true; return historicalContextRegionsOrNations.includes(n) || promptGenData.allNations.includes(n); }); if (affinityInContext.length > 0) nationPool = affinityInContext; else nationPool = historicalContextRegionsOrNations; } else { nationPool = historicalContextRegionsOrNations; } } let potentialModernNations = []; if (!['ancient', 'medieval', 'renaissance'].includes(currentEra) || nationPool.every(n => promptGenData.allNations.includes(n))) { potentialModernNations = nationPool.map(regionOrNation => { if (promptGenData.allNations.includes(regionOrNation)) return regionOrNation; for (const modernNation of promptGenData.allNations) { if (regionOrNation.toLowerCase().includes(modernNation.toLowerCase())) return modernNation; } return null; }).filter(n => n !== null); } if (potentialModernNations.length > 0) { nationPool = [...new Set(potentialModernNations)]; } else if (nationPool.some(n => !promptGenData.allNations.includes(n)) && ['ancient', 'medieval', 'renaissance', 'baroque'].includes(currentEra)) { const historicalOnly = nationPool.filter(n => !promptGenData.allNations.includes(n)); if (historicalOnly.length > 0) return getRandomElement(historicalOnly); } if (nationPool.length === 0 || nationPool.every(n => !promptGenData.allNations.includes(n) && !['ancient', 'medieval', 'renaissance', 'baroque'].includes(currentEra) )) { nationPool = [...promptGenData.allNations]; } else { const modernNationsInPool = nationPool.filter(n => promptGenData.allNations.includes(n)); if (modernNationsInPool.length > 0) { nationPool = modernNationsInPool; } else if (!['ancient', 'medieval', 'renaissance', 'baroque'].includes(currentEra)) { nationPool = [...promptGenData.allNations]; } else if (nationPool.filter(n => !promptGenData.allNations.includes(n)).length > 0) { return getRandomElement(nationPool.filter(n => !promptGenData.allNations.includes(n))); } else { nationPool = [...promptGenData.allNations]; } } if (['mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern', 'early20th'].includes(currentEra) && nationPool.filter(n => promptGenData.allNations.includes(n)).length > 3) { let modernNationChoices = nationPool.filter(n => promptGenData.allNations.includes(n)); let majorNationsInPool = modernNationChoices.filter(n => promptGenData.majorMusicNations.includes(n)); let otherNationsInPool = modernNationChoices.filter(n => promptGenData.otherMusicNations.includes(n) && !promptGenData.majorMusicNations.includes(n)); if (Math.random() < MAJOR_NATION_PROBABILITY && majorNationsInPool.length > 0) { nationPool = majorNationsInPool; } else if (otherNationsInPool.length > 0) { nationPool = otherNationsInPool; } else if (majorNationsInPool.length > 0) { nationPool = majorNationsInPool; } else { nationPool = modernNationChoices; } } if (nationPool.length === 0) return getRandomElement(promptGenData.allNations); return getRandomElement([...new Set(nationPool.filter(n => typeof n === 'string' && n.length > 0))]); }
    function isInstrumentCompatibleWithPeriod(instrument, periodOrEraString) { let era; if (promptGenData.allEras.includes(periodOrEraString)) { era = periodOrEraString; } else if (promptGenData.periodToEraMap[periodOrEraString]) { era = getEraForPeriod(periodOrEraString); } else { era = 'contemporary'; if (typeof periodOrEraString === 'string' && periodOrEraString.endsWith('s') && !isNaN(parseInt(periodOrEraString.substring(0,4)))) { const year = parseInt(periodOrEraString.substring(0,4)); if (year >= 2030) era = 'future'; else if (year >= 2000) era = 'contemporary'; else if (year >= 1980) era = 'late20th'; else if (year >= 1950) era = 'mid20th'; else if (year >= 1900) era = 'early20th'; else if (year >= 1800) era = 'romantic'; else if (year >= 1700) era = 'classical'; else if (year >= 1600) era = 'baroque'; } } const instLower = instrument.toLowerCase(); const historicalEras = ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic']; const veryOldEras = ['ancient', 'medieval', 'renaissance']; if (promptGenData.modernElectronicInstruments.some(modInst => instLower.includes(modInst.toLowerCase()))) { if (instLower.includes('theremin') || instLower.includes('ondes martenot')) return ['early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (instLower.includes('hammond organ') || (instLower.includes('electric organ') && !instLower.includes('portative') && !instLower.includes('reed'))) return ['early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (instLower.includes('mellotron')) return ['mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (['synthesizer', 'sampler', 'drum machine', 'sequencer', 'vocoder', 'arpeggiator', 'modular', 'analog synth', 'digital synth', 'fm synth', 'wavetable', 'granular synth', 'chiptune', 'circuit bent', 'glitch sounds', 'moog'].some(s => instLower.includes(s))) { if (era === 'mid20th' && (instLower.includes('synthesizer') || instLower.includes('sampler') || instLower.includes('drum machine'))) return true; return ['late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); } if (['808', '909', 'linndrum', 'sp-1200', 'synth pad', 'synth lead', 'synth bass', 'sub bass', 'synth strings', 'synth brass'].some(s => instLower.includes(s))) { return ['late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); } if (instLower.includes('turntables') || instLower.includes('talk box')) return ['late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (instLower.includes('laser harp') || instLower.includes('stylophone')) return ['late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); return false; } if (promptGenData.definitelyOldInstruments.some(oldInst => instLower.includes(oldInst.toLowerCase()))) { if (instLower.includes('harpsichord') || instLower.includes('clavichord')) return ['renaissance', 'baroque', 'classical'].includes(era); if (instLower.includes('lute') || instLower.includes('theorbo') || instLower.includes('archlute')) return ['renaissance', 'baroque'].includes(era); if (instLower.includes('viola da gamba')) return ['renaissance', 'baroque', 'classical'].includes(era); if (instLower.includes('recorder') && !instLower.includes("alto") && !instLower.includes("bass")) return !['contemporary', 'future', 'late20th', 'mid20th'].includes(era); return veryOldEras.includes(era) || era === 'baroque'; } if (instLower.includes('electric guitar') || instLower.includes('electric bass guitar') || (instLower.includes('bass guitar') && !instLower.includes('acoustic'))) { return ['mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); } if (instLower.includes('drum kit')) return ['early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (instLower.includes('piano') && !instLower.includes('electric') && !instLower.includes('thumb') && !instLower.includes('upright')) { return ['classical', 'romantic', 'early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad', 'any_broad_modern'].includes(era) && era !== 'baroque' && era !== 'renaissance' && era !== 'medieval' && era !== 'ancient'; } if (instLower.includes('upright piano')) return ['romantic', 'early20th', 'mid20th', 'late20th', 'contemporary', 'future', 'any_broad', 'any_broad_modern'].includes(era); if (instLower.includes('electric piano')) return ['mid20th', 'late20th', 'contemporary', 'future', 'any_broad_modern'].includes(era); if (instLower.includes("clarinet") && !instLower.includes('alto') && !instLower.includes('bass')) return !['ancient', 'medieval', 'renaissance', 'baroque'].includes(era); if (instLower.includes("saxophone")) return !['ancient', 'medieval', 'renaissance', 'baroque', 'classical'].includes(era); if (instLower.includes("tuba")) return !['ancient', 'medieval', 'renaissance', 'baroque', 'classical'].includes(era); if (instLower.includes("violin") || instLower.includes("viola") || instLower.includes("cello") || instLower.includes("double bass") && !instLower.includes("electric")) { return !['ancient', 'medieval', 'renaissance'].includes(era); } if (instLower.includes("flute") && !instLower.includes("alto") && !instLower.includes("bass") && !instLower.includes("pan") && !instLower.includes("nose") && !instLower.includes("recorder")) { return !['ancient', 'medieval'].includes(era); } if (era === 'any_broad' || era === 'any_broad_modern') return true; const commonFolkInstruments = ["acoustic guitar", "fiddle", "banjo", "mandolin", "accordion", "harmonica", "bodhrán", "whistle", "bouzouki", "oud", "sitar", "tabla", "didgeridoo", "bagpipes", "dombra"]; if (commonFolkInstruments.some(folkInst => instLower.includes(folkInst))) { if (instLower.includes("banjo") || instLower.includes("accordion") || instLower.includes("harmonica") || instLower.includes("concertina")) { return !['ancient', 'medieval', 'renaissance', 'baroque', 'classical'].includes(era); } return !['ancient'].includes(era) || instLower.includes("oud") || instLower.includes("harp") || instLower.includes("lyre") || instLower.includes("frame drum"); } if (historicalEras.includes(era)) { const allowedForHistorical = ["vocals", "percussion", "hand drum", "frame drum", "tambourine", "harp", "lyre", "oud", "bells", "chimes", "cymbals", "horns", "bullroarer", "pipe organ", "classical guitar", "acoustic guitar"]; if (allowedForHistorical.some(histInst => instLower.includes(histInst))) return true; return false; } return true; }
    function getCompatibleInstruments(periodOrEraString) { let era; if (promptGenData.allEras.includes(periodOrEraString)) { era = periodOrEraString; } else if (promptGenData.periodToEraMap[periodOrEraString]) { era = getEraForPeriod(periodOrEraString); } else { const year = parseInt(String(periodOrEraString).substring(0,4)); if (!isNaN(year)) { if (year >= 2030) era = 'future'; else if (year >= 2000) era = 'contemporary'; else if (year >= 1980) era = 'late20th'; else if (year >= 1950) era = 'mid20th'; else if (year >= 1900) era = 'early20th'; else if (year >= 1800) era = 'romantic'; else if (year >= 1700) era = 'classical'; else if (year >= 1600) era = 'baroque'; else era = 'contemporary'; } else { era = 'contemporary'; } } const filtered = promptGenData.instruments.filter(inst => isInstrumentCompatibleWithPeriod(inst, era)); if (filtered.length === 0) { const genericModern = ["Acoustic Guitar", "Piano", "Bass Guitar", "Vocals", "Keyboard", "String Section"]; const genericHistorical = ["Vocals", "Percussion", "Flute", "String Section", "Harp", "Lute"]; if (['contemporary', 'late20th', 'mid20th', 'early20th', 'future'].includes(era)) { return genericModern.filter(inst => promptGenData.instruments.includes(inst) && isInstrumentCompatibleWithPeriod(inst, era)); } else if (['baroque', 'classical', 'romantic'].includes(era)) { return genericHistorical.filter(inst => promptGenData.instruments.includes(inst) && isInstrumentCompatibleWithPeriod(inst, era)); } return ["Vocals", "Acoustic Guitar", "Piano"].filter(i => promptGenData.instruments.includes(i)); } return filtered; }
    function getInstrumentsForGenre(primaryGenre, periodCompatibleInstruments, effectiveEra) { let affinity = promptGenData.genreInstrumentAffinity[primaryGenre]; if (!affinity) { const genreLower = primaryGenre.toLowerCase(); if (genreLower.includes("hiphop") || genreLower.includes("hip hop")) affinity = promptGenData.genreInstrumentAffinity["HipHop"]; else if (genreLower.includes("metal")) affinity = promptGenData.genreInstrumentAffinity["Metal"]; else if (genreLower.includes("punk")) affinity = promptGenData.genreInstrumentAffinity["Punk"]; else if (genreLower.includes("edm") || (genreLower.includes("electronic") && primaryGenre === "EDM")) affinity = promptGenData.genreInstrumentAffinity["EDM"]; else if (genreLower.includes("house")) affinity = promptGenData.genreInstrumentAffinity["House"]; else if (genreLower.includes("techno")) affinity = promptGenData.genreInstrumentAffinity["Techno"]; else if (genreLower.includes("rock")) affinity = promptGenData.genreInstrumentAffinity["Rock"]; else if (genreLower.includes("jazz")) affinity = promptGenData.genreInstrumentAffinity["Jazz"]; else if (genreLower.includes("electronic") || genreLower.includes("synth")) affinity = promptGenData.genreInstrumentAffinity["Electronic"]; else if (genreLower.includes("folk")) affinity = promptGenData.genreInstrumentAffinity["Folk"]; else if (genreLower.includes("blues")) affinity = promptGenData.genreInstrumentAffinity["Blues"]; else if (genreLower.includes("country")) affinity = promptGenData.genreInstrumentAffinity["Country"]; else if (genreLower.includes("pop")) affinity = promptGenData.genreInstrumentAffinity["Pop"]; else if (primaryGenre === "Baroque" || effectiveEra === 'baroque') affinity = promptGenData.genreInstrumentAffinity["Baroque"]; else if (primaryGenre === "Renaissance" || effectiveEra === 'renaissance') affinity = promptGenData.genreInstrumentAffinity["Renaissance"]; else if (primaryGenre === "Medieval" || effectiveEra === 'medieval') affinity = promptGenData.genreInstrumentAffinity["Medieval"]; else if (genreLower.includes("classical") || ['classical', 'romantic'].includes(effectiveEra)) affinity = promptGenData.genreInstrumentAffinity["ClassicalMusic"]; else if (genreLower.includes("ambient")) affinity = promptGenData.genreInstrumentAffinity["Ambient"]; } let finalInstrumentList = [...periodCompatibleInstruments]; if (affinity) { if (affinity.exclude && affinity.exclude.length > 0) { const excludedLower = affinity.exclude.map(ex => ex.toLowerCase()); finalInstrumentList = finalInstrumentList.filter(inst => { const instLower = inst.toLowerCase(); if (affinity.core && affinity.core.map(c => c.toLowerCase()).includes(instLower)) return true; return !excludedLower.some(ex => instLower.includes(ex) || ex.includes(instLower)); }); } if (affinity.core && affinity.core.length > 0) { const compatibleCore = affinity.core.filter(coreInst => periodCompatibleInstruments.includes(coreInst) && isInstrumentCompatibleWithPeriod(coreInst, effectiveEra)); if (compatibleCore.length > 0) { const nonCoreInList = finalInstrumentList.filter(inst => !compatibleCore.includes(inst)); let biasedPool = []; for(let i=0; i<3; i++) biasedPool.push(...compatibleCore); biasedPool.push(...getRandomElement(nonCoreInList, Math.min(nonCoreInList.length, 2))); if (biasedPool.length > 0) { finalInstrumentList = [...new Set(biasedPool)]; } else { finalInstrumentList = compatibleCore; } if (finalInstrumentList.length < 2 && periodCompatibleInstruments.length > finalInstrumentList.length) { finalInstrumentList.push(...getRandomElement(periodCompatibleInstruments.filter(i => !finalInstrumentList.includes(i)), 2 - finalInstrumentList.length)); finalInstrumentList = [...new Set(finalInstrumentList)]; } } } } if (finalInstrumentList.length === 0 && periodCompatibleInstruments.length > 0) { finalInstrumentList = [...periodCompatibleInstruments]; } if (finalInstrumentList.length === 0) { return getCompatibleInstruments(effectiveEra); } return [...new Set(finalInstrumentList)]; }
    function getCompatibleVocals(periodOrEraString) { let era; if (promptGenData.allEras.includes(periodOrEraString)) { era = periodOrEraString; } else if (promptGenData.periodToEraMap[periodOrEraString]) { era = getEraForPeriod(periodOrEraString); } else { const year = parseInt(String(periodOrEraString).substring(0,4)); if (!isNaN(year)) { if (year >= 2030) era = 'future'; else if (year >= 2000) era = 'contemporary'; else if (year >= 1980) era = 'late20th'; else if (year >= 1950) era = 'mid20th'; else if (year >= 1900) era = 'early20th'; else era = 'contemporary'; } else { era = 'contemporary'; } } let baseVocals = [...promptGenData.vocals]; const historicalEras = ['ancient', 'medieval', 'renaissance', 'baroque', 'classical', 'romantic']; if (historicalEras.includes(era)) { const allowedHistoricalTokens = ["chant", "operatic", "belting", "legato", "melismatic", "harmonic", "choir", "folk", "story", "liturg", "clear", "pure", "resonant", "head-voice", "falsetto", "yodel", "keening", "plainchant", "ballad", "madrigal", "aria", "recitative", "lieder", "sacred", "ethereal", "angelic", "seraphic"]; baseVocals = promptGenData.vocals.filter(vocalStyle => { const vocalLower = vocalStyle.toLowerCase(); if (promptGenData.modernVocalStyles.some(modStyle => vocalLower.includes(modStyle.toLowerCase()))) return false; if (["rap", "electronic", "distorted", "robotic", "glitch", "hyperpop", "beatbox", "industrial", "cyber", "metal scream", "growl", "shriek", "snarl", "autotune", "vocal fry"].some( disallowedToken => vocalLower.includes(disallowedToken))) return false; if (allowedHistoricalTokens.some(histToken => vocalLower.includes(histToken))) return true; const generallySafe = !vocalLower.includes("aggressive") && !vocalLower.includes("edgy") && !vocalLower.includes("raw") && !vocalLower.includes("gritty"); return generallySafe; }); if (baseVocals.length === 0) baseVocals = ["Melodic", "Chanted", "Operatic", "Clear", "Full-bodied", "Pure", "Angelic"]; } else if (era === 'early20th') { baseVocals = promptGenData.vocals.filter(vocalStyle => { const vocalLower = vocalStyle.toLowerCase(); return !["Autotuned", "Hyperpop", "Glitch Sounds", "Full Growl/Scream (as primary)", "Mumbled Rap", "Robotic", "Heavy Distortion"].some(modStyle => vocalLower.includes(modStyle.toLowerCase())); }); } return baseVocals.length > 0 ? baseVocals : [...promptGenData.vocals]; }
    function generateUdioStructuredPrompt(promptGenMode, selectedGenreCategoryFromUI) { const mode = promptGenMode; const isInstrumentalOnly = mode.includes('instrumental'); const isCoherent = mode.includes('coherent'); const selectedGenreCategory = selectedGenreCategoryFromUI || "all"; let promptSegments = []; let primaryGenre = null; let effectiveEra = 'contemporary'; let finalSelectedPeriodForDisplay = 'Contemporary'; let genrePoolForPrimary = promptGenData.genres; let genrePoolForSecondary = promptGenData.genres; if (isCoherent) { if (selectedGenreCategory !== "all" && promptGenData.metaGenreCategories[selectedGenreCategory]) { genrePoolForPrimary = promptGenData.metaGenreCategories[selectedGenreCategory]; primaryGenre = getRandomElement(genrePoolForPrimary); if (!primaryGenre && promptGenData.genres.length > 0) primaryGenre = getRandomElement(promptGenData.genres); const typicalErasForPrimary = getTypicalErasForGenre(primaryGenre); effectiveEra = selectBestEraFromList(typicalErasForPrimary, isGenreStrictlyModern(primaryGenre), primaryGenre); } else { const tempEraChoices = ['contemporary', 'late20th', 'mid20th', 'early20th', 'romantic', 'classical', 'baroque', 'any_broad']; const tempInitialEra = getRandomElement(tempEraChoices); genrePoolForPrimary = getGenresForEra(tempInitialEra, "all"); if (!genrePoolForPrimary || genrePoolForPrimary.length === 0) { genrePoolForPrimary = promptGenData.genres; } primaryGenre = getRandomElement(genrePoolForPrimary); if (!primaryGenre && promptGenData.genres.length > 0) primaryGenre = getRandomElement(promptGenData.genres); const typicalErasForPrimary = getTypicalErasForGenre(primaryGenre); effectiveEra = selectBestEraFromList(typicalErasForPrimary, isGenreStrictlyModern(primaryGenre), primaryGenre); } if (!primaryGenre && promptGenData.genres.length > 0) primaryGenre = getRandomElement(promptGenData.genres); if (!primaryGenre) primaryGenre = "Music"; finalSelectedPeriodForDisplay = getSensibleDisplayPeriodForEra(effectiveEra, primaryGenre); genrePoolForSecondary = getGenresForEra(effectiveEra, selectedGenreCategory, true); if (!genrePoolForSecondary || genrePoolForSecondary.length === 0) { genrePoolForSecondary = getGenresForEra(effectiveEra, selectedGenreCategory, false); if (!genrePoolForSecondary || genrePoolForSecondary.length === 0) { genrePoolForSecondary = getGenresForEra(effectiveEra, "all"); if (!genrePoolForSecondary || genrePoolForSecondary.length === 0) genrePoolForSecondary = [primaryGenre]; } } } else { finalSelectedPeriodForDisplay = getRandomElement(promptGenData.periods); effectiveEra = getEraForPeriod(finalSelectedPeriodForDisplay); genrePoolForPrimary = (selectedGenreCategory !== "all" && promptGenData.metaGenreCategories[selectedGenreCategory]) ? promptGenData.metaGenreCategories[selectedGenreCategory] : promptGenData.genres; if (!genrePoolForPrimary || genrePoolForPrimary.length === 0) genrePoolForPrimary = promptGenData.genres; primaryGenre = getRandomElement(genrePoolForPrimary); if (!primaryGenre && promptGenData.genres.length > 0) primaryGenre = getRandomElement(promptGenData.genres); if (!primaryGenre) primaryGenre = "Music"; genrePoolForSecondary = genrePoolForPrimary; } let genreTermsForPrompt = [primaryGenre]; let numGenres = 1; if (Math.random() < 0.4 && genrePoolForSecondary.length > 1) { let genre2 = ""; if (isCoherent && promptGenData.genreToSubgenresMap[primaryGenre]) { const potentialSubgenres = promptGenData.genreToSubgenresMap[primaryGenre].filter(sg => genrePoolForSecondary.includes(sg) && sg !== primaryGenre && isGenreCompatibleWithEra(sg, effectiveEra) ); if (potentialSubgenres.length > 0) genre2 = getRandomElement(potentialSubgenres); } if (!genre2) { const filteredSecondaryPool = genrePoolForSecondary.filter(sg => sg !== primaryGenre && (isCoherent ? isGenreCompatibleWithEra(sg, effectiveEra) : true) ); if(filteredSecondaryPool.length > 0) genre2 = getRandomElement(filteredSecondaryPool); } if (genre2 && genre2 !== primaryGenre) { genreTermsForPrompt.push(genre2); numGenres = 2; } } let productionAndGenreSegment = ""; const prod1 = getRandomElement(promptGenData.productions); if (numGenres === 1) { productionAndGenreSegment = `${prod1} ${genreTermsForPrompt[0]}`; if (Math.random() < 0.3 && promptGenData.productions.length > 1) { const prod2Single = getRandomElementExcluding(promptGenData.productions, prod1); productionAndGenreSegment = `${prod1}, ${prod2Single} ${genreTermsForPrompt[0]}`; } } else { const prod2 = getRandomElementExcluding(promptGenData.productions, prod1); productionAndGenreSegment = `${prod1} ${genreTermsForPrompt[0]}, ${prod2} ${genreTermsForPrompt[1]}`; } promptSegments.push(productionAndGenreSegment.trim()); const periodCompatibleInstruments = getCompatibleInstruments(effectiveEra); let instrumentPool = getInstrumentsForGenre(primaryGenre, periodCompatibleInstruments, effectiveEra); if (!instrumentPool || instrumentPool.length === 0) instrumentPool = periodCompatibleInstruments.length > 0 ? periodCompatibleInstruments : getCompatibleInstruments('contemporary'); if (!instrumentPool || instrumentPool.length === 0) instrumentPool = [...promptGenData.instruments]; const emotion1 = getRandomElement(promptGenData.emotions); let instrument1 = getRandomElement(instrumentPool); if (instrument1) promptSegments.push(`${emotion1} ${instrument1}`.trim()); if (Math.random() < 0.6 && instrumentPool.length > (instrumentPool.includes(instrument1) ? 1 : 0) && instrument1) { const emotion2 = getRandomElement(promptGenData.emotions); let instrument2 = getRandomElementExcluding(instrumentPool, instrument1); if (instrument2 && instrument2 !== instrument1) { promptSegments.push(`${emotion2} ${instrument2}`.trim()); } } if (!isInstrumentalOnly) { let vocalStylePool = getCompatibleVocals(effectiveEra); if (vocalStylePool.length === 0) vocalStylePool = promptGenData.vocals; const vocalStyle = getRandomElement(vocalStylePool); let vocalCategoryDescription = ""; const baseTypeChance = Math.random(); let gender = ""; if (primaryGenre && (primaryGenre.toLowerCase().includes("metal") || primaryGenre.toLowerCase().includes("hardcore") || primaryGenre.toLowerCase().includes("hip hop") || primaryGenre.toLowerCase().includes("rap"))) { if (Math.random() < 0.75) gender = "Male"; else gender = "Female"; } else if (primaryGenre && (primaryGenre.toLowerCase().includes("pop") || primaryGenre.toLowerCase().includes("soul") || primaryGenre.toLowerCase().includes("r&b") || primaryGenre.toLowerCase().includes("diva") || primaryGenre.toLowerCase().includes("singer-songwriter"))) { if (Math.random() < 0.55) gender = "Female"; else gender = "Male"; } else { if (Math.random() < 0.5) gender = getRandomElement(["Male", "Female"]); } if (baseTypeChance < 0.65) { vocalCategoryDescription = gender ? `${gender} Vocalist` : "Solo Vocalist"; } else if (baseTypeChance < 0.90) { vocalCategoryDescription = `${gender || getRandomElement(["Male", "Female", "Mixed"])} Group Vocals`; } else { vocalCategoryDescription = `${gender || getRandomElement(["Male", "Female", "Mixed"])} Choir`; } if (!vocalCategoryDescription.toLowerCase().includes("choir") && Math.random() < 0.33) { const role = getRandomElement(["Lead", "Backing"]); vocalCategoryDescription = vocalCategoryDescription.replace(/(Vocalist|Group Vocals)/i, `${role} $1`); } if (vocalStyle && vocalCategoryDescription) promptSegments.push(`${vocalStyle} ${vocalCategoryDescription}`.trim()); } let selectedNation = getAffinityNationsForPromptGen(primaryGenre, effectiveEra); if (selectedNation) promptSegments.push(selectedNation); if (finalSelectedPeriodForDisplay) promptSegments.push(finalSelectedPeriodForDisplay); return promptSegments.filter(s => s && String(s).trim() !== '').join(', '); }

    GM_addElement('link', { href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200', rel: 'stylesheet' });
    const ICONS = { settings: 'settings', add: 'add_circle', delete: 'delete_sweep', close: 'close', upload: 'file_upload', download: 'file_download', confirm: 'check_circle', cancel: 'cancel', drag_handle: 'drag_indicator', edit: 'edit', save: 'save', apply: 'input', expand_more: 'expand_more', expand_less: 'expand_less', manage: 'tune', sort_alpha: 'sort_by_alpha', prev: 'arrow_back_ios', next: 'arrow_forward_ios', generate: 'auto_awesome', copy: 'content_copy', delete_forever: 'delete_forever', library_add: 'library_add', chevron_right: 'chevron_right', search: 'search' };
    function createIcon(iconName, extraClass = '') { const span = document.createElement('span'); span.className = `material-symbols-outlined ${UPM_UI_PREFIX}-icon ${USPM_UI_PREFIX}-icon ${extraClass}`; span.textContent = iconName; return span; }
    function debounce(func, delay) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), delay); }; }
    async function applyReactControlledInputPreset(inputElement, valueToSet, managerId) { const logPrefix = `[${managerId || UPM_UI_PREFIX}]`; if (!inputElement) { alert(`${logPrefix} Target input field not found.`); return false; } UdioIntegration.log(`${logPrefix} Attempting to apply value: "${valueToSet}" to input:`, inputElement); let reactPropsKey = null; for (const key in inputElement) { if (key.startsWith("__reactProps$")) { reactPropsKey = key; break; } } const isStyleReductionCMDK = managerId && managerId.toLowerCase().includes('style-reduction') && inputElement.hasAttribute('cmdk-input'); if (!reactPropsKey || !inputElement[reactPropsKey] || typeof inputElement[reactPropsKey].onChange !== 'function') { UdioIntegration.log(`${logPrefix} React props or onChange not found. Falling back to direct value set for input:`, inputElement); inputElement.value = ""; inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); await new Promise(resolve => setTimeout(resolve, 60)); inputElement.value = valueToSet; inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); if (isStyleReductionCMDK && valueToSet.trim() !== "") { inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true })); } await new Promise(resolve => setTimeout(resolve, 120)); let fallbackSuccess = false; if (isStyleReductionCMDK) { const tagsContainer = inputElement.closest('.group')?.querySelector('.flex.flex-wrap.gap-1'); if (valueToSet.trim() === "") { fallbackSuccess = !tagsContainer || tagsContainer.children.length === 0; } else if (tagsContainer) { const currentTags = Array.from(tagsContainer.querySelectorAll('span[cmdk-tag-value]')).map(tagEl => tagEl.getAttribute('cmdk-tag-value').trim()); fallbackSuccess = currentTags.includes(valueToSet.trim()); } else { fallbackSuccess = false; } } else { fallbackSuccess = inputElement.value === valueToSet; } return fallbackSuccess; } const reactProps = inputElement[reactPropsKey]; const callReactOnChange = (val, simulateEnter = false) => { const mockEvent = { currentTarget: { value: val }, target: inputElement, isTrusted: false, bubbles: true, nativeEvent: {isTrusted: false }}; inputElement.value = val; reactProps.onChange(mockEvent); inputElement.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: val, composed: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); if (simulateEnter) { inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true, composed: true })); } }; inputElement.focus(); await new Promise(resolve => setTimeout(resolve, 60)); if (isStyleReductionCMDK) { const clearButton = inputElement.closest('.relative.w-full.space-y-2')?.querySelector('button[title="Clear Style Reduction"]:not([disabled])'); if (clearButton) { clearButton.click(); await new Promise(resolve => setTimeout(resolve, 120)); } else { callReactOnChange("", false); await new Promise(resolve => setTimeout(resolve, 60)); } } else { callReactOnChange("", false); await new Promise(resolve => setTimeout(resolve, 60)); } if (valueToSet.trim() !== "") { if (isStyleReductionCMDK) { callReactOnChange(valueToSet, true); } else { callReactOnChange(valueToSet, false); } await new Promise(resolve => setTimeout(resolve, 120)); } await new Promise(resolve => setTimeout(resolve, 180)); let success; if (isStyleReductionCMDK) { const tagsContainer = inputElement.closest('.group')?.querySelector('.flex.flex-wrap.gap-1'); if (valueToSet.trim() === "") { success = !tagsContainer || tagsContainer.children.length === 0; } else if (tagsContainer) { const currentTags = Array.from(tagsContainer.querySelectorAll('span[cmdk-tag-value]')).map(tagEl => tagEl.getAttribute('cmdk-tag-value').trim()); const expectedSingleTag = valueToSet.trim(); const isTagPresent = currentTags.includes(expectedSingleTag); if (isTagPresent && currentTags.length === 1) { success = true; } else if (isTagPresent && currentTags.length > 1) { success = true; } else { success = false; } } else { success = false; } } else { success = inputElement.value === valueToSet; } if (success) { inputElement.blur(); await new Promise(resolve => setTimeout(resolve, 60)); if (!isStyleReductionCMDK && inputElement.value !== valueToSet) { inputElement.focus(); await new Promise(resolve => setTimeout(resolve, 60)); callReactOnChange("", false); await new Promise(resolve => setTimeout(resolve, 60)); if (valueToSet.trim() !== "") { callReactOnChange(valueToSet, false); await new Promise(resolve => setTimeout(resolve, 60)); } success = inputElement.value === valueToSet; inputElement.blur(); } } else { if (document.activeElement === inputElement) { inputElement.blur(); } } return success; }
    const UdioPromptGeneratorIntegrated = { managerInstance: null, ui: { accordionHeader: null, accordionContent: null, promptModeSelect: null, genreCategorySelect: null, generateBtn: null, generatedPromptTextarea: null, copyBtn: null, applyBtn: null, lookupResultsContainer: null, }, isAccordionOpen: GM_getValue('upmAdvGenAccordionOpen_v1', false), populateGenreCategoryFilterForEmbeddedUI: function(selectElement) { selectElement.innerHTML = '<option value="all">All Genre Categories</option>'; const categoryKeys = Object.keys(promptGenData.metaGenreCategories).filter(key => key !== "all" && promptGenData.metaGenreCategories[key] && Array.isArray(promptGenData.metaGenreCategories[key])).sort(); categoryKeys.forEach(categoryKey => { if (promptGenData.metaGenreCategories[categoryKey].length === 0) return; const option = document.createElement('option'); option.value = categoryKey; let displayName = categoryKey.replace(/([A-Z]+)/g, " $1").replace(/([A-Z][a-z])/g, " $1").trim(); displayName = displayName.replace(/\bRnB\b/g, 'R&B').replace(/World Latin Reggae/g, 'World/Latin/Reggae').replace(/Blues Country/g, 'Blues/Country').replace(/Hip Hop/g, 'Hip Hop').replace(/Classical Music/g, 'Classical'); option.textContent = displayName.charAt(0).toUpperCase() + displayName.slice(1); selectElement.appendChild(option); }); }, createUI: function() { const generatorContainer = document.createElement('div'); generatorContainer.id = `${UPM_UI_PREFIX}-integrated-advanced-generator`; generatorContainer.className = `${UPM_UI_PREFIX}-integrated-advanced-generator`; this.ui.accordionHeader = document.createElement('div'); this.ui.accordionHeader.className = `${UPM_UI_PREFIX}-advanced-generator-accordion-header`; this.ui.accordionHeader.textContent = 'Advanced Prompt Generator '; const expandIcon = createIcon(this.isAccordionOpen ? ICONS.expand_less : ICONS.expand_more, `${UPM_UI_PREFIX}-accordion-icon`); this.ui.accordionHeader.appendChild(expandIcon); this.ui.accordionHeader.onclick = () => this.toggleAccordion(); generatorContainer.appendChild(this.ui.accordionHeader); this.ui.accordionContent = document.createElement('div'); this.ui.accordionContent.className = `${UPM_UI_PREFIX}-advanced-generator-accordion-content`; this.ui.accordionContent.style.display = this.isAccordionOpen ? 'block' : 'none'; const controlsDiv = document.createElement('div'); controlsDiv.className = `${UPM_UI_PREFIX}-advanced-generator-controls ${UPM_UI_PREFIX}-integrated-controls`; this.ui.promptModeSelect = document.createElement('select'); this.ui.promptModeSelect.id = `${UPM_UI_PREFIX}IntegratedAdvancedPromptMode`; this.ui.promptModeSelect.className = `${UPM_UI_PREFIX}-advanced-generator-select ${UPM_UI_PREFIX}-integrated-select`; const modes = [ { value: "coherent", text: "Coherent Random" }, { value: "coherent_instrumental", text: "Coherent Instrumental" }, { value: "hardcore", text: "Hardcore Random" }, { value: "hardcore_instrumental", text: "Instrumental Random" } ]; modes.forEach(m => { const opt = document.createElement('option'); opt.value = m.value; opt.textContent = m.text; this.ui.promptModeSelect.appendChild(opt); }); controlsDiv.appendChild(this.ui.promptModeSelect); this.ui.genreCategorySelect = document.createElement('select'); this.ui.genreCategorySelect.id = `${UPM_UI_PREFIX}IntegratedGenreCategoryFilter`; this.ui.genreCategorySelect.className = `${UPM_UI_PREFIX}-advanced-generator-select ${UPM_UI_PREFIX}-integrated-select`; this.populateGenreCategoryFilterForEmbeddedUI(this.ui.genreCategorySelect); controlsDiv.appendChild(this.ui.genreCategorySelect); this.ui.generateBtn = document.createElement('button'); this.ui.generateBtn.id = `${UPM_UI_PREFIX}IntegratedAdvancedGenerateBtn`; this.ui.generateBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-generate`; this.ui.generateBtn.appendChild(createIcon(ICONS.generate)); this.ui.generateBtn.appendChild(document.createTextNode(' Generate')); this.ui.generateBtn.onclick = () => this.handleGeneratePrompt(); controlsDiv.appendChild(this.ui.generateBtn); this.ui.accordionContent.appendChild(controlsDiv); const modeDescription = document.createElement('p'); modeDescription.className = `${UPM_UI_PREFIX}-advanced-generator-mode-desc ${UPM_UI_PREFIX}-integrated-mode-desc`; modeDescription.textContent = 'Coherent: Uses category filter. Hardcore: Full random.'; this.ui.accordionContent.appendChild(modeDescription); const promptDisplayArea = document.createElement('div'); promptDisplayArea.className = `${UPM_UI_PREFIX}-advanced-generator-prompt-area ${UPM_UI_PREFIX}-integrated-prompt-area`; this.ui.generatedPromptTextarea = document.createElement('textarea'); this.ui.generatedPromptTextarea.id = `${UPM_UI_PREFIX}IntegratedAdvancedGeneratedPrompt`; this.ui.generatedPromptTextarea.className = `${UPM_UI_PREFIX}-advanced-generator-textarea ${UPM_UI_PREFIX}-integrated-textarea`; this.ui.generatedPromptTextarea.readOnly = true; this.ui.generatedPromptTextarea.rows = 3; this.ui.generatedPromptTextarea.placeholder = 'Generated prompt...'; promptDisplayArea.appendChild(this.ui.generatedPromptTextarea); const promptActionsDiv = document.createElement('div'); promptActionsDiv.className = `${UPM_UI_PREFIX}-advanced-generator-prompt-actions ${UPM_UI_PREFIX}-integrated-prompt-actions`; this.ui.copyBtn = document.createElement('button'); this.ui.copyBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-secondary ${UPM_UI_PREFIX}-integrated-action-btn`; this.ui.copyBtn.appendChild(createIcon(ICONS.copy)); this.ui.copyBtn.appendChild(document.createTextNode(' Copy')); this.ui.copyBtn.onclick = () => this.handleCopyPrompt(); promptActionsDiv.appendChild(this.ui.copyBtn); this.ui.applyBtn = document.createElement('button'); this.ui.applyBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-apply ${UPM_UI_PREFIX}-integrated-action-btn`; this.ui.applyBtn.appendChild(createIcon(ICONS.apply)); this.ui.applyBtn.appendChild(document.createTextNode(' Apply to Main Input')); this.ui.applyBtn.onclick = () => this.handleApplyPrompt(); promptActionsDiv.appendChild(this.ui.applyBtn); promptDisplayArea.appendChild(promptActionsDiv); this.ui.accordionContent.appendChild(promptDisplayArea); const lookupControlsDiv = document.createElement('div'); lookupControlsDiv.className = `${UPM_UI_PREFIX}-advanced-generator-lookup-controls ${UPM_UI_PREFIX}-integrated-lookup-controls`; const lookupItems = [ { id: 'showGenresBtnAdvInt', text: 'Genres', dataKey: 'genres' }, { id: 'showInstrumentsBtnAdvInt', text: 'Instruments', dataKey: 'instruments' }, { id: 'showEmotionsBtnAdvInt', text: 'Emotions', dataKey: 'emotions' }, { id: 'showProductionBtnAdvInt', text: 'Production', dataKey: 'productions' }, { id: 'showVocalsBtnAdvInt', text: 'Vocals', dataKey: 'vocals' }, { id: 'showPeriodsBtnAdvInt', text: 'Periods', dataKey: 'periods' }, ]; lookupItems.forEach(item => { const btn = document.createElement('button'); btn.id = item.id; btn.className = `${UPM_UI_PREFIX}-lookup-btn ${UPM_UI_PREFIX}-integrated-lookup-btn`; btn.textContent = item.text; btn.onclick = () => this.displayQuickLookup(promptGenData[item.dataKey], item.text); lookupControlsDiv.appendChild(btn); }); this.ui.accordionContent.appendChild(lookupControlsDiv); this.ui.lookupResultsContainer = document.createElement('div'); this.ui.lookupResultsContainer.id = `${UPM_UI_PREFIX}IntegratedAdvancedLookupResults`; this.ui.lookupResultsContainer.className = `${UPM_UI_PREFIX}-advanced-generator-lookup-results ${UPM_UI_PREFIX}-integrated-lookup-results`; this.ui.accordionContent.appendChild(this.ui.lookupResultsContainer); generatorContainer.appendChild(this.ui.accordionContent); return generatorContainer; }, toggleAccordion: function() { this.isAccordionOpen = !this.isAccordionOpen; GM_setValue('upmAdvGenAccordionOpen_v1', this.isAccordionOpen); if (this.ui.accordionContent && this.ui.accordionHeader) { this.ui.accordionContent.style.display = this.isAccordionOpen ? 'block' : 'none'; const icon = this.ui.accordionHeader.querySelector(`.${UPM_UI_PREFIX}-accordion-icon`); if (icon) { icon.textContent = this.isAccordionOpen ? ICONS.expand_less : ICONS.expand_more; } } }, handleGeneratePrompt: function() { if (!this.ui.promptModeSelect || !this.ui.genreCategorySelect || !this.ui.generatedPromptTextarea || !this.ui.generateBtn) return; if (!isDataLoaded) { alert("Prompt generation data is not loaded yet."); return; } this.ui.generateBtn.classList.add(`${UPM_UI_PREFIX}-glowing`); setTimeout(() => { this.ui.generateBtn.classList.remove(`${UPM_UI_PREFIX}-glowing`); }, 500); const mode = this.ui.promptModeSelect.value; const selectedCategory = this.ui.genreCategorySelect.value; const newPrompt = generateUdioStructuredPrompt(mode, selectedCategory); this.ui.generatedPromptTextarea.value = newPrompt; this.ui.generatedPromptTextarea.scrollTop = 0; }, handleCopyPrompt: function() { if (!this.ui.generatedPromptTextarea || !this.ui.copyBtn) return; const textToCopy = this.ui.generatedPromptTextarea.value; if (!textToCopy) return; navigator.clipboard.writeText(textToCopy).then(() => { const originalText = this.ui.copyBtn.innerHTML; this.ui.copyBtn.innerHTML = `${createIcon(ICONS.confirm).outerHTML} Copied!`; this.ui.copyBtn.classList.add(`${UPM_UI_PREFIX}-copied-transient`); setTimeout(() => { this.ui.copyBtn.innerHTML = originalText; this.ui.copyBtn.classList.remove(`${UPM_UI_PREFIX}-copied-transient`); }, 1500); }).catch(err => { const originalText = this.ui.copyBtn.innerHTML; this.ui.copyBtn.textContent = 'Error'; setTimeout(() => { this.ui.copyBtn.innerHTML = originalText; }, 1500); }); }, handleApplyPrompt: async function() { if (!this.ui.generatedPromptTextarea) return; const promptToApply = this.ui.generatedPromptTextarea.value; if (!promptToApply) return; const targetTextarea = (typeof MANAGER_CONFIGS.prompt.targetInputSelector === 'function') ? MANAGER_CONFIGS.prompt.targetInputSelector() : document.querySelector(MANAGER_CONFIGS.prompt.targetInputSelector); if (targetTextarea) { const success = await applyReactControlledInputPreset(targetTextarea, promptToApply, `${UPM_UI_PREFIX}-AdvGen-Integrated`); if (success) { this.ui.applyBtn.classList.add(`${UPM_UI_PREFIX}-applied-transient`); const originalText = this.ui.applyBtn.innerHTML; this.ui.applyBtn.innerHTML = `${createIcon(ICONS.confirm).outerHTML} Applied!`; setTimeout(() => { this.ui.applyBtn.innerHTML = originalText; this.ui.applyBtn.classList.remove(`${UPM_UI_PREFIX}-applied-transient`); }, 1500); } else { alert(`[${UPM_UI_PREFIX} Gen] Failed to apply prompt to main input.`); } } else { alert(`[${UPM_UI_PREFIX} Gen] Could not find the main Udio prompt textarea.`); } }, displayQuickLookup: function(dataList, categoryName) { if (!this.ui.lookupResultsContainer) return; this.ui.lookupResultsContainer.innerHTML = ''; if (!isDataLoaded) { this.ui.lookupResultsContainer.textContent = "Data not loaded."; return;} if (!dataList || dataList.length === 0) { this.ui.lookupResultsContainer.textContent = `No items in ${categoryName}.`; } else { const title = document.createElement('h4'); title.textContent = `Random ${categoryName} (Max 3):`; title.className = `${UPM_UI_PREFIX}-lookup-results-title`; this.ui.lookupResultsContainer.appendChild(title); const itemsToShow = getRandomElement(dataList, 3); itemsToShow.forEach(item => { const itemBox = document.createElement('div'); itemBox.className = `${UPM_UI_PREFIX}-lookup-item-box`; const itemParagraph = document.createElement('p'); itemParagraph.textContent = item; const copyBtn = document.createElement('button'); copyBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-icon-only ${UPM_UI_PREFIX}-lookup-copy-btn`; copyBtn.title = "Copy term"; copyBtn.appendChild(createIcon(ICONS.copy)); copyBtn.onclick = (e) => { e.stopPropagation(); this.copyIndividualTerm(item, copyBtn); }; itemBox.appendChild(itemParagraph); itemBox.appendChild(copyBtn); this.ui.lookupResultsContainer.appendChild(itemBox); }); } }, copyIndividualTerm: function(textToCopy, buttonElement) { navigator.clipboard.writeText(textToCopy).then(() => { const originalIcon = buttonElement.innerHTML; buttonElement.innerHTML = createIcon(ICONS.confirm).outerHTML; buttonElement.classList.add(`${UPM_UI_PREFIX}-copied-transient`); setTimeout(() => { buttonElement.innerHTML = originalIcon; buttonElement.classList.remove(`${UPM_UI_PREFIX}-copied-transient`); }, 1200); }); } };
    class PresetManager {
        constructor(config) {
            this.config = config; this.presets = [];
            this.ui = { managerWindow: null, presetListContainer: null, newPresetNameInput: null, newPresetValueInput: null, newPresetPositionInput: null, newPresetPositionTotalSpan: null, resizeHandle: null, managerPaginationControls: null };
            this.isDragging = false; this.dragOffsetX = 0; this.dragOffsetY = 0;
            this.isResizing = false; this.resizeStartX = 0; this.resizeStartWidth = 0;
            this.editingPresetIndex = null; this.originalAddButtonText = null; this.originalAddButtonOnClick = null;
            this.draggedPresetElement = null; this.draggedPresetOriginalIndex = -1;
            this.config.uiSizeStorageKey = this.config.uiSizeStorageKey || `${this.config.id}_${USPM_UI_PREFIX}_uiSize_v1`;
            this.config.uiPositionStorageKey = this.config.uiPositionStorageKey || `${this.config.id}_${USPM_UI_PREFIX}_uiPos_v1`;
            this.DEFAULT_ITEMS_PER_MANAGER_PAGE = 20; // 4 rows of 5 items
            this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE;
            this.managerCurrentPage = 1;
            this.pageChangeTimer = null; this.PAGE_CHANGE_DRAG_HOVER_DELAY = 750;
            this.loadPresets();
        }
        addPreset(name, value) { if (this.editingPresetIndex !== null) { this.cancelEditPreset(); } if (!name?.trim() || value === undefined) { alert("Preset name cannot be empty."); return; } const trimmedName = name.trim(); const trimmedValue = value.trim(); if (this.presets.some(p => p.name.toLowerCase() === trimmedName.toLowerCase())) { alert(`A preset with the name "${trimmedName}" already exists.`); return; } this.presets.unshift({ name: trimmedName, value: trimmedValue }); this.savePresets(); this.renderPresetList(); if (this.ui.newPresetNameInput && this.ui.newPresetValueInput) { this.ui.newPresetNameInput.value = ''; this.ui.newPresetValueInput.value = ''; this.ui.newPresetNameInput.focus(); } UdioIntegration.refreshIntegratedUI(); }
        loadPresets() { const stored = GM_getValue(this.config.storageKey); if (stored) { try { this.presets = JSON.parse(stored); if (!Array.isArray(this.presets) || !this.presets.every(p => typeof p === 'object' && 'name' in p && 'value' in p)) { this.presets = []; } } catch (e) { this.presets = []; } } }
        savePresets() { GM_setValue(this.config.storageKey, JSON.stringify(this.presets)); UdioIntegration.refreshIntegratedUI(); }
        deletePreset(indexInFullArray) { if (this.editingPresetIndex === indexInFullArray) { this.cancelEditPreset(); } if (indexInFullArray < 0 || indexInFullArray >= this.presets.length) return; this.presets.splice(indexInFullArray, 1); this.savePresets(); const totalPages = Math.ceil(this.presets.length / this.itemsPerPageManager); if (this.managerCurrentPage > totalPages && totalPages > 0) { this.managerCurrentPage = totalPages; } else if (totalPages === 0) { this.managerCurrentPage = 1; } this.renderPresetList(); UdioIntegration.refreshIntegratedUI(); }
        _handlePageButtonDragOver(event, isNextButton) { event.preventDefault(); if (!this.draggedPresetElement) return; const button = event.currentTarget; button.classList.add(`${USPM_UI_PREFIX}-page-btn-drag-hotspot`); if (!this.pageChangeTimer) { this.pageChangeTimer = setTimeout(() => { if (isNextButton) { if (this.managerCurrentPage < Math.ceil(this.presets.length / this.itemsPerPageManager)) { this.managerCurrentPage++; this.renderPresetList(); } } else { if (this.managerCurrentPage > 1) { this.managerCurrentPage--; this.renderPresetList(); } } this.pageChangeTimer = null; }, this.PAGE_CHANGE_DRAG_HOVER_DELAY); } }
        _handlePageButtonDragLeave(event) { event.currentTarget.classList.remove(`${USPM_UI_PREFIX}-page-btn-drag-hotspot`); if (this.pageChangeTimer) { clearTimeout(this.pageChangeTimer); this.pageChangeTimer = null; } }
        renderPresetList() { if (!this.ui.presetListContainer || !this.ui.managerPaginationControls) return; this.ui.presetListContainer.innerHTML = ''; this.ui.managerPaginationControls.innerHTML = ''; if (this.presets.length === 0) { this.ui.presetListContainer.innerHTML = `<p class="${USPM_UI_PREFIX}-no-presets ${USPM_UI_PREFIX}-no-presets-${this.config.id}">No presets yet.</p>`; this.ui.managerPaginationControls.style.display = 'none'; return; } const totalPresets = this.presets.length; const totalPages = Math.ceil(totalPresets / this.itemsPerPageManager); if (this.managerCurrentPage > totalPages && totalPages > 0) this.managerCurrentPage = totalPages; if (this.managerCurrentPage < 1) this.managerCurrentPage = 1; const startIndex = (this.managerCurrentPage - 1) * this.itemsPerPageManager; const endIndex = Math.min(startIndex + this.itemsPerPageManager, totalPresets); const presetsToDisplay = this.presets.slice(startIndex, endIndex); presetsToDisplay.forEach((preset, indexInPage) => { const originalIndex = startIndex + indexInPage; const item = document.createElement('div'); item.className = `${USPM_UI_PREFIX}-preset-item ${USPM_UI_PREFIX}-preset-item-${this.config.id}`; item.title = `Value: ${preset.value}`; item.draggable = true; item.dataset.originalIndex = originalIndex; item.onclick = (e) => { if (e.target.closest('button')) return; this.applyPresetToTarget(preset.value); }; const nameSpan = document.createElement('span'); nameSpan.className = `${USPM_UI_PREFIX}-preset-name`; nameSpan.textContent = preset.name; item.appendChild(nameSpan); const controlsDiv = document.createElement('div'); controlsDiv.className = `${USPM_UI_PREFIX}-preset-item-controls`; const editBtn = document.createElement('button'); editBtn.className = `${USPM_UI_PREFIX}-icon-button ${USPM_UI_PREFIX}-edit-btn`; editBtn.appendChild(createIcon(ICONS.edit)); editBtn.title = "Edit"; editBtn.onclick = (e) => { e.stopPropagation(); this.startEditPreset(originalIndex); }; controlsDiv.appendChild(editBtn); const deleteBtn = document.createElement('button'); deleteBtn.className = `${USPM_UI_PREFIX}-icon-button ${USPM_UI_PREFIX}-delete-btn`; deleteBtn.appendChild(createIcon(ICONS.delete)); deleteBtn.title = "Delete"; deleteBtn.onclick = (e) => { e.stopPropagation(); this.showConfirm(`Delete preset "${preset.name}"?`, () => this.deletePreset(originalIndex)); }; controlsDiv.appendChild(deleteBtn); item.appendChild(controlsDiv); this.ui.presetListContainer.appendChild(item); item.addEventListener('dragstart', this._handleDragStart.bind(this)); item.addEventListener('dragend', this._handleDragEnd.bind(this)); }); if (totalPages > 1) { this.ui.managerPaginationControls.style.display = 'flex'; const prevBtn = document.createElement('button'); prevBtn.className = `${USPM_UI_PREFIX}-page-btn ${USPM_UI_PREFIX}-page-prev`; prevBtn.appendChild(createIcon(ICONS.prev)); prevBtn.disabled = this.managerCurrentPage <= 1; prevBtn.onclick = () => { if (this.managerCurrentPage > 1) { this.managerCurrentPage--; this.renderPresetList(); } }; prevBtn.addEventListener('dragover', (e) => this._handlePageButtonDragOver(e, false)); prevBtn.addEventListener('dragleave', this._handlePageButtonDragLeave.bind(this)); const pageInfo = document.createElement('span'); pageInfo.className = `${USPM_UI_PREFIX}-page-info`; pageInfo.textContent = `Page ${this.managerCurrentPage} / ${totalPages}`; const nextBtn = document.createElement('button'); nextBtn.className = `${USPM_UI_PREFIX}-page-btn ${USPM_UI_PREFIX}-page-next`; nextBtn.appendChild(createIcon(ICONS.next)); nextBtn.disabled = this.managerCurrentPage >= totalPages; nextBtn.onclick = () => { if (this.managerCurrentPage < totalPages) { this.managerCurrentPage++; this.renderPresetList(); } }; nextBtn.addEventListener('dragover', (e) => this._handlePageButtonDragOver(e, true)); nextBtn.addEventListener('dragleave', this._handlePageButtonDragLeave.bind(this)); this.ui.managerPaginationControls.appendChild(prevBtn); this.ui.managerPaginationControls.appendChild(pageInfo); this.ui.managerPaginationControls.appendChild(nextBtn); } else { this.ui.managerPaginationControls.style.display = 'none'; } }
        _handleDragStart(e) { this.draggedPresetElement = e.target.closest(`.${USPM_UI_PREFIX}-preset-item`); if (!this.draggedPresetElement) return; this.draggedPresetOriginalIndex = parseInt(this.draggedPresetElement.dataset.originalIndex); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', this.draggedPresetOriginalIndex.toString()); this.draggedPresetElement.classList.add(`${USPM_UI_PREFIX}-dragging-item`); this.ui.presetListContainer.classList.add(`${USPM_UI_PREFIX}-list-dragging-active`); }
        _handleDragEnd(e) { if (this.draggedPresetElement) { this.draggedPresetElement.classList.remove(`${USPM_UI_PREFIX}-dragging-item`); } this.ui.presetListContainer.querySelectorAll(`.${USPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${USPM_UI_PREFIX}-drag-over-target`)); this.ui.presetListContainer.classList.remove(`${USPM_UI_PREFIX}-list-dragging-active`); this.draggedPresetElement = null; this.draggedPresetOriginalIndex = -1; if (this.pageChangeTimer) { clearTimeout(this.pageChangeTimer); this.pageChangeTimer = null; } this.ui.managerPaginationControls.querySelectorAll(`.${USPM_UI_PREFIX}-page-btn-drag-hotspot`).forEach(b => b.classList.remove(`${USPM_UI_PREFIX}-page-btn-drag-hotspot`)); UdioIntegration.refreshIntegratedUI(); }
        _handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (!this.ui.presetListContainer.classList.contains(`${USPM_UI_PREFIX}-list-dragging-active`)) return; const targetItem = e.target.closest(`.${USPM_UI_PREFIX}-preset-item`); this.ui.presetListContainer.querySelectorAll(`.${USPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${USPM_UI_PREFIX}-drag-over-target`)); if (targetItem && targetItem !== this.draggedPresetElement) { targetItem.classList.add(`${USPM_UI_PREFIX}-drag-over-target`); } }
        _handleDragLeave(e) { const targetItem = e.target.closest(`.${USPM_UI_PREFIX}-preset-item`); if (targetItem) { if (!targetItem.contains(e.relatedTarget) ) { targetItem.classList.remove(`${USPM_UI_PREFIX}-drag-over-target`); } } }
        _handleDrop(e) { e.preventDefault(); if (!this.draggedPresetElement || this.draggedPresetOriginalIndex === -1) return; const targetItemElement = e.target.closest(`.${USPM_UI_PREFIX}-preset-item`); this.ui.presetListContainer.querySelectorAll(`.${USPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${USPM_UI_PREFIX}-drag-over-target`)); if (!targetItemElement || targetItemElement === this.draggedPresetElement) return; const fromIndex = this.draggedPresetOriginalIndex; const toIndex = parseInt(targetItemElement.dataset.originalIndex); if (fromIndex === toIndex) return; const itemToMove = this.presets.splice(fromIndex, 1)[0]; this.presets.splice(toIndex, 0, itemToMove); this.savePresets(); this.renderPresetList(); UdioIntegration.refreshIntegratedUI(); }
        startEditPreset(index) { if (index < 0 || index >= this.presets.length) return; this.editingPresetIndex = index; const preset = this.presets[index]; if (this.ui.newPresetNameInput && this.ui.newPresetValueInput && this.ui.newPresetPositionInput && this.ui.newPresetPositionTotalSpan && this.ui.managerWindow) { this.ui.newPresetNameInput.value = preset.name; this.ui.newPresetValueInput.value = preset.value; this.ui.newPresetPositionInput.value = index + 1; this.ui.newPresetPositionInput.max = this.presets.length.toString(); this.ui.newPresetPositionTotalSpan.textContent = ` of ${this.presets.length}`; this.ui.newPresetPositionInput.style.display = 'inline-block'; this.ui.newPresetPositionTotalSpan.style.display = 'inline-block'; this.ui.newPresetNameInput.focus(); const addButton = this.ui.managerWindow.querySelector(`.${USPM_UI_PREFIX}-add-btn-${this.config.id}`); if (addButton) { if (!this.originalAddButtonOnClick) { this.originalAddButtonText = addButton.innerHTML; this.originalAddButtonOnClick = addButton.onclick; } addButton.innerHTML = ''; addButton.appendChild(createIcon(ICONS.save)); addButton.appendChild(document.createTextNode(' Save Edit')); addButton.onclick = () => this.saveEditedPreset(); } this.addCancelEditButton(addButton); } }
        addCancelEditButton(addButtonReference) { if (!this.ui.managerWindow) return; let cancelBtn = this.ui.managerWindow.querySelector(`.${USPM_UI_PREFIX}-cancel-edit-btn`); const addControlsContainer = this.ui.managerWindow.querySelector(`.${USPM_UI_PREFIX}-add-controls-container`); if (!cancelBtn && addControlsContainer) { cancelBtn = document.createElement('button'); cancelBtn.className = `${USPM_UI_PREFIX}-text-button secondary ${USPM_UI_PREFIX}-cancel-edit-btn`; cancelBtn.appendChild(createIcon(ICONS.cancel)); cancelBtn.appendChild(document.createTextNode(' Cancel')); addControlsContainer.insertBefore(cancelBtn, addButtonReference); } if(cancelBtn) { cancelBtn.onclick = () => this.cancelEditPreset(); cancelBtn.style.display = 'inline-flex'; } }
        removeCancelEditButton() { if (!this.ui.managerWindow) return; const cancelBtn = this.ui.managerWindow.querySelector(`.${USPM_UI_PREFIX}-cancel-edit-btn`); if (cancelBtn) { cancelBtn.remove(); } }
        cancelEditPreset() { this.editingPresetIndex = null; if (this.ui.newPresetNameInput && this.ui.newPresetValueInput) { this.ui.newPresetNameInput.value = ''; this.ui.newPresetValueInput.value = ''; } if(this.ui.newPresetPositionInput) { this.ui.newPresetPositionInput.value = ''; this.ui.newPresetPositionInput.style.display = 'none'; } if(this.ui.newPresetPositionTotalSpan) { this.ui.newPresetPositionTotalSpan.textContent = ''; this.ui.newPresetPositionTotalSpan.style.display = 'none'; } this.restoreAddButton(); this.removeCancelEditButton(); }
        restoreAddButton() { if (!this.ui.managerWindow) return; const addButton = this.ui.managerWindow.querySelector(`.${USPM_UI_PREFIX}-add-btn-${this.config.id}`); if (addButton && this.originalAddButtonText !== null && this.originalAddButtonOnClick !== null) { addButton.innerHTML = this.originalAddButtonText; addButton.onclick = this.originalAddButtonOnClick; this.originalAddButtonText = null; this.originalAddButtonOnClick = null; } else if (addButton) { addButton.innerHTML = ''; addButton.appendChild(createIcon(ICONS.add)); addButton.appendChild(document.createTextNode(' Add Preset')); addButton.onclick = () => this.addPreset(this.ui.newPresetNameInput.value, this.ui.newPresetValueInput.value); } }
        saveEditedPreset() { if (this.editingPresetIndex === null || this.editingPresetIndex < 0 || this.editingPresetIndex >= this.presets.length) { this.cancelEditPreset(); return; } if (!this.ui.newPresetNameInput || !this.ui.newPresetValueInput || !this.ui.newPresetPositionInput) return; const newName = this.ui.newPresetNameInput.value.trim(); const newValue = this.ui.newPresetValueInput.value.trim(); if (!newName) { alert("Preset name cannot be empty."); return; } const conflictingPreset = this.presets.find((p, i) => i !== this.editingPresetIndex && p.name.toLowerCase() === newName.toLowerCase() ); if (conflictingPreset) { alert(`A preset with the name "${newName}" already exists.`); return; } let newPosition = parseInt(this.ui.newPresetPositionInput.value, 10) -1; if (isNaN(newPosition) || newPosition < 0 || newPosition >= this.presets.length) { alert(`Invalid position. Must be between 1 and ${this.presets.length}.`); this.ui.newPresetPositionInput.focus(); return; } this.presets[this.editingPresetIndex].name = newName; this.presets[this.editingPresetIndex].value = newValue; if (newPosition !== this.editingPresetIndex) { const itemToMove = this.presets.splice(this.editingPresetIndex, 1)[0]; this.presets.splice(newPosition, 0, itemToMove); } this.savePresets(); this.renderPresetList(); this.cancelEditPreset(); if (this.ui.newPresetNameInput) this.ui.newPresetNameInput.focus(); UdioIntegration.refreshIntegratedUI(); }
        createManagerWindow() { if (document.getElementById(`${USPM_UI_PREFIX}-window-${this.config.id}`)) { this.ui.managerWindow = document.getElementById(`${USPM_UI_PREFIX}-window-${this.config.id}`); return; } this.ui.managerWindow = document.createElement('div'); this.ui.managerWindow.id = `${USPM_UI_PREFIX}-window-${this.config.id}`; this.ui.managerWindow.className = `${USPM_UI_PREFIX}-window`; this.ui.managerWindow.style.display = 'none'; const header = document.createElement('div'); header.className = `${USPM_UI_PREFIX}-header`; const dragHandle = createIcon(ICONS.drag_handle, `${USPM_UI_PREFIX}-drag-handle-icon`); header.appendChild(dragHandle); const titleSpan = document.createElement('span'); titleSpan.className = `${USPM_UI_PREFIX}-header-title`; titleSpan.textContent = this.config.uiTitle; header.appendChild(titleSpan); const headerControls = document.createElement('div'); headerControls.className = `${USPM_UI_PREFIX}-header-controls`; const sortBtn = this.createHeaderButton(ICONS.sort_alpha, "Sort A-Z", this.sortPresetsByName.bind(this)); headerControls.appendChild(sortBtn); const closeBtn = this.createHeaderButton(ICONS.close, "Close", this.toggleManagerWindow.bind(this)); headerControls.appendChild(closeBtn); header.appendChild(headerControls); this.ui.managerWindow.appendChild(header); this.ui.presetListContainer = document.createElement('div'); this.ui.presetListContainer.className = `${USPM_UI_PREFIX}-list-container`; this.ui.presetListContainer.addEventListener('dragover', this._handleDragOver.bind(this)); this.ui.presetListContainer.addEventListener('drop', this._handleDrop.bind(this)); this.ui.presetListContainer.addEventListener('dragleave', this._handleDragLeave.bind(this)); this.ui.managerWindow.appendChild(this.ui.presetListContainer); this.ui.managerPaginationControls = document.createElement('div'); this.ui.managerPaginationControls.className = `${USPM_UI_PREFIX}-manager-pagination-controls`; this.ui.managerWindow.appendChild(this.ui.managerPaginationControls); const addArea = document.createElement('div'); addArea.className = `${USPM_UI_PREFIX}-add-area`; const nameAndPositionContainer = document.createElement('div'); nameAndPositionContainer.className = `${USPM_UI_PREFIX}-name-pos-container`; this.ui.newPresetNameInput = document.createElement('input'); this.ui.newPresetNameInput.type = 'text'; this.ui.newPresetNameInput.placeholder = 'Preset Name'; this.ui.newPresetNameInput.className = `${USPM_UI_PREFIX}-input ${USPM_UI_PREFIX}-name-input`; nameAndPositionContainer.appendChild(this.ui.newPresetNameInput); this.ui.newPresetPositionInput = document.createElement('input'); this.ui.newPresetPositionInput.type = 'number'; this.ui.newPresetPositionInput.className = `${USPM_UI_PREFIX}-input ${USPM_UI_PREFIX}-pos-input`; this.ui.newPresetPositionInput.style.display = 'none'; this.ui.newPresetPositionInput.min = "1"; nameAndPositionContainer.appendChild(this.ui.newPresetPositionInput); this.ui.newPresetPositionTotalSpan = document.createElement('span'); this.ui.newPresetPositionTotalSpan.className = `${USPM_UI_PREFIX}-pos-total`; this.ui.newPresetPositionTotalSpan.style.display = 'none'; nameAndPositionContainer.appendChild(this.ui.newPresetPositionTotalSpan); addArea.appendChild(nameAndPositionContainer); this.ui.newPresetValueInput = document.createElement('input'); this.ui.newPresetValueInput.type = 'text'; this.ui.newPresetValueInput.placeholder = this.config.id === 'prompt' ? 'Prompt text...' : 'Style reduction text...'; this.ui.newPresetValueInput.className = `${USPM_UI_PREFIX}-input`; addArea.appendChild(this.ui.newPresetValueInput); const addControlsContainer = document.createElement('div'); addControlsContainer.className = `${USPM_UI_PREFIX}-add-controls-container`; const addButton = document.createElement('button'); addButton.className = `${USPM_UI_PREFIX}-text-button ${USPM_UI_PREFIX}-add-btn ${USPM_UI_PREFIX}-add-btn-${this.config.id}`; this.originalAddButtonText = `${createIcon(ICONS.add).outerHTML}Add Preset`; this.originalAddButtonOnClick = () => this.addPreset(this.ui.newPresetNameInput.value, this.ui.newPresetValueInput.value); addButton.innerHTML = this.originalAddButtonText; addButton.onclick = this.originalAddButtonOnClick; addControlsContainer.appendChild(addButton); addArea.appendChild(addControlsContainer); this.ui.managerWindow.appendChild(addArea); const footer = document.createElement('div'); footer.className = `${USPM_UI_PREFIX}-footer`; const importBtn = this.createFooterButton(ICONS.upload, "Import", this.handleImport.bind(this)); const exportBtn = this.createFooterButton(ICONS.download, "Export", this.handleExport.bind(this)); const loadDefaultsBtn = this.createFooterButton(ICONS.library_add, "Load Defaults", this.loadDefaultPresets.bind(this)); const deleteAllBtn = this.createFooterButton(ICONS.delete_forever, "Delete All", this.deleteAllPresets.bind(this), `${USPM_UI_PREFIX}-footer-btn-danger`); footer.appendChild(importBtn); footer.appendChild(exportBtn); footer.appendChild(loadDefaultsBtn); footer.appendChild(deleteAllBtn); this.ui.managerWindow.appendChild(footer); this.ui.resizeHandle = document.createElement('div'); this.ui.resizeHandle.className = `${USPM_UI_PREFIX}-resize-handle`; this.ui.resizeHandle.innerHTML = '↔'; this.ui.managerWindow.appendChild(this.ui.resizeHandle); this.makeResizable(this.ui.resizeHandle, this.ui.managerWindow); document.body.appendChild(this.ui.managerWindow); this.makeDraggable(header, this.ui.managerWindow); this.loadWindowPosition(); this.loadWindowSize(); this.ensureWindowInViewport(); }
        createHeaderButton(icon, title, onClickHandler) { const btn = document.createElement('button'); btn.className = `${USPM_UI_PREFIX}-icon-button ${USPM_UI_PREFIX}-header-action-btn`; btn.title = title; btn.appendChild(createIcon(icon)); btn.onclick = onClickHandler; return btn; }
        sortPresetsByName() { this.presets.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); this.savePresets(); this.renderPresetList(); UdioIntegration.refreshIntegratedUI(); }
        createFooterButton(icon, title, onClickHandler, extraClass = '') { const btn = document.createElement('button'); btn.className = `${USPM_UI_PREFIX}-icon-button ${USPM_UI_PREFIX}-footer-btn ${extraClass}`; btn.title = title; btn.appendChild(createIcon(icon)); btn.onclick = onClickHandler; return btn; }
        toggleManagerWindow() { if (!this.ui.managerWindow || !document.body.contains(this.ui.managerWindow)) { this.createManagerWindow(); } if (this.editingPresetIndex !== null) { this.cancelEditPreset(); } const win = this.ui.managerWindow; const isVisible = win.style.display === 'block'; win.style.display = isVisible ? 'none' : 'block'; if (!isVisible) { this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE; this.managerCurrentPage = 1; this.loadWindowSize(); this.renderPresetList(); if (this.ui.newPresetNameInput) this.ui.newPresetNameInput.focus(); this.ensureWindowInViewport(); } }
        makeDraggable(dragHandle, elementToDrag) { dragHandle.onmousedown = (e) => { if (e.target.closest(`.${USPM_UI_PREFIX}-icon-button, .${USPM_UI_PREFIX}-resize-handle, .${USPM_UI_PREFIX}-header-title`)) { if (e.target.closest(`.${USPM_UI_PREFIX}-header-title`) || e.target.classList.contains(`${USPM_UI_PREFIX}-drag-handle-icon`)) {} else return; } e.preventDefault(); this.isDragging = true; const rect = elementToDrag.getBoundingClientRect(); this.dragOffsetX = e.clientX - rect.left; this.dragOffsetY = e.clientY - rect.top; document.onmousemove = this.dragElement.bind(this); document.onmouseup = this.stopDrag.bind(this); dragHandle.style.cursor = 'grabbing'; const dragIcon = dragHandle.querySelector(`.${USPM_UI_PREFIX}-drag-handle-icon`); if(dragIcon) dragIcon.style.cursor = 'grabbing'; }; }
        dragElement(e) { if (!this.isDragging || !this.ui.managerWindow) return; e.preventDefault(); this.ui.managerWindow.style.left = `${e.clientX - this.dragOffsetX}px`; this.ui.managerWindow.style.top = `${e.clientY - this.dragOffsetY}px`; }
        stopDrag() { if (!this.isDragging) return; this.isDragging = false; document.onmouseup = null; document.onmousemove = null; const dragHandle = this.ui.managerWindow?.querySelector(`.${USPM_UI_PREFIX}-header`); if(dragHandle) dragHandle.style.cursor = 'grab'; const dragIcon = this.ui.managerWindow?.querySelector(`.${USPM_UI_PREFIX}-drag-handle-icon`); if(dragIcon) dragIcon.style.cursor = 'grab'; this.saveWindowPosition(); this.ensureWindowInViewport(); }
        makeResizable(resizeHandle, elementToResize) { resizeHandle.onmousedown = (e) => { e.preventDefault(); e.stopPropagation(); this.isResizing = true; this.resizeStartX = e.clientX; this.resizeStartWidth = parseInt(document.defaultView.getComputedStyle(elementToResize).width, 10); document.body.style.cursor = 'ew-resize'; resizeHandle.style.cursor = 'ew-resize'; document.onmousemove = this.resizeElement.bind(this); document.onmouseup = this.stopResize.bind(this); }; }
        resizeElement(e) { if (!this.isResizing || !this.ui.managerWindow) return; e.preventDefault(); const cs = document.defaultView.getComputedStyle(this.ui.managerWindow); const minWidth = parseInt(cs.minWidth, 10) || 300; const maxWidth = window.innerWidth * 0.95; let newWidth = this.resizeStartWidth + (e.clientX - this.resizeStartX); newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth)); this.ui.managerWindow.style.width = `${newWidth}px`; }
        stopResize() { if (!this.isResizing) return; this.isResizing = false; document.onmousemove = null; document.onmouseup = null; if (this.ui.resizeHandle) this.ui.resizeHandle.style.cursor = 'ew-resize'; document.body.style.cursor = 'default'; this.saveWindowSize(); this.ensureWindowInViewport(); }
        updateVisibleItemsInManager() { if (this.ui.managerWindow && this.ui.managerWindow.style.display !== 'none') { this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE; this.renderPresetList(); } }
        saveWindowSize() { if (!this.ui.managerWindow || !this.config.uiSizeStorageKey) return; const size = { width: this.ui.managerWindow.style.width }; if (size.width) GM_setValue(this.config.uiSizeStorageKey, JSON.stringify(size)); }
        applyDefaultWidth() { if (!this.ui.managerWindow) return; this.ui.managerWindow.style.width = "1200px"; }
        setDefaultSize() { if (!this.ui.managerWindow) return; this.applyDefaultWidth(); }
        loadWindowSize() { if (!this.ui.managerWindow || !this.config.uiSizeStorageKey) return this.setDefaultSize(); const storedSize = GM_getValue(this.config.uiSizeStorageKey); if (storedSize) { try { const size = JSON.parse(storedSize); if (size.width && CSS.supports('width', size.width)) this.ui.managerWindow.style.width = size.width; else this.applyDefaultWidth(); } catch (e) { this.setDefaultSize(); } } else { this.setDefaultSize(); } this.updateVisibleItemsInManager(); }
        saveWindowPosition() { if(!this.ui.managerWindow) return; const pos = { top: this.ui.managerWindow.offsetTop, left: this.ui.managerWindow.offsetLeft }; GM_setValue(this.config.uiPositionStorageKey, JSON.stringify(pos)); }
        loadWindowPosition() { if(!this.ui.managerWindow) return; const storedPos = GM_getValue(this.config.uiPositionStorageKey); if (storedPos) { try { const pos = JSON.parse(storedPos); if (typeof pos.top === 'number' && typeof pos.left === 'number') { this.ui.managerWindow.style.top = `${pos.top}px`; this.ui.managerWindow.style.left = `${pos.left}px`; } else { this.setDefaultPosition(); } } catch (e) { this.setDefaultPosition(); } } else { this.setDefaultPosition(); } }
        setDefaultPosition() { if(!this.ui.managerWindow) return; const winWidth = parseInt(this.ui.managerWindow.style.width || "1200"); const winHeight = this.ui.managerWindow.offsetHeight; let defaultTop = (window.innerHeight - winHeight) / 2 ; let defaultLeft = (window.innerWidth - winWidth) / 2; defaultTop = Math.max(10, Math.min(defaultTop, window.innerHeight - winHeight - 10)); defaultLeft = Math.max(10, Math.min(defaultLeft, window.innerWidth - winWidth - 10)); this.ui.managerWindow.style.top = `${Math.round(defaultTop)}px`; this.ui.managerWindow.style.left = `${Math.round(defaultLeft)}px`; }
        ensureWindowInViewport() { if (!this.ui.managerWindow) return; const win = this.ui.managerWindow; const cs = window.getComputedStyle(win); let currentL = parseFloat(win.style.left); if (isNaN(currentL)) currentL = (window.innerWidth - (parseFloat(win.style.width) || parseFloat(cs.minWidth) || 0)) / 2; let currentT = parseFloat(win.style.top); if (isNaN(currentT)) currentT = (window.innerHeight - win.offsetHeight) / 2; let rect; const originalDisplay = win.style.display; if (originalDisplay === 'none') { win.style.visibility = 'hidden'; win.style.display = 'block'; rect = win.getBoundingClientRect(); win.style.display = originalDisplay; win.style.visibility = 'visible'; } else { rect = win.getBoundingClientRect(); } const vpW = window.innerWidth; const vpH = window.innerHeight; let newL = currentL; let newT = currentT; if (rect.left < 0) newL = 0; if (rect.right > vpW) newL = Math.max(0, vpW - rect.width); if (rect.top < 0) newT = 0; if (rect.bottom > vpH) newT = Math.max(0, vpH - rect.height); if (Math.round(newL) !== Math.round(currentL) || Math.round(newT) !== Math.round(currentT)) { win.style.left = `${Math.round(newL)}px`; win.style.top = `${Math.round(newT)}px`; this.saveWindowPosition(); } let currentWidth = rect.width; let newWidth = currentWidth; const minAllowedWidth = parseFloat(cs.minWidth) || 300; const maxAllowedWidth = parseFloat(cs.maxWidth) || (vpW * 0.95); if (currentWidth > vpW - 20) newWidth = vpW - 20; newWidth = Math.max(minAllowedWidth, Math.min(newWidth, maxAllowedWidth)); if (Math.round(newWidth) !== Math.round(currentWidth)) { if(Math.round(newWidth) !== Math.round(currentWidth)) win.style.width = `${Math.round(newWidth)}px`; this.saveWindowSize(); } }
        showConfirm(message, onConfirm) { if (!sharedConfirmDialog.dialog) sharedConfirmDialog.create(); sharedConfirmDialog.show(message, onConfirm); }
        handleExport() { const dataStr = JSON.stringify(this.presets, null, 2); const blob = new Blob([dataStr], {type: "application/json"}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = this.config.exportFileName; a.click(); URL.revokeObjectURL(url); a.remove(); }
        handleImport() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const text = await file.text(); try { const imported = JSON.parse(text); if (Array.isArray(imported) && imported.every(p => typeof p === 'object' && 'name' in p && 'value' in p)) { const existingNames = new Set(this.presets.map(p => p.name.toLowerCase())); let added = 0; imported.reverse().forEach(pNew => { if (typeof pNew.name === 'string' && typeof pNew.value === 'string' && !existingNames.has(pNew.name.toLowerCase())) { this.presets.unshift({name: pNew.name.trim(), value: pNew.value.trim()}); existingNames.add(pNew.name.toLowerCase()); added++; } }); this.savePresets(); this.renderPresetList(); UdioIntegration.refreshIntegratedUI(); alert(`Imported ${added} new presets for ${this.config.uiTitle}.`); } else { alert("Invalid file format."); } } catch (err) { alert("Error reading file: " + err.message); } }; input.click(); }
        deleteAllPresets() { this.showConfirm(`Are you sure you want to delete ALL ${this.presets.length} presets for '${this.config.uiTitle}'? This action cannot be undone.`, () => { if (this.editingPresetIndex !== null) this.cancelEditPreset(); this.presets = []; this.managerCurrentPage = 1; this.savePresets(); this.renderPresetList(); UdioIntegration.refreshIntegratedUI(); alert('All presets deleted.'); }); }
        loadDefaultPresets() { if (!this.config.defaultPresetsUrl) { alert("No default presets URL configured for this manager."); return; } const self = this; GM_xmlhttpRequest({ method: "GET", url: this.config.defaultPresetsUrl, onload: function(response) { try { if (response.status < 200 || response.status >= 300) { throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`); } const defaultData = JSON.parse(response.responseText); if (!Array.isArray(defaultData) || !defaultData.every(p => typeof p === 'object' && 'name' in p && typeof p.name === 'string' && 'value' in p && typeof p.value === 'string')) { alert("Invalid default presets data structure from URL."); return; } const existingNames = new Set(self.presets.map(p => p.name.toLowerCase())); let addedCount = 0; let skippedCount = 0; defaultData.reverse().forEach(pDefault => { const trimmedName = pDefault.name.trim(); const trimmedValue = pDefault.value.trim(); if (trimmedName && !existingNames.has(trimmedName.toLowerCase())) { self.presets.unshift({ name: trimmedName, value: trimmedValue }); existingNames.add(trimmedName.toLowerCase()); addedCount++; } else { skippedCount++; } }); if (addedCount > 0) self.savePresets(); self.renderPresetList(); UdioIntegration.refreshIntegratedUI(); alert(`Added ${addedCount} new default presets for ${self.config.uiTitle}. ${skippedCount} duplicates were skipped.`); } catch (error) { alert(`Failed to process default presets: ${error.message}`); } }, onerror: function() { alert(`Failed to load default presets for ${self.config.uiTitle}. Network error.`); } }); }
        async applyPresetToTarget(presetValue) { let targetInput; if (typeof this.config.targetInputSelector === 'function') { targetInput = this.config.targetInputSelector(); } else { targetInput = document.querySelector(this.config.targetInputSelector); } if (targetInput) { const success = await this.config.applyPreset(targetInput, presetValue, this.config.id); if (!success) { alert(`[${this.config.id}] Preset did not apply correctly.`); } } else { alert(`[${this.config.id}] Target input field not found.`); } }
    }

    const sharedConfirmDialog = { dialog: null, create: function() { this.dialog = document.createElement('div'); this.dialog.id = `${UPM_UI_PREFIX}-confirm-dialog-shared`; this.dialog.className = `${UPM_UI_PREFIX}-modal-overlay`; this.dialog.style.display = 'none'; const box = document.createElement('div'); box.className = `${UPM_UI_PREFIX}-dialog-box`; const msgP = document.createElement('p'); msgP.id = `${UPM_UI_PREFIX}-confirm-msg-shared`; const btnsDiv = document.createElement('div'); btnsDiv.className = `${UPM_UI_PREFIX}-dialog-buttons`; const yesBtn = document.createElement('button'); yesBtn.id = `${UPM_UI_PREFIX}-confirm-yes-shared`; yesBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-danger`; yesBtn.appendChild(createIcon(ICONS.confirm)); yesBtn.appendChild(document.createTextNode(' Confirm')); const noBtn = document.createElement('button'); noBtn.id = `${UPM_UI_PREFIX}-confirm-no-shared`; noBtn.className = `${UPM_UI_PREFIX}-btn ${UPM_UI_PREFIX}-btn-secondary`; noBtn.appendChild(createIcon(ICONS.cancel)); noBtn.appendChild(document.createTextNode(' Cancel')); btnsDiv.appendChild(yesBtn); btnsDiv.appendChild(noBtn); box.appendChild(msgP); box.appendChild(btnsDiv); this.dialog.appendChild(box); document.body.appendChild(this.dialog); }, show: function(message, onConfirmCallback) { if (!this.dialog) this.create(); this.dialog.querySelector(`#${UPM_UI_PREFIX}-confirm-msg-shared`).textContent = message; this.dialog.style.display = 'flex'; const yesBtn = this.dialog.querySelector(`#${UPM_UI_PREFIX}-confirm-yes-shared`); const noBtn = this.dialog.querySelector(`#${UPM_UI_PREFIX}-confirm-no-shared`); const newYes = yesBtn.cloneNode(true); yesBtn.parentNode.replaceChild(newYes, yesBtn); const newNo = noBtn.cloneNode(true); noBtn.parentNode.replaceChild(newNo, noBtn); newYes.onclick = () => { this.dialog.style.display = 'none'; onConfirmCallback(); }; newNo.onclick = () => { this.dialog.style.display = 'none'; }; } };
    const UdioIntegration = { ui: { mainCollapsibleSection: null, collapsibleHeader: null, collapsibleContent: null, promptPresetListContainer: null, styleReductionPresetListContainer: null, styleReductionSubSection: null, styleReductionManageButton: null, advancedPromptGeneratorUI: null, isAccordionOpen: GM_getValue('upmAccordionOpenState_v1', false), promptIntegratedCurrentPage: GM_getValue('upmPromptIntegratedPage_v1', 1), styleReductionIntegratedCurrentPage: GM_getValue('upmStyleReductionIntegratedPage_v1', 1), promptSearchTerm: '', styleReductionSearchTerm: '', }, promptManager: null, styleReductionManager: null, isIntegratedUISetup: false, DEBUG_MODE: false, observerInstance: null, observerOptions: { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'id', 'data-state', 'hidden'] }, _checkForUITimeout: null, _searchDebouncers: {}, UI_INJECTION_PARENT_SELECTOR: 'div.joyride-create', PROMPT_BOX_SELECTOR: 'div.relative.w-full.overflow-hidden.rounded-lg.border.bg-gray-panel.transition.border-white\\/20', UI_INJECTION_REFERENCE_NODE_SELECTOR: 'div.mt-2.flex.w-full.flex-row.items-center.justify-between.gap-2',
        log(...args) { if (this.DEBUG_MODE) { console.log(`[${UPM_UI_PREFIX} DEBUG]`, ...args); } },
        init(promptMgr, styleReductionMgr) { this.promptManager = promptMgr; this.styleReductionManager = styleReductionMgr; this.ui.isAccordionOpen = GM_getValue('upmAccordionOpenState_v1', false); this.ui.promptIntegratedCurrentPage = parseInt(GM_getValue('upmPromptIntegratedPage_v1', 1), 10) || 1; this.ui.styleReductionIntegratedCurrentPage = parseInt(GM_getValue('upmStyleReductionIntegratedPage_v1', 1), 10) || 1; this.ui.promptSearchTerm = ''; this.ui.styleReductionSearchTerm = ''; this.observerInstance = new MutationObserver((mutations) => { let triggerUICheck = false; for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes))) { if (node.nodeType === Node.ELEMENT_NODE) { if ( (node.matches && (node.matches(this.UI_INJECTION_PARENT_SELECTOR) || node.matches(this.PROMPT_BOX_SELECTOR) || node.matches(this.UI_INJECTION_REFERENCE_NODE_SELECTOR) )) || (node.querySelector && (node.querySelector(this.UI_INJECTION_PARENT_SELECTOR) || node.querySelector(this.PROMPT_BOX_SELECTOR) || node.querySelector(this.UI_INJECTION_REFERENCE_NODE_SELECTOR) )) || node.id === 'Style Reduction' || (node.querySelector && node.querySelector('#Style Reduction')) ) { triggerUICheck = true; break; } } } if (triggerUICheck) break; } else if (mutation.type === 'attributes' && (mutation.attributeName === 'style' || mutation.attributeName === 'hidden' || mutation.attributeName === 'data-state') && mutation.target.closest) { const advControlsContent = mutation.target.closest('div[data-state="open"][role="region"][id^="radix-"], div[data-state="closed"][role="region"][id^="radix-"]'); if (advControlsContent && (advControlsContent.querySelector('input[cmdk-input][placeholder*="avoid"]') || mutation.target.id?.startsWith('radix-'))) { triggerUICheck = true; } else if(mutation.target.matches && mutation.target.matches('div[data-sentry-component="Create"] > div[aria-haspopup="dialog"]')) { triggerUICheck = true; } } } if (triggerUICheck) { clearTimeout(this._checkForUITimeout); this._checkForUITimeout = setTimeout(() => this.checkForUIDisplay(), 450); } }); this.connectObserver(); },
        disconnectObserver() { if (this.observerInstance) { this.observerInstance.disconnect(); } },
        connectObserver() { if (this.observerInstance) { this.observerInstance.observe(document.body, this.observerOptions); } },
        async checkForUIDisplay() { this.disconnectObserver(); const injectionParent = document.querySelector(this.UI_INJECTION_PARENT_SELECTOR); if (!injectionParent) { if (this.isIntegratedUISetup) { this.isIntegratedUISetup = false; } this.connectObserver(); return; } const referenceNode = injectionParent.querySelector(this.UI_INJECTION_REFERENCE_NODE_SELECTOR); if (!referenceNode) { if (this.isIntegratedUISetup) { this.isIntegratedUISetup = false; } this.connectObserver(); return; } const existingSectionById = document.getElementById(`${UPM_UI_PREFIX}-main-collapsible-section`); if (!existingSectionById || !injectionParent.contains(existingSectionById)) { if (existingSectionById && !injectionParent.contains(existingSectionById)) { existingSectionById.remove(); } this.injectMainCollapsibleSection(injectionParent, referenceNode); } else { this.ui.mainCollapsibleSection = existingSectionById; this.isIntegratedUISetup = true; this.ui.collapsibleHeader = this.ui.mainCollapsibleSection.querySelector(`.${UPM_UI_PREFIX}-collapsible-header`); this.ui.collapsibleContent = this.ui.mainCollapsibleSection.querySelector(`.${UPM_UI_PREFIX}-collapsible-content`); this.ui.promptPresetListContainer = this.ui.mainCollapsibleSection.querySelector(`.${UPM_UI_PREFIX}-subsection[data-type="prompt"] .${UPM_UI_PREFIX}-integrated-preset-list-area`); this.ui.styleReductionSubSection = this.ui.mainCollapsibleSection.querySelector(`.${UPM_UI_PREFIX}-subsection[data-type="style-reduction"]`); this.ui.styleReductionPresetListContainer = this.ui.styleReductionSubSection?.querySelector(`.${UPM_UI_PREFIX}-integrated-preset-list-area`); this.ui.styleReductionManageButton = this.ui.styleReductionSubSection?.querySelector(`.${UPM_UI_PREFIX}-manage-btn`); this.ui.advancedPromptGeneratorUI = this.ui.mainCollapsibleSection.querySelector(`#${UPM_UI_PREFIX}-integrated-advanced-generator`); if (!this.ui.collapsibleHeader || !this.ui.collapsibleContent || !this.ui.promptPresetListContainer || !this.ui.styleReductionSubSection || !this.ui.styleReductionPresetListContainer || !this.ui.styleReductionManageButton || !this.ui.advancedPromptGeneratorUI) { existingSectionById.remove(); this.isIntegratedUISetup = false; this.injectMainCollapsibleSection(injectionParent, referenceNode); } else { if (this.ui.collapsibleContent) this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const icon = this.ui.collapsibleHeader?.querySelector(`.${UPM_UI_PREFIX}-expand-icon`); if (icon) icon.textContent = this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); this.refreshIntegratedUI(); } } this.connectObserver(); },
        injectMainCollapsibleSection(injectionParent, referenceNode) { const oldSectionGlobally = document.getElementById(`${UPM_UI_PREFIX}-main-collapsible-section`); if (oldSectionGlobally) { oldSectionGlobally.remove(); } this.ui.mainCollapsibleSection = document.createElement('div'); this.ui.mainCollapsibleSection.id = `${UPM_UI_PREFIX}-main-collapsible-section`; this.ui.mainCollapsibleSection.className = `${UPM_UI_PREFIX}-main-collapsible-section`; this.ui.collapsibleHeader = document.createElement('div'); this.ui.collapsibleHeader.className = `${UPM_UI_PREFIX}-collapsible-header`; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); const titleDiv = document.createElement('div'); titleDiv.className = `${UPM_UI_PREFIX}-header-title-integrated`; const iconSpan = createIcon(this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right, `${UPM_UI_PREFIX}-expand-icon`); titleDiv.appendChild(iconSpan); titleDiv.appendChild(document.createTextNode(' Prompt & Style Presets')); this.ui.collapsibleHeader.appendChild(titleDiv); this.ui.collapsibleHeader.onclick = (e) => { e.stopPropagation(); this.toggleAccordion(); }; this.ui.collapsibleContent = document.createElement('div'); this.ui.collapsibleContent.className = `${UPM_UI_PREFIX}-collapsible-content`; this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const promptSection = this.createIntegratedSubSection('prompt', 'Prompt Presets', this.promptManager); this.ui.promptPresetListContainer = promptSection.querySelector(`.${UPM_UI_PREFIX}-integrated-preset-list-area`); this.ui.collapsibleContent.appendChild(promptSection); this.ui.styleReductionSubSection = this.createIntegratedSubSection('style-reduction', 'Style Reduction Presets', this.styleReductionManager); this.ui.styleReductionPresetListContainer = this.ui.styleReductionSubSection.querySelector(`.${UPM_UI_PREFIX}-integrated-preset-list-area`); this.ui.styleReductionManageButton = this.ui.styleReductionSubSection.querySelector(`.${UPM_UI_PREFIX}-manage-btn`); this.ui.collapsibleContent.appendChild(this.ui.styleReductionSubSection); this.ui.advancedPromptGeneratorUI = UdioPromptGeneratorIntegrated.createUI(); this.ui.collapsibleContent.appendChild(this.ui.advancedPromptGeneratorUI); this.ui.mainCollapsibleSection.appendChild(this.ui.collapsibleHeader); this.ui.mainCollapsibleSection.appendChild(this.ui.collapsibleContent); Array.from(injectionParent.querySelectorAll(`.${UPM_UI_PREFIX}-custom-divider`)).forEach(d => d.remove()); injectionParent.insertBefore(this.ui.mainCollapsibleSection, referenceNode); this.isIntegratedUISetup = true; this.refreshIntegratedUI(); },
        _debouncedSearchHandler: function(type, searchTerm) { if (type === 'prompt') { if (this.ui.promptSearchTerm !== searchTerm) { this.ui.promptIntegratedCurrentPage = 1; GM_setValue('upmPromptIntegratedPage_v1', 1); } this.ui.promptSearchTerm = searchTerm; } else if (type === 'style-reduction') { if (this.ui.styleReductionSearchTerm !== searchTerm) { this.ui.styleReductionIntegratedCurrentPage = 1; GM_setValue('upmStyleReductionIntegratedPage_v1', 1); } this.ui.styleReductionSearchTerm = searchTerm; } this.refreshIntegratedUI(); },
        createIntegratedSubSection(type, title, managerInstance) { const section = document.createElement('div'); section.className = `${UPM_UI_PREFIX}-subsection`; section.dataset.type = type; const headerEl = document.createElement('h4'); headerEl.className = `${UPM_UI_PREFIX}-subsection-header`; headerEl.textContent = title; section.appendChild(headerEl); const controls = document.createElement('div'); controls.className = `${UPM_UI_PREFIX}-subsection-controls`; const manageBtn = document.createElement('button'); manageBtn.className = `${UPM_UI_PREFIX}-manage-btn`; manageBtn.appendChild(createIcon(ICONS.manage)); manageBtn.appendChild(document.createTextNode(' Manage Presets')); manageBtn.onclick = (e) => { e.stopPropagation(); managerInstance.toggleManagerWindow(); }; controls.appendChild(manageBtn); const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.className = `${UPM_UI_PREFIX}-integrated-search-input`; searchInput.placeholder = `Search ${title}...`; searchInput.setAttribute('aria-label', `Search ${title}`); if (!this._searchDebouncers[type]) { this._searchDebouncers[type] = debounce((currentSearchTerm) => { this._debouncedSearchHandler(type, currentSearchTerm); }, 300); } searchInput.addEventListener('input', (e) => { this._searchDebouncers[type](e.target.value.trim()); }); controls.appendChild(searchInput); section.appendChild(controls); const presetArea = document.createElement('div'); presetArea.className = `${UPM_UI_PREFIX}-integrated-preset-list-area`; section.appendChild(presetArea); return section; },
        toggleAccordion() { this.ui.isAccordionOpen = !this.ui.isAccordionOpen; GM_setValue('upmAccordionOpenState_v1', this.ui.isAccordionOpen); if (this.ui.collapsibleContent && this.ui.collapsibleHeader) { this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const icon = this.ui.collapsibleHeader.querySelector(`.${UPM_UI_PREFIX}-expand-icon`); if (icon) icon.textContent = this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); } },
        refreshIntegratedUI() { if (!isDataLoaded) return; if (!this.isIntegratedUISetup || !this.ui.mainCollapsibleSection || !document.body.contains(this.ui.mainCollapsibleSection)) { return; } this.renderIntegratedPresetList(this.promptManager, this.ui.promptPresetListContainer, 'prompt'); this.renderIntegratedPresetList(this.styleReductionManager, this.ui.styleReductionPresetListContainer, 'style-reduction'); const srInput = MANAGER_CONFIGS.styleReduction.targetInputSelector(); const srInputNowConsideredVisible = !!srInput; if (this.ui.styleReductionSubSection) { const header = this.ui.styleReductionSubSection.querySelector(`.${UPM_UI_PREFIX}-subsection-header`); if (header) { header.textContent = srInputNowConsideredVisible ? 'Style Reduction Presets' : 'Style Reduction Presets (Open Advanced Controls to use)'; } if (srInputNowConsideredVisible) { this.ui.styleReductionSubSection.classList.remove(`${UPM_UI_PREFIX}-subsection-disabled`); if (this.ui.styleReductionManageButton) this.ui.styleReductionManageButton.disabled = false; } else { this.ui.styleReductionSubSection.classList.add(`${UPM_UI_PREFIX}-subsection-disabled`); if (this.ui.styleReductionManageButton) this.ui.styleReductionManageButton.disabled = true; } } if (this.ui.styleReductionPresetListContainer) { const srPresetButtons = this.ui.styleReductionPresetListContainer.querySelectorAll(`.${UPM_UI_PREFIX}-integrated-preset-item`); srPresetButtons.forEach(button => button.disabled = !srInputNowConsideredVisible); const srPaginationControls = this.ui.styleReductionPresetListContainer.parentElement.querySelector(`.${UPM_UI_PREFIX}-pagination-controls-integrated`); if (srPaginationControls) { const srPaginationButtons = srPaginationControls.querySelectorAll(`.${UPM_UI_PREFIX}-page-btn-integrated`); const srItemsPerPage = MANAGER_CONFIGS.styleReduction.itemsPerPageIntegrated || 20; const srTotalPresets = this.styleReductionManager.presets.filter(p => { const searchTerm = (this.ui.styleReductionSearchTerm || '').toLowerCase().trim(); return searchTerm === '' || p.name.toLowerCase().includes(searchTerm); }).length; const srTotalPages = Math.ceil(srTotalPresets / srItemsPerPage); srPaginationButtons.forEach(button => { if (button.classList.contains(`${UPM_UI_PREFIX}-page-prev-integrated`)) { button.disabled = !srInputNowConsideredVisible || this.ui.styleReductionIntegratedCurrentPage <= 1; } else if (button.classList.contains(`${UPM_UI_PREFIX}-page-next-integrated`)) { button.disabled = !srInputNowConsideredVisible || this.ui.styleReductionIntegratedCurrentPage >= srTotalPages; } else { button.disabled = !srInputNowConsideredVisible; } }); } } },
        renderIntegratedPresetList(manager, areaContainer, type) { let currentSearchTerm = ''; if (type === 'prompt') { currentSearchTerm = (this.ui.promptSearchTerm || '').trim(); } else if (type === 'style-reduction') { currentSearchTerm = (this.ui.styleReductionSearchTerm || '').trim(); } if (!this.isIntegratedUISetup || !manager || !areaContainer || !areaContainer.isConnected) return; areaContainer.innerHTML = ''; const listElement = document.createElement('div'); listElement.className = `${UPM_UI_PREFIX}-integrated-preset-list`; areaContainer.appendChild(listElement); let presetsToConsider = manager.presets; if (currentSearchTerm !== '') { const searchTermLower = currentSearchTerm.toLowerCase(); presetsToConsider = manager.presets.filter(preset => preset.name.toLowerCase().includes(searchTermLower) ); } const itemsPerPage = (type === 'prompt' ? MANAGER_CONFIGS.prompt.itemsPerPageIntegrated : MANAGER_CONFIGS.styleReduction.itemsPerPageIntegrated) || 20; let currentPage = (type === 'prompt') ? this.ui.promptIntegratedCurrentPage : this.ui.styleReductionIntegratedCurrentPage; const totalFilteredPresets = presetsToConsider.length; const totalPages = Math.ceil(totalFilteredPresets / itemsPerPage); if (currentPage > totalPages && totalPages > 0) currentPage = totalPages; if (currentPage < 1 && totalPages > 0) currentPage = 1; else if (totalPages === 0 && totalFilteredPresets === 0) currentPage = 1; if (type === 'prompt') { this.ui.promptIntegratedCurrentPage = currentPage; GM_setValue('upmPromptIntegratedPage_v1', currentPage); } else { this.ui.styleReductionIntegratedCurrentPage = currentPage; GM_setValue('upmStyleReductionIntegratedPage_v1', currentPage); } const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const presetsToDisplay = presetsToConsider.slice(startIndex, endIndex); if (totalFilteredPresets === 0) { listElement.innerHTML = `<p class="${UPM_UI_PREFIX}-no-presets-integrated">${currentSearchTerm !== '' ? 'No presets match your search.' : 'No presets. Click "Manage Presets".'}</p>`; const existingPagination = areaContainer.querySelector(`.${UPM_UI_PREFIX}-pagination-controls-integrated`); if (existingPagination) existingPagination.classList.add(`${UPM_UI_PREFIX}-hidden`); return; } let isSectionCurrentlyActive = true; if (type === 'style-reduction') { isSectionCurrentlyActive = !!(MANAGER_CONFIGS.styleReduction.targetInputSelector()); } presetsToDisplay.forEach(preset => { const button = document.createElement('button'); button.className = `${UPM_UI_PREFIX}-integrated-preset-item`; button.title = `Apply: ${preset.name}\nValue: ${preset.value}`; const textWrapper = document.createElement('span'); textWrapper.className = `${UPM_UI_PREFIX}-preset-text-wrapper`; textWrapper.textContent = preset.name; button.appendChild(textWrapper); if (type === 'style-reduction' && !isSectionCurrentlyActive) { button.disabled = true; } button.onclick = async (e) => { e.stopPropagation(); button.classList.add(`${UPM_UI_PREFIX}-applying`); await this.handleApplyPreset(type, preset.value); await new Promise(resolve => setTimeout(resolve, 100)); button.classList.remove(`${UPM_UI_PREFIX}-applying`); }; listElement.appendChild(button); }); let paginationControls = areaContainer.querySelector(`.${UPM_UI_PREFIX}-pagination-controls-integrated`); if (!paginationControls) { paginationControls = document.createElement('div'); paginationControls.className = `${UPM_UI_PREFIX}-pagination-controls-integrated`; areaContainer.appendChild(paginationControls); } else { paginationControls.innerHTML = ''; } if (totalPages > 1) { paginationControls.classList.remove(`${UPM_UI_PREFIX}-hidden`); const prevBtn = document.createElement('button'); prevBtn.className = `${UPM_UI_PREFIX}-page-btn-integrated ${UPM_UI_PREFIX}-page-prev-integrated`; prevBtn.appendChild(createIcon(ICONS.prev)); const pageInfoEl = document.createElement('span'); pageInfoEl.className = `${UPM_UI_PREFIX}-page-info-integrated`; pageInfoEl.textContent = `Page ${currentPage} / ${totalPages}`; const nextBtn = document.createElement('button'); nextBtn.className = `${UPM_UI_PREFIX}-page-btn-integrated ${UPM_UI_PREFIX}-page-next-integrated`; nextBtn.appendChild(createIcon(ICONS.next)); if (type === 'style-reduction' && !isSectionCurrentlyActive) { prevBtn.disabled = true; nextBtn.disabled = true; } else { prevBtn.disabled = currentPage <= 1; nextBtn.disabled = currentPage >= totalPages; } prevBtn.onclick = (e) => { e.stopPropagation(); if (type === 'prompt' && this.ui.promptIntegratedCurrentPage > 1) { this.ui.promptIntegratedCurrentPage--; GM_setValue('upmPromptIntegratedPage_v1', this.ui.promptIntegratedCurrentPage); } else if (type === 'style-reduction' && this.ui.styleReductionIntegratedCurrentPage > 1) { this.ui.styleReductionIntegratedCurrentPage--; GM_setValue('upmStyleReductionIntegratedPage_v1', this.ui.styleReductionIntegratedCurrentPage); } this.renderIntegratedPresetList(manager, areaContainer, type); }; nextBtn.onclick = (e) => { e.stopPropagation(); if (type === 'prompt' && this.ui.promptIntegratedCurrentPage < totalPages) { this.ui.promptIntegratedCurrentPage++; GM_setValue('upmPromptIntegratedPage_v1', this.ui.promptIntegratedCurrentPage); } else if (type === 'style-reduction' && this.ui.styleReductionIntegratedCurrentPage < totalPages) { this.ui.styleReductionIntegratedCurrentPage++; GM_setValue('upmStyleReductionIntegratedPage_v1', this.ui.styleReductionIntegratedCurrentPage); } this.renderIntegratedPresetList(manager, areaContainer, type); }; paginationControls.appendChild(prevBtn); paginationControls.appendChild(pageInfoEl); paginationControls.appendChild(nextBtn); } else { paginationControls.classList.add(`${UPM_UI_PREFIX}-hidden`); } },
        async handleApplyPreset(type, presetValue) { let targetInput, managerId; if (type === 'prompt') { targetInput = MANAGER_CONFIGS.prompt.targetInputSelector(); managerId = MANAGER_CONFIGS.prompt.id; } else { targetInput = MANAGER_CONFIGS.styleReduction.targetInputSelector(); managerId = MANAGER_CONFIGS.styleReduction.id; } if (targetInput) { await applyReactControlledInputPreset(targetInput, presetValue, `${managerId}-integrated`); } else { if (type === 'style-reduction') alert("Style Reduction input not found or not visible."); } }
    };
    const MANAGER_CONFIGS_STANDALONE = { prompt: { id: 'prompt', uiTitle: 'Udio Prompt Presets', storageKey: 'udioPromptPresets_v2', uiPositionStorageKey: `udioPrompt_${USPM_UI_PREFIX}_uiPos_v1`, uiSizeStorageKey: `udioPrompt_${USPM_UI_PREFIX}_uiSize_v1`, exportFileName: 'udio_prompts.json', defaultPresetsUrl: 'https://lyricism.neocities.org/data/prompts.json', targetInputSelector: MANAGER_CONFIGS.prompt.targetInputSelector, applyPreset: applyReactControlledInputPreset, }, styleReduction: { id: 'style-reduction', uiTitle: 'Udio Style Reduction Presets', storageKey: 'udioStyleReductionPresets_v4', uiPositionStorageKey: `udioStyleReduction_${USPM_UI_PREFIX}_uiPos_v1`, uiSizeStorageKey: `udioStyleReduction_${USPM_UI_PREFIX}_uiSize_v1`, exportFileName: 'udio_stylereduction.json', defaultPresetsUrl: 'https://lyricism.neocities.org/data/stylereduction.json', targetInputSelector: MANAGER_CONFIGS.styleReduction.targetInputSelector, applyPreset: applyReactControlledInputPreset, } };

    GM_addStyle(`
        :root { --udio-accent-color: ${UDIO_ACCENT_COLOR}; --upm-accent-color: var(--udio-accent-color); --upm-bg-primary: #18181b; --upm-bg-secondary: #27272a; --upm-bg-tertiary: #3f3f46; --upm-text-primary: #e4e4e7; --upm-text-secondary: #a1a1aa; --upm-border-primary: #3f3f46; --upm-border-input: #52525b; --uspm-accent-color: var(--upm-accent-color); --uspm-bg-primary: var(--upm-bg-primary); --uspm-bg-secondary: var(--upm-bg-secondary); --uspm-bg-tertiary: var(--upm-bg-tertiary); --uspm-bg-item-hover: color-mix(in srgb, var(--upm-bg-tertiary) 85%, #ffffff 15%); --uspm-text-primary: var(--upm-text-primary); --uspm-text-secondary: var(--upm-text-secondary); --uspm-border-primary: var(--upm-border-primary); --uspm-border-input: var(--upm-border-input); --uspm-button-icon-color: var(--upm-text-secondary); --uspm-button-icon-hover-color: var(--upm-text-primary); --uspm-button-secondary-bg: #5a5a5a; --uspm-button-secondary-hover-bg: #6a6a6a; --uspm-preset-item-height: 60px; --uspm-preset-item-gap: 8px; --uspm-preset-list-padding: 12px; --uspm-preset-list-rows: 4; /* UPDATED */ --integrated-preset-item-gap: 8px; --integrated-preset-list-padding: 10px; }
        .material-symbols-outlined.${USPM_UI_PREFIX}-icon, .material-symbols-outlined.${UPM_UI_PREFIX}-icon { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; vertical-align: middle; line-height: 1; }
        .${UPM_UI_PREFIX}-btn .${UPM_UI_PREFIX}-icon, .${USPM_UI_PREFIX}-text-button .${USPM_UI_PREFIX}-icon { margin-right: 5px; }
        .${USPM_UI_PREFIX}-icon-button .${USPM_UI_PREFIX}-icon { margin-right: 0; }
        .${USPM_UI_PREFIX}-window { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; position: fixed; z-index: 10005; width: 1200px; background-color: var(--uspm-bg-primary); color: var(--uspm-text-primary); border: 1px solid var(--uspm-border-primary); border-radius: 8px; box-shadow: 0 8px 25px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; min-width: 500px; }
        .${USPM_UI_PREFIX}-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background-color: var(--uspm-bg-secondary); border-bottom: 1px solid var(--uspm-border-primary); cursor: grab; flex-shrink: 0; user-select: none; }
        .${USPM_UI_PREFIX}-drag-handle-icon { color: var(--uspm-text-secondary); margin-right: 8px; cursor: grab; font-size: 22px; }
        .${USPM_UI_PREFIX}-header-title { font-size: 0.9em; font-weight: 600; color: var(--uspm-text-primary); flex-grow: 1; }
        .${USPM_UI_PREFIX}-header-controls { display: flex; align-items: center; gap: 6px; }
        .${USPM_UI_PREFIX}-icon-button { background-color: transparent; border: none; color: var(--uspm-button-icon-color); padding: 6px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s, color 0.2s; }
        .${USPM_UI_PREFIX}-icon-button:hover { background-color: var(--uspm-bg-tertiary); color: var(--uspm-button-icon-hover-color); }
        .${USPM_UI_PREFIX}-icon-button .${USPM_UI_PREFIX}-icon { font-size: 20px; }
        .${USPM_UI_PREFIX}-list-container { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--uspm-preset-item-gap); padding: var(--uspm-preset-list-padding); overflow-y: auto; height: calc( (var(--uspm-preset-item-height) * var(--uspm-preset-list-rows)) + (var(--uspm-preset-item-gap) * (var(--uspm-preset-list-rows) - 1)) + (var(--uspm-preset-list-padding) * 2) ); background-color: var(--uspm-bg-primary); flex-shrink: 0; align-content: start; box-sizing: border-box; }
        .${USPM_UI_PREFIX}-list-container::-webkit-scrollbar { width: 10px; } .${USPM_UI_PREFIX}-list-container::-webkit-scrollbar-track { background: var(--uspm-bg-secondary); border-radius: 5px; } .${USPM_UI_PREFIX}-list-container::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; border: 2px solid var(--uspm-bg-secondary); } .${USPM_UI_PREFIX}-list-container::-webkit-scrollbar-thumb:hover { background: #777; }
        .${USPM_UI_PREFIX}-preset-item { background-color: var(--uspm-bg-tertiary); border: 1px solid var(--uspm-border-primary); border-radius: 6px; padding: 10px; height: var(--uspm-preset-item-height); box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; align-items: center; position: relative; cursor: pointer; transition: background-color 0.2s; overflow: hidden; }
        .${USPM_UI_PREFIX}-preset-item:hover { background-color: var(--uspm-bg-item-hover); }
        .${USPM_UI_PREFIX}-preset-item.${USPM_UI_PREFIX}-dragging-item { opacity: 0.5; border: 1px dashed var(--uspm-accent-color); background-color: #222; cursor: grabbing; }
        .${USPM_UI_PREFIX}-preset-item.${USPM_UI_PREFIX}-drag-over-target { border-top: 3px solid var(--uspm-accent-color) !important; }
        .${USPM_UI_PREFIX}-list-container.${USPM_UI_PREFIX}-list-dragging-active .${USPM_UI_PREFIX}-preset-item:not(.${USPM_UI_PREFIX}-dragging-item):hover { background-color: #5a5a5a; }
        .${USPM_UI_PREFIX}-preset-name { font-size: 0.8em; line-height: 1.4em; color: var(--uspm-text-primary); width: 100%; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-word; text-align: center; }
        .${USPM_UI_PREFIX}-preset-item-controls { position: absolute; bottom: 4px; right: 4px; display: none; flex-direction: row; gap: 3px; background-color: rgba(30, 30, 30, 0.85); padding: 4px; border-radius: 4px; z-index: 1; }
        .${USPM_UI_PREFIX}-preset-item:hover .${USPM_UI_PREFIX}-preset-item-controls { display: flex; }
        .${USPM_UI_PREFIX}-preset-item-controls .${USPM_UI_PREFIX}-icon-button { padding: 3px; background-color: transparent; }
        .${USPM_UI_PREFIX}-preset-item-controls .${USPM_UI_PREFIX}-icon-button .${USPM_UI_PREFIX}-icon { font-size: 16px; }
        .${USPM_UI_PREFIX}-manager-pagination-controls { display: flex; justify-content: center; align-items: center; padding: 8px 12px; border-top: 1px solid var(--uspm-border-primary); background-color: var(--uspm-bg-secondary); flex-shrink: 0; gap: 10px; }
        .${USPM_UI_PREFIX}-page-btn { background-color: var(--uspm-bg-tertiary); border: 1px solid var(--uspm-border-input); color: var(--uspm-text-primary); padding: 5px 8px; border-radius: 4px; cursor: pointer; font-size: 0.8em; line-height: 1; display: inline-flex; align-items: center; }
        .${USPM_UI_PREFIX}-page-btn .${USPM_UI_PREFIX}-icon { font-size: 18px !important; margin-right:0; }
        .${USPM_UI_PREFIX}-page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
        .${USPM_UI_PREFIX}-page-btn:hover:not(:disabled) { background-color: var(--uspm-accent-color); border-color: var(--uspm-accent-color); color: white; }
        .${USPM_UI_PREFIX}-page-btn.${USPM_UI_PREFIX}-page-btn-drag-hotspot { background-color: color-mix(in srgb, var(--uspm-accent-color) 60%, black) !important; outline: 1px solid var(--uspm-accent-color); }
        .${USPM_UI_PREFIX}-page-info { font-size: 0.75em; color: var(--uspm-text-secondary); min-width: 70px; text-align: center; }
        .${USPM_UI_PREFIX}-add-area { padding: 12px; border-top: 1px solid var(--uspm-border-primary); background-color: var(--uspm-bg-secondary); display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }
        .${USPM_UI_PREFIX}-manager-pagination-controls[style*="display: flex;"] + .${USPM_UI_PREFIX}-add-area { border-top: none; }
        .${USPM_UI_PREFIX}-name-pos-container { display: flex; gap: 8px; align-items: center; }
        .${USPM_UI_PREFIX}-input.${USPM_UI_PREFIX}-name-input { flex-grow: 1; }
        .${USPM_UI_PREFIX}-input.${USPM_UI_PREFIX}-pos-input { width: 70px; max-width: 75px; text-align: right; padding: 8px; flex-shrink: 0; -moz-appearance: textfield; }
        .${USPM_UI_PREFIX}-input.${USPM_UI_PREFIX}-pos-input::-webkit-outer-spin-button, .${USPM_UI_PREFIX}-input.${USPM_UI_PREFIX}-pos-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
        .${USPM_UI_PREFIX}-pos-total { font-size: 0.8em; color: var(--uspm-text-secondary); flex-shrink: 0; margin-left: -4px; }
        .${USPM_UI_PREFIX}-add-area input[type="text"], .${USPM_UI_PREFIX}-add-area input[type="number"] { padding: 8px 10px; background-color: var(--uspm-bg-primary); border: 1px solid var(--uspm-border-input); color: var(--uspm-text-primary); border-radius: 4px; font-size: 0.85em; box-sizing: border-box; }
        .${USPM_UI_PREFIX}-add-controls-container { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
        .${USPM_UI_PREFIX}-text-button { background-color: var(--uspm-accent-color); color: white; border: none; padding: 7px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85em; display: inline-flex; align-items: center; gap: 5px; transition: background-color 0.2s; line-height: 1.2; }
        .${USPM_UI_PREFIX}-text-button:hover { background-color: color-mix(in srgb, var(--uspm-accent-color) 85%, black); }
        .${USPM_UI_PREFIX}-text-button .${USPM_UI_PREFIX}-icon { font-size: 18px; }
        .${USPM_UI_PREFIX}-text-button.secondary { background-color: var(--uspm-button-secondary-bg); }
        .${USPM_UI_PREFIX}-text-button.secondary:hover { background-color: var(--uspm-button-secondary-hover-bg); }
        .${USPM_UI_PREFIX}-footer { display: flex; justify-content: flex-end; align-items: center; padding: 8px 12px; padding-right: 28px; border-top: 1px solid var(--uspm-border-primary); background-color: var(--uspm-bg-secondary); flex-shrink: 0; gap: 8px; position: relative; }
        .${USPM_UI_PREFIX}-footer-btn-danger { border-color: color-mix(in srgb, var(--uspm-accent-color) 60%, black); color: color-mix(in srgb, var(--uspm-accent-color) 80%, white); }
        .${USPM_UI_PREFIX}-footer-btn-danger:hover { background-color: var(--uspm-accent-color); border-color: var(--uspm-accent-color); color: white; }
        .${USPM_UI_PREFIX}-resize-handle { width: 20px; height: 20px; position: absolute; bottom: 2px; right: 2px; cursor: ew-resize; display: flex; align-items: center; justify-content: center; color: var(--uspm-text-secondary); user-select: none; font-size: 16px; line-height: 1; z-index: 10; }
        .${USPM_UI_PREFIX}-resize-handle:hover { color: var(--uspm-text-primary); }
        .${USPM_UI_PREFIX}-no-presets { text-align: center; padding: 20px; color: var(--uspm-text-secondary); grid-column: 1 / -1; font-size: 0.9em;}
        .${UPM_UI_PREFIX}-main-collapsible-section { width: 100%; background-color: var(--upm-bg-primary); border: 1px solid var(--upm-border-primary); border-radius: 8px; margin-top: 10px; box-sizing: border-box; }
        .${UPM_UI_PREFIX}-collapsible-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background-color: var(--upm-bg-secondary); border-bottom: 1px solid transparent; cursor: pointer; user-select: none; border-top-left-radius: 7px; border-top-right-radius: 7px; }
        .${UPM_UI_PREFIX}-collapsible-header.open { border-bottom-color: var(--upm-border-primary); }
        .${UPM_UI_PREFIX}-collapsible-header:hover { background-color: var(--upm-bg-tertiary); }
        .${UPM_UI_PREFIX}-header-title-integrated { font-size: 0.95em; font-weight: 600; color: var(--upm-text-primary); display: flex; align-items: center; }
        .${UPM_UI_PREFIX}-expand-icon { font-size: 22px !important; transition: transform 0.2s ease-in-out; margin-right: 8px; }
        .${UPM_UI_PREFIX}-collapsible-content { padding: 15px; display: none; }
        .${UPM_UI_PREFIX}-collapsible-header.open + .${UPM_UI_PREFIX}-collapsible-content { display: block; }
        .${UPM_UI_PREFIX}-subsection { margin-bottom: 20px; } .${UPM_UI_PREFIX}-subsection:last-child { margin-bottom: 0; }
        .${UPM_UI_PREFIX}-subsection-header { font-size: 0.8em; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--upm-text-secondary); margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px solid var(--upm-border-primary); }
        .${UPM_UI_PREFIX}-subsection-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 10px; }
        .${UPM_UI_PREFIX}-manage-btn { background-color: var(--upm-bg-tertiary); color: var(--upm-text-primary); border: 1px solid var(--upm-border-input); padding: 6px 10px; border-radius: 4px; font-size: 0.8em; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; }
        .${UPM_UI_PREFIX}-manage-btn:hover { background-color: var(--upm-border-input); }
        .${UPM_UI_PREFIX}-manage-btn .${UPM_UI_PREFIX}-icon { font-size: 18px !important; }
        .${UPM_UI_PREFIX}-integrated-search-input { flex-grow: 1; max-width: 300px; padding: 6px 10px; font-size: 0.8em; background-color: var(--upm-bg-primary); border: 1px solid var(--upm-border-input); color: var(--upm-text-primary); border-radius: 4px; box-sizing: border-box; }
        .${UPM_UI_PREFIX}-integrated-preset-list-area { }
        .${UPM_UI_PREFIX}-integrated-preset-list { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--integrated-preset-item-gap); }
        .${UPM_UI_PREFIX}-integrated-preset-item { background-color: var(--upm-bg-tertiary); color: var(--upm-text-primary); border: 1px solid var(--upm-border-input); border-radius: 5px; min-height: 42px; padding: 6px 8px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.78em; line-height: 1.35; text-align: center; cursor: pointer; transition: background-color 0.2s, border-color 0.2s, color 0.2s; overflow: hidden; }
        .${UPM_UI_PREFIX}-integrated-preset-item:hover { background-color: var(--upm-accent-color); border-color: var(--upm-accent-color); color: white; }
        .${UPM_UI_PREFIX}-preset-text-wrapper { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; word-break: break-word; width: 100%; }
        .${UPM_UI_PREFIX}-no-presets-integrated { grid-column: 1 / -1; text-align: center; color: var(--upm-text-secondary); padding: 10px 0; font-size: 0.9em; }
        .${UPM_UI_PREFIX}-pagination-controls-integrated { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; }
        .${UPM_UI_PREFIX}-page-btn-integrated { background-color: var(--upm-bg-secondary); border: 1px solid var(--upm-border-input); color: var(--upm-text-secondary); padding: 5px 8px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; }
        .${UPM_UI_PREFIX}-page-btn-integrated:hover:not(:disabled) { background-color: var(--upm-bg-tertiary); color: var(--upm-text-primary); }
        .${UPM_UI_PREFIX}-page-btn-integrated:disabled { opacity: 0.5; cursor: not-allowed; }
        .${UPM_UI_PREFIX}-page-btn-integrated .${UPM_UI_PREFIX}-icon { font-size: 18px !important; margin-right:0; }
        .${UPM_UI_PREFIX}-page-info-integrated { font-size: 0.75em; color: var(--upm-text-secondary); min-width: 60px; text-align: center; }
        .${UPM_UI_PREFIX}-integrated-advanced-generator { padding: 10px 0px; margin-top: 15px; border-top: 1px solid var(--upm-border-primary); background-color: transparent; flex-shrink: 0; }
        .${UPM_UI_PREFIX}-advanced-generator-accordion-header { font-size: 0.9rem; font-weight: 500; color: var(--upm-text-secondary); padding: 8px 0px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: color 0.2s; border-bottom: none; }
        .${UPM_UI_PREFIX}-advanced-generator-accordion-header:hover { color: var(--upm-text-primary); }
        .${UPM_UI_PREFIX}-advanced-generator-accordion-header .${UPM_UI_PREFIX}-accordion-icon { font-size: 20px !important; }
        .${UPM_UI_PREFIX}-advanced-generator-accordion-content { padding-top: 10px; border-top: 1px solid var(--upm-border-primary); margin-top: -1px; overflow: hidden; }
        .${UPM_UI_PREFIX}-integrated-controls { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
        .${UPM_UI_PREFIX}-integrated-select { background-color: var(--upm-bg-tertiary); color: var(--upm-text-primary); border: 1px solid var(--upm-border-input); padding: 6px 8px; font-size: 0.8em; border-radius: 4px; cursor: pointer; min-width: 160px; flex-grow: 1; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url('data:image/svg+xml;utf8,<svg fill="%23e0e0e0" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'); background-repeat: no-repeat; background-position: right 0.5rem center; background-size: 0.9em; }
        .${UPM_UI_PREFIX}-integrated-select:hover { border-color: var(--upm-accent-color); }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-btn-generate { background-color: var(--upm-accent-color); font-weight: 500; padding: 7px 12px !important; font-size: 0.85em !important; flex-shrink: 0; }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-btn-generate:hover { background-color: color-mix(in srgb, var(--upm-accent-color) 85%, black); }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-btn-generate .${UPM_UI_PREFIX}-icon { font-size: 17px !important; }
        .${UPM_UI_PREFIX}-integrated-mode-desc { font-size: 0.75em; color: var(--upm-text-secondary); margin-bottom: 10px; margin-top: -2px; }
        .${UPM_UI_PREFIX}-integrated-prompt-area { margin-bottom: 10px; }
        .${UPM_UI_PREFIX}-integrated-textarea { width: 100%; min-height: 45px; padding: 8px; background-color: var(--upm-bg-primary); border: 1px solid var(--upm-border-input); color: var(--upm-text-primary); border-radius: 4px; font-size: 0.85em; resize: vertical; box-sizing: border-box; margin-bottom: 8px; line-height: 1.4; }
        .${UPM_UI_PREFIX}-integrated-prompt-actions { display: flex; gap: 8px; justify-content: flex-start; }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-integrated-action-btn { padding: 6px 10px !important; font-size: 0.8em !important; }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-integrated-action-btn .${UPM_UI_PREFIX}-icon { font-size: 16px !important; }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-btn-apply.${UPM_UI_PREFIX}-integrated-action-btn { background-color: #5cb85c; }
        .${UPM_UI_PREFIX}-btn.${UPM_UI_PREFIX}-btn-apply.${UPM_UI_PREFIX}-integrated-action-btn:hover { background-color: #4cae4c; }
        .${UPM_UI_PREFIX}-integrated-lookup-controls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; padding-top: 8px; border-top: 1px solid #383838; }
        .${UPM_UI_PREFIX}-integrated-lookup-btn { background-color: var(--upm-bg-tertiary); color: #ccc; border: 1px solid var(--upm-border-input); padding: 5px 10px; font-size: 0.75em; border-radius: 4px; cursor: pointer; transition: background-color 0.2s ease; }
        .${UPM_UI_PREFIX}-integrated-lookup-btn:hover { background-color: var(--upm-bg-secondary); border-color: var(--upm-accent-color);}
        .${UPM_UI_PREFIX}-integrated-lookup-results { background-color: var(--upm-bg-primary); padding: 8px; border-radius: 5px; min-height: 25px; font-size: 0.85em; border: 1px solid var(--upm-border-input); }
        .${UPM_UI_PREFIX}-lookup-results-title { font-size: 0.85em; color: var(--upm-text-secondary); margin-bottom: 6px; font-weight: 500; }
        .${UPM_UI_PREFIX}-lookup-item-box { background-color: var(--upm-bg-secondary); padding: 6px 10px; border-radius: 4px; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; word-wrap: break-word; overflow-wrap: break-word; border: 1px solid var(--upm-border-input); }
        .${UPM_UI_PREFIX}-lookup-item-box p { font-size: 0.85rem; color: var(--upm-text-primary); margin: 0; flex-grow: 1; }
        .${UPM_UI_PREFIX}-btn-icon-only.${UPM_UI_PREFIX}-lookup-copy-btn { padding: 3px !important; background-color: transparent !important; border: none !important;}
        .${UPM_UI_PREFIX}-btn-icon-only.${UPM_UI_PREFIX}-lookup-copy-btn:hover { background-color: var(--upm-bg-tertiary) !important; }
        .${UPM_UI_PREFIX}-btn-icon-only.${UPM_UI_PREFIX}-lookup-copy-btn .${UPM_UI_PREFIX}-icon { font-size: 16px !important; }
        .${UPM_UI_PREFIX}-btn-glowing { animation: ${UPM_UI_PREFIX}-glow-animation 0.5s ease-out; }
        @keyframes ${UPM_UI_PREFIX}-glow-animation { 0% { box-shadow: 0 0 0px 0px rgba(227, 11, 93, 0.0); } 50% { box-shadow: 0 0 10px 3px rgba(227, 11, 93, 0.5); } 100% { box-shadow: 0 0 0px 0px rgba(227, 11, 93, 0.0); } }
        .${UPM_UI_PREFIX}-applying { animation: ${UPM_UI_PREFIX}-flash 0.5s ease-out; }
        @keyframes ${UPM_UI_PREFIX}-flash { 0%, 100% { background-color: var(--upm-bg-tertiary); } 50% { background-color: #4caf50; color: white; } }
        .${UPM_UI_PREFIX}-hidden { display: none !important; }
        .${UPM_UI_PREFIX}-btn { padding: 8px 14px; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; display: inline-flex; align-items: center; gap: 6px; font-size: 0.9em; transition: background-color 0.2s ease; }
        .${UPM_UI_PREFIX}-btn .${UPM_UI_PREFIX}-icon { color: white !important; font-size: 18px !important; margin-right: 3px !important; }
        .${UPM_UI_PREFIX}-btn-danger { background-color: var(--upm-accent-color); }
        .${UPM_UI_PREFIX}-btn-danger:hover { background-color: color-mix(in srgb, var(--upm-accent-color) 85%, black); }
        .${UPM_UI_PREFIX}-btn-secondary { background-color: var(--upm-button-secondary-bg); }
        .${UPM_UI_PREFIX}-btn-secondary:hover { background-color: var(--uspm-button-secondary-hover-bg); }
        .${UPM_UI_PREFIX}-modal-overlay { position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.7); display:flex;align-items:center;justify-content:center;z-index:20006 !important; }
        .${UPM_UI_PREFIX}-dialog-box { background-color: var(--upm-bg-secondary);padding:25px;border-radius:8px;box-shadow:0 5px 15px rgba(0,0,0,.5); width:350px;max-width:80vw;text-align:center;color:var(--upm-text-primary); }
        .${UPM_UI_PREFIX}-dialog-box p {margin-bottom:20px;font-size:1.1em;}
        .${UPM_UI_PREFIX}-dialog-buttons {display:flex;justify-content:space-around;}
        .${USPM_UI_PREFIX}-trigger-btn { display: none !important; }
        .${UPM_UI_PREFIX}-subsection.${UPM_UI_PREFIX}-subsection-disabled { opacity: 0.6; }
        .${UPM_UI_PREFIX}-subsection.${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-subsection-header { color: var(--upm-text-secondary) !important; }
        .${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-manage-btn, .${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-integrated-search-input, .${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-integrated-preset-item, .${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-page-btn-integrated { cursor: not-allowed !important; background-color: var(--upm-bg-secondary) !important; border-color: var(--upm-border-primary) !important; color: var(--upm-text-secondary) !important; pointer-events: none; }
        .${UPM_UI_PREFIX}-subsection-disabled .${UPM_UI_PREFIX}-integrated-preset-item:hover { transform: none !important; }
        .${USPM_UI_PREFIX}-copied-transient, .${USPM_UI_PREFIX}-applied-transient, .${UPM_UI_PREFIX}-copied-transient, .${UPM_UI_PREFIX}-applied-transient { background-color: #2ecc71 !important; color: #1a1a1d !important; }
        .${USPM_UI_PREFIX}-copied-transient .${USPM_UI_PREFIX}-icon, .${USPM_UI_PREFIX}-applied-transient .${USPM_UI_PREFIX}-icon, .${UPM_UI_PREFIX}-copied-transient .${UPM_UI_PREFIX}-icon, .${UPM_UI_PREFIX}-applied-transient .${UPM_UI_PREFIX}-icon { color: #1a1a1d !important; }
    `);

    if (!window.udioPresetManagers) window.udioPresetManagers = {};
    let mainInitializationCalled = false;

    function postDataLoadInitialization() {
        if (mainInitializationCalled && UdioIntegration.isIntegratedUISetup) { UdioIntegration.refreshIntegratedUI(); if (UdioPromptGeneratorIntegrated.ui.genreCategorySelect) { UdioPromptGeneratorIntegrated.populateGenreCategoryFilterForEmbeddedUI(UdioPromptGeneratorIntegrated.ui.genreCategorySelect); } return; }
        if (mainInitializationCalled) { setTimeout(() => UdioIntegration.checkForUIDisplay(), 200); return; }
        mainInitializationCalled = true;
        if (!window.udioPresetManagers.prompt) { window.udioPresetManagers.prompt = new PresetManager(MANAGER_CONFIGS_STANDALONE.prompt); }
        if (!window.udioPresetManagers.styleReduction) { window.udioPresetManagers.styleReduction = new PresetManager(MANAGER_CONFIGS_STANDALONE.styleReduction); }
        if (!UdioIntegration.promptManager || !UdioIntegration.styleReductionManager) { UdioIntegration.init( window.udioPresetManagers.prompt, window.udioPresetManagers.styleReduction ); }
        setTimeout(() => { UdioIntegration.checkForUIDisplay(); }, 1000);
    }

    function initializeScriptShell() {
        const mainObserver = new MutationObserver((mutations, obs) => { let relevantChangeForMainUI = false; for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes))) { if (node.nodeType === Node.ELEMENT_NODE) { if ( (node.matches && (node.matches(UdioIntegration.UI_INJECTION_PARENT_SELECTOR) || node.matches(UdioIntegration.PROMPT_BOX_SELECTOR) || node.matches(UdioIntegration.UI_INJECTION_REFERENCE_NODE_SELECTOR) )) || (node.querySelector && (node.querySelector(UdioIntegration.UI_INJECTION_PARENT_SELECTOR) || node.querySelector(UdioIntegration.PROMPT_BOX_SELECTOR) || node.querySelector(UdioIntegration.UI_INJECTION_REFERENCE_NODE_SELECTOR) )) || node.id === 'Style Reduction' || (node.querySelector && node.querySelector('#Style Reduction')) ) { relevantChangeForMainUI = true; break; } } } if (relevantChangeForMainUI) break; } else if (mutation.type === 'attributes' && (mutation.attributeName === 'style' || mutation.attributeName === 'hidden' || mutation.attributeName === 'data-state') && mutation.target.closest) { const advControlsContent = mutation.target.closest('div[data-state="open"][role="region"][id^="radix-"], div[data-state="closed"][role="region"][id^="radix-"]'); if (advControlsContent && (advControlsContent.querySelector('input[cmdk-input][placeholder*="avoid"]') || mutation.target.id?.startsWith('radix-'))) { relevantChangeForMainUI = true; } else if(mutation.target.matches && mutation.target.matches('div[data-sentry-component="Create"] > div[aria-haspopup="dialog"]')) { relevantChangeForMainUI = true; } } } if(relevantChangeForMainUI && isDataLoaded) { clearTimeout(UdioIntegration._checkForUITimeout); UdioIntegration._checkForUITimeout = setTimeout(() => UdioIntegration.checkForUIDisplay(), 400); } });
        mainObserver.observe(document.body, UdioIntegration.observerOptions);
        document.body.addEventListener('click', function(event) { if (!isDataLoaded) return; const targetElement = event.target; if (targetElement.closest) { const advancedControlsHeader = targetElement.closest('h3'); if (advancedControlsHeader && advancedControlsHeader.textContent && advancedControlsHeader.textContent.trim().toLowerCase() === 'advanced controls') { setTimeout(() => UdioIntegration.checkForUIDisplay(), 450); } if (targetElement.matches && targetElement.matches('button.px-4.py-3.text-lg.font-medium')) { const buttonText = targetElement.textContent.trim().toLowerCase(); if (buttonText === "describe your song" || buttonText === "use style reference") { setTimeout(() => UdioIntegration.checkForUIDisplay(), 450); } } } }, true);
    }

    loadPromptGenData().catch(err => { console.error("Failed to load prompt generation data.", err); });
    initializeScriptShell();

    const tryFinalInit = () => { if (isDataLoaded) { if (!mainInitializationCalled) { postDataLoadInitialization(); } else { if (!UdioIntegration.isIntegratedUISetup || !UdioIntegration.ui.mainCollapsibleSection || !document.body.contains(UdioIntegration.ui.mainCollapsibleSection) ) { UdioIntegration.checkForUIDisplay(); } else { UdioIntegration.refreshIntegratedUI(); } } } };
    if (document.readyState === 'complete' || document.readyState === 'interactive') { tryFinalInit(); } else { window.addEventListener('load', tryFinalInit); }
    setTimeout(() => { if (isDataLoaded && UdioIntegration && typeof UdioIntegration.checkForUIDisplay === 'function' && !UdioIntegration.isIntegratedUISetup) UdioIntegration.checkForUIDisplay(); }, 2500);
    setTimeout(() => { if (isDataLoaded && UdioIntegration && typeof UdioIntegration.checkForUIDisplay === 'function' && !UdioIntegration.isIntegratedUISetup) UdioIntegration.checkForUIDisplay(); }, 5000);

    console.log("Udio Enhanced Preset System v" + (typeof GM_info !== 'undefined' ? GM_info.script.version : '2.3') + " initialized.");
})();