{ a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
function randomPose(seed, n){ const R = mulberry(seed), P = []; for (let i=0;i { const L = 1.3, y = -L/2 + (n>1 ? k/(n-1) : .5)*L; return {x:0, y, ox:0, oy:L/(n*2.2), ix:0, iy:-L/(n*2.2)}; }); }
function lerpPose(A, B, t){ return A.map((a,i) => { const b = B[i]; return {x:lerp(a.x,b.x,t), y:lerp(a.y,b.y,t), ox:lerp(a.ox,b.ox,t), oy:lerp(a.oy,b.oy,t), ix:lerp(a.ix,b.ix,t), iy:lerp(a.iy,b.iy,t)}; }); }
function place(P, cx, cy, R, rot){ const c = Math.cos(rot), s = Math.sin(rot), T = (x,y) => [cx + (x*c - y*s)*R, cy + (x*s + y*c)*R]; return P.map(p => { const [x,y] = T(p.x,p.y), [ox,oy] = T(p.x+p.ox,p.y+p.oy), [ix,iy] = T(p.x+p.ix,p.y+p.iy); return {x,y,ox,oy,ix,iy}; }); }
const veil = $('veil'), vg = veil.getContext('2d');
let vPose = randomPose(101, 7);
function drawVeil(k){
const d = Math.min(2, devicePixelRatio||1), W = innerWidth, H = innerHeight;
if (veil.width !== Math.round(W*d)){ veil.width = Math.round(W*d); veil.height = Math.round(H*d); }
vg.setTransform(d,0,0,d,0,0); vg.clearRect(0,0,W,H);
if (k <= .001) return;
const env = smooth(clamp(k/.35)), grow = Math.pow(clamp((k-.05)/.95), 2.2);
const R = lerp(.26*Math.min(W,H), 2.6*Math.hypot(W,H), grow);
const P = place(lerpPose(seedOf(vPose.length), vPose, Math.max(env,.001)), W/2, H/2, R, k*1.3);
vg.beginPath(); vg.moveTo(P[0].x, P[0].y);
for (let i=0;i .82){ vg.globalAlpha = smooth((k-.82)/.18); vg.fillRect(0,0,W,H); vg.globalAlpha = 1; }
}
function animate(from, to, ms){
return new Promise(res => {
if (reduce){ drawVeil(to); res(); return; }
const t0 = performance.now();
const step = now => { const f = clamp((now - t0)/ms); drawVeil(lerp(from, to, ease(f))); f < 1 ? requestAnimationFrame(step) : res(); };
requestAnimationFrame(step);
});
}
function loadPage(page){
return new Promise(res => {
view.onload = () => { loadedPage = page; res(); };
view.srcdoc = src(page);
});
}
function scrollInside(anchor, smoothly){
try {
const doc = view.contentDocument, el = doc && doc.getElementById(anchor);
if (el) view.contentWindow.scrollTo({top: el.offsetTop, behavior: smoothly && !reduce ? 'smooth' : 'auto'});
} catch(e){}
}
async function go(route, first){
const R = ROUTES[route] || ROUTES.home;
if (busy) return; busy = true;
document.title = R.title;
document.documentElement.style.setProperty('--fcol', R.f);
items.forEach(it => { const a = it.querySelector('a'); a.toggleAttribute('aria-current', a.getAttribute('href') === '#/' + route); if (a.hasAttribute('aria-current')) a.setAttribute('aria-current','page'); });
if (!first && loadedPage === R.page){
// stessa pagina: basta scorrere fino al punto giusto
scrollInside(R.anchor || 'nothing', true);
if (!R.anchor){ try { view.contentWindow.scrollTo({top:0, behavior: reduce ? 'auto' : 'smooth'}); } catch(e){} }
} else {
if (!first){ vPose = randomPose(Math.floor(Math.random()*1e6), 7); await animate(0, 1, 700); }
await loadPage(R.page);
if (R.anchor) setTimeout(() => scrollInside(R.anchor, false), 60);
if (!first) await animate(1, 0, 800);
view.focus({preventScroll:true});
}
current = route; busy = false;
}
function routeFromHash(){ const h = location.hash.replace(/^#\/?/, ''); return ROUTES[h] ? h : 'home'; }
addEventListener('hashchange', () => { if (routeFromHash() !== current) go(routeFromHash()); });
/* ================= il menu: la f che fiorisce, una voce per stanghetta ================= */
const STEM = 'M64.5 5.5H42C32 5.5 26.5 11 26.5 22V146C26.5 157 21 162.5 12 162.5H0', SPINE = 26.5;
const STAGES = [[],[35,35],[35,41,51,62],[35,40,51,62,70,81],[35,41,57,68,82,94,112,123],[35,40,54,65,76,87,97,108,122,133]];
function barState(s){ s = clamp(s,0,5); const i = Math.min(4, Math.floor(s)), f = s - i, A = STAGES[i], B = STAGES[i+1], born = B.length - A.length;
return Array.from({length:10}, (_,k) => { const a = A[k], b = B[k]; if (a!=null) return {y: a + (b-a)*ease(f), k:1}; if (b!=null){ const j = k - A.length; return {y:b, k: ease(clamp((f - j/born)*born))}; } return {y:0, k:0}; }); }
function fSvg(svg, stage){
svg.innerHTML = '';
const p = document.createElementNS(NS,'path'); p.setAttribute('d',STEM); p.setAttribute('class','inks'); p.setAttribute('stroke-width',11); svg.append(p);
const rects = [...Array(10)].map((_,k) => { const r = document.createElementNS(NS,'rect'), right = k%2===0; r.setAttribute('class','ink'); r.setAttribute('x', right?20.5:0); r.setAttribute('width', right?42:32); r.setAttribute('height',11); svg.append(r); return r; });
const set = s => { const st = barState(s); rects.forEach((r,k) => { const {y,k:g} = st[k]; r.setAttribute('y', y.toFixed(2)); r.setAttribute('transform', `translate(${SPINE} 0) scale(${g.toFixed(3)} 1) translate(${-SPINE} 0)`); r.style.display = g<=0.001 ? 'none' : ''; }); return st; };
set(stage); return set;
}
fSvg($('fsmall'), 1);
const menu = $('menu'), mf = $('mf'), mfsvg = $('mfsvg'), fbtn = $('fbtn'), mhint = $('mhint'), setMenuF = fSvg(mfsvg, 1);
const items = VOCI.map(([t,h],k) => {
const d = document.createElement('div'); d.className = 'it ' + (k%2===0 ? 'r' : 'l');
const ext = h.startsWith('http');
d.innerHTML = `${t}`;
mf.append(d); return d;
});
let mOpen = false, mTarget = 0, mCur = 0;
const MAXS = 1 + (Math.ceil(VOCI.length/2) - 1); // fino a quante stanghette servono
function paintMenu(){ setMenuF(1 + mCur*(MAXS-1)).forEach((b,k) => { const it = items[k]; if (!it) return; it.style.top = ((b.y + 5.5)/168*100) + '%'; it.style.setProperty('--k', b.k.toFixed(3)); it.style.visibility = b.k > .01 ? 'visible' : 'hidden'; it.querySelector('a').tabIndex = b.k > .6 ? 0 : -1; }); mhint.style.opacity = mCur > .92 ? 0 : 1; }
function flip(fromRect, reverse){ const to = mf.getBoundingClientRect(), sc = fromRect.height / to.height; const a = `translate(${fromRect.left-to.left}px,${fromRect.top-to.top}px) scale(${sc})`; return mf.animate([{transform: reverse?'none':a},{transform: reverse?a:'none'}], {duration: reduce?1:650, easing:'cubic-bezier(.77,0,.18,1)', fill:'both'}); }
function openMenu(){ if (mOpen) return; mOpen = true; mTarget = 0; mCur = 0; paintMenu(); const from = fbtn.querySelector('svg').getBoundingClientRect(); menu.classList.add('open'); document.body.classList.add('menu-open'); fbtn.setAttribute('aria-expanded','true'); flip(from,false).finished.then(() => mfsvg.focus({preventScroll:true})); }
function closeMenu(){ if (!mOpen) return Promise.resolve(); mOpen = false; mTarget = 0; fbtn.setAttribute('aria-expanded','false');
return flip(fbtn.querySelector('svg').getBoundingClientRect(), true).finished.then(() => { menu.classList.remove('open'); document.body.classList.remove('menu-open'); mf.getAnimations().forEach(a => a.cancel()); mCur = 0; paintMenu(); }); }
fbtn.addEventListener('click', openMenu); $('mclose').addEventListener('click', () => closeMenu().then(() => fbtn.focus()));
mfsvg.addEventListener('click', () => closeMenu()); mfsvg.addEventListener('keydown', e => { if (e.key==='Enter'||e.key===' '){ e.preventDefault(); closeMenu(); } });
items.forEach(it => { const a = it.querySelector('a'); if (a.target) return; a.addEventListener('click', e => { e.preventDefault(); const h = a.getAttribute('href'); closeMenu().then(() => { if (location.hash !== h) location.hash = h; else go(routeFromHash()); }); }); });
menu.addEventListener('wheel', e => { e.preventDefault(); mTarget = clamp(mTarget + e.deltaY/1000); }, {passive:false});
let ty = null;
menu.addEventListener('touchstart', e => { ty = e.touches[0].clientY; }, {passive:true});
menu.addEventListener('touchmove', e => { if (ty==null) return; const y = e.touches[0].clientY; mTarget = clamp(mTarget + (ty-y)/450); ty = y; e.preventDefault(); }, {passive:false});
addEventListener('keydown', e => { if (!mOpen) return; if (e.key==='Escape') closeMenu().then(() => fbtn.focus()); if (e.key==='ArrowDown'){ e.preventDefault(); mTarget = clamp(mTarget+.17); } if (e.key==='ArrowUp'){ e.preventDefault(); mTarget = clamp(mTarget-.17); } });
/* ================= ciclo della cornice ================= */
function frame(){
if (mOpen || mCur !== mTarget){ const d = mTarget - mCur; mCur = Math.abs(d) < .0005 ? mTarget : mCur + d*(reduce ? 1 : .12); paintMenu(); }
// nella home la f compare solo quando il logotipo è diventato f
let hide = false;
if (loadedPage === 'home'){ try { hide = !view.contentDocument.body.classList.contains('ready'); } catch(e){} }
fbtn.classList.toggle('hide', hide);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
go(routeFromHash(), true);
})();