This commit is contained in:
Dayowe 2025-06-26 11:19:29 +02:00
parent 119f1905ec
commit f3baa12bd3

View File

@ -1264,6 +1264,66 @@
text-align: center;
}
}
/* Lock Icon Styles */
.dog-calculator-lock-icon {
display: inline-block;
width: 16px;
height: 16px;
margin-left: 8px;
cursor: pointer;
font-size: 14px;
line-height: 1;
vertical-align: middle;
transition: all 0.2s ease;
user-select: none;
opacity: 0.6;
}
.dog-calculator-lock-icon:hover {
opacity: 1;
transform: scale(1.1);
}
.dog-calculator-lock-icon.locked {
color: #f19a5f;
opacity: 1;
font-weight: bold;
}
.dog-calculator-lock-icon.unlocked {
color: #635870;
}
.dog-calculator-lock-icon.disabled {
opacity: 0.3;
cursor: not-allowed;
}
.dog-calculator-lock-icon.disabled:hover {
opacity: 0.3;
transform: none;
}
/* Dark theme support for lock icons */
.dog-calculator-container.theme-dark .dog-calculator-lock-icon.unlocked {
color: #b8b0c2;
}
.dog-calculator-container.theme-dark .dog-calculator-lock-icon.locked {
color: #f19a5f;
}
/* System theme support for lock icons */
@media (prefers-color-scheme: dark) {
.dog-calculator-container.theme-system .dog-calculator-lock-icon.unlocked {
color: #b8b0c2;
}
.dog-calculator-container.theme-system .dog-calculator-lock-icon.locked {
color: #f19a5f;
}
}
</style>
</head>
<body>
@ -1534,7 +1594,8 @@
name: `Food Source ${this.foodSources.length + 1}`,
energy: '',
energyUnit: this.isImperial ? 'kcalcup' : 'kcal100g',
percentage: this.foodSources.length === 0 ? 100 : 0
percentage: this.foodSources.length === 0 ? 100 : 0,
isLocked: false
};
this.foodSources.push(foodSource);
@ -1575,12 +1636,24 @@
const count = this.foodSources.length;
if (count === 0) return;
const equalPercentage = Math.floor(100 / count);
const remainder = 100 - (equalPercentage * count);
// Only redistribute among unlocked sources
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
const lockedSources = this.foodSources.filter(fs => fs.isLocked);
// Calculate total locked percentage
const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
// Available percentage for unlocked sources
const availablePercentage = 100 - totalLockedPercentage;
if (unlockedSources.length > 0) {
const equalPercentage = Math.floor(availablePercentage / unlockedSources.length);
const remainder = availablePercentage - (equalPercentage * unlockedSources.length);
this.foodSources.forEach((fs, index) => {
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
});
unlockedSources.forEach((fs, index) => {
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
});
}
// Update the UI sliders and inputs
this.updatePercentageInputs();
@ -1605,31 +1678,41 @@
this.foodSources[changedIndex].percentage = newPercentage;
// Distribute the difference among other sources proportionally
const otherSources = this.foodSources.filter((fs, index) => index !== changedIndex);
if (otherSources.length === 0) return;
const totalOtherPercentage = otherSources.reduce((sum, fs) => sum + fs.percentage, 0);
// Only redistribute among unlocked sources (excluding the changed one)
const otherUnlockedSources = this.foodSources.filter((fs, index) =>
index !== changedIndex && !fs.isLocked
);
if (totalOtherPercentage === 0) {
// If all others are 0, distribute equally
const remainingPercentage = 100 - newPercentage;
const equalShare = Math.floor(remainingPercentage / otherSources.length);
const remainder = remainingPercentage - (equalShare * otherSources.length);
if (otherUnlockedSources.length === 0) return;
// Calculate total locked percentage (excluding the changed source)
const lockedSources = this.foodSources.filter((fs, index) =>
index !== changedIndex && fs.isLocked
);
const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
// Available percentage for unlocked sources
const availablePercentage = 100 - newPercentage - totalLockedPercentage;
const totalUnlockedPercentage = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
if (totalUnlockedPercentage === 0) {
// If all other unlocked sources are 0, distribute equally
const equalShare = Math.floor(availablePercentage / otherUnlockedSources.length);
const remainder = availablePercentage - (equalShare * otherUnlockedSources.length);
otherSources.forEach((fs, index) => {
otherUnlockedSources.forEach((fs, index) => {
fs.percentage = equalShare + (index < remainder ? 1 : 0);
});
} else {
// Distribute proportionally
const targetTotal = 100 - newPercentage;
const scale = targetTotal / totalOtherPercentage;
// Distribute proportionally among unlocked sources
const scale = availablePercentage / totalUnlockedPercentage;
let distributedTotal = 0;
otherSources.forEach((fs, index) => {
if (index === otherSources.length - 1) {
otherUnlockedSources.forEach((fs, index) => {
if (index === otherUnlockedSources.length - 1) {
// Last item gets the remainder to ensure exact 100%
fs.percentage = targetTotal - distributedTotal;
fs.percentage = availablePercentage - distributedTotal;
} else {
fs.percentage = Math.round(fs.percentage * scale);
distributedTotal += fs.percentage;
@ -1700,6 +1783,7 @@
<div class="dog-calculator-percentage-group">
<label class="dog-calculator-percentage-label" for="percentage-slider-${foodSource.id}">
Percentage of Diet: <span id="percentage-display-${foodSource.id}">${foodSource.percentage}%</span>
<span class="dog-calculator-lock-icon unlocked" id="lock-${foodSource.id}" title="Lock this percentage">🔒</span>
</label>
<div class="dog-calculator-percentage-input-group">
<input type="range" id="percentage-slider-${foodSource.id}" class="dog-calculator-percentage-slider"
@ -1724,6 +1808,7 @@
const percentageSlider = document.getElementById(`percentage-slider-${id}`);
const percentageInput = document.getElementById(`percentage-input-${id}`);
const removeBtn = document.getElementById(`remove-${id}`);
const lockBtn = document.getElementById(`lock-${id}`);
if (energyInput) {
energyInput.addEventListener('input', () => {
@ -1759,6 +1844,63 @@
if (removeBtn) {
removeBtn.addEventListener('click', () => this.removeFoodSource(id));
}
if (lockBtn) {
lockBtn.addEventListener('click', () => this.toggleLock(id));
}
}
toggleLock(id) {
const foodSource = this.foodSources.find(fs => fs.id === id);
if (!foodSource) return;
// Check if we're trying to lock the last unlocked source
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
if (unlockedSources.length === 1 && unlockedSources[0].id === id) {
// Cannot lock the last unlocked source
alert('At least one food source must remain flexible for percentage adjustments.');
return;
}
// Toggle lock state
foodSource.isLocked = !foodSource.isLocked;
this.updateLockIcon(id);
this.updateLockStates();
}
updateLockIcon(id) {
const foodSource = this.foodSources.find(fs => fs.id === id);
const lockIcon = document.getElementById(`lock-${id}`);
if (!lockIcon || !foodSource) return;
if (foodSource.isLocked) {
lockIcon.classList.remove('unlocked');
lockIcon.classList.add('locked');
lockIcon.title = 'Unlock this percentage';
} else {
lockIcon.classList.remove('locked');
lockIcon.classList.add('unlocked');
lockIcon.title = 'Lock this percentage';
}
}
updateLockStates() {
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
// Update lock icon states - disable lock for last unlocked source
this.foodSources.forEach(fs => {
const lockIcon = document.getElementById(`lock-${fs.id}`);
if (lockIcon) {
if (!fs.isLocked && unlockedSources.length === 1) {
lockIcon.classList.add('disabled');
lockIcon.title = 'Cannot lock - at least one source must remain flexible';
} else {
lockIcon.classList.remove('disabled');
lockIcon.title = fs.isLocked ? 'Unlock this percentage' : 'Lock this percentage';
}
}
});
}
updateFoodSourceData(id, field, value) {
@ -2123,7 +2265,8 @@
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
calories: dailyCaloriesForThisFood
calories: dailyCaloriesForThisFood,
isLocked: fs.isLocked
});
totalDailyGrams += dailyGramsForThisFood;
@ -2154,7 +2297,7 @@
if (foodBreakdownList && foodBreakdowns.length > 1) {
const breakdownHTML = foodBreakdowns.map(breakdown => `
<div class="dog-calculator-food-result-item">
<span class="dog-calculator-food-result-label">${breakdown.name} (${breakdown.percentage}%):</span>
<span class="dog-calculator-food-result-label">${breakdown.name} (${breakdown.percentage}%${breakdown.isLocked ? ' - locked' : ''}):</span>
<span class="dog-calculator-food-result-value">${this.formatNumber(breakdown.dailyGrams, 1)} g/day</span>
</div>
`).join('');