<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Il fiocco di neve del tuo nome</title>
<style>
:root { --notte: #0b1630; --notte2: #050a18; --ghiaccio: #eaf3ff; --tenue: #9fb3d6; }
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 20px 16px;
background: radial-gradient(ellipse at 50% 30%, var(--notte), var(--notte2) 70%);
color: var(--ghiaccio); font-family: "Iowan Old Style", Palatino, Georgia, serif; }
main { width: min(560px, 100%); text-align: center; }
h1 { font-weight: 400; font-style: italic; font-size: clamp(24px, 6vw, 34px); margin: 0 0 4px; }
p { color: var(--tenue); margin: 0 0 18px; font-size: 15px; }
canvas { width: 100%; aspect-ratio: 1; display: block; border-radius: 18px; }
.riga { display: flex; gap: 10px; margin-top: 16px; }
input { flex: 1; min-width: 0; font: inherit; font-size: 18px; padding: 12px 14px; border-radius: 12px;
border: 1px solid #2c4170; background: #0d1a38; color: var(--ghiaccio); outline: none; }
input:focus { border-color: var(--tenue); }
button { font: inherit; font-size: 16px; padding: 12px 16px; border-radius: 12px; border: 0; cursor: pointer;
background: var(--ghiaccio); color: var(--notte); }
</style>
</head>
<body>
<main>
<h1>Il fiocco di neve del tuo nome</h1>
<p>Ogni nome cresce in un cristallo diverso. Lo stesso nome dà sempre lo stesso fiocco.</p>
<canvas id="tela" width="1200" height="1200"></canvas>
<div class="riga">
<input id="nome" maxlength="40" placeholder="Scrivi un nome…" value="Natale" aria-label="Nome">
<button id="scarica">Scarica</button>
</div>
</main>
<script>
// ---------------------------------------------------------------------------
// Dal nome a un numero (FNV-1a) e da quel numero a una sequenza di "dadi"
// (mulberry32): tutto deterministico, nessuna casualità vera.
// ---------------------------------------------------------------------------
const pulisci = (s) => s.trim().toLowerCase().replace(/\s+/g, " ") || "neve";
function seme(testo) {
let h = 2166136261;
for (const c of testo) { h ^= c.codePointAt(0); h = Math.imul(h, 16777619); }
return h >>> 0;
}
function dado(s) {
return () => {
s = (s + 0x6d2b79f5) | 0;
let t = Math.imul(s ^ (s >>> 15), 1 | s);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// ---------------------------------------------------------------------------
// Il progetto di un braccio. I cristalli veri crescono a 60 gradi:
// rami e rametti seguono solo quelle direzioni, e ogni braccio è speculare.
// ---------------------------------------------------------------------------
function progetto(nome) {
const r = dado(seme(pulisci(nome)));
const tra = (a, b) => a + (b - a) * r();
const rami = [];
const n = 3 + Math.floor(r() * 4); // da 3 a 6 coppie di rami
let pos = tra(0.14, 0.26);
for (let i = 0; i < n && pos < 0.9; i++) {
const lung = (1 - pos) * tra(0.3, 0.8) * (pos < 0.3 ? 0.7 : 1);
rami.push({
pos, lung,
sub: r() < 0.6 ? 1 + Math.floor(r() * 4) : 0, // rametti sui rami
piastra: r() < 0.2, // piccola piastra sull'asse
gemma: r() < 0.3, // esagono in punta al ramo
});
pos += tra(0.09, 0.22);
}
return {
rami,
centro: tra(0.08, 0.16), // raggio della piastra centrale
anelli: 1 + Math.floor(r() * 3),
punta: ["esagono", "rombo", "nulla"][Math.floor(r() * 3)],
spessore: tra(0.011, 0.016),
settori: r() < 0.35, // "piastre a settori" tra i rami
felce: r() < 0.3, // dendriti fitti, come una felce
};
}
// ---------------------------------------------------------------------------
// Disegno. t va da 0 a 1 e fa crescere il cristallo dal centro verso fuori.
// ---------------------------------------------------------------------------
const esagono = (ctx, x, y, raggio) => {
ctx.beginPath();
for (let k = 0; k < 6; k++) {
const a = Math.PI / 6 + (k * Math.PI) / 3;
ctx.lineTo(x + raggio * Math.cos(a), y + raggio * Math.sin(a));
}
ctx.closePath();
};
function braccio(ctx, p, L, t) {
const cresci = (da, a) => Math.max(0, Math.min(1, (t - da) / (a - da)));
const spina = L * cresci(0.0, 0.65);
const sx = Math.sin(Math.PI / 3), sy = -Math.cos(Math.PI / 3);
ctx.beginPath(); ctx.moveTo(0, -L * p.centro); ctx.lineTo(0, -Math.max(spina, L * p.centro)); ctx.stroke();
if (p.felce) { // dendriti corti lungo tutto l'asse
ctx.save(); ctx.lineWidth *= 0.6;
for (let u = p.centro + 0.06; u < 0.95; u += 0.055) {
const g = cresci(u * 0.65, u * 0.65 + 0.2), l = L * 0.07 * (1 - u * 0.5) * g;
if (g <= 0) continue;
for (const lato of [-1, 1]) { ctx.beginPath(); ctx.moveTo(0, -L * u); ctx.lineTo(lato * sx * l, -L * u + sy * l); ctx.stroke(); }
}
ctx.restore();
}
for (const ramo of p.rami) {
const y = -L * ramo.pos;
const g = cresci(ramo.pos * 0.65, ramo.pos * 0.65 + 0.35);
if (g <= 0) continue;
const l = L * ramo.lung * g;
for (const lato of [-1, 1]) { // specchio: sinistra e destra
const dx = lato * sx, dy = sy;
if (p.settori && g > 0.6) { // velo di ghiaccio tra asse e ramo
ctx.save(); ctx.globalAlpha *= 0.18;
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(dx * l * 0.75, y + dy * l * 0.75); ctx.lineTo(0, y - l * 0.75); ctx.fill();
ctx.restore();
}
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(dx * l, y + dy * l); ctx.stroke();
for (let s = 1; s <= ramo.sub; s++) { // rametti: uno verso la punta, uno verso fuori
const q = (s / (ramo.sub + 1)) * l;
const lr = (l - q) * 0.5;
const bx = dx * q, by = y + dy * q;
ctx.beginPath(); ctx.moveTo(bx, by); ctx.lineTo(bx, by - lr); ctx.stroke();
ctx.beginPath(); ctx.moveTo(bx, by); ctx.lineTo(bx + lato * lr * sx, by + lr * 0.5); ctx.stroke();
}
if (ramo.gemma && g > 0.8) { esagono(ctx, dx * l, y + dy * l, L * 0.022); ctx.stroke(); }
}
if (ramo.piastra && g > 0.5) { esagono(ctx, 0, y, L * 0.05 * g); ctx.stroke(); }
}
const fine = cresci(0.6, 0.75);
if (fine > 0 && p.punta !== "nulla") {
const s = L * 0.045 * fine;
if (p.punta === "esagono") esagono(ctx, 0, -L - s * 0.4, s);
else { ctx.beginPath(); ctx.moveTo(0, -L - s * 2); ctx.lineTo(s, -L); ctx.lineTo(0, -L + s * 0.6); ctx.lineTo(-s, -L); ctx.closePath(); }
ctx.stroke();
}
}
function disegna(ctx, nome, t, conNome) {
const W = ctx.canvas.width, C = W / 2, L = W * (conNome ? 0.335 : 0.42);
const cy = conNome ? W * 0.45 : C;
const p = progetto(nome);
const sfondo = ctx.createRadialGradient(C, cy, 0, C, cy, W * 0.75);
sfondo.addColorStop(0, "#16295a"); sfondo.addColorStop(1, "#060c1d");
ctx.fillStyle = sfondo; ctx.fillRect(0, 0, W, W);
ctx.save();
ctx.translate(C, cy);
ctx.lineCap = "round"; ctx.lineJoin = "round";
for (const passata of [0, 1]) { // alone largo, poi linea viva
ctx.strokeStyle = ctx.fillStyle = passata ? "#f4f9ff" : "rgba(160, 205, 255, 0.35)";
ctx.lineWidth = L * p.spessore * (passata ? 1 : 3.2);
ctx.shadowColor = "rgba(150, 200, 255, 0.9)"; ctx.shadowBlur = passata ? W * 0.012 : 0;
for (let k = 0; k < 6; k++) { ctx.save(); ctx.rotate((k * Math.PI) / 3); braccio(ctx, p, L, t); ctx.restore(); }
const rc = L * p.centro * Math.min(1, t * 4);
for (let a = 0; a < p.anelli; a++) { esagono(ctx, 0, 0, rc * (1 - a * 0.3)); ctx.stroke(); }
}
ctx.restore();
if (conNome) {
ctx.fillStyle = "#eaf3ff"; ctx.textAlign = "center";
ctx.font = `italic ${W * 0.055}px "Iowan Old Style", Palatino, Georgia, serif`;
ctx.fillText(nome.trim() || "neve", C, W * 0.9);
}
}
// ---------------------------------------------------------------------------
// Pagina: il fiocco ricresce a ogni lettera; "Scarica" salva un PNG con il nome.
// ---------------------------------------------------------------------------
const tela = document.getElementById("tela"), ctx = tela.getContext("2d");
const campo = document.getElementById("nome");
let inizio = 0, fotogramma = 0;
function anima(ora) {
if (!inizio) inizio = ora;
const t = Math.min(1, (ora - inizio) / 1600);
disegna(ctx, campo.value, 1 - Math.pow(1 - t, 3), true);
if (t < 1) fotogramma = requestAnimationFrame(anima);
}
function riparti() { cancelAnimationFrame(fotogramma); inizio = 0; fotogramma = requestAnimationFrame(anima); }
campo.addEventListener("input", riparti);
document.getElementById("scarica").addEventListener("click", () => {
const grande = document.createElement("canvas");
grande.width = grande.height = 2000;
disegna(grande.getContext("2d"), campo.value, 1, true);
grande.toBlob((b) => {
const a = document.createElement("a");
a.href = URL.createObjectURL(b);
a.download = `fiocco-${pulisci(campo.value).replace(/[^a-z0-9àèéìòù]+/g, "-")}.png`;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
});
});
// Un nome nell'indirizzo (pagina.html#Giulia) apre direttamente il suo fiocco.
let daLink = "";
try { daLink = decodeURIComponent(location.hash.slice(1)); } catch (e) { daLink = ""; }
if (daLink) campo.value = daLink.slice(0, 40);
riparti();
</script>
</body>
</html>Il fiocco di neve del tuo nome
Scrivi un nome e guardalo crescere in un cristallo di neve. Ogni nome ha il suo, e lo stesso nome dà sempre lo stesso fiocco. Rami a 60 gradi come nei cristalli veri, braccia speculari, piastre e dendriti. Un solo file HTML senza librerie: aprilo nel browser e premi «Scarica» per avere il PNG con il nome, da usare come biglietto o immagine del profilo.
Prompt
Scrivi una pagina HTML con JavaScript, senza librerie, che trasformi un nome in un fiocco di neve unico. Il nome diventa un seme numerico; da quel seme si decide la forma di un braccio (rami a 60 gradi, rametti, piastre esagonali, dendriti) che viene ripetuto sei volte e specchiato, come nei cristalli veri. Lo stesso nome deve dare sempre lo stesso fiocco. Il fiocco cresce con un’animazione a ogni lettera digitata e un pulsante «Scarica» salva un PNG grande con il nome sotto. Commenti in italiano.