Add favicon images, site manifest, and update logo references in HTML and CSS
This commit is contained in:
parent
bc36232b45
commit
236e64a2cc
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"}
|
29
index.html
29
index.html
|
@ -12,13 +12,20 @@
|
||||||
<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">
|
||||||
|
<link rel="stylesheet" href="test-styles.css">
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||||
|
@ -34,7 +41,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">LaForceIT</span>
|
||||||
<span class="logo-dot">.com</span>
|
<span class="logo-dot">.com</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +54,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 +134,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 +363,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 +586,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 +643,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 +658,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>© <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>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -0,0 +1,459 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Simulated form submission - in production replace with actual API call
|
||||||
|
// const formData = new FormData(form);
|
||||||
|
// const formValues = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
|
// Simulated API response delay
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
|
// Success message
|
||||||
|
alert('Thank you for your message! I will get back to you soon.');
|
||||||
|
form.reset();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Failed to send message. Please try again or contact me directly via email.');
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
});
|
23
styles.css
23
styles.css
|
@ -209,20 +209,31 @@ section {
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
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;
|
||||||
|
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;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* Test CSS file to confirm changes are working */
|
||||||
|
|
||||||
|
/* Save this as test-styles.css in your repository root */
|
||||||
|
|
||||||
|
/* Logo Styles - Enhanced Version */
|
||||||
|
.logo {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
background: linear-gradient(90deg, #ff0000 0%, #ff6e00 50%, #ffa500 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
position: relative;
|
||||||
|
text-shadow: 0 0 15px rgba(255, 150, 100, 0.2);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-dot {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #ff6e00;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo with tech icon detail - using different brackets to be obviously visible */
|
||||||
|
.logo a::before {
|
||||||
|
content: "[ ";
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
color: #ff6e00;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo a::after {
|
||||||
|
content: " ]";
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
color: #ff6e00;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
Loading…
Reference in New Issue