Mooshie's picture
increase professionalism and interactivity
ec87762 verified
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);