diff --git a/index.html b/index.html index 6ce1fc7..a81af8f 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,496 @@ - + - Document + Interstellar Development | Free and Open Source Software + + + + + + + + + + + + + -

THERE IS NOTHING HERE! - WEBSITE OUT OF COMMISSION

- ForgeJo + +
+
+ + + + +
+ +
+
+
+

+ Advancing Free Software Development +

+

+ We develop high-quality free and open source software solutions, + with a focus on gaming and applications that respect user freedom. +

+ +
+
+
+ + +
+
+
+

Our Projects

+

+ High-quality free software solutions built with passion and + dedication +

+
+ + +
+
+ + + + +
+ +
+ +
+ +
+ +
+
+
+
+ + +
+
+
+

Our Team

+

+ A small but dedicated international team committed to free + software +

+
+ +
+
+
+ +
+

Patrick

+
+ Lead Programmer & System Administrator +
+

+ Patrick focuses on backend development and system + administration, managing our infrastructure and core application + logic with expertise in server-side technologies and DevOps. +

+
+ +
+
+ +
+

Sage

+
Frontend Developer & Communications
+

+ Sage focuses on frontend development and is in charge of + communication and UI/UX design, ensuring our projects have + intuitive interfaces and excellent user experiences. +

+
+
+
+
+ + +
+
+
+

About Us

+
+ +
+

+ Interstellar Development is a small international team dedicated + to advancing free and open source software. Our diverse + backgrounds and shared commitment to user freedom drive our + development philosophy. +

+ +

+ We began with a vision to create high-quality free software, + particularly in gaming where such options are often limited. We + recognized that many free software games either lack polish or + simply don't exist, inspiring us to focus on this under-served + area. +

+ +

+ At Interstellar Development, we believe true digital freedom can + only be achieved through free software. All our projects are + released under copyleft licenses, ensuring they remain free for + everyone to use, modify, and distribute. +

+ +

+ While we don't currently accept donations, we welcome feedback and + suggestions. If you wish to support the free software movement, we + encourage donations to the Free Software Foundation, whose work + continues to inspire our journey. +

+
+
+
+ + +
+
+
+

Our Vision

+
+ +
+
+

+ Our mission is to create high-quality free software + alternatives, particularly for games that are unavailable on + free operating systems like GNU/Linux and FreeBSD. We aim to + address gaps in the software ecosystem where free alternatives + are lacking. +

+
+ +
+
+
+ +
+

Software Freedom

+

+ All our projects are released under copyleft licenses to + ensure user freedoms are protected and preserved. +

+
+ +
+
+ +
+

Community Collaboration

+

+ We believe in building software with and for the community, + welcoming contributions and feedback. +

+
+ +
+
+ +
+

Quality Standards

+

+ We strive to create software that matches or exceeds the + quality of proprietary alternatives. +

+
+
+
+
+
+ + +
+
+
+
+
0
+
Projects
+
+
+
0
+
Team Members
+
+
+
0
+
Years Active
+
+
+
0
+
% Free Software
+
+
+
+
+ + +
+
+
+

Get In Touch

+

+ Have questions about our projects or want to contribute? We'd love + to hear from you. +

+
+ +
+
+
+
+
+ +
+ +
+ +
+
+ +
+
+

Source Code

