Improve command input and "how it works" animation

Fixes cursor positioning errors by explicitly managing the space between the command prompt and user input, and enhances the "how it works" animation with glitch effects and a more dynamic visual sequence.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 6def8112-39d2-4641-b93b-f39108179f33
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 9956cf72-7ee5-4600-91e5-0d22e4fbc583
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/42ae33dd-8759-4196-85a5-434465c72ece/6def8112-39d2-4641-b93b-f39108179f33/HyFeWjl
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
keshavananddev
2026-04-24 03:52:04 +00:00
parent ac056ef7fb
commit 4d0c742b02
4 changed files with 72 additions and 27 deletions

View File

@@ -1,7 +1,6 @@
import './style.css';
const COMMAND = 'ssh portfolio@keshavanand.net';
const PROMPT_TAIL = ' '; // single space between prefix and user input
const typedEl = document.getElementById('typed') as HTMLSpanElement;
const userEl = document.getElementById('userInput') as HTMLSpanElement;
@@ -58,7 +57,7 @@ function typeIntro(target: string, onDone?: () => void) {
function nextChar() {
if (i >= target.length) {
typedEl.textContent = resolved + PROMPT_TAIL;
typedEl.textContent = resolved;
onDone?.();
return;
}
@@ -82,7 +81,7 @@ function typeIntro(target: string, onDone?: () => void) {
}
if (reduceMotion) {
typedEl.textContent = COMMAND + PROMPT_TAIL;
typedEl.textContent = COMMAND;
inputEnabled = true;
} else {
setTimeout(() => typeIntro(COMMAND, () => {
@@ -206,40 +205,68 @@ commandBox.addEventListener('click', (e) => {
/* ---------- auto-type from "how it works" link ---------- */
function rand<T>(a: ArrayLike<T>): T {
return a[Math.floor(Math.random() * a.length)];
}
async function autoTypeHelp() {
if (!inputEnabled || isAutoTyping) return;
isAutoTyping = true;
// clear whatever the user had typed
// clear current input, arm the box
userInput = '';
refreshUI();
commandBox.classList.add('armed');
await sleep(120);
// ripple from where the user clicked the link
const linkRect = docsLink.getBoundingClientRect();
spawnRipple(linkRect.left + linkRect.width / 2, linkRect.top + linkRect.height / 2);
const text = '-h';
for (const ch of text) {
// small scramble for flair, then settle
let cycles = 2;
while (cycles > 0) {
const sc = SCRAMBLE[Math.floor(Math.random() * SCRAMBLE.length)];
userEl.textContent = userInput + sc;
await sleep(38);
cycles--;
await sleep(90);
// PHASE 1 — heavy glitch flicker across both character slots,
// with a tiny box jitter for chromatic-aberration energy
commandBox.classList.add('glitch');
const glitchSteps = 14;
for (let s = 0; s < glitchSteps; s++) {
const a = rand(SCRAMBLE);
const b = Math.random() > 0.4 ? rand(SCRAMBLE) : '';
userEl.textContent = a + b;
// micro-jitter the box
const jx = (Math.random() - 0.5) * 3;
const jy = (Math.random() - 0.5) * 1.5;
commandBox.style.transform = `translate(${jx.toFixed(2)}px, ${jy.toFixed(2)}px)`;
await sleep(28 + Math.random() * 18);
}
commandBox.style.transform = '';
commandBox.classList.remove('glitch');
// PHASE 2 — settle to "-h" with a brief micro-flicker on each char
userInput = '';
for (const ch of '-h') {
// 1-2 quick scramble flickers, then resolve
for (let c = 0; c < 2; c++) {
userEl.textContent = userInput + rand(SCRAMBLE);
await sleep(34);
}
userInput += ch;
refreshUI();
await sleep(70);
await sleep(60);
}
// brief pause so the user reads it
await sleep(260);
// PHASE 3 — quick read pause + ripple from the box itself
await sleep(220);
const boxRect = commandBox.getBoundingClientRect();
spawnRipple(boxRect.right - 24, boxRect.top + boxRect.height / 2);
// visual "Enter pressed" — pulse the action button
actionBtn.style.transform = 'scale(0.85)';
setTimeout(() => { actionBtn.style.transform = ''; }, 140);
await sleep(180);
// PHASE 4 — Enter "press": punch the action icon down + a confirming ripple
actionBtn.style.transition = 'transform 90ms ease-out';
actionBtn.style.transform = 'scale(0.78)';
await sleep(110);
actionBtn.style.transform = 'scale(1)';
await sleep(140);
actionBtn.style.transition = '';
actionBtn.style.transform = '';
isAutoTyping = false;
submit();