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:
BIN
attached_assets/image_1777002696453.png
Normal file
BIN
attached_assets/image_1777002696453.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -14,7 +14,7 @@
|
||||
|
||||
<main class="stage">
|
||||
<div class="command-box" id="commandBox">
|
||||
<span class="dollar">$</span><span class="typed" id="typed"></span><span class="user-input" id="userInput"></span><span class="caret" id="caret">█</span>
|
||||
<span class="dollar">$</span><span class="typed" id="typed"></span><span class="prompt-space" aria-hidden="true"> </span><span class="user-input" id="userInput"></span><span class="caret" id="caret" aria-hidden="true"></span>
|
||||
<button class="action-btn" id="actionBtn" aria-label="Copy to clipboard">
|
||||
<svg id="actionIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path id="iconPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
|
||||
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();
|
||||
|
||||
@@ -111,6 +111,18 @@ body {
|
||||
box-shadow: 0 0 0 1px var(--accent-soft), 0 0 28px rgba(166, 227, 161, 0.18);
|
||||
}
|
||||
|
||||
/* glitch state — subtle chromatic-aberration shadow on the typed text */
|
||||
.command-box.glitch {
|
||||
border-color: var(--accent-strong);
|
||||
box-shadow: 0 0 0 1px rgba(166, 227, 161, 0.10), 0 0 32px rgba(166, 227, 161, 0.10);
|
||||
}
|
||||
.command-box.glitch .typed,
|
||||
.command-box.glitch .user-input {
|
||||
text-shadow:
|
||||
-1px 0 rgba(243, 139, 168, 0.7),
|
||||
1px 0 rgba(137, 180, 250, 0.7);
|
||||
}
|
||||
|
||||
.dollar {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
@@ -122,17 +134,23 @@ body {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt-space {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.user-input {
|
||||
color: var(--fg);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* a real terminal block caret — exactly one monospace char wide, no offset */
|
||||
.caret {
|
||||
display: inline-block;
|
||||
color: var(--accent);
|
||||
font-size: 0.85em;
|
||||
transform: translateY(-1px);
|
||||
margin-left: 1px;
|
||||
width: 0.6em;
|
||||
height: 1.05em;
|
||||
background: var(--accent);
|
||||
vertical-align: text-bottom;
|
||||
margin-bottom: 0.05em;
|
||||
animation: blink 1.05s steps(1, end) infinite;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user