+ View Our Projects +
+
+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+ + + +
+ + + + + + + + + + + + diff --git a/src/favicon/android-chrome-192x192.png b/src/favicon/android-chrome-192x192.png new file mode 100644 index 0000000..e636383 Binary files /dev/null and b/src/favicon/android-chrome-192x192.png differ diff --git a/src/favicon/android-chrome-512x512.png b/src/favicon/android-chrome-512x512.png new file mode 100644 index 0000000..65f6ef4 Binary files /dev/null and b/src/favicon/android-chrome-512x512.png differ diff --git a/src/favicon/apple-touch-icon.png b/src/favicon/apple-touch-icon.png new file mode 100644 index 0000000..42de221 Binary files /dev/null and b/src/favicon/apple-touch-icon.png differ diff --git a/src/favicon/favicon-16x16.png b/src/favicon/favicon-16x16.png new file mode 100644 index 0000000..e736fae Binary files /dev/null and b/src/favicon/favicon-16x16.png differ diff --git a/src/favicon/favicon-32x32.png b/src/favicon/favicon-32x32.png new file mode 100644 index 0000000..74501cd Binary files /dev/null and b/src/favicon/favicon-32x32.png differ diff --git a/src/favicon/favicon.ico b/src/favicon/favicon.ico new file mode 100644 index 0000000..4de4913 Binary files /dev/null and b/src/favicon/favicon.ico differ diff --git a/src/images/Patrick.png b/src/images/Patrick.png new file mode 100644 index 0000000..6aa6377 Binary files /dev/null and b/src/images/Patrick.png differ diff --git a/src/images/sage.png b/src/images/sage.png new file mode 100644 index 0000000..3bc7bd8 Binary files /dev/null and b/src/images/sage.png differ diff --git a/src/js/animation/Meteor.js b/src/js/animation/Meteor.js new file mode 100644 index 0000000..1387e54 --- /dev/null +++ b/src/js/animation/Meteor.js @@ -0,0 +1,274 @@ +class Meteor { + constructor(container) { + this.container = container; + this.containerRect = container.getBoundingClientRect(); + + // Smooth movement settings + this.speed = Math.random() * 1.5 + 2; + this.life = 1.0; + this.fadeSpeed = 0.002; + + // Enhanced color schemes for vibrant streaks + this.colorSchemes = [ + // Classic golden-white meteor + { + core: "#ffffff", + mid: "#ffeb99", + outer: "#ff9933", + }, + // Blue-white ice meteor + { + core: "#ffffff", + mid: "#aaddff", + outer: "#4488ff", + }, + // Purple cosmic meteor + { + core: "#ffffff", + mid: "#ddaaff", + outer: "#aa44ff", + }, + // Green-white aurora meteor + { + core: "#ffffff", + mid: "#aaffcc", + outer: "#44ff88", + }, + ]; + + this.colors = + this.colorSchemes[Math.floor(Math.random() * this.colorSchemes.length)]; + + // Start positions + const startSide = Math.random(); + if (startSide < 0.7) { + this.x = Math.random() * this.containerRect.width; + this.y = -100; + this.angle = Math.PI / 2 + (Math.random() - 0.5) * 0.6; + } else if (startSide < 0.85) { + this.x = -100; + this.y = Math.random() * this.containerRect.height * 0.3; + this.angle = Math.PI / 4 + Math.random() * 0.4; + } else { + this.x = this.containerRect.width + 100; + this.y = Math.random() * this.containerRect.height * 0.3; + this.angle = (Math.PI * 3) / 4 + Math.random() * 0.4; + } + + this.dx = Math.cos(this.angle) * this.speed; + this.dy = Math.sin(this.angle) * this.speed; + + // Trail settings + this.trailPoints = []; + this.maxTrailLength = 25; + + this.hasBeenOnScreen = false; + + this.init(); + } + + init() { + // Create SVG for smooth gradient trail + this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + this.svg.style.cssText = ` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow: visible; + z-index: 9; + `; + this.svg.setAttribute("width", this.containerRect.width); + this.svg.setAttribute("height", this.containerRect.height); + + // Create gradient for trail + const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); + const gradient = document.createElementNS( + "http://www.w3.org/2000/svg", + "linearGradient" + ); + this.gradientId = `meteor-gradient-${Date.now()}-${Math.random() + .toString(36) + .substr(2, 9)}`; + gradient.setAttribute("id", this.gradientId); + gradient.setAttribute("gradientUnits", "userSpaceOnUse"); + + // Gradient stops for smooth colored fade + const stops = [ + { offset: "0%", color: this.colors.core, opacity: "1" }, + { offset: "30%", color: this.colors.mid, opacity: "0.8" }, + { offset: "60%", color: this.colors.outer, opacity: "0.5" }, + { offset: "100%", color: this.colors.outer, opacity: "0" }, + ]; + + stops.forEach((stop) => { + const stopEl = document.createElementNS( + "http://www.w3.org/2000/svg", + "stop" + ); + stopEl.setAttribute("offset", stop.offset); + stopEl.setAttribute("stop-color", stop.color); + stopEl.setAttribute("stop-opacity", stop.opacity); + gradient.appendChild(stopEl); + }); + + defs.appendChild(gradient); + this.svg.appendChild(defs); + + // Create path for trail + this.trailPath = document.createElementNS( + "http://www.w3.org/2000/svg", + "path" + ); + this.trailPath.setAttribute("fill", "none"); + this.trailPath.setAttribute("stroke", `url(#${this.gradientId})`); + this.trailPath.setAttribute("stroke-linecap", "round"); + this.trailPath.setAttribute("stroke-linejoin", "round"); + + const headSize = Math.random() * 2 + 2.5; + this.headSize = headSize; + this.trailPath.setAttribute("stroke-width", headSize * 2); + this.trailPath.style.filter = `blur(${headSize * 0.4}px)`; + + this.svg.appendChild(this.trailPath); + this.container.appendChild(this.svg); + + // Meteor head - use absolute positioning like the SVG + this.head = document.createElement("div"); + this.head.style.cssText = ` + position: absolute; + width: ${headSize}px; + height: ${headSize}px; + border-radius: 50%; + background: ${this.colors.core}; + box-shadow: 0 0 ${headSize * 3}px ${this.colors.mid}, + 0 0 ${headSize * 5}px ${this.colors.outer}; + pointer-events: none; + z-index: 10; + transform: translate(-50%, -50%); + `; + + this.container.appendChild(this.head); + + // Initialize trail + for (let i = 0; i < 3; i++) { + this.trailPoints.push({ x: this.x, y: this.y }); + } + } + + isOnScreen() { + const buffer = 50; + return ( + this.x > -buffer && + this.x < this.containerRect.width + buffer && + this.y > -buffer && + this.y < this.containerRect.height + buffer + ); + } + + isOffScreen() { + const buffer = 200; + return ( + this.x < -buffer || + this.x > this.containerRect.width + buffer || + this.y < -buffer || + this.y > this.containerRect.height + buffer + ); + } + + updateTrail() { + // Add current position to trail + this.trailPoints.unshift({ x: this.x, y: this.y }); + + // Limit trail length + if (this.trailPoints.length > this.maxTrailLength) { + this.trailPoints.pop(); + } + + // Update SVG path with smooth curves + if (this.trailPoints.length >= 2) { + let pathData = `M ${this.trailPoints[0].x} ${this.trailPoints[0].y}`; + + // Use smooth curves + for (let i = 1; i < this.trailPoints.length - 1; i++) { + const curr = this.trailPoints[i]; + const next = this.trailPoints[i + 1]; + const midX = (curr.x + next.x) / 2; + const midY = (curr.y + next.y) / 2; + pathData += ` Q ${curr.x} ${curr.y} ${midX} ${midY}`; + } + + // Final point + if (this.trailPoints.length > 1) { + const last = this.trailPoints[this.trailPoints.length - 1]; + pathData += ` L ${last.x} ${last.y}`; + } + + this.trailPath.setAttribute("d", pathData); + + // Update gradient position for directional effect + const gradient = document.getElementById(this.gradientId); + if (gradient) { + gradient.setAttribute("x1", this.x); + gradient.setAttribute("y1", this.y); + const lastPoint = this.trailPoints[this.trailPoints.length - 1]; + gradient.setAttribute("x2", lastPoint.x); + gradient.setAttribute("y2", lastPoint.y); + } + } + } + + update(deltaTime) { + // Use deltaTime for consistent movement + const dt = Math.min(deltaTime / 16.67, 2); + + // Update position + this.x += this.dx * dt; + this.y += this.dy * dt; + + // Track screen presence + if (!this.hasBeenOnScreen && this.isOnScreen()) { + this.hasBeenOnScreen = true; + } + + // Fade when off screen + if (this.hasBeenOnScreen && this.isOffScreen()) { + this.life -= this.fadeSpeed * 3 * dt; + } + + // Update head position using left/top (same coordinate system as SVG) + this.head.style.left = `${this.x}px`; + this.head.style.top = `${this.y}px`; + this.head.style.opacity = this.life; + + // Update SVG trail opacity + this.svg.style.opacity = this.life; + + // Update trail + this.updateTrail(); + + // Check if should be removed + if ( + this.life <= 0 || + (this.hasBeenOnScreen && this.isOffScreen() && this.life < 0.3) + ) { + this.remove(); + return false; + } + + return true; + } + + remove() { + if (this.svg && this.svg.parentNode) { + this.svg.remove(); + } + if (this.head && this.head.parentNode) { + this.head.remove(); + } + } +} + +export default Meteor; diff --git a/src/js/animation/Star.js b/src/js/animation/Star.js new file mode 100644 index 0000000..53b21f2 --- /dev/null +++ b/src/js/animation/Star.js @@ -0,0 +1,63 @@ +class Star { + constructor(container) { + this.element = document.createElement("div"); + this.element.classList.add("star"); + this.container = container; + this.speed = Math.random() * 0.5 + 0.1; + + // Random direction angle (0 to 360 degrees) + this.angle = Math.random() * Math.PI * 2; + this.dx = Math.cos(this.angle) * this.speed; + this.dy = Math.sin(this.angle) * this.speed; + + this.x = Math.random() * 100; + this.y = Math.random() * 100; + + this.color = this.getRandomStarColor(); + this.init(); + } + + getRandomStarColor() { + const colors = [ + "rgba(255, 255, 255, 0.9)", // Pure white + "rgba(255, 250, 200, 0.9)", // Warm white + "rgba(200, 220, 255, 0.9)", // Cool blue-white + "rgba(255, 220, 180, 0.9)", // Yellow-white + "rgba(180, 200, 255, 0.9)", // Blue-white + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + init() { + const size = Math.random() * 3 + 0.5; + this.element.style.width = `${size}px`; + this.element.style.height = `${size}px`; + this.element.style.left = `${this.x}%`; + this.element.style.top = `${this.y}%`; + this.element.style.background = this.color; + this.element.style.boxShadow = `0 0 ${size * 2}px ${this.color}`; + this.element.style.animationDelay = `${Math.random() * 5}s`; + this.element.style.animationDuration = `${3 + Math.random() * 4}s`; + this.container.appendChild(this.element); + } + + update() { + this.x += this.dx * 0.05; + this.y += this.dy * 0.05; + + // Wrap around when star goes off screen + if (this.y > 100) this.y = 0; + if (this.y < 0) this.y = 100; + if (this.x > 100) this.x = 0; + if (this.x < 0) this.x = 100; + + this.element.style.left = `${this.x}%`; + this.element.style.top = `${this.y}%`; + } + + remove() { + this.element.remove(); + } +} + +export default Star; diff --git a/src/js/animation/starBackground.js b/src/js/animation/starBackground.js new file mode 100644 index 0000000..83611a0 --- /dev/null +++ b/src/js/animation/starBackground.js @@ -0,0 +1,161 @@ +import Star from "./Star.js"; +import Meteor from "./Meteor.js"; + +const starInstances = []; +const meteorInstances = []; +let lastTime = performance.now(); +let isTabVisible = true; +let animationFrameId = null; + +// Track tab visibility to prevent spawning when hidden +document.addEventListener("visibilitychange", () => { + isTabVisible = !document.hidden; + + // If tab becomes visible after being hidden, clean up excess meteors + if (isTabVisible && meteorInstances.length > 3) { + // Remove excess meteors + const excess = meteorInstances.length - 3; + for (let i = 0; i < excess; i++) { + if (meteorInstances[i]) { + meteorInstances[i].remove(); + } + } + meteorInstances.splice(0, excess); + } + + // Resume animation loop if it stopped + if (isTabVisible && !animationFrameId) { + lastTime = performance.now(); + animate(); + } +}); + +function createStars() { + const stars = document.getElementById("stars"); + const count = 400; + stars.innerHTML = ""; + starInstances.length = 0; + + for (let i = 0; i < count; i++) { + const star = new Star(stars); + starInstances.push(star); + } +} + +function injectStarCSS() { + const style = document.createElement("style"); + style.textContent = ` + .star { + position: absolute; + border-radius: 50%; + animation: twinkle ease-in-out infinite; + } + + @keyframes twinkle { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } + } + `; + document.head.appendChild(style); +} + +function trySpawnMeteor() { + // Only spawn if tab is visible and not too many meteors + if (!isTabVisible || meteorInstances.length >= 2) { + return; + } + + const spawnChance = 0.15; + if (Math.random() < spawnChance) { + const stars = document.getElementById("stars"); + const newMeteor = new Meteor(stars); + meteorInstances.push(newMeteor); + } +} + +function animate() { + const currentTime = performance.now(); + const deltaTime = currentTime - lastTime; + lastTime = currentTime; + + // Cap deltaTime to prevent huge jumps when tab is hidden + const cappedDelta = Math.min(deltaTime, 100); + + // Update stars with delta time + starInstances.forEach((star) => star.update()); + + // Update meteors and remove dead ones + for (let i = meteorInstances.length - 1; i >= 0; i--) { + if (!meteorInstances[i].update(cappedDelta)) { + meteorInstances.splice(i, 1); + } + } + + animationFrameId = requestAnimationFrame(animate); +} + +function createMeteorBurst() { + // Only burst if tab is visible + if (!isTabVisible) return; + + // Limit burst size + const burstSize = Math.min(2, 3 - meteorInstances.length); + + for (let i = 0; i < burstSize; i++) { + setTimeout(() => { + if (isTabVisible && meteorInstances.length < 3) { + const stars = document.getElementById("stars"); + const newMeteor = new Meteor(stars); + meteorInstances.push(newMeteor); + } + }, i * 300); + } +} + +let spawnInterval = null; +let burstInterval = null; + +function init() { + injectStarCSS(); + createStars(); + lastTime = performance.now(); + animate(); + + // Longer interval for spawning + spawnInterval = setInterval(() => { + if (isTabVisible) { + trySpawnMeteor(); + } + }, 3000); // Every 3 seconds instead of 2 + + // Longer interval for bursts + burstInterval = setInterval(() => { + if (isTabVisible) { + createMeteorBurst(); + } + }, 20000); // Every 20 seconds instead of 15 +} + +// Cleanup function +function cleanup() { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + } + if (spawnInterval) { + clearInterval(spawnInterval); + } + if (burstInterval) { + clearInterval(burstInterval); + } +} + +// Handle page unload +window.addEventListener("beforeunload", cleanup); + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} + +export { cleanup }; diff --git a/src/js/form.js b/src/js/form.js new file mode 100644 index 0000000..4dbfa2e --- /dev/null +++ b/src/js/form.js @@ -0,0 +1,22 @@ +// Form submission +function setupForm() { + const form = document.getElementById("contact-form"); + if (form) { + form.addEventListener("submit", function (e) { + e.preventDefault(); + + // Simple form validation + const name = document.getElementById("name").value; + const email = document.getElementById("email").value; + const message = document.getElementById("message").value; + + if (name && email && message) { + // In a real implementation, you would send the form data to a server + alert("Thank you for your message! We will get back to you soon."); + form.reset(); + } else { + alert("Please fill in all required fields."); + } + }); + } +} diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..bd2f14a --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,27 @@ +// Initialize everything when DOM is loaded +document.addEventListener("DOMContentLoaded", function () { + // Initialize components + if (typeof createStars === "function") createStars(); + if (typeof updateNavigation === "function") updateNavigation(); + if (typeof setupForm === "function") setupForm(); + if (typeof setupMobileNavigation === "function") setupMobileNavigation(); + + // Set up event listeners + window.addEventListener("scroll", updateNavigation); + + // Smooth scrolling for navigation links + document.querySelectorAll('a[href^="#"]').forEach((anchor) => { + anchor.addEventListener("click", function (e) { + e.preventDefault(); + const targetId = this.getAttribute("href"); + if (targetId === "#") return; + + const targetElement = document.querySelector(targetId); + if (targetElement) { + targetElement.scrollIntoView({ + behavior: "smooth", + }); + } + }); + }); +}); diff --git a/src/js/navigation.js b/src/js/navigation.js new file mode 100644 index 0000000..974476d --- /dev/null +++ b/src/js/navigation.js @@ -0,0 +1,106 @@ +// Navigation JavaScript +let lastScrollY = window.scrollY; +let ticking = false; + +function updateNavigation() { + const navigation = document.getElementById("navigation"); + const scrolled = window.scrollY > 50; + + if (scrolled) { + navigation.classList.add("scrolled"); + + // Hide/show nav on scroll + if (window.scrollY > lastScrollY && window.scrollY > 100) { + navigation.classList.add("hidden"); + } else { + navigation.classList.remove("hidden"); + } + } else { + navigation.classList.remove("scrolled", "hidden"); + } + + lastScrollY = window.scrollY; + ticking = false; +} + +function requestTick() { + if (!ticking) { + requestAnimationFrame(updateNavigation); + ticking = true; + } +} + +// Mobile navigation functionality +function setupMobileNavigation() { + const burgerMenu = document.getElementById("burger-menu"); + const mainNav = document.getElementById("main-nav"); + const body = document.body; + + // Create overlay for mobile menu + const overlay = document.createElement("div"); + overlay.className = "nav-overlay"; + document.body.appendChild(overlay); + + if (burgerMenu && mainNav) { + burgerMenu.addEventListener("click", function () { + const isActive = mainNav.classList.contains("active"); + + if (isActive) { + // Close menu + mainNav.classList.remove("active"); + burgerMenu.classList.remove("active"); + overlay.classList.remove("active"); + body.classList.remove("nav-open"); + } else { + // Open menu + mainNav.classList.add("active"); + burgerMenu.classList.add("active"); + overlay.classList.add("active"); + body.classList.add("nav-open"); + } + }); + + // Close menu when clicking on overlay + overlay.addEventListener("click", function () { + mainNav.classList.remove("active"); + burgerMenu.classList.remove("active"); + overlay.classList.remove("active"); + body.classList.remove("nav-open"); + }); + + // Close menu when clicking on a link + const navLinks = mainNav.querySelectorAll("a"); + navLinks.forEach((link) => { + link.addEventListener("click", function () { + mainNav.classList.remove("active"); + burgerMenu.classList.remove("active"); + overlay.classList.remove("active"); + body.classList.remove("nav-open"); + + // Update active state + navLinks.forEach((l) => l.classList.remove("active")); + this.classList.add("active"); + }); + }); + + // Set initial active state based on current hash + function setActiveNavLink() { + const currentHash = window.location.hash || "#home"; + navLinks.forEach((link) => { + if (link.getAttribute("href") === currentHash) { + link.classList.add("active"); + } else { + link.classList.remove("active"); + } + }); + } + + // Update active state on hash change + window.addEventListener("hashchange", setActiveNavLink); + setActiveNavLink(); + } +} + +// Initialize navigation +window.addEventListener("scroll", requestTick); +window.addEventListener("load", updateNavigation); diff --git a/src/js/overview.js b/src/js/overview.js new file mode 100644 index 0000000..9dd7bf2 --- /dev/null +++ b/src/js/overview.js @@ -0,0 +1,57 @@ +// Counter animation for stats +document.addEventListener("DOMContentLoaded", function () { + const counters = document.querySelectorAll(".stat-number"); + let hasCounted = false; + + function animateCounters() { + if (hasCounted) return; + + counters.forEach((counter) => { + const target = parseInt(counter.getAttribute("data-count")); + const duration = 2000; // 2 seconds + const frameDuration = 1000 / 60; // 60 frames per second + const totalFrames = Math.round(duration / frameDuration); + let frame = 0; + + const counterInterval = setInterval(() => { + frame++; + const progress = frame / totalFrames; + const currentCount = Math.round(target * progress); + + counter.textContent = currentCount; + + if (frame === totalFrames) { + clearInterval(counterInterval); + } + }, frameDuration); + + counter.classList.add("animated"); + }); + + hasCounted = true; + } + + // Check if element is in viewport + function isInViewport(element) { + const rect = element.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + + // Check on scroll and on load + function checkCounters() { + const statsContainer = document.querySelector(".stats-container"); + if (statsContainer && isInViewport(statsContainer)) { + animateCounters(); + window.removeEventListener("scroll", checkCounters); + } + } + + window.addEventListener("scroll", checkCounters); + checkCounters(); // Check on page load +}); diff --git a/src/js/portfolioCard.js b/src/js/portfolioCard.js new file mode 100644 index 0000000..2fd2d79 --- /dev/null +++ b/src/js/portfolioCard.js @@ -0,0 +1,228 @@ +class PortfolioCard extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: "open" }); + + const icon = this.getAttribute("icon") || "fas fa-code"; + const title = this.getAttribute("title") || "Project Title"; + const desc = this.getAttribute("desc") || "Project description goes here."; + const tags = (this.getAttribute("tags") || "").split(","); + const link = this.getAttribute("link") || "#"; + const collaboration = this.getAttribute("collaboration") || ""; + + shadow.innerHTML = ` + + + + + + + +
+
+ +
+
+

+ ${title} +

+

${desc}

+ ${ + collaboration + ? ` +
+ ${collaboration} +
+ ` + : "" + } +
+ ${tags + .map((tag) => `${tag.trim()}`) + .join("")} +
+
+
+ + Explore Project + +
+
+ `; + } +} + +customElements.define("portfolio-card", PortfolioCard); diff --git a/src/js/portfolioFilter.js b/src/js/portfolioFilter.js new file mode 100644 index 0000000..7779c87 --- /dev/null +++ b/src/js/portfolioFilter.js @@ -0,0 +1,95 @@ +// Portfolio filtering functionality +document.addEventListener("DOMContentLoaded", function () { + // Get all portfolio items + const portfolioItems = document.querySelectorAll("portfolio-card"); + const filterButtons = document.querySelectorAll(".filter-btn"); + const searchInput = document.getElementById("project-search"); + + // Create a container for no results message + const noResults = document.createElement("div"); + noResults.className = "no-results"; + noResults.innerHTML = ` + +

No projects found

+

Try adjusting your search or filter criteria

+ `; + + // Function to filter portfolio items + function filterPortfolio() { + const activeFilter = + document.querySelector(".filter-btn.active").dataset.filter; + const searchTerm = searchInput.value.toLowerCase(); + let visibleItems = 0; + + portfolioItems.forEach((item) => { + const tags = item.getAttribute("tags").toLowerCase(); + const title = item.getAttribute("title").toLowerCase(); + const desc = item.getAttribute("desc").toLowerCase(); + + // Check if item matches the active filter + const matchesFilter = + activeFilter === "all" || + tags.includes(activeFilter) || + title.includes(activeFilter) || + desc.includes(activeFilter); + + // Check if item matches the search term + const matchesSearch = + searchTerm === "" || + title.includes(searchTerm) || + desc.includes(searchTerm) || + tags.includes(searchTerm); + + // Show or hide the item based on filters + if (matchesFilter && matchesSearch) { + item.style.display = "block"; + visibleItems++; + + // Add animation for appearing items + item.style.animation = "fadeInUp 0.5s ease forwards"; + } else { + item.style.display = "none"; + } + }); + + // Show no results message if needed + const portfolioGrid = document.querySelector(".portfolio-grid"); + const existingNoResults = portfolioGrid.querySelector(".no-results"); + + if (visibleItems === 0) { + if (!existingNoResults) { + portfolioGrid.appendChild(noResults); + } + } else if (existingNoResults) { + portfolioGrid.removeChild(existingNoResults); + } + } + + // Add click event listeners to filter buttons + filterButtons.forEach((button) => { + button.addEventListener("click", function () { + // Remove active class from all buttons + filterButtons.forEach((btn) => btn.classList.remove("active")); + + // Add active class to clicked button + this.classList.add("active"); + + // Filter portfolio items + filterPortfolio(); + }); + }); + + // Add input event listener to search field + searchInput.addEventListener("input", filterPortfolio); + + // Add keyboard shortcut for search (Ctrl/Cmd + F) + document.addEventListener("keydown", function (e) { + if ((e.ctrlKey || e.metaKey) && e.key === "f") { + e.preventDefault(); + searchInput.focus(); + } + }); + + // Initialize filter on page load + filterPortfolio(); +}); diff --git a/src/js/sectionTracker.js b/src/js/sectionTracker.js new file mode 100644 index 0000000..e392724 --- /dev/null +++ b/src/js/sectionTracker.js @@ -0,0 +1,111 @@ +// Section tracking and navigation highlighting +document.addEventListener("DOMContentLoaded", function () { + const sections = document.querySelectorAll("section[id]"); + const navLinks = document.querySelectorAll(".nav-links a"); + + // Configuration + const offset = 100; // Offset for when section becomes "active" (accounts for fixed nav) + let isScrolling = false; + + function getCurrentSection() { + let currentSection = ""; + const scrollPosition = window.scrollY + offset; + + // Find which section we're currently in + sections.forEach((section) => { + const sectionTop = section.offsetTop; + const sectionHeight = section.offsetHeight; + const sectionId = section.getAttribute("id"); + + if ( + scrollPosition >= sectionTop && + scrollPosition < sectionTop + sectionHeight + ) { + currentSection = sectionId; + } + }); + + // Special case: if we're at the very top, activate the first section + if (window.scrollY < 100) { + currentSection = sections[0]?.getAttribute("id") || ""; + } + + return currentSection; + } + + function updateActiveNavLink() { + const currentSection = getCurrentSection(); + + navLinks.forEach((link) => { + const href = link.getAttribute("href"); + + // Remove active class from all links + link.classList.remove("active"); + + // Add active class to matching link + if (href === `#${currentSection}`) { + link.classList.add("active"); + } + }); + } + + // Throttle scroll events for better performance + function handleScroll() { + if (!isScrolling) { + window.requestAnimationFrame(() => { + updateActiveNavLink(); + isScrolling = false; + }); + isScrolling = true; + } + } + + // Listen for scroll events + window.addEventListener("scroll", handleScroll); + + // Update on page load + updateActiveNavLink(); + + // Also update when clicking nav links (for smooth scroll) + navLinks.forEach((link) => { + link.addEventListener("click", function (e) { + // Remove active from all + navLinks.forEach((l) => l.classList.remove("active")); + // Add active to clicked link + this.classList.add("active"); + + // Let the scroll handler update it properly after scroll completes + setTimeout(updateActiveNavLink, 100); + }); + }); + + // Handle hash changes (browser back/forward) + window.addEventListener("hashchange", function () { + setTimeout(updateActiveNavLink, 100); + }); + + // Intersection Observer for more precise tracking (progressive enhancement) + if ("IntersectionObserver" in window) { + const observerOptions = { + rootMargin: "-20% 0px -70% 0px", // Trigger when section is roughly in the middle of viewport + threshold: 0, + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const sectionId = entry.target.getAttribute("id"); + navLinks.forEach((link) => { + link.classList.remove("active"); + if (link.getAttribute("href") === `#${sectionId}`) { + link.classList.add("active"); + } + }); + } + }); + }, observerOptions); + + // Observe all sections + sections.forEach((section) => observer.observe(section)); + } +}); diff --git a/src/styles/styles.css b/src/styles/styles.css new file mode 100644 index 0000000..3229eb9 --- /dev/null +++ b/src/styles/styles.css @@ -0,0 +1,1943 @@ +/* Modern CSS Reset and Variables */ +:root { + /* Color System */ + --bg-primary: #0a0a0a; + --bg-secondary: #111111; + --bg-tertiary: #1a1a1a; + --bg-card: #161616; + --bg-card-hover: #1e1e1e; + + --text-primary: #ffffff; + --text-secondary: #b3b3b3; + --text-muted: #737373; + + --accent-primary: #00f5ff; + --accent-secondary: #7c3aed; + --accent-gradient: linear-gradient(135deg, #00f5ff, #7c3aed); + --accent-gradient-reverse: linear-gradient(135deg, #7c3aed, #00f5ff); + + --purple: #8b5cf6; + --purple-light: #a78bfa; + --purple-dark: #7c3aed; + --purple-muted: #6d28d9; + --shadow-purple: rgba(139, 92, 246, 0.3); + + --border-subtle: #262626; + --border-accent: #333333; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); + --shadow-glow: 0 0 20px rgba(0, 245, 255, 0.1); + + /* Border Radius */ + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 24px; + + /* Spacing */ + --spacing-xs: 0.5rem; + --spacing-sm: 1rem; + --spacing-md: 1.5rem; + --spacing-lg: 2rem; + --spacing-xl: 3rem; + --spacing-2xl: 4rem; + + /* Typography */ + --font-primary: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; + --font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace; + + /* Z-Index Scale */ + --z-navigation: 1000; + --z-modal: 1100; + --z-tooltip: 1200; +} + +/* Reset */ +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + font-size: 16px; +} + +body { + font-family: var(--font-primary); + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body.nav-open { + overflow: hidden; +} + +/* Links */ +a { + color: inherit; + text-decoration: none; +} + +/* Lists */ +ul, +ol { + list-style: none; +} + +/* Images */ +img { + max-width: 100%; + height: auto; +} + +/* Focus States */ +*:focus { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; +} + +/* Enhanced Background Effects */ +.animated-bg { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -2; + background: radial-gradient( + circle at 20% 50%, + rgba(124, 58, 237, 0.15) 0%, + transparent 50% + ), + radial-gradient( + circle at 80% 20%, + rgba(0, 245, 255, 0.12) 0%, + transparent 50% + ), + radial-gradient( + circle at 40% 80%, + rgba(124, 58, 237, 0.1) 0%, + transparent 50% + ); + animation: gradientShift 15s ease infinite; +} + +@keyframes gradientShift { + 0%, + 100% { + background-position: 0% 50%, 100% 50%, 50% 100%; + } + 50% { + background-position: 100% 50%, 0% 50%, 50% 0%; + } +} + +.stars { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: -1; +} + +.star { + position: absolute; + background: rgba(255, 255, 255, 0.8); + border-radius: 50%; + animation: twinkle 3s infinite ease-in-out; +} + +@keyframes twinkle { + 0%, + 100% { + opacity: 0.3; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.2); + } +} + +/* Enhanced Navigation with Fixed Background Animations */ +nav { + position: fixed; + top: 0; + width: 100%; + background: rgba(10, 10, 10, 0.95); + backdrop-filter: blur(20px) saturate(180%); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + z-index: var(--z-navigation); + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + transform: translateY(0); +} + +/* Hide nav on scroll down, show on scroll up */ +nav.hidden { + transform: translateY(-100%); +} + +nav.scrolled { + background: rgba(10, 10, 10, 0.98); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + border-bottom-color: rgba(124, 58, 237, 0.2); +} + +.nav-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 var(--spacing-lg); + display: flex; + align-items: center; + justify-content: space-between; + height: 70px; +} + +.logo { + font-weight: 800; + font-size: 1.5rem; + background: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + position: relative; + padding: var(--spacing-xs) 0; +} + +.logo::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: var(--accent-gradient); + transition: width 0.3s ease; +} + +.logo:hover::after { + width: 100%; +} + +.nav-links { + display: flex; + gap: var(--spacing-md); + align-items: center; +} + +.nav-links a { + color: var(--text-secondary); + font-weight: 500; + font-size: 0.9rem; + padding: var(--spacing-sm) var(--spacing-md); + border-radius: var(--radius-md); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +/* Fixed background animation - only shows on hover */ +.nav-links a::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--accent-gradient); + opacity: 0; + transition: opacity 0.3s ease; + z-index: -1; + border-radius: var(--radius-md); +} + +.nav-links a:hover::before { + opacity: 0.1; +} + +/* Underline animation */ +.nav-links a::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent-primary); + transition: width 0.3s ease; + border-radius: 2px; +} + +.nav-links a:hover { + color: var(--accent-primary); + transform: translateY(-1px); +} + +.nav-links a:hover::after { + width: 80%; +} + +/* Active state */ +.nav-links a.active { + color: var(--accent-primary); + background: rgba(0, 245, 255, 0.1); +} + +.nav-links a.active::after { + width: 80%; +} + +.nav-links a.active::before { + opacity: 0.1; +} + +/* Mobile menu button */ +.mobile-menu-btn { + display: none; + background: none; + border: none; + color: var(--text-primary); + font-size: 1.5rem; + cursor: pointer; + padding: var(--spacing-sm); + border-radius: var(--radius-md); + transition: all 0.3s ease; + position: relative; + z-index: 1001; +} + +.mobile-menu-btn:hover { + background: rgba(124, 58, 237, 0.1); + color: var(--accent-primary); +} + +.mobile-menu-btn.active { + color: var(--accent-primary); +} + +/* Mobile menu styles */ +@media (max-width: 768px) { + .mobile-menu-btn { + display: block; + } + + .nav-links { + position: fixed; + top: 0; + right: -100%; + width: 280px; + height: 100vh; + background: rgba(10, 10, 10, 0.98); + backdrop-filter: blur(30px); + flex-direction: column; + padding: 80px var(--spacing-lg) var(--spacing-lg); + transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + border-left: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: -10px 0 30px rgba(0, 0, 0, 0.3); + } + + .nav-links.active { + right: 0; + } + + .nav-links a { + width: 100%; + text-align: center; + padding: var(--spacing-md); + font-size: 1.1rem; + border-radius: var(--radius-lg); + margin-bottom: var(--spacing-xs); + } + + .nav-links a::after { + display: none; + } + + .nav-links a::before { + border-radius: var(--radius-lg); + } + + /* Overlay for mobile menu */ + .nav-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); + z-index: 999; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + } + + .nav-overlay.active { + opacity: 1; + visibility: visible; + } +} + +/* Enhanced scroll behavior */ +.nav-scroll-hide { + animation: navHide 0.4s ease forwards; +} + +.nav-scroll-show { + animation: navShow 0.4s ease forwards; +} + +@keyframes navHide { + from { + transform: translateY(0); + } + to { + transform: translateY(-100%); + } +} + +@keyframes navShow { + from { + transform: translateY(-100%); + } + to { + transform: translateY(0); + } +} + +/* Container */ +.container { + max-width: 1400px; + margin: 0 auto; + padding: 0 var(--spacing-lg); +} + +/* Enhanced Hero Section */ +.hero { + min-height: 100vh; + display: flex; + align-items: center; + padding-top: 80px; + position: relative; + overflow: hidden; +} + +.hero::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + background: radial-gradient( + circle at center, + rgba(124, 58, 237, 0.1) 0%, + transparent 70% + ); + transform: translate(-50%, -50%); + animation: pulse 4s ease-in-out infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 0.5; + transform: translate(-50%, -50%) scale(1); + } + 50% { + opacity: 0.8; + transform: translate(-50%, -50%) scale(1.1); + } +} + +.hero-content { + max-width: 800px; + margin: 0 auto; + text-align: center; + padding: var(--spacing-lg) 0; + position: relative; + z-index: 1; +} + +.hero h1 { + font-size: clamp(2.5rem, 6vw, 4rem); + font-weight: 800; + margin-bottom: var(--spacing-md); + line-height: 1.1; + letter-spacing: -0.02em; + animation: slideUp 1s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.hero h1 .highlight { + background: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + position: relative; + display: inline-block; +} + +.hero h1 .highlight::after { + content: ""; + position: absolute; + bottom: -5px; + left: 0; + width: 100%; + height: 3px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + animation: expandLine 1s ease-out 0.5s forwards; +} + +@keyframes expandLine { + to { + transform: scaleX(1); + } +} + +.hero p { + font-size: 1.25rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-xl); + max-width: 600px; + margin-left: auto; + margin-right: auto; + line-height: 1.6; + animation: slideUp 1s ease-out 0.2s both; +} + +.hero-buttons { + display: flex; + gap: var(--spacing-sm); + justify-content: center; + flex-wrap: wrap; + animation: slideUp 1s ease-out 0.4s both; +} + +/* Enhanced Buttons */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: 0.875rem var(--spacing-lg); + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.9rem; + text-decoration: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: none; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.btn::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.2), + transparent + ); + transition: left 0.5s ease; +} + +.btn:hover::before { + left: 100%; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: var(--accent-gradient); + color: var(--bg-primary); + box-shadow: var(--shadow-glow); +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-3px); + box-shadow: 0 15px 30px rgba(0, 245, 255, 0.4); +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 2px solid var(--border-accent); + position: relative; + overflow: hidden; +} + +.btn-secondary::after { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: var(--accent-gradient); + opacity: 0.1; + transition: left 0.3s ease; + z-index: -1; +} + +.btn-secondary:hover:not(:disabled) { + background: var(--bg-card); + border-color: var(--accent-primary); + transform: translateY(-3px); +} + +.btn-secondary:hover::after { + left: 0; +} + +/* Enhanced Sections */ +.section { + padding: 8rem 0; + position: relative; +} + +.section::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 180deg, + transparent 0%, + rgba(124, 58, 237, 0.03) 50%, + transparent 100% + ); + pointer-events: none; +} + +.section-header { + text-align: center; + margin-bottom: var(--spacing-2xl); + position: relative; +} + +.section-title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: var(--spacing-sm); + letter-spacing: -0.02em; + position: relative; + display: inline-block; +} + +.section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 3px; + background: var(--accent-gradient); + border-radius: 2px; +} + +.section-subtitle { + font-size: 1.125rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto; + line-height: 1.6; +} + +/* Enhanced Portfolio Controls */ +.portfolio-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-xl); + flex-wrap: wrap; + gap: var(--spacing-md); +} + +.filter-buttons { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.filter-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + color: var(--text-secondary); + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.filter-btn::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: var(--accent-gradient); + transition: left 0.3s ease; + z-index: -1; +} + +.filter-btn:hover { + background: var(--bg-card-hover); + color: var(--text-primary); + transform: translateY(-2px); + border-color: var(--accent-primary); +} + +.filter-btn.active { + background: var(--accent-gradient); + color: var(--bg-primary); + border-color: transparent; + box-shadow: var(--shadow-glow); +} + +.search-box { + position: relative; + max-width: 300px; + width: 100%; +} + +.search-box input { + width: 100%; + padding: 0.75rem 1rem 0.75rem 2.5rem; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: 0.95rem; + transition: all 0.3s ease; +} + +.search-box input:focus { + outline: none; + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px rgba(0, 245, 255, 0.1); + background: var(--bg-card-hover); +} + +.search-box i { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); + transition: color 0.3s ease; +} + +.search-box input:focus + i { + color: var(--accent-primary); +} + +/* Enhanced Portfolio Grid */ +.portfolio-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: var(--spacing-lg); +} + +portfolio-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + position: relative; + display: block; + opacity: 0; + transform: translateY(30px); + animation: fadeInUp 0.6s ease forwards; +} + +@keyframes fadeInUp { + to { + opacity: 1; + transform: translateY(0); + } +} + +portfolio-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 3px; + background: var(--accent-gradient); + transform: scaleX(0); + transform-origin: left; + transition: transform 0.3s ease; +} + +portfolio-card:hover::before { + transform: scaleX(1); +} + +portfolio-card:hover { + transform: translateY(-10px) scale(1.02); + background: var(--bg-card-hover); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 30px rgba(0, 245, 255, 0.1); + border-color: var(--border-accent); +} + +.portfolio-img { + display: flex; + align-items: center; + justify-content: center; + height: 120px; + background: var(--accent-gradient); + font-size: 2.5rem; + color: #ffffff; + position: relative; + overflow: hidden; +} + +.portfolio-img::after { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.2), + transparent + ); + transition: left 0.5s ease; +} + +portfolio-card:hover .portfolio-img::after { + left: 100%; +} + +.portfolio-content { + padding: var(--spacing-lg); +} + +.portfolio-title { + display: flex; + align-items: center; + gap: var(--spacing-sm); + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--spacing-sm); + color: var(--text-primary); +} + +.portfolio-desc { + color: var(--text-secondary); + margin-bottom: var(--spacing-md); + line-height: 1.6; +} + +.portfolio-tags { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs); + margin-bottom: var(--spacing-md); +} + +.tag { + padding: 0.25rem 0.75rem; + background: rgba(0, 245, 255, 0.1); + color: var(--accent-primary); + border-radius: var(--radius-sm); + font-size: 0.8rem; + font-weight: 500; + font-family: var(--font-mono); + transition: all 0.3s ease; +} + +.tag:hover { + background: rgba(0, 245, 255, 0.2); + transform: translateY(-1px); +} + +/* Enhanced Portfolio Button */ +.portfolio-btn-container { + margin-top: auto; + padding: 0 1.5rem 1.5rem; +} + +.portfolio-btn { + width: 100%; + position: relative; + overflow: hidden; + z-index: 1; + padding: 1rem 1.8rem; + border-radius: 8px; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; + font-size: 0.9rem; + transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1); + border: 2px solid transparent; + background: linear-gradient(135deg, var(--purple), var(--purple-dark)); + color: white; + box-shadow: 0 4px 15px var(--shadow-purple); + display: flex; + justify-content: center; + align-items: center; + gap: 0.8rem; + text-decoration: none; + text-align: center; +} + +.portfolio-btn::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, var(--purple-dark), var(--purple-muted)); + z-index: -1; + transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); + transform: scaleX(0); + transform-origin: right; +} + +.portfolio-btn:hover::before { + transform: scaleX(1); + transform-origin: left; +} + +.portfolio-btn:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(168, 85, 247, 0.4); + border-color: var(--purple-light); +} + +.portfolio-btn:active { + transform: translateY(0); + box-shadow: 0 4px 15px var(--shadow-purple); +} + +.portfolio-btn i { + font-size: 1rem; + transition: transform 0.3s ease; +} + +.portfolio-btn:hover i { + transform: translateX(4px); +} + +.portfolio-btn::after { + content: ""; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient( + to bottom right, + rgba(255, 255, 255, 0.2), + rgba(255, 255, 255, 0.1) 20%, + rgba(255, 255, 255, 0) 50%, + rgba(255, 255, 255, 0) 100% + ); + transform: rotate(30deg) translateY(-150%); + transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); +} + +.portfolio-btn:hover::after { + transform: rotate(30deg) translateY(150%); +} + +.portfolio-btn:focus { + outline: 2px solid var(--purple-light); + outline-offset: 2px; +} + +/* Enhanced Collaboration Badge */ +.collaboration-badge { + display: inline-flex; + align-items: center; + gap: 0.4rem; + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: white; + padding: 0.3rem 0.8rem; + border-radius: 20px; + font-size: 0.75rem; + margin-top: 0.5rem; + margin-bottom: 1rem; + animation: pulse 2s infinite; + position: relative; + overflow: hidden; +} + +.collaboration-badge::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent + ); + transition: left 0.5s ease; +} + +.collaboration-badge:hover::before { + left: 100%; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); + } + 70% { + box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); + } +} + +/* Enhanced Stats Section */ +.stats-section { + background: var(--bg-secondary); + padding: var(--spacing-2xl) 0; + position: relative; + overflow: hidden; +} + +.stats-section::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 45deg, + transparent 30%, + rgba(124, 58, 237, 0.05) 50%, + transparent 70% + ); + animation: shimmer 3s ease-in-out infinite; +} + +@keyframes shimmer { + 0%, + 100% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } +} + +.stats-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--spacing-lg); + text-align: center; + position: relative; + z-index: 1; +} + +.stat-item { + padding: var(--spacing-lg); + position: relative; +} + +.stat-item::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--accent-gradient); + opacity: 0; + border-radius: var(--radius-lg); + transition: opacity 0.3s ease; + z-index: -1; +} + +.stat-item:hover::before { + opacity: 0.05; +} + +.stat-number { + font-size: 3rem; + font-weight: 800; + background: var(--accent-gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: var(--spacing-sm); +} + +.stat-label { + color: var(--text-secondary); + font-weight: 500; + font-size: 1.1rem; +} + +/* Enhanced Team Section */ +.team-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--spacing-lg); + margin-top: var(--spacing-lg); +} + +.team-member { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + text-align: center; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + position: relative; + overflow: hidden; +} + +.team-member::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: var(--accent-gradient); + opacity: 0.05; + transition: left 0.3s ease; + z-index: -1; +} + +.team-member:hover::before { + left: 0; +} + +.team-member:hover { + transform: translateY(-8px) scale(1.02); + background: var(--bg-card-hover); + box-shadow: var(--shadow-xl); + border-color: var(--border-accent); +} + +.member-avatar { + width: 120px; + height: 120px; + background: var(--accent-gradient); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; + color: #ffffff; + margin: 0 auto var(--spacing-md); + position: relative; + transition: all 0.3s ease; +} + +.team-member:hover .member-avatar { + transform: scale(1.1) rotate(5deg); +} + +.member-avatar::after { + content: ""; + position: absolute; + inset: 3px; + background: var(--bg-card); + border-radius: 50%; + z-index: 1; +} + +.member-avatar i { + z-index: 2; + position: relative; +} + +.team-member h3 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--spacing-xs); +} + +.team-role { + color: var(--accent-primary); + font-weight: 500; + margin-bottom: var(--spacing-sm); + font-family: var(--font-mono); + font-size: 0.9rem; +} + +.team-member p { + color: var(--text-secondary); + line-height: 1.6; +} + +/* Enhanced About Section */ +.about-content { + max-width: 800px; + margin: 0 auto; + text-align: center; +} + +.about-content p { + font-size: 1.125rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-lg); + line-height: 1.7; + text-align: left; + position: relative; + padding-left: var(--spacing-lg); +} + +.about-content p::before { + content: ""; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 3px; + background: var(--accent-gradient); + border-radius: 2px; + transform: scaleY(0); + transition: transform 0.3s ease; +} + +.about-content p:hover::before { + transform: scaleY(1); +} + +.about-content p:last-child { + margin-bottom: 0; +} + +/* Enhanced Vision Section */ +.vision-content { + max-width: 1000px; + margin: 0 auto; +} + +.vision-text { + text-align: center; + margin-bottom: var(--spacing-2xl); +} + +.vision-text p { + font-size: 1.125rem; + color: var(--text-secondary); + line-height: 1.7; + max-width: 700px; + margin: 0 auto; +} + +.principles-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--spacing-lg); +} + +.principle-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + text-align: center; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + position: relative; + overflow: hidden; +} + +.principle-card::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: var(--accent-gradient); + opacity: 0.05; + transition: left 0.3s ease; + z-index: -1; +} + +.principle-card:hover::before { + left: 0; +} + +.principle-card:hover { + transform: translateY(-8px) scale(1.02); + background: var(--bg-card-hover); + box-shadow: var(--shadow-lg); + border-color: var(--border-accent); +} + +.principle-icon { + display: flex; + align-items: center; + justify-content: center; + width: 80px; + height: 80px; + background: var(--accent-gradient); + border-radius: 50%; + margin: 0 auto var(--spacing-md); + font-size: 1.8rem; + color: #ffffff; + transition: all 0.3s ease; +} + +.principle-card:hover .principle-icon { + transform: scale(1.1) rotate(10deg); +} + +.principle-card h4 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: var(--spacing-sm); + color: var(--text-primary); +} + +.principle-card p { + color: var(--text-secondary); + line-height: 1.6; +} + +/* Enhanced Contact Section */ +.contact-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-2xl); + max-width: 1000px; + margin: 0 auto; +} + +.contact-methods { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); +} + +.contact-item { + display: flex; + align-items: flex-start; + gap: var(--spacing-sm); + transition: all 0.3s ease; + padding: var(--spacing-md); + border-radius: var(--radius-lg); +} + +.contact-item:hover { + background: var(--bg-card); + transform: translateX(10px); +} + +.contact-icon { + display: flex; + align-items: center; + justify-content: center; + width: 50px; + height: 50px; + background: var(--accent-gradient); + border-radius: var(--radius-md); + font-size: 1.2rem; + color: #ffffff; + flex-shrink: 0; + transition: all 0.3s ease; +} + +.contact-item:hover .contact-icon { + transform: scale(1.1) rotate(5deg); +} + +.contact-details h4 { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: var(--spacing-xs); + color: var(--text-primary); +} + +.contact-details a { + color: var(--text-secondary); + transition: color 0.3s ease; + position: relative; +} + +.contact-details a::after { + content: ""; + position: absolute; + bottom: -2px; + left: 0; + width: 0; + height: 1px; + background: var(--accent-primary); + transition: width 0.3s ease; +} + +.contact-details a:hover { + color: var(--accent-primary); +} + +.contact-details a:hover::after { + width: 100%; +} + +/* Enhanced Contact Form */ +.contact-form { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--spacing-xl); + transition: all 0.3s ease; +} + +.contact-form:hover { + border-color: var(--accent-primary); + box-shadow: var(--shadow-glow); +} + +.form-group { + margin-bottom: var(--spacing-md); + position: relative; +} + +.form-group label { + display: block; + margin-bottom: var(--spacing-xs); + font-weight: 500; + color: var(--text-primary); + font-size: 0.9rem; + transition: color 0.3s ease; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: var(--spacing-sm); + background: var(--bg-tertiary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + color: var(--text-primary); + font-size: 0.95rem; + font-family: var(--font-primary); + transition: all 0.3s ease; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px rgba(0, 245, 255, 0.1); + background: var(--bg-card-hover); +} + +.form-group textarea { + resize: vertical; + min-height: 120px; + font-family: var(--font-primary); +} + +.form-group select { + cursor: pointer; +} + +.contact-form .btn { + width: 100%; + justify-content: center; + margin-top: var(--spacing-sm); +} + +/* Enhanced Footer */ +.footer { + background: var(--bg-secondary); + border-top: 1px solid var(--border-subtle); + padding: var(--spacing-2xl) 0 var(--spacing-lg); + position: relative; +} + +.footer::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 1px; + background: var(--accent-gradient); +} + +.footer-content { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: var(--spacing-xl); + margin-bottom: var(--spacing-xl); +} + +.footer-brand .logo { + font-size: 1.5rem; + margin-bottom: var(--spacing-sm); + display: block; +} + +.footer-brand p { + color: var(--text-secondary); + margin-bottom: var(--spacing-lg); + line-height: 1.6; + max-width: 300px; +} + +.footer-social { + display: flex; + gap: var(--spacing-sm); +} + +.social-link { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + color: var(--text-secondary); + font-size: 1.1rem; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.social-link::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: var(--accent-gradient); + transition: left 0.3s ease; + z-index: -1; +} + +.social-link:hover::before { + left: 0; +} + +.social-link:hover { + background: var(--accent-gradient); + color: #ffffff; + transform: translateY(-3px) scale(1.1); + border-color: transparent; +} + +.footer-links h4 { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--spacing-sm); + position: relative; +} + +.footer-links h4::after { + content: ""; + position: absolute; + bottom: -5px; + left: 0; + width: 30px; + height: 2px; + background: var(--accent-primary); +} + +.footer-links ul { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.footer-links a { + color: var(--text-secondary); + font-size: 0.9rem; + transition: all 0.3s ease; + padding: 0.25rem 0; + position: relative; +} + +.footer-links a::before { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background: var(--accent-primary); + transition: width 0.3s ease; +} + +.footer-links a:hover { + color: var(--accent-primary); + transform: translateX(5px); +} + +.footer-links a:hover::before { + width: 100%; +} + +.footer-bottom { + border-top: 1px solid var(--border-subtle); + padding-top: var(--spacing-lg); +} + +.footer-bottom-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--spacing-sm); +} + +.footer-bottom p { + color: var(--text-muted); + font-size: 0.9rem; +} + +.footer-badges { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.badge { + display: flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + font-size: 0.8rem; + color: var(--text-muted); + transition: all 0.3s ease; +} + +.badge:hover { + background: var(--accent-gradient); + color: var(--bg-primary); + border-color: transparent; + transform: translateY(-2px); +} + +.badge i { + color: var(--accent-primary); +} + +.badge:hover i { + color: var(--bg-primary); +} + +/* Enhanced No Results */ +.no-results { + grid-column: 1 / -1; + text-align: center; + padding: var(--spacing-2xl); + color: var(--text-muted); + position: relative; +} + +.no-results i { + font-size: 3rem; + margin-bottom: var(--spacing-md); + opacity: 0.5; + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-10px); + } + 60% { + transform: translateY(-5px); + } +} + +.no-results h3 { + font-size: 1.5rem; + margin-bottom: var(--spacing-sm); + color: var(--text-secondary); +} + +.no-results p { + color: var(--text-muted); +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .nav-container { + padding: 0 var(--spacing-md); + } + + .container { + padding: 0 var(--spacing-md); + } + + .footer-content { + grid-template-columns: 1fr 1fr; + gap: var(--spacing-lg); + } + + .contact-container { + grid-template-columns: 1fr; + gap: var(--spacing-xl); + } +} + +@media (max-width: 768px) { + .mobile-menu-btn { + display: block; + } + + .nav-links { + position: fixed; + top: 0; + right: -100%; + width: 280px; + height: 100vh; + background: rgba(10, 10, 10, 0.98); + backdrop-filter: blur(30px); + flex-direction: column; + padding: 80px var(--spacing-lg) var(--spacing-lg); + transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + border-left: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: -10px 0 30px rgba(0, 0, 0, 0.3); + } + + .nav-links.active { + right: 0; + } + + .nav-links a { + width: 100%; + text-align: center; + padding: var(--spacing-md); + font-size: 1.1rem; + border-radius: var(--radius-lg); + margin-bottom: var(--spacing-xs); + } + + .nav-links a::after { + display: none; + } + + .nav-links a::before { + border-radius: var(--radius-lg); + } + + .nav-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(5px); + z-index: 999; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + } + + .nav-overlay.active { + opacity: 1; + visibility: visible; + } + + .hero h1 { + font-size: clamp(2rem, 8vw, 3rem); + } + + .hero p { + font-size: 1.1rem; + } + + .hero-buttons { + flex-direction: column; + align-items: center; + } + + .section { + padding: 4rem 0; + } + + .portfolio-controls { + flex-direction: column; + align-items: stretch; + } + + .filter-buttons { + justify-content: center; + } + + .portfolio-grid { + grid-template-columns: 1fr; + } + + .team-grid { + grid-template-columns: 1fr; + } + + .principles-grid { + grid-template-columns: 1fr; + } + + .stats-container { + grid-template-columns: repeat(2, 1fr); + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + gap: var(--spacing-lg); + } + + .footer-brand p { + max-width: none; + } + + .footer-social { + justify-content: center; + } + + .footer-bottom-content { + flex-direction: column; + text-align: center; + gap: var(--spacing-sm); + } + + .portfolio-btn { + padding: 0.9rem 1.5rem; + font-size: 0.85rem; + } +} + +@media (max-width: 480px) { + .container { + padding: 0 var(--spacing-sm); + } + + .nav-container { + padding: 0 var(--spacing-sm); + } + + .hero-content { + padding: var(--spacing-sm) 0; + } + + .project-card, + .team-member, + .principle-card, + .contact-form { + padding: var(--spacing-md); + } + + .section-header { + margin-bottom: var(--spacing-xl); + } + + .contact-form { + padding: var(--spacing-lg); + } + + .stats-container { + grid-template-columns: 1fr; + } +} + +/* Animations and Interactions */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .star { + animation: none; + } +} + +/* Print Styles */ +@media print { + .animated-bg, + .stars, + nav, + .hero-buttons, + .contact-form, + .footer-social { + display: none; + } + + body { + background: white; + color: black; + } + + .section { + padding: 2rem 0; + } +}