30 CSS Text Hover Effects17 / 30
Typography Scramble / Matrix Effect
Labels that decrypt under the cursor: on hover, every character cycles through random glyphs, then resolves to the real text left-to-right like a codebreaker locking digits. The 22-line engine is the honest minimum — original strings cached in data-text, one interval per element, resolve speed proportional to position — and the CSS does the part JS versions always botch: monospace + a min-width in ch units, so the row NEVER changes width mid-scramble. Phosphor-green security-console styling included, as the search intent demands.
Published
The code
<section class="cth-17" aria-label="Text scramble decrypt demo">
<div class="cth-17__stage">
<div class="cth-17__term">
<header class="cth-17__bar"><i></i><i></i><i></i><span>krypta — ops console v3.1</span></header>
<p class="cth-17__log">> session verified · clearance <b>ONYX</b> · 3 actions armed</p>
<nav class="cth-17__menu" aria-label="Operations">
<a class="cth-17__op" href="#cth17-top" id="cth17-top" data-text="ACCESS THE VAULT" style="min-width:16ch">ACCESS THE VAULT</a>
<a class="cth-17__op" href="#cth17-top" data-text="ROTATE ALL KEYS" style="min-width:15ch">ROTATE ALL KEYS</a>
<a class="cth-17__op" href="#cth17-top" data-text="BURN THIS SESSION" style="min-width:17ch">BURN THIS SESSION</a>
</nav>
<p class="cth-17__hint">Hover an action — characters cycle, then lock left to right. Monospace + <code>ch</code> widths keep the row from breathing.</p>
</div>
</div>
</section><section class="cth-17" aria-label="Text scramble decrypt demo">
<div class="cth-17__stage">
<div class="cth-17__term">
<header class="cth-17__bar"><i></i><i></i><i></i><span>krypta — ops console v3.1</span></header>
<p class="cth-17__log">> session verified · clearance <b>ONYX</b> · 3 actions armed</p>
<nav class="cth-17__menu" aria-label="Operations">
<a class="cth-17__op" href="#cth17-top" id="cth17-top" data-text="ACCESS THE VAULT" style="min-width:16ch">ACCESS THE VAULT</a>
<a class="cth-17__op" href="#cth17-top" data-text="ROTATE ALL KEYS" style="min-width:15ch">ROTATE ALL KEYS</a>
<a class="cth-17__op" href="#cth17-top" data-text="BURN THIS SESSION" style="min-width:17ch">BURN THIS SESSION</a>
</nav>
<p class="cth-17__hint">Hover an action — characters cycle, then lock left to right. Monospace + <code>ch</code> widths keep the row from breathing.</p>
</div>
</div>
</section>.cth-17,
.cth-17 *,
.cth-17 *::before,
.cth-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-17 {
--bg: oklch(0.14 0.01 155);
--panel: oklch(0.18 0.015 155);
--ink: oklch(0.87 0.12 150);
--dim: oklch(0.55 0.06 150);
--hot: oklch(0.92 0.16 145);
--line: oklch(0.3 0.03 152);
font-family: ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
color: var(--ink);
}
.cth-17__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 80% at 50% 0%,oklch(0.17 0.014 155),var(--bg) 70%);
padding: clamp(20px,4vw,56px);
container: cth17/inline-size;
}
.cth-17__term {
width: min(100%,640px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 24px 70px oklch(0.05 0.02 155/.55),0 0 0 1px oklch(0.1 0.02 155);
position: relative;
}
.cth-17__term::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(0deg,oklch(0 0 0/.14) 0 1px,transparent 1px 3px);
mix-blend-mode: multiply;
}
.cth-17__bar {
display: flex;
align-items: center;
gap: 7px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
background: oklch(0.16 0.012 155);
}
.cth-17__bar i {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--line);
}
.cth-17__bar i:first-child {
background: oklch(0.6 0.16 25);
}
.cth-17__bar span {
margin-left: 8px;
font-size: 11.5px;
letter-spacing: .08em;
color: var(--dim);
}
.cth-17__log {
padding: 18px 20px 4px;
font-size: 12.5px;
line-height: 1.8;
color: var(--dim);
}
.cth-17__log b {
color: var(--hot);
font-weight: 700;
}
.cth-17__menu {
display: flex;
flex-direction: column;
padding: 12px 12px 8px;
}
.cth-17__op {
display: flex;
align-items: center;
min-height: 52px;
padding: 0 12px;
font-size: clamp(15px,2.6cqi,19px);
font-weight: 700;
letter-spacing: .06em;
color: var(--ink);
text-decoration: none;
border-radius: 8px;
white-space: nowrap;
transition: background .2s,color .2s,text-shadow .3s;
}
.cth-17__op::before {
content: '> ';
color: var(--dim);
transition: color .2s;
}
.cth-17__op:hover,
.cth-17__op:focus-visible {
background: oklch(0.22 0.025 152);
color: var(--hot);
text-shadow: 0 0 .5em color-mix(in oklch,var(--hot) 60%,transparent);
}
.cth-17__op:hover::before,
.cth-17__op:focus-visible::before {
color: var(--hot);
}
.cth-17__op:focus-visible {
outline: 1.5px solid var(--hot);
outline-offset: -1px;
}
.cth-17__hint {
padding: 10px 20px 20px;
font-size: 11.5px;
line-height: 1.7;
color: var(--dim);
}
.cth-17__hint code {
background: oklch(0.24 0.02 152);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
.cth-17 * {
transition-duration: .01ms !important;
}
}.cth-17,
.cth-17 *,
.cth-17 *::before,
.cth-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-17 {
--bg: oklch(0.14 0.01 155);
--panel: oklch(0.18 0.015 155);
--ink: oklch(0.87 0.12 150);
--dim: oklch(0.55 0.06 150);
--hot: oklch(0.92 0.16 145);
--line: oklch(0.3 0.03 152);
font-family: ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
color: var(--ink);
}
.cth-17__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 80% at 50% 0%,oklch(0.17 0.014 155),var(--bg) 70%);
padding: clamp(20px,4vw,56px);
container: cth17/inline-size;
}
.cth-17__term {
width: min(100%,640px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 24px 70px oklch(0.05 0.02 155/.55),0 0 0 1px oklch(0.1 0.02 155);
position: relative;
}
.cth-17__term::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(0deg,oklch(0 0 0/.14) 0 1px,transparent 1px 3px);
mix-blend-mode: multiply;
}
.cth-17__bar {
display: flex;
align-items: center;
gap: 7px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
background: oklch(0.16 0.012 155);
}
.cth-17__bar i {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--line);
}
.cth-17__bar i:first-child {
background: oklch(0.6 0.16 25);
}
.cth-17__bar span {
margin-left: 8px;
font-size: 11.5px;
letter-spacing: .08em;
color: var(--dim);
}
.cth-17__log {
padding: 18px 20px 4px;
font-size: 12.5px;
line-height: 1.8;
color: var(--dim);
}
.cth-17__log b {
color: var(--hot);
font-weight: 700;
}
.cth-17__menu {
display: flex;
flex-direction: column;
padding: 12px 12px 8px;
}
.cth-17__op {
display: flex;
align-items: center;
min-height: 52px;
padding: 0 12px;
font-size: clamp(15px,2.6cqi,19px);
font-weight: 700;
letter-spacing: .06em;
color: var(--ink);
text-decoration: none;
border-radius: 8px;
white-space: nowrap;
transition: background .2s,color .2s,text-shadow .3s;
}
.cth-17__op::before {
content: '> ';
color: var(--dim);
transition: color .2s;
}
.cth-17__op:hover,
.cth-17__op:focus-visible {
background: oklch(0.22 0.025 152);
color: var(--hot);
text-shadow: 0 0 .5em color-mix(in oklch,var(--hot) 60%,transparent);
}
.cth-17__op:hover::before,
.cth-17__op:focus-visible::before {
color: var(--hot);
}
.cth-17__op:focus-visible {
outline: 1.5px solid var(--hot);
outline-offset: -1px;
}
.cth-17__hint {
padding: 10px 20px 20px;
font-size: 11.5px;
line-height: 1.7;
color: var(--dim);
}
.cth-17__hint code {
background: oklch(0.24 0.02 152);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
.cth-17 * {
transition-duration: .01ms !important;
}
}(function(){
var CHARS='#$%&@[]{}<>/=+*^!?:;0123456789';
var reduced=matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.cth-17__op').forEach(function(el){
function scramble(){
if(reduced.matches)return;
var txt=el.getAttribute('data-text'),tick=0;
clearInterval(el._sc);
el._sc=setInterval(function(){
var out='';
for(var i=0;i<txt.length;i++){
out+=(tick>i*2.6||txt.charAt(i)===' ')?txt.charAt(i):CHARS.charAt((Math.random()*CHARS.length)|0);
}
el.textContent=out;
if(tick++>txt.length*2.6+2){clearInterval(el._sc);el.textContent=txt;}
},28);
}
el.addEventListener('pointerenter',scramble);
el.addEventListener('focus',scramble);
});
})();(function(){
var CHARS='#$%&@[]{}<>/=+*^!?:;0123456789';
var reduced=matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.cth-17__op').forEach(function(el){
function scramble(){
if(reduced.matches)return;
var txt=el.getAttribute('data-text'),tick=0;
clearInterval(el._sc);
el._sc=setInterval(function(){
var out='';
for(var i=0;i<txt.length;i++){
out+=(tick>i*2.6||txt.charAt(i)===' ')?txt.charAt(i):CHARS.charAt((Math.random()*CHARS.length)|0);
}
el.textContent=out;
if(tick++>txt.length*2.6+2){clearInterval(el._sc);el.textContent=txt;}
},28);
}
el.addEventListener('pointerenter',scramble);
el.addEventListener('focus',scramble);
});
})();Here's a working CSS Text Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Typography Scramble / Matrix Effect
Source: https://codefronts.com/motion/css-text-hover-effects/typography-scramble-matrix-effect/
Labels that decrypt under the cursor: on hover, every character cycles through random glyphs, then resolves to the real text left-to-right like a codebreaker locking digits. The 22-line engine is the honest minimum — original strings cached in data-text, one interval per element, resolve speed proportional to position — and the CSS does the part JS versions always botch: monospace + a min-width in ch units, so the row NEVER changes width mid-scramble. Phosphor-green security-console styling included, as the search intent demands.
## HTML
```html
<section class="cth-17" aria-label="Text scramble decrypt demo">
<div class="cth-17__stage">
<div class="cth-17__term">
<header class="cth-17__bar"><i></i><i></i><i></i><span>krypta — ops console v3.1</span></header>
<p class="cth-17__log">> session verified · clearance <b>ONYX</b> · 3 actions armed</p>
<nav class="cth-17__menu" aria-label="Operations">
<a class="cth-17__op" href="#cth17-top" id="cth17-top" data-text="ACCESS THE VAULT" style="min-width:16ch">ACCESS THE VAULT</a>
<a class="cth-17__op" href="#cth17-top" data-text="ROTATE ALL KEYS" style="min-width:15ch">ROTATE ALL KEYS</a>
<a class="cth-17__op" href="#cth17-top" data-text="BURN THIS SESSION" style="min-width:17ch">BURN THIS SESSION</a>
</nav>
<p class="cth-17__hint">Hover an action — characters cycle, then lock left to right. Monospace + <code>ch</code> widths keep the row from breathing.</p>
</div>
</div>
</section>
```
## CSS
```css
.cth-17,
.cth-17 *,
.cth-17 *::before,
.cth-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-17 {
--bg: oklch(0.14 0.01 155);
--panel: oklch(0.18 0.015 155);
--ink: oklch(0.87 0.12 150);
--dim: oklch(0.55 0.06 150);
--hot: oklch(0.92 0.16 145);
--line: oklch(0.3 0.03 152);
font-family: ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
color: var(--ink);
}
.cth-17__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 80% at 50% 0%,oklch(0.17 0.014 155),var(--bg) 70%);
padding: clamp(20px,4vw,56px);
container: cth17/inline-size;
}
.cth-17__term {
width: min(100%,640px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 24px 70px oklch(0.05 0.02 155/.55),0 0 0 1px oklch(0.1 0.02 155);
position: relative;
}
.cth-17__term::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(0deg,oklch(0 0 0/.14) 0 1px,transparent 1px 3px);
mix-blend-mode: multiply;
}
.cth-17__bar {
display: flex;
align-items: center;
gap: 7px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
background: oklch(0.16 0.012 155);
}
.cth-17__bar i {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--line);
}
.cth-17__bar i:first-child {
background: oklch(0.6 0.16 25);
}
.cth-17__bar span {
margin-left: 8px;
font-size: 11.5px;
letter-spacing: .08em;
color: var(--dim);
}
.cth-17__log {
padding: 18px 20px 4px;
font-size: 12.5px;
line-height: 1.8;
color: var(--dim);
}
.cth-17__log b {
color: var(--hot);
font-weight: 700;
}
.cth-17__menu {
display: flex;
flex-direction: column;
padding: 12px 12px 8px;
}
.cth-17__op {
display: flex;
align-items: center;
min-height: 52px;
padding: 0 12px;
font-size: clamp(15px,2.6cqi,19px);
font-weight: 700;
letter-spacing: .06em;
color: var(--ink);
text-decoration: none;
border-radius: 8px;
white-space: nowrap;
transition: background .2s,color .2s,text-shadow .3s;
}
.cth-17__op::before {
content: '> ';
color: var(--dim);
transition: color .2s;
}
.cth-17__op:hover,
.cth-17__op:focus-visible {
background: oklch(0.22 0.025 152);
color: var(--hot);
text-shadow: 0 0 .5em color-mix(in oklch,var(--hot) 60%,transparent);
}
.cth-17__op:hover::before,
.cth-17__op:focus-visible::before {
color: var(--hot);
}
.cth-17__op:focus-visible {
outline: 1.5px solid var(--hot);
outline-offset: -1px;
}
.cth-17__hint {
padding: 10px 20px 20px;
font-size: 11.5px;
line-height: 1.7;
color: var(--dim);
}
.cth-17__hint code {
background: oklch(0.24 0.02 152);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
.cth-17 * {
transition-duration: .01ms !important;
}
}
```
## JavaScript
```js
(function(){
var CHARS='#$%&@[]{}<>/=+*^!?:;0123456789';
var reduced=matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.cth-17__op').forEach(function(el){
function scramble(){
if(reduced.matches)return;
var txt=el.getAttribute('data-text'),tick=0;
clearInterval(el._sc);
el._sc=setInterval(function(){
var out='';
for(var i=0;i<txt.length;i++){
out+=(tick>i*2.6||txt.charAt(i)===' ')?txt.charAt(i):CHARS.charAt((Math.random()*CHARS.length)|0);
}
el.textContent=out;
if(tick++>txt.length*2.6+2){clearInterval(el._sc);el.textContent=txt;}
},28);
}
el.addEventListener('pointerenter',scramble);
el.addEventListener('focus',scramble);
});
})();
```Here's a working CSS Text Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Typography Scramble / Matrix Effect
Source: https://codefronts.com/motion/css-text-hover-effects/typography-scramble-matrix-effect/
Labels that decrypt under the cursor: on hover, every character cycles through random glyphs, then resolves to the real text left-to-right like a codebreaker locking digits. The 22-line engine is the honest minimum — original strings cached in data-text, one interval per element, resolve speed proportional to position — and the CSS does the part JS versions always botch: monospace + a min-width in ch units, so the row NEVER changes width mid-scramble. Phosphor-green security-console styling included, as the search intent demands.
## HTML
```html
<section class="cth-17" aria-label="Text scramble decrypt demo">
<div class="cth-17__stage">
<div class="cth-17__term">
<header class="cth-17__bar"><i></i><i></i><i></i><span>krypta — ops console v3.1</span></header>
<p class="cth-17__log">> session verified · clearance <b>ONYX</b> · 3 actions armed</p>
<nav class="cth-17__menu" aria-label="Operations">
<a class="cth-17__op" href="#cth17-top" id="cth17-top" data-text="ACCESS THE VAULT" style="min-width:16ch">ACCESS THE VAULT</a>
<a class="cth-17__op" href="#cth17-top" data-text="ROTATE ALL KEYS" style="min-width:15ch">ROTATE ALL KEYS</a>
<a class="cth-17__op" href="#cth17-top" data-text="BURN THIS SESSION" style="min-width:17ch">BURN THIS SESSION</a>
</nav>
<p class="cth-17__hint">Hover an action — characters cycle, then lock left to right. Monospace + <code>ch</code> widths keep the row from breathing.</p>
</div>
</div>
</section>
```
## CSS
```css
.cth-17,
.cth-17 *,
.cth-17 *::before,
.cth-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cth-17 {
--bg: oklch(0.14 0.01 155);
--panel: oklch(0.18 0.015 155);
--ink: oklch(0.87 0.12 150);
--dim: oklch(0.55 0.06 150);
--hot: oklch(0.92 0.16 145);
--line: oklch(0.3 0.03 152);
font-family: ui-monospace,'Cascadia Code',Menlo,Consolas,monospace;
color: var(--ink);
}
.cth-17__stage {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(100% 80% at 50% 0%,oklch(0.17 0.014 155),var(--bg) 70%);
padding: clamp(20px,4vw,56px);
container: cth17/inline-size;
}
.cth-17__term {
width: min(100%,640px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 24px 70px oklch(0.05 0.02 155/.55),0 0 0 1px oklch(0.1 0.02 155);
position: relative;
}
.cth-17__term::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(0deg,oklch(0 0 0/.14) 0 1px,transparent 1px 3px);
mix-blend-mode: multiply;
}
.cth-17__bar {
display: flex;
align-items: center;
gap: 7px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
background: oklch(0.16 0.012 155);
}
.cth-17__bar i {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--line);
}
.cth-17__bar i:first-child {
background: oklch(0.6 0.16 25);
}
.cth-17__bar span {
margin-left: 8px;
font-size: 11.5px;
letter-spacing: .08em;
color: var(--dim);
}
.cth-17__log {
padding: 18px 20px 4px;
font-size: 12.5px;
line-height: 1.8;
color: var(--dim);
}
.cth-17__log b {
color: var(--hot);
font-weight: 700;
}
.cth-17__menu {
display: flex;
flex-direction: column;
padding: 12px 12px 8px;
}
.cth-17__op {
display: flex;
align-items: center;
min-height: 52px;
padding: 0 12px;
font-size: clamp(15px,2.6cqi,19px);
font-weight: 700;
letter-spacing: .06em;
color: var(--ink);
text-decoration: none;
border-radius: 8px;
white-space: nowrap;
transition: background .2s,color .2s,text-shadow .3s;
}
.cth-17__op::before {
content: '> ';
color: var(--dim);
transition: color .2s;
}
.cth-17__op:hover,
.cth-17__op:focus-visible {
background: oklch(0.22 0.025 152);
color: var(--hot);
text-shadow: 0 0 .5em color-mix(in oklch,var(--hot) 60%,transparent);
}
.cth-17__op:hover::before,
.cth-17__op:focus-visible::before {
color: var(--hot);
}
.cth-17__op:focus-visible {
outline: 1.5px solid var(--hot);
outline-offset: -1px;
}
.cth-17__hint {
padding: 10px 20px 20px;
font-size: 11.5px;
line-height: 1.7;
color: var(--dim);
}
.cth-17__hint code {
background: oklch(0.24 0.02 152);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
.cth-17 * {
transition-duration: .01ms !important;
}
}
```
## JavaScript
```js
(function(){
var CHARS='#$%&@[]{}<>/=+*^!?:;0123456789';
var reduced=matchMedia('(prefers-reduced-motion: reduce)');
document.querySelectorAll('.cth-17__op').forEach(function(el){
function scramble(){
if(reduced.matches)return;
var txt=el.getAttribute('data-text'),tick=0;
clearInterval(el._sc);
el._sc=setInterval(function(){
var out='';
for(var i=0;i<txt.length;i++){
out+=(tick>i*2.6||txt.charAt(i)===' ')?txt.charAt(i):CHARS.charAt((Math.random()*CHARS.length)|0);
}
el.textContent=out;
if(tick++>txt.length*2.6+2){clearInterval(el._sc);el.textContent=txt;}
},28);
}
el.addEventListener('pointerenter',scramble);
el.addEventListener('focus',scramble);
});
})();
```How this works
The engine swaps characters; the important engineering is what it refuses to do — touch layout:
function scramble(el){
var txt = el.dataset.text, tick = 0;
clearInterval(el._sc);
el._sc = setInterval(function(){
var out = '';
for (var i = 0; i < txt.length; i++){
out += (tick > i*2.6 || txt[i]===' ')
? txt[i] /* locked */
: CHARS[(Math.random()*CHARS.length)|0]; /* cycling */
}
el.textContent = out;
if (tick++ > txt.length*2.6+2) clearInterval(el._sc);
}, 28);
}The lock condition tick > i * 2.6 is the whole choreography: character 0 resolves almost immediately, character 12 keeps churning for ~33 ticks — the left-to-right decrypt wave. Spaces lock instantly so word shapes surface early (that detail alone makes it read as 'decoding' rather than 'noise'). Re-hovering calls clearInterval first — one timer per element, ever; no stacking, no runaway CPU after impatient cursors.
The CSS contract: the label is ui-monospace with min-width in ch — in a monospace face, every substitute glyph occupies exactly 1ch, so the button doesn't breathe while scrambling (proportional fonts make scramble effects wobble like jelly; that's the #1 complaint on every tutorial). The charset avoids descend-y outliers and sticks to blocky symbols for the terminal texture. Reduced-motion users skip the theatre entirely — the guard returns before the first tick, and hover just shows the plain label.
Make it yours
- Charset is the costume: katakana-heavy strings read Matrix-movie; '01' reads binary; hex digits read forensics. Swap the CHARS constant only.
- Resolve tempo: the 2.6 multiplier is ticks-per-character — 1.5 decrypts urgently, 4 is a slow crack. Interval 28ms ≈ 35fps, deliberately under 60 for that stepped terminal feel.
- Scramble on load too: call scramble() on each element in a loop with staggered setTimeout for the classic boot-sequence intro, then leave hover re-triggering it.
- Right-to-left decrypt: lock condition tick > (len-1-i)*2.6 — reads as a countdown wipe.
Gotchas — read before shipping
- Cache the original in data-text ONCE — reading textContent as the source corrupts the string forever the first time a scramble is interrupted mid-cycle.
- Proportional fonts change width every tick (layout thrash + visual wobble). Monospace + ch-based min-width isn't styling here, it's the mechanism.
- Screen readers: the mutating textContent can fire a live-region storm if an ancestor has aria-live. These labels are plain links (no live region), and the resting DOM always ends as the true string — never leave an element scrambled.
- clearInterval before every start. Two overlapping intervals on one element is the classic bug: the text flickers forever and never resolves.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 113+ | 111+ |
The JS is ES5-safe back to 2015 browsers; floors come from oklch()/color-mix() styling. No-JS fallback is intact plain links — the effect is pure enhancement.