Compare commits
2 Commits
bc36232b45
...
04c59f7d7a
Author | SHA1 | Date |
---|---|---|
|
04c59f7d7a | |
|
236e64a2cc |
|
@ -0,0 +1,54 @@
|
|||
import { Resend } from 'resend';
|
||||
|
||||
/**
|
||||
* Cloudflare Pages Function - /api/send-email
|
||||
*/
|
||||
export async function onRequestPost(context) {
|
||||
const { name, email, subject, message } = await context.request.json();
|
||||
|
||||
try {
|
||||
const response = await fetch("https://api.mailersend.com/v1/email", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: "Bearer " + context.env.MAILERSEND_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from: {
|
||||
email: "daniel@laforceit.com",
|
||||
name: "Daniel LaForce"
|
||||
},
|
||||
to: [
|
||||
{
|
||||
email: "daniel@laforceit.com",
|
||||
name: "Daniel LaForce"
|
||||
}
|
||||
],
|
||||
subject: `[Argobox] ${subject}`,
|
||||
html: `
|
||||
<h2>New Contact Message</h2>
|
||||
<p><strong>Name:</strong> ${name}</p>
|
||||
<p><strong>Email:</strong> ${email}</p>
|
||||
<p><strong>Message:</strong><br>${message.replace(/\n/g, "<br>")}</p>
|
||||
`,
|
||||
reply_to: [
|
||||
{
|
||||
email,
|
||||
name
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("MailerSend Error:", await response.json());
|
||||
return new Response(JSON.stringify({ error: "Failed to send email" }), { status: 500 });
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
||||
|
||||
} catch (err) {
|
||||
console.error("Unexpected Error:", err);
|
||||
return new Response(JSON.stringify({ error: "Unexpected server error" }), { status: 500 });
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 228 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 371 B |
Binary file not shown.
After Width: | Height: | Size: 927 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
31
index.html
31
index.html
|
@ -12,10 +12,16 @@
|
|||
<meta property="og:title" content="Daniel LaForce | Infrastructure & Systems Architect">
|
||||
<meta property="og:description" content="Expert in infrastructure architecture, DevOps automation, and secure cloud migrations. View my live lab dashboard and projects.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://argobox.com">
|
||||
<meta property="og:url" content="https://laforceit.com">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" type="image/x-icon" href="images/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<link rel="manifest" href="images/site.webmanifest">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="images/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="images/android-chrome-512x512.png">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
@ -34,8 +40,7 @@
|
|||
<div class="container">
|
||||
<div class="logo">
|
||||
<a href="#home">
|
||||
<span class="logo-text">Argobox</span>
|
||||
<span class="logo-dot">.com</span>
|
||||
<span class="logo-text-glow">LaForceIT</span><span class="logo-dot-glow">.com</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
|
@ -47,7 +52,7 @@
|
|||
<a href="#contact" class="nav-link">Contact</a>
|
||||
</div>
|
||||
<div class="nav-buttons">
|
||||
<a href="dashboard.html" class="dashboard-link" target="_blank">
|
||||
<a href="https://argobox.com/dashboard" class="dashboard-link" target="_blank">
|
||||
<span class="live-indicator"></span>
|
||||
<span>Live Dashboard</span>
|
||||
</a>
|
||||
|
@ -127,7 +132,7 @@
|
|||
<span class="btn-icon"><i class="fas fa-arrow-right"></i></span>
|
||||
</a>
|
||||
|
||||
<a href="ansible-sandbox.html" class="btn btn-outline btn-featured" target="_blank">
|
||||
<a href="https://argobox.com/ansible-sandbox" class="btn btn-outline btn-featured" target="_blank">
|
||||
<span class="pulse-ring"></span>
|
||||
<span class="btn-text">Explore My Lab</span>
|
||||
<span class="btn-icon"><i class="fas fa-server"></i></span>
|
||||
|
@ -356,13 +361,13 @@
|
|||
</ul>
|
||||
|
||||
<div class="lab-buttons">
|
||||
<a href="dashboard.html" class="btn btn-primary" target="_blank">
|
||||
<a href="https://argobox.com/dashboard" class="btn btn-primary" target="_blank">
|
||||
<span class="flex-center">
|
||||
<span class="live-indicator"></span>
|
||||
<span>View Live Dashboard</span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="ansible-sandbox.html" class="btn btn-outline" target="_blank">Try Ansible Sandbox</a>
|
||||
<a href="https://argobox.com/ansible-sandbox" class="btn btn-outline" target="_blank">Try Ansible Sandbox</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -579,7 +584,7 @@
|
|||
<i class="fas fa-envelope"></i>
|
||||
</div>
|
||||
<h3 class="contact-title">Email</h3>
|
||||
<p><a href="mailto:daniel.laforce@argobox.com">daniel.laforce@argobox.com</a></p>
|
||||
<p><a href="mailto:daniel@laforceit.com">daniel@laforceit.com</a></p>
|
||||
</div>
|
||||
|
||||
<div class="contact-item">
|
||||
|
@ -636,9 +641,9 @@
|
|||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-logo">
|
||||
<span class="logo-text">Argobox</span>
|
||||
<span class="logo-text">LaForceIT</span>
|
||||
<span class="logo-dot">.com</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-links">
|
||||
<a href="#home">Home</a>
|
||||
|
@ -651,12 +656,12 @@
|
|||
<div class="footer-social">
|
||||
<a href="https://github.com/keyargo" target="_blank" aria-label="GitHub"><i class="fab fa-github"></i></a>
|
||||
<a href="https://www.linkedin.com/in/danlaforce" target="_blank" aria-label="LinkedIn"><i class="fab fa-linkedin"></i></a>
|
||||
<a href="mailto:daniel.laforce@argobox.com" aria-label="Email"><i class="fas fa-envelope"></i></a>
|
||||
<a href="mailto:daniel@laforceit.com" aria-label="Email"><i class="fas fa-envelope"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© <span id="current-year"></span> All rights reserved. Inovin LLC</p>
|
||||
<p>© <span id="current-year"></span> All rights reserved. LaForce IT Consulting</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -294,8 +294,8 @@
|
|||
<div class="container">
|
||||
<div class="logo">
|
||||
<a href="index.html">
|
||||
<span class="logo-text">Argobox</span>
|
||||
<span class="logo-dot">.com</span>
|
||||
<span class="logo-text-glow">LaForceIT</span>
|
||||
<span class="logo-dot-glow">.com</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
|
@ -805,8 +805,8 @@
|
|||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-logo">
|
||||
<span class="logo-text">Argobox</span>
|
||||
<span class="logo-dot">.com</span>
|
||||
<span class="logo-text-glow">LaForceIT</span>
|
||||
<span class="logo-dot-glow">.com</span>
|
||||
</div>
|
||||
|
||||
<div class="footer-links">
|
||||
|
|
|
@ -0,0 +1,476 @@
|
|||
/**
|
||||
* Main JavaScript file for argobox.com
|
||||
* Handles animations, interactions, and dynamic content
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all website functionality
|
||||
initNavigation();
|
||||
initParticlesAndIcons();
|
||||
initRoleRotation();
|
||||
initTerminalTyping();
|
||||
initSolutionsCarousel();
|
||||
initScrollReveal();
|
||||
updateMetrics();
|
||||
updateYear();
|
||||
|
||||
// Initialize form handling
|
||||
const contactForm = document.getElementById('contact-form');
|
||||
if (contactForm) {
|
||||
initFormHandling(contactForm);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Set up navigation functionality - mobile menu and scroll spy
|
||||
*/
|
||||
function initNavigation() {
|
||||
// Mobile menu toggle
|
||||
const menuToggle = document.querySelector('.menu-toggle');
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
|
||||
if (menuToggle && navMenu) {
|
||||
menuToggle.addEventListener('click', function() {
|
||||
navMenu.classList.toggle('active');
|
||||
menuToggle.setAttribute('aria-expanded',
|
||||
menuToggle.getAttribute('aria-expanded') === 'true' ? 'false' : 'true');
|
||||
});
|
||||
}
|
||||
|
||||
// Navigation scroll spy
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
|
||||
function updateActiveNavLink() {
|
||||
let scrollPosition = window.scrollY + 100;
|
||||
|
||||
sections.forEach(section => {
|
||||
const sectionTop = section.offsetTop;
|
||||
const sectionHeight = section.offsetHeight;
|
||||
const sectionId = section.getAttribute('id');
|
||||
|
||||
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
|
||||
navLinks.forEach(link => {
|
||||
link.classList.remove('active');
|
||||
if (link.getAttribute('href') === `#${sectionId}`) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar style change on scroll
|
||||
const navbar = document.querySelector('.navbar');
|
||||
|
||||
function updateNavbarStyle() {
|
||||
if (window.scrollY > 50) {
|
||||
navbar?.classList.add('scrolled');
|
||||
} else {
|
||||
navbar?.classList.remove('scrolled');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
updateActiveNavLink();
|
||||
updateNavbarStyle();
|
||||
});
|
||||
|
||||
// Initial call to set correct states
|
||||
updateActiveNavLink();
|
||||
updateNavbarStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background particles and floating tech icons
|
||||
*/
|
||||
function initParticlesAndIcons() {
|
||||
createBackgroundParticles();
|
||||
createFloatingIcons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create animated background particles
|
||||
*/
|
||||
function createBackgroundParticles() {
|
||||
const particlesContainer = document.getElementById('particles-container');
|
||||
|
||||
if (!particlesContainer) return;
|
||||
|
||||
particlesContainer.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.classList.add('particle');
|
||||
|
||||
// Random size, opacity, and position
|
||||
const size = Math.random() * 4 + 1;
|
||||
const opacity = Math.random() * 0.3 + 0.1;
|
||||
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
particle.style.opacity = opacity;
|
||||
|
||||
// Position randomly with some clustering toward top areas
|
||||
const xPos = Math.random() * 100;
|
||||
const yPos = Math.random() * 100;
|
||||
|
||||
particle.style.left = `${xPos}%`;
|
||||
particle.style.top = `${yPos}%`;
|
||||
|
||||
// Animation properties
|
||||
particle.style.animationDuration = `${Math.random() * 20 + 10}s`;
|
||||
particle.style.animationDelay = `${Math.random() * 5}s`;
|
||||
|
||||
// Add particle animation
|
||||
particle.style.animation = `particle-float ${Math.random() * 20 + 10}s linear infinite`;
|
||||
|
||||
particlesContainer.appendChild(particle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create floating tech icons in the background
|
||||
*/
|
||||
function createFloatingIcons() {
|
||||
const iconContainer = document.getElementById('floating-icons');
|
||||
|
||||
if (!iconContainer) return;
|
||||
|
||||
iconContainer.innerHTML = '';
|
||||
|
||||
// Tech-related unicode symbols and fontawesome classes
|
||||
const icons = [
|
||||
'⚙️', '💻', '🔒', '🔌', '🌐', '☁️', '📊',
|
||||
'fa-server', 'fa-network-wired', 'fa-database',
|
||||
'fa-code-branch', 'fa-cloud', 'fa-shield-alt'
|
||||
];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const icon = document.createElement('div');
|
||||
icon.classList.add('floating-icon');
|
||||
|
||||
const iconType = icons[Math.floor(Math.random() * icons.length)];
|
||||
|
||||
// Handle both unicode and font awesome
|
||||
if (iconType.startsWith('fa-')) {
|
||||
const faIcon = document.createElement('i');
|
||||
faIcon.className = `fas ${iconType}`;
|
||||
icon.appendChild(faIcon);
|
||||
} else {
|
||||
icon.textContent = iconType;
|
||||
}
|
||||
|
||||
// Random size and position
|
||||
const size = Math.random() * 24 + 16;
|
||||
icon.style.fontSize = `${size}px`;
|
||||
|
||||
// Position
|
||||
icon.style.left = `${Math.random() * 100}%`;
|
||||
icon.style.bottom = `-50px`;
|
||||
|
||||
// Animation
|
||||
icon.style.animationDuration = `${Math.random() * 30 + 20}s`;
|
||||
icon.style.animationDelay = `${Math.random() * 10}s`;
|
||||
|
||||
iconContainer.appendChild(icon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize role rotation in the hero section
|
||||
*/
|
||||
function initRoleRotation() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize terminal typing animation in the hero section
|
||||
*/
|
||||
function initTerminalTyping() {
|
||||
const terminalText = document.getElementById('terminal-text');
|
||||
if (!terminalText) return;
|
||||
|
||||
const terminalMessages = [
|
||||
"> Ready for deployment...",
|
||||
"> Reducing operational costs by 30%",
|
||||
"> Improving system reliability to 99.9%",
|
||||
"> Accelerating digital transformation",
|
||||
"> Enhancing security compliance",
|
||||
"> Streamlining IT workflows",
|
||||
"> Optimizing infrastructure performance",
|
||||
"> Implementing best practices",
|
||||
"> Supporting business objectives"
|
||||
];
|
||||
|
||||
let currentMessage = 0;
|
||||
|
||||
function typeMessage(message, index = 0) {
|
||||
if (index < message.length) {
|
||||
terminalText.textContent = message.substring(0, index + 1);
|
||||
setTimeout(() => typeMessage(message, index + 1), 50 + Math.random() * 50);
|
||||
} else {
|
||||
// Wait before clearing and typing next message
|
||||
setTimeout(clearAndTypeNext, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function clearAndTypeNext() {
|
||||
// Clear the current text with a backspace effect
|
||||
const currentText = terminalText.textContent;
|
||||
|
||||
function backspace(length = currentText.length) {
|
||||
if (length > 0) {
|
||||
terminalText.textContent = currentText.substring(0, length - 1);
|
||||
setTimeout(() => backspace(length - 1), 20);
|
||||
} else {
|
||||
// Move to next message
|
||||
currentMessage = (currentMessage + 1) % terminalMessages.length;
|
||||
setTimeout(() => typeMessage(terminalMessages[currentMessage]), 500);
|
||||
}
|
||||
}
|
||||
|
||||
backspace();
|
||||
}
|
||||
|
||||
// Start the typing animation with the first message
|
||||
typeMessage(terminalMessages[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the solutions carousel
|
||||
*/
|
||||
function initSolutionsCarousel() {
|
||||
const slides = document.querySelectorAll('.solution-slide');
|
||||
const dots = document.querySelectorAll('.slider-dot');
|
||||
|
||||
if (slides.length === 0 || dots.length === 0) return;
|
||||
|
||||
let currentSlide = 0;
|
||||
let slideInterval;
|
||||
|
||||
function showSlide(index) {
|
||||
// Hide all slides
|
||||
slides.forEach(slide => slide.classList.remove('active'));
|
||||
dots.forEach(dot => dot.classList.remove('active'));
|
||||
|
||||
// Show selected slide
|
||||
slides[index].classList.add('active');
|
||||
dots[index].classList.add('active');
|
||||
|
||||
currentSlide = index;
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
const next = (currentSlide + 1) % slides.length;
|
||||
showSlide(next);
|
||||
}
|
||||
|
||||
// Add click events to dots
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
clearInterval(slideInterval);
|
||||
showSlide(index);
|
||||
|
||||
// Restart automatic rotation
|
||||
slideInterval = setInterval(nextSlide, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
// Start automatic rotation
|
||||
slideInterval = setInterval(nextSlide, 5000);
|
||||
|
||||
// Show first slide initially
|
||||
showSlide(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add scroll reveal animations to elements
|
||||
*/
|
||||
function initScrollReveal() {
|
||||
const revealElements = document.querySelectorAll('.section-header, .service-card, .project-card, .lab-card, .timeline-item, .contact-item');
|
||||
|
||||
revealElements.forEach(element => {
|
||||
element.classList.add('reveal');
|
||||
});
|
||||
|
||||
function checkReveal() {
|
||||
revealElements.forEach(element => {
|
||||
const elementTop = element.getBoundingClientRect().top;
|
||||
const elementVisible = 150;
|
||||
|
||||
if (elementTop < window.innerHeight - elementVisible) {
|
||||
element.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial check
|
||||
checkReveal();
|
||||
|
||||
// Check on scroll
|
||||
window.addEventListener('scroll', checkReveal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metrics values periodically to simulate live data
|
||||
*/
|
||||
function updateMetrics() {
|
||||
const metrics = {
|
||||
'CPU Usage': { min: 30, max: 60, element: null },
|
||||
'Memory': { min: 45, max: 70, element: null },
|
||||
'Storage': { min: 60, max: 75, element: null },
|
||||
'Network': { min: 15, max: 40, element: null }
|
||||
};
|
||||
|
||||
// Get all metric elements
|
||||
document.querySelectorAll('.metric').forEach(metric => {
|
||||
const nameElement = metric.querySelector('.metric-name');
|
||||
if (nameElement && metrics[nameElement.textContent]) {
|
||||
metrics[nameElement.textContent].element = metric;
|
||||
}
|
||||
});
|
||||
|
||||
function updateMetricValues() {
|
||||
Object.keys(metrics).forEach(key => {
|
||||
const metric = metrics[key];
|
||||
if (!metric.element) return;
|
||||
|
||||
const valueEl = metric.element.querySelector('.metric-value');
|
||||
const progressEl = metric.element.querySelector('.metric-progress');
|
||||
|
||||
if (valueEl && progressEl) {
|
||||
const newValue = Math.floor(Math.random() * (metric.max - metric.min)) + metric.min;
|
||||
valueEl.textContent = `${newValue}%`;
|
||||
progressEl.style.width = `${newValue}%`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update metrics every 5 seconds
|
||||
setInterval(updateMetricValues, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize contact form handling
|
||||
*/
|
||||
/**
|
||||
* Initialize contact form handling
|
||||
*/
|
||||
function initFormHandling(form) {
|
||||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
const originalButtonText = submitButton.innerHTML;
|
||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
||||
submitButton.disabled = true;
|
||||
|
||||
const formData = {
|
||||
name: form.elements['name'].value,
|
||||
email: form.elements['email'].value,
|
||||
subject: form.elements['subject'].value,
|
||||
message: form.elements['message'].value
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/send-email", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
alert("Thank you for your message! I will get back to you soon.");
|
||||
form.reset();
|
||||
} else {
|
||||
alert("Failed to send message. Please try again or contact me directly.");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("Something went wrong while sending your message.");
|
||||
} finally {
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update copyright year in the footer
|
||||
*/
|
||||
function updateYear() {
|
||||
const yearElement = document.getElementById('current-year');
|
||||
if (yearElement) {
|
||||
yearElement.textContent = new Date().getFullYear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to add particle float animation
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all website functionality
|
||||
initNavigation();
|
||||
initParticlesAndIcons();
|
||||
initRoleRotation(); // Updated function
|
||||
initTerminalTyping(); // Updated function
|
||||
initSolutionsCarousel(); // Updated function
|
||||
initScrollReveal();
|
||||
updateMetrics();
|
||||
updateYear();
|
||||
|
||||
// Initialize form handling
|
||||
const contactForm = document.getElementById('contact-form');
|
||||
if (contactForm) {
|
||||
initFormHandling(contactForm);
|
||||
}
|
||||
});
|
56
styles.css
56
styles.css
|
@ -209,20 +209,32 @@ section {
|
|||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
color: transparent;
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
background-clip: text;
|
||||
text-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.logo a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
color: var(--accent);
|
||||
margin-right: 4px;
|
||||
color: inherit; /* inherit gradient */
|
||||
}
|
||||
|
||||
.logo-dot {
|
||||
color: var(--text-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -1495,3 +1507,35 @@ section {
|
|||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================
|
||||
LaForceIT.com Logo Styling
|
||||
============================ */
|
||||
|
||||
.logo-text-glow {
|
||||
font-weight: 800;
|
||||
font-size: 1.6rem;
|
||||
background: linear-gradient(90deg, var(--accent), var(--accent-darker));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 0 0 6px rgba(59, 130, 246, 0.5);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.logo-dot-glow {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 1.6rem;
|
||||
margin-left: 2px;
|
||||
text-shadow: 0 0 3px rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.logo-text-glow:hover,
|
||||
.logo-dot-glow:hover {
|
||||
text-shadow: 0 0 10px rgba(59, 130, 246, 0.8), 0 0 8px rgba(255, 255, 255, 0.3);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue