Completa traduzioni EN/TH e fix sezioni home #27

Merged
javaxman merged 4 commits from feat/complete-en-th-translations into main 2026-07-31 14:39:13 +02:00
37 changed files with 6280 additions and 3482 deletions
Showing only changes of commit b687bc8ac2 - Show all commits

View File

@ -1,27 +1,28 @@
/** /**
* Rete di particelle + linee (canvas #particleCanvas). * Rete di particelle + linee (canvas #particleCanvas).
* Deve stare DENTRO il contenitore della pagina (es. .nx-grid-bg), non dietro body, * Limitato al viewport (non allaltezza documento): evita canvas giganti che
* altrimenti lo sfondo opaco della pagina lo copre. * bloccano lo scroll sulla home lunga.
*/ */
(function () { (function () {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
var canvas = document.getElementById('particleCanvas'); var canvas = document.getElementById('particleCanvas');
if (!canvas) return; if (!canvas) return;
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d', { alpha: true });
if (!ctx) return; if (!ctx) return;
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var particles = []; var particles = [];
var mouse = { x: null, y: null, radius: 150 }; var mouse = { x: null, y: null, radius: 150 };
var raf = 0; var raf = 0;
var running = false;
var cachedW = 0;
var cachedH = 0;
var scrollPauseTimer = 0;
function canvasSize() { function viewportSize() {
var p = canvas.parentElement; var w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth || 1));
if (!p) return { w: 0, h: 0 }; var h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight || 1));
var w = Math.max(1, Math.floor(p.clientWidth));
var h = Math.max(1, Math.floor(p.clientHeight));
return { w: w, h: h }; return { w: w, h: h };
} }
@ -29,8 +30,8 @@
this.x = Math.random() * w; this.x = Math.random() * w;
this.y = Math.random() * h; this.y = Math.random() * h;
this.size = Math.random() * 2 + 1; this.size = Math.random() * 2 + 1;
this.speedX = (Math.random() - 0.5) * 1.5; this.speedX = (Math.random() - 0.5) * 1.2;
this.speedY = (Math.random() - 0.5) * 1.5; this.speedY = (Math.random() - 0.5) * 1.2;
} }
Particle.prototype.draw = function () { Particle.prototype.draw = function () {
@ -58,46 +59,55 @@
var fx = dx / distance; var fx = dx / distance;
var fy = dy / distance; var fy = dy / distance;
var force = (mouse.radius - distance) / mouse.radius; var force = (mouse.radius - distance) / mouse.radius;
this.x -= fx * force * 5; this.x -= fx * force * 4;
this.y -= fy * force * 5; this.y -= fy * force * 4;
} }
}; };
function init() { function init() {
var sz = canvasSize(); var sz = viewportSize();
var w = sz.w; var w = sz.w;
var h = sz.h; var h = sz.h;
if (w < 8 || h < 8) return; cachedW = w;
cachedH = h;
var dpr = Math.min(window.devicePixelRatio || 1, 2); var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
canvas.width = Math.floor(w * dpr); canvas.width = Math.floor(w * dpr);
canvas.height = Math.floor(h * dpr); canvas.height = Math.floor(h * dpr);
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
particles = []; particles = [];
var n = Math.floor((w * h) / 9000); var area = w * h;
if (n > 130) n = 130; var n = Math.floor(area / 14000);
if (n < 45) n = 45; if (n > 80) n = 80;
if (n < 28) n = 28;
if (w < 768) {
n = Math.min(n, 40);
}
for (var i = 0; i < n; i++) { for (var i = 0; i < n; i++) {
particles.push(new Particle(w, h)); particles.push(new Particle(w, h));
} }
} }
function connect(w, h) { function connect(w) {
var maxDist = w < 768 ? 110 : 140;
var a; var a;
var b; var b;
var dx; var dx;
var dy; var dy;
var distance; var distance;
var opacity; var opacity;
for (a = 0; a < particles.length; a++) { var len = particles.length;
for (b = a + 1; b < particles.length; b++) { for (a = 0; a < len; a++) {
for (b = a + 1; b < len; b++) {
dx = particles[a].x - particles[b].x; dx = particles[a].x - particles[b].x;
dy = particles[a].y - particles[b].y; dy = particles[a].y - particles[b].y;
distance = Math.sqrt(dx * dx + dy * dy); distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) { if (distance < maxDist) {
opacity = (1 - distance / 150) * 0.22; opacity = (1 - distance / maxDist) * 0.2;
ctx.strokeStyle = 'rgba(59, 130, 246, ' + opacity + ')'; ctx.strokeStyle = 'rgba(59, 130, 246,' + opacity + ')';
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(particles[a].x, particles[a].y); ctx.moveTo(particles[a].x, particles[a].y);
@ -109,9 +119,9 @@
} }
function frame() { function frame() {
var sz = canvasSize(); if (!running) return;
var w = sz.w; var w = cachedW;
var h = sz.h; var h = cachedH;
ctx.clearRect(0, 0, w, h); ctx.clearRect(0, 0, w, h);
var i; var i;
@ -119,29 +129,33 @@
particles[i].update(w, h); particles[i].update(w, h);
particles[i].draw(); particles[i].draw();
} }
connect(w, h); connect(w);
raf = requestAnimationFrame(frame); raf = requestAnimationFrame(frame);
} }
function syncCanvasCssSize() { function start() {
var sz = canvasSize(); if (reduced || document.hidden || running || !particles.length) return;
canvas.style.width = sz.w + 'px'; running = true;
canvas.style.height = sz.h + 'px'; raf = requestAnimationFrame(frame);
}
function stop() {
running = false;
if (raf) {
cancelAnimationFrame(raf);
raf = 0;
}
} }
function onResize() { function onResize() {
cancelAnimationFrame(raf); stop();
syncCanvasCssSize();
init(); init();
if (!reduced && particles.length) { start();
raf = requestAnimationFrame(frame);
}
} }
function setMouseFromEvent(e) { function setMouseFromEvent(e) {
var rect = canvas.getBoundingClientRect(); mouse.x = e.clientX;
mouse.x = e.clientX - rect.left; mouse.y = e.clientY;
mouse.y = e.clientY - rect.top;
} }
window.addEventListener( window.addEventListener(
@ -152,22 +166,26 @@
{ passive: true }, { passive: true },
); );
var parent = canvas.parentElement; window.addEventListener(
if (parent) { 'scroll',
var ro = new ResizeObserver(onResize); function () {
ro.observe(parent); // Libera il main thread durante lo scroll; riprende subito dopo.
} stop();
if (scrollPauseTimer) window.clearTimeout(scrollPauseTimer);
scrollPauseTimer = window.setTimeout(function () {
start();
}, 120);
},
{ passive: true },
);
document.addEventListener('visibilitychange', function () {
if (document.hidden) stop();
else start();
});
window.addEventListener('resize', onResize, { passive: true }); window.addEventListener('resize', onResize, { passive: true });
syncCanvasCssSize();
init(); init();
start();
if (reduced) {
return;
}
if (particles.length) {
raf = requestAnimationFrame(frame);
}
})(); })();

