Enhance CTA section, fix errors in contact api, and update role animation in hero section.
|
@ -0,0 +1,49 @@
|
||||||
|
@keyframes pulse-ring {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.33);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
80%, 100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Particles */
|
||||||
|
.particles-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(59, 130, 246, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-particle {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translate(50px, -50px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(100px, 0);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
function initFormHandling() {
|
||||||
|
const form = document.getElementById('contact-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
const originalBtnText = submitBtn.innerHTML;
|
||||||
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const data = {
|
||||||
|
name: formData.get('name'),
|
||||||
|
email: formData.get('email'),
|
||||||
|
subject: formData.get('subject'),
|
||||||
|
message: formData.get('message')
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/send-email', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.details || result.error || 'Failed to send message');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showAlert('success', 'Message sent successfully! I will get back to you soon.');
|
||||||
|
form.reset();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Form submission error:', error);
|
||||||
|
showAlert('error', `Something went wrong: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
submitBtn.innerHTML = originalBtnText;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... existing code ...
|
|
@ -6,6 +6,13 @@ export async function onRequestPost(context) {
|
||||||
const { name, email, subject, message } = await context.request.json();
|
const { name, email, subject, message } = await context.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Check if API key is configured
|
||||||
|
if (!context.env.MAILERSEND_API_KEY) {
|
||||||
|
throw new Error('MailerSend API key is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Attempting to send email with MailerSend...');
|
||||||
|
|
||||||
const response = await fetch("https://api.mailersend.com/v1/email", {
|
const response = await fetch("https://api.mailersend.com/v1/email", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -14,16 +21,16 @@ export async function onRequestPost(context) {
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
from: {
|
from: {
|
||||||
email: "contact@argobox.com",
|
email: "contact@laforceit.com",
|
||||||
name: "Daniel LaForce"
|
name: "Daniel LaForce"
|
||||||
},
|
},
|
||||||
to: [
|
to: [
|
||||||
{
|
{
|
||||||
email: "daniel.laforce@argobox.com",
|
email: "daniel.laforce@laforceit.com",
|
||||||
name: "Daniel LaForce"
|
name: "Daniel LaForce"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
subject: `[Argobox] ${subject}`,
|
subject: `[LaForceIT] ${subject}`,
|
||||||
html: `
|
html: `
|
||||||
<h2>New Contact Message</h2>
|
<h2>New Contact Message</h2>
|
||||||
<p><strong>Name:</strong> ${name}</p>
|
<p><strong>Name:</strong> ${name}</p>
|
||||||
|
@ -39,15 +46,32 @@ export async function onRequestPost(context) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const responseData = await response.text();
|
||||||
|
console.log("MailerSend Response:", responseData);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("MailerSend Error:", await response.json());
|
const errorDetail = responseData ? JSON.parse(responseData) : {};
|
||||||
return new Response(JSON.stringify({ error: "Failed to send email" }), { status: 500 });
|
throw new Error(`MailerSend API error (${response.status}): ${JSON.stringify(errorDetail)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
return new Response(JSON.stringify({ success: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Unexpected Error:", err);
|
console.error("Email Send Error:", err);
|
||||||
return new Response(JSON.stringify({ error: "Unexpected server error" }), { status: 500 });
|
return new Response(JSON.stringify({
|
||||||
|
error: "Failed to send email",
|
||||||
|
details: err.message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}), {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 199 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 789 B After Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
13
index.html
|
@ -35,6 +35,9 @@
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Particles Container -->
|
||||||
|
<div class="particles-container" id="particles-container"></div>
|
||||||
|
|
||||||
<!-- Navigation Bar -->
|
<!-- Navigation Bar -->
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -66,7 +69,6 @@
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section id="home" class="hero">
|
<section id="home" class="hero">
|
||||||
<!-- Background elements -->
|
<!-- Background elements -->
|
||||||
<div class="particles-container" id="particles-container"></div>
|
|
||||||
<div class="floating-icons" id="floating-icons"></div>
|
<div class="floating-icons" id="floating-icons"></div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -80,14 +82,17 @@
|
||||||
<h1 class="hero-title">
|
<h1 class="hero-title">
|
||||||
<span class="role-wrapper">
|
<span class="role-wrapper">
|
||||||
<span class="role active" data-role="admin" data-description="Building scalable infrastructure with optimized performance and bulletproof security — from server management to end-user support that keeps businesses running smoothly.">
|
<span class="role active" data-role="admin" data-description="Building scalable infrastructure with optimized performance and bulletproof security — from server management to end-user support that keeps businesses running smoothly.">
|
||||||
<span class="highlight">Network</span> & <span class="highlight">Cyber Security</span> Professional
|
<span class="highlight">Systems</span> <span class="highlight">Administrator</span> & IT Expert
|
||||||
</span>
|
</span>
|
||||||
<span class="role" data-role="devops" data-description="Streamlining development workflows with CI/CD pipelines and infrastructure as code — automating deployments and improving reliability across your entire technology stack.">
|
<span class="role" data-role="cyber" data-description="Protecting your digital assets with cutting-edge security solutions and proactive threat detection — ensuring your business stays ahead of evolving cyber threats.">
|
||||||
<span class="highlight">DevOps</span> & <span class="highlight">Automation</span> Engineer
|
<span class="highlight">Network</span> & <span class="highlight">Cyber Security</span> Professional
|
||||||
</span>
|
</span>
|
||||||
<span class="role" data-role="architect" data-description="Designing resilient infrastructure and automation solutions — from virtualization and containerization to secure network architecture and cloud migration strategies.">
|
<span class="role" data-role="architect" data-description="Designing resilient infrastructure and automation solutions — from virtualization and containerization to secure network architecture and cloud migration strategies.">
|
||||||
<span class="highlight">Infrastructure</span> & <span class="highlight">Systems</span> Architect
|
<span class="highlight">Infrastructure</span> & <span class="highlight">Systems</span> Architect
|
||||||
</span>
|
</span>
|
||||||
|
<span class="role" data-role="devops" data-description="Streamlining development workflows with CI/CD pipelines and infrastructure as code — automating deployments and improving reliability across your entire technology stack.">
|
||||||
|
<span class="highlight">DevOps</span> & <span class="highlight">Automation</span> Expert
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|
184
resume.html
|
@ -9,7 +9,7 @@
|
||||||
<meta name="description" content="Professional resume of Daniel LaForce - Systems & Infrastructure Engineer specializing in virtualization, containerization, and secure network architecture.">
|
<meta name="description" content="Professional resume of Daniel LaForce - Systems & Infrastructure Engineer specializing in virtualization, containerization, and secure network architecture.">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22256%22 height=%22256%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%230f172a%22></rect><path fill=%22%233b82f6%22 d=%22M30 40L50 20L70 40L50 60L30 40Z%22></path><path fill=%22%233b82f6%22 d=%22M50 60L70 40L70 70L50 90L30 70L30 40L50 60Z%22 fill-opacity=%220.6%22></path></svg>">
|
<link rel="icon" type="image/png" href="images/favicon.png">
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
@ -34,9 +34,11 @@
|
||||||
|
|
||||||
.resume-container {
|
.resume-container {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 6rem auto 4rem;
|
margin: 2rem auto;
|
||||||
padding: 0 1.5rem;
|
padding: 2rem;
|
||||||
flex: 1;
|
background: var(--card-bg);
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resume-header {
|
.resume-header {
|
||||||
|
@ -286,9 +288,149 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.contact-links a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--secondary-bg);
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
.contact-links a:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
.contact-links a {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call to Action Styles */
|
||||||
|
.cta-banner {
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(37, 99, 235, 0.2));
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: 2.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-content {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-description {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlights {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlight:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlight i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlight span {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn {
|
||||||
|
min-width: 200px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cta-title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-description {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-highlight {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Bubble Animation -->
|
||||||
|
<div class="bubbles">
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
<div class="bubble"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -320,9 +462,41 @@
|
||||||
|
|
||||||
<!-- Resume Content -->
|
<!-- Resume Content -->
|
||||||
<div class="resume-container">
|
<div class="resume-container">
|
||||||
|
<!-- Call to Action Section -->
|
||||||
|
<div class="cta-banner">
|
||||||
|
<div class="cta-content">
|
||||||
|
<h2 class="cta-title">Transform Your Business with Enterprise-Grade IT Solutions</h2>
|
||||||
|
<p class="cta-description">Whether you need to enhance your cybersecurity posture, modernize your infrastructure, or seek a seasoned IT leader for your team, I deliver proven results:</p>
|
||||||
|
<div class="cta-highlights">
|
||||||
|
<div class="cta-highlight">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
<span>Reduced security incidents by 83% for enterprise clients</span>
|
||||||
|
</div>
|
||||||
|
<div class="cta-highlight">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>
|
||||||
|
<span>Improved system performance by 60% through optimization</span>
|
||||||
|
</div>
|
||||||
|
<div class="cta-highlight">
|
||||||
|
<i class="fas fa-chart-line"></i>
|
||||||
|
<span>Decreased operational costs by 30% with automation</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cta-buttons">
|
||||||
|
<a href="index.html#contact" class="btn btn-primary cta-btn">
|
||||||
|
<i class="fas fa-rocket"></i>
|
||||||
|
Schedule a Free Consultation
|
||||||
|
</a>
|
||||||
|
<a href="mailto:daniel.laforce@argobox.com" class="btn btn-outline cta-btn">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
Discuss Employment Opportunities
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="resume-header">
|
<div class="resume-header">
|
||||||
<h1 class="resume-name">Daniel LaForce</h1>
|
<h1 class="resume-name">Daniel LaForce</h1>
|
||||||
<h2 class="resume-title">Infrastructure & Systems Engineer</h2>
|
<h2 class="resume-title">Network & Cyber Security Professional</h2>
|
||||||
|
|
||||||
<div class="resume-contact">
|
<div class="resume-contact">
|
||||||
<div class="resume-contact-item">
|
<div class="resume-contact-item">
|
||||||
|
|
116
script.js
|
@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Initialize all website functionality
|
// Initialize all website functionality
|
||||||
initNavigation();
|
initNavigation();
|
||||||
initParticlesAndIcons();
|
initParticlesAndIcons();
|
||||||
initRoleRotation();
|
initRoleAnimation();
|
||||||
initTerminalTyping();
|
initTerminalTyping();
|
||||||
initSolutionsCarousel();
|
initSolutionsCarousel();
|
||||||
initScrollReveal();
|
initScrollReveal();
|
||||||
|
@ -119,11 +119,12 @@ function createBackgroundParticles() {
|
||||||
particle.style.top = `${yPos}%`;
|
particle.style.top = `${yPos}%`;
|
||||||
|
|
||||||
// Animation properties
|
// Animation properties
|
||||||
particle.style.animationDuration = `${Math.random() * 20 + 10}s`;
|
const duration = Math.random() * 20 + 10;
|
||||||
particle.style.animationDelay = `${Math.random() * 5}s`;
|
particle.style.animationDuration = `${duration}s`;
|
||||||
|
particle.style.animationDelay = `${Math.random() * -duration}s`;
|
||||||
|
|
||||||
// Add particle animation
|
// Add particle animation
|
||||||
particle.style.animation = `particle-float ${Math.random() * 20 + 10}s linear infinite`;
|
particle.style.animation = `float-particle ${duration}s linear infinite`;
|
||||||
|
|
||||||
particlesContainer.appendChild(particle);
|
particlesContainer.appendChild(particle);
|
||||||
}
|
}
|
||||||
|
@ -180,51 +181,24 @@ function createFloatingIcons() {
|
||||||
/**
|
/**
|
||||||
* Initialize role rotation in the hero section
|
* Initialize role rotation in the hero section
|
||||||
*/
|
*/
|
||||||
function initRoleRotation() {
|
function initRoleAnimation() {
|
||||||
const roles = document.querySelectorAll('.role');
|
const roles = document.querySelectorAll('.role');
|
||||||
const descriptionElement = document.getElementById('role-description');
|
const description = document.getElementById('role-description');
|
||||||
|
let currentIndex = 0;
|
||||||
if (roles.length === 0 || !descriptionElement) return;
|
|
||||||
|
function updateRole() {
|
||||||
let currentRole = 0;
|
roles.forEach(role => role.classList.remove('active'));
|
||||||
|
roles[currentIndex].classList.add('active');
|
||||||
function rotateRoles() {
|
description.textContent = roles[currentIndex].getAttribute('data-description');
|
||||||
// Hide current role
|
description.style.opacity = '0';
|
||||||
roles[currentRole].classList.remove('active');
|
setTimeout(() => {
|
||||||
|
description.style.opacity = '1';
|
||||||
// Move to next role
|
}, 100);
|
||||||
currentRole = (currentRole + 1) % roles.length;
|
currentIndex = (currentIndex + 1) % roles.length;
|
||||||
|
|
||||||
// Show new role
|
|
||||||
roles[currentRole].classList.add('active');
|
|
||||||
|
|
||||||
// Update description text
|
|
||||||
const newDescription = roles[currentRole].getAttribute('data-description');
|
|
||||||
if (newDescription) {
|
|
||||||
descriptionElement.textContent = newDescription;
|
|
||||||
|
|
||||||
// Animate the description change
|
|
||||||
descriptionElement.style.opacity = '0';
|
|
||||||
descriptionElement.style.transform = 'translateY(10px)';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
descriptionElement.style.opacity = '1';
|
|
||||||
descriptionElement.style.transform = 'translateY(0)';
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial role to active
|
updateRole(); // Set initial state
|
||||||
roles[0].classList.add('active');
|
setInterval(updateRole, 5000); // Switch roles every 5 seconds
|
||||||
|
|
||||||
// Initialize with the first description
|
|
||||||
const initialDescription = roles[0].getAttribute('data-description');
|
|
||||||
if (initialDescription && descriptionElement) {
|
|
||||||
descriptionElement.textContent = initialDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start rotation with delay
|
|
||||||
setInterval(rotateRoles, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -406,6 +380,10 @@ function initFormHandling(form) {
|
||||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
|
|
||||||
|
const notification = document.getElementById('form-notification');
|
||||||
|
const notificationIcon = notification.querySelector('i');
|
||||||
|
const notificationText = notification.querySelector('.notification-text');
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
name: form.elements['name'].value,
|
name: form.elements['name'].value,
|
||||||
email: form.elements['email'].value,
|
email: form.elements['email'].value,
|
||||||
|
@ -414,6 +392,7 @@ function initFormHandling(form) {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('Sending form data...');
|
||||||
const res = await fetch("/api/send-email", {
|
const res = await fetch("/api/send-email", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -423,47 +402,38 @@ function initFormHandling(form) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
console.log('Response:', data);
|
||||||
|
|
||||||
const notification = document.getElementById('form-notification');
|
if (res.ok) {
|
||||||
const notificationIcon = notification.querySelector('i');
|
notification.style.display = 'flex';
|
||||||
const notificationText = notification.querySelector('.notification-text');
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
notification.style.display = 'block';
|
|
||||||
notification.classList.add('success');
|
|
||||||
notification.classList.remove('error');
|
notification.classList.remove('error');
|
||||||
|
notification.classList.add('success');
|
||||||
notificationIcon.className = 'fas fa-check-circle';
|
notificationIcon.className = 'fas fa-check-circle';
|
||||||
notificationText.textContent = "Message sent successfully! We'll get back to you soon.";
|
notificationText.textContent = "Message sent successfully! We'll get back to you soon.";
|
||||||
form.reset();
|
form.reset();
|
||||||
} else {
|
} else {
|
||||||
notification.style.display = 'block';
|
notification.style.display = 'flex';
|
||||||
notification.classList.add('error');
|
|
||||||
notification.classList.remove('success');
|
notification.classList.remove('success');
|
||||||
|
notification.classList.add('error');
|
||||||
notificationIcon.className = 'fas fa-exclamation-circle';
|
notificationIcon.className = 'fas fa-exclamation-circle';
|
||||||
notificationText.textContent = "Failed to send message. Please try again or contact me directly.";
|
notificationText.textContent = `Error: ${data.error}${data.details ? ` - ${data.details}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide notification after 5 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Form submission error:", error);
|
||||||
const notification = document.getElementById('form-notification');
|
notification.style.display = 'flex';
|
||||||
notification.style.display = 'block';
|
|
||||||
notification.classList.add('error');
|
|
||||||
notification.classList.remove('success');
|
notification.classList.remove('success');
|
||||||
notification.querySelector('i').className = 'fas fa-exclamation-circle';
|
notification.classList.add('error');
|
||||||
notification.querySelector('.notification-text').textContent = "Something went wrong while sending your message.";
|
notificationIcon.className = 'fas fa-exclamation-circle';
|
||||||
|
notificationText.textContent = `Error: ${error.message}`;
|
||||||
// Hide notification after 5 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
} finally {
|
} finally {
|
||||||
submitButton.innerHTML = originalButtonText;
|
submitButton.innerHTML = originalButtonText;
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
|
|
||||||
|
// Hide notification after 10 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.display = 'none';
|
||||||
|
}, 10000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
297
styles.css
|
@ -1540,24 +1540,25 @@ transform: scale(1.02);
|
||||||
|
|
||||||
.form-notification {
|
.form-notification {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem 1.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
animation: slideIn 0.3s ease-out;
|
animation: slideIn 0.3s ease-out;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-notification.success {
|
.form-notification.success {
|
||||||
background-color: #d1fae5;
|
background-color: rgba(16, 185, 129, 0.1);
|
||||||
color: #065f46;
|
color: #10b981;
|
||||||
border: 1px solid #34d399;
|
border: 1px solid #10b981;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-notification.error {
|
.form-notification.error {
|
||||||
background-color: #fee2e2;
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
color: #991b1b;
|
color: #ef4444;
|
||||||
border: 1px solid #f87171;
|
border: 1px solid #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-notification i {
|
.form-notification i {
|
||||||
|
@ -1575,4 +1576,286 @@ transform: scale(1.02);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bubble Animation */
|
||||||
|
.bubbles {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
position: absolute;
|
||||||
|
background: radial-gradient(circle at center, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.05) 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: float-bubble 8s infinite ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble:nth-child(1) {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
left: 10%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble:nth-child(2) {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
left: 25%;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble:nth-child(3) {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
left: 40%;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble:nth-child(4) {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
left: 65%;
|
||||||
|
animation-delay: 6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble:nth-child(5) {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
left: 80%;
|
||||||
|
animation-delay: 8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-bubble {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100vh) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-100vh) scale(1.2);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add this to both index.html and resume.html body */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(circle at 20% 35%, rgba(29, 78, 216, 0.15) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 75% 60%, rgba(14, 165, 233, 0.1) 0%, transparent 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slogan Styles */
|
||||||
|
.slogan-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
animation: fadeIn 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan-sub {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA Section Styles */
|
||||||
|
.cta-section {
|
||||||
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(37, 99, 235, 0.2));
|
||||||
|
padding: 6rem 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-grid {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-content {
|
||||||
|
max-width: 800px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-description {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-features {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-feature {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
transition: all var(--transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-feature:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-feature i {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-feature span {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.slogan {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan-sub {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-stats, .cta-features {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-description {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Particles */
|
||||||
|
.particles-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.particle {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(59, 130, 246, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float-particle {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translate(50px, -50px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(100px, 0);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|