Compare commits

...

2 Commits

12 changed files with 604 additions and 24 deletions

View File

@ -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

BIN
images/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

BIN
images/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

BIN
images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
images/site.webmanifest Normal file
View File

@ -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"}

View File

@ -12,10 +12,16 @@
<meta property="og:title" content="Daniel LaForce | Infrastructure & Systems Architect"> <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: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:type" content="website">
<meta property="og:url" content="https://argobox.com"> <meta property="og:url" content="https://laforceit.com">
<!-- Favicon --> <!-- 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 --> <!-- CSS -->
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
@ -34,8 +40,7 @@
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<a href="#home"> <a href="#home">
<span class="logo-text">Argobox</span> <span class="logo-text-glow">LaForceIT</span><span class="logo-dot-glow">.com</span>
<span class="logo-dot">.com</span>
</a> </a>
</div> </div>
<div class="nav-menu"> <div class="nav-menu">
@ -47,7 +52,7 @@
<a href="#contact" class="nav-link">Contact</a> <a href="#contact" class="nav-link">Contact</a>
</div> </div>
<div class="nav-buttons"> <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 class="live-indicator"></span>
<span>Live Dashboard</span> <span>Live Dashboard</span>
</a> </a>
@ -127,7 +132,7 @@
<span class="btn-icon"><i class="fas fa-arrow-right"></i></span> <span class="btn-icon"><i class="fas fa-arrow-right"></i></span>
</a> </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="pulse-ring"></span>
<span class="btn-text">Explore My Lab</span> <span class="btn-text">Explore My Lab</span>
<span class="btn-icon"><i class="fas fa-server"></i></span> <span class="btn-icon"><i class="fas fa-server"></i></span>
@ -356,13 +361,13 @@
</ul> </ul>
<div class="lab-buttons"> <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="flex-center">
<span class="live-indicator"></span> <span class="live-indicator"></span>
<span>View Live Dashboard</span> <span>View Live Dashboard</span>
</span> </span>
</a> </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>
</div> </div>
@ -579,7 +584,7 @@
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</div> </div>
<h3 class="contact-title">Email</h3> <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>
<div class="contact-item"> <div class="contact-item">
@ -636,7 +641,7 @@
<div class="container"> <div class="container">
<div class="footer-content"> <div class="footer-content">
<div class="footer-logo"> <div class="footer-logo">
<span class="logo-text">Argobox</span> <span class="logo-text">LaForceIT</span>
<span class="logo-dot">.com</span> <span class="logo-dot">.com</span>
</div> </div>
@ -651,12 +656,12 @@
<div class="footer-social"> <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://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="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> </div>
<div class="footer-bottom"> <div class="footer-bottom">
<p>&copy; <span id="current-year"></span> All rights reserved. Inovin LLC</p> <p>&copy; <span id="current-year"></span> All rights reserved. LaForce IT Consulting</p>
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -294,8 +294,8 @@
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<a href="index.html"> <a href="index.html">
<span class="logo-text">Argobox</span> <span class="logo-text-glow">LaForceIT</span>
<span class="logo-dot">.com</span> <span class="logo-dot-glow">.com</span>
</a> </a>
</div> </div>
<div class="nav-menu"> <div class="nav-menu">
@ -805,8 +805,8 @@
<div class="container"> <div class="container">
<div class="footer-content"> <div class="footer-content">
<div class="footer-logo"> <div class="footer-logo">
<span class="logo-text">Argobox</span> <span class="logo-text-glow">LaForceIT</span>
<span class="logo-dot">.com</span> <span class="logo-dot-glow">.com</span>
</div> </div>
<div class="footer-links"> <div class="footer-links">

476
script.js Normal file
View File

@ -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);
}
});

View File

@ -213,16 +213,28 @@ section {
align-items: center; align-items: center;
font-weight: 700; font-weight: 700;
font-size: 1.5rem; 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 { .logo-text {
color: var(--accent); margin-right: 4px;
color: inherit; /* inherit gradient */
} }
.logo-dot { .logo-dot {
color: var(--text-primary); color: var(--text-primary);
} }
.nav-menu { .nav-menu {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1495,3 +1507,35 @@ section {
page-break-inside: avoid; 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);
}