ASCII Matrix Rain
A Canvas rain of katakana and ASCII floods the screen column by column, glowing at the leading edge, then retracts in a second wave. CRT scanlines throughout. A cybersecurity platform.
ASCII Matrix Rain the 10th of 11 designs in the 11 CSS Page Transitions collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<canvas id="matCanvas"></canvas>
<div class="pages">
<div class="page active" id="p0">
<div class="term-frame">
<div class="term-topbar">
<div class="term-dots">
<div class="term-dot td-red"></div>
<div class="term-dot td-yellow"></div>
<div class="term-dot td-green"></div>
</div>
<div class="term-title">NEXUS-OS v4.2.1 · SECURE SHELL · AUTHENTICATED</div>
</div>
<div class="term-body">
<div class="term-hero">
<div class="term-prompt"><span>root@nexus</span>:~# whoami --extended</div>
<div class="term-big">NEXUS</div>
<div class="term-big-dim">Security Intelligence Platform</div>
</div>
<div class="term-output">
<div class="term-line c">// System online. All nodes responsive.</div>
<div class="term-line g">✓ Threat monitoring active · 2,847 signatures loaded</div>
<div class="term-line g">✓ Network perimeter intact · 0 active intrusions</div>
<div class="term-line y">⚠ 3 unpatched CVEs detected · action required</div>
<div class="term-line c">// Last audit: 2025-09-14 03:12:47 UTC</div>
<div class="term-line" style="margin-top:8px">
<span style="color:var(--green)">root@nexus</span><span style="color:var(--dim)">:~# </span><span class="blink"></span>
</div>
</div>
<div class="term-commands">
<button class="cmd-btn primary" data-next="1">./ops_status</button>
<button class="cmd-btn" data-next="2">./threat_intel</button>
<button class="cmd-btn" data-next="3">./team --list</button>
</div>
</div>
<div class="term-side">
<div class="side-label">// SYSTEM STATUS</div>
<div class="side-row"><span class="side-k">CPU</span><span class="side-v ok">12%</span></div>
<div class="side-row"><span class="side-k">MEM</span><span class="side-v ok">68%</span></div>
<div class="side-row"><span class="side-k">NET I/O</span><span class="side-v ok">2.1 Gb/s</span></div>
<div class="side-row"><span class="side-k">UPTIME</span><span class="side-v ok">847d</span></div>
<div class="side-row"><span class="side-k">THREATS</span><span class="side-v warn">3</span></div>
<div class="side-row"><span class="side-k">INTRUSION</span><span class="side-v ok">0</span></div>
<div class="side-row"><span class="side-k">NODES</span><span class="side-v ok">48/48</span></div>
<div class="side-row"><span class="side-k">KERNEL</span><span class="side-v ok">6.8.4</span></div>
</div>
</div>
</div>
<div class="page" id="p1">
<div class="p1-header">
<h2 class="p1-title">OPS_STATUS</h2>
<div class="p1-uptime">UPTIME 847d 14h 22m · ALL SYSTEMS</div>
</div>
<div class="ops-grid">
<div class="op-card"><div class="op-status"><div class="op-dot dot-ok"></div><div class="op-status-text">Operational</div></div><div class="op-name">Perimeter Watch</div><div class="op-desc">Real-time monitoring of 48 network boundary nodes. Zero intrusion events in past 72 hours.</div><div class="op-meta">Last event: 2025-09-01 · False positive</div></div>
<div class="op-card"><div class="op-status"><div class="op-dot dot-ok"></div><div class="op-status-text">Operational</div></div><div class="op-name">Endpoint XDR</div><div class="op-desc">Extended detection across 2,400 managed endpoints. Behavioral AI scoring all process trees.</div><div class="op-meta">Last quarantine: 2025-09-10 · Ransomware sim</div></div>
<div class="op-card"><div class="op-status"><div class="op-dot dot-warn"></div><div class="op-status-text">Degraded</div></div><div class="op-name">Intel Feed</div><div class="op-desc">External threat intelligence ingestion partially degraded — 2 upstream providers offline.</div><div class="op-meta">ETA restore: 2025-09-15 09:00 UTC</div></div>
<div class="op-card"><div class="op-status"><div class="op-dot dot-ok"></div><div class="op-status-text">Operational</div></div><div class="op-name">SIEM</div><div class="op-desc">Security event correlation processing 4.2M events/sec. Zero queue backlog. All rules current.</div><div class="op-meta">Rules version: 9.4.1 · Updated yesterday</div></div>
</div>
<div class="log-feed">
<div class="log-title">// RECENT LOG ENTRIES</div>
<div class="log-entry"><span class="log-time">09:14:03</span> <span class="log-ok">INFO</span> <span class="log-msg">Perimeter scan complete. 0 anomalies detected.</span></div>
<div class="log-entry"><span class="log-time">09:11:47</span> <span class="log-ok">INFO</span> <span class="log-msg">Intel feed sync: 2847 signatures updated.</span></div>
<div class="log-entry"><span class="log-time">09:08:22</span> <span class="log-err">WARN</span> <span class="log-msg">Feed provider sigma.intel unreachable. Retrying.</span></div>
<div class="log-entry"><span class="log-time">09:01:05</span> <span class="log-ok">INFO</span> <span class="log-msg">Daily key rotation completed. All sessions refreshed.</span></div>
</div>
</div>
<div class="page" id="p2">
<div class="p2-header">
<h2 class="p2-title">THREAT_INTEL</h2>
<div class="threat-level"><div class="op-dot dot-warn" style="margin-right:2px"></div>THREAT LEVEL: ELEVATED</div>
</div>
<div class="threat-table">
<div class="threat-row head">
<div class="th-cell">CVE ID</div>
<div class="th-cell">Description</div>
<div class="th-cell">Severity</div>
<div class="th-cell">CVSS</div>
<div class="th-cell">Status</div>
</div>
<div class="threat-row"><div class="th-cell" style="color:var(--green)">CVE-2025-4471</div><div class="th-cell" style="color:var(--dim)">Remote code exec in libssl 3.x via malformed cert chain</div><div class="th-cell"><span class="sev sev-crit">Critical</span></div><div class="th-cell" style="color:var(--red)">9.8</div><div class="th-cell"><span class="stat-badge badge-new">New</span></div></div>
<div class="threat-row"><div class="th-cell" style="color:var(--green)">CVE-2025-3892</div><div class="th-cell" style="color:var(--dim)">Privilege escalation in Linux kernel via io_uring</div><div class="th-cell"><span class="sev sev-high">High</span></div><div class="th-cell" style="color:#ffa000">8.1</div><div class="th-cell"><span class="stat-badge badge-active">Active</span></div></div>
<div class="threat-row"><div class="th-cell" style="color:var(--green)">CVE-2025-3301</div><div class="th-cell" style="color:var(--dim)">SQL injection in Nginx Plus management API</div><div class="th-cell"><span class="sev sev-med">Medium</span></div><div class="th-cell" style="color:#ffee00">6.5</div><div class="th-cell"><span class="stat-badge badge-patch">Patch ready</span></div></div>
<div class="threat-row"><div class="th-cell" style="color:var(--green)">CVE-2025-2984</div><div class="th-cell" style="color:var(--dim)">Zero-day in AMD CPU microcode (speculative exec)</div><div class="th-cell"><span class="sev sev-high">High</span></div><div class="th-cell" style="color:#ffa000">7.4</div><div class="th-cell"><span class="stat-badge badge-active">Active</span></div></div>
</div>
</div>
<div class="page" id="p3">
<div class="p3-header">
<h2 class="p3-title">./TEAM</h2>
<div style="font-size:9px;letter-spacing:0.2em;color:var(--green-dim)">8 OPERATORS · CLEARANCE L4+</div>
</div>
<div class="team-grid">
<div class="team-card"><div class="tc-handle">vex</div><div class="tc-role">Lead Analyst</div><div class="tc-name">Vera Okonkwo</div><div class="tc-skills"><span class="skill-tag">malware</span><span class="skill-tag">RE</span><span class="skill-tag">threat hunting</span></div></div>
<div class="team-card"><div class="tc-handle">z0r</div><div class="tc-role">Exploit Dev</div><div class="tc-name">Kai Ishikawa</div><div class="tc-skills"><span class="skill-tag">kernel</span><span class="skill-tag">asm</span><span class="skill-tag">fuzzing</span></div></div>
<div class="team-card"><div class="tc-handle">gh0st</div><div class="tc-role">Red Team Lead</div><div class="tc-name">Lena Brandt</div><div class="tc-skills"><span class="skill-tag">pentest</span><span class="skill-tag">social eng</span><span class="skill-tag">APT sim</span></div></div>
<div class="team-card"><div class="tc-handle">null_ptr</div><div class="tc-role">Platform Eng</div><div class="tc-name">Marcus Webb</div><div class="tc-skills"><span class="skill-tag">rust</span><span class="skill-tag">eBPF</span><span class="skill-tag">k8s</span></div></div>
</div>
</div>
</div>
<nav class="term-nav">
<button class="tnav-item active" data-i="0" data-cmd="// ">./nexus_home</button>
<button class="tnav-item" data-i="1" data-cmd="// ">./ops_status</button>
<button class="tnav-item" data-i="2" data-cmd="// ">./threat_intel</button>
<button class="tnav-item" data-i="3" data-cmd="// ">./team --list</button>
<div class="term-clock" id="termClock">00:00:00 UTC</div>
</nav> *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--terminal:#020d02;
--green:#00e060;
--green-dim:#006030;
--green-faint:rgba(0,224,96,0.08);
--green-mid:rgba(0,224,96,0.4);
--white:#d0f0d0;
--dim:rgba(208,240,208,0.4);
--red:#ff3050;
}
html,body{height:100%;overflow:hidden;background:var(--terminal);font-family:'JetBrains Mono',monospace;color:var(--white);}
/* CRT scanline effect */
body::after{
content:'';position:fixed;inset:0;z-index:9999;pointer-events:none;
background:repeating-linear-gradient(0deg,transparent,transparent 1px,rgba(0,0,0,0.08) 1px,rgba(0,0,0,0.08) 2px);
}
/* Phosphor glow vignette */
body::before{
content:'';position:fixed;inset:0;z-index:9998;pointer-events:none;
background:radial-gradient(ellipse at center,transparent 60%,rgba(0,0,0,0.4) 100%);
}
/* Pages */
.pages{position:fixed;inset:0;}
.page{position:absolute;inset:0;pointer-events:none;visibility:hidden;}
.page.active{visibility:visible;pointer-events:auto;}
/* Matrix canvas */
#matCanvas{
position:fixed;inset:0;z-index:700;pointer-events:none;
}
/* PAGE 0 — TERMINAL */
#p0{background:var(--terminal);}
.term-frame{
height:100%;
display:flex;flex-direction:column;
padding:32px 48px;
position:relative;
}
.term-topbar{
display:flex;align-items:center;gap:12px;
margin-bottom:32px;
border-bottom:1px solid var(--green-faint);
padding-bottom:20px;
}
.term-dots{display:flex;gap:6px;}
.term-dot{width:10px;height:10px;border-radius:50%;}
.td-red{background:#ff3b3b;}
.td-yellow{background:#ffbb00;}
.td-green{background:#00e060;}
.term-title{
font-size:9px;letter-spacing:0.2em;
color:var(--green-dim);flex:1;text-align:center;
text-transform:uppercase;
}
.term-body{
flex:1;
display:flex;flex-direction:column;
justify-content:center;
max-width:700px;
margin:0 auto;
width:100%;
}
.term-prompt{
font-size:9px;color:var(--green-dim);
margin-bottom:8px;letter-spacing:0.08em;
}
.term-prompt span{color:var(--green);}
.term-hero{
margin-bottom:48px;
}
.term-big{
font-family:'Syne',sans-serif;
font-size:clamp(44px,8vw,96px);
font-weight:800;line-height:0.9;
letter-spacing:-0.04em;
color:var(--green);
text-shadow:0 0 40px rgba(0,224,96,0.3);
margin-bottom:4px;
}
.term-big-dim{
font-family:'Syne',sans-serif;
font-size:clamp(20px,3.5vw,44px);
font-weight:400;line-height:1;
letter-spacing:-0.02em;
color:var(--green-dim);
}
.term-output{
display:flex;flex-direction:column;gap:6px;
margin-bottom:40px;
}
.term-line{
font-size:12px;letter-spacing:0.04em;line-height:1.6;
}
.term-line.c{color:var(--dim);}
.term-line.g{color:var(--green);}
.term-line.r{color:var(--red);}
.term-line.y{color:#ffee00;}
.blink{
display:inline-block;width:8px;height:14px;
background:var(--green);vertical-align:middle;
animation:blink .8s step-end infinite;
}
@keyframes blink{0%,100%{opacity:1;}50%{opacity:0;}}
.term-commands{
display:flex;gap:12px;flex-wrap:wrap;
}
.cmd-btn{
padding:10px 24px;
border:1px solid var(--green-dim);
background:none;color:var(--green-mid);
font-family:'JetBrains Mono',monospace;
font-size:11px;letter-spacing:0.08em;
cursor:pointer;
transition:all .15s;
}
.cmd-btn:hover{
border-color:var(--green);color:var(--green);
background:var(--green-faint);
box-shadow:0 0 12px rgba(0,224,96,0.15);
}
.cmd-btn.primary{
background:var(--green);color:var(--terminal);
font-weight:700;border-color:var(--green);
}
.cmd-btn.primary:hover{
background:#00ff70;
box-shadow:0 0 20px rgba(0,224,96,0.4);
}
/* System info sidebar */
.term-side{
position:absolute;right:48px;top:50%;transform:translateY(-50%);
width:220px;
border:1px solid var(--green-faint);
padding:20px;
background:rgba(0,224,96,0.02);
}
.side-label{
font-size:8px;letter-spacing:0.25em;text-transform:uppercase;
color:var(--green-dim);margin-bottom:16px;
border-bottom:1px solid var(--green-faint);
padding-bottom:10px;
}
.side-row{
display:flex;justify-content:space-between;
font-size:10px;padding:6px 0;
border-bottom:1px solid rgba(0,224,96,0.04);
}
.side-k{color:var(--green-dim);}
.side-v{color:var(--green);}
.side-v.ok{color:#00e060;}
.side-v.warn{color:#ffee00;}
.side-v.crit{color:var(--red);animation:blink .5s step-end infinite;}
/* PAGE 1 — OPERATIONS */
#p1{
background:#010a01;
padding:48px;
overflow-y:auto;
}
.p1-header{
display:flex;align-items:baseline;gap:24px;
margin-bottom:40px;
border-bottom:1px solid var(--green-faint);
padding-bottom:20px;
}
.p1-title{
font-family:'Syne',sans-serif;
font-size:clamp(32px,5vw,64px);
font-weight:800;letter-spacing:-0.04em;
color:var(--green);line-height:1;
}
.p1-uptime{
font-size:9px;letter-spacing:0.2em;
color:var(--green-dim);
}
.ops-grid{
display:grid;grid-template-columns:repeat(2,1fr);
gap:2px;
margin-bottom:32px;
}
.op-card{
border:1px solid var(--green-faint);
padding:24px;
background:rgba(0,224,96,0.015);
transition:border-color .2s,background .2s;
cursor:pointer;
}
.op-card:hover{
border-color:var(--green-dim);
background:rgba(0,224,96,0.03);
}
.op-status{
display:flex;align-items:center;gap:8px;
margin-bottom:16px;
}
.op-dot{
width:6px;height:6px;border-radius:50%;
}
.dot-ok{background:var(--green);box-shadow:0 0 6px var(--green);}
.dot-warn{background:#ffee00;box-shadow:0 0 6px #ffee00;}
.dot-crit{background:var(--red);box-shadow:0 0 6px var(--red);animation:blink .5s step-end infinite;}
.op-status-text{
font-size:8px;letter-spacing:0.2em;text-transform:uppercase;color:var(--green-dim);
}
.op-name{
font-family:'Syne',sans-serif;
font-size:20px;font-weight:700;color:var(--white);
letter-spacing:-0.02em;margin-bottom:8px;
}
.op-desc{
font-size:10px;line-height:1.7;color:var(--dim);
letter-spacing:0.02em;margin-bottom:16px;
}
.op-meta{
font-size:9px;color:var(--green-dim);letter-spacing:0.08em;
font-style:italic;
}
.log-feed{
border:1px solid var(--green-faint);
padding:16px 20px;
background:rgba(0,224,96,0.01);
max-height:200px;overflow-y:auto;
}
.log-title{font-size:8px;letter-spacing:0.2em;text-transform:uppercase;color:var(--green-dim);margin-bottom:12px;}
.log-entry{font-size:9px;line-height:1.8;letter-spacing:0.04em;}
.log-time{color:var(--green-dim);}
.log-msg{color:var(--dim);}
.log-ok{color:var(--green);}
.log-err{color:var(--red);}
/* PAGE 2 — THREAT INTEL */
#p2{
background:#010802;
padding:48px;
overflow-y:auto;
}
.p2-header{
display:flex;justify-content:space-between;align-items:flex-end;
margin-bottom:40px;
padding-bottom:20px;
border-bottom:1px solid var(--green-faint);
}
.p2-title{
font-family:'Syne',sans-serif;
font-size:clamp(32px,5vw,64px);
font-weight:800;letter-spacing:-0.04em;
color:var(--red);line-height:1;
}
.threat-level{
display:flex;align-items:center;gap:8px;
padding:8px 20px;
border:1px solid rgba(255,48,80,0.4);
background:rgba(255,48,80,0.05);
font-size:9px;letter-spacing:0.2em;text-transform:uppercase;
color:var(--red);
}
.threat-table{
display:flex;flex-direction:column;gap:2px;
margin-bottom:28px;
}
.threat-row{
display:grid;
grid-template-columns:140px 1fr 100px 80px 80px;
border:1px solid var(--green-faint);
font-size:10px;letter-spacing:0.04em;
}
.threat-row.head{
border-color:rgba(0,224,96,0.12);
font-size:8px;letter-spacing:0.2em;text-transform:uppercase;
}
.th-cell{
padding:14px 16px;
border-right:1px solid var(--green-faint);
display:flex;align-items:center;
}
.th-cell:last-child{border-right:none;}
.sev{
padding:3px 10px;font-size:8px;letter-spacing:0.12em;text-transform:uppercase;
font-weight:700;
}
.sev-crit{background:rgba(255,48,80,0.15);color:var(--red);}
.sev-high{background:rgba(255,160,0,0.12);color:#ffa000;}
.sev-med{background:rgba(255,238,0,0.08);color:#ffee00;}
.stat-badge{
padding:2px 10px;font-size:8px;letter-spacing:0.1em;
}
.badge-new{border:1px solid var(--red);color:var(--red);}
.badge-active{border:1px solid #ffa000;color:#ffa000;}
.badge-patch{border:1px solid var(--green-dim);color:var(--green-dim);}
/* PAGE 3 — TEAM */
#p3{
background:var(--terminal);
padding:48px;
display:flex;flex-direction:column;
}
.p3-header{
display:flex;align-items:baseline;gap:20px;
margin-bottom:48px;border-bottom:1px solid var(--green-faint);
padding-bottom:20px;
}
.p3-title{
font-family:'Syne',sans-serif;
font-size:clamp(36px,6vw,72px);
font-weight:800;letter-spacing:-0.04em;
color:var(--green);line-height:1;
}
.team-grid{
display:grid;grid-template-columns:repeat(4,1fr);gap:2px;
flex:1;align-content:start;
}
.team-card{
border:1px solid var(--green-faint);
padding:24px;
position:relative;
overflow:hidden;
transition:border-color .2s,background .2s;
}
.team-card:hover{
border-color:var(--green-dim);
background:rgba(0,224,96,0.02);
}
.tc-handle{
font-size:10px;letter-spacing:0.12em;
color:var(--green);margin-bottom:12px;
display:flex;align-items:center;gap:6px;
}
.tc-handle::before{content:'@';color:var(--green-dim);}
.tc-role{
font-size:9px;letter-spacing:0.15em;text-transform:uppercase;
color:var(--green-dim);margin-bottom:8px;
}
.tc-name{
font-family:'Syne',sans-serif;
font-size:15px;font-weight:700;
color:var(--white);margin-bottom:16px;
}
.tc-skills{
display:flex;flex-wrap:wrap;gap:6px;
}
.skill-tag{
font-size:8px;letter-spacing:0.1em;
border:1px solid var(--green-faint);
color:var(--green-dim);padding:2px 8px;
}
/* Nav */
.term-nav{
position:fixed;bottom:0;left:0;right:0;z-index:800;
background:rgba(1,10,1,0.95);
border-top:1px solid var(--green-faint);
display:flex;align-items:center;
padding:12px 48px;
gap:0;
backdrop-filter:blur(20px);
}
.tnav-item{
flex:1;padding:8px 16px;
font-size:9px;letter-spacing:0.15em;text-transform:uppercase;
color:var(--green-dim);background:none;border:none;
cursor:pointer;font-family:'JetBrains Mono',monospace;
position:relative;
transition:color .2s;
text-align:left;
}
.tnav-item::before{
content:attr(data-cmd);
position:absolute;left:0;top:0;bottom:0;
display:flex;align-items:center;padding-left:16px;
color:rgba(0,224,96,0.2);
font-size:8px;
pointer-events:none;
transition:opacity .2s;
}
.tnav-item:hover{color:var(--green);}
.tnav-item.active{color:var(--green);}
.tnav-item.active::after{
content:'';position:absolute;top:0;left:0;right:0;height:1px;
background:var(--green);
box-shadow:0 0 8px var(--green);
}
.term-clock{
font-size:9px;letter-spacing:0.15em;color:var(--green-dim);
margin-left:auto;
} const matCanvas=document.getElementById('matCanvas');
const matCtx=matCanvas.getContext('2d');
const pages=document.querySelectorAll('.page');
const navItems=document.querySelectorAll('.tnav-item');
let cur=0,busy=false;
function resize(){matCanvas.width=window.innerWidth;matCanvas.height=window.innerHeight;}
resize();window.addEventListener('resize',resize);
// Matrix characters — katakana + ASCII
const CHARS='アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*<>/\\|{}[]';
function matrixTransition(color,midCallback,done){
const W=matCanvas.width,H=matCanvas.height;
const fontSize=14;
const cols=Math.ceil(W/fontSize);
const rows=Math.ceil(H/fontSize);
const dropPos=Array.from({length:cols},()=>-Math.floor(Math.random()*rows*0.8));
const speed=Array.from({length:cols},()=>Math.random()*1.5+0.8);
let midDone=false;
let phase=0;
function frame(){
matCtx.clearRect(0,0,W,H);
matCtx.fillStyle='rgba(2,13,2,0.15)';
matCtx.fillRect(0,0,W,H);
let allCovered=true;
for(let c=0;c<cols;c++){
const x=c*fontSize;
const drop=dropPos[c];
if(phase===0){
for(let r=0;r<Math.min(drop,rows);r++){
const char=CHARS[Math.floor(Math.random()*CHARS.length)];
const alpha=r/Math.max(drop,1);
if(r===Math.floor(drop)-1){
matCtx.fillStyle='#80ffb0';
matCtx.shadowColor='#00ff70';
matCtx.shadowBlur=8;
}else{
const g=Math.floor(alpha*180+20);
matCtx.fillStyle=`rgba(0,${g},${Math.floor(g*0.3)},${0.3+alpha*0.7})`;
matCtx.shadowBlur=0;
}
matCtx.font=`${fontSize}px 'JetBrains Mono'`;
matCtx.fillText(char,x,r*fontSize);
}
if(drop<rows) allCovered=false;
dropPos[c]+=speed[c];
if(!midDone&&allCovered){
midDone=true;
midCallback&&midCallback();
phase=1;
dropPos.fill(0);
}
} else {
const clearPos=dropPos[c];
for(let r=Math.floor(clearPos);r<rows;r++){
const char=CHARS[Math.floor(Math.random()*CHARS.length)];
const alpha=(r-clearPos)/rows;
matCtx.fillStyle=`rgba(0,${Math.floor(150+alpha*80)},${Math.floor(60+alpha*20)},${0.6-alpha*0.6})`;
matCtx.shadowBlur=0;
matCtx.font=`${fontSize}px 'JetBrains Mono'`;
matCtx.fillText(char,x,r*fontSize);
}
dropPos[c]+=speed[c]*1.5;
}
}
matCtx.shadowBlur=0;
if(phase===1&&dropPos.every(d=>d>=rows)){
matCtx.clearRect(0,0,W,H);
done&&done();
} else {
requestAnimationFrame(frame);
}
}
requestAnimationFrame(frame);
}
function goTo(n){
if(busy||n===cur)return;
busy=true;
const prev=cur;cur=n;
navItems.forEach((it,i)=>it.classList.toggle('active',i===cur));
matrixTransition('#020d02',
()=>{
pages[prev].classList.remove('active');
pages[cur].classList.add('active');
},
()=>{busy=false;}
);
}
navItems.forEach(it=>it.addEventListener('click',()=>goTo(+it.dataset.i)));
document.querySelectorAll('[data-next]').forEach(b=>b.addEventListener('click',()=>goTo(+b.dataset.next)));
let wt=0;
window.addEventListener('wheel',e=>{
const now=Date.now();if(now-wt<1500)return;wt=now;
goTo(Math.max(0,Math.min(3,cur+(e.deltaY>0?1:-1))));
},{passive:true});
document.addEventListener('keydown',e=>{
if(e.key==='ArrowRight')goTo(Math.min(3,cur+1));
if(e.key==='ArrowLeft')goTo(Math.max(0,cur-1));
});
// Clock
(function tick(){
const now=new Date();
const t=now.toUTCString().split(' ').slice(-2)[0];
document.getElementById('termClock').textContent=t+' UTC';
setTimeout(tick,1000);
})();