// ── Name anagram: clicks cycle through rearrangements ───────── (function () { const h1 = document.querySelector('.hero-name'); if (!h1) return; const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); const NAME = 'Nikolaj Gade'; const CYCLE = [NAME, 'a joking deal', NAME, 'naked oil jag']; // The visual letters rearrange, but assistive tech always gets the name h1.setAttribute('aria-label', NAME); // Split into persistent per-letter spans; the exact glyphs get reused, // so the capitals travel (see "a jokiNG deal") const letters = []; h1.textContent = ''; for (const ch of NAME) { if (ch === ' ') { h1.appendChild(document.createTextNode(' ')); continue; } const s = document.createElement('span'); s.className = 'ltr'; s.textContent = ch; h1.appendChild(s); letters.push({ el: s, ch: ch.toLowerCase() }); } function arrange(phrase) { // Pick a distinct source glyph for every letter of the target const pool = [...letters]; const seq = []; for (const ch of phrase) { if (ch === ' ') { seq.push(null); continue; } const i = pool.findIndex((l) => l.ch === ch.toLowerCase()); seq.push(pool.splice(i, 1)[0].el); } // FLIP: measure, reorder the DOM, measure again, then animate each // glyph from its old position into its new one const first = new Map(letters.map((l) => [l.el, l.el.getBoundingClientRect()])); h1.textContent = ''; seq.forEach((el) => h1.appendChild(el || document.createTextNode(' '))); if (reduceMotion.matches) return Promise.resolve(); return Promise.all( letters.map((l, k) => { const a = first.get(l.el); const b = l.el.getBoundingClientRect(); return l.el.animate( [ { transform: `translate(${(a.left - b.left).toFixed(1)}px, ${(a.top - b.top).toFixed(1)}px)` }, { transform: 'translate(0, 0)' }, ], { duration: 650, delay: k * 18, fill: 'backwards', easing: 'cubic-bezier(0.3, 0.9, 0.3, 1)', } ).finished; }) ); } let pos = 0; let busy = false; h1.addEventListener('click', () => { if (window.achv) window.achv.mark('anagram'); if (busy) return; busy = true; pos = (pos + 1) % CYCLE.length; arrange(CYCLE[pos]).finally(() => { busy = false; }); }); })();