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();
|
||||
|
||||
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", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -14,16 +21,16 @@ export async function onRequestPost(context) {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
from: {
|
||||
email: "contact@argobox.com",
|
||||
email: "contact@laforceit.com",
|
||||
name: "Daniel LaForce"
|
||||
},
|
||||
to: [
|
||||
{
|
||||
email: "daniel.laforce@argobox.com",
|
||||
email: "daniel.laforce@laforceit.com",
|
||||
name: "Daniel LaForce"
|
||||
}
|
||||
],
|
||||
subject: `[Argobox] ${subject}`,
|
||||
subject: `[LaForceIT] ${subject}`,
|
||||
html: `
|
||||
<h2>New Contact Message</h2>
|
||||
<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) {
|
||||
console.error("MailerSend Error:", await response.json());
|
||||
return new Response(JSON.stringify({ error: "Failed to send email" }), { status: 500 });
|
||||
const errorDetail = responseData ? JSON.parse(responseData) : {};
|
||||
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) {
|
||||
console.error("Unexpected Error:", err);
|
||||
return new Response(JSON.stringify({ error: "Unexpected server error" }), { status: 500 });
|
||||
console.error("Email Send Error:", err);
|
||||
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">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Particles Container -->
|
||||
<div class="particles-container" id="particles-container"></div>
|
||||
|
||||
<!-- Navigation Bar -->
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
|
@ -66,7 +69,6 @@
|
|||
<!-- Hero Section -->
|
||||
<section id="home" class="hero">
|
||||
<!-- Background elements -->
|
||||
<div class="particles-container" id="particles-container"></div>
|
||||
<div class="floating-icons" id="floating-icons"></div>
|
||||
|
||||
<div class="container">
|
||||
|
@ -80,14 +82,17 @@
|
|||
<h1 class="hero-title">
|
||||
<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="highlight">Network</span> & <span class="highlight">Cyber Security</span> Professional
|
||||
<span class="highlight">Systems</span> <span class="highlight">Administrator</span> & IT Expert
|
||||
</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> Engineer
|
||||
<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">Network</span> & <span class="highlight">Cyber Security</span> Professional
|
||||
</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="highlight">Infrastructure</span> & <span class="highlight">Systems</span> Architect
|
||||
</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>
|
||||
</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.">
|
||||
|
||||
<!-- 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 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
|
@ -34,9 +34,11 @@
|
|||
|
||||
.resume-container {
|
||||
max-width: 1000px;
|
||||
margin: 6rem auto 4rem;
|
||||
padding: 0 1.5rem;
|
||||
flex: 1;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background: var(--card-bg);
|
||||
border-radius: 1rem;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.resume-header {
|
||||
|
@ -286,9 +288,149 @@
|
|||
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>
|
||||
</head>
|
||||
<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 -->
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
|
@ -320,9 +462,41 @@
|
|||
|
||||
<!-- Resume Content -->
|
||||
<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">
|
||||
<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-item">
|
||||
|
|
116
script.js
|
@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
// Initialize all website functionality
|
||||
initNavigation();
|
||||
initParticlesAndIcons();
|
||||
initRoleRotation();
|
||||
initRoleAnimation();
|
||||
initTerminalTyping();
|
||||
initSolutionsCarousel();
|
||||
initScrollReveal();
|
||||
|
@ -119,11 +119,12 @@ function createBackgroundParticles() {
|
|||
particle.style.top = `${yPos}%`;
|
||||
|
||||
// Animation properties
|
||||
particle.style.animationDuration = `${Math.random() * 20 + 10}s`;
|
||||
particle.style.animationDelay = `${Math.random() * 5}s`;
|
||||
const duration = Math.random() * 20 + 10;
|
||||
particle.style.animationDuration = `${duration}s`;
|
||||
particle.style.animationDelay = `${Math.random() * -duration}s`;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
@ -180,51 +181,24 @@ function createFloatingIcons() {
|
|||
/**
|
||||
* Initialize role rotation in the hero section
|
||||
*/
|
||||
function initRoleRotation() {
|
||||
function initRoleAnimation() {
|
||||
const roles = document.querySelectorAll('.role');
|
||||
const descriptionElement = document.getElementById('role-description');
|
||||
|
||||
if (roles.length === 0 || !descriptionElement) return;
|
||||
|
||||
let currentRole = 0;
|
||||
|
||||
function rotateRoles() {
|
||||
// Hide current role
|
||||
roles[currentRole].classList.remove('active');
|
||||
|
||||
// Move to next role
|
||||
currentRole = (currentRole + 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);
|
||||
}
|
||||
const description = document.getElementById('role-description');
|
||||
let currentIndex = 0;
|
||||
|
||||
function updateRole() {
|
||||
roles.forEach(role => role.classList.remove('active'));
|
||||
roles[currentIndex].classList.add('active');
|
||||
description.textContent = roles[currentIndex].getAttribute('data-description');
|
||||
description.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
description.style.opacity = '1';
|
||||
}, 100);
|
||||
currentIndex = (currentIndex + 1) % roles.length;
|
||||
}
|
||||
|
||||
// Set initial role to active
|
||||
roles[0].classList.add('active');
|
||||
|
||||
// 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);
|
||||
|
||||
updateRole(); // Set initial state
|
||||
setInterval(updateRole, 5000); // Switch roles every 5 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -406,6 +380,10 @@ function initFormHandling(form) {
|
|||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
||||
submitButton.disabled = true;
|
||||
|
||||
const notification = document.getElementById('form-notification');
|
||||
const notificationIcon = notification.querySelector('i');
|
||||
const notificationText = notification.querySelector('.notification-text');
|
||||
|
||||
const formData = {
|
||||
name: form.elements['name'].value,
|
||||
email: form.elements['email'].value,
|
||||
|
@ -414,6 +392,7 @@ function initFormHandling(form) {
|
|||
};
|
||||
|
||||
try {
|
||||
console.log('Sending form data...');
|
||||
const res = await fetch("/api/send-email", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -423,47 +402,38 @@ function initFormHandling(form) {
|
|||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log('Response:', data);
|
||||
|
||||
const notification = document.getElementById('form-notification');
|
||||
const notificationIcon = notification.querySelector('i');
|
||||
const notificationText = notification.querySelector('.notification-text');
|
||||
|
||||
if (data.success) {
|
||||
notification.style.display = 'block';
|
||||
notification.classList.add('success');
|
||||
if (res.ok) {
|
||||
notification.style.display = 'flex';
|
||||
notification.classList.remove('error');
|
||||
notification.classList.add('success');
|
||||
notificationIcon.className = 'fas fa-check-circle';
|
||||
notificationText.textContent = "Message sent successfully! We'll get back to you soon.";
|
||||
form.reset();
|
||||
} else {
|
||||
notification.style.display = 'block';
|
||||
notification.classList.add('error');
|
||||
notification.style.display = 'flex';
|
||||
notification.classList.remove('success');
|
||||
notification.classList.add('error');
|
||||
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) {
|
||||
console.error("Error:", error);
|
||||
const notification = document.getElementById('form-notification');
|
||||
notification.style.display = 'block';
|
||||
notification.classList.add('error');
|
||||
console.error("Form submission error:", error);
|
||||
notification.style.display = 'flex';
|
||||
notification.classList.remove('success');
|
||||
notification.querySelector('i').className = 'fas fa-exclamation-circle';
|
||||
notification.querySelector('.notification-text').textContent = "Something went wrong while sending your message.";
|
||||
|
||||
// Hide notification after 5 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'none';
|
||||
}, 5000);
|
||||
notification.classList.add('error');
|
||||
notificationIcon.className = 'fas fa-exclamation-circle';
|
||||
notificationText.textContent = `Error: ${error.message}`;
|
||||
} finally {
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
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 {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-notification.success {
|
||||
background-color: #d1fae5;
|
||||
color: #065f46;
|
||||
border: 1px solid #34d399;
|
||||
background-color: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
border: 1px solid #10b981;
|
||||
}
|
||||
|
||||
.form-notification.error {
|
||||
background-color: #fee2e2;
|
||||
color: #991b1b;
|
||||
border: 1px solid #f87171;
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|