🐐
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// ── 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; });
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user