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:
71
src/main.ts
71
src/main.ts
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user