Compare commits

..

No commits in common. "95f551288582006afb68de79cc09b1e5c8d6009c" and "44cfcec197bf7c9bfc8c22c3d2c670a67d5a997c" have entirely different histories.

20 changed files with 3575 additions and 4 deletions

View file

@ -1,12 +1,496 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<title>Interstellar Development | Free and Open Source Software</title>
<meta
name="description"
content="We develop high-quality free and open source software, with a focus on gaming solutions that respect user freedom."
/>
<!-- Font Awesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<!-- Google Fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<!-- Main CSS -->
<link rel="stylesheet" href="src/styles/styles.css" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="src/favicon/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="src/favicon/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="src/favicon/favicon-16x16.png"
/>
</head>
<body>
<h1>THERE IS NOTHING HERE! - WEBSITE OUT OF COMMISSION</h1>
<a href="https://interstellardevelopment.org/code/">ForgeJo</a>
<!-- Animated background -->
<div class="animated-bg"></div>
<div class="stars" id="stars"></div>
<!-- Navigation -->
<nav id="navigation">
<div class="nav-container">
<div class="logo">
<a href="#welcome-screen">Interstellar Development</a>
</div>
<button
class="mobile-menu-btn"
id="burger-menu"
aria-label="Toggle navigation"
>
<i class="fas fa-bars"></i>
</button>
<ul class="nav-links" id="main-nav">
<li><a href="#projects">Projects</a></li>
<li><a href="#team">Team</a></li>
<li><a href="#about">About</a></li>
<li><a href="#vision">Vision</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
</nav>
<main>
<!-- Hero Section -->
<section class="hero" id="welcome-screen">
<div class="container">
<div class="hero-content">
<h1>
Advancing <span class="highlight">Free Software</span> Development
</h1>
<p>
We develop high-quality free and open source software solutions,
with a focus on gaming and applications that respect user freedom.
</p>
<div class="hero-buttons">
<a href="#projects" class="btn btn-primary">
<i class="fas fa-rocket"></i>
View Our Projects
</a>
<a href="#about" class="btn btn-secondary">
<i class="fas fa-info-circle"></i>
Learn More
</a>
</div>
</div>
</div>
</section>
<!-- Projects Section -->
<section id="projects" class="section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Projects</h2>
<p class="section-subtitle">
High-quality free software solutions built with passion and
dedication
</p>
</div>
<!-- Portfolio Filter Controls -->
<div class="portfolio-controls">
<div class="filter-buttons">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="gaming">Gaming</button>
<button class="filter-btn" data-filter="software">
Software
</button>
<button class="filter-btn" data-filter="web">Web</button>
</div>
<div class="search-box">
<input
type="text"
id="project-search"
placeholder="Search projects..."
/>
<i class="fas fa-search"></i>
</div>
</div>
<div class="portfolio-grid">
<!-- Gaming Projects -->
<div class="portfolio-grid">
<portfolio-card
icon="fas fa-rocket"
title="Projects Coming Soon"
desc="We're currently working on exciting new free and open source software projects. Stay tuned for updates as we develop high-quality solutions that respect user freedom."
tags="all"
collaboration="Internal Project"
></portfolio-card>
</div>
</div>
</div>
</section>
<!-- Team Section -->
<section id="team" class="section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Team</h2>
<p class="section-subtitle">
A small but dedicated international team committed to free
software
</p>
</div>
<div class="team-grid">
<div class="team-member">
<div class="member-avatar">
<i class="fas fa-server"></i>
</div>
<h3>Patrick</h3>
<div class="team-role">
Lead Programmer & System Administrator
</div>
<p>
Patrick focuses on backend development and system
administration, managing our infrastructure and core application
logic with expertise in server-side technologies and DevOps.
</p>
</div>
<div class="team-member">
<div class="member-avatar">
<i class="fas fa-palette"></i>
</div>
<h3>Sage</h3>
<div class="team-role">Frontend Developer & Communications</div>
<p>
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.
</p>
</div>
</div>
</div>
</section>
<!-- About Section -->
<section id="about" class="section">
<div class="container">
<div class="section-header">
<h2 class="section-title">About Us</h2>
</div>
<div class="about-content">
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</div>
</div>
</section>
<!-- Vision Section -->
<section id="vision" class="section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Our Vision</h2>
</div>
<div class="vision-content">
<div class="vision-text">
<p>
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.
</p>
</div>
<div class="principles-grid">
<div class="principle-card">
<div class="principle-icon">
<i class="fas fa-unlock"></i>
</div>
<h4>Software Freedom</h4>
<p>
All our projects are released under copyleft licenses to
ensure user freedoms are protected and preserved.
</p>
</div>
<div class="principle-card">
<div class="principle-icon">
<i class="fas fa-users"></i>
</div>
<h4>Community Collaboration</h4>
<p>
We believe in building software with and for the community,
welcoming contributions and feedback.
</p>
</div>
<div class="principle-card">
<div class="principle-icon">
<i class="fas fa-star"></i>
</div>
<h4>Quality Standards</h4>
<p>
We strive to create software that matches or exceeds the
quality of proprietary alternatives.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="stats-section">
<div class="container">
<div class="stats-container">
<div class="stat-item">
<div class="stat-number" data-count="0">0</div>
<div class="stat-label">Projects</div>
</div>
<div class="stat-item">
<div class="stat-number" data-count="2">0</div>
<div class="stat-label">Team Members</div>
</div>
<div class="stat-item">
<div class="stat-number" data-count="3">0</div>
<div class="stat-label">Years Active</div>
</div>
<div class="stat-item">
<div class="stat-number" data-count="100">0</div>
<div class="stat-label">% Free Software</div>
</div>
</div>
</div>
</section>
<!-- Contact Section -->
<section id="contact" class="section">
<div class="container">
<div class="section-header">
<h2 class="section-title">Get In Touch</h2>
<p class="section-subtitle">
Have questions about our projects or want to contribute? We'd love
to hear from you.
</p>
</div>
<div class="contact-container">
<div class="contact-info">
<div class="contact-methods">
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-envelope"></i>
</div>
<div class="contact-details">
<h4>Email</h4>
<a href="mailto:info@interstellardevelopment.org"
>info@interstellardevelopment.org</a
>
</div>
</div>
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-code-branch"></i>
</div>
<div class="contact-details">
<h4>Source Code</h4>
<a
href="https://interstellardevelopment.org/code/interstellar_development"
target="_blank"
>View Our Projects</a
>
</div>
</div>
</div>
</div>
<form class="contact-form" id="contact-form">
<div class="form-group">
<label for="name">Your Name</label>
<input type="text" id="name" name="name" required />
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required />
</div>
<div class="form-group">
<label for="subject">Subject</label>
<select id="subject" name="subject" required>
<option value="">Select a subject</option>
<option value="contribution">Project Contribution</option>
<option value="feedback">Feedback</option>
<option value="inquiry">General Inquiry</option>
<option value="bug">Bug Report</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea
id="message"
name="message"
rows="5"
required
></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane"></i>
Send Message
</button>
</form>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-brand">
<div class="logo">Interstellar Development</div>
<p>
Developing free and open source software with a focus on user
freedom and quality.
</p>
<div class="footer-social">
<a
href="https://interstellardevelopment.org/code/interstellar_development"
class="social-link"
target="_blank"
aria-label="Source Code"
>
<i class="fas fa-code-branch"></i>
</a>
<a href="#" class="social-link" aria-label="Discord">
<i class="fab fa-discord"></i>
</a>
<a href="#" class="social-link" aria-label="Mastodon">
<i class="fab fa-mastodon"></i>
</a>
</div>
</div>
<div class="footer-links">
<h4>Navigation</h4>
<ul>
<li><a href="#projects">Projects</a></li>
<li><a href="#team">Team</a></li>
<li><a href="#about">About</a></li>
<li><a href="#vision">Vision</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
<div class="footer-links">
<h4>Projects</h4>
<ul>
<li>
<a href="" target="_blank">Coming soon</a>
</li>
</ul>
</div>
<div class="footer-links">
<h4>Resources</h4>
<ul>
<li>
<a href="https://www.fsf.org/" target="_blank"
>Free Software Foundation</a
>
</li>
<li>
<a href="https://www.gnu.org/" target="_blank">GNU Project</a>
</li>
<li>
<a
href="https://interstellardevelopment.org/code/interstellar_development"
target="_blank"
>Source Code</a
>
</li>
<li>
<a href="mailto:info@interstellardevelopment.org"
>Contact Us</a
>
</li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div class="footer-bottom-content">
<p>&copy; 2025 Interstellar Development. All rights reserved.</p>
<div class="footer-badges">
<span class="badge">
<i class="fas fa-heart"></i>
Committed to Free Software
</span>
</div>
</div>
</div>
</div>
</footer>
</main>
<!-- General scripts -->
<script src="src/js/main.js"></script>
<script src="src/js/navigation.js"></script>
<script src="src/js/form.js"></script>
<script src="src/js/portfolioCard.js"></script>
<script src="src/js/portfolioFilter.js"></script>
<script src="src/js/overview.js"></script>
<script src="src/js/sectionTracker.js"></script>
<!-- Animation scripts -->
<script type="module" src="src/js/animation/starBackground.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/images/Patrick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
src/images/sage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

