feat: Add back button to return to index.html in both modes
All checks were successful
Build and Deploy / build (push) Successful in 32s

This commit is contained in:
d.viti
2025-10-14 00:46:43 +02:00
parent d759bec45d
commit e5a72183b5
2 changed files with 262 additions and 155 deletions

View File

@@ -104,19 +104,28 @@
> >
<div class="container mx-auto p-4 max-w-7xl"> <div class="container mx-auto p-4 max-w-7xl">
<!-- Header --> <!-- Header -->
<div <div class="glass rounded-2xl p-8 mb-6 animate-slide-in">
class="glass rounded-2xl p-8 mb-6 animate-slide-in text-center" <div class="flex items-center justify-between mb-4">
> <a
<h1 href="index.html"
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3" class="bg-gradient-to-r from-gray-500 to-gray-600 text-white font-semibold py-2 px-4 rounded-lg hover:from-gray-600 hover:to-gray-700 transition-all flex items-center gap-2 shadow-lg"
> >
<i class="fas fa-code text-blue-600"></i> <i class="fas fa-arrow-left"></i>
Calcolatore Prezzi Software Pro <span>Torna al Menu</span>
</h1> </a>
<p class="text-gray-600 mt-2"> </div>
Sistema professionale per preventivi sviluppo software in <div class="text-center">
Italia <h1
</p> class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
>
<i class="fas fa-code text-blue-600"></i>
Calcolatore Prezzi Software Pro
</h1>
<p class="text-gray-600 mt-2">
Sistema professionale per preventivi sviluppo software
in Italia
</p>
</div>
</div> </div>
<!-- Notifiche Toast --> <!-- Notifiche Toast -->

View File

@@ -82,18 +82,27 @@
> >
<div class="container mx-auto p-4 max-w-7xl"> <div class="container mx-auto p-4 max-w-7xl">
<!-- Header --> <!-- Header -->
<div <div class="glass rounded-2xl p-8 mb-6 animate-slide-in">
class="glass rounded-2xl p-8 mb-6 animate-slide-in text-center" <div class="flex items-center justify-between mb-4">
> <a
<h1 href="index.html"
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3" class="bg-gradient-to-r from-gray-500 to-gray-600 text-white font-semibold py-2 px-4 rounded-lg hover:from-gray-600 hover:to-gray-700 transition-all flex items-center gap-2 shadow-lg"
> >
<i class="fas fa-store text-emerald-600"></i> <i class="fas fa-arrow-left"></i>
Calcolatore Prezzi - Modalità Negozio <span>Torna al Menu</span>
</h1> </a>
<p class="text-gray-600 mt-2"> </div>
Sistema di preventivi basato su articoli e quantità <div class="text-center">
</p> <h1
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
>
<i class="fas fa-store text-emerald-600"></i>
Calcolatore Prezzi - Modalità Negozio
</h1>
<p class="text-gray-600 mt-2">
Sistema di preventivi basato su articoli e quantità
</p>
</div>
</div> </div>
<!-- Notifiche Toast --> <!-- Notifiche Toast -->
@@ -170,17 +179,32 @@
</div> </div>
<div x-show="logoColor" class="mt-3"> <div x-show="logoColor" class="mt-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<span class="text-xs font-semibold text-gray-700">Colore Brand (media palette):</span> <span class="text-xs font-semibold text-gray-700"
<div :style="'background-color: ' + logoColor" class="w-10 h-10 rounded-lg border-2 border-gray-300 shadow-sm"></div> >Colore Brand (media palette):</span
<span class="text-xs font-mono font-bold" x-text="logoColor"></span> >
<div
:style="'background-color: ' + logoColor"
class="w-10 h-10 rounded-lg border-2 border-gray-300 shadow-sm"
></div>
<span
class="text-xs font-mono font-bold"
x-text="logoColor"
></span>
</div> </div>
<div x-show="colorPalette.length > 0" class="mt-2"> <div x-show="colorPalette.length > 0" class="mt-2">
<span class="text-xs text-gray-600">Palette estratta:</span> <span class="text-xs text-gray-600"
>Palette estratta:</span
>
<div class="flex gap-1 mt-1 flex-wrap"> <div class="flex gap-1 mt-1 flex-wrap">
<template x-for="color in colorPalette" :key="color"> <template
<div :style="'background-color: ' + color" x-for="color in colorPalette"
:title="color" :key="color"
class="w-8 h-8 rounded border border-gray-300 shadow-sm cursor-pointer hover:scale-110 transition-transform"></div> >
<div
:style="'background-color: ' + color"
:title="color"
class="w-8 h-8 rounded border border-gray-300 shadow-sm cursor-pointer hover:scale-110 transition-transform"
></div>
</template> </template>
</div> </div>
</div> </div>
@@ -727,153 +751,227 @@
reader.readAsDataURL(file); reader.readAsDataURL(file);
}, },
extractDominantColor(imageData) {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
extractDominantColor(imageData) { try {
const img = new Image(); const imgData = ctx.getImageData(
img.onload = () => { 0,
const canvas = document.createElement("canvas"); 0,
const ctx = canvas.getContext("2d"); canvas.width,
canvas.width = img.width; canvas.height,
canvas.height = img.height; );
ctx.drawImage(img, 0, 0); const data = imgData.data;
try { // Step 1: Collect all valid colors
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const colors = [];
const data = imgData.data; for (let i = 0; i < data.length; i += 16) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// Step 1: Collect all valid colors // Skip transparent
const colors = []; if (a < 125) continue;
for (let i = 0; i < data.length; i += 16) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// Skip transparent // Skip whites (> 235)
if (a < 125) continue; if (r > 235 && g > 235 && b > 235) continue;
// Skip whites (> 235) // Skip blacks (< 20)
if (r > 235 && g > 235 && b > 235) continue; if (r < 20 && g < 20 && b < 20) continue;
// Skip blacks (< 20) // Calculate saturation to skip grays
if (r < 20 && g < 20 && b < 20) continue; const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const saturation =
max === 0 ? 0 : (max - min) / max;
if (saturation < 0.2) continue; // Skip grays
// Calculate saturation to skip grays colors.push({ r, g, b });
const max = Math.max(r, g, b); }
const min = Math.min(r, g, b);
const saturation = max === 0 ? 0 : (max - min) / max;
if (saturation < 0.2) continue; // Skip grays
colors.push({ r, g, b }); if (colors.length === 0) {
} this.logoColor = "#10b981";
this.colorPalette = [];
console.log(
"No valid colors found, using fallback",
);
return;
}
if (colors.length === 0) { // Step 2: K-means clustering to extract palette (5-7 colors)
this.logoColor = "#10b981"; const numClusters = Math.min(
this.colorPalette = []; 6,
console.log("No valid colors found, using fallback"); Math.max(3, Math.floor(colors.length / 50)),
return; );
} const palette = this.kMeansClustering(
colors,
numClusters,
);
// Step 2: K-means clustering to extract palette (5-7 colors) // Step 3: Filter palette colors by lightness
const numClusters = Math.min(6, Math.max(3, Math.floor(colors.length / 50))); const filteredPalette = palette.filter(
const palette = this.kMeansClustering(colors, numClusters); (color) => {
const max = Math.max(
color.r,
color.g,
color.b,
);
const min = Math.min(
color.r,
color.g,
color.b,
);
const l = (max + min) / 2 / 255;
return l > 0.25 && l < 0.8; // Keep mid-range lightness
},
);
// Step 3: Filter palette colors by lightness if (filteredPalette.length === 0) {
const filteredPalette = palette.filter(color => { this.logoColor = "#10b981";
const max = Math.max(color.r, color.g, color.b); this.colorPalette = [];
const min = Math.min(color.r, color.g, color.b); console.log(
const l = (max + min) / 2 / 255; "No suitable colors in palette, using fallback",
return l > 0.25 && l < 0.80; // Keep mid-range lightness );
}); return;
}
if (filteredPalette.length === 0) { // Step 4: Calculate average color from palette
this.logoColor = "#10b981"; const avgR = Math.round(
this.colorPalette = []; filteredPalette.reduce(
console.log("No suitable colors in palette, using fallback"); (sum, c) => sum + c.r,
return; 0,
} ) / filteredPalette.length,
);
const avgG = Math.round(
filteredPalette.reduce(
(sum, c) => sum + c.g,
0,
) / filteredPalette.length,
);
const avgB = Math.round(
filteredPalette.reduce(
(sum, c) => sum + c.b,
0,
) / filteredPalette.length,
);
// Step 4: Calculate average color from palette // Store results
const avgR = Math.round(filteredPalette.reduce((sum, c) => sum + c.r, 0) / filteredPalette.length); this.logoColor = this.rgbToHex(
const avgG = Math.round(filteredPalette.reduce((sum, c) => sum + c.g, 0) / filteredPalette.length); avgR,
const avgB = Math.round(filteredPalette.reduce((sum, c) => sum + c.b, 0) / filteredPalette.length); avgG,
avgB,
);
this.colorPalette = filteredPalette.map((c) =>
this.rgbToHex(c.r, c.g, c.b),
);
// Store results console.log(
this.logoColor = this.rgbToHex(avgR, avgG, avgB); "Extracted palette:",
this.colorPalette = filteredPalette.map(c => this.rgbToHex(c.r, c.g, c.b)); this.colorPalette,
);
console.log(
"Average brand color:",
this.logoColor,
);
} catch (err) {
console.error("Error extracting color:", err);
this.logoColor = "#10b981";
this.colorPalette = [];
}
};
img.src = imageData;
},
console.log("Extracted palette:", this.colorPalette); kMeansClustering(colors, k) {
console.log("Average brand color:", this.logoColor); // Initialize centroids randomly
} catch (err) { let centroids = [];
console.error("Error extracting color:", err); const shuffled = [...colors].sort(
this.logoColor = "#10b981"; () => Math.random() - 0.5,
this.colorPalette = []; );
} for (let i = 0; i < k; i++) {
}; centroids.push({
img.src = imageData; ...shuffled[i % shuffled.length],
}, });
}
kMeansClustering(colors, k) { // K-means iterations
// Initialize centroids randomly for (let iter = 0; iter < 10; iter++) {
let centroids = []; // Assign colors to nearest centroid
const shuffled = [...colors].sort(() => Math.random() - 0.5); const clusters = Array(k)
for (let i = 0; i < k; i++) { .fill(null)
centroids.push({ ...shuffled[i % shuffled.length] }); .map(() => []);
}
// K-means iterations colors.forEach((color) => {
for (let iter = 0; iter < 10; iter++) { let minDist = Infinity;
// Assign colors to nearest centroid let closestIdx = 0;
const clusters = Array(k).fill(null).map(() => []);
colors.forEach(color => { centroids.forEach((centroid, idx) => {
let minDist = Infinity; const dist = Math.sqrt(
let closestIdx = 0; Math.pow(color.r - centroid.r, 2) +
Math.pow(color.g - centroid.g, 2) +
Math.pow(color.b - centroid.b, 2),
);
if (dist < minDist) {
minDist = dist;
closestIdx = idx;
}
});
centroids.forEach((centroid, idx) => { clusters[closestIdx].push(color);
const dist = Math.sqrt( });
Math.pow(color.r - centroid.r, 2) +
Math.pow(color.g - centroid.g, 2) +
Math.pow(color.b - centroid.b, 2)
);
if (dist < minDist) {
minDist = dist;
closestIdx = idx;
}
});
clusters[closestIdx].push(color); // Update centroids
}); const newCentroids = clusters.map((cluster) => {
if (cluster.length === 0) return centroids[0]; // Fallback
// Update centroids const avgR = Math.round(
const newCentroids = clusters.map(cluster => { cluster.reduce((sum, c) => sum + c.r, 0) /
if (cluster.length === 0) return centroids[0]; // Fallback cluster.length,
);
const avgG = Math.round(
cluster.reduce((sum, c) => sum + c.g, 0) /
cluster.length,
);
const avgB = Math.round(
cluster.reduce((sum, c) => sum + c.b, 0) /
cluster.length,
);
const avgR = Math.round(cluster.reduce((sum, c) => sum + c.r, 0) / cluster.length); return { r: avgR, g: avgG, b: avgB };
const avgG = Math.round(cluster.reduce((sum, c) => sum + c.g, 0) / cluster.length); });
const avgB = Math.round(cluster.reduce((sum, c) => sum + c.b, 0) / cluster.length);
return { r: avgR, g: avgG, b: avgB }; // Check convergence
}); const converged = centroids.every(
(c, i) =>
c.r === newCentroids[i].r &&
c.g === newCentroids[i].g &&
c.b === newCentroids[i].b,
);
// Check convergence centroids = newCentroids;
const converged = centroids.every((c, i) => if (converged) break;
c.r === newCentroids[i].r && }
c.g === newCentroids[i].g &&
c.b === newCentroids[i].b
);
centroids = newCentroids; // Sort by saturation (most saturated first)
if (converged) break; return centroids.sort((a, b) => {
} const satA =
(Math.max(a.r, a.g, a.b) -
// Sort by saturation (most saturated first) Math.min(a.r, a.g, a.b)) /
return centroids.sort((a, b) => { Math.max(a.r, a.g, a.b);
const satA = (Math.max(a.r, a.g, a.b) - Math.min(a.r, a.g, a.b)) / Math.max(a.r, a.g, a.b); const satB =
const satB = (Math.max(b.r, b.g, b.b) - Math.min(b.r, b.g, b.b)) / Math.max(b.r, b.g, b.b); (Math.max(b.r, b.g, b.b) -
return satB - satA; Math.min(b.r, b.g, b.b)) /
}); Math.max(b.r, b.g, b.b);
}, return satB - satA;
});
},
rgbToHex(r, g, b) { rgbToHex(r, g, b) {
return ( return (