{"version":3,"file":"3ds-atlas.mjs","sources":["../scripts/constants.mjs","../scripts/theme/presets.mjs","../scripts/theme/theme-engine.mjs","../scripts/utils/logger.mjs","../scripts/registry.mjs","../scripts/diagnostics.mjs","../scripts/troubleshooter.mjs","../scripts/updates/update-notice.mjs","../scripts/updates/update-checker.mjs","../scripts/api.mjs","../scripts/theme/theme-editor.mjs","../scripts/theme/theme-config.mjs","../scripts/settings.mjs","../3ds-atlas.mjs"],"sourcesContent":["/** @type {object} Module identification */\nexport const MODULE = {\n ID: '3ds-atlas',\n LOG_LEVEL: 0,\n TITLE: '3DS:ATLAS'\n};\n\n/** @enum {string} Settings keys for Foundry VTT game settings */\nexport const SETTINGS = {\n LOGGING_LEVEL: 'loggingLevel',\n MODULE_THEMES: 'moduleThemes',\n FORCED_THEMES: 'forcedThemes',\n CUSTOM_THEMES: 'customThemes',\n UPDATE_NOTICES: 'updateNotices',\n CHANGELOGGER: 'changelogger',\n SEEN_VERSIONS: 'seenVersions',\n NOTIFIED_AVAILABLE: 'notifiedAvailable'\n};\n\n/** @enum {string} Custom hook event names dispatched by ATLAS */\nexport const HOOKS = {\n READY: `${MODULE.ID}.ready`,\n REGISTERED: `${MODULE.ID}.moduleRegistered`,\n THEME_CHANGED: `${MODULE.ID}.themeChanged`\n};\n\n/** @type {string} DOM id prefix + CSS var namespace shared by every consuming module. */\nexport const CSS_NAMESPACE = 'atlas';\n","import { CSS_NAMESPACE } from '../constants.mjs';\n\n/** @type {Object} Arcane - deep indigo surfaces, lavender text, and antique-gold accents. */\nexport const ARCANE_COLORS = {\n bg: '#1a0a2e',\n bgLighter: '#231440',\n bgHover: '#3d2a6e',\n border: '#4a3580',\n borderLight: '#5a45a0',\n divider: '#5a45a0',\n inputBg: '#251845',\n text: '#c8b8e8',\n textDim: '#7766a0',\n textHeading: '#e8d090',\n textSecondary: '#9988c0',\n titleText: '#e8d090',\n buttonBg: '#2e1e55',\n buttonText: '#c8b8e8',\n buttonBorder: '#4a3580',\n primary: '#5fc8c8',\n today: '#d4b040',\n accent: '#b89838',\n error: '#ff5555',\n warning: '#e8a040',\n success: '#66bb88',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#d4b040',\n calloutText: '#e0c060',\n restDay: '#9988c0',\n weekdayHeader: '#c8b8e8'\n};\n\n/** @type {Object} Dark - the neutral charcoal default: grey surfaces with a blue primary. */\nexport const DARK_COLORS = {\n bg: '#1f1f1f',\n bgLighter: '#2a2a2a',\n bgHover: '#353535',\n border: '#4a4a4a',\n borderLight: '#3a3a3a',\n divider: '#414141',\n inputBg: '#303030',\n text: '#e0e0e0',\n textDim: '#999999',\n textHeading: '#eeeeee',\n textSecondary: '#aaaaaa',\n titleText: '#ffffff',\n buttonBg: '#3a3a3a',\n buttonText: '#e0e0e0',\n buttonBorder: '#4a4a4a',\n primary: '#4a90e2',\n today: '#ff6400',\n accent: '#a89060',\n error: '#f44336',\n warning: '#ffc107',\n success: '#88cc88',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#d4af37',\n calloutText: '#ffd700',\n restDay: '#8b9dc3',\n weekdayHeader: '#e0e0e0'\n};\n\n/** @type {Object} Dark Parchment - warm brown surfaces with brighter gold; lifted from Bondsmith. */\nexport const DARK_PARCHMENT_COLORS = {\n bg: '#2b2317',\n bgLighter: '#362c1d',\n bgHover: '#1f1810',\n border: '#4a3f28',\n borderLight: '#3a3020',\n divider: '#40351f',\n inputBg: '#362c1d',\n text: '#ede0c5',\n textDim: '#9d8e71',\n textHeading: '#ede0c5',\n textSecondary: '#b8a988',\n titleText: '#d8a73a',\n buttonBg: '#d8a73a',\n buttonText: '#2b2317',\n buttonBorder: '#b48424',\n primary: '#d8a73a',\n today: '#d8a73a',\n accent: '#b48424',\n error: '#c44d3f',\n warning: '#d8a73a',\n success: '#5a9e3e',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#b48424',\n calloutText: '#d8a73a',\n restDay: '#b8a988',\n weekdayHeader: '#ede0c5'\n};\n\n/** @type {Object} Frost - cold slate-blue backgrounds with icy accents. */\nexport const FROST_COLORS = {\n bg: '#a8c4d8',\n bgLighter: '#c4dae8',\n bgHover: '#98b4c8',\n border: '#6890a8',\n borderLight: '#80a8c0',\n divider: '#5880a0',\n inputBg: '#b8d0e0',\n text: '#0e1e2a',\n textDim: '#3a5a70',\n textHeading: '#081828',\n textSecondary: '#284860',\n titleText: '#081828',\n buttonBg: '#98b8cc',\n buttonText: '#0e1e2a',\n buttonBorder: '#6890a8',\n primary: '#d4884f',\n today: '#1868a0',\n accent: '#3080b0',\n error: '#b83030',\n warning: '#b08020',\n success: '#287848',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#1868a0',\n calloutText: '#105888',\n restDay: '#3a5a70',\n weekdayHeader: '#0e1e2a'\n};\n\n/** @type {Object} Gold Parchment - warm cream surfaces with gold trim; lifted from Bondsmith. */\nexport const GOLD_PARCHMENT_COLORS = {\n bg: '#e8dcc8',\n bgLighter: '#efe4d2',\n bgHover: '#ddd0bb',\n border: '#a08c64',\n borderLight: '#c2b088',\n divider: '#b0a074',\n inputBg: '#e8dcc8',\n text: '#3d3224',\n textDim: '#6d5f47',\n textHeading: '#2e2516',\n textSecondary: '#685a44',\n titleText: '#a07520',\n buttonBg: '#c8922a',\n buttonText: '#2e2516',\n buttonBorder: '#a07520',\n primary: '#c8922a',\n today: '#c8922a',\n accent: '#a07520',\n error: '#c44d3f',\n warning: '#c8922a',\n success: '#5a9e3e',\n shadow: '#786446',\n overlay: '#000000',\n calloutBorder: '#a07520',\n calloutText: '#c8922a',\n restDay: '#685a44',\n weekdayHeader: '#3d3224'\n};\n\n/** @type {Object} Hero Mancer - deep-navy backgrounds with a gold primary; lifted from Hero Mancer. */\nexport const HEROMANCER_COLORS = {\n bg: '#101626',\n bgLighter: '#182034',\n bgHover: '#1e2840',\n border: '#404e66',\n borderLight: '#404e66',\n divider: '#404e66',\n inputBg: '#182034',\n text: '#e8ecf6',\n textDim: '#a0aabe',\n textHeading: '#e8ecf6',\n textSecondary: '#a0aabe',\n titleText: '#f5c35f',\n buttonBg: '#182034',\n buttonText: '#e8ecf6',\n buttonBorder: '#404e66',\n primary: '#f5c35f',\n today: '#f5c35f',\n accent: '#f5c35f',\n error: '#f05a50',\n warning: '#ffa532',\n success: '#78c88c',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#f5c35f',\n calloutText: '#f5c35f',\n restDay: '#a0aabe',\n weekdayHeader: '#e8ecf6'\n};\n\n/** @type {Object} High Contrast - pure black and white with a bright yellow accent for accessibility. */\nexport const HIGH_CONTRAST_COLORS = {\n bg: '#000000',\n bgLighter: '#1a1a1a',\n bgHover: '#333333',\n border: '#ffffff',\n borderLight: '#cccccc',\n divider: '#666666',\n inputBg: '#1a1a1a',\n text: '#ffffff',\n textDim: '#cccccc',\n textHeading: '#ffffff',\n textSecondary: '#dddddd',\n titleText: '#ffffff',\n buttonBg: '#333333',\n buttonText: '#ffffff',\n buttonBorder: '#ffffff',\n primary: '#00aaff',\n today: '#ff8800',\n accent: '#ffcc00',\n error: '#ff4444',\n warning: '#ffcc00',\n success: '#00ff88',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ffdd00',\n calloutText: '#ffee00',\n restDay: '#99bbff',\n weekdayHeader: '#ffffff'\n};\n\n/** @type {Object} Infernal - near-black backgrounds with molten orange and red accents. */\nexport const INFERNAL_COLORS = {\n bg: '#1a0a0a',\n bgLighter: '#2a1515',\n bgHover: '#3d2020',\n border: '#5a2020',\n borderLight: '#6a3030',\n divider: '#5a2020',\n inputBg: '#201010',\n text: '#e0c0b8',\n textDim: '#8a5550',\n textHeading: '#ff8866',\n textSecondary: '#b08070',\n titleText: '#ff8866',\n buttonBg: '#351818',\n buttonText: '#e0c0b8',\n buttonBorder: '#5a2020',\n primary: '#e04030',\n today: '#ff6600',\n accent: '#ff8844',\n error: '#ff4444',\n warning: '#ffaa00',\n success: '#66aa55',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ff6600',\n calloutText: '#ff8844',\n restDay: '#b08070',\n weekdayHeader: '#e0c0b8'\n};\n\n/** @type {Object} Light - the neutral bright default: off-white surfaces with a blue primary. */\nexport const LIGHT_COLORS = {\n bg: '#f5f5f5',\n bgLighter: '#ffffff',\n bgHover: '#e8e8e8',\n border: '#d0d0d0',\n borderLight: '#e0e0e0',\n divider: '#d2d2d2',\n inputBg: '#f8f8f8',\n text: '#2c2c2c',\n textDim: '#666666',\n textHeading: '#202020',\n textSecondary: '#505050',\n titleText: '#1a1a1a',\n buttonBg: '#e8e8e8',\n buttonText: '#2c2c2c',\n buttonBorder: '#d0d0d0',\n primary: '#2a70c2',\n today: '#e05500',\n accent: '#8b7000',\n error: '#d32f2f',\n warning: '#f5b400',\n success: '#228822',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#b89527',\n calloutText: '#8b7000',\n restDay: '#5a6a8a',\n weekdayHeader: '#2c2c2c'\n};\n\n/** @type {Object} Minimalist - flat monochrome greys with a single red accent. */\nexport const MINIMALIST_COLORS = {\n bg: '#ffffff',\n bgLighter: '#f5f5f5',\n bgHover: '#e8e8e8',\n border: '#1a1a1a',\n borderLight: '#cccccc',\n divider: '#1a1a1a',\n inputBg: '#f5f5f5',\n text: '#1a1a1a',\n textDim: '#666666',\n textHeading: '#000000',\n textSecondary: '#444444',\n titleText: '#000000',\n buttonBg: '#f0f0f0',\n buttonText: '#1a1a1a',\n buttonBorder: '#1a1a1a',\n primary: '#0066cc',\n today: '#dd0000',\n accent: '#dd0000',\n error: '#dd0000',\n warning: '#dd8800',\n success: '#008844',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#dd0000',\n calloutText: '#aa0000',\n restDay: '#888888',\n weekdayHeader: '#1a1a1a'\n};\n\n/** @type {Object} Neon - black backgrounds with electric cyan, magenta, and green. */\nexport const NEON_COLORS = {\n bg: '#050505',\n bgLighter: '#111111',\n bgHover: '#1a1a1a',\n border: '#00ddff',\n borderLight: '#00aacc',\n divider: '#ff44aa',\n inputBg: '#0a0a0a',\n text: '#f0f0f0',\n textDim: '#00cc99',\n textHeading: '#ff44aa',\n textSecondary: '#00ddff',\n titleText: '#ff44aa',\n buttonBg: '#1a1a1a',\n buttonText: '#00ddff',\n buttonBorder: '#ff44aa',\n primary: '#ffea00',\n today: '#ff44aa',\n accent: '#00ff88',\n error: '#ff3366',\n warning: '#ffcc00',\n success: '#00ff88',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ffcc00',\n calloutText: '#00ff88',\n restDay: '#00cc99',\n weekdayHeader: '#00ddff'\n};\n\n/** @type {Object} Parchment - aged-paper cream with sepia text and brown accents. */\nexport const PARCHMENT_COLORS = {\n bg: '#c8aa78',\n bgLighter: '#e0cca0',\n bgHover: '#b89868',\n border: '#9a7840',\n borderLight: '#b09058',\n divider: '#907030',\n inputBg: '#d8c090',\n text: '#3c2415',\n textDim: '#8a7460',\n textHeading: '#5c3a1e',\n textSecondary: '#6b4f3a',\n titleText: '#5c3a1e',\n buttonBg: '#d4ba88',\n buttonText: '#3c2415',\n buttonBorder: '#b89050',\n primary: '#1a6470',\n today: '#a01818',\n accent: '#8b6914',\n error: '#c62828',\n warning: '#e6a000',\n success: '#5a7a2e',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#b8860b',\n calloutText: '#8b6914',\n restDay: '#7a6040',\n weekdayHeader: '#3c2415'\n};\n\n/** @type {Object} Royal - deep regal backgrounds with cream text and gold accents. */\nexport const ROYAL_COLORS = {\n bg: '#0a0e1a',\n bgLighter: '#121830',\n bgHover: '#1a2240',\n border: '#2a3050',\n borderLight: '#3a4060',\n divider: '#2a3050',\n inputBg: '#0e1220',\n text: '#d0c8b0',\n textDim: '#706848',\n textHeading: '#f0d060',\n textSecondary: '#a09878',\n titleText: '#f0d060',\n buttonBg: '#161e38',\n buttonText: '#d0c8b0',\n buttonBorder: '#2a3050',\n primary: '#d4a830',\n today: '#cc2222',\n accent: '#c09030',\n error: '#cc3333',\n warning: '#e0a030',\n success: '#408848',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#d4a830',\n calloutText: '#f0c848',\n restDay: '#8090a8',\n weekdayHeader: '#d0c8b0'\n};\n\n/** @type {Object} Sakura - soft blush-pink surfaces with rose and plum accents. */\nexport const SAKURA_COLORS = {\n bg: '#4a2838',\n bgLighter: '#6a4458',\n bgHover: '#5a3448',\n border: '#8a5c70',\n borderLight: '#9a6c80',\n divider: '#7a5060',\n inputBg: '#402030',\n text: '#f2dce4',\n textDim: '#b08898',\n textHeading: '#ffeef4',\n textSecondary: '#d0a8b4',\n titleText: '#ffeef4',\n buttonBg: '#6a4458',\n buttonText: '#f2dce4',\n buttonBorder: '#8a5c70',\n primary: '#5fb89e',\n today: '#ff5080',\n accent: '#e06080',\n error: '#e04848',\n warning: '#daa050',\n success: '#60a868',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ff5080',\n calloutText: '#f88898',\n restDay: '#d0a8b4',\n weekdayHeader: '#f2dce4'\n};\n\n/** @type {Object} Slate - cool grey-green surfaces with warm gold highlights. */\nexport const SLATE_COLORS = {\n bg: '#2b3d35',\n bgLighter: '#344840',\n bgHover: '#3e5448',\n border: '#4a6050',\n borderLight: '#546a5a',\n divider: '#44584c',\n inputBg: '#304038',\n text: '#dedad0',\n textDim: '#8a9888',\n textHeading: '#ece8dc',\n textSecondary: '#aab4a4',\n titleText: '#ece8dc',\n buttonBg: '#384e42',\n buttonText: '#dedad0',\n buttonBorder: '#4a6050',\n primary: '#8c6cb0',\n today: '#ecc850',\n accent: '#c0b898',\n error: '#d86050',\n warning: '#dab850',\n success: '#80b070',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ecc850',\n calloutText: '#daba48',\n restDay: '#aab4a4',\n weekdayHeader: '#dedad0'\n};\n\n/** @type {Object} Solarized - the Solarized base palette with magenta and violet accents. */\nexport const SOLARIZED_COLORS = {\n bg: '#002b36',\n bgLighter: '#073642',\n bgHover: '#0a4050',\n border: '#586e75',\n borderLight: '#657b83',\n divider: '#586e75',\n inputBg: '#073642',\n text: '#839496',\n textDim: '#586e75',\n textHeading: '#93a1a1',\n textSecondary: '#657b83',\n titleText: '#eee8d5',\n buttonBg: '#073642',\n buttonText: '#839496',\n buttonBorder: '#586e75',\n primary: '#d33682',\n today: '#cb4b16',\n accent: '#2aa198',\n error: '#dc322f',\n warning: '#b58900',\n success: '#859900',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#d33682',\n calloutText: '#6c71c4',\n restDay: '#657b83',\n weekdayHeader: '#839496'\n};\n\n/** @type {Object} Spellbook - slate surfaces with a gold signature; lifted from Spell Book. */\nexport const SPELLBOOK_COLORS = {\n bg: '#1b1d24',\n bgLighter: '#2a2c33',\n bgHover: '#3a3c43',\n border: '#999999',\n borderLight: '#c9c7b8',\n divider: '#555555',\n inputBg: '#3a3c43',\n text: '#f1f1f1',\n textDim: '#777777',\n textHeading: '#f1f1f1',\n textSecondary: '#bababa',\n titleText: '#9f9275',\n buttonBg: '#2a2c33',\n buttonText: '#bababa',\n buttonBorder: '#999999',\n primary: '#9f9275',\n today: '#55aaff',\n accent: '#ffc919',\n error: '#f44336',\n warning: '#ffc107',\n success: '#4caf50',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#ffc919',\n calloutText: '#55aaff',\n restDay: '#bababa',\n weekdayHeader: '#f1f1f1'\n};\n\n/** @type {Object} Steampunk - brass and tan tones with copper accents. */\nexport const STEAMPUNK_COLORS = {\n bg: '#1c1410',\n bgLighter: '#2a2018',\n bgHover: '#3a3025',\n border: '#6a5030',\n borderLight: '#7a6040',\n divider: '#5a4428',\n inputBg: '#221a14',\n text: '#d4c4a8',\n textDim: '#8a7860',\n textHeading: '#e8c878',\n textSecondary: '#b0a080',\n titleText: '#e8c878',\n buttonBg: '#30261c',\n buttonText: '#d4c4a8',\n buttonBorder: '#6a5030',\n primary: '#5a8a40',\n today: '#e09030',\n accent: '#b87333',\n error: '#cc4040',\n warning: '#e8a030',\n success: '#5a9050',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#cd8032',\n calloutText: '#e8a040',\n restDay: '#b0a080',\n weekdayHeader: '#d4c4a8'\n};\n\n/** @type {Object} Velvet - plum backgrounds with orchid accents; lifted from Bondsmith. */\nexport const VELVET_COLORS = {\n bg: '#2a1a2e',\n bgLighter: '#341f3a',\n bgHover: '#1f1422',\n border: '#4a3550',\n borderLight: '#3a2840',\n divider: '#40304a',\n inputBg: '#341f3a',\n text: '#ece0f0',\n textDim: '#a489ab',\n textHeading: '#ece0f0',\n textSecondary: '#b59ec0',\n titleText: '#c47bd8',\n buttonBg: '#c47bd8',\n buttonText: '#1f1422',\n buttonBorder: '#8e4ea2',\n primary: '#c47bd8',\n today: '#c47bd8',\n accent: '#8e4ea2',\n error: '#c44d3f',\n warning: '#d8a73a',\n success: '#5a9e3e',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#8e4ea2',\n calloutText: '#c47bd8',\n restDay: '#b59ec0',\n weekdayHeader: '#ece0f0'\n};\n\n/** @type {Object} Verdant - forest-green surfaces with gold and foliage accents. */\nexport const VERDANT_COLORS = {\n bg: '#0f1f0f',\n bgLighter: '#1a2e1a',\n bgHover: '#2a4030',\n border: '#3a5a3a',\n borderLight: '#4a6a4a',\n divider: '#3a5a3a',\n inputBg: '#152515',\n text: '#c8dcc8',\n textDim: '#6a8a6a',\n textHeading: '#d4e8b0',\n textSecondary: '#8aaa8a',\n titleText: '#d4e8b0',\n buttonBg: '#1e3420',\n buttonText: '#c8dcc8',\n buttonBorder: '#3a5a3a',\n primary: '#cc6688',\n today: '#d4a017',\n accent: '#d4a017',\n error: '#e05555',\n warning: '#e8b84d',\n success: '#55cc55',\n shadow: '#000000',\n overlay: '#000000',\n calloutBorder: '#d4a017',\n calloutText: '#ffd700',\n restDay: '#8aaa8a',\n weekdayHeader: '#c8dcc8'\n};\n\nexport const THEME_PRESETS = {\n arcane: { name: 'ATLAS.Theme.Presets.Arcane', colors: ARCANE_COLORS },\n dark: { name: 'ATLAS.Theme.Presets.Dark', colors: DARK_COLORS },\n darkParchment: { name: 'ATLAS.Theme.Presets.DarkParchment', colors: DARK_PARCHMENT_COLORS },\n frost: { name: 'ATLAS.Theme.Presets.Frost', colors: FROST_COLORS },\n goldParchment: { name: 'ATLAS.Theme.Presets.GoldParchment', colors: GOLD_PARCHMENT_COLORS },\n heromancer: { name: 'ATLAS.Theme.Presets.HeroMancer', colors: HEROMANCER_COLORS },\n highContrast: { name: 'ATLAS.Theme.Presets.HighContrast', colors: HIGH_CONTRAST_COLORS },\n infernal: { name: 'ATLAS.Theme.Presets.Infernal', colors: INFERNAL_COLORS },\n light: { name: 'ATLAS.Theme.Presets.Light', colors: LIGHT_COLORS },\n minimalist: { name: 'ATLAS.Theme.Presets.Minimalist', colors: MINIMALIST_COLORS },\n neon: { name: 'ATLAS.Theme.Presets.Neon', colors: NEON_COLORS },\n parchment: { name: 'ATLAS.Theme.Presets.Parchment', colors: PARCHMENT_COLORS },\n royal: { name: 'ATLAS.Theme.Presets.Royal', colors: ROYAL_COLORS },\n sakura: { name: 'ATLAS.Theme.Presets.Sakura', colors: SAKURA_COLORS },\n slate: { name: 'ATLAS.Theme.Presets.Slate', colors: SLATE_COLORS },\n solarized: { name: 'ATLAS.Theme.Presets.Solarized', colors: SOLARIZED_COLORS },\n spellbook: { name: 'ATLAS.Theme.Presets.Spellbook', colors: SPELLBOOK_COLORS },\n steampunk: { name: 'ATLAS.Theme.Presets.Steampunk', colors: STEAMPUNK_COLORS },\n velvet: { name: 'ATLAS.Theme.Presets.Velvet', colors: VELVET_COLORS },\n verdant: { name: 'ATLAS.Theme.Presets.Verdant', colors: VERDANT_COLORS }\n};\n\n/** @type {Array<{key: string, label: string, category: string}>} Editable color fields with display labels. */\nexport const COLOR_DEFINITIONS = [\n { key: 'bg', label: 'ATLAS.Theme.Colors.Background', category: 'backgrounds' },\n { key: 'bgLighter', label: 'ATLAS.Theme.Colors.BackgroundLighter', category: 'backgrounds' },\n { key: 'bgHover', label: 'ATLAS.Theme.Colors.BackgroundHover', category: 'backgrounds' },\n { key: 'inputBg', label: 'ATLAS.Theme.Colors.InputBackground', category: 'backgrounds' },\n { key: 'divider', label: 'ATLAS.Theme.Colors.Divider', category: 'borders' },\n { key: 'border', label: 'ATLAS.Theme.Colors.Border', category: 'borders' },\n { key: 'borderLight', label: 'ATLAS.Theme.Colors.BorderLight', category: 'borders' },\n { key: 'text', label: 'ATLAS.Theme.Colors.Text', category: 'text' },\n { key: 'textDim', label: 'ATLAS.Theme.Colors.TextDim', category: 'text' },\n { key: 'textHeading', label: 'ATLAS.Theme.Colors.TextHeading', category: 'text' },\n { key: 'textSecondary', label: 'ATLAS.Theme.Colors.TextSecondary', category: 'text' },\n { key: 'titleText', label: 'ATLAS.Theme.Colors.TitleText', category: 'text' },\n { key: 'buttonBg', label: 'ATLAS.Theme.Colors.ButtonBackground', category: 'buttons' },\n { key: 'buttonText', label: 'ATLAS.Theme.Colors.ButtonText', category: 'buttons' },\n { key: 'buttonBorder', label: 'ATLAS.Theme.Colors.ButtonBorder', category: 'buttons' },\n { key: 'primary', label: 'ATLAS.Theme.Colors.Primary', category: 'accents' },\n { key: 'today', label: 'ATLAS.Theme.Colors.Highlight', category: 'accents' },\n { key: 'accent', label: 'ATLAS.Theme.Colors.Accent', category: 'accents' },\n { key: 'error', label: 'ATLAS.Theme.Colors.Error', category: 'accents' },\n { key: 'warning', label: 'ATLAS.Theme.Colors.Warning', category: 'accents' },\n { key: 'success', label: 'ATLAS.Theme.Colors.Success', category: 'accents' },\n { key: 'calloutBorder', label: 'ATLAS.Theme.Colors.CalloutBorder', category: 'callouts' },\n { key: 'calloutText', label: 'ATLAS.Theme.Colors.CalloutText', category: 'callouts' },\n { key: 'restDay', label: 'ATLAS.Theme.Colors.RestDay', category: 'callouts' },\n { key: 'weekdayHeader', label: 'ATLAS.Theme.Colors.WeekdayHeader', category: 'callouts' },\n { key: 'shadow', label: 'ATLAS.Theme.Colors.Shadow', category: 'effects' },\n { key: 'overlay', label: 'ATLAS.Theme.Colors.Overlay', category: 'effects' }\n];\n\n/** @type {Object} Category display labels. */\nexport const COLOR_CATEGORIES = {\n backgrounds: 'ATLAS.Theme.Category.Backgrounds',\n borders: 'ATLAS.Theme.Category.Borders',\n text: 'ATLAS.Theme.Category.Text',\n buttons: 'ATLAS.Theme.Category.Buttons',\n accents: 'ATLAS.Theme.Category.Accents',\n callouts: 'ATLAS.Theme.Category.Callouts',\n effects: 'ATLAS.Theme.Category.Effects'\n};\n\n/**\n * Build the CSS variable name for a color key.\n * @param {string} key Color key\n * @returns {string}\n */\nexport function cssVar(key) {\n return `--${CSS_NAMESPACE}-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;\n}\n\n/**\n * Generate the derived CSS variables from a base palette.\n * @param {Object} colors Base palette keyed by semantic color name.\n * @returns {Object}\n */\nexport function generateDerivedColors(colors) {\n const derived = {};\n const ns = CSS_NAMESPACE;\n const { Color } = foundry.utils;\n const white = Color.fromString('#ffffff');\n const lighten = (hex, percent) => Color.fromString(hex).mix(white, percent / 100).css;\n if (colors.today) {\n const today = Color.fromString(colors.today);\n derived[`--${ns}-highlight-bg`] = today.toRGBA(0.2);\n derived[`--${ns}-current-hour`] = today.toRGBA(0.12);\n }\n if (colors.primary) {\n derived[`--${ns}-selected-bg`] = Color.fromString(colors.primary).toRGBA(0.15);\n derived[`--${ns}-primary-hover`] = lighten(colors.primary, 10);\n derived[`--${ns}-link`] = lighten(colors.primary, 15);\n }\n if (colors.shadow) {\n const shadow = Color.fromString(colors.shadow);\n derived[`--${ns}-shadow-color`] = shadow.toRGBA(0.4);\n derived[`--${ns}-shadow-light`] = shadow.toRGBA(0.2);\n }\n if (colors.overlay) derived[`--${ns}-overlay-color`] = Color.fromString(colors.overlay).toRGBA(0.5);\n if (colors.calloutBorder) derived[`--${ns}-callout-bg`] = Color.fromString(colors.calloutBorder).toRGBA(0.15);\n if (colors.error) derived[`--${ns}-error-bg`] = Color.fromString(colors.error).toRGBA(0.15);\n if (colors.success) derived[`--${ns}-positive-bg`] = Color.fromString(colors.success).toRGBA(0.2);\n if (colors.bgLighter) {\n const surface = Color.fromString(colors.bgLighter);\n derived[`--${ns}-surface-dim`] = colors.bgLighter;\n derived[`--${ns}-surface-dim-10`] = surface.toRGBA(0.1);\n derived[`--${ns}-surface-dim-25`] = surface.toRGBA(0.25);\n derived[`--${ns}-surface-dim-50`] = surface.toRGBA(0.5);\n derived[`--${ns}-surface-dim-75`] = surface.toRGBA(0.75);\n }\n derived[`--${ns}-text-on-color`] = '#ffffff';\n derived[`--${ns}-text-shadow-on-color`] = 'rgb(0 0 0 / 50%)';\n if (colors.bg && !colors.bgHover) derived[`--${ns}-bg-hover`] = lighten(colors.bg, 8);\n if (colors.buttonBg) derived[`--${ns}-button-hover`] = lighten(colors.buttonBg, 10);\n return derived;\n}\n","import { CSS_NAMESPACE, HOOKS, MODULE, SETTINGS } from '../constants.mjs';\nimport { getModule, getRegisteredModules } from '../registry.mjs';\nimport { cssVar, DARK_COLORS, generateDerivedColors, THEME_PRESETS } from './presets.mjs';\n\n/**\n * Read the per-module theme selection map.\n * @returns {Object}\n */\nexport function getModuleThemes() {\n return game.settings.get(MODULE.ID, SETTINGS.MODULE_THEMES) || {};\n}\n\n/**\n * Read the world-scoped forced-theme override map.\n * @returns {Object}\n */\nexport function getForcedThemes() {\n return game.settings.get(MODULE.ID, SETTINGS.FORCED_THEMES) || {};\n}\n\n/**\n * Read the GM-forced theme key for a module, if any.\n * @param {string} moduleId Registered module id\n * @returns {string|null}\n */\nexport function getForcedTheme(moduleId) {\n return getForcedThemes()[moduleId] || null;\n}\n\n/**\n * Set or clear the GM-forced theme for a module.\n * @param {string} moduleId Registered module id\n * @param {string|null} keyOrNull Theme key to force; null/'none' clears the override\n * @returns {Promise}\n */\nexport async function setForcedTheme(moduleId, keyOrNull) {\n if (!game.user.isGM) return;\n const forced = getForcedThemes();\n if (!keyOrNull || keyOrNull === 'none') delete forced[moduleId];\n else forced[moduleId] = keyOrNull;\n await game.settings.set(MODULE.ID, SETTINGS.FORCED_THEMES, forced);\n}\n\n/**\n * Read the map of user-created custom themes.\n * @returns {Object}\n */\nexport function getCustomThemes() {\n return game.settings.get(MODULE.ID, SETTINGS.CUSTOM_THEMES) || {};\n}\n\n/**\n * Whether a theme key refers to a user-created custom theme.\n * @param {string} key Theme key\n * @returns {boolean}\n */\nexport function isCustomThemeKey(key) {\n return !!key && key.startsWith('custom_') && !THEME_PRESETS[key];\n}\n\n/**\n * Resolve the full color object for any theme key.\n * @param {string} key Theme key\n * @returns {Object}\n */\nexport function getColorsForTheme(key) {\n if (THEME_PRESETS[key]) return { ...THEME_PRESETS[key].colors };\n if (isCustomThemeKey(key)) {\n const custom = getCustomThemes()[key];\n if (custom) return { ...(THEME_PRESETS[custom.basePreset]?.colors || DARK_COLORS), ...custom.colors };\n }\n return { ...DARK_COLORS };\n}\n\n/**\n * Normalize a module's registered sub-application scopes.\n * @param {object} themeConfig The module's registered `theme` config\n * @returns {Array<{key: string, label: string, selector: string}>}\n */\nexport function getAppScopes(themeConfig) {\n if (!themeConfig?.apps) return [];\n return Object.entries(themeConfig.apps).map(([key, v]) => (typeof v === 'string' ? { key, label: key, selector: v } : { key, label: v.label || key, selector: v.selector }));\n}\n\n/**\n * Build the CSS text for a scoped theme block.\n * @param {string} scope CSS selector\n * @param {Object} colors Color map\n * @returns {string}\n */\nfunction buildScopedCss(scope, colors) {\n const decls = [];\n for (const [key, value] of Object.entries(colors)) if (value) decls.push(`${cssVar(key)}: ${value};`);\n for (const [name, value] of Object.entries(generateDerivedColors(colors))) decls.push(`${name}: ${value};`);\n return `${scope} {\\n ${decls.join('\\n ')}\\n}`;\n}\n\n/**\n * Write or clear one scoped `