274
src/js/animation/Meteor.js Normal file
View file

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

63
src/js/animation/Star.js Normal file
View file

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

View file

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

22
src/js/form.js Normal file
View file

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

27
src/js/main.js Normal file
View file

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

106
src/js/navigation.js Normal file
View file

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

57
src/js/overview.js Normal file
View file

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

228
src/js/portfolioCard.js Normal file
View file

@ -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 = `
<!-- Font Awesome inside Shadow DOM -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Use global styles.css from index.html -->
<link rel="stylesheet" href="src/styles/styles.css">
<style>
/* Collaboration badge styling */
.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;
}
@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 card animations */
.portfolio-item {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s ease forwards;
display: flex;
flex-direction: column;
height: 100%;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Portfolio title with icon */
.portfolio-title {
display: flex;
align-items: center;
gap: 0.5rem;
}
.portfolio-content {
display: flex;
flex-direction: column;
flex: 1;
padding: 1.5rem;
}
.portfolio-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
/* Full-width Enhanced Button Styles */
.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;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.portfolio-btn {
padding: 0.9rem 1.5rem;
font-size: 0.85rem;
}
}
</style>
<div class="portfolio-item">
<div class="portfolio-img">
<i class="${icon}"></i>
</div>
<div class="portfolio-content">
<h3 class="portfolio-title">
<i class="${icon}"></i> ${title}
</h3>
<p class="portfolio-desc">${desc}</p>
${
collaboration
? `
<div class="collaboration-badge">
<i class="fas fa-users"></i> ${collaboration}
</div>
`
: ""
}
<div class="portfolio-tags">
${tags
.map((tag) => `<span class="tag">${tag.trim()}</span>`)
.join("")}
</div>
</div>
<div class="portfolio-btn-container">
<a href="${link}" target="_blank" class="portfolio-btn">
<i class="fas fa-arrow-right"></i> Explore Project
</a>
</div>
</div>
`;
}
}
customElements.define("portfolio-card", PortfolioCard);

95
src/js/portfolioFilter.js Normal file
View file

@ -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 = `
<i class="fas fa-search"></i>
<h3>No projects found</h3>
<p>Try adjusting your search or filter criteria</p>
`;
// 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();
});

111
src/js/sectionTracker.js Normal file
View file

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

1943
src/styles/styles.css Normal file

File diff suppressed because it is too large Load diff