feat: Refactor fear tracker styling to use CSS variables and enable advanced gradient customization.

This commit is contained in:
CPTN Cosmo 2025-12-19 21:53:00 +01:00
parent 4fd16bd9fb
commit 97ff59e703
3 changed files with 166 additions and 79 deletions

View file

@ -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."
}

View file

@ -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
// CSS Variables to be applied
let cssBg = 'transparent';
let cssBgSize = 'cover';
let cssBgPos = 'center';
let cssIconColor = '#ffffff';
let cssIconBgSize = 'cover';
let cssIconBgPos = 'center';
// 4. Handle Icon Color (Glyph)
if (!isSVG) {
if (iconColor && iconColor !== '#ffffff') {
cssIconColor = iconColor;
// 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) {
icon.style.background = emptyColor;
icon.style.backgroundSize = 'cover'; // Reset size for empty
icon.style.backgroundPosition = 'center'; // Reset pos for empty
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);
icon.style.background = color;
icon.style.backgroundSize = 'cover';
icon.style.backgroundPosition = 'center';
// 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) {
icon.style.background = fullColor;
icon.style.backgroundSize = `${totalIcons * 100}% 100%`;
cssBg = fullColor;
cssBgSize = `${totalIcons * 100}% 100%`;
// Calculate position
// Leftmost (index 0) = 0%
// Rightmost (index total-1) = 100%
let pos = 0;
if (totalIcons > 1) {
pos = (index / (totalIcons - 1)) * 100;
}
icon.style.backgroundPosition = `${pos}% 0%`;
icon.style.backgroundAttachment = 'local'; // Ensure it sticks to the specific element config if needed, though default is usually fine.
cssBgPos = `${pos}% 0%`;
} else {
// Solid Color
icon.style.background = fullColor;
icon.style.backgroundSize = 'cover';
icon.style.backgroundPosition = 'center';
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'));
}
}

View file

@ -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;
}