View File

@ -3,32 +3,20 @@
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra, * Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti". * inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
* Dietro a testo e card (main ha stacking; questo blocco è z-0). * Dietro a testo e card (main ha stacking; questo blocco è z-0).
* Senza feGaussianBlur: il filtro SVG su un layer alto quanto il main
* rendeva lo scroll a scatti.
*/ */
--- ---
<div class="nx-dataflow-spine" aria-hidden="true"> <div class="nx-dataflow-spine" aria-hidden="true">
<svg <svg
class="nx-dataflow-spine-svg" class="nx-dataflow-spine-svg"
viewBox="0 0 1480 2800" viewBox="0 0 1480 1600"
preserveAspectRatio="none" preserveAspectRatio="none"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<defs> <defs>
<filter
id="nx-spine-glow"
x="-30%"
y="-30%"
width="160%"
height="160%"
color-interpolation-filters="sRGB"
>
<feGaussianBlur stdDeviation="2.2" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%"> <linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%">
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" /> <stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" /> <stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
@ -36,8 +24,8 @@
</linearGradient> </linearGradient>
</defs> </defs>
<g filter="url(#nx-spine-glow)" opacity="0.72"> <g opacity="0.72">
<!-- Entrata da dx (fuori canvas) → curva verso sx con pendenza verso il basso; fascia ~y 9801280 = titolo Prodotti su viewport tipiche --> <!-- Entrata da dx → curva verso sx; fascia titolo Prodotti -->
<path <path
class="nx-flow-line nx-flow-line--a" class="nx-flow-line nx-flow-line--a"
vector-effect="non-scaling-stroke" vector-effect="non-scaling-stroke"

View File

@ -36,14 +36,15 @@
} }
} }
/* Canvas particelle: dentro .nx-grid-bg, sopra lo sfondo della sezione */ /* Canvas particelle: fisso al viewport (non alto quanto tutto il documento) */
.nx-particle-layer { .nx-particle-layer {
position: absolute; position: fixed;
inset: 0; inset: 0;
z-index: 0; z-index: 0;
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
/* Subtle grid (Vercel-like) + vignette */ /* Subtle grid (Vercel-like) + vignette */
@ -128,11 +129,16 @@
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */ /* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
.nx-dataflow-spine { .nx-dataflow-spine {
position: absolute; position: absolute;
inset: 0; top: 0;
left: 0;
right: 0;
/* Solo fascia hero → prodotti: evita SVG filtrato alto quanto tutto il main */
height: min(160vh, 2200px);
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
overflow: hidden; overflow: hidden;
opacity: 0.46; opacity: 0.46;
contain: paint;
mask-image: linear-gradient( mask-image: linear-gradient(
to bottom, to bottom,
transparent 0, transparent 0,