Spaces:
Running
Running
| class CustomTerminal extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: block; | |
| padding: 6rem 1rem; | |
| background: #0f0f0f; | |
| border-top: 1px solid #222; | |
| border-bottom: 1px solid #222; | |
| } | |
| .terminal-window { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| background: #1a1a1a; | |
| border-radius: 8px; | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 0 15px rgba(234, 179, 8, 0.05); | |
| border: 1px solid #333; | |
| overflow: hidden; | |
| font-family: 'Fira Code', monospace; | |
| } | |
| .terminal-header { | |
| background: #262626; | |
| padding: 0.75rem 1rem; | |
| display: flex; | |
| align-items: center; | |
| border-bottom: 1px solid #333; | |
| } | |
| .terminal-buttons { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .dot { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| } | |
| .red { background: #ef4444; } | |
| .yellow { background: #eab308; } | |
| .green { background: #22c55e; } | |
| .terminal-title { | |
| margin-left: 1rem; | |
| color: #9ca3af; | |
| font-size: 0.8rem; | |
| } | |
| .terminal-body { | |
| padding: 1.5rem; | |
| color: #d4d4d8; | |
| font-size: 0.95rem; | |
| line-height: 1.8; | |
| min-height: 200px; | |
| } | |
| .command-line { | |
| display: flex; | |
| margin-bottom: 0.5rem; | |
| } | |
| .prompt { | |
| color: #EAB308; | |
| margin-right: 0.75rem; | |
| white-space: nowrap; | |
| } | |
| .command { | |
| color: #fff; | |
| } | |
| .output { | |
| color: #a1a1aa; | |
| margin-bottom: 1.5rem; | |
| padding-left: 1.5rem; | |
| border-left: 2px solid #333; | |
| } | |
| .output li { | |
| list-style: none; | |
| } | |
| .output strong { | |
| color: #EAB308; | |
| } | |
| .cursor { | |
| display: inline-block; | |
| width: 8px; | |
| height: 18px; | |
| background: #EAB308; | |
| vertical-align: middle; | |
| margin-left: 2px; | |
| animation: blink 1s step-end infinite; | |
| } | |
| @keyframes blink { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0; } | |
| } | |
| .typing-effect { | |
| overflow: hidden; | |
| white-space: nowrap; | |
| width: 0; | |
| animation: typing 2s steps(40, end) forwards; | |
| } | |
| @keyframes typing { | |
| from { width: 0 } | |
| to { width: 100% } | |
| } | |
| .interactive-command { | |
| color: #22c55e; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .interactive-command:hover { | |
| color: #4ade80; | |
| text-decoration: underline; | |
| } | |
| .file-tree { | |
| font-family: 'Fira Code', monospace; | |
| color: #a1a1aa; | |
| line-height: 1.6; | |
| } | |
| .tree-item { | |
| padding: 0.25rem 0; | |
| } | |
| .tree-folder { | |
| color: #EAB308; | |
| } | |
| .tree-file { | |
| color: #9ca3af; | |
| } | |
| </style> | |
| <section id="about"> | |
| <div class="terminal-window"> | |
| <div class="terminal-header"> | |
| <div class="terminal-buttons"> | |
| <div class="dot red"></div> | |
| <div class="dot yellow"></div> | |
| <div class="dot green"></div> | |
| </div> | |
| <div class="terminal-title">kent@devops:~</div> | |
| </div> | |
| <div class="terminal-body" id="terminal-content"> | |
| <!-- Content injected via JS --> | |
| <div class="command-line"> | |
| <span class="prompt">$</span> | |
| <span class="command typing-effect" style="animation-delay: 0.5s;">whoami</span> | |
| </div> | |
| <div class="output" id="output-1" style="opacity: 0; animation: fadeIn 0.5s forwards 2.5s;"> | |
| <strong>> Kent Vuong</strong><br> | |
| > Full-Stack Developer & DevOps Engineer<br> | |
| > Building scalable solutions since 2019 | |
| </div> | |
| <div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 3s;"> | |
| <span class="prompt">$</span> | |
| <span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 3.5s;">ls -la experience/</span> | |
| </div> | |
| <div class="output" id="output-2" style="opacity: 0; animation: fadeIn 0.5s forwards 5.5s;"> | |
| <div class="file-tree"> | |
| <div class="tree-item tree-folder">π .senior-engineer/</div> | |
| <div class="tree-item tree-file"> βββ microservices-architecture.yaml</div> | |
| <div class="tree-item tree-folder">π .cloud-infrastructure/</div> | |
| <div class="tree-item tree-file"> βββ aws-deployments/</div> | |
| <div class="tree-item tree-file"> βββ kubernetes-clusters/</div> | |
| <div class="tree-item tree-file"> βββ ci-cd-pipelines/</div> | |
| <div class="tree-item tree-folder">π .web-development/</div> | |
| <div class="tree-item tree-file"> βββ react-applications/</div> | |
| <div class="tree-item tree-file"> βββ nextjs-ssr/</div> | |
| <div class="tree-item tree-file"> βββ nodejs-apis/</div> | |
| </div> | |
| </div> | |
| <div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 6s;"> | |
| <span class="prompt">$</span> | |
| <span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 6.5s;">cat motivation.txt</span> | |
| </div> | |
| <div class="output" style="opacity: 0; animation: fadeIn 0.5s forwards 8.5s;"> | |
| <strong>> "Code is poetry, infrastructure is the canvas."</strong><br> | |
| > Passionate about creating elegant solutions<br> | |
| > to complex problems. Always learning, always evolving. | |
| </div> | |
| <div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 9s;"> | |
| <span class="prompt">$</span> | |
| <span class="interactive-command" id="try-cmd">Try typing a command...</span> | |
| </div> | |
| <div class="command-line"> | |
| <span class="prompt">$</span> | |
| <input type="text" id="terminal-input" style="background: transparent; border: none; color: #fff; font-family: inherit; font-size: inherit; outline: none; width: calc(100% - 40px);" autocomplete="off"> | |
| <span class="cursor"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <style> | |
| @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } | |
| </style> | |
| `; | |
| } | |
| initTerminal() { | |
| const input = this.shadowRoot.getElementById('terminal-input'); | |
| input.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| const command = input.value.trim().toLowerCase(); | |
| this.handleCommand(command); | |
| input.value = ''; | |
| } | |
| }); | |
| } | |
| handleCommand(command) { | |
| const terminalBody = this.shadowRoot.getElementById('terminal-content'); | |
| // Create new command line | |
| const newCommandLine = document.createElement('div'); | |
| newCommandLine.className = 'command-line'; | |
| newCommandLine.innerHTML = `<span class="prompt">$</span><span class="command">${command}</span>`; | |
| // Insert before the input line | |
| const inputLine = terminalBody.lastElementChild; | |
| terminalBody.insertBefore(newCommandLine, inputLine); | |
| // Create output | |
| const output = document.createElement('div'); | |
| output.className = 'output'; | |
| switch(command) { | |
| case 'help': | |
| output.innerHTML = `<strong>Available commands:</strong><br> | |
| > about - Learn more about me<br> | |
| > skills - View my technical skills<br> | |
| > projects - See my work<br> | |
| > contact - Get in touch<br> | |
| > clear - Clear terminal`; | |
| break; | |
| case 'about': | |
| output.innerHTML = `<strong>Kent Vuong</strong><br> | |
| > Full-Stack & DevOps Engineer<br> | |
| > 5+ years of experience<br> | |
| > Based in San Francisco, CA`; | |
| break; | |
| case 'skills': | |
| output.innerHTML = `<strong>Core Skills:</strong><br> | |
| > Frontend: React, Vue, Next.js<br> | |
| > Backend: Node.js, Python, Go<br> | |
| > DevOps: AWS, Docker, K8s, Terraform<br> | |
| > Databases: PostgreSQL, MongoDB, Redis`; | |
| break; | |
| case 'projects': | |
| output.innerHTML = `<strong>Featured Projects:</strong><br> | |
| > mooshieblob.com - AI Showcase<br> | |
| > Cloud Dashboard - Infrastructure Monitor<br> | |
| > E-Commerce Platform - Full-Stack Solution<br> | |
| > DevOps Pipeline - CI/CD Automation`; | |
| break; | |
| case 'contact': | |
| output.innerHTML = `<strong>Let's Connect!</strong><br> | |
| > Email: hello@kentvuong.com<br> | |
| > GitHub: @Mooshieblob1<br> | |
| > LinkedIn: /in/kentvuong<br> | |
| > <a href="#contact" style="color: #EAB308;">Jump to Contact Section β</a>`; | |
| break; | |
| case 'clear': | |
| // Remove all command lines except the last one (input) | |
| while (terminalBody.children.length > 1) { | |
| terminalBody.removeChild(terminalBody.firstChild); | |
| } | |
| return; | |
| default: | |
| output.innerHTML = `<span style="color: #ef4444;">Command not found: ${command}</span><br>Type 'help' for available commands.`; | |
| } | |
| terminalBody.insertBefore(output, inputLine); | |
| } | |
| } | |
| customElements.define('custom-terminal', CustomTerminal); | |