feat: Add back button to return to index.html in both modes
All checks were successful
Build and Deploy / build (push) Successful in 32s
All checks were successful
Build and Deploy / build (push) Successful in 32s
This commit is contained in:
@@ -104,9 +104,17 @@
|
|||||||
>
|
>
|
||||||
<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
|
||||||
|
href="index.html"
|
||||||
|
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-arrow-left"></i>
|
||||||
|
<span>Torna al Menu</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
<h1
|
<h1
|
||||||
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
|
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
|
||||||
>
|
>
|
||||||
@@ -114,10 +122,11 @@
|
|||||||
Calcolatore Prezzi Software Pro
|
Calcolatore Prezzi Software Pro
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-600 mt-2">
|
<p class="text-gray-600 mt-2">
|
||||||
Sistema professionale per preventivi sviluppo software in
|
Sistema professionale per preventivi sviluppo software
|
||||||
Italia
|
in Italia
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Notifiche Toast -->
|
<!-- Notifiche Toast -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
188
shop-mode.html
188
shop-mode.html
@@ -82,9 +82,17 @@
|
|||||||
>
|
>
|
||||||
<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
|
||||||
|
href="index.html"
|
||||||
|
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-arrow-left"></i>
|
||||||
|
<span>Torna al Menu</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
<h1
|
<h1
|
||||||
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
|
class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3"
|
||||||
>
|
>
|
||||||
@@ -95,6 +103,7 @@
|
|||||||
Sistema di preventivi basato su articoli e quantità
|
Sistema di preventivi basato su articoli e quantità
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Notifiche Toast -->
|
<!-- Notifiche Toast -->
|
||||||
<div
|
<div
|
||||||
@@ -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"
|
||||||
|
:key="color"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:style="'background-color: ' + color"
|
||||||
:title="color"
|
:title="color"
|
||||||
class="w-8 h-8 rounded border border-gray-300 shadow-sm cursor-pointer hover:scale-110 transition-transform"></div>
|
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,8 +751,7 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
extractDominantColor(imageData) {
|
||||||
extractDominantColor(imageData) {
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
@@ -738,7 +761,12 @@ extractDominantColor(imageData) {
|
|||||||
ctx.drawImage(img, 0, 0);
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const imgData = ctx.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
const data = imgData.data;
|
const data = imgData.data;
|
||||||
|
|
||||||
// Step 1: Collect all valid colors
|
// Step 1: Collect all valid colors
|
||||||
@@ -761,7 +789,8 @@ extractDominantColor(imageData) {
|
|||||||
// Calculate saturation to skip grays
|
// Calculate saturation to skip grays
|
||||||
const max = Math.max(r, g, b);
|
const max = Math.max(r, g, b);
|
||||||
const min = Math.min(r, g, b);
|
const min = Math.min(r, g, b);
|
||||||
const saturation = max === 0 ? 0 : (max - min) / max;
|
const saturation =
|
||||||
|
max === 0 ? 0 : (max - min) / max;
|
||||||
if (saturation < 0.2) continue; // Skip grays
|
if (saturation < 0.2) continue; // Skip grays
|
||||||
|
|
||||||
colors.push({ r, g, b });
|
colors.push({ r, g, b });
|
||||||
@@ -770,40 +799,87 @@ extractDominantColor(imageData) {
|
|||||||
if (colors.length === 0) {
|
if (colors.length === 0) {
|
||||||
this.logoColor = "#10b981";
|
this.logoColor = "#10b981";
|
||||||
this.colorPalette = [];
|
this.colorPalette = [];
|
||||||
console.log("No valid colors found, using fallback");
|
console.log(
|
||||||
|
"No valid colors found, using fallback",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: K-means clustering to extract palette (5-7 colors)
|
// Step 2: K-means clustering to extract palette (5-7 colors)
|
||||||
const numClusters = Math.min(6, Math.max(3, Math.floor(colors.length / 50)));
|
const numClusters = Math.min(
|
||||||
const palette = this.kMeansClustering(colors, numClusters);
|
6,
|
||||||
|
Math.max(3, Math.floor(colors.length / 50)),
|
||||||
|
);
|
||||||
|
const palette = this.kMeansClustering(
|
||||||
|
colors,
|
||||||
|
numClusters,
|
||||||
|
);
|
||||||
|
|
||||||
// Step 3: Filter palette colors by lightness
|
// Step 3: Filter palette colors by lightness
|
||||||
const filteredPalette = palette.filter(color => {
|
const filteredPalette = palette.filter(
|
||||||
const max = Math.max(color.r, color.g, color.b);
|
(color) => {
|
||||||
const min = Math.min(color.r, color.g, color.b);
|
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;
|
const l = (max + min) / 2 / 255;
|
||||||
return l > 0.25 && l < 0.80; // Keep mid-range lightness
|
return l > 0.25 && l < 0.8; // Keep mid-range lightness
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (filteredPalette.length === 0) {
|
if (filteredPalette.length === 0) {
|
||||||
this.logoColor = "#10b981";
|
this.logoColor = "#10b981";
|
||||||
this.colorPalette = [];
|
this.colorPalette = [];
|
||||||
console.log("No suitable colors in palette, using fallback");
|
console.log(
|
||||||
|
"No suitable colors in palette, using fallback",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Calculate average color from palette
|
// Step 4: Calculate average color from palette
|
||||||
const avgR = Math.round(filteredPalette.reduce((sum, c) => sum + c.r, 0) / filteredPalette.length);
|
const avgR = Math.round(
|
||||||
const avgG = Math.round(filteredPalette.reduce((sum, c) => sum + c.g, 0) / filteredPalette.length);
|
filteredPalette.reduce(
|
||||||
const avgB = Math.round(filteredPalette.reduce((sum, c) => sum + c.b, 0) / filteredPalette.length);
|
(sum, c) => sum + c.r,
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
// Store results
|
// Store results
|
||||||
this.logoColor = this.rgbToHex(avgR, avgG, avgB);
|
this.logoColor = this.rgbToHex(
|
||||||
this.colorPalette = filteredPalette.map(c => this.rgbToHex(c.r, c.g, c.b));
|
avgR,
|
||||||
|
avgG,
|
||||||
|
avgB,
|
||||||
|
);
|
||||||
|
this.colorPalette = filteredPalette.map((c) =>
|
||||||
|
this.rgbToHex(c.r, c.g, c.b),
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Extracted palette:", this.colorPalette);
|
console.log(
|
||||||
console.log("Average brand color:", this.logoColor);
|
"Extracted palette:",
|
||||||
|
this.colorPalette,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"Average brand color:",
|
||||||
|
this.logoColor,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error extracting color:", err);
|
console.error("Error extracting color:", err);
|
||||||
this.logoColor = "#10b981";
|
this.logoColor = "#10b981";
|
||||||
@@ -811,22 +887,28 @@ extractDominantColor(imageData) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
img.src = imageData;
|
img.src = imageData;
|
||||||
},
|
},
|
||||||
|
|
||||||
kMeansClustering(colors, k) {
|
kMeansClustering(colors, k) {
|
||||||
// Initialize centroids randomly
|
// Initialize centroids randomly
|
||||||
let centroids = [];
|
let centroids = [];
|
||||||
const shuffled = [...colors].sort(() => Math.random() - 0.5);
|
const shuffled = [...colors].sort(
|
||||||
|
() => Math.random() - 0.5,
|
||||||
|
);
|
||||||
for (let i = 0; i < k; i++) {
|
for (let i = 0; i < k; i++) {
|
||||||
centroids.push({ ...shuffled[i % shuffled.length] });
|
centroids.push({
|
||||||
|
...shuffled[i % shuffled.length],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// K-means iterations
|
// K-means iterations
|
||||||
for (let iter = 0; iter < 10; iter++) {
|
for (let iter = 0; iter < 10; iter++) {
|
||||||
// Assign colors to nearest centroid
|
// Assign colors to nearest centroid
|
||||||
const clusters = Array(k).fill(null).map(() => []);
|
const clusters = Array(k)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => []);
|
||||||
|
|
||||||
colors.forEach(color => {
|
colors.forEach((color) => {
|
||||||
let minDist = Infinity;
|
let minDist = Infinity;
|
||||||
let closestIdx = 0;
|
let closestIdx = 0;
|
||||||
|
|
||||||
@@ -834,7 +916,7 @@ kMeansClustering(colors, k) {
|
|||||||
const dist = Math.sqrt(
|
const dist = Math.sqrt(
|
||||||
Math.pow(color.r - centroid.r, 2) +
|
Math.pow(color.r - centroid.r, 2) +
|
||||||
Math.pow(color.g - centroid.g, 2) +
|
Math.pow(color.g - centroid.g, 2) +
|
||||||
Math.pow(color.b - centroid.b, 2)
|
Math.pow(color.b - centroid.b, 2),
|
||||||
);
|
);
|
||||||
if (dist < minDist) {
|
if (dist < minDist) {
|
||||||
minDist = dist;
|
minDist = dist;
|
||||||
@@ -846,21 +928,31 @@ kMeansClustering(colors, k) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update centroids
|
// Update centroids
|
||||||
const newCentroids = clusters.map(cluster => {
|
const newCentroids = clusters.map((cluster) => {
|
||||||
if (cluster.length === 0) return centroids[0]; // Fallback
|
if (cluster.length === 0) return centroids[0]; // Fallback
|
||||||
|
|
||||||
const avgR = Math.round(cluster.reduce((sum, c) => sum + c.r, 0) / cluster.length);
|
const avgR = Math.round(
|
||||||
const avgG = Math.round(cluster.reduce((sum, c) => sum + c.g, 0) / cluster.length);
|
cluster.reduce((sum, c) => sum + c.r, 0) /
|
||||||
const avgB = Math.round(cluster.reduce((sum, c) => sum + c.b, 0) / cluster.length);
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
return { r: avgR, g: avgG, b: avgB };
|
return { r: avgR, g: avgG, b: avgB };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check convergence
|
// Check convergence
|
||||||
const converged = centroids.every((c, i) =>
|
const converged = centroids.every(
|
||||||
|
(c, i) =>
|
||||||
c.r === newCentroids[i].r &&
|
c.r === newCentroids[i].r &&
|
||||||
c.g === newCentroids[i].g &&
|
c.g === newCentroids[i].g &&
|
||||||
c.b === newCentroids[i].b
|
c.b === newCentroids[i].b,
|
||||||
);
|
);
|
||||||
|
|
||||||
centroids = newCentroids;
|
centroids = newCentroids;
|
||||||
@@ -869,11 +961,17 @@ kMeansClustering(colors, k) {
|
|||||||
|
|
||||||
// Sort by saturation (most saturated first)
|
// Sort by saturation (most saturated first)
|
||||||
return centroids.sort((a, b) => {
|
return centroids.sort((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 satA =
|
||||||
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(a.r, a.g, a.b) -
|
||||||
|
Math.min(a.r, a.g, a.b)) /
|
||||||
|
Math.max(a.r, a.g, a.b);
|
||||||
|
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);
|
||||||
return satB - satA;
|
return satB - satA;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
rgbToHex(r, g, b) {
|
rgbToHex(r, g, b) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user