Complete website overhaul #28
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# pages
|
||||
|
||||
61
dropdown.js
|
|
@ -1,61 +0,0 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||
|
||||
/*
|
||||
* InterstellarDevelopment website
|
||||
* Copyright (C) 2024 interstellar_development
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const menu = document.querySelector(".menu");
|
||||
const burgerMenu = document.querySelector(".burger-menu");
|
||||
|
||||
if (!menu || !burgerMenu) {
|
||||
console.warn(
|
||||
"Menu or burger menu element not found. Ensure they exist in the DOM."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the menu visibility
|
||||
function toggleMenu() {
|
||||
menu.classList.toggle("active");
|
||||
|
||||
if (menu.classList.contains("active")) {
|
||||
// Add click listener to close menu when clicking outside
|
||||
document.addEventListener("click", closeMenu);
|
||||
} else {
|
||||
// Remove the click listener when menu is closed
|
||||
document.removeEventListener("click", closeMenu);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the menu if clicking outside of it
|
||||
function closeMenu(event) {
|
||||
if (
|
||||
!menu.contains(event.target) &&
|
||||
!event.target.classList.contains("burger-menu")
|
||||
) {
|
||||
menu.classList.remove("active");
|
||||
document.removeEventListener("click", closeMenu);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach click event to the burger menu button
|
||||
burgerMenu.addEventListener("click", (event) => {
|
||||
event.stopPropagation(); // Prevent click from immediately triggering closeMenu
|
||||
toggleMenu();
|
||||
});
|
||||
});
|
||||
// @license-end
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
This favicon was generated using the following graphics from Twitter Twemoji:
|
||||
|
||||
- Graphics Title: 1f680.svg
|
||||
- Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji)
|
||||
- Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f680.svg
|
||||
- Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 751 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1 +0,0 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
68
footer.js
|
|
@ -1,68 +0,0 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||
|
||||
/*
|
||||
* interstellar_development website
|
||||
* Copyright (C) 2024 interstellar_development
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Footer extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<center>
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<p>2024 Interstellar Development</p>
|
||||
<!-- Hidden Button -->
|
||||
<button class="secret-button">👀</button>
|
||||
</div>
|
||||
</footer>
|
||||
</center>
|
||||
`;
|
||||
|
||||
// Add event listener for button click
|
||||
this.querySelector(".secret-button").addEventListener("click", () => {
|
||||
window.open("secret/index.html", "_blank");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("footer-component", Footer);
|
||||
|
||||
// CSS for the hidden button
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
.secret-button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer-content:hover .secret-button {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// @license-end
|
||||
46
header.js
|
|
@ -1,46 +0,0 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||
|
||||
/*
|
||||
* interstellar_development website
|
||||
* Copyright (C) 2024 interstellar_development
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Header extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<div><a href="index.html" class="project-name">Interstellar Development</a></div>
|
||||
<button class="burger-menu" onclick="toggleMenu()">☰</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="div-menu">
|
||||
<ul class="menu">
|
||||
<li><a href="index.html#project">Our Projects</a></li>
|
||||
<li><a href="index.html#cards">Our Team</a></li>
|
||||
<li><a href="index.html#about">About us</a></li>
|
||||
<li><a href="index.html#vision">Our Vision</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("header-component", Header);
|
||||
// @license-end
|
||||
|
Before Width: | Height: | Size: 156 KiB |
BIN
images/star.jpg
|
Before Width: | Height: | Size: 545 KiB |
657
index.html
|
|
@ -1,233 +1,496 @@
|
|||
<!--
|
||||
interstellar development website
|
||||
Copyright (C) 2024 interstellar_development
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="dropdown.js" type="text/javascript" defer></script>
|
||||
<script src="header.js" type="text/javascript" defer></script>
|
||||
<script src="footer.js" type="text/javascript" defer></script>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<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="favicon_io/apple-touch-icon.png"
|
||||
href="src/favicon/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="favicon_io/favicon-32x32.png"
|
||||
href="src/favicon/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="favicon_io/favicon-16x16.png"
|
||||
href="src/favicon/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="favicon_io/site.webmanifest" />
|
||||
<title>Interstellar Development</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Custom header component -->
|
||||
<header-component></header-component>
|
||||
<!-- Animated background -->
|
||||
<div class="animated-bg"></div>
|
||||
<div class="stars" id="stars"></div>
|
||||
|
||||
<article>
|
||||
<section id="project">
|
||||
<h1>Our Projects</h1>
|
||||
|
||||
<h2>Our Games</h2>
|
||||
<ul>
|
||||
<a href="webGames/index.html" target="_blank" class="listElement">
|
||||
<li>Web game collection</li>
|
||||
</a>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>Previously we had more unfinished Games listed here.</p>
|
||||
<p>
|
||||
We decided against displaying them and giving people the
|
||||
impression we are working on them currently.
|
||||
</p>
|
||||
<p>In the Future we will display the released games here</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Our Other Projects</h2>
|
||||
<ul>
|
||||
<a href="foss_alternatives/" target="_blank" class="listElement">
|
||||
<li>FOSS Alternatives</li>
|
||||
</a>
|
||||
<a href="react/" target="_blank" class="listElement">
|
||||
<li>React</li>
|
||||
</a>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Cards section with team members -->
|
||||
<h1>Our Team</h1>
|
||||
<section class="cards" id="cards">
|
||||
<div class="card">
|
||||
<a
|
||||
href="https://interstellardevelopment.org/code/Patrick_Pluto"
|
||||
class="card-link"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="images/Patrick.png" alt="Patrick" />
|
||||
<h3>Patrick_Pluto</h3>
|
||||
<p>
|
||||
The system administrator and our lead coder. He is the one you
|
||||
will need to blame for bugs in the games
|
||||
</p>
|
||||
</a>
|
||||
<!-- Navigation -->
|
||||
<nav id="navigation">
|
||||
<div class="nav-container">
|
||||
<div class="logo">
|
||||
<a href="#welcome-screen">Interstellar Development</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<a
|
||||
href="https://interstellardevelopment.org/code/sageTheDm"
|
||||
class="card-link"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="images/sage.png" alt="Sage" />
|
||||
<h3>sageTheDM</h3>
|
||||
<p>
|
||||
Our mostly competent web developer and secondary coder, if you
|
||||
experience any bugs on the website or spelling mistake he is to
|
||||
blame
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
class="mobile-menu-btn"
|
||||
id="burger-menu"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<div class="card">
|
||||
<a
|
||||
href="https://interstellardevelopment.org/code/Patrick_Pluto"
|
||||
class="card-link"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="images/nicolas.png" alt="Patrick" />
|
||||
<h3>St. Nicolaus</h3>
|
||||
<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>
|
||||
Our game level and asset designer. He is responsible for all
|
||||
assets in our FreeFTF game. So if an asset looks ugly be mad at
|
||||
him. Also, we are not sure if he is human or just a drunk wizard
|
||||
cat but one thing is very clear he is still a novice at his job.
|
||||
We develop high-quality free and open source software solutions,
|
||||
with a focus on gaming and applications that respect user freedom.
|
||||
</p>
|
||||
</a>
|
||||
<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>
|
||||
|
||||
<!-- About Us section -->
|
||||
<section id="about">
|
||||
<h2>About Us</h2>
|
||||
<p>
|
||||
Welcome to Interstellar Development! We are a small, passionate
|
||||
international team dedicated to transforming the programming world
|
||||
into a free-and-open-source future. Our diverse backgrounds and
|
||||
experiences fuel our commitment to creating free and open-source
|
||||
software, particularly in the realm of gaming.
|
||||
</p>
|
||||
<p>
|
||||
Our journey began with a shared vision: to better organize our efforts
|
||||
in making free and open-source games more efficient and accessible. We
|
||||
recognized that many current free software games are either lacking in
|
||||
quality or simply do not exist. This realization inspired us to focus
|
||||
on developing games that are unplayable on GNU/Linux and FreeBSD
|
||||
systems, as well as creating free software alternatives for those in
|
||||
need.
|
||||
</p>
|
||||
<p>
|
||||
At Interstellar Development, we believe that true freedom for computer
|
||||
users can only be achieved through the use of free software. That’s
|
||||
why we are committed to licensing all of our projects under copyleft
|
||||
free and open-source software licenses, ensuring that our games remain
|
||||
free for everyone to enjoy, forever.
|
||||
</p>
|
||||
<p>
|
||||
While we are not currently accepting donations, we welcome your
|
||||
support in the form of feedback and suggestions for improvements. If
|
||||
you wish to contribute financially, we encourage you to donate to the
|
||||
Free Software Foundation, as without them, we would have never started
|
||||
this.
|
||||
</p>
|
||||
<p>
|
||||
Join us as we strive to create a vibrant community around free
|
||||
software and gaming. Together, we can make a difference and pave the
|
||||
way for a free future!
|
||||
</p>
|
||||
<!-- 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>
|
||||
|
||||
<section id="vision">
|
||||
<h2>Our Vision</h2>
|
||||
<h2>Interstellar Development: Free Software is Our Passion</h2>
|
||||
<p>
|
||||
At Interstellar Development, we embarked on this journey to create a
|
||||
more organized and efficient approach to developing free and
|
||||
open-source software, with a particular focus on gaming. We recognized
|
||||
a significant gap in the availability and quality of free software
|
||||
games, which often fail to meet the expectations of users or simply do
|
||||
not exist. Our mission is to fill this void and elevate the standards
|
||||
of free gaming experiences.
|
||||
</p>
|
||||
<p>
|
||||
Our primary goal is to target games that are currently unplayable on
|
||||
GNU/Linux and FreeBSD systems. We aim to develop high-quality
|
||||
alternatives that not only provide enjoyable gameplay but also adhere
|
||||
to the principles of free software. Additionally, we are committed to
|
||||
identifying and addressing other areas within the software ecosystem
|
||||
that lack free alternatives, ensuring that users have access to a
|
||||
diverse range of tools and applications that respect their freedom.
|
||||
</p>
|
||||
<p>
|
||||
At Interstellar Development, we firmly believe that the path to true
|
||||
freedom for computer users lies in the adoption and promotion of free
|
||||
software. To this end, we are dedicated to licensing all of our
|
||||
projects under copyleft free and open-source software licenses. This
|
||||
commitment ensures that our games and software remain free for
|
||||
everyone to play, modify, and share, fostering a culture of
|
||||
collaboration and innovation within the community.
|
||||
</p>
|
||||
<p>
|
||||
We understand that community involvement is crucial to our success.
|
||||
While we are not currently accepting donations, we invite you to
|
||||
support us by providing feedback, reporting issues, and suggesting
|
||||
improvements. Your insights are invaluable in helping us refine our
|
||||
projects and enhance the user experience.
|
||||
</p>
|
||||
<p>
|
||||
If you feel compelled to contribute financially, we encourage you to
|
||||
consider donating to the Free Software Foundation. Their unwavering
|
||||
support and advocacy for free software principles have been
|
||||
instrumental in our journey, and your contributions to them help
|
||||
sustain the broader movement that enables us to create these games.
|
||||
</p>
|
||||
<p>
|
||||
Together, we can build a vibrant community around free software and
|
||||
gaming, paving the way for a future where everyone has access to
|
||||
high-quality, open-source alternatives. Join us in our mission to make
|
||||
a difference and champion the cause of free software for all!
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Custom footer component -->
|
||||
<footer-component></footer-component>
|
||||
<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>© 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>
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Game Landing Page</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="landing-page">
|
||||
<div class="game-background">
|
||||
<!-- Game is embedded or shown as background -->
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h1>Welcome to the Asteroid Game!</h1>
|
||||
<p>
|
||||
In this game, you control a spaceship that shoots at asteroids to
|
||||
avoid destruction and collect items for power-ups.
|
||||
</p>
|
||||
<p>Your goal is to survive as long as possible while scoring points!</p>
|
||||
<button onclick="window.location.href='secret.html'">Play Game</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,414 +0,0 @@
|
|||
"use strict";
|
||||
// Canvas setup
|
||||
const canvas = document.getElementById("gameCanvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const targetFPS = 60;
|
||||
const targetFrameTime = 1000 / targetFPS;
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
let lastFrameTime = performance.now();
|
||||
|
||||
// Game elements
|
||||
const player = {
|
||||
x: canvas.width / 2,
|
||||
y: canvas.height - 60,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: "white",
|
||||
speed: 5,
|
||||
dx: 0,
|
||||
};
|
||||
|
||||
let bullets = [];
|
||||
let asteroids = [];
|
||||
let items = [];
|
||||
let score = 0;
|
||||
let totalBulletsFired = 0;
|
||||
let isGameOver = false;
|
||||
let lastBulletTime = 0;
|
||||
let ammo = 100; // Ammo counter
|
||||
|
||||
// Difficulty control
|
||||
let asteroidSpawnInterval = 800; // Faster spawn rate
|
||||
let asteroidSpeedMultiplier = 1.5; // Faster asteroids from the start
|
||||
let difficultyIncreaseRate = 0.2; // Faster scaling every 10 seconds
|
||||
|
||||
// Controls
|
||||
let canShoot = true; // Flag to control shooting
|
||||
let rapidFireActive = false;
|
||||
let shotgunActive = false;
|
||||
let rainbowActive = false;
|
||||
let lastShotgunTime = 0;
|
||||
|
||||
// Controls for sphere effects
|
||||
let blueSphereCooldown = 0;
|
||||
let yellowSphereCooldown = 0;
|
||||
let greenSphereCooldown = 0;
|
||||
let rainbowSphereCooldown = 0;
|
||||
|
||||
// Sphere types
|
||||
const sphereTypes = ["blue", "yellow", "green", "rainbow"];
|
||||
|
||||
/// Control for left button press and release
|
||||
function btnMoveLeft(isPressed) {
|
||||
if (isPressed) {
|
||||
player.dx = -player.speed; // Start moving left
|
||||
} else {
|
||||
player.dx = 0; // Stop moving when button is released
|
||||
}
|
||||
}
|
||||
|
||||
// Control for shoot button click (simulates spacebar press)
|
||||
function btnShoot() {
|
||||
if (canShoot && !isGameOver) {
|
||||
shootBullet();
|
||||
canShoot = false; // Prevent shooting until the button is "released"
|
||||
}
|
||||
}
|
||||
|
||||
// Control for right button press and release
|
||||
function btnMoveRight(isPressed) {
|
||||
if (isPressed) {
|
||||
player.dx = player.speed; // Start moving right
|
||||
} else {
|
||||
player.dx = 0; // Stop moving when button is released
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("shootBtn").addEventListener("mouseup", () => {
|
||||
canShoot = true; // Allow shooting again when button is released
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowLeft" || e.key === "a") player.dx = -player.speed;
|
||||
if (e.key === "ArrowRight" || e.key === "d") player.dx = player.speed;
|
||||
|
||||
// Shoot only if it's not a hold, and we can shoot
|
||||
if (e.key === " " && canShoot && !isGameOver) {
|
||||
shootBullet();
|
||||
canShoot = false; // Prevent shooting until the key is released
|
||||
}
|
||||
|
||||
if (e.key === "r" && isGameOver) restartGame();
|
||||
});
|
||||
|
||||
window.addEventListener("keyup", (e) => {
|
||||
// Stop moving when either the arrow keys or the 'a'/'d' keys are released
|
||||
if (
|
||||
e.key === "ArrowLeft" ||
|
||||
e.key === "ArrowRight" ||
|
||||
e.key === "a" ||
|
||||
e.key === "d"
|
||||
) {
|
||||
player.dx = 0;
|
||||
}
|
||||
|
||||
// Allow shooting again when the space key is released
|
||||
if (e.key === " ") {
|
||||
canShoot = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Bullet mechanics with cooldown
|
||||
function shootBullet() {
|
||||
const now = Date.now();
|
||||
if (now - lastBulletTime < 100) return; // Enforce cooldown of 0.1 seconds
|
||||
if (ammo <= 0) return; // Prevent shooting if ammo is empty
|
||||
lastBulletTime = now;
|
||||
ammo--; // Decrease ammo
|
||||
|
||||
totalBulletsFired++; // Increment total bullets fired
|
||||
|
||||
if (rapidFireActive) {
|
||||
// If rapid fire is active, fire bullets continuously with a short delay
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => {
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
});
|
||||
}, i * 50); // Fire bullets with 50ms delay between shots
|
||||
}
|
||||
} else if (shotgunActive) {
|
||||
// Shotgun effect, firing 3 bullets with a spread
|
||||
for (let i = -1; i <= 1; i++) {
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
angle: i * 10, // Spray the bullets at different angles
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Normal bullet
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate random color
|
||||
function getRandomColor() {
|
||||
const colors = ["red", "blue", "green", "orange", "purple", "pink"];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// Asteroid mechanics
|
||||
function createAsteroid() {
|
||||
const size = Math.random() * 40 + 30; // Bigger asteroids (min: 30, max: 70)
|
||||
asteroids.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: -size,
|
||||
width: size,
|
||||
height: size,
|
||||
color: getRandomColor(),
|
||||
speed: (Math.random() * 3 + 2) * asteroidSpeedMultiplier, // Faster initial speed
|
||||
});
|
||||
}
|
||||
|
||||
// Item mechanics
|
||||
function createItem() {
|
||||
const randomType =
|
||||
sphereTypes[Math.floor(Math.random() * sphereTypes.length)];
|
||||
const size = 30;
|
||||
const x = Math.random() * canvas.width;
|
||||
items.push({
|
||||
x: x,
|
||||
y: -size,
|
||||
width: size,
|
||||
height: size,
|
||||
type: randomType,
|
||||
color:
|
||||
randomType === "blue"
|
||||
? "blue"
|
||||
: randomType === "yellow"
|
||||
? "yellow"
|
||||
: randomType === "green"
|
||||
? "green"
|
||||
: "rainbow",
|
||||
speed: 3,
|
||||
});
|
||||
}
|
||||
|
||||
// Update game elements
|
||||
function updatePlayer() {
|
||||
player.x += player.dx;
|
||||
if (player.x < 0) player.x = 0;
|
||||
if (player.x + player.width > canvas.width)
|
||||
player.x = canvas.width - player.width;
|
||||
}
|
||||
|
||||
function updateBullets() {
|
||||
bullets.forEach((bullet, index) => {
|
||||
bullet.y -= bullet.speed;
|
||||
if (bullet.y + bullet.height < 0) bullets.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function updateAsteroids() {
|
||||
asteroids.forEach((asteroid, index) => {
|
||||
asteroid.y += asteroid.speed;
|
||||
if (asteroid.y > canvas.height) asteroids.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function updateItems() {
|
||||
items.forEach((item, index) => {
|
||||
item.y += item.speed;
|
||||
if (item.y > canvas.height) items.splice(index, 1);
|
||||
// Check if player collects the item
|
||||
if (
|
||||
player.x < item.x + item.width &&
|
||||
player.x + player.width > item.x &&
|
||||
player.y < item.y + item.height &&
|
||||
player.y + player.height > item.y
|
||||
) {
|
||||
applyItemEffect(item.type);
|
||||
items.splice(index, 1); // Remove the item after it is collected
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyItemEffect(type) {
|
||||
let points = Math.floor(Math.random() * 5) + 1; // Random points between 1 and 5
|
||||
if (type === "blue") {
|
||||
rapidFireActive = true;
|
||||
setTimeout(() => (rapidFireActive = false), 15000); // 15 seconds of rapid fire
|
||||
} else if (type === "yellow") {
|
||||
shotgunActive = true;
|
||||
setTimeout(() => (shotgunActive = false), 30000); // 30 seconds of shotgun
|
||||
} else if (type === "green") {
|
||||
ammo = 100; // Refill ammo
|
||||
} else if (type === "rainbow") {
|
||||
rapidFireActive = true;
|
||||
shotgunActive = true;
|
||||
setTimeout(() => {
|
||||
rapidFireActive = false;
|
||||
shotgunActive = false;
|
||||
}, 15000); // 15 seconds with all effects
|
||||
}
|
||||
score += points; // Add points when an item is collected
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
function checkCollisions() {
|
||||
bullets.forEach((bullet, bIndex) => {
|
||||
asteroids.forEach((asteroid, aIndex) => {
|
||||
if (
|
||||
bullet.x < asteroid.x + asteroid.width &&
|
||||
bullet.x + bullet.width > asteroid.x &&
|
||||
bullet.y < asteroid.y + asteroid.height &&
|
||||
bullet.y + bullet.height > asteroid.y
|
||||
) {
|
||||
bullets.splice(bIndex, 1);
|
||||
asteroids.splice(aIndex, 1);
|
||||
score += Math.floor(Math.random() * 5) + 1; // Add points when an asteroid is destroyed
|
||||
// Visual feedback for destroyed asteroid
|
||||
createExplosion(asteroid.x, asteroid.y);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
asteroids.forEach((asteroid) => {
|
||||
if (
|
||||
player.x < asteroid.x + asteroid.width &&
|
||||
player.x + player.width > asteroid.x &&
|
||||
player.y < asteroid.y + asteroid.height &&
|
||||
player.y + player.height > asteroid.y
|
||||
) {
|
||||
isGameOver = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Explosion effect
|
||||
function createExplosion(x, y) {
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 20, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
setTimeout(() => ctx.clearRect(x - 20, y - 20, 40, 40), 200); // Clear explosion after 200ms
|
||||
}
|
||||
|
||||
// Draw elements
|
||||
function drawPlayer() {
|
||||
ctx.fillStyle = player.color;
|
||||
ctx.fillRect(player.x, player.y, player.width, player.height);
|
||||
}
|
||||
|
||||
function drawBullets() {
|
||||
bullets.forEach((bullet) => {
|
||||
ctx.fillStyle = bullet.color;
|
||||
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
|
||||
});
|
||||
}
|
||||
|
||||
function drawAsteroids() {
|
||||
asteroids.forEach((asteroid) => {
|
||||
ctx.fillStyle = asteroid.color;
|
||||
ctx.fillRect(asteroid.x, asteroid.y, asteroid.width, asteroid.height);
|
||||
});
|
||||
}
|
||||
|
||||
function drawItems() {
|
||||
items.forEach((item) => {
|
||||
ctx.fillStyle = item.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
item.x + item.width / 2,
|
||||
item.y + item.height / 2,
|
||||
item.width / 2,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
function drawScore() {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Score: ${score}`, 20, 40); // Score at top-left corner
|
||||
}
|
||||
|
||||
function drawAmmo() {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Ammo: ${ammo}`, 20, 70); // Ammo at top-left corner
|
||||
}
|
||||
|
||||
function drawGameOver() {
|
||||
if (isGameOver) {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "40px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Game Over!", canvas.width / 2, canvas.height / 2 - 40);
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Total Score: ${score}`, canvas.width / 2, canvas.height / 2);
|
||||
ctx.fillText(
|
||||
`Bullets Fired: ${totalBulletsFired}`,
|
||||
canvas.width / 2,
|
||||
canvas.height / 2 + 40
|
||||
);
|
||||
ctx.fillText(
|
||||
'Press "R" to Restart',
|
||||
canvas.width / 2,
|
||||
canvas.height / 2 + 80
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Restart game
|
||||
function restartGame() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// Main game loop
|
||||
function gameLoop() {
|
||||
const currentTime = performance.now();
|
||||
const elapsedTime = currentTime - lastFrameTime;
|
||||
|
||||
if (elapsedTime >= targetFrameTime) {
|
||||
lastFrameTime = currentTime - (elapsedTime % targetFrameTime);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (!isGameOver) {
|
||||
updatePlayer();
|
||||
updateBullets();
|
||||
updateAsteroids();
|
||||
updateItems();
|
||||
checkCollisions();
|
||||
}
|
||||
|
||||
drawPlayer();
|
||||
drawBullets();
|
||||
drawAsteroids();
|
||||
drawItems();
|
||||
drawScore();
|
||||
drawAmmo();
|
||||
drawGameOver();
|
||||
|
||||
if (!isGameOver) {
|
||||
if (Math.random() < 0.01) createAsteroid(); // 1% chance every frame to spawn an asteroid
|
||||
if (Math.random() < 0.005) createItem(); // 0.5% chance to spawn an item
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
// Start game loop
|
||||
requestAnimationFrame(gameLoop);
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Asteroid Shooter</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
|
||||
<!-- Virtual buttons for mobile -->
|
||||
<div class="controls">
|
||||
<button id="leftBtn" class="control-btn">Left</button>
|
||||
<button id="shootBtn" class="control-btn" onclick="btnShoot()">
|
||||
Shoot
|
||||
</button>
|
||||
<button id="rightBtn" class="control-btn">Right</button>
|
||||
</div>
|
||||
|
||||
<script src="game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
background: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
flex-grow: 1;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.control-btn {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #000;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.landing-page {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7); /* Dark overlay */
|
||||
backdrop-filter: blur(8px); /* Apply blur effect to the background */
|
||||
z-index: -1; /* Ensures it's in the background */
|
||||
pointer-events: none; /* Prevent interaction with the blurred background */
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
z-index: 1; /* Ensure content appears above the game background */
|
||||
padding: 20px;
|
||||
max-width: 600px; /* Limit the width of the content */
|
||||
position: relative;
|
||||
color: white;
|
||||
backdrop-filter: blur(
|
||||
8px
|
||||
); /* Ensure content has some blur as well for contrast */
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
background-color: #ffcc00;
|
||||
color: black;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #ff9900;
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Endless runner</title>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="game-container">
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
<button id="restartBtn" onclick="restartGame()">Restart</button>
|
||||
</div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export default function EndlessRunner() {
|
||||
const canvasRef = useRef(null);
|
||||
const [running, setRunning] = useState(true);
|
||||
const player = { x: 50, y: 150, width: 20, height: 20, dy: 0 };
|
||||
const gravity = 0.5;
|
||||
let obstacles = [];
|
||||
let score = 0;
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = 300;
|
||||
|
||||
function update() {
|
||||
if (!running) return;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Player physics
|
||||
player.dy += gravity;
|
||||
player.y += player.dy;
|
||||
if (player.y > 150) {
|
||||
player.y = 150;
|
||||
player.dy = 0;
|
||||
}
|
||||
|
||||
// Draw player
|
||||
ctx.fillStyle = "blue";
|
||||
ctx.fillRect(player.x, player.y, player.width, player.height);
|
||||
|
||||
// Obstacles
|
||||
if (Math.random() < 0.02) {
|
||||
obstacles.push({ x: canvas.width, y: 150, width: 20, height: 20 });
|
||||
}
|
||||
obstacles = obstacles.map((obstacle) => ({
|
||||
...obstacle,
|
||||
x: obstacle.x - 5,
|
||||
}));
|
||||
obstacles = obstacles.filter(
|
||||
(obstacle) => obstacle.x + obstacle.width > 0
|
||||
);
|
||||
|
||||
obstacles.forEach((obstacle) => {
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
|
||||
|
||||
// Collision detection
|
||||
if (
|
||||
player.x < obstacle.x + obstacle.width &&
|
||||
player.x + player.width > obstacle.x &&
|
||||
player.y < obstacle.y + obstacle.height &&
|
||||
player.y + player.height > obstacle.y
|
||||
) {
|
||||
setRunning(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Score
|
||||
score++;
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillText("Score: " + score, 10, 20);
|
||||
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
update();
|
||||
}, [running]);
|
||||
|
||||
function jump() {
|
||||
if (player.y >= 150) {
|
||||
player.dy = -10;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<canvas ref={canvasRef} className="border" onClick={jump}></canvas>
|
||||
{!running && (
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Restart
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 2px solid black;
|
||||
background-color: white;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#restartBtn {
|
||||
margin-top: 10px;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restartBtn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
404
secret/game.js
|
|
@ -1,404 +0,0 @@
|
|||
"use strict";
|
||||
// Canvas setup
|
||||
const canvas = document.getElementById("gameCanvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
// Game elements
|
||||
const player = {
|
||||
x: canvas.width / 2,
|
||||
y: canvas.height - 60,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: "white",
|
||||
speed: 5,
|
||||
dx: 0,
|
||||
};
|
||||
|
||||
let bullets = [];
|
||||
let asteroids = [];
|
||||
let items = [];
|
||||
let score = 0;
|
||||
let totalBulletsFired = 0;
|
||||
let isGameOver = false;
|
||||
let lastBulletTime = 0;
|
||||
let ammo = 100; // Ammo counter
|
||||
|
||||
// Difficulty control
|
||||
let asteroidSpawnInterval = 800; // Faster spawn rate
|
||||
let asteroidSpeedMultiplier = 1.5; // Faster asteroids from the start
|
||||
let difficultyIncreaseRate = 0.2; // Faster scaling every 10 seconds
|
||||
|
||||
// Controls
|
||||
let canShoot = true; // Flag to control shooting
|
||||
let rapidFireActive = false;
|
||||
let shotgunActive = false;
|
||||
let rainbowActive = false;
|
||||
let lastShotgunTime = 0;
|
||||
|
||||
// Controls for sphere effects
|
||||
let blueSphereCooldown = 0;
|
||||
let yellowSphereCooldown = 0;
|
||||
let greenSphereCooldown = 0;
|
||||
let rainbowSphereCooldown = 0;
|
||||
|
||||
// Sphere types
|
||||
const sphereTypes = ["blue", "yellow", "green", "rainbow"];
|
||||
|
||||
/// Control for left button press and release
|
||||
function btnMoveLeft(isPressed) {
|
||||
if (isPressed) {
|
||||
player.dx = -player.speed; // Start moving left
|
||||
} else {
|
||||
player.dx = 0; // Stop moving when button is released
|
||||
}
|
||||
}
|
||||
|
||||
// Control for shoot button click (simulates spacebar press)
|
||||
function btnShoot() {
|
||||
if (canShoot && !isGameOver) {
|
||||
shootBullet();
|
||||
canShoot = false; // Prevent shooting until the button is "released"
|
||||
}
|
||||
}
|
||||
|
||||
// Control for right button press and release
|
||||
function btnMoveRight(isPressed) {
|
||||
if (isPressed) {
|
||||
player.dx = player.speed; // Start moving right
|
||||
} else {
|
||||
player.dx = 0; // Stop moving when button is released
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("shootBtn").addEventListener("mouseup", () => {
|
||||
canShoot = true; // Allow shooting again when button is released
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowLeft" || e.key === "a") player.dx = -player.speed;
|
||||
if (e.key === "ArrowRight" || e.key === "d") player.dx = player.speed;
|
||||
|
||||
// Shoot only if it's not a hold, and we can shoot
|
||||
if (e.key === " " && canShoot && !isGameOver) {
|
||||
shootBullet();
|
||||
canShoot = false; // Prevent shooting until the key is released
|
||||
}
|
||||
|
||||
if (e.key === "r" && isGameOver) restartGame();
|
||||
});
|
||||
|
||||
window.addEventListener("keyup", (e) => {
|
||||
// Stop moving when either the arrow keys or the 'a'/'d' keys are released
|
||||
if (
|
||||
e.key === "ArrowLeft" ||
|
||||
e.key === "ArrowRight" ||
|
||||
e.key === "a" ||
|
||||
e.key === "d"
|
||||
) {
|
||||
player.dx = 0;
|
||||
}
|
||||
|
||||
// Allow shooting again when the space key is released
|
||||
if (e.key === " ") {
|
||||
canShoot = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Bullet mechanics with cooldown
|
||||
function shootBullet() {
|
||||
const now = Date.now();
|
||||
if (now - lastBulletTime < 100) return; // Enforce cooldown of 0.1 seconds
|
||||
if (ammo <= 0) return; // Prevent shooting if ammo is empty
|
||||
lastBulletTime = now;
|
||||
ammo--; // Decrease ammo
|
||||
|
||||
totalBulletsFired++; // Increment total bullets fired
|
||||
|
||||
if (rapidFireActive) {
|
||||
// If rapid fire is active, fire bullets continuously with a short delay
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => {
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
});
|
||||
}, i * 50); // Fire bullets with 50ms delay between shots
|
||||
}
|
||||
} else if (shotgunActive) {
|
||||
// Shotgun effect, firing 3 bullets with a spread
|
||||
for (let i = -1; i <= 1; i++) {
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
angle: i * 10, // Spray the bullets at different angles
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Normal bullet
|
||||
bullets.push({
|
||||
x: player.x + player.width / 2 - 2.5,
|
||||
y: player.y,
|
||||
width: 5,
|
||||
height: 10,
|
||||
color: "yellow",
|
||||
speed: 7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate random color
|
||||
function getRandomColor() {
|
||||
const colors = ["red", "blue", "green", "orange", "purple", "pink"];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// Asteroid mechanics
|
||||
function createAsteroid() {
|
||||
const size = Math.random() * 40 + 30; // Bigger asteroids (min: 30, max: 70)
|
||||
asteroids.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: -size,
|
||||
width: size,
|
||||
height: size,
|
||||
color: getRandomColor(),
|
||||
speed: (Math.random() * 3 + 2) * asteroidSpeedMultiplier, // Faster initial speed
|
||||
});
|
||||
}
|
||||
|
||||
// Item mechanics
|
||||
function createItem() {
|
||||
const randomType =
|
||||
sphereTypes[Math.floor(Math.random() * sphereTypes.length)];
|
||||
const size = 30;
|
||||
const x = Math.random() * canvas.width;
|
||||
items.push({
|
||||
x: x,
|
||||
y: -size,
|
||||
width: size,
|
||||
height: size,
|
||||
type: randomType,
|
||||
color:
|
||||
randomType === "blue"
|
||||
? "blue"
|
||||
: randomType === "yellow"
|
||||
? "yellow"
|
||||
: randomType === "green"
|
||||
? "green"
|
||||
: "rainbow",
|
||||
speed: 3,
|
||||
});
|
||||
}
|
||||
|
||||
// Update game elements
|
||||
function updatePlayer() {
|
||||
player.x += player.dx;
|
||||
if (player.x < 0) player.x = 0;
|
||||
if (player.x + player.width > canvas.width)
|
||||
player.x = canvas.width - player.width;
|
||||
}
|
||||
|
||||
function updateBullets() {
|
||||
bullets.forEach((bullet, index) => {
|
||||
bullet.y -= bullet.speed;
|
||||
if (bullet.y + bullet.height < 0) bullets.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function updateAsteroids() {
|
||||
asteroids.forEach((asteroid, index) => {
|
||||
asteroid.y += asteroid.speed;
|
||||
if (asteroid.y > canvas.height) asteroids.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function updateItems() {
|
||||
items.forEach((item, index) => {
|
||||
item.y += item.speed;
|
||||
if (item.y > canvas.height) items.splice(index, 1);
|
||||
// Check if player collects the item
|
||||
if (
|
||||
player.x < item.x + item.width &&
|
||||
player.x + player.width > item.x &&
|
||||
player.y < item.y + item.height &&
|
||||
player.y + player.height > item.y
|
||||
) {
|
||||
applyItemEffect(item.type);
|
||||
items.splice(index, 1); // Remove the item after it is collected
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyItemEffect(type) {
|
||||
let points = Math.floor(Math.random() * 5) + 1; // Random points between 1 and 5
|
||||
if (type === "blue") {
|
||||
rapidFireActive = true;
|
||||
setTimeout(() => (rapidFireActive = false), 15000); // 15 seconds of rapid fire
|
||||
} else if (type === "yellow") {
|
||||
shotgunActive = true;
|
||||
setTimeout(() => (shotgunActive = false), 30000); // 30 seconds of shotgun
|
||||
} else if (type === "green") {
|
||||
ammo = 100; // Refill ammo
|
||||
} else if (type === "rainbow") {
|
||||
rapidFireActive = true;
|
||||
shotgunActive = true;
|
||||
setTimeout(() => {
|
||||
rapidFireActive = false;
|
||||
shotgunActive = false;
|
||||
}, 15000); // 15 seconds with all effects
|
||||
}
|
||||
score += points; // Add points when an item is collected
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
function checkCollisions() {
|
||||
bullets.forEach((bullet, bIndex) => {
|
||||
asteroids.forEach((asteroid, aIndex) => {
|
||||
if (
|
||||
bullet.x < asteroid.x + asteroid.width &&
|
||||
bullet.x + bullet.width > asteroid.x &&
|
||||
bullet.y < asteroid.y + asteroid.height &&
|
||||
bullet.y + bullet.height > asteroid.y
|
||||
) {
|
||||
bullets.splice(bIndex, 1);
|
||||
asteroids.splice(aIndex, 1);
|
||||
score += Math.floor(Math.random() * 5) + 1; // Add points when an asteroid is destroyed
|
||||
// Visual feedback for destroyed asteroid
|
||||
createExplosion(asteroid.x, asteroid.y);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
asteroids.forEach((asteroid) => {
|
||||
if (
|
||||
player.x < asteroid.x + asteroid.width &&
|
||||
player.x + player.width > asteroid.x &&
|
||||
player.y < asteroid.y + asteroid.height &&
|
||||
player.y + player.height > asteroid.y
|
||||
) {
|
||||
isGameOver = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Explosion effect
|
||||
function createExplosion(x, y) {
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 20, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
setTimeout(() => ctx.clearRect(x - 20, y - 20, 40, 40), 200); // Clear explosion after 200ms
|
||||
}
|
||||
|
||||
// Draw elements
|
||||
function drawPlayer() {
|
||||
ctx.fillStyle = player.color;
|
||||
ctx.fillRect(player.x, player.y, player.width, player.height);
|
||||
}
|
||||
|
||||
function drawBullets() {
|
||||
bullets.forEach((bullet) => {
|
||||
ctx.fillStyle = bullet.color;
|
||||
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
|
||||
});
|
||||
}
|
||||
|
||||
function drawAsteroids() {
|
||||
asteroids.forEach((asteroid) => {
|
||||
ctx.fillStyle = asteroid.color;
|
||||
ctx.fillRect(asteroid.x, asteroid.y, asteroid.width, asteroid.height);
|
||||
});
|
||||
}
|
||||
|
||||
function drawItems() {
|
||||
items.forEach((item) => {
|
||||
ctx.fillStyle = item.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
item.x + item.width / 2,
|
||||
item.y + item.height / 2,
|
||||
item.width / 2,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
function drawScore() {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Score: ${score}`, 20, 40); // Score at top-left corner
|
||||
}
|
||||
|
||||
function drawAmmo() {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Ammo: ${ammo}`, 20, 70); // Ammo at top-left corner
|
||||
}
|
||||
|
||||
function drawGameOver() {
|
||||
if (isGameOver) {
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "40px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Game Over!", canvas.width / 2, canvas.height / 2 - 40);
|
||||
ctx.font = "24px Arial";
|
||||
ctx.fillText(`Total Score: ${score}`, canvas.width / 2, canvas.height / 2);
|
||||
ctx.fillText(
|
||||
`Bullets Fired: ${totalBulletsFired}`,
|
||||
canvas.width / 2,
|
||||
canvas.height / 2 + 40
|
||||
);
|
||||
ctx.fillText(
|
||||
'Press "R" to Restart',
|
||||
canvas.width / 2,
|
||||
canvas.height / 2 + 80
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Restart game
|
||||
function restartGame() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// Main game loop
|
||||
function gameLoop() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (!isGameOver) {
|
||||
updatePlayer();
|
||||
updateBullets();
|
||||
updateAsteroids();
|
||||
updateItems();
|
||||
checkCollisions();
|
||||
}
|
||||
|
||||
drawPlayer();
|
||||
drawBullets();
|
||||
drawAsteroids();
|
||||
drawItems();
|
||||
drawScore();
|
||||
drawAmmo();
|
||||
drawGameOver();
|
||||
|
||||
if (!isGameOver) {
|
||||
if (Math.random() < 0.01) createAsteroid(); // 1% chance every frame to spawn an asteroid
|
||||
if (Math.random() < 0.005) createItem(); // 0.5% chance to spawn an item
|
||||
}
|
||||
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
// Start game loop
|
||||
gameLoop();
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const targetNum = Math.trunc(Math.random() * 20) + 1;
|
||||
let highScore = Number(localStorage.getItem("highscore")) || 0;
|
||||
let userGuess = 10; // Default guess
|
||||
let currScore = 20;
|
||||
|
||||
const screenEl = document.querySelector(".screen");
|
||||
const msgEl = document.querySelector(".message");
|
||||
const guessInput = document.querySelector("#guess");
|
||||
const scoreEl = document.querySelector(".score");
|
||||
const highScoreEl = document.querySelector(".highScore");
|
||||
const checkBtn = document.querySelector("#check");
|
||||
const restartBtn = document.querySelector("#restart");
|
||||
const incBtn = document.querySelector("#up");
|
||||
const decBtn = document.querySelector("#down");
|
||||
|
||||
const setMsg = (msg) => (msgEl.textContent = msg);
|
||||
const setScore = (score) =>
|
||||
(scoreEl.textContent = `Score: ${(currScore = score)}`);
|
||||
const setHighScore = () => {
|
||||
highScoreEl.textContent = `Highscore: ${highScore}`;
|
||||
localStorage.setItem("highscore", highScore);
|
||||
};
|
||||
const changeColor = (color) => (screenEl.style.backgroundColor = color);
|
||||
|
||||
checkBtn.addEventListener("click", () => {
|
||||
userGuess = Number(guessInput.textContent);
|
||||
if (!userGuess || userGuess < 1 || userGuess > 20) {
|
||||
setMsg("Please enter a valid number between 1 and 20.");
|
||||
} else if (userGuess === targetNum) {
|
||||
highScore = Math.max(highScore, currScore);
|
||||
setHighScore();
|
||||
setMsg(
|
||||
currScore !== 20 ? "Correct Number!" : "Are you sure you didn't cheat?"
|
||||
);
|
||||
changeColor(currScore !== 20 ? "#1ba100" : "#FFC300");
|
||||
} else {
|
||||
setMsg(userGuess > targetNum ? "Too High!" : "Too Low!");
|
||||
if (currScore > 1) {
|
||||
setScore(currScore - 1);
|
||||
} else {
|
||||
setScore(1);
|
||||
setMsg("You lost the game!");
|
||||
changeColor("#a10000");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
restartBtn.addEventListener("click", () => location.reload());
|
||||
incBtn.addEventListener(
|
||||
"click",
|
||||
() =>
|
||||
(guessInput.textContent = Math.min(Number(guessInput.textContent) + 1, 20))
|
||||
);
|
||||
decBtn.addEventListener(
|
||||
"click",
|
||||
() =>
|
||||
(guessInput.textContent = Math.max(Number(guessInput.textContent) - 1, 1))
|
||||
);
|
||||
|
||||
guessInput.textContent = userGuess;
|
||||
setMsg("Guess a number");
|
||||
setScore(currScore);
|
||||
setHighScore();
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Guess My Number</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="gameboy">
|
||||
<!-- Game Boy Screen -->
|
||||
<div class="screen">
|
||||
<article class="game">
|
||||
<h1>Guess My Number - Game</h1>
|
||||
<p class="message"></p>
|
||||
<div class="guess-display">
|
||||
<span id="guess"></span>
|
||||
</div>
|
||||
<p class="score"></p>
|
||||
<p class="highScore"></p>
|
||||
|
||||
<div class="description">
|
||||
<h2>Description</h2>
|
||||
<p>Guess a number between 1 and 20</p>
|
||||
<p>A = check</p>
|
||||
<p>B = Reload</p>
|
||||
<p>▲ = increases guess by one</p>
|
||||
<p>▼ = decreases guess by one</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<!-- D-Pad on the left -->
|
||||
<div class="dpad">
|
||||
<button class="dpad-btn up" id="up">▲</button>
|
||||
<button class="dpad-btn left" id="left">◀</button>
|
||||
<div class="dpad-center"></div>
|
||||
<button class="dpad-btn right" id="right">▶</button>
|
||||
<button class="dpad-btn down" id="down">▼</button>
|
||||
</div>
|
||||
|
||||
<!-- A and B Buttons on the right -->
|
||||
<div class="action-buttons">
|
||||
<button class="btn" id="check">A</button>
|
||||
<button class="btn" id="restart">B</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="game.js"></script>
|
||||
<script src="styles.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
/* Base Reset */
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: monospace;
|
||||
background-color: #3a2d56;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* GameBoy Layout */
|
||||
.gameboy {
|
||||
background-color: #5f4c82; /* Game Boy Color purple shell */
|
||||
width: 441px;
|
||||
height: 735px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gameboy {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen */
|
||||
.screen {
|
||||
background-color: #306230; /* Game Boy green screen */
|
||||
border: 4px solid #0f380f;
|
||||
width: 90%;
|
||||
height: 55%;
|
||||
margin-top: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: inset 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.game {
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
h1 {
|
||||
font-size: 2rem; /* Increased font size */
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Guess Display */
|
||||
.guess-display {
|
||||
background-color: #9bbc0f;
|
||||
color: #0f380f;
|
||||
border: 2px solid #0f380f;
|
||||
font-size: 2rem; /* Increased font size */
|
||||
width: 80px; /* Increased width */
|
||||
text-align: center;
|
||||
margin: 10px auto;
|
||||
padding: 10px; /* Increased padding */
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.message,
|
||||
.score,
|
||||
.highScore {
|
||||
font-size: 1.4rem; /* Increased font size */
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.description,
|
||||
.description p {
|
||||
font-size: 1.2rem;
|
||||
margin: 0 auto;
|
||||
padding: 0 auto;
|
||||
}
|
||||
|
||||
/* Controls Section */
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* D-Pad */
|
||||
.dpad {
|
||||
position: relative;
|
||||
width: 120px; /* Increased size */
|
||||
height: 120px; /* Increased size */
|
||||
}
|
||||
|
||||
/* Base Styling for D-Pad Buttons */
|
||||
.dpad-btn {
|
||||
background-color: #0f380f;
|
||||
color: #9bbc0f;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
font-size: 1.5rem; /* Increased size */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dpad-btn.up {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.down {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.left {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.right {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* D-Pad Center to Connect Buttons */
|
||||
.dpad-center {
|
||||
background-color: #0f380f;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #9cbc0f00;
|
||||
z-index: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* A and B Buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 140px; /* Increased height */
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #0f380f;
|
||||
color: #9bbc0f;
|
||||
border: 2px solid #9bbc0f;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.8rem; /* Increased font size */
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #9bbc0f;
|
||||
color: #0f380f;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
"use strict";
|
||||
const left = document.querySelector("#left");
|
||||
const right = document.querySelector("#right");
|
||||
const gameboy = document.querySelector(".gameboy");
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
const screen = document.querySelector(".screen");
|
||||
const dpadButtons = document.querySelectorAll(".dpad-btn");
|
||||
const dpadCenter = document.querySelector(".dpad-center"); // Darker variant
|
||||
const actionButtons = document.querySelectorAll(".btn");
|
||||
|
||||
const colors = [
|
||||
{
|
||||
gameboyColor: "#B39DDB",
|
||||
htmlColor: "#D1C4E9",
|
||||
screenColor: "#E1BEE7",
|
||||
buttonColor: "#673AB7",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#5E35B1",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FFC107",
|
||||
htmlColor: "#FFF9C4",
|
||||
screenColor: "#FFEB3B",
|
||||
buttonColor: "#FF9800",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#EF6C00",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#8BC34A",
|
||||
htmlColor: "#C5E1A5",
|
||||
screenColor: "#A5D6A7",
|
||||
buttonColor: "#FF5722",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#E64A19",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#F44336",
|
||||
htmlColor: "#FFCDD2",
|
||||
screenColor: "#EF9A9A",
|
||||
buttonColor: "#E91E63",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#C2185B",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#03A9F4",
|
||||
htmlColor: "#BBDEFB",
|
||||
screenColor: "#90CAF9",
|
||||
buttonColor: "#FFEB3B",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#0277BD",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FF7043",
|
||||
htmlColor: "#FFCCBC",
|
||||
screenColor: "#FFAB91",
|
||||
buttonColor: "#FF5722",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#D84315",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#9C27B0",
|
||||
htmlColor: "#E1BEE7",
|
||||
screenColor: "#D1C4E9",
|
||||
buttonColor: "#7B1FA2",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#6A1B9A",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FFD700",
|
||||
htmlColor: "#FFF9C4",
|
||||
screenColor: "#FFF59D",
|
||||
buttonColor: "#FF9800",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#F57F17",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#009688",
|
||||
htmlColor: "#B2DFDB",
|
||||
screenColor: "#80CBC4",
|
||||
buttonColor: "#4CAF50",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#00796B",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#795548",
|
||||
htmlColor: "#D7CCC8",
|
||||
screenColor: "#A1887F",
|
||||
buttonColor: "#9E9E9E",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#5D4037",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FF5733",
|
||||
htmlColor: "#FFCCCB",
|
||||
screenColor: "#FFABAB",
|
||||
buttonColor: "#C70039",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#B71C1C",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#00BCD4",
|
||||
htmlColor: "#B2EBF2",
|
||||
screenColor: "#80DEEA",
|
||||
buttonColor: "#00ACC1",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#00838F",
|
||||
},
|
||||
];
|
||||
|
||||
let currentColorIndex = localStorage.getItem("gameboyColorIndex")
|
||||
? parseInt(localStorage.getItem("gameboyColorIndex"))
|
||||
: 0;
|
||||
|
||||
function updateGameBoyColor() {
|
||||
gameboy.style.backgroundColor = colors[currentColorIndex].gameboyColor;
|
||||
html.style.backgroundColor = colors[currentColorIndex].htmlColor;
|
||||
body.style.backgroundColor = colors[currentColorIndex].htmlColor;
|
||||
screen.style.backgroundColor = colors[currentColorIndex].screenColor;
|
||||
|
||||
dpadButtons.forEach((button) => {
|
||||
button.style.backgroundColor = colors[currentColorIndex].buttonColor;
|
||||
button.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
});
|
||||
|
||||
// Using darker dpad center color
|
||||
dpadCenter.style.backgroundColor = colors[currentColorIndex].dpadCenterColor;
|
||||
dpadCenter.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
|
||||
actionButtons.forEach((button) => {
|
||||
button.style.backgroundColor = colors[currentColorIndex].buttonColor;
|
||||
button.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
});
|
||||
}
|
||||
|
||||
left.addEventListener("click", () => {
|
||||
currentColorIndex = (currentColorIndex - 1 + colors.length) % colors.length;
|
||||
localStorage.setItem("gameboyColorIndex", currentColorIndex);
|
||||
updateGameBoyColor();
|
||||
});
|
||||
|
||||
right.addEventListener("click", () => {
|
||||
currentColorIndex = (currentColorIndex + 1) % colors.length;
|
||||
localStorage.setItem("gameboyColorIndex", currentColorIndex);
|
||||
updateGameBoyColor();
|
||||
});
|
||||
|
||||
updateGameBoyColor();
|
||||
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 949 KiB |
|
Before Width: | Height: | Size: 9 KiB |
|
Before Width: | Height: | Size: 573 KiB |
|
Before Width: | Height: | Size: 455 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
|
@ -1,118 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Secret Game Collection</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Secret Game Collection</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="grid-container">
|
||||
<a
|
||||
href="asteroidDestroyer/explenation.html"
|
||||
target="_blank"
|
||||
class="item"
|
||||
>
|
||||
<img src="images/asteroid.png" alt="Image can't be displayed" />
|
||||
<h2>Secret Asteroid Shooter</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
In this game, you control a spaceship that shoots at asteroids to
|
||||
avoid destruction and collect items for power-ups.
|
||||
</p>
|
||||
<p>
|
||||
Your goal is to survive as long as possible while scoring points!
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" target="_blank" class="item">
|
||||
<img src="images/default.jpeg" alt="Image can't be displayed" />
|
||||
<h2>Secret Blackjack</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Try to beat the dealer by getting a hand value as close to 21 as
|
||||
possible without going over.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" target="_blank" class="item">
|
||||
<img src="images/default.jpeg" alt="Image can't be displayed" />
|
||||
<h2>Snake</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Guide the snake to eat food and grow longer while avoiding
|
||||
collisions with the walls and itself.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" target="_blank" class="item">
|
||||
<img src="images/default.jpeg" alt="Image can't be displayed" />
|
||||
<h2>Solitaire</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
A classic card game where the objective is to move all cards to
|
||||
foundation piles in ascending order.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="mineSweeper/index.html" target="_blank" class="item">
|
||||
<img src="images/minesweeper.png" alt="Image can't be displayed" />
|
||||
<h2>Minesweeper</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Uncover squares on a grid while avoiding hidden mines, using
|
||||
numbers to deduce safe spots.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="guessMyNumber/index.html" target="_blank" class="item">
|
||||
<img src="images/number.jpeg" alt="Image can't be displayed" />
|
||||
<h2>Guess My Number</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
A simple game where you try to guess a randomly chosen number
|
||||
within a certain range.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" target="_blank" class="item">
|
||||
<img src="images/default.jpeg" alt="Image can't be displayed" />
|
||||
<h2>Endless Runner</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Run through an endless landscape, avoiding obstacles and
|
||||
collecting items to score points.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Game Collection</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Minesweeper</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="settings">
|
||||
<h1>Minesweeper</h1>
|
||||
<label for="gridSize">Grid Size:</label>
|
||||
<input
|
||||
type="number"
|
||||
id="gridSize"
|
||||
min="6"
|
||||
max="25"
|
||||
value="9"
|
||||
aria-label="Grid Size"
|
||||
/>
|
||||
|
||||
<label for="bombs">Number of Bombs:</label>
|
||||
<input
|
||||
type="number"
|
||||
id="bombs"
|
||||
min="1"
|
||||
max="300"
|
||||
value="9"
|
||||
aria-label="Number of Bombs"
|
||||
/>
|
||||
|
||||
<button id="startGame">Start Game</button>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<canvas id="game"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
document.getElementById("startGame").addEventListener("click", function () {
|
||||
const gridSize = parseInt(document.getElementById("gridSize").value);
|
||||
const bombs = parseInt(document.getElementById("bombs").value);
|
||||
|
||||
document.getElementById("settings").style.display = "none";
|
||||
document.getElementById("game").style.display = "block";
|
||||
|
||||
renderGame(gridSize, bombs);
|
||||
});
|
||||
|
||||
const canvas = document.getElementById("game");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
class Minesweeper {
|
||||
constructor(width, height, bombs) {
|
||||
this.size = 25;
|
||||
this.field = new Array(width);
|
||||
this.bombs = new Array(width);
|
||||
for (let x = 0; x < this.field.length; x++) {
|
||||
this.field[x] = new Array(height);
|
||||
this.bombs[x] = new Array(height);
|
||||
for (let y = 0; y < this.field[x].length; y++) {
|
||||
this.field[x][y] = 1;
|
||||
this.bombs[x][y] = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.bombAmount =
|
||||
bombs > (width * height) / 2 ? (width * height) / 2 : bombs;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.firstClick = false;
|
||||
this.drawField();
|
||||
}
|
||||
|
||||
generateBombs() {
|
||||
for (let i = 0; i < this.bombAmount; i++) {
|
||||
let x = Math.floor(Math.random() * this.width);
|
||||
let y = Math.floor(Math.random() * this.height);
|
||||
this.bombs[x][y] || this.field[x][y] == 0
|
||||
? i--
|
||||
: (this.bombs[x][y] = true);
|
||||
}
|
||||
this.firstClick = true;
|
||||
}
|
||||
|
||||
getNearbyBombs(x, y) {
|
||||
let counter = 0;
|
||||
for (let newX = x - 1; newX <= x + 1; newX++) {
|
||||
for (let newY = y - 1; newY <= y + 1; newY++) {
|
||||
if (
|
||||
newX < this.field.length &&
|
||||
newX >= 0 &&
|
||||
newY < this.field[0].length &&
|
||||
newY >= 0
|
||||
) {
|
||||
this.bombs[newX][newY] ? counter++ : {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
checkWin() {
|
||||
for (let x = 0; x < this.field.length; x++) {
|
||||
for (let y = 0; y < this.field[x].length; y++) {
|
||||
if (this.field[x][y] != 0 && !this.bombs[x][y]) {
|
||||
return;
|
||||
} else if (this.field[x][y] == 0 && this.bombs[x][y]) {
|
||||
alert(`[ERROR]: Square (${x}|${y}) should not be a bomb!`);
|
||||
this.bombs[x][y] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.drawField();
|
||||
alert("You won!");
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
markSquare(x, y) {
|
||||
if (x < this.field.length && y < this.field[0].length) {
|
||||
switch (this.field[x][y]) {
|
||||
case 1:
|
||||
this.field[x][y]++;
|
||||
break;
|
||||
case 2:
|
||||
this.field[x][y]++;
|
||||
break;
|
||||
case 3:
|
||||
this.field[x][y] = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.drawField();
|
||||
}
|
||||
}
|
||||
|
||||
uncoverSquare(x, y) {
|
||||
if (x < this.field.length && x >= 0 && y < this.field[0].length && y >= 0) {
|
||||
if (this.bombs[x][y] && this.field[x][y] == 1) {
|
||||
alert("You lost!");
|
||||
window.location.reload();
|
||||
} else if (this.field[x][y] == 1) {
|
||||
this.field[x][y] = 0;
|
||||
!this.firstClick ? this.generateBombs() : {};
|
||||
if (this.getNearbyBombs(x, y) == 0) {
|
||||
for (let newX = x - 1; newX <= x + 1; newX++) {
|
||||
for (let newY = y - 1; newY <= y + 1; newY++) {
|
||||
if (
|
||||
newX < this.field.length &&
|
||||
newX >= 0 &&
|
||||
newY < this.field[0].length &&
|
||||
newY >= 0
|
||||
) {
|
||||
this.field[newX][newY] == 1
|
||||
? this.uncoverSquare(newX, newY)
|
||||
: {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkWin();
|
||||
this.drawField();
|
||||
}
|
||||
}
|
||||
|
||||
drawSquare(x, y, type) {
|
||||
ctx.lineWidth = 3;
|
||||
let uncovered = (x + y) % 2 === 0 ? "#D3D3D3" : "#A9A9A9";
|
||||
let covered = (x + y) % 2 === 0 ? "#4CAF50" : "#81C784";
|
||||
ctx.fillStyle = type != 0 ? covered : uncovered;
|
||||
ctx.fillRect(x * this.size, y * this.size, this.size, this.size);
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.strokeRect(x * this.size, y * this.size, this.size, this.size);
|
||||
|
||||
if (type != 1) {
|
||||
const fontSize = this.size / 2;
|
||||
const number = this.getNearbyBombs(x, y);
|
||||
let finalPrint;
|
||||
ctx.font = `${fontSize}px sans-serif`;
|
||||
ctx.fillStyle = "#000";
|
||||
type == 0 ? (finalPrint = number ? number : " ") : {};
|
||||
type == 2 ? (finalPrint = "🚩") : {};
|
||||
type == 3 ? (finalPrint = "❓") : {};
|
||||
ctx.fillText(
|
||||
finalPrint,
|
||||
x * this.size + fontSize / (type == 0 ? 1.5 : 1.8),
|
||||
y * this.size + fontSize * 1.3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
drawField() {
|
||||
if (window.innerWidth > window.innerHeight) {
|
||||
this.size = window.innerHeight / (this.field[0].length + 4);
|
||||
} else {
|
||||
this.size = window.innerWidth / (this.field.length + 4);
|
||||
}
|
||||
|
||||
canvas.width = this.size * this.field.length;
|
||||
canvas.height = this.size * this.field[0].length;
|
||||
|
||||
const offsetX = (canvas.width - this.field.length * this.size) / 2;
|
||||
const offsetY = (canvas.height - this.field[0].length * this.size) / 2;
|
||||
|
||||
for (let x = 0; x < this.field.length; x++) {
|
||||
for (let y = 0; y < this.field[x].length; y++) {
|
||||
this.drawSquare(x, y, this.field[x][y], offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renderGame = (gridSize, bombs) => {
|
||||
let field = new Minesweeper(gridSize, gridSize, bombs);
|
||||
window.addEventListener("resize", () => field.drawField());
|
||||
canvas.addEventListener("click", (event) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = Math.floor((event.clientX - rect.left) / field.size);
|
||||
const y = Math.floor((event.clientY - rect.top) / field.size);
|
||||
field.uncoverSquare(x, y);
|
||||
});
|
||||
canvas.addEventListener("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = Math.floor((event.clientX - rect.left) / field.size);
|
||||
const y = Math.floor((event.clientY - rect.top) / field.size);
|
||||
field.markSquare(x, y);
|
||||
});
|
||||
};
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
background-color: #1e1e1e;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.5);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 12px;
|
||||
font-size: 20px;
|
||||
color: #d1d1d1;
|
||||
}
|
||||
|
||||
input[type="number"],
|
||||
input[type="range"],
|
||||
span {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
background-color: #333;
|
||||
color: #e0e0e0;
|
||||
font-size: 18px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
input[type="number"]:focus,
|
||||
input[type="range"]:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
#settings {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input[type="number"],
|
||||
input[type="range"],
|
||||
button {
|
||||
width: 90%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const cvs = document.getElementById("snake");
|
||||
const ctx = cvs.getContext("2d");
|
||||
const box = 20;
|
||||
|
||||
let snake = [{ x: 9 * box, y: 10 * box }];
|
||||
let food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
let score = 0;
|
||||
let d;
|
||||
let game;
|
||||
|
||||
document.addEventListener("keydown", direction);
|
||||
function direction(event) {
|
||||
let key = event.keyCode;
|
||||
if ((key == 37 || key == 65) && d != "RIGHT") d = "LEFT";
|
||||
else if ((key == 38 || key == 87) && d != "DOWN") d = "UP";
|
||||
else if ((key == 39 || key == 68) && d != "LEFT") d = "RIGHT";
|
||||
else if ((key == 40 || key == 83) && d != "UP") d = "DOWN";
|
||||
}
|
||||
|
||||
function collision(head, array) {
|
||||
return array.some((part) => head.x == part.x && head.y == part.y);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.fillStyle = "#0f380f";
|
||||
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
||||
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(food.x, food.y, box, box);
|
||||
|
||||
for (let i = 0; i < snake.length; i++) {
|
||||
ctx.fillStyle = i == 0 ? "lime" : "white";
|
||||
ctx.fillRect(snake[i].x, snake[i].y, box, box);
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.strokeRect(snake[i].x, snake[i].y, box, box);
|
||||
}
|
||||
|
||||
let snakeX = snake[0].x;
|
||||
let snakeY = snake[0].y;
|
||||
|
||||
if (d == "LEFT") snakeX -= box;
|
||||
if (d == "UP") snakeY -= box;
|
||||
if (d == "RIGHT") snakeX += box;
|
||||
if (d == "DOWN") snakeY += box;
|
||||
|
||||
if (snakeX == food.x && snakeY == food.y) {
|
||||
score++;
|
||||
food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
} else {
|
||||
snake.pop();
|
||||
}
|
||||
|
||||
let newHead = { x: snakeX, y: snakeY };
|
||||
|
||||
if (
|
||||
snakeX < 0 ||
|
||||
snakeX >= cvs.width ||
|
||||
snakeY < 0 ||
|
||||
snakeY >= cvs.height ||
|
||||
collision(newHead, snake)
|
||||
) {
|
||||
clearInterval(game);
|
||||
document.getElementById("restartBtn").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
snake.unshift(newHead);
|
||||
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "20px Arial";
|
||||
ctx.fillText("Score: " + score, 10, 20);
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
snake = [{ x: 9 * box, y: 10 * box }];
|
||||
food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
score = 0;
|
||||
d = "";
|
||||
document.getElementById("restartBtn").style.display = "none";
|
||||
game = setInterval(draw, 150);
|
||||
}
|
||||
|
||||
document.getElementById("restartBtn").addEventListener("click", restartGame);
|
||||
game = setInterval(draw, 150);
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
|
@ -1,67 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Snake - Game</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="gameboy">
|
||||
<!-- Game Boy Screen -->
|
||||
<div class="screen">
|
||||
<article class="game">
|
||||
<h1 class="title" id="title">Snake - Game</h1>
|
||||
<div class="description" id="description">
|
||||
<h2>Description</h2>
|
||||
<p>Eat as many apples and grow as much as possible</p>
|
||||
<p>◀ or A or arrow left = move left</p>
|
||||
<p>▶ or D or arrow right = move right</p>
|
||||
<p>▲ or W or arrow up = move up</p>
|
||||
<p>▼ or S or arrow down = move down</p>
|
||||
</div>
|
||||
<canvas id="snake" width="400" height="400"></canvas>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<!-- D-Pad on the left -->
|
||||
<div class="dpad">
|
||||
<button class="dpad-btn up" id="up">▲</button>
|
||||
<button class="dpad-btn left" id="left">◀</button>
|
||||
<div class="dpad-center"></div>
|
||||
<button class="dpad-btn right" id="right">▶</button>
|
||||
<button class="dpad-btn down" id="down">▼</button>
|
||||
</div>
|
||||
|
||||
<!-- A, B and start button on the right -->
|
||||
<div class="action-buttons">
|
||||
<button class="start-btn btn" id="start">Start</button>
|
||||
<button class="btn" id="a">A</button>
|
||||
<button class="btn" id="b">B</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="game.js"></script>
|
||||
<script src="styles.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
/* Base Reset */
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: monospace;
|
||||
background-color: #3a2d56;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* GameBoy Layout */
|
||||
.gameboy {
|
||||
background-color: #5f4c82;
|
||||
width: 441px;
|
||||
height: 735px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gameboy {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen */
|
||||
.screen {
|
||||
background-color: black;
|
||||
border: 4px solid #0f380f;
|
||||
width: 90%;
|
||||
height: 55%;
|
||||
margin-top: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: inset 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.game {
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
color: #9bbc0f;
|
||||
}
|
||||
|
||||
.description,
|
||||
.description p {
|
||||
font-size: 1.2rem;
|
||||
margin: 0 auto;
|
||||
padding: 0 auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Grid container */
|
||||
#grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr); /* Adjust to match gridSize */
|
||||
grid-template-rows: repeat(10, 1fr); /* Adjust to match gridSize */
|
||||
width: 400px; /* Adjust as needed */
|
||||
height: 400px; /* Adjust as needed */
|
||||
border: 2px solid #0f380f;
|
||||
margin: 20px auto;
|
||||
/* initially hide */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Individual cells */
|
||||
.cell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cell.light-green {
|
||||
background-color: #9bbc0f;
|
||||
}
|
||||
|
||||
.cell.dark-green {
|
||||
background-color: #0f380f;
|
||||
}
|
||||
|
||||
/* Snake styling */
|
||||
.snake {
|
||||
background-color: #e600ff; /* Snake color */
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Apple styling */
|
||||
.apple {
|
||||
background-color: red; /* Apple color */
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* Controls Section */
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* D-Pad */
|
||||
.dpad {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
/* Base Styling for D-Pad Buttons */
|
||||
.dpad-btn {
|
||||
background-color: #0f380f;
|
||||
color: #9bbc0f;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dpad-btn.up {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.down {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.left {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.dpad-btn.right {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* D-Pad Center to Connect Buttons */
|
||||
.dpad-center {
|
||||
background-color: #0f380f;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid transparent;
|
||||
z-index: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* A and B Buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #0f380f;
|
||||
color: #9bbc0f;
|
||||
border: 2px solid #9bbc0f;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.8rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #9bbc0f;
|
||||
color: #0f380f;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
/* Start Button */
|
||||
.start-btn {
|
||||
background-color: #0f380f;
|
||||
color: #9bbc0f;
|
||||
border: 2px solid #9bbc0f;
|
||||
border-radius: 5px;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s, background-color 0.2s;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.start-btn:hover {
|
||||
background-color: #9bbc0f;
|
||||
color: #0f380f;
|
||||
}
|
||||
|
||||
.start-btn:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
/* Hidden Canvas for Debugging or Fallback */
|
||||
canvas {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
"use strict";
|
||||
const aBtn = document.querySelector("#a");
|
||||
const bBtn = document.querySelector("#b");
|
||||
const gameboy = document.querySelector(".gameboy");
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
const dpadButtons = document.querySelectorAll(".dpad-btn");
|
||||
const dpadCenter = document.querySelector(".dpad-center"); // Darker variant
|
||||
const actionButtons = document.querySelectorAll(".btn");
|
||||
|
||||
const colors = [
|
||||
{
|
||||
gameboyColor: "#B39DDB",
|
||||
htmlColor: "#D1C4E9",
|
||||
buttonColor: "#673AB7",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#5E35B1",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FFC107",
|
||||
htmlColor: "#FFF9C4",
|
||||
buttonColor: "#FF9800",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#EF6C00",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#8BC34A",
|
||||
htmlColor: "#C5E1A5",
|
||||
buttonColor: "#FF5722",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#E64A19",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#F44336",
|
||||
htmlColor: "#FFCDD2",
|
||||
buttonColor: "#E91E63",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#C2185B",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#03A9F4",
|
||||
htmlColor: "#BBDEFB",
|
||||
buttonColor: "#FFEB3B",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#0277BD",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FF7043",
|
||||
htmlColor: "#FFCCBC",
|
||||
buttonColor: "#FF5722",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#D84315",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#9C27B0",
|
||||
htmlColor: "#E1BEE7",
|
||||
buttonColor: "#7B1FA2",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#6A1B9A",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FFD700",
|
||||
htmlColor: "#FFF9C4",
|
||||
buttonColor: "#FF9800",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#F57F17",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#009688",
|
||||
htmlColor: "#B2DFDB",
|
||||
buttonColor: "#4CAF50",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#00796B",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#795548",
|
||||
htmlColor: "#D7CCC8",
|
||||
buttonColor: "#9E9E9E",
|
||||
buttonTextColor: "#000000",
|
||||
dpadCenterColor: "#5D4037",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#FF5733",
|
||||
htmlColor: "#FFCCCB",
|
||||
buttonColor: "#C70039",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#B71C1C",
|
||||
},
|
||||
{
|
||||
gameboyColor: "#00BCD4",
|
||||
htmlColor: "#B2EBF2",
|
||||
buttonColor: "#00ACC1",
|
||||
buttonTextColor: "#FFFFFF",
|
||||
dpadCenterColor: "#00838F",
|
||||
},
|
||||
];
|
||||
|
||||
let currentColorIndex = localStorage.getItem("gameboyColorIndex")
|
||||
? parseInt(localStorage.getItem("gameboyColorIndex"))
|
||||
: 0;
|
||||
|
||||
function updateGameBoyColor() {
|
||||
gameboy.style.backgroundColor = colors[currentColorIndex].gameboyColor;
|
||||
html.style.backgroundColor = colors[currentColorIndex].htmlColor;
|
||||
body.style.backgroundColor = colors[currentColorIndex].htmlColor;
|
||||
|
||||
dpadButtons.forEach((button) => {
|
||||
button.style.backgroundColor = colors[currentColorIndex].buttonColor;
|
||||
button.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
});
|
||||
|
||||
// Using darker dpad center color
|
||||
dpadCenter.style.backgroundColor = colors[currentColorIndex].dpadCenterColor;
|
||||
dpadCenter.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
|
||||
actionButtons.forEach((button) => {
|
||||
button.style.backgroundColor = colors[currentColorIndex].buttonColor;
|
||||
button.style.color = colors[currentColorIndex].buttonTextColor;
|
||||
});
|
||||
}
|
||||
|
||||
aBtn.addEventListener("click", () => {
|
||||
currentColorIndex = (currentColorIndex - 1 + colors.length) % colors.length;
|
||||
localStorage.setItem("gameboyColorIndex", currentColorIndex);
|
||||
updateGameBoyColor();
|
||||
});
|
||||
|
||||
bBtn.addEventListener("click", () => {
|
||||
currentColorIndex = (currentColorIndex + 1) % colors.length;
|
||||
localStorage.setItem("gameboyColorIndex", currentColorIndex);
|
||||
updateGameBoyColor();
|
||||
});
|
||||
|
||||
updateGameBoyColor();
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Snake Game - GameBoy Style</title>
|
||||
<style>
|
||||
/* Base Reset */
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: monospace;
|
||||
background-color: #3a2d56;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* GameBoy Layout */
|
||||
.gameboy {
|
||||
background-color: #5f4c82;
|
||||
width: 441px;
|
||||
height: 735px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gameboy {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen */
|
||||
.screen {
|
||||
background-color: black;
|
||||
border: 4px solid #0f380f;
|
||||
width: 90%;
|
||||
height: 55%;
|
||||
margin-top: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: inset 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
/* Restart Button */
|
||||
#restartBtn {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
background-color: #0f380f;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restartBtn:hover {
|
||||
background-color: #9bbc0f;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
color: #9bbc0f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="gameboy">
|
||||
<h1>Snake Game</h1>
|
||||
<div class="screen">
|
||||
<canvas id="snake" width="400" height="400"></canvas>
|
||||
</div>
|
||||
<button id="restartBtn">Restart</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
const cvs = document.getElementById("snake");
|
||||
const ctx = cvs.getContext("2d");
|
||||
const box = 20;
|
||||
|
||||
let snake = [{ x: 9 * box, y: 10 * box }];
|
||||
let food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
let score = 0;
|
||||
let d;
|
||||
let game;
|
||||
|
||||
document.addEventListener("keydown", direction);
|
||||
function direction(event) {
|
||||
let key = event.keyCode;
|
||||
if ((key == 37 || key == 65) && d != "RIGHT") d = "LEFT";
|
||||
else if ((key == 38 || key == 87) && d != "DOWN") d = "UP";
|
||||
else if ((key == 39 || key == 68) && d != "LEFT") d = "RIGHT";
|
||||
else if ((key == 40 || key == 83) && d != "UP") d = "DOWN";
|
||||
}
|
||||
|
||||
function collision(head, array) {
|
||||
return array.some((part) => head.x == part.x && head.y == part.y);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.fillStyle = "#0f380f";
|
||||
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
||||
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(food.x, food.y, box, box);
|
||||
|
||||
for (let i = 0; i < snake.length; i++) {
|
||||
ctx.fillStyle = i == 0 ? "lime" : "white";
|
||||
ctx.fillRect(snake[i].x, snake[i].y, box, box);
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.strokeRect(snake[i].x, snake[i].y, box, box);
|
||||
}
|
||||
|
||||
let snakeX = snake[0].x;
|
||||
let snakeY = snake[0].y;
|
||||
|
||||
if (d == "LEFT") snakeX -= box;
|
||||
if (d == "UP") snakeY -= box;
|
||||
if (d == "RIGHT") snakeX += box;
|
||||
if (d == "DOWN") snakeY += box;
|
||||
|
||||
if (snakeX == food.x && snakeY == food.y) {
|
||||
score++;
|
||||
food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
} else {
|
||||
snake.pop();
|
||||
}
|
||||
|
||||
let newHead = { x: snakeX, y: snakeY };
|
||||
|
||||
if (
|
||||
snakeX < 0 ||
|
||||
snakeX >= cvs.width ||
|
||||
snakeY < 0 ||
|
||||
snakeY >= cvs.height ||
|
||||
collision(newHead, snake)
|
||||
) {
|
||||
clearInterval(game);
|
||||
document.getElementById("restartBtn").style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
snake.unshift(newHead);
|
||||
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "20px Arial";
|
||||
ctx.fillText("Score: " + score, 10, 20);
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
snake = [{ x: 9 * box, y: 10 * box }];
|
||||
food = {
|
||||
x: Math.floor(Math.random() * 19 + 1) * box,
|
||||
y: Math.floor(Math.random() * 19 + 1) * box,
|
||||
};
|
||||
score = 0;
|
||||
d = "";
|
||||
document.getElementById("restartBtn").style.display = "none";
|
||||
game = setInterval(draw, 150);
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("restartBtn")
|
||||
.addEventListener("click", restartGame);
|
||||
game = setInterval(draw, 150);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
background-color: #0d0d0d;
|
||||
color: #b0b0b0;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
background-image: url("images/background.jpg");
|
||||
background-size: cover; /* Adjust size for tape appearance */
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #222; /* Fully opaque background */
|
||||
color: #b0b0b0;
|
||||
text-align: center;
|
||||
padding: 1em 0;
|
||||
font-size: 2rem;
|
||||
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.7);
|
||||
animation: neonFlicker 1.5s infinite;
|
||||
}
|
||||
|
||||
/* Create the flickering neon light effect */
|
||||
@keyframes neonFlicker {
|
||||
0% {
|
||||
text-shadow: 0 0 5px #ffcc00, 0 0 10px #ffcc00, 0 0 15px #ffcc00,
|
||||
0 0 20px #ffcc00, 0 0 30px #ffcc00, 0 0 40px #ffcc00, 0 0 50px #ffcc00;
|
||||
}
|
||||
20% {
|
||||
text-shadow: 0 0 3px #ffcc00, 0 0 7px #ffcc00, 0 0 10px #ffcc00,
|
||||
0 0 15px #ffcc00, 0 0 20px #ffcc00;
|
||||
}
|
||||
40% {
|
||||
text-shadow: 0 0 5px #ffcc00, 0 0 15px #ffcc00, 0 0 25px #ffcc00;
|
||||
}
|
||||
60% {
|
||||
text-shadow: 0 0 5px #ffcc00, 0 0 10px #ffcc00, 0 0 15px #ffcc00,
|
||||
0 0 20px #ffcc00, 0 0 30px #ffcc00;
|
||||
}
|
||||
80% {
|
||||
text-shadow: 0 0 3px #ffcc00, 0 0 7px #ffcc00, 0 0 10px #ffcc00;
|
||||
}
|
||||
100% {
|
||||
text-shadow: 0 0 5px #ffcc00, 0 0 10px #ffcc00, 0 0 15px #ffcc00,
|
||||
0 0 20px #ffcc00, 0 0 30px #ffcc00, 0 0 40px #ffcc00;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #111;
|
||||
color: #b0b0b0;
|
||||
text-align: center;
|
||||
padding: 1em 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
background-color: #1a1a1a;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.8);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
|
||||
.item .description {
|
||||
padding: 30px;
|
||||
font-size: 1rem;
|
||||
color: #ccc;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 0 0 10px 10px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
p {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.9);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.item:hover img {
|
||||
transform: scale(1.1);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.item h2 {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #ffffff;
|
||||
font-size: 1.8rem;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.item:hover h2 {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(-10px);
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
header {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
BIN
src/favicon/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/favicon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/favicon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
src/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 385 KiB |
311
src/js/animation/Meteor.js
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
class Meteor {
|
||||
constructor(container) {
|
||||
this.element = document.createElement("div");
|
||||
this.element.style.position = "absolute";
|
||||
this.container = container;
|
||||
this.speed = Math.random() * 3 + 4;
|
||||
this.life = 1.0;
|
||||
this.particles = [];
|
||||
|
||||
this.colorType = Math.floor(Math.random() * 4);
|
||||
this.colors = this.getMeteorColors();
|
||||
|
||||
this.containerRect = container.getBoundingClientRect();
|
||||
|
||||
// Start from random edge with PIXEL-based positioning
|
||||
const edge = Math.floor(Math.random() * 4);
|
||||
switch (edge) {
|
||||
case 0:
|
||||
// Top
|
||||
this.x = Math.random() * this.containerRect.width;
|
||||
this.y = -20;
|
||||
case 1:
|
||||
// Right
|
||||
this.x = this.containerRect.width + 20;
|
||||
this.y = Math.random() * this.containerRect.height;
|
||||
case 2:
|
||||
// Bottom
|
||||
this.x = Math.random() * this.containerRect.width;
|
||||
this.y = this.containerRect.height + 20;
|
||||
case 3:
|
||||
// Left
|
||||
this.x = -20;
|
||||
this.y = Math.random() * this.containerRect.height;
|
||||
default:
|
||||
console.log("Error creating Meteor direction");
|
||||
}
|
||||
|
||||
const centerX = this.containerRect.width / 2 + (Math.random() - 0.5) * 200;
|
||||
const centerY = this.containerRect.height / 2 + (Math.random() - 0.5) * 200;
|
||||
this.angle =
|
||||
Math.atan2(centerY - this.y, centerX - this.x) +
|
||||
(Math.random() - 0.5) * 0.8;
|
||||
this.dx = Math.cos(this.angle) * this.speed;
|
||||
this.dy = Math.sin(this.angle) * this.speed;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
getMeteorColors() {
|
||||
const colorSchemes = [
|
||||
{
|
||||
head: "rgba(255, 255, 255, 1)",
|
||||
core: "rgba(255, 230, 150, 0.9)",
|
||||
mid: "rgba(255, 180, 80, 0.7)",
|
||||
outer: "rgba(255, 100, 50, 0.5)",
|
||||
glow: "rgba(255, 200, 100, 0.9)",
|
||||
},
|
||||
{
|
||||
head: "rgba(255, 255, 255, 1)",
|
||||
core: "rgba(180, 220, 255, 0.9)",
|
||||
mid: "rgba(100, 180, 255, 0.7)",
|
||||
outer: "rgba(50, 100, 255, 0.5)",
|
||||
glow: "rgba(100, 180, 255, 0.9)",
|
||||
},
|
||||
{
|
||||
head: "rgba(255, 255, 255, 1)",
|
||||
core: "rgba(220, 180, 255, 0.9)",
|
||||
mid: "rgba(200, 100, 255, 0.7)",
|
||||
outer: "rgba(180, 50, 255, 0.5)",
|
||||
glow: "rgba(200, 150, 255, 0.9)",
|
||||
},
|
||||
{
|
||||
head: "rgba(255, 255, 255, 1)",
|
||||
core: "rgba(180, 255, 200, 0.9)",
|
||||
mid: "rgba(80, 255, 150, 0.7)",
|
||||
outer: "rgba(50, 200, 100, 0.5)",
|
||||
glow: "rgba(150, 255, 180, 0.9)",
|
||||
},
|
||||
];
|
||||
return colorSchemes[this.colorType];
|
||||
}
|
||||
|
||||
init() {
|
||||
const size = Math.random() * 4 + 10;
|
||||
const colors = this.colors;
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.style.width = `${size}px`;
|
||||
head.style.height = `${size}px`;
|
||||
head.style.borderRadius = "50%";
|
||||
head.style.background = `
|
||||
radial-gradient(
|
||||
circle at 30% 30%,
|
||||
${colors.head} 0%,
|
||||
${colors.core} 30%,
|
||||
${colors.mid} 60%,
|
||||
${colors.outer} 100%
|
||||
)
|
||||
`;
|
||||
head.style.boxShadow = `
|
||||
0 0 ${size * 4}px ${colors.glow},
|
||||
0 0 ${size * 2}px rgba(255, 255, 255, 0.8),
|
||||
inset 0 0 ${size * 0.5}px rgba(255, 255, 255, 0.9)
|
||||
`;
|
||||
head.style.position = "absolute";
|
||||
head.style.left = "50%";
|
||||
head.style.top = "50%";
|
||||
head.style.transform = "translate(-50%, -50%)";
|
||||
head.style.zIndex = "3";
|
||||
head.style.filter = "blur(0.5px)";
|
||||
|
||||
// Create multi-layer tail
|
||||
this.createTailLayer(size * 3, size * 1.2, colors.core, 1, "1px");
|
||||
this.createTailLayer(size * 5, size * 2, colors.mid, 0.6, "2px");
|
||||
this.createTailLayer(size * 8, size * 3, colors.outer, 0.3, "3px");
|
||||
|
||||
// Assemble meteor
|
||||
this.element.appendChild(head);
|
||||
this.element.style.left = `${this.x}px`;
|
||||
this.element.style.top = `${this.y}px`;
|
||||
this.element.style.zIndex = "10";
|
||||
this.element.style.filter = "blur(0.5px)";
|
||||
|
||||
this.head = head;
|
||||
this.size = size;
|
||||
|
||||
this.container.appendChild(this.element);
|
||||
|
||||
this.createTrailParticles();
|
||||
}
|
||||
|
||||
createTailLayer(length, width, color, opacity, blur) {
|
||||
const tail = document.createElement("div");
|
||||
const tailAngle = Math.atan2(this.dy, this.dx) + Math.PI;
|
||||
|
||||
tail.style.width = `${length}px`;
|
||||
tail.style.height = `${width}px`;
|
||||
tail.style.position = "absolute";
|
||||
tail.style.left = "50%";
|
||||
tail.style.top = "50%";
|
||||
tail.style.transformOrigin = "left center";
|
||||
tail.style.transform = `translate(0, -50%) rotate(${tailAngle}rad)`;
|
||||
tail.style.background = `linear-gradient(to right,
|
||||
${color} 0%,
|
||||
${color.replace(")", ", 0.8)").replace("rgba", "rgba")} 20%,
|
||||
${color.replace(")", ", 0.4)").replace("rgba", "rgba")} 50%,
|
||||
${color.replace(")", ", 0.1)").replace("rgba", "rgba")} 80%,
|
||||
transparent 100%)`;
|
||||
tail.style.opacity = opacity;
|
||||
tail.style.filter = `blur(${blur})`;
|
||||
tail.style.pointerEvents = "none";
|
||||
tail.style.zIndex = "2";
|
||||
tail.style.borderRadius = "0 50% 50% 0";
|
||||
|
||||
this.element.appendChild(tail);
|
||||
this.tail = tail;
|
||||
}
|
||||
|
||||
createTrailParticles() {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this.addTrailParticle();
|
||||
}
|
||||
}
|
||||
|
||||
addTrailParticle() {
|
||||
const particle = document.createElement("div");
|
||||
const size = Math.random() * 2 + 1;
|
||||
const colors = this.colors;
|
||||
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
particle.style.borderRadius = "50%";
|
||||
particle.style.background = colors.mid;
|
||||
particle.style.position = "absolute";
|
||||
particle.style.left = `${this.x}px`;
|
||||
particle.style.top = `${this.y}px`;
|
||||
particle.style.boxShadow = `0 0 ${size * 3}px ${colors.glow}`;
|
||||
particle.style.opacity = "0.7";
|
||||
particle.style.zIndex = "1";
|
||||
particle.style.pointerEvents = "none";
|
||||
|
||||
this.container.appendChild(particle);
|
||||
|
||||
this.particles.push({
|
||||
element: particle,
|
||||
life: 1.0,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: (Math.random() - 0.5) * 0.5,
|
||||
size: size,
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.dx;
|
||||
this.y += this.dy;
|
||||
this.life -= 0.005;
|
||||
|
||||
this.element.style.left = `${this.x}px`;
|
||||
this.element.style.top = `${this.y}px`;
|
||||
this.element.style.opacity = this.life;
|
||||
|
||||
// Update tail direction
|
||||
const tailAngle = Math.atan2(this.dy, this.dx) + Math.PI;
|
||||
const tails = this.element.querySelectorAll("div:not(.particle)");
|
||||
tails.forEach((tail) => {
|
||||
if (tail !== this.head) {
|
||||
tail.style.transform = `translate(0, -50%) rotate(${tailAngle}rad)`;
|
||||
tail.style.opacity = this.life;
|
||||
}
|
||||
});
|
||||
|
||||
if (Math.random() < 0.3) {
|
||||
this.addTrailParticle();
|
||||
}
|
||||
|
||||
// Update existing particles with pixel positioning
|
||||
for (let i = this.particles.length - 1; i >= 0; i--) {
|
||||
const p = this.particles[i];
|
||||
p.life -= 0.02;
|
||||
p.x += p.vx;
|
||||
p.y += p.vy;
|
||||
|
||||
p.element.style.left = `${p.x}px`;
|
||||
p.element.style.top = `${p.y}px`;
|
||||
p.element.style.opacity = p.life;
|
||||
p.element.style.transform = `scale(${p.life})`;
|
||||
|
||||
if (p.life <= 0) {
|
||||
p.element.remove();
|
||||
this.particles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.x < -50 ||
|
||||
this.x > this.containerRect.width + 50 ||
|
||||
this.y < -50 ||
|
||||
this.y > this.containerRect.height + 50 ||
|
||||
this.life <= 0
|
||||
) {
|
||||
// Fade out particles
|
||||
this.particles.forEach((p) => {
|
||||
p.element.style.transition = "opacity 0.5s";
|
||||
p.element.style.opacity = "0";
|
||||
setTimeout(() => p.element.remove(), 500);
|
||||
});
|
||||
this.remove();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.life > 0.3) {
|
||||
this.createExplosion();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.element.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
createExplosion() {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const particle = document.createElement("div");
|
||||
const size = Math.random() * 3 + 2;
|
||||
const colors = this.colors;
|
||||
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
particle.style.borderRadius = "50%";
|
||||
particle.style.background = colors.core;
|
||||
particle.style.position = "absolute";
|
||||
particle.style.left = `${this.x}px`;
|
||||
particle.style.top = `${this.y}px`;
|
||||
particle.style.boxShadow = `0 0 ${size * 4}px ${colors.glow}`;
|
||||
particle.style.opacity = "0.8";
|
||||
particle.style.zIndex = "2";
|
||||
|
||||
this.container.appendChild(particle);
|
||||
|
||||
// Animate explosion
|
||||
const angle = (i / 8) * Math.PI * 2;
|
||||
const distance = Math.random() * 20 + 10;
|
||||
const duration = Math.random() * 500 + 500;
|
||||
|
||||
particle.animate(
|
||||
[
|
||||
{
|
||||
transform: "translate(0, 0) scale(1)",
|
||||
opacity: 0.8,
|
||||
},
|
||||
{
|
||||
transform: `translate(${Math.cos(angle) * distance}px, ${
|
||||
Math.sin(angle) * distance
|
||||
}px) scale(0.1)`,
|
||||
opacity: 0,
|
||||
},
|
||||
],
|
||||
{
|
||||
duration: duration,
|
||||
easing: "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}
|
||||
).onfinish = () => particle.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Meteor;
|
||||
63
src/js/animation/Star.js
Normal 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;
|
||||
81
src/js/animation/starBackground.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import Star from "./Star.js";
|
||||
import Meteor from "./Meteor.js";
|
||||
|
||||
const starInstances = [];
|
||||
const meteorInstances = [];
|
||||
|
||||
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() {
|
||||
const spawnChance = 0.12;
|
||||
if (Math.random() < spawnChance) {
|
||||
const stars = document.getElementById("stars");
|
||||
const newMeteor = new Meteor(stars);
|
||||
meteorInstances.push(newMeteor);
|
||||
}
|
||||
}
|
||||
|
||||
function animateStars() {
|
||||
starInstances.forEach((star) => star.update());
|
||||
|
||||
// Update meteors and remove dead ones
|
||||
for (let i = meteorInstances.length - 1; i >= 0; i--) {
|
||||
if (!meteorInstances[i].update()) {
|
||||
meteorInstances.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animateStars);
|
||||
}
|
||||
|
||||
function createMeteorBurst() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => {
|
||||
const stars = document.getElementById("stars");
|
||||
const newMeteor = new Meteor(stars);
|
||||
meteorInstances.push(newMeteor);
|
||||
}, i * 200);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
injectStarCSS();
|
||||
createStars();
|
||||
animateStars();
|
||||
|
||||
setInterval(trySpawnMeteor, 2000);
|
||||
setInterval(createMeteorBurst, 15000);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
22
src/js/form.js
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
432
styles.css
|
|
@ -1,432 +0,0 @@
|
|||
/*
|
||||
interstellar_development website
|
||||
Copyright (C) 2024 interstellar_development
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--background-color: #0b0c1d;
|
||||
--text-color: #c7d5e0;
|
||||
--accent-color: #ffdd55;
|
||||
--accent-hover-color: #ffd700;
|
||||
--dark-blue: rgba(31, 42, 64, 1);
|
||||
--dark-blue-translucent: rgba(31, 42, 64, 0.9);
|
||||
--light-blue: rgba(46, 58, 95, 0.8);
|
||||
--border-radius: 8px;
|
||||
--transition-speed: 0.3s;
|
||||
--box-shadow: 0 2px 15px rgba(0, 0, 0, 0.7);
|
||||
--font-size-large: 2.5em;
|
||||
--font-size-medium: 1.8em;
|
||||
}
|
||||
|
||||
/* Reset and normalize */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Body styling */
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
font-family: "Arial", sans-serif;
|
||||
line-height: 1.6;
|
||||
padding: 0 20px;
|
||||
background: url("images/star.jpg") no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
/* Header Styling */
|
||||
/* Header Styling */
|
||||
header {
|
||||
background-color: var(--dark-blue);
|
||||
height: 5em;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 15px 20px;
|
||||
box-shadow: var(--box-shadow);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 100;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* Burger Menu Styling */
|
||||
.burger-menu {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
font-size: 1.8em;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 0;
|
||||
z-index: 110;
|
||||
}
|
||||
|
||||
/* Dropdown Menu (Hidden by Default) */
|
||||
.div-menu {
|
||||
z-index: 1;
|
||||
background-color: var(--light-blue);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
padding: 0;
|
||||
margin-top: 0px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.div-menu li {
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.div-menu a {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.div-menu a:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
/* Menu Animation and Styling */
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--light-blue);
|
||||
position: absolute;
|
||||
top: -50vh;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 5;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-weight: bolder;
|
||||
font-size: large;
|
||||
transition: top 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.menu.active {
|
||||
display: flex;
|
||||
top: 4em;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* Header Content Container */
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Project Name Styling */
|
||||
.project-name {
|
||||
font-size: 2em;
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-speed), text-shadow var(--transition-speed);
|
||||
}
|
||||
|
||||
.project-name:hover {
|
||||
color: var(--accent-hover-color);
|
||||
text-shadow: 0 0 10px var(--accent-hover-color);
|
||||
}
|
||||
|
||||
/* Article styling */
|
||||
article {
|
||||
max-width: 800px;
|
||||
margin: 6.25em auto;
|
||||
padding: 20px;
|
||||
background-color: var(--dark-blue-translucent);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
article h1 {
|
||||
font-size: var(--font-size-large);
|
||||
margin-bottom: 20px;
|
||||
color: var(--accent-color);
|
||||
text-shadow: 0 0 15px var(--accent-color);
|
||||
}
|
||||
|
||||
article p {
|
||||
color: var(--text-color);
|
||||
margin-bottom: 20px;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
/* Download list */
|
||||
article h2 {
|
||||
font-size: var(--font-size-medium);
|
||||
margin: 20px 0 10px;
|
||||
color: var(--accent-color);
|
||||
text-shadow: 0 0 10px var(--accent-color);
|
||||
}
|
||||
|
||||
article ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
article ul a li {
|
||||
background-color: var(--light-blue);
|
||||
margin-bottom: 10px;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px;
|
||||
transition: background-color var(--transition-speed),
|
||||
box-shadow var(--transition-speed);
|
||||
}
|
||||
|
||||
article ul a li:hover {
|
||||
background-color: rgba(68, 80, 124, 0.9);
|
||||
box-shadow: 0 0 10px var(--accent-color);
|
||||
}
|
||||
|
||||
article ul a li {
|
||||
text-decoration: none;
|
||||
color: var(--accent-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Footer styling */
|
||||
footer {
|
||||
background-color: var(--dark-blue);
|
||||
padding: 10px 20px;
|
||||
color: var(--text-color);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Card container styles */
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* Display 3 cards per line */
|
||||
gap: 20px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
/* Ensure the <a> tag covers the entire card */
|
||||
section .card a {
|
||||
display: flex; /* Use flex to make <a> fill the card and align content */
|
||||
flex-direction: column;
|
||||
justify-content: center; /* Vertically center the content */
|
||||
align-items: center; /* Horizontally center the content */
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Card styles */
|
||||
section .card {
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 50, 0.9),
|
||||
rgba(10, 10, 100, 0.9),
|
||||
rgba(30, 30, 150, 0.9)
|
||||
);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 50, 0.8), 0 0 10px rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid #2e3a60;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
transition: background 0.5s ease, transform 0.4s ease, box-shadow 0.5s ease;
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
section .card:hover {
|
||||
transform: translateY(-8px);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(30, 30, 150, 0.9),
|
||||
rgba(40, 0, 100, 0.9),
|
||||
rgba(100, 0, 150, 0.9)
|
||||
);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 100, 0.7), 0 0 20px rgba(255, 221, 85, 0.8);
|
||||
}
|
||||
|
||||
section .card img {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto 15px;
|
||||
box-shadow: 0 0 15px rgba(255, 221, 85, 0.5);
|
||||
}
|
||||
|
||||
section .card h3 {
|
||||
margin: 10px 0;
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
color: rgba(255, 221, 85, 1);
|
||||
text-shadow: 0 0 15px rgba(255, 221, 85, 0.9);
|
||||
}
|
||||
|
||||
section .card p {
|
||||
flex-grow: 1;
|
||||
color: rgba(200, 220, 255, 0.8);
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
section .card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: -20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 255, 255, 0.1),
|
||||
rgba(255, 255, 255, 0.02)
|
||||
);
|
||||
box-shadow: 0 0 50px rgba(255, 255, 255, 0.5);
|
||||
animation: spin 8s linear infinite;
|
||||
}
|
||||
|
||||
section .card .suit-icon {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0.7;
|
||||
transition: opacity var(--transition-speed);
|
||||
}
|
||||
|
||||
section .card:hover .suit-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Keyframes for spinning element */
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form styling */
|
||||
form {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: var(--dark-blue-translucent);
|
||||
padding: 20px;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
form label,
|
||||
form input,
|
||||
form textarea {
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-blue);
|
||||
border: 1px solid #3a4b7f;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form input[type="submit"] {
|
||||
background-color: var(--accent-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-speed);
|
||||
}
|
||||
|
||||
form input[type="submit"]:hover {
|
||||
background-color: var(--accent-hover-color);
|
||||
}
|
||||
|
||||
/* Footer Styling */
|
||||
footer {
|
||||
background-color: var(--dark-blue);
|
||||
padding: 10px 20px;
|
||||
color: var(--text-color);
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.cards {
|
||||
grid-template-columns: 1fr; /* 1 card per line on smaller screens */
|
||||
}
|
||||
|
||||
header ul {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
article {
|
||||
margin: 12em 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
section .card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
section .card img {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Game Collection</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="../favicon_io/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="../favicon_io/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="../favicon_io/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="../favicon_io/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Game Collection</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="grid-container">
|
||||
<a href="" target="_blank" class="item">
|
||||
<img
|
||||
src="../secret/../secret/images/default.jpeg"
|
||||
alt="Image can't be displayed"
|
||||
/>
|
||||
<h2>Snake</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Guide the snake to eat food and grow longer while avoiding
|
||||
collisions with the walls and itself.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" target="_blank" class="item">
|
||||
<img
|
||||
src="../secret/images/default.jpeg"
|
||||
alt="Image can't be displayed"
|
||||
/>
|
||||
<h2>Solitaire</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
A classic card game where the objective is to move all cards to
|
||||
foundation piles in ascending order.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="../secret/mineSweeper/index.html" target="_blank" class="item">
|
||||
<img
|
||||
src="../secret/images/minesweeper.png"
|
||||
alt="Image can't be displayed"
|
||||
/>
|
||||
<h2>Minesweeper</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Uncover squares on a grid while avoiding hidden mines, using
|
||||
numbers to deduce safe spots.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="../secret/guessMyNumber/index.html"
|
||||
target="_blank"
|
||||
class="item"
|
||||
>
|
||||
<img
|
||||
src="../secret/images/number.jpeg"
|
||||
alt="Image can't be displayed"
|
||||
/>
|
||||
<h2>Guess My Number</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
A simple game where you try to guess a randomly chosen number
|
||||
within a certain range.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="" target="_blank" class="item">
|
||||
<img
|
||||
src="../secret/images/default.jpeg"
|
||||
alt="Image can't be displayed"
|
||||
/>
|
||||
<h2>Endless Runner</h2>
|
||||
<div class="description">
|
||||
<p>
|
||||
Run through an endless landscape, avoiding obstacles and
|
||||
collecting items to score points.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Game Collection</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/* Reset and box-sizing */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* General Styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #282c34;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1em 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1em 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Grid Styles */
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px; /* Space between items */
|
||||
padding: 20px; /* Space around the grid */
|
||||
}
|
||||
|
||||
/* Game Item */
|
||||
.item {
|
||||
position: relative;
|
||||
background-color: #444;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
|
||||
width: 100%; /* Ensure it takes full width of the column */
|
||||
height: 400px; /* Set a fixed height for all items */
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack children vertically */
|
||||
}
|
||||
|
||||
/* Ensure the image takes the top part of the card */
|
||||
.item img {
|
||||
width: 100%;
|
||||
height: 100%; /* Set a height for the image */
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.item .description {
|
||||
padding: 30px;
|
||||
font-size: 1rem;
|
||||
color: #ddd;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 0 0 10px 10px;
|
||||
flex-grow: 1; /* Allow description to take remaining space */
|
||||
}
|
||||
|
||||
p {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Hover effect for scaling and glowing */
|
||||
.item:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.item:hover img {
|
||||
transform: scale(1.1); /* Slight zoom-in effect for the image */
|
||||
filter: brightness(1.1); /* Increase image brightness */
|
||||
}
|
||||
|
||||
.item h2 {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.item:hover h2 {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(-10px); /* Move the title upwards with hover */
|
||||
}
|
||||
|
||||
/* Mobile Optimization */
|
||||
@media (max-width: 600px) {
|
||||
header {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
height: auto; /* Allow auto height on mobile for better responsiveness */
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||