<section class="demo">
<div class="demo__inner">
<p class="demo__eyebrow">Hover to decode</p>
<h2 class="demo__word" data-scramble>DESIGNBASE</h2>
<p class="demo__sub" data-scramble>Interaction Library</p>
</div>
</section>.demo {
width: 100%;
min-height: 100dvh;
display: grid;
place-items: center;
background: #08080a;
color: #f4f4f2;
font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.demo__inner { text-align: center; padding: 24px; }
.demo__eyebrow {
margin: 0 0 18px;
font-size: 10px;
letter-spacing: 0.24em;
text-transform: uppercase;
color: #4d4d55;
}
.demo__word {
margin: 0;
font-size: clamp(28px, 8vw, 56px);
font-weight: 500;
letter-spacing: 0.02em;
cursor: default;
}
.demo__sub {
margin: 12px 0 0;
font-size: 12px;
letter-spacing: 0.2em;
color: #6e6e76;
cursor: default;
}const CHARS = "!<>-_\\/[]{}—=+*^?#";
function scramble(el, duration) {
const target = el.dataset.text || (el.dataset.text = el.textContent);
const chars = target.split("");
const state = { progress: 0 };
gsap.killTweensOf(state);
gsap.to(state, {
progress: 1,
duration,
ease: "power2.inOut",
onUpdate: () => {
const settled = Math.floor(state.progress * chars.length);
el.textContent = chars
.map((char, index) => {
if (index < settled || char === " ") return char;
return CHARS[Math.floor(Math.random() * CHARS.length)];
})
.join("");
},
onComplete: () => { el.textContent = target; },
});
}
const targets = gsap.utils.toArray("[data-scramble]");
targets.forEach((el, index) => {
el.addEventListener("pointerenter", () => scramble(el, 0.9));
// 첫 진입에서도 한 번 보여줍니다.
gsap.delayedCall(0.15 + index * 0.12, () => scramble(el, 1.2));
});