Spaces:
Running
Running
increase professionalism and interactivity
Browse files- components/back-to-top.js +75 -0
- components/contact.js +41 -4
- components/footer.js +118 -0
- components/hero.js +86 -11
- components/preloader.js +96 -0
- components/projects.js +63 -23
- components/scroll-progress.js +37 -0
- components/skills.js +70 -10
- components/terminal.js +132 -9
- index.html +18 -4
- script.js +112 -5
- style.css +121 -3
components/back-to-top.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomBackToTop extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
position: fixed;
|
| 8 |
+
bottom: 2rem;
|
| 9 |
+
right: 2rem;
|
| 10 |
+
z-index: 100;
|
| 11 |
+
opacity: 0;
|
| 12 |
+
visibility: hidden;
|
| 13 |
+
transition: all 0.3s ease;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
:host.visible {
|
| 17 |
+
opacity: 1;
|
| 18 |
+
visibility: visible;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.back-to-top-btn {
|
| 22 |
+
width: 50px;
|
| 23 |
+
height: 50px;
|
| 24 |
+
background: #EAB308;
|
| 25 |
+
border: none;
|
| 26 |
+
border-radius: 50%;
|
| 27 |
+
cursor: pointer;
|
| 28 |
+
display: flex;
|
| 29 |
+
align-items: center;
|
| 30 |
+
justify-content: center;
|
| 31 |
+
box-shadow: 0 4px 15px rgba(234, 179, 8, 0.3);
|
| 32 |
+
transition: all 0.3s ease;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.back-to-top-btn:hover {
|
| 36 |
+
transform: translateY(-5px);
|
| 37 |
+
box-shadow: 0 8px 25px rgba(234, 179, 8, 0.4);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.back-to-top-btn svg {
|
| 41 |
+
color: black;
|
| 42 |
+
transition: transform 0.3s ease;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.back-to-top-btn:hover svg {
|
| 46 |
+
transform: translateY(-3px);
|
| 47 |
+
}
|
| 48 |
+
</style>
|
| 49 |
+
|
| 50 |
+
<button class="back-to-top-btn" id="back-to-top" aria-label="Back to top">
|
| 51 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>
|
| 52 |
+
</button>
|
| 53 |
+
`;
|
| 54 |
+
|
| 55 |
+
const btn = this.shadowRoot.getElementById('back-to-top');
|
| 56 |
+
|
| 57 |
+
// Show/hide button based on scroll position
|
| 58 |
+
window.addEventListener('scroll', () => {
|
| 59 |
+
if (window.scrollY > 500) {
|
| 60 |
+
this.classList.add('visible');
|
| 61 |
+
} else {
|
| 62 |
+
this.classList.remove('visible');
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
// Scroll to top on click
|
| 67 |
+
btn.addEventListener('click', () => {
|
| 68 |
+
window.scrollTo({
|
| 69 |
+
top: 0,
|
| 70 |
+
behavior: 'smooth'
|
| 71 |
+
});
|
| 72 |
+
});
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
customElements.define('custom-back-to-top', CustomBackToTop);
|
components/contact.js
CHANGED
|
@@ -41,13 +41,32 @@ class CustomContact extends HTMLElement {
|
|
| 41 |
transition: all 0.3s ease;
|
| 42 |
margin-bottom: 3rem;
|
| 43 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
.email-btn:hover {
|
| 46 |
background: rgba(234, 179, 8, 0.1);
|
| 47 |
transform: translateY(-3px);
|
|
|
|
| 48 |
}
|
| 49 |
-
|
| 50 |
-
.social-links {
|
| 51 |
display: flex;
|
| 52 |
justify-content: center;
|
| 53 |
gap: 2rem;
|
|
@@ -57,13 +76,31 @@ class CustomContact extends HTMLElement {
|
|
| 57 |
color: #52525b;
|
| 58 |
transition: all 0.3s ease;
|
| 59 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
.social-link:hover {
|
| 62 |
color: #EAB308;
|
| 63 |
transform: translateY(-3px);
|
| 64 |
}
|
| 65 |
-
|
| 66 |
-
.footer-note {
|
| 67 |
margin-top: 4rem;
|
| 68 |
color: #52525b;
|
| 69 |
font-size: 0.875rem;
|
|
|
|
| 41 |
transition: all 0.3s ease;
|
| 42 |
margin-bottom: 3rem;
|
| 43 |
}
|
| 44 |
+
.email-btn {
|
| 45 |
+
position: relative;
|
| 46 |
+
overflow: hidden;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.email-btn::before {
|
| 50 |
+
content: '';
|
| 51 |
+
position: absolute;
|
| 52 |
+
top: 0;
|
| 53 |
+
left: -100%;
|
| 54 |
+
width: 100%;
|
| 55 |
+
height: 100%;
|
| 56 |
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
| 57 |
+
transition: left 0.5s ease;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.email-btn:hover::before {
|
| 61 |
+
left: 100%;
|
| 62 |
+
}
|
| 63 |
|
| 64 |
.email-btn:hover {
|
| 65 |
background: rgba(234, 179, 8, 0.1);
|
| 66 |
transform: translateY(-3px);
|
| 67 |
+
box-shadow: 0 10px 20px rgba(234, 179, 8, 0.2);
|
| 68 |
}
|
| 69 |
+
.social-links {
|
|
|
|
| 70 |
display: flex;
|
| 71 |
justify-content: center;
|
| 72 |
gap: 2rem;
|
|
|
|
| 76 |
color: #52525b;
|
| 77 |
transition: all 0.3s ease;
|
| 78 |
}
|
| 79 |
+
.social-link {
|
| 80 |
+
position: relative;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.social-link::after {
|
| 84 |
+
content: '';
|
| 85 |
+
position: absolute;
|
| 86 |
+
bottom: -5px;
|
| 87 |
+
left: 50%;
|
| 88 |
+
width: 0;
|
| 89 |
+
height: 2px;
|
| 90 |
+
background: #EAB308;
|
| 91 |
+
transition: all 0.3s ease;
|
| 92 |
+
transform: translateX(-50%);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.social-link:hover::after {
|
| 96 |
+
width: 100%;
|
| 97 |
+
}
|
| 98 |
|
| 99 |
.social-link:hover {
|
| 100 |
color: #EAB308;
|
| 101 |
transform: translateY(-3px);
|
| 102 |
}
|
| 103 |
+
.footer-note {
|
|
|
|
| 104 |
margin-top: 4rem;
|
| 105 |
color: #52525b;
|
| 106 |
font-size: 0.875rem;
|
components/footer.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
background: #0a0a0a;
|
| 9 |
+
border-top: 1px solid #262626;
|
| 10 |
+
padding: 4rem 1rem 2rem 1rem;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.footer-content {
|
| 14 |
+
max-width: 1200px;
|
| 15 |
+
margin: 0 auto;
|
| 16 |
+
text-align: center;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.footer-logo {
|
| 20 |
+
font-family: 'Fira Code', monospace;
|
| 21 |
+
font-size: 1.5rem;
|
| 22 |
+
font-weight: 700;
|
| 23 |
+
color: #EAB308;
|
| 24 |
+
margin-bottom: 1.5rem;
|
| 25 |
+
display: inline-block;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.footer-links {
|
| 29 |
+
display: flex;
|
| 30 |
+
justify-content: center;
|
| 31 |
+
gap: 2rem;
|
| 32 |
+
margin-bottom: 2rem;
|
| 33 |
+
flex-wrap: wrap;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.footer-link {
|
| 37 |
+
color: #9ca3af;
|
| 38 |
+
text-decoration: none;
|
| 39 |
+
font-size: 0.9rem;
|
| 40 |
+
transition: color 0.3s ease;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.footer-link:hover {
|
| 44 |
+
color: #EAB308;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.social-icons {
|
| 48 |
+
display: flex;
|
| 49 |
+
justify-content: center;
|
| 50 |
+
gap: 1.5rem;
|
| 51 |
+
margin-bottom: 2rem;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.social-icon {
|
| 55 |
+
color: #52525b;
|
| 56 |
+
transition: all 0.3s ease;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.social-icon:hover {
|
| 60 |
+
color: #EAB308;
|
| 61 |
+
transform: translateY(-3px);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.copyright {
|
| 65 |
+
color: #52525b;
|
| 66 |
+
font-size: 0.875rem;
|
| 67 |
+
font-family: 'Fira Code', monospace;
|
| 68 |
+
padding-top: 2rem;
|
| 69 |
+
border-top: 1px solid #1f1f1f;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.heart {
|
| 73 |
+
color: #EAB308;
|
| 74 |
+
animation: pulse 1s ease infinite;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
@keyframes pulse {
|
| 78 |
+
0%, 100% { transform: scale(1); }
|
| 79 |
+
50% { transform: scale(1.2); }
|
| 80 |
+
}
|
| 81 |
+
</style>
|
| 82 |
+
|
| 83 |
+
<footer>
|
| 84 |
+
<div class="footer-content">
|
| 85 |
+
<div class="footer-logo"><KV/></div>
|
| 86 |
+
|
| 87 |
+
<nav class="footer-links">
|
| 88 |
+
<a href="#hero" class="footer-link">Home</a>
|
| 89 |
+
<a href="#about" class="footer-link">About</a>
|
| 90 |
+
<a href="#skills" class="footer-link">Skills</a>
|
| 91 |
+
<a href="#projects" class="footer-link">Projects</a>
|
| 92 |
+
<a href="#contact" class="footer-link">Contact</a>
|
| 93 |
+
</nav>
|
| 94 |
+
|
| 95 |
+
<div class="social-icons">
|
| 96 |
+
<a href="#" class="social-icon" aria-label="GitHub">
|
| 97 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 98 |
+
</a>
|
| 99 |
+
<a href="#" class="social-icon" aria-label="LinkedIn">
|
| 100 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
| 101 |
+
</a>
|
| 102 |
+
<a href="#" class="social-icon" aria-label="Twitter">
|
| 103 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>
|
| 104 |
+
</a>
|
| 105 |
+
<a href="mailto:hello@kentvuong.com" class="social-icon" aria-label="Email">
|
| 106 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
| 107 |
+
</a>
|
| 108 |
+
</div>
|
| 109 |
+
|
| 110 |
+
<div class="copyright">
|
| 111 |
+
<p>Built with <span class="heart">♥</span> by Kent Vuong © 2024</p>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
</footer>
|
| 115 |
+
`;
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
customElements.define('custom-footer', CustomFooter);
|
components/hero.js
CHANGED
|
@@ -123,7 +123,6 @@ class CustomHero extends HTMLElement {
|
|
| 123 |
z-index: 1;
|
| 124 |
pointer-events: none;
|
| 125 |
}
|
| 126 |
-
|
| 127 |
.grid-bg {
|
| 128 |
position: absolute;
|
| 129 |
top: 0;
|
|
@@ -132,27 +131,103 @@ class CustomHero extends HTMLElement {
|
|
| 132 |
height: 100%;
|
| 133 |
background-image: linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
| 134 |
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
| 135 |
-
background-size:
|
| 136 |
z-index: 0;
|
| 137 |
-
mask-image: radial-gradient(circle at center, black
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
<div class="grid-bg"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
<div class="glow-bg"></div>
|
| 143 |
-
|
| 144 |
-
<div class="container animate-fade-in">
|
| 145 |
<div class="status-badge">
|
| 146 |
<div class="status-dot"></div>
|
| 147 |
<span>Open to Work</span>
|
| 148 |
</div>
|
| 149 |
-
|
| 150 |
<h1>
|
| 151 |
-
Hi, I'm Kent Vuong
|
| 152 |
<span class="highlight">Full-Stack & DevOps Engineer</span>
|
| 153 |
</h1>
|
| 154 |
-
|
| 155 |
-
<p>
|
| 156 |
I build scalable web applications and robust cloud infrastructure.
|
| 157 |
Passionate about clean code, automation, and delivering exceptional user experiences.
|
| 158 |
</p>
|
|
|
|
| 123 |
z-index: 1;
|
| 124 |
pointer-events: none;
|
| 125 |
}
|
|
|
|
| 126 |
.grid-bg {
|
| 127 |
position: absolute;
|
| 128 |
top: 0;
|
|
|
|
| 131 |
height: 100%;
|
| 132 |
background-image: linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
| 133 |
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
| 134 |
+
background-size: 50px 50px;
|
| 135 |
z-index: 0;
|
| 136 |
+
mask-image: radial-gradient(circle at center, black 30%, transparent 100%);
|
| 137 |
+
animation: gridMove 20s linear infinite;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
@keyframes gridMove {
|
| 141 |
+
0% { transform: perspective(500px) rotateX(60deg) translateY(0); }
|
| 142 |
+
100% { transform: perspective(500px) rotateX(60deg) translateY(50px); }
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.floating-shapes {
|
| 146 |
+
position: absolute;
|
| 147 |
+
top: 0;
|
| 148 |
+
left: 0;
|
| 149 |
+
width: 100%;
|
| 150 |
+
height: 100%;
|
| 151 |
+
overflow: hidden;
|
| 152 |
+
z-index: 1;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.shape {
|
| 156 |
+
position: absolute;
|
| 157 |
+
border-radius: 50%;
|
| 158 |
+
opacity: 0.1;
|
| 159 |
+
animation: float 15s ease-in-out infinite;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.shape-1 {
|
| 163 |
+
width: 300px;
|
| 164 |
+
height: 300px;
|
| 165 |
+
background: #EAB308;
|
| 166 |
+
top: 10%;
|
| 167 |
+
right: 10%;
|
| 168 |
+
animation-delay: 0s;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.shape-2 {
|
| 172 |
+
width: 200px;
|
| 173 |
+
height: 200px;
|
| 174 |
+
background: #EAB308;
|
| 175 |
+
bottom: 20%;
|
| 176 |
+
left: 10%;
|
| 177 |
+
animation-delay: 5s;
|
| 178 |
}
|
| 179 |
+
|
| 180 |
+
.shape-3 {
|
| 181 |
+
width: 150px;
|
| 182 |
+
height: 150px;
|
| 183 |
+
background: #EAB308;
|
| 184 |
+
top: 50%;
|
| 185 |
+
left: 5%;
|
| 186 |
+
animation-delay: 10s;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
@keyframes float {
|
| 190 |
+
0%, 100% { transform: translateY(0) rotate(0deg); }
|
| 191 |
+
25% { transform: translateY(-20px) rotate(5deg); }
|
| 192 |
+
50% { transform: translateY(0) rotate(0deg); }
|
| 193 |
+
75% { transform: translateY(20px) rotate(-5deg); }
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.typing-text {
|
| 197 |
+
display: inline-block;
|
| 198 |
+
overflow: hidden;
|
| 199 |
+
border-right: 3px solid #EAB308;
|
| 200 |
+
white-space: nowrap;
|
| 201 |
+
animation: typing 3s steps(40, end), blink-caret 0.75s step-end infinite;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
@keyframes typing {
|
| 205 |
+
from { width: 0 }
|
| 206 |
+
to { width: 100% }
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
@keyframes blink-caret {
|
| 210 |
+
from, to { border-color: transparent }
|
| 211 |
+
50% { border-color: #EAB308 }
|
| 212 |
+
}
|
| 213 |
+
</style>
|
| 214 |
<div class="grid-bg"></div>
|
| 215 |
+
<div class="floating-shapes">
|
| 216 |
+
<div class="shape shape-1"></div>
|
| 217 |
+
<div class="shape shape-2"></div>
|
| 218 |
+
<div class="shape shape-3"></div>
|
| 219 |
+
</div>
|
| 220 |
<div class="glow-bg"></div>
|
| 221 |
+
<div class="container animate-fade-in">
|
|
|
|
| 222 |
<div class="status-badge">
|
| 223 |
<div class="status-dot"></div>
|
| 224 |
<span>Open to Work</span>
|
| 225 |
</div>
|
|
|
|
| 226 |
<h1>
|
| 227 |
+
Hi, I'm <span style="background: linear-gradient(135deg, #EAB308, #CA8A04); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Kent Vuong</span>
|
| 228 |
<span class="highlight">Full-Stack & DevOps Engineer</span>
|
| 229 |
</h1>
|
| 230 |
+
<p>
|
|
|
|
| 231 |
I build scalable web applications and robust cloud infrastructure.
|
| 232 |
Passionate about clean code, automation, and delivering exceptional user experiences.
|
| 233 |
</p>
|
components/preloader.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomPreloader extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
position: fixed;
|
| 8 |
+
top: 0;
|
| 9 |
+
left: 0;
|
| 10 |
+
width: 100%;
|
| 11 |
+
height: 100%;
|
| 12 |
+
background: #0a0a0a;
|
| 13 |
+
display: flex;
|
| 14 |
+
align-items: center;
|
| 15 |
+
justify-content: center;
|
| 16 |
+
z-index: 9999;
|
| 17 |
+
transition: opacity 0.5s ease, visibility 0.5s ease;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
:host.hidden {
|
| 21 |
+
opacity: 0;
|
| 22 |
+
visibility: hidden;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.preloader-content {
|
| 26 |
+
text-align: center;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.loader {
|
| 30 |
+
width: 60px;
|
| 31 |
+
height: 60px;
|
| 32 |
+
border: 3px solid #262626;
|
| 33 |
+
border-top-color: #EAB308;
|
| 34 |
+
border-radius: 50%;
|
| 35 |
+
animation: spin 0.8s linear infinite;
|
| 36 |
+
margin: 0 auto 1.5rem auto;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@keyframes spin {
|
| 40 |
+
to { transform: rotate(360deg); }
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.loading-text {
|
| 44 |
+
font-family: 'Fira Code', monospace;
|
| 45 |
+
color: #EAB308;
|
| 46 |
+
font-size: 0.9rem;
|
| 47 |
+
letter-spacing: 2px;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.progress-bar {
|
| 51 |
+
width: 200px;
|
| 52 |
+
height: 2px;
|
| 53 |
+
background: #262626;
|
| 54 |
+
margin: 1rem auto 0;
|
| 55 |
+
border-radius: 2px;
|
| 56 |
+
overflow: hidden;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.progress-fill {
|
| 60 |
+
height: 100%;
|
| 61 |
+
background: #EAB308;
|
| 62 |
+
width: 0;
|
| 63 |
+
transition: width 0.3s ease;
|
| 64 |
+
}
|
| 65 |
+
</style>
|
| 66 |
+
|
| 67 |
+
<div class="preloader-content">
|
| 68 |
+
<div class="loader"></div>
|
| 69 |
+
<div class="loading-text">LOADING</div>
|
| 70 |
+
<div class="progress-bar">
|
| 71 |
+
<div class="progress-fill" id="progress-fill"></div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
`;
|
| 75 |
+
|
| 76 |
+
// Simulate loading progress
|
| 77 |
+
const progressFill = this.shadowRoot.getElementById('progress-fill');
|
| 78 |
+
let progress = 0;
|
| 79 |
+
const interval = setInterval(() => {
|
| 80 |
+
progress += Math.random() * 30;
|
| 81 |
+
if (progress >= 100) {
|
| 82 |
+
progress = 100;
|
| 83 |
+
clearInterval(interval);
|
| 84 |
+
setTimeout(() => {
|
| 85 |
+
this.classList.add('hidden');
|
| 86 |
+
document.body.style.overflow = 'auto';
|
| 87 |
+
}, 300);
|
| 88 |
+
}
|
| 89 |
+
progressFill.style.width = `${progress}%`;
|
| 90 |
+
}, 200);
|
| 91 |
+
|
| 92 |
+
// Prevent scroll while loading
|
| 93 |
+
document.body.style.overflow = 'hidden';
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
customElements.define('custom-preloader', CustomPreloader);
|
components/projects.js
CHANGED
|
@@ -64,12 +64,16 @@ class CustomProjects extends HTMLElement {
|
|
| 64 |
min-width: calc(50% - 1rem);
|
| 65 |
}
|
| 66 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
.project-container:hover {
|
| 69 |
border-color: #EAB308;
|
|
|
|
| 70 |
}
|
| 71 |
-
|
| 72 |
-
.project-image {
|
| 73 |
width: 100%;
|
| 74 |
height: 300px;
|
| 75 |
object-fit: cover;
|
|
@@ -140,13 +144,31 @@ class CustomProjects extends HTMLElement {
|
|
| 140 |
.link-item:hover {
|
| 141 |
color: #EAB308;
|
| 142 |
}
|
| 143 |
-
|
| 144 |
.link-item svg {
|
| 145 |
width: 18px;
|
| 146 |
height: 18px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
position: absolute;
|
| 151 |
top: 50%;
|
| 152 |
transform: translateY(-50%);
|
|
@@ -246,23 +268,24 @@ class CustomProjects extends HTMLElement {
|
|
| 246 |
<img src="http://static.photos/technology/640x360/42" alt="Mooshieblob.com Project" class="project-image">
|
| 247 |
|
| 248 |
<div class="project-content">
|
|
|
|
|
|
|
|
|
|
| 249 |
<div class="folder-icon">
|
| 250 |
-
|
| 251 |
</div>
|
| 252 |
-
|
| 253 |
<h3>mooshieblob.com</h3>
|
| 254 |
|
| 255 |
<p>
|
| 256 |
-
My fun alias profile showcasing my AI projects and online persona, featuring interactive demos
|
| 257 |
</p>
|
| 258 |
-
|
| 259 |
<div class="tech-stack">
|
| 260 |
<span class="tech-item">Nuxt.js</span>
|
| 261 |
-
<span class="tech-item">Tailwind</span>
|
| 262 |
<span class="tech-item">Cloudflare AI</span>
|
|
|
|
| 263 |
</div>
|
| 264 |
-
|
| 265 |
-
<div class="project-links">
|
| 266 |
<a href="https://github.com/Mooshieblob1/mooshieblob1.github.io" target="_blank" class="link-item">
|
| 267 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 268 |
Code
|
|
@@ -283,21 +306,27 @@ class CustomProjects extends HTMLElement {
|
|
| 283 |
<div class="folder-icon">
|
| 284 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 285 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
<h3>Cloud Dashboard</h3>
|
| 288 |
|
| 289 |
<p>
|
| 290 |
-
A comprehensive cloud infrastructure monitoring dashboard with real-time metrics, alerts,
|
| 291 |
</p>
|
| 292 |
-
|
| 293 |
<div class="tech-stack">
|
| 294 |
<span class="tech-item">React</span>
|
|
|
|
| 295 |
<span class="tech-item">AWS</span>
|
| 296 |
<span class="tech-item">Terraform</span>
|
| 297 |
<span class="tech-item">Grafana</span>
|
|
|
|
| 298 |
</div>
|
| 299 |
-
|
| 300 |
-
<div class="project-links">
|
| 301 |
<a href="#" target="_blank" class="link-item">
|
| 302 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 303 |
Code
|
|
@@ -318,21 +347,27 @@ class CustomProjects extends HTMLElement {
|
|
| 318 |
<div class="folder-icon">
|
| 319 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 320 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
<h3>E-Commerce Platform</h3>
|
| 323 |
|
| 324 |
<p>
|
| 325 |
-
A full-stack e-commerce solution with headless CMS, payment integration, inventory management, and multi-region deployment.
|
| 326 |
</p>
|
| 327 |
-
|
| 328 |
<div class="tech-stack">
|
| 329 |
<span class="tech-item">Next.js</span>
|
| 330 |
<span class="tech-item">Stripe</span>
|
| 331 |
<span class="tech-item">PostgreSQL</span>
|
|
|
|
| 332 |
<span class="tech-item">Docker</span>
|
|
|
|
| 333 |
</div>
|
| 334 |
-
|
| 335 |
-
<div class="project-links">
|
| 336 |
<a href="#" target="_blank" class="link-item">
|
| 337 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 338 |
Code
|
|
@@ -353,21 +388,26 @@ class CustomProjects extends HTMLElement {
|
|
| 353 |
<div class="folder-icon">
|
| 354 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 355 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
<h3>DevOps Pipeline</h3>
|
| 358 |
|
| 359 |
<p>
|
| 360 |
-
Automated CI/CD pipeline solution with self-hosted runners, security scanning, and multi-environment deployments.
|
| 361 |
</p>
|
| 362 |
-
|
| 363 |
<div class="tech-stack">
|
| 364 |
<span class="tech-item">GitHub Actions</span>
|
| 365 |
<span class="tech-item">Kubernetes</span>
|
| 366 |
<span class="tech-item">Helm</span>
|
| 367 |
<span class="tech-item">ArgoCD</span>
|
|
|
|
| 368 |
</div>
|
| 369 |
-
|
| 370 |
-
<div class="project-links">
|
| 371 |
<a href="#" target="_blank" class="link-item">
|
| 372 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 373 |
Code
|
|
|
|
| 64 |
min-width: calc(50% - 1rem);
|
| 65 |
}
|
| 66 |
}
|
| 67 |
+
.project-container {
|
| 68 |
+
transform-style: preserve-3d;
|
| 69 |
+
transition: all 0.5s ease;
|
| 70 |
+
}
|
| 71 |
|
| 72 |
.project-container:hover {
|
| 73 |
border-color: #EAB308;
|
| 74 |
+
transform: translateY(-10px) rotateX(5deg);
|
| 75 |
}
|
| 76 |
+
.project-image {
|
|
|
|
| 77 |
width: 100%;
|
| 78 |
height: 300px;
|
| 79 |
object-fit: cover;
|
|
|
|
| 144 |
.link-item:hover {
|
| 145 |
color: #EAB308;
|
| 146 |
}
|
|
|
|
| 147 |
.link-item svg {
|
| 148 |
width: 18px;
|
| 149 |
height: 18px;
|
| 150 |
+
transition: transform 0.3s ease;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.link-item:hover svg {
|
| 154 |
+
transform: translateX(5px);
|
| 155 |
}
|
| 156 |
|
| 157 |
+
.project-tags {
|
| 158 |
+
display: flex;
|
| 159 |
+
gap: 0.5rem;
|
| 160 |
+
margin-bottom: 1rem;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.tag {
|
| 164 |
+
background: rgba(234, 179, 8, 0.1);
|
| 165 |
+
color: #EAB308;
|
| 166 |
+
padding: 0.25rem 0.75rem;
|
| 167 |
+
border-radius: 9999px;
|
| 168 |
+
font-size: 0.75rem;
|
| 169 |
+
font-family: 'Fira Code', monospace;
|
| 170 |
+
}
|
| 171 |
+
.nav-btn {
|
| 172 |
position: absolute;
|
| 173 |
top: 50%;
|
| 174 |
transform: translateY(-50%);
|
|
|
|
| 268 |
<img src="http://static.photos/technology/640x360/42" alt="Mooshieblob.com Project" class="project-image">
|
| 269 |
|
| 270 |
<div class="project-content">
|
| 271 |
+
<div class="project-tags">
|
| 272 |
+
<span class="tag">Featured</span>
|
| 273 |
+
</div>
|
| 274 |
<div class="folder-icon">
|
| 275 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 276 |
</div>
|
|
|
|
| 277 |
<h3>mooshieblob.com</h3>
|
| 278 |
|
| 279 |
<p>
|
| 280 |
+
My fun alias profile showcasing my AI projects and online persona, featuring interactive demos, creative experiments, and real-time AI integrations.
|
| 281 |
</p>
|
|
|
|
| 282 |
<div class="tech-stack">
|
| 283 |
<span class="tech-item">Nuxt.js</span>
|
| 284 |
+
<span class="tech-item">Tailwind CSS</span>
|
| 285 |
<span class="tech-item">Cloudflare AI</span>
|
| 286 |
+
<span class="tech-item">Vercel</span>
|
| 287 |
</div>
|
| 288 |
+
<div class="project-links">
|
|
|
|
| 289 |
<a href="https://github.com/Mooshieblob1/mooshieblob1.github.io" target="_blank" class="link-item">
|
| 290 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 291 |
Code
|
|
|
|
| 306 |
<div class="folder-icon">
|
| 307 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 308 |
</div>
|
| 309 |
+
<div class="project-tags">
|
| 310 |
+
<span class="tag">Enterprise</span>
|
| 311 |
+
</div>
|
| 312 |
+
<div class="folder-icon">
|
| 313 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 314 |
+
</div>
|
| 315 |
|
| 316 |
<h3>Cloud Dashboard</h3>
|
| 317 |
|
| 318 |
<p>
|
| 319 |
+
A comprehensive cloud infrastructure monitoring dashboard with real-time metrics, intelligent alerts, automated scaling controls, and predictive analytics.
|
| 320 |
</p>
|
|
|
|
| 321 |
<div class="tech-stack">
|
| 322 |
<span class="tech-item">React</span>
|
| 323 |
+
<span class="tech-item">TypeScript</span>
|
| 324 |
<span class="tech-item">AWS</span>
|
| 325 |
<span class="tech-item">Terraform</span>
|
| 326 |
<span class="tech-item">Grafana</span>
|
| 327 |
+
<span class="tech-item">Prometheus</span>
|
| 328 |
</div>
|
| 329 |
+
<div class="project-links">
|
|
|
|
| 330 |
<a href="#" target="_blank" class="link-item">
|
| 331 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 332 |
Code
|
|
|
|
| 347 |
<div class="folder-icon">
|
| 348 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 349 |
</div>
|
| 350 |
+
<div class="project-tags">
|
| 351 |
+
<span class="tag">SaaS</span>
|
| 352 |
+
</div>
|
| 353 |
+
<div class="folder-icon">
|
| 354 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 355 |
+
</div>
|
| 356 |
|
| 357 |
<h3>E-Commerce Platform</h3>
|
| 358 |
|
| 359 |
<p>
|
| 360 |
+
A full-stack e-commerce solution with headless CMS, seamless payment integration, real-time inventory management, and global multi-region deployment.
|
| 361 |
</p>
|
|
|
|
| 362 |
<div class="tech-stack">
|
| 363 |
<span class="tech-item">Next.js</span>
|
| 364 |
<span class="tech-item">Stripe</span>
|
| 365 |
<span class="tech-item">PostgreSQL</span>
|
| 366 |
+
<span class="tech-item">Redis</span>
|
| 367 |
<span class="tech-item">Docker</span>
|
| 368 |
+
<span class="tech-item">GraphQL</span>
|
| 369 |
</div>
|
| 370 |
+
<div class="project-links">
|
|
|
|
| 371 |
<a href="#" target="_blank" class="link-item">
|
| 372 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 373 |
Code
|
|
|
|
| 388 |
<div class="folder-icon">
|
| 389 |
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 390 |
</div>
|
| 391 |
+
<div class="project-tags">
|
| 392 |
+
<span class="tag">Infrastructure</span>
|
| 393 |
+
</div>
|
| 394 |
+
<div class="folder-icon">
|
| 395 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
| 396 |
+
</div>
|
| 397 |
|
| 398 |
<h3>DevOps Pipeline</h3>
|
| 399 |
|
| 400 |
<p>
|
| 401 |
+
Automated CI/CD pipeline solution with self-hosted runners, comprehensive security scanning, automated testing, and multi-environment deployments.
|
| 402 |
</p>
|
|
|
|
| 403 |
<div class="tech-stack">
|
| 404 |
<span class="tech-item">GitHub Actions</span>
|
| 405 |
<span class="tech-item">Kubernetes</span>
|
| 406 |
<span class="tech-item">Helm</span>
|
| 407 |
<span class="tech-item">ArgoCD</span>
|
| 408 |
+
<span class="tech-item">SonarQube</span>
|
| 409 |
</div>
|
| 410 |
+
<div class="project-links">
|
|
|
|
| 411 |
<a href="#" target="_blank" class="link-item">
|
| 412 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
|
| 413 |
Code
|
components/scroll-progress.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomScrollProgress extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
position: fixed;
|
| 8 |
+
top: 0;
|
| 9 |
+
left: 0;
|
| 10 |
+
width: 100%;
|
| 11 |
+
height: 3px;
|
| 12 |
+
z-index: 9999;
|
| 13 |
+
pointer-events: none;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.progress-bar {
|
| 17 |
+
height: 100%;
|
| 18 |
+
background: linear-gradient(90deg, #EAB308, #CA8A04);
|
| 19 |
+
width: 0%;
|
| 20 |
+
transition: width 0.1s ease-out;
|
| 21 |
+
box-shadow: 0 0 10px rgba(234, 179, 8, 0.5);
|
| 22 |
+
}
|
| 23 |
+
</style>
|
| 24 |
+
<div class="progress-bar" id="progress-bar"></div>
|
| 25 |
+
`;
|
| 26 |
+
|
| 27 |
+
const progressBar = this.shadowRoot.getElementById('progress-bar');
|
| 28 |
+
|
| 29 |
+
window.addEventListener('scroll', () => {
|
| 30 |
+
const scrollTop = window.scrollY;
|
| 31 |
+
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
| 32 |
+
const progress = (scrollTop / docHeight) * 100;
|
| 33 |
+
progressBar.style.width = `${progress}%`;
|
| 34 |
+
});
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
customElements.define('custom-scroll-progress', CustomScrollProgress);
|
components/skills.js
CHANGED
|
@@ -99,11 +99,41 @@ class CustomSkills extends HTMLElement {
|
|
| 99 |
color: #d4d4d8;
|
| 100 |
transition: all 0.2s;
|
| 101 |
}
|
| 102 |
-
|
| 103 |
.tech-item:hover {
|
| 104 |
background: #EAB308;
|
| 105 |
color: black;
|
| 106 |
font-weight: 500;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
}
|
| 108 |
</style>
|
| 109 |
|
|
@@ -113,10 +143,17 @@ class CustomSkills extends HTMLElement {
|
|
| 113 |
<p class="subtitle">Technologies I work with</p>
|
| 114 |
</div>
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
<div class="skills-grid">
|
| 117 |
<!-- Frontend -->
|
| 118 |
-
<div class="skill-card">
|
| 119 |
-
|
| 120 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
| 121 |
</div>
|
| 122 |
<h3>Frontend</h3>
|
|
@@ -131,10 +168,9 @@ class CustomSkills extends HTMLElement {
|
|
| 131 |
<li class="tech-item">SCSS</li>
|
| 132 |
</ul>
|
| 133 |
</div>
|
| 134 |
-
|
| 135 |
<!-- Backend -->
|
| 136 |
-
<div class="skill-card">
|
| 137 |
-
|
| 138 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
| 139 |
</div>
|
| 140 |
<h3>Backend</h3>
|
|
@@ -149,10 +185,9 @@ class CustomSkills extends HTMLElement {
|
|
| 149 |
<li class="tech-item">GraphQL</li>
|
| 150 |
</ul>
|
| 151 |
</div>
|
| 152 |
-
|
| 153 |
<!-- DevOps -->
|
| 154 |
-
<div class="skill-card">
|
| 155 |
-
|
| 156 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path></svg>
|
| 157 |
</div>
|
| 158 |
<h3>DevOps & Cloud</h3>
|
|
@@ -171,5 +206,30 @@ class CustomSkills extends HTMLElement {
|
|
| 171 |
</section>
|
| 172 |
`;
|
| 173 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
}
|
| 175 |
-
customElements.define('custom-skills', CustomSkills);
|
|
|
|
| 99 |
color: #d4d4d8;
|
| 100 |
transition: all 0.2s;
|
| 101 |
}
|
|
|
|
| 102 |
.tech-item:hover {
|
| 103 |
background: #EAB308;
|
| 104 |
color: black;
|
| 105 |
font-weight: 500;
|
| 106 |
+
transform: translateY(-2px);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.filter-buttons {
|
| 110 |
+
display: flex;
|
| 111 |
+
justify-content: center;
|
| 112 |
+
gap: 1rem;
|
| 113 |
+
margin-bottom: 3rem;
|
| 114 |
+
flex-wrap: wrap;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.filter-btn {
|
| 118 |
+
padding: 0.5rem 1.5rem;
|
| 119 |
+
background: transparent;
|
| 120 |
+
border: 1px solid #262626;
|
| 121 |
+
color: #9ca3af;
|
| 122 |
+
border-radius: 9999px;
|
| 123 |
+
cursor: pointer;
|
| 124 |
+
font-size: 0.9rem;
|
| 125 |
+
font-family: 'Fira Code', monospace;
|
| 126 |
+
transition: all 0.3s ease;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
.filter-btn:hover, .filter-btn.active {
|
| 130 |
+
border-color: #EAB308;
|
| 131 |
+
color: #EAB308;
|
| 132 |
+
background: rgba(234, 179, 8, 0.1);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.skill-card.hidden {
|
| 136 |
+
display: none;
|
| 137 |
}
|
| 138 |
</style>
|
| 139 |
|
|
|
|
| 143 |
<p class="subtitle">Technologies I work with</p>
|
| 144 |
</div>
|
| 145 |
|
| 146 |
+
<div class="filter-buttons">
|
| 147 |
+
<button class="filter-btn active" data-filter="all">All</button>
|
| 148 |
+
<button class="filter-btn" data-filter="frontend">Frontend</button>
|
| 149 |
+
<button class="filter-btn" data-filter="backend">Backend</button>
|
| 150 |
+
<button class="filter-btn" data-filter="devops">DevOps</button>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
<div class="skills-grid">
|
| 154 |
<!-- Frontend -->
|
| 155 |
+
<div class="skill-card" data-category="frontend">
|
| 156 |
+
<div class="card-icon">
|
| 157 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
| 158 |
</div>
|
| 159 |
<h3>Frontend</h3>
|
|
|
|
| 168 |
<li class="tech-item">SCSS</li>
|
| 169 |
</ul>
|
| 170 |
</div>
|
|
|
|
| 171 |
<!-- Backend -->
|
| 172 |
+
<div class="skill-card" data-category="backend">
|
| 173 |
+
<div class="card-icon">
|
| 174 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
| 175 |
</div>
|
| 176 |
<h3>Backend</h3>
|
|
|
|
| 185 |
<li class="tech-item">GraphQL</li>
|
| 186 |
</ul>
|
| 187 |
</div>
|
|
|
|
| 188 |
<!-- DevOps -->
|
| 189 |
+
<div class="skill-card" data-category="devops">
|
| 190 |
+
<div class="card-icon">
|
| 191 |
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path></svg>
|
| 192 |
</div>
|
| 193 |
<h3>DevOps & Cloud</h3>
|
|
|
|
| 206 |
</section>
|
| 207 |
`;
|
| 208 |
}
|
| 209 |
+
|
| 210 |
+
initFilter() {
|
| 211 |
+
const filterBtns = this.shadowRoot.querySelectorAll('.filter-btn');
|
| 212 |
+
const skillCards = this.shadowRoot.querySelectorAll('.skill-card');
|
| 213 |
+
|
| 214 |
+
filterBtns.forEach(btn => {
|
| 215 |
+
btn.addEventListener('click', () => {
|
| 216 |
+
const filter = btn.dataset.filter;
|
| 217 |
+
|
| 218 |
+
// Update active button
|
| 219 |
+
filterBtns.forEach(b => b.classList.remove('active'));
|
| 220 |
+
btn.classList.add('active');
|
| 221 |
+
|
| 222 |
+
// Filter cards
|
| 223 |
+
skillCards.forEach(card => {
|
| 224 |
+
if (filter === 'all' || card.dataset.category === filter) {
|
| 225 |
+
card.classList.remove('hidden');
|
| 226 |
+
card.style.animation = 'fadeIn 0.5s ease forwards';
|
| 227 |
+
} else {
|
| 228 |
+
card.classList.add('hidden');
|
| 229 |
+
}
|
| 230 |
+
});
|
| 231 |
+
});
|
| 232 |
+
});
|
| 233 |
+
}
|
| 234 |
}
|
| 235 |
+
customElements.define('custom-skills', CustomSkills);
|
components/terminal.js
CHANGED
|
@@ -109,11 +109,39 @@ class CustomTerminal extends HTMLElement {
|
|
| 109 |
width: 0;
|
| 110 |
animation: typing 2s steps(40, end) forwards;
|
| 111 |
}
|
| 112 |
-
|
| 113 |
@keyframes typing {
|
| 114 |
from { width: 0 }
|
| 115 |
to { width: 100% }
|
| 116 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
</style>
|
| 118 |
|
| 119 |
<section id="about">
|
|
@@ -135,40 +163,135 @@ class CustomTerminal extends HTMLElement {
|
|
| 135 |
|
| 136 |
<div class="output" id="output-1" style="opacity: 0; animation: fadeIn 0.5s forwards 2.5s;">
|
| 137 |
<strong>> Kent Vuong</strong><br>
|
| 138 |
-
> Full-Stack
|
|
|
|
| 139 |
</div>
|
| 140 |
|
| 141 |
<div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 3s;">
|
| 142 |
<span class="prompt">$</span>
|
| 143 |
-
<span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 3.5s;">
|
| 144 |
</div>
|
| 145 |
|
| 146 |
<div class="output" id="output-2" style="opacity: 0; animation: fadeIn 0.5s forwards 5.5s;">
|
| 147 |
-
<
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
</div>
|
| 150 |
|
| 151 |
<div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 6s;">
|
| 152 |
<span class="prompt">$</span>
|
| 153 |
-
<span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 6.5s;">
|
| 154 |
</div>
|
| 155 |
|
| 156 |
<div class="output" style="opacity: 0; animation: fadeIn 0.5s forwards 8.5s;">
|
| 157 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
</div>
|
| 159 |
|
| 160 |
<div class="command-line">
|
| 161 |
<span class="prompt">$</span>
|
|
|
|
| 162 |
<span class="cursor"></span>
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
</div>
|
| 166 |
</section>
|
| 167 |
-
|
| 168 |
<style>
|
| 169 |
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 170 |
</style>
|
| 171 |
`;
|
| 172 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
}
|
| 174 |
-
customElements.define('custom-terminal', CustomTerminal);
|
|
|
|
| 109 |
width: 0;
|
| 110 |
animation: typing 2s steps(40, end) forwards;
|
| 111 |
}
|
|
|
|
| 112 |
@keyframes typing {
|
| 113 |
from { width: 0 }
|
| 114 |
to { width: 100% }
|
| 115 |
}
|
| 116 |
+
|
| 117 |
+
.interactive-command {
|
| 118 |
+
color: #22c55e;
|
| 119 |
+
cursor: pointer;
|
| 120 |
+
transition: all 0.2s;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.interactive-command:hover {
|
| 124 |
+
color: #4ade80;
|
| 125 |
+
text-decoration: underline;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.file-tree {
|
| 129 |
+
font-family: 'Fira Code', monospace;
|
| 130 |
+
color: #a1a1aa;
|
| 131 |
+
line-height: 1.6;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.tree-item {
|
| 135 |
+
padding: 0.25rem 0;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.tree-folder {
|
| 139 |
+
color: #EAB308;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.tree-file {
|
| 143 |
+
color: #9ca3af;
|
| 144 |
+
}
|
| 145 |
</style>
|
| 146 |
|
| 147 |
<section id="about">
|
|
|
|
| 163 |
|
| 164 |
<div class="output" id="output-1" style="opacity: 0; animation: fadeIn 0.5s forwards 2.5s;">
|
| 165 |
<strong>> Kent Vuong</strong><br>
|
| 166 |
+
> Full-Stack Developer & DevOps Engineer<br>
|
| 167 |
+
> Building scalable solutions since 2019
|
| 168 |
</div>
|
| 169 |
|
| 170 |
<div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 3s;">
|
| 171 |
<span class="prompt">$</span>
|
| 172 |
+
<span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 3.5s;">ls -la experience/</span>
|
| 173 |
</div>
|
| 174 |
|
| 175 |
<div class="output" id="output-2" style="opacity: 0; animation: fadeIn 0.5s forwards 5.5s;">
|
| 176 |
+
<div class="file-tree">
|
| 177 |
+
<div class="tree-item tree-folder">📁 .senior-engineer/</div>
|
| 178 |
+
<div class="tree-item tree-file"> └── microservices-architecture.yaml</div>
|
| 179 |
+
<div class="tree-item tree-folder">📁 .cloud-infrastructure/</div>
|
| 180 |
+
<div class="tree-item tree-file"> ├── aws-deployments/</div>
|
| 181 |
+
<div class="tree-item tree-file"> ├── kubernetes-clusters/</div>
|
| 182 |
+
<div class="tree-item tree-file"> └── ci-cd-pipelines/</div>
|
| 183 |
+
<div class="tree-item tree-folder">📁 .web-development/</div>
|
| 184 |
+
<div class="tree-item tree-file"> ├── react-applications/</div>
|
| 185 |
+
<div class="tree-item tree-file"> ├── nextjs-ssr/</div>
|
| 186 |
+
<div class="tree-item tree-file"> └── nodejs-apis/</div>
|
| 187 |
+
</div>
|
| 188 |
</div>
|
| 189 |
|
| 190 |
<div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 6s;">
|
| 191 |
<span class="prompt">$</span>
|
| 192 |
+
<span class="command typing-effect" style="width: 0; animation: typing 1.5s steps(30, end) forwards 6.5s;">cat motivation.txt</span>
|
| 193 |
</div>
|
| 194 |
|
| 195 |
<div class="output" style="opacity: 0; animation: fadeIn 0.5s forwards 8.5s;">
|
| 196 |
+
<strong>> "Code is poetry, infrastructure is the canvas."</strong><br>
|
| 197 |
+
> Passionate about creating elegant solutions<br>
|
| 198 |
+
> to complex problems. Always learning, always evolving.
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<div class="command-line" style="opacity: 0; animation: fadeIn 0.5s forwards 9s;">
|
| 202 |
+
<span class="prompt">$</span>
|
| 203 |
+
<span class="interactive-command" id="try-cmd">Try typing a command...</span>
|
| 204 |
</div>
|
| 205 |
|
| 206 |
<div class="command-line">
|
| 207 |
<span class="prompt">$</span>
|
| 208 |
+
<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">
|
| 209 |
<span class="cursor"></span>
|
| 210 |
</div>
|
| 211 |
</div>
|
| 212 |
</div>
|
| 213 |
</section>
|
|
|
|
| 214 |
<style>
|
| 215 |
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 216 |
</style>
|
| 217 |
`;
|
| 218 |
}
|
| 219 |
+
|
| 220 |
+
initTerminal() {
|
| 221 |
+
const input = this.shadowRoot.getElementById('terminal-input');
|
| 222 |
+
|
| 223 |
+
input.addEventListener('keypress', (e) => {
|
| 224 |
+
if (e.key === 'Enter') {
|
| 225 |
+
const command = input.value.trim().toLowerCase();
|
| 226 |
+
this.handleCommand(command);
|
| 227 |
+
input.value = '';
|
| 228 |
+
}
|
| 229 |
+
});
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
handleCommand(command) {
|
| 233 |
+
const terminalBody = this.shadowRoot.getElementById('terminal-content');
|
| 234 |
+
|
| 235 |
+
// Create new command line
|
| 236 |
+
const newCommandLine = document.createElement('div');
|
| 237 |
+
newCommandLine.className = 'command-line';
|
| 238 |
+
newCommandLine.innerHTML = `<span class="prompt">$</span><span class="command">${command}</span>`;
|
| 239 |
+
|
| 240 |
+
// Insert before the input line
|
| 241 |
+
const inputLine = terminalBody.lastElementChild;
|
| 242 |
+
terminalBody.insertBefore(newCommandLine, inputLine);
|
| 243 |
+
|
| 244 |
+
// Create output
|
| 245 |
+
const output = document.createElement('div');
|
| 246 |
+
output.className = 'output';
|
| 247 |
+
|
| 248 |
+
switch(command) {
|
| 249 |
+
case 'help':
|
| 250 |
+
output.innerHTML = `<strong>Available commands:</strong><br>
|
| 251 |
+
> about - Learn more about me<br>
|
| 252 |
+
> skills - View my technical skills<br>
|
| 253 |
+
> projects - See my work<br>
|
| 254 |
+
> contact - Get in touch<br>
|
| 255 |
+
> clear - Clear terminal`;
|
| 256 |
+
break;
|
| 257 |
+
case 'about':
|
| 258 |
+
output.innerHTML = `<strong>Kent Vuong</strong><br>
|
| 259 |
+
> Full-Stack & DevOps Engineer<br>
|
| 260 |
+
> 5+ years of experience<br>
|
| 261 |
+
> Based in San Francisco, CA`;
|
| 262 |
+
break;
|
| 263 |
+
case 'skills':
|
| 264 |
+
output.innerHTML = `<strong>Core Skills:</strong><br>
|
| 265 |
+
> Frontend: React, Vue, Next.js<br>
|
| 266 |
+
> Backend: Node.js, Python, Go<br>
|
| 267 |
+
> DevOps: AWS, Docker, K8s, Terraform<br>
|
| 268 |
+
> Databases: PostgreSQL, MongoDB, Redis`;
|
| 269 |
+
break;
|
| 270 |
+
case 'projects':
|
| 271 |
+
output.innerHTML = `<strong>Featured Projects:</strong><br>
|
| 272 |
+
> mooshieblob.com - AI Showcase<br>
|
| 273 |
+
> Cloud Dashboard - Infrastructure Monitor<br>
|
| 274 |
+
> E-Commerce Platform - Full-Stack Solution<br>
|
| 275 |
+
> DevOps Pipeline - CI/CD Automation`;
|
| 276 |
+
break;
|
| 277 |
+
case 'contact':
|
| 278 |
+
output.innerHTML = `<strong>Let's Connect!</strong><br>
|
| 279 |
+
> Email: hello@kentvuong.com<br>
|
| 280 |
+
> GitHub: @Mooshieblob1<br>
|
| 281 |
+
> LinkedIn: /in/kentvuong<br>
|
| 282 |
+
> <a href="#contact" style="color: #EAB308;">Jump to Contact Section →</a>`;
|
| 283 |
+
break;
|
| 284 |
+
case 'clear':
|
| 285 |
+
// Remove all command lines except the last one (input)
|
| 286 |
+
while (terminalBody.children.length > 1) {
|
| 287 |
+
terminalBody.removeChild(terminalBody.firstChild);
|
| 288 |
+
}
|
| 289 |
+
return;
|
| 290 |
+
default:
|
| 291 |
+
output.innerHTML = `<span style="color: #ef4444;">Command not found: ${command}</span><br>Type 'help' for available commands.`;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
terminalBody.insertBefore(output, inputLine);
|
| 295 |
+
}
|
| 296 |
}
|
| 297 |
+
customElements.define('custom-terminal', CustomTerminal);
|
index.html
CHANGED
|
@@ -39,11 +39,22 @@
|
|
| 39 |
</head>
|
| 40 |
<body class="bg-brand-black text-gray-200 font-sans antialiased overflow-x-hidden selection:bg-brand-yellow selection:text-brand-black">
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
<!-- Components -->
|
| 43 |
<script src="components/navbar.js"></script>
|
| 44 |
<custom-navbar></custom-navbar>
|
| 45 |
-
|
| 46 |
-
<main class="relative z-10">
|
| 47 |
|
| 48 |
<!-- Hero Section -->
|
| 49 |
<script src="components/hero.js"></script>
|
|
@@ -64,11 +75,14 @@
|
|
| 64 |
<!-- Contact Section -->
|
| 65 |
<script src="components/contact.js"></script>
|
| 66 |
<custom-contact></custom-contact>
|
| 67 |
-
|
| 68 |
</main>
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
<script src="script.js"></script>
|
| 71 |
-
|
| 72 |
feather.replace();
|
| 73 |
</script>
|
| 74 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
|
|
|
| 39 |
</head>
|
| 40 |
<body class="bg-brand-black text-gray-200 font-sans antialiased overflow-x-hidden selection:bg-brand-yellow selection:text-brand-black">
|
| 41 |
|
| 42 |
+
<!-- Preloader -->
|
| 43 |
+
<custom-preloader></custom-preloader>
|
| 44 |
+
<script src="components/preloader.js"></script>
|
| 45 |
+
|
| 46 |
+
<!-- Scroll Progress -->
|
| 47 |
+
<custom-scroll-progress></custom-scroll-progress>
|
| 48 |
+
<script src="components/scroll-progress.js"></script>
|
| 49 |
+
|
| 50 |
+
<!-- Back to Top -->
|
| 51 |
+
<custom-back-to-top></custom-back-to-top>
|
| 52 |
+
<script src="components/back-to-top.js"></script>
|
| 53 |
+
|
| 54 |
<!-- Components -->
|
| 55 |
<script src="components/navbar.js"></script>
|
| 56 |
<custom-navbar></custom-navbar>
|
| 57 |
+
<main class="relative z-10">
|
|
|
|
| 58 |
|
| 59 |
<!-- Hero Section -->
|
| 60 |
<script src="components/hero.js"></script>
|
|
|
|
| 75 |
<!-- Contact Section -->
|
| 76 |
<script src="components/contact.js"></script>
|
| 77 |
<custom-contact></custom-contact>
|
|
|
|
| 78 |
</main>
|
| 79 |
|
| 80 |
+
<!-- Footer -->
|
| 81 |
+
<script src="components/footer.js"></script>
|
| 82 |
+
<custom-footer></custom-footer>
|
| 83 |
+
|
| 84 |
<script src="script.js"></script>
|
| 85 |
+
<script>
|
| 86 |
feather.replace();
|
| 87 |
</script>
|
| 88 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
script.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
// Logic to handle navigation active state based on scroll position
|
| 2 |
document.addEventListener('DOMContentLoaded', () => {
|
| 3 |
const sections = document.querySelectorAll('section[id]');
|
|
@@ -14,11 +15,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 14 |
if (entry.isIntersecting) {
|
| 15 |
const id = entry.target.getAttribute('id');
|
| 16 |
navLinks.forEach(link => {
|
| 17 |
-
link.classList.remove('
|
| 18 |
-
link.classList.add('text-gray-400', 'border-transparent');
|
| 19 |
if (link.getAttribute('href') === `#${id}`) {
|
| 20 |
-
link.classList.
|
| 21 |
-
link.classList.add('text-brand-yellow', 'border-brand-yellow');
|
| 22 |
}
|
| 23 |
});
|
| 24 |
}
|
|
@@ -27,6 +26,114 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 27 |
|
| 28 |
sections.forEach(section => observer.observe(section));
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
// Dispatch event to notify components that the DOM is ready
|
| 31 |
window.dispatchEvent(new Event('app-ready'));
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
// Logic to handle navigation active state based on scroll position
|
| 3 |
document.addEventListener('DOMContentLoaded', () => {
|
| 4 |
const sections = document.querySelectorAll('section[id]');
|
|
|
|
| 15 |
if (entry.isIntersecting) {
|
| 16 |
const id = entry.target.getAttribute('id');
|
| 17 |
navLinks.forEach(link => {
|
| 18 |
+
link.classList.remove('active');
|
|
|
|
| 19 |
if (link.getAttribute('href') === `#${id}`) {
|
| 20 |
+
link.classList.add('active');
|
|
|
|
| 21 |
}
|
| 22 |
});
|
| 23 |
}
|
|
|
|
| 26 |
|
| 27 |
sections.forEach(section => observer.observe(section));
|
| 28 |
|
| 29 |
+
// Scroll reveal animation
|
| 30 |
+
const revealElements = document.querySelectorAll('.reveal, .reveal-left, .reveal-right');
|
| 31 |
+
|
| 32 |
+
const revealObserver = new IntersectionObserver((entries) => {
|
| 33 |
+
entries.forEach(entry => {
|
| 34 |
+
if (entry.isIntersecting) {
|
| 35 |
+
entry.target.classList.add('active');
|
| 36 |
+
}
|
| 37 |
+
});
|
| 38 |
+
}, { threshold: 0.1 });
|
| 39 |
+
|
| 40 |
+
revealElements.forEach(el => revealObserver.observe(el));
|
| 41 |
+
|
| 42 |
+
// Initialize components when DOM is ready
|
| 43 |
+
const initComponents = () => {
|
| 44 |
+
// Initialize Terminal
|
| 45 |
+
const terminal = document.querySelector('custom-terminal');
|
| 46 |
+
if (terminal && terminal.initTerminal) {
|
| 47 |
+
setTimeout(() => terminal.initTerminal(), 3000);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Initialize Projects scroll
|
| 51 |
+
const projects = document.querySelector('custom-projects');
|
| 52 |
+
if (projects && projects.initScrollNavigation) {
|
| 53 |
+
projects.initScrollNavigation();
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Initialize Skills filter
|
| 57 |
+
const skills = document.querySelector('custom-skills');
|
| 58 |
+
if (skills && skills.initFilter) {
|
| 59 |
+
skills.initFilter();
|
| 60 |
+
}
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
// Dispatch event to notify components that the DOM is ready
|
| 64 |
window.dispatchEvent(new Event('app-ready'));
|
| 65 |
+
|
| 66 |
+
// Initialize components
|
| 67 |
+
initComponents();
|
| 68 |
+
|
| 69 |
+
// Parallax effect for hero section
|
| 70 |
+
window.addEventListener('scroll', () => {
|
| 71 |
+
const scrolled = window.pageYOffset;
|
| 72 |
+
const hero = document.querySelector('custom-hero');
|
| 73 |
+
if (hero) {
|
| 74 |
+
const shapes = hero.shadowRoot?.querySelector('.floating-shapes');
|
| 75 |
+
if (shapes) {
|
| 76 |
+
shapes.style.transform = `translateY(${scrolled * 0.3}px)`;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
// Magnetic button effect
|
| 82 |
+
const magneticBtns = document.querySelectorAll('.magnetic-btn');
|
| 83 |
+
magneticBtns.forEach(btn => {
|
| 84 |
+
btn.addEventListener('mousemove', (e) => {
|
| 85 |
+
const rect = btn.getBoundingClientRect();
|
| 86 |
+
const x = e.clientX - rect.left - rect.width / 2;
|
| 87 |
+
const y = e.clientY - rect.top - rect.height / 2;
|
| 88 |
+
btn.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
btn.addEventListener('mouseleave', () => {
|
| 92 |
+
btn.style.transform = 'translate(0, 0)';
|
| 93 |
+
});
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
// Smooth scroll for anchor links
|
| 97 |
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 98 |
+
anchor.addEventListener('click', function(e) {
|
| 99 |
+
e.preventDefault();
|
| 100 |
+
const target = document.querySelector(this.getAttribute('href'));
|
| 101 |
+
if (target) {
|
| 102 |
+
target.scrollIntoView({
|
| 103 |
+
behavior: 'smooth',
|
| 104 |
+
block: 'start'
|
| 105 |
+
});
|
| 106 |
+
}
|
| 107 |
+
});
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
// Add reveal classes to skill cards
|
| 111 |
+
const skillCards = document.querySelectorAll('.skill-card');
|
| 112 |
+
skillCards.forEach((card, index) => {
|
| 113 |
+
card.classList.add('reveal');
|
| 114 |
+
card.style.transitionDelay = `${index * 0.1}s`;
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
// Add reveal classes to project containers
|
| 118 |
+
const projectContainers = document.querySelectorAll('.project-container');
|
| 119 |
+
projectContainers.forEach((card, index) => {
|
| 120 |
+
card.classList.add('reveal');
|
| 121 |
+
card.style.transitionDelay = `${index * 0.15}s`;
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
// Typing effect for hero
|
| 126 |
+
function typeWriter(element, text, speed = 50) {
|
| 127 |
+
let i = 0;
|
| 128 |
+
element.innerHTML = '';
|
| 129 |
+
|
| 130 |
+
function type() {
|
| 131 |
+
if (i < text.length) {
|
| 132 |
+
element.innerHTML += text.charAt(i);
|
| 133 |
+
i++;
|
| 134 |
+
setTimeout(type, speed);
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
type();
|
| 139 |
+
}
|
style.css
CHANGED
|
@@ -31,17 +31,77 @@ body {
|
|
| 31 |
-webkit-background-clip: text;
|
| 32 |
-webkit-text-fill-color: transparent;
|
| 33 |
}
|
| 34 |
-
|
| 35 |
/* Animations */
|
| 36 |
@keyframes fadeIn {
|
| 37 |
from { opacity: 0; transform: translateY(20px); }
|
| 38 |
to { opacity: 1; transform: translateY(0); }
|
| 39 |
}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
.animate-fade-in {
|
| 42 |
animation: fadeIn 0.8s ease-out forwards;
|
| 43 |
}
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
/* Blinking Cursor Animation */
|
| 46 |
@keyframes blink {
|
| 47 |
0%, 100% { opacity: 1; }
|
|
@@ -58,7 +118,6 @@ body {
|
|
| 58 |
linear-gradient(90deg, rgba(234, 179, 8, 0.05) 1px, transparent 1px);
|
| 59 |
background-size: 40px 40px;
|
| 60 |
}
|
| 61 |
-
|
| 62 |
/* Glitch Effect on Hover */
|
| 63 |
.glitch-hover:hover {
|
| 64 |
animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;
|
|
@@ -72,4 +131,63 @@ body {
|
|
| 72 |
60% { transform: translate(2px, 2px) }
|
| 73 |
80% { transform: translate(2px, -2px) }
|
| 74 |
100% { transform: translate(0) }
|
| 75 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
-webkit-background-clip: text;
|
| 32 |
-webkit-text-fill-color: transparent;
|
| 33 |
}
|
|
|
|
| 34 |
/* Animations */
|
| 35 |
@keyframes fadeIn {
|
| 36 |
from { opacity: 0; transform: translateY(20px); }
|
| 37 |
to { opacity: 1; transform: translateY(0); }
|
| 38 |
}
|
| 39 |
|
| 40 |
+
@keyframes slideUp {
|
| 41 |
+
from { opacity: 0; transform: translateY(40px); }
|
| 42 |
+
to { opacity: 1; transform: translateY(0); }
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
@keyframes slideInLeft {
|
| 46 |
+
from { opacity: 0; transform: translateX(-30px); }
|
| 47 |
+
to { opacity: 1; transform: translateX(0); }
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
@keyframes slideInRight {
|
| 51 |
+
from { opacity: 0; transform: translateX(30px); }
|
| 52 |
+
to { opacity: 1; transform: translateX(0); }
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
@keyframes scaleIn {
|
| 56 |
+
from { opacity: 0; transform: scale(0.9); }
|
| 57 |
+
to { opacity: 1; transform: scale(1); }
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
.animate-fade-in {
|
| 61 |
animation: fadeIn 0.8s ease-out forwards;
|
| 62 |
}
|
| 63 |
|
| 64 |
+
.animate-slide-up {
|
| 65 |
+
animation: slideUp 0.8s ease-out forwards;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.animate-scale-in {
|
| 69 |
+
animation: scaleIn 0.6s ease-out forwards;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/* Scroll Reveal Classes */
|
| 73 |
+
.reveal {
|
| 74 |
+
opacity: 0;
|
| 75 |
+
transform: translateY(30px);
|
| 76 |
+
transition: all 0.8s ease;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.reveal.active {
|
| 80 |
+
opacity: 1;
|
| 81 |
+
transform: translateY(0);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.reveal-left {
|
| 85 |
+
opacity: 0;
|
| 86 |
+
transform: translateX(-50px);
|
| 87 |
+
transition: all 0.8s ease;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.reveal-left.active {
|
| 91 |
+
opacity: 1;
|
| 92 |
+
transform: translateX(0);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.reveal-right {
|
| 96 |
+
opacity: 0;
|
| 97 |
+
transform: translateX(50px);
|
| 98 |
+
transition: all 0.8s ease;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.reveal-right.active {
|
| 102 |
+
opacity: 1;
|
| 103 |
+
transform: translateX(0);
|
| 104 |
+
}
|
| 105 |
/* Blinking Cursor Animation */
|
| 106 |
@keyframes blink {
|
| 107 |
0%, 100% { opacity: 1; }
|
|
|
|
| 118 |
linear-gradient(90deg, rgba(234, 179, 8, 0.05) 1px, transparent 1px);
|
| 119 |
background-size: 40px 40px;
|
| 120 |
}
|
|
|
|
| 121 |
/* Glitch Effect on Hover */
|
| 122 |
.glitch-hover:hover {
|
| 123 |
animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;
|
|
|
|
| 131 |
60% { transform: translate(2px, 2px) }
|
| 132 |
80% { transform: translate(2px, -2px) }
|
| 133 |
100% { transform: translate(0) }
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/* Smooth Scroll Behavior */
|
| 137 |
+
html {
|
| 138 |
+
scroll-behavior: smooth;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/* Selection Styling */
|
| 142 |
+
::selection {
|
| 143 |
+
background: #EAB308;
|
| 144 |
+
color: #0a0a0a;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/* Focus Styles */
|
| 148 |
+
a:focus, button:focus, input:focus {
|
| 149 |
+
outline: 2px solid #EAB308;
|
| 150 |
+
outline-offset: 2px;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/* Improved Button Hover Effects */
|
| 154 |
+
.btn-primary {
|
| 155 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.btn-primary:active {
|
| 159 |
+
transform: scale(0.95);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
/* Card Hover 3D Effect */
|
| 163 |
+
.card-3d {
|
| 164 |
+
perspective: 1000px;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.card-3d-inner {
|
| 168 |
+
transition: transform 0.6s;
|
| 169 |
+
transform-style: preserve-3d;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.card-3d:hover .card-3d-inner {
|
| 173 |
+
transform: rotateY(5deg) rotateX(5deg);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/* Gradient Text Animation */
|
| 177 |
+
.gradient-text-animate {
|
| 178 |
+
background: linear-gradient(90deg, #EAB308, #CA8A04, #EAB308);
|
| 179 |
+
background-size: 200% auto;
|
| 180 |
+
-webkit-background-clip: text;
|
| 181 |
+
-webkit-text-fill-color: transparent;
|
| 182 |
+
animation: gradientMove 3s linear infinite;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
@keyframes gradientMove {
|
| 186 |
+
0% { background-position: 0% center; }
|
| 187 |
+
100% { background-position: 200% center; }
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/* Magnetic Button Effect */
|
| 191 |
+
.magnetic-btn {
|
| 192 |
+
transition: transform 0.3s ease;
|
| 193 |
+
}
|