Elastic Passage
A single editorial action responds to cursor velocity rather than cursor position. Its letters briefly travel along a restrained wave when the pointer crosses the target, creating a tactile sense of passage without a lens, halo, ring, or floating cursor ornament. Activating the action reveals a concise local confirmation, with the same behavior available through focus and touch.
Live preview
What is Elastic Passage?
A single editorial action responds to cursor velocity rather than cursor position. Its letters briefly travel along a restrained wave when the pointer crosses the target, creating a tactile sense of passage without a lens, halo, ring, or floating cursor ornament. Activating the action reveals a concise local confirmation, with the same behavior available through focus and touch.
How it works
Pointer movement across the action briefly adds alternating vertical lifts and slight rotations to its individual letters. The wave is triggered by movement speed, not by a tracked cursor object. Clicking or tapping opens a local confirmation line, while keyboard focus provides the same usable action without depending on hover.
Where to use it
- Editorial collection links
- Portfolio entry points with a typographic gesture
- Premium brand navigation with a compact local confirmation
Accessibility
The interaction is a native button with an accessible name and visible keyboard focus. The pointer effect is decorative and skipped for touch and reduced-motion users; activation remains available through keyboard, touch, and click.
Performance
The component uses one scoped pointer listener, a short timeout, CSS transforms, and no continuous animation loop. It has no external assets, libraries, navigation, or network requests.
Source code
Explore and copy the original source. Interactive previews apply localized labels, while source code keeps the original strings (usually English). For translated dynamic labels in a project, use AuroraLabI18n.t(key, fallback).
<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');
});
}