From 97ff59e7038a43c999d7cefaaa8e8118e09eced7 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Fri, 19 Dec 2025 21:53:00 +0100 Subject: [PATCH] feat: Refactor fear tracker styling to use CSS variables and enable advanced gradient customization. --- module.json | 4 +- scripts/module.js | 182 +++++++++++++++++++++++++++++----------------- styles/module.css | 59 ++++++++++++--- 3 files changed, 166 insertions(+), 79 deletions(-) diff --git a/module.json b/module.json index 7b8d589..b7a4f66 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-feartrackerplus", "title": "Daggerheart Fear Tracker Plus", - "version": "1.2.0", + "version": "1.3.0", "compatibility": { "minimum": "13", "verified": "13" @@ -32,6 +32,6 @@ ], "url": "https://github.com/cptn-cosmo/dh-feartrackerplus", "manifest": "https://github.com/cptn-cosmo/dh-feartrackerplus/releases/latest/download/module.json", - "download": "https://github.com/cptn-cosmo/dh-feartrackerplus/releases/download/1.2.0/dh-feartrackerplus.zip", + "download": "https://github.com/cptn-cosmo/dh-feartrackerplus/releases/download/1.3.0/dh-feartrackerplus.zip", "description": "Customizes the Fear Tracker for Daggerheart." } \ No newline at end of file diff --git a/scripts/module.js b/scripts/module.js index 9a3b710..2a0909b 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -95,7 +95,7 @@ Hooks.once('init', () => { scope: 'client', config: true, type: String, - default: '#ff0000', + default: '#000000', onChange: refreshFearTracker }); @@ -390,23 +390,31 @@ function injectFearCustomization(html) { let themeEnd = null; // Handle Themes - if (colorTheme !== 'custom' && colorTheme !== 'foundryborne') { + if (colorTheme !== 'custom') { // Removed check for 'foundryborne' to treat it as a preset const themes = { + 'foundryborne': { start: null, end: null, empty: 'transparent' }, 'hope-fear': { start: '#FFC107', end: '#512DA8', empty: '#2e1c4a' }, 'blood-moon': { start: '#5c0000', end: '#ff0000', empty: '#2a0000' }, 'ethereal': { start: '#00FFFF', end: '#0000FF', empty: '#002a33' }, 'toxic': { start: '#00FF00', end: '#FFFF00', empty: '#003300' } }; + const theme = themes[colorTheme]; if (theme) { + // Standard Interpolated Preset themeStart = theme.start; themeEnd = theme.end; - emptyColor = theme.empty; - // Fallback fullColor for non-interpolation uses if any fullColor = theme.start; + + emptyColor = theme.empty; } + + + + emptyColor = theme.empty; } + // Apply Scaling const scale = game.settings.get(MODULE_ID, 'trackerScale'); if (scale !== 1.0) { @@ -433,7 +441,9 @@ function injectFearCustomization(html) { icons.forEach((icon, index) => { // 1. Reset Icon State // Remove common FA prefixes just in case - icon.classList.remove('fa-skull', 'fas', 'far', 'fal', 'fad', 'fab'); + icon.classList.remove('fa-skull', 'fas', 'far', 'fal', 'fad', 'fab', 'dh-fear-plus-bg-override', 'dh-fear-plus-icon-override'); + + const isInactive = icon.classList.contains('inactive'); // Remove old SVG img if present from previous renders const oldImg = icon.querySelector('img.fear-tracker-icon'); @@ -460,90 +470,122 @@ function injectFearCustomization(html) { const newClasses = iconClass.split(' ').filter(c => c.trim() !== ''); icon.classList.add(...newClasses, 'fear-tracker-plus-custom'); - // Icon Color Application - if (iconColor && iconColor !== '#ffffff') { - // Check if it's a gradient - if (iconColor.includes('gradient')) { - icon.style.background = iconColor; - icon.style.webkitBackgroundClip = 'text'; - icon.style.backgroundClip = 'text'; - icon.style.webkitTextFillColor = 'transparent'; - icon.style.color = 'transparent'; - } else { - icon.style.color = iconColor; - } - } else { - icon.style.color = '#ffffff'; // Default White - } } // 3. Remove System Styling (Module Overrides) - // Skip this for Foundryborne to keep default system look + // Skip this for Foundryborne to keep default system look (filters/brightness) + // UPDATE: We now WANT to override Foundryborne to apply our custom gradient, BUT we want to keep its filters! if (colorTheme !== 'foundryborne') { icon.style.filter = 'none'; icon.style.opacity = '1'; - - icon.style.webkitTextFillColor = 'initial'; - icon.style.backgroundClip = 'border-box'; - icon.style.webkitBackgroundClip = 'border-box'; } - // 4. Handle Background Color - const isInactive = icon.classList.contains('inactive'); - // Skip custom coloring for Foundryborne - if (colorTheme !== 'foundryborne') { - if (isInactive) { - icon.style.background = emptyColor; - icon.style.backgroundSize = 'cover'; // Reset size for empty - icon.style.backgroundPosition = 'center'; // Reset pos for empty - } else { - // Active - if (themeStart && themeEnd && totalIcons > 1) { - // Interpolate (Preset Themes) - const factor = index / (totalIcons - 1); - const color = interpolateColor(themeStart, themeEnd, factor); - icon.style.background = color; - icon.style.backgroundSize = 'cover'; - icon.style.backgroundPosition = 'center'; - } else { - // Custom Theme - // Check if fullColor appears to be a gradient - const isGradient = fullColor.includes('gradient'); - if (isGradient && totalIcons > 0) { - icon.style.background = fullColor; - icon.style.backgroundSize = `${totalIcons * 100}% 100%`; + // CSS Variables to be applied + let cssBg = 'transparent'; + let cssBgSize = 'cover'; + let cssBgPos = 'center'; + let cssIconColor = '#ffffff'; + let cssIconBgSize = 'cover'; + let cssIconBgPos = 'center'; - // Calculate position - // Leftmost (index 0) = 0% - // Rightmost (index total-1) = 100% - let pos = 0; - if (totalIcons > 1) { - pos = (index / (totalIcons - 1)) * 100; - } + // 4. Handle Icon Color (Glyph) + if (!isSVG) { + if (iconColor && iconColor !== '#ffffff') { + cssIconColor = iconColor; - icon.style.backgroundPosition = `${pos}% 0%`; - icon.style.backgroundAttachment = 'local'; // Ensure it sticks to the specific element config if needed, though default is usually fine. - } else { - // Solid Color - icon.style.background = fullColor; - icon.style.backgroundSize = 'cover'; - icon.style.backgroundPosition = 'center'; + // Spanning Logic for Icon Gradient + if (iconColor.includes('gradient') && totalIcons > 0) { + cssIconBgSize = `${totalIcons * 100}% 100%`; + let pos = 0; + if (totalIcons > 1) { + pos = (index / (totalIcons - 1)) * 100; } + cssIconBgPos = `${pos}% 0%`; + } + } else { + cssIconColor = '#ffffff'; + } + } + + // 5. Handle Background Color (Shape) + // Enable custom coloring for ALL themes now, but toggle classes selectively for Hybrid "Foundryborne" + + // Always apply icon override for gradients + icon.classList.add('dh-fear-plus-icon-override'); + + if (colorTheme !== 'foundryborne') { + // Apply background override for all OTHER themes + icon.classList.add('dh-fear-plus-bg-override'); + } + if (isInactive) { + cssBg = emptyColor; + cssBgSize = 'cover'; + cssBgPos = 'center'; + } else { + // Active + if (themeStart && themeEnd && totalIcons > 1) { + // Interpolate (Preset Themes) + const factor = index / (totalIcons - 1); + const color = interpolateColor(themeStart, themeEnd, factor); + + // Apply Theme Color to BACKGROUND only + cssBg = color; + cssBgSize = 'cover'; + cssBgPos = 'center'; + } else { + // Custom Theme + // Check if fullColor appears to be a gradient + const isGradient = fullColor.includes('gradient'); + + if (isGradient && totalIcons > 0) { + cssBg = fullColor; + cssBgSize = `${totalIcons * 100}% 100%`; + + // Calculate position + let pos = 0; + if (totalIcons > 1) { + pos = (index / (totalIcons - 1)) * 100; + } + cssBgPos = `${pos}% 0%`; + } else { + // Solid Color + cssBg = fullColor; + cssBgSize = 'cover'; + cssBgPos = 'center'; } } } - // 5. Handle Shape + // } + + // 6. Handle Shape let borderRadius = '50%'; if (iconShape === 'rounded') borderRadius = '20%'; else if (iconShape === 'square') borderRadius = '0%'; - icon.style.borderRadius = borderRadius; + // 7. Apply CSS Variables + icon.style.setProperty('--dh-fear-bg', cssBg); + icon.style.setProperty('--dh-fear-bg-size', cssBgSize); + icon.style.setProperty('--dh-fear-bg-pos', cssBgPos); + icon.style.setProperty('--dh-fear-icon-color', cssIconColor); + icon.style.setProperty('--dh-fear-icon-bg-size', cssIconBgSize); + icon.style.setProperty('--dh-fear-icon-bg-pos', cssIconBgPos); + icon.style.setProperty('--dh-fear-border-radius', borderRadius); + + // Clean up direct styles that might interfere/confuse + icon.style.background = ''; + icon.style.color = ''; + icon.style.webkitBackgroundClip = ''; + icon.style.backgroundClip = ''; + icon.style.webkitTextFillColor = ''; + icon.style.borderRadius = ''; }); // Remove legacy container class if present fearContainer.classList.remove('fear-tracker-plus-container-gradient'); + + // Always clear container background to ensure our icon colors are visible and not obscured by system or previous styles fearContainer.style.background = 'none'; // Max Fear Animation @@ -554,15 +596,25 @@ function injectFearCustomization(html) { // If totalIcons > 0 and activeIcons === totalIcons, apply animation if (totalIcons > 0 && activeIcons === totalIcons) { + // Prevent system "Blue" overrides + fearContainer.classList.remove('complete', 'max', 'full'); + icons.forEach(icon => { icon.classList.add('fear-tracker-plus-animate'); + // Force filter reset to ensure our animation works and system color doesn't override + icon.style.filter = 'none'; }); } else { icons.forEach(icon => { icon.classList.remove('fear-tracker-plus-animate'); + // Restore filter if we touched it (only relevant if not custom theme) + if (colorTheme === 'foundryborne') { + icon.style.filter = ''; + } }); } } else { icons.forEach(icon => icon.classList.remove('fear-tracker-plus-animate')); } } + diff --git a/styles/module.css b/styles/module.css index b1ee8b7..eba885d 100644 --- a/styles/module.css +++ b/styles/module.css @@ -1,16 +1,56 @@ -.fear-tracker-plus-gradient { - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - display: inline-block; - /* Required for gradient text */ +/* CSS Variable Defaults */ +:root { + --dh-fear-bg: transparent; + --dh-fear-bg-size: cover; + --dh-fear-bg-pos: center; + --dh-fear-icon-color: #ffffff; + --dh-fear-border-radius: 50%; } -/* Ensure the icon keeps its size and doesn't collapse */ +/* Base Icon Styling */ #resource-fear i.fear-tracker-plus-custom { + position: relative; + z-index: 1; transition: all 0.5s ease; + border-radius: var(--dh-fear-border-radius); + + /* Background Shape Styling (Applied to container) - REMOVED */ + + /* Ensure icon is centered */ + display: inline-flex; + justify-content: center; + align-items: center; } +/* Scoped Override for Custom Themes */ +/* Scoped Override for Custom Themes */ +#resource-fear i.fear-tracker-plus-custom.dh-fear-plus-bg-override { + background: var(--dh-fear-bg) !important; + background-size: var(--dh-fear-bg-size) !important; + background-position: var(--dh-fear-bg-pos) !important; + background-repeat: no-repeat !important; +} + +/* Icon Glyph Styling (Applied to ::before which contains the character) */ +/* Icon Glyph Styling (Applied to ::before which contains the character) */ +#resource-fear i.fear-tracker-plus-custom.dh-fear-plus-icon-override::before { + /* Apply Gradient or Solid Color to Text */ + background: var(--dh-fear-icon-color); + background-size: var(--dh-fear-icon-bg-size, cover); + background-position: var(--dh-fear-icon-bg-pos, center); + background-repeat: no-repeat; + + -webkit-background-clip: text !important; + background-clip: text !important; + -webkit-text-fill-color: transparent !important; + color: transparent !important; + + /* Ensure it overlays correctly */ + display: inline-block; +} + + + @keyframes fearPulse { 0% { transform: scale(1); @@ -29,10 +69,5 @@ } #resource-fear i.fear-tracker-plus-animate { - display: inline-flex; - justify-content: center; - align-items: center; - vertical-align: middle; - transform-origin: center center; animation: fearPulse 2s infinite ease-in-out; } \ No newline at end of file