Elastic Passage
Un’azione editoriale reagisce alla velocità del cursore, non alla sua posizione. Quando il puntatore attraversa il bersaglio, le lettere si muovono per un istante lungo un’onda discreta, creando una sensazione tattile senza lenti, aloni, anelli o cursori fluttuanti. L’attivazione mostra una conferma locale concisa; lo stesso comportamento resta disponibile con focus e touch.
Prova l’esperimento
Cos’è Elastic Passage?
Una singola azione editoriale reagisce alla velocità del cursore invece che alla sua posizione. Le lettere compiono una breve onda controllata quando il puntatore attraversa il bersaglio, offrendo una risposta tattile senza ricorrere a lenti, aloni, anelli o ornamenti flottanti. Un clic o un tocco mostra una conferma in loco, mentre il focus mantiene l’azione utilizzabile anche da tastiera.
Come funziona
Il passaggio del puntatore sull’azione attiva per un istante sollevamenti verticali alternati e leggere rotazioni sulle singole lettere. L’onda dipende dal movimento rilevato, non da un cursore tracciato. Un clic o un tocco apre una breve conferma locale; il focus da tastiera conserva la stessa azione senza dipendere dall’hover.
Dove usarlo
- Link editoriali verso raccolte
- Punti d’accesso a progetti portfolio con un gesto tipografico
- Navigazione di brand premium con una conferma locale e compatta
Accessibilità
Il componente è un button nativo con nome accessibile e focus da tastiera ben visibile. L’effetto del puntatore è decorativo e viene saltato su touch e con la riduzione del movimento; l’attivazione resta disponibile da tastiera, touch e clic.
Prestazioni
Il componente utilizza un solo ascoltatore del puntatore limitato all’area, un breve timeout e trasformazioni CSS, senza cicli di animazione continui. Non richiede risorse esterne, librerie, navigazione o richieste di rete.
Il codice
Esplora e copia il codice originale. Nell’anteprima le etichette vengono tradotte, mentre nel sorgente restano quelle originali (di solito inglesi). Per i testi dinamici usa AuroraLabI18n.t(chiave, testoPredefinito).
<main class="cursor-stage">
<section class="passage-zone" aria-label="Featured collection action">
<button class="passage-trigger" type="button" data-ali-i18n-aria-label="target_aria" aria-label="Open selected collection">
<span class="passage-word" aria-hidden="true">
<span class="letter">S</span><span class="letter">e</span><span class="letter">l</span><span class="letter">e</span><span class="letter">c</span><span class="letter">t</span><span class="letter">e</span><span class="letter">d</span>
</span>
<span class="passage-action" data-ali-i18n="copy_1">Open collection</span>
<span class="passage-state" data-ali-i18n="copy_2">A considered edit of current work</span>
</button>
</section>
</main>
:root{
--bg:#0D0B0F;
--surface:#16131A;
--fg:#EEEAE4;
--muted:#A49DAB;
--violet:#62447D;
--raspberry:#DF284D;
--gold:#F5BA09;
--lilac:#B79BD4;
--wave:0deg;
}
*{box-sizing:border-box}
html,body{margin:0;min-width:0;min-height:100%;background:var(--bg)}
body{color:var(--fg);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
.cursor-stage{
width:100%;
min-height:100vh;
overflow:hidden;
display:grid;
place-items:center;
padding:clamp(28px,8vw,96px);
background:var(--bg);
}
.passage-zone{
width:min(100%,720px);
display:grid;
place-items:center;
}
.passage-trigger{
position:relative;
display:grid;
justify-items:center;
gap:clamp(18px,3.5vw,30px);
width:min(100%,620px);
padding:clamp(24px,5vw,48px) clamp(12px,3vw,30px);
border:0;
background:transparent;
color:var(--fg);
font:inherit;
cursor:none;
}
.passage-word{
display:flex;
justify-content:center;
white-space:nowrap;
font-family:Georgia,"Times New Roman",serif;
font-size:clamp(3.7rem,13vw,8.8rem);
font-weight:400;
letter-spacing:-.09em;
line-height:.82;
pointer-events:none;
}
.letter{
display:inline-block;
transform:translateY(var(--lift,0)) rotate(var(--turn,0deg));
transition:transform .42s cubic-bezier(.2,.82,.2,1),color .28s ease;
}
.passage-trigger.is-moving .letter:nth-child(1){--lift:-.16em;--turn:-3deg}
.passage-trigger.is-moving .letter:nth-child(2){--lift:.11em;--turn:2deg}
.passage-trigger.is-moving .letter:nth-child(3){--lift:-.08em;--turn:-2deg}
.passage-trigger.is-moving .letter:nth-child(4){--lift:.14em;--turn:3deg}
.passage-trigger.is-moving .letter:nth-child(5){--lift:-.12em;--turn:-3deg}
.passage-trigger.is-moving .letter:nth-child(6){--lift:.08em;--turn:2deg}
.passage-trigger.is-moving .letter:nth-child(7){--lift:-.1em;--turn:-2deg}
.passage-trigger.is-moving .letter:nth-child(8){--lift:.13em;--turn:3deg}
.passage-trigger.is-moving .letter:nth-child(odd){color:var(--gold)}
.passage-action{
display:inline-block;
color:var(--muted);
font-size:clamp(.76rem,1.8vw,.92rem);
font-weight:700;
letter-spacing:.14em;
line-height:1.2;
text-transform:uppercase;
transition:color .3s ease,transform .35s ease;
}
.passage-trigger:hover .passage-action,
.passage-trigger:focus-visible .passage-action{color:var(--raspberry);transform:translateY(-3px)}
.passage-state{
max-width:25rem;
max-height:0;
overflow:hidden;
color:var(--lilac);
font-size:clamp(.82rem,1.8vw,1rem);
line-height:1.45;
opacity:0;
text-align:center;
transform:translateY(10px);
transition:max-height .45s ease,opacity .35s ease,transform .45s cubic-bezier(.2,.8,.2,1);
}
.passage-trigger.is-open .passage-state{max-height:4rem;opacity:1;transform:translateY(0)}
.passage-trigger.is-open .passage-action{color:var(--gold)}
.passage-trigger:focus-visible{outline:2px solid var(--gold);outline-offset:12px}
@media (hover:none),(pointer:coarse){
.passage-trigger{cursor:pointer}
.passage-trigger.is-moving .letter{--lift:0!important;--turn:0deg!important}
}
@media (prefers-reduced-motion:reduce){
.letter,.passage-action,.passage-state{transition-duration:.01ms}
.passage-trigger.is-moving .letter{--lift:0!important;--turn:0deg!important}
}
@media (max-width:420px){
.cursor-stage{padding-inline:16px}
.passage-word{font-size:clamp(3.2rem,17vw,5.2rem)}
.passage-trigger{padding-inline:0}
}
var trigger = document.querySelector('.passage-trigger');
var word = document.querySelector('.passage-word');
if (trigger && word) {
var lastX = null;
var lastTime = 0;
var motionTimer = null;
var open = false;
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function text(key, fallback) {
return window.AuroraLabI18n && typeof window.AuroraLabI18n.t === 'function' ? window.AuroraLabI18n.t(key, fallback) : fallback;
}
function markMovement() {
if (reduced) return;
trigger.classList.add('is-moving');
window.clearTimeout(motionTimer);
motionTimer = window.setTimeout(function () {
trigger.classList.remove('is-moving');
}, 430);
}
trigger.addEventListener('pointermove', function (event) {
if (event.pointerType === 'touch') return;
var now = performance.now();
if (lastX !== null && now - lastTime > 12) {
var distance = Math.abs(event.clientX - lastX);
if (distance > 3) markMovement();
}
lastX = event.clientX;
lastTime = now;
});
trigger.addEventListener('pointerleave', function () {
lastX = null;
window.clearTimeout(motionTimer);
trigger.classList.remove('is-moving');
});
trigger.addEventListener('click', function () {
open = !open;
trigger.classList.toggle('is-open', open);
trigger.setAttribute('aria-label', text(open ? 'open_aria' : 'target_aria', open ? 'Close selected collection' : 'Open selected collection'));
var action = trigger.querySelector('.passage-action');
if (action) action.textContent = text(open ? 'close_action' : 'copy_1', open ? 'Close collection' : 'Open collection');
});
}