Spaces:
Running
Running
In the block code in the header, make it better, like add a typing animation for the code, like we are typing it live. Once its done, wait 5s, erase everything and restart animation
Browse files- index.html +5 -10
- script.js +204 -2
- style.css +58 -1
index.html
CHANGED
|
@@ -186,15 +186,10 @@
|
|
| 186 |
<div class="text-xs text-zinc-400">Design system + SSR with Next.js</div>
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
-
<
|
| 190 |
-
<
|
| 191 |
-
<
|
| 192 |
-
|
| 193 |
-
<button className=<span class="text-amber-300">"btn-primary"</span> {...props}>
|
| 194 |
-
{children}
|
| 195 |
-
</button>
|
| 196 |
-
)
|
| 197 |
-
}</pre>
|
| 198 |
<div class="mt-6 grid grid-cols-3 gap-3 text-center">
|
| 199 |
<div class="rounded-xl bg-white/5 border border-white/10 p-3">
|
| 200 |
<div class="text-2xl font-extrabold text-zinc-100">5+ yrs</div>
|
|
@@ -210,7 +205,7 @@
|
|
| 210 |
</div>
|
| 211 |
</div>
|
| 212 |
</div>
|
| 213 |
-
|
| 214 |
<div class="absolute -right-6 -bottom-6 hidden lg:block">
|
| 215 |
<div class="glass-card rounded-2xl border border-white/10 bg-[color:rgb(var(--bg)_/_0.75)] p-4 shadow-soft">
|
| 216 |
<div class="flex items-center gap-3">
|
|
|
|
| 186 |
<div class="text-xs text-zinc-400">Design system + SSR with Next.js</div>
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
+
<div class="mt-6 rounded-xl bg-black/50 text-zinc-300 p-4 overflow-x-auto text-sm leading-relaxed font-mono relative min-h-[140px]">
|
| 190 |
+
<div id="typing-code"></div>
|
| 191 |
+
<div class="typing-cursor"></div>
|
| 192 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
<div class="mt-6 grid grid-cols-3 gap-3 text-center">
|
| 194 |
<div class="rounded-xl bg-white/5 border border-white/10 p-3">
|
| 195 |
<div class="text-2xl font-extrabold text-zinc-100">5+ yrs</div>
|
|
|
|
| 205 |
</div>
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
+
<!-- Floating card -->
|
| 209 |
<div class="absolute -right-6 -bottom-6 hidden lg:block">
|
| 210 |
<div class="glass-card rounded-2xl border border-white/10 bg-[color:rgb(var(--bg)_/_0.75)] p-4 shadow-soft">
|
| 211 |
<div class="flex items-center gap-3">
|
script.js
CHANGED
|
@@ -267,7 +267,6 @@ if (grid) {
|
|
| 267 |
grid.appendChild(el);
|
| 268 |
});
|
| 269 |
}
|
| 270 |
-
|
| 271 |
// Year
|
| 272 |
document.getElementById('year').textContent = new Date().getFullYear();
|
| 273 |
|
|
@@ -281,4 +280,207 @@ document.getElementById('download-cv')?.addEventListener('click', (e) => {
|
|
| 281 |
a.download = 'CoolDev-Resume.txt';
|
| 282 |
a.click();
|
| 283 |
URL.revokeObjectURL(url);
|
| 284 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
grid.appendChild(el);
|
| 268 |
});
|
| 269 |
}
|
|
|
|
| 270 |
// Year
|
| 271 |
document.getElementById('year').textContent = new Date().getFullYear();
|
| 272 |
|
|
|
|
| 280 |
a.download = 'CoolDev-Resume.txt';
|
| 281 |
a.click();
|
| 282 |
URL.revokeObjectURL(url);
|
| 283 |
+
});
|
| 284 |
+
|
| 285 |
+
// Typing animation for code
|
| 286 |
+
function initTypingAnimation() {
|
| 287 |
+
const codeContainer = document.getElementById('typing-code');
|
| 288 |
+
if (!codeContainer) return;
|
| 289 |
+
|
| 290 |
+
const codeLines = [
|
| 291 |
+
{
|
| 292 |
+
text: 'const theme = "dark"',
|
| 293 |
+
chars: [
|
| 294 |
+
{ type: 'keyword', text: 'const' },
|
| 295 |
+
{ type: 'text', text: ' ' },
|
| 296 |
+
{ type: 'text', text: 'theme ' },
|
| 297 |
+
{ type: 'operator', text: '=' },
|
| 298 |
+
{ type: 'text', text: ' ' },
|
| 299 |
+
{ type: 'string', text: '"dark"' }
|
| 300 |
+
]
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
text: 'function Button({ children, ...props }) {',
|
| 304 |
+
chars: [
|
| 305 |
+
{ type: 'keyword', text: 'function' },
|
| 306 |
+
{ type: 'text', text: ' ' },
|
| 307 |
+
{ type: 'function', text: 'Button' },
|
| 308 |
+
{ type: 'punctuation', text: '(' },
|
| 309 |
+
{ type: 'punctuation', text: '{' },
|
| 310 |
+
{ type: 'text', text: ' children, ' },
|
| 311 |
+
{ type: 'operator', text: '...' },
|
| 312 |
+
{ type: 'text', text: 'props ' },
|
| 313 |
+
{ type: 'punctuation', text: '}' },
|
| 314 |
+
{ type: 'punctuation', text: ')' },
|
| 315 |
+
{ type: 'text', text: ' ' },
|
| 316 |
+
{ type: 'punctuation', text: '{' }
|
| 317 |
+
]
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
text: ' return (',
|
| 321 |
+
chars: [
|
| 322 |
+
{ type: 'text', text: ' ' },
|
| 323 |
+
{ type: 'keyword', text: 'return' },
|
| 324 |
+
{ type: 'text', text: ' (' }
|
| 325 |
+
]
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
text: ' <button className="btn-primary" {...props}>',
|
| 329 |
+
chars: [
|
| 330 |
+
{ type: 'text', text: ' ' },
|
| 331 |
+
{ type: 'tag', text: '<' },
|
| 332 |
+
{ type: 'tag', text: 'button' },
|
| 333 |
+
{ type: 'text', text: ' ' },
|
| 334 |
+
{ type: 'attr', text: 'className' },
|
| 335 |
+
{ type: 'operator', text: '=' },
|
| 336 |
+
{ type: 'string', text: '"btn-primary"' },
|
| 337 |
+
{ type: 'text', text: ' ' },
|
| 338 |
+
{ type: 'operator', text: '{' },
|
| 339 |
+
{ type: 'operator', text: '.' },
|
| 340 |
+
{ type: 'operator', text: '.' },
|
| 341 |
+
{ type: 'operator', text: '}' },
|
| 342 |
+
{ type: 'tag', text: '>' }
|
| 343 |
+
]
|
| 344 |
+
},
|
| 345 |
+
{
|
| 346 |
+
text: ' {children}',
|
| 347 |
+
chars: [
|
| 348 |
+
{ type: 'text', text: ' ' },
|
| 349 |
+
{ type: 'punctuation', text: '{' },
|
| 350 |
+
{ type: 'text', text: 'children' },
|
| 351 |
+
{ type: 'punctuation', text: '}' }
|
| 352 |
+
]
|
| 353 |
+
},
|
| 354 |
+
{
|
| 355 |
+
text: ' </button>',
|
| 356 |
+
chars: [
|
| 357 |
+
{ type: 'text', text: ' ' },
|
| 358 |
+
{ type: 'tag', text: '<' },
|
| 359 |
+
{ type: 'operator', text: '/' },
|
| 360 |
+
{ type: 'tag', text: 'button' },
|
| 361 |
+
{ type: 'tag', text: '>' }
|
| 362 |
+
]
|
| 363 |
+
},
|
| 364 |
+
{
|
| 365 |
+
text: ' )',
|
| 366 |
+
chars: [
|
| 367 |
+
{ type: 'text', text: ' ' },
|
| 368 |
+
{ type: 'punctuation', text: ')' }
|
| 369 |
+
]
|
| 370 |
+
},
|
| 371 |
+
{
|
| 372 |
+
text: '}',
|
| 373 |
+
chars: [
|
| 374 |
+
{ type: 'punctuation', text: '}' }
|
| 375 |
+
]
|
| 376 |
+
}
|
| 377 |
+
];
|
| 378 |
+
|
| 379 |
+
let currentLine = 0;
|
| 380 |
+
let currentChar = 0;
|
| 381 |
+
let isTyping = true;
|
| 382 |
+
let timeoutId;
|
| 383 |
+
|
| 384 |
+
const cursor = document.querySelector('.typing-cursor');
|
| 385 |
+
|
| 386 |
+
function typeCharacter() {
|
| 387 |
+
if (currentLine >= codeLines.length) {
|
| 388 |
+
// Finished typing all lines
|
| 389 |
+
isTyping = false;
|
| 390 |
+
|
| 391 |
+
// Wait 5 seconds, then restart
|
| 392 |
+
setTimeout(() => {
|
| 393 |
+
restartTyping();
|
| 394 |
+
}, 5000);
|
| 395 |
+
|
| 396 |
+
return;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
const line = codeLines[currentLine];
|
| 400 |
+
const char = line.chars[currentChar];
|
| 401 |
+
|
| 402 |
+
// Create and append the character element
|
| 403 |
+
const charElement = document.createElement('span');
|
| 404 |
+
charElement.className = char.type !== 'text' ? char.type : '';
|
| 405 |
+
charElement.textContent = char.text;
|
| 406 |
+
codeContainer.appendChild(charElement);
|
| 407 |
+
|
| 408 |
+
currentChar++;
|
| 409 |
+
|
| 410 |
+
// Calculate typing speed (varies by character type)
|
| 411 |
+
let delay = 50; // default delay
|
| 412 |
+
|
| 413 |
+
if (char.type === 'punctuation') delay = 100;
|
| 414 |
+
else if (char.type === 'operator') delay = 80;
|
| 415 |
+
else if (char.type === 'keyword' || char.type === 'function') delay = 120;
|
| 416 |
+
else if (char.type === 'string') delay = 150;
|
| 417 |
+
else if (char.text === ' ') delay = 30;
|
| 418 |
+
else if (char.text === '\n') delay = 500;
|
| 419 |
+
|
| 420 |
+
// Add some random variation
|
| 421 |
+
delay += Math.random() * 50;
|
| 422 |
+
|
| 423 |
+
timeoutId = setTimeout(typeCharacter, delay);
|
| 424 |
+
|
| 425 |
+
// If we finished the current line, move to next line
|
| 426 |
+
if (currentChar >= line.chars.length) {
|
| 427 |
+
// Add line break
|
| 428 |
+
const lineBreak = document.createElement('br');
|
| 429 |
+
codeContainer.appendChild(lineBreak);
|
| 430 |
+
currentLine++;
|
| 431 |
+
currentChar = 0;
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
function eraseCharacters() {
|
| 436 |
+
const lastChild = codeContainer.lastElementChild;
|
| 437 |
+
if (!lastChild || (lastChild.tagName === 'BR' && !lastChild.previousElementSibling)) {
|
| 438 |
+
// If only line breaks left, restart typing
|
| 439 |
+
if (!lastChild || !lastChild.previousElementSibling) {
|
| 440 |
+
restartTyping();
|
| 441 |
+
return;
|
| 442 |
+
}
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
if (lastChild) {
|
| 446 |
+
codeContainer.removeChild(lastChild);
|
| 447 |
+
setTimeout(eraseCharacters, 20); // Erase speed
|
| 448 |
+
} else {
|
| 449 |
+
setTimeout(eraseCharacters, 50);
|
| 450 |
+
}
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
function restartTyping() {
|
| 454 |
+
// Clear existing content
|
| 455 |
+
codeContainer.innerHTML = '';
|
| 456 |
+
|
| 457 |
+
// Reset counters
|
| 458 |
+
currentLine = 0;
|
| 459 |
+
currentChar = 0;
|
| 460 |
+
isTyping = true;
|
| 461 |
+
|
| 462 |
+
// Clear any existing timeout
|
| 463 |
+
if (timeoutId) {
|
| 464 |
+
clearTimeout(timeoutId);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
// Start typing again
|
| 468 |
+
setTimeout(typeCharacter, 1000); // 1 second delay before restart
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
// Start the typing animation
|
| 472 |
+
setTimeout(typeCharacter, 2000); // 2 second initial delay
|
| 473 |
+
|
| 474 |
+
// Hide cursor when not typing (during wait periods)
|
| 475 |
+
setInterval(() => {
|
| 476 |
+
if (cursor && isTyping) {
|
| 477 |
+
cursor.classList.toggle('hide');
|
| 478 |
+
}
|
| 479 |
+
}, 500);
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
// Initialize typing animation when page loads
|
| 483 |
+
document.addEventListener('DOMContentLoaded', initTypingAnimation);
|
| 484 |
+
|
| 485 |
+
// Re-initialize when feather icons are replaced (since it might interfere)
|
| 486 |
+
setTimeout(initTypingAnimation, 100);
|
style.css
CHANGED
|
@@ -165,10 +165,67 @@ input, textarea {
|
|
| 165 |
z-index: 60;
|
| 166 |
transition: width 0.1s linear;
|
| 167 |
}
|
| 168 |
-
|
| 169 |
/* Reduce motion for users who prefer it */
|
| 170 |
@media (prefers-reduced-motion: reduce) {
|
| 171 |
.animate-blob, .animate-float {
|
| 172 |
animation: none !important;
|
| 173 |
}
|
| 174 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
z-index: 60;
|
| 166 |
transition: width 0.1s linear;
|
| 167 |
}
|
|
|
|
| 168 |
/* Reduce motion for users who prefer it */
|
| 169 |
@media (prefers-reduced-motion: reduce) {
|
| 170 |
.animate-blob, .animate-float {
|
| 171 |
animation: none !important;
|
| 172 |
}
|
| 173 |
}
|
| 174 |
+
|
| 175 |
+
/* Typing animation styles */
|
| 176 |
+
.typing-cursor {
|
| 177 |
+
display: inline-block;
|
| 178 |
+
width: 2px;
|
| 179 |
+
height: 1.2em;
|
| 180 |
+
background: #a78bfa;
|
| 181 |
+
margin-left: 2px;
|
| 182 |
+
animation: blink 1s infinite;
|
| 183 |
+
vertical-align: bottom;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.typing-cursor.hide {
|
| 187 |
+
opacity: 0;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
@keyframes blink {
|
| 191 |
+
0%, 50% { opacity: 1; }
|
| 192 |
+
51%, 100% { opacity: 0; }
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/* Syntax highlighting for typing animation */
|
| 196 |
+
.typing-text {
|
| 197 |
+
white-space: pre-wrap;
|
| 198 |
+
word-break: break-word;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.keyword {
|
| 202 |
+
color: #10b981; /* emerald-400 */
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.string {
|
| 206 |
+
color: #f59e0b; /* amber-500 */
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.operator {
|
| 210 |
+
color: #ec4899; /* pink-400 */
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.function {
|
| 214 |
+
color: #38bdf8; /* sky-400 */
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.tag {
|
| 218 |
+
color: #f87171; /* red-400 */
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.attr {
|
| 222 |
+
color: #f59e0b; /* amber-500 */
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.comment {
|
| 226 |
+
color: #6b7280; /* gray-500 */
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.punctuation {
|
| 230 |
+
color: #9ca3af; /* gray-400 */
|
| 231 |
+
}
|