update website with better performance and styles

This commit is contained in:
Frederik Spirgi
2026-10-07 10:56:35 +02:00
parent f34ef3b84c
commit 433bc8ee25
6 changed files with 1266 additions and 1369 deletions
BIN
View File
Binary file not shown.
+31 -26
View File
@@ -1,26 +1,31 @@
const darkModeToggle = document.getElementById('dark-mode-toggle'); const darkModeToggle = document.getElementById('dark-mode-toggle');
localStorage.setItem('darkMode', 'disabled'); const themeIcon = darkModeToggle.querySelector('.theme-icon');
if (localStorage.getItem('darkMode') === 'enabled') { const themeLabel = darkModeToggle.querySelector('.theme-label');
document.body.classList.add('dark-mode');
} function applyTheme(isDark) {
darkModeToggle.addEventListener('click', () => { document.body.classList.toggle('dark-mode', isDark);
document.body.classList.toggle('dark-mode'); darkModeToggle.setAttribute('aria-pressed', String(isDark));
darkModeToggle.classList.toggle('dark-mode'); themeIcon.textContent = isDark ? '☀' : '☾';
if (document.body.classList.contains('dark-mode')) { themeLabel.textContent = isDark ? 'Light mode' : 'Dark mode';
localStorage.setItem('darkMode', 'enabled'); document.querySelector('meta[name="theme-color"]').content = isDark ? '#0d1828' : '#e9f3fb';
darkModeToggle.style.color = "black"; window.dispatchEvent(new Event('themechange'));
darkModeToggle.innerHTML = "☀️<br>Toggle Light Mode"; }
} else {
localStorage.setItem('darkMode', 'disabled'); let savedTheme;
darkModeToggle.style.color = "white"; try {
darkModeToggle.innerHTML = "🌙<br>Toggle Dark Mode"; savedTheme = localStorage.getItem('darkMode');
} } catch {
}); savedTheme = null;
}
enterButton.addEventListener('click', () => {
if (document.body.classList.contains('dark-mode')) { applyTheme(savedTheme === 'enabled' || (savedTheme === null && window.matchMedia('(prefers-color-scheme: dark)').matches));
document.body.style.backgroundColor = "black";
} else { darkModeToggle.addEventListener('click', () => {
document.body.style.backgroundColor = "white"; const isDark = !document.body.classList.contains('dark-mode');
} applyTheme(isDark);
}); try {
localStorage.setItem('darkMode', isDark ? 'enabled' : 'disabled');
} catch {
// The chosen theme remains active for this visit when storage is unavailable.
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+200 -111
View File
@@ -1,111 +1,200 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fretux</title> <meta name="description" content="Explore Fretux's projects in an interactive pixel-art portfolio.">
<link rel="stylesheet" href="style.css"> <meta name="theme-color" content="#e9f3fb">
<link rel="icon" href="img/favicon.ico " type=image/x-icon> <title>Fretux — Interactive Portfolio</title>
</head> <link rel="stylesheet" href="style.css">
<body> <link rel="icon" href="img/favicon.ico" type="image/x-icon">
<!-- Welcome Screen --> </head>
<div id="welcome-screen"> <body>
<div id="welcome-content"> <!-- Welcome Screen -->
<h1 id="welcome-text">Welcome! Click the image below to begin your journey through my site!</h1> <div id="welcome-screen">
<img id="enter-button" src="img/Penguin_Normal.png" alt="Enter Website"/> <div id="welcome-content">
<img id="turn-device-sideways" src="img/turn-device-sideways.png" alt="Enter Website"/> <p class="eyebrow">Fretux</p>
<button id="dark-mode-toggle" class="dark-mode-toggle"> <h1 id="welcome-text">A little world to explore</h1>
🌙<br> <button id="enter-button" type="button">Start <span aria-hidden="true">→</span></button>
Toggle Dark Mode <p class="control-guide">← → / A D to move &nbsp;·&nbsp; ↑ / W to jump &nbsp;·&nbsp; Enter to interact</p>
</button> </div>
</div> </div>
</div> <button id="dark-mode-toggle" class="dark-mode-toggle" type="button" aria-pressed="false"><span class="theme-icon" aria-hidden="true">☾</span><span class="theme-label">Dark mode</span></button>
<button id="sound-toggle" type="button" aria-label="Mute sound effects" aria-pressed="true" title="Toggle sound (M)"><span id="sound-icon" aria-hidden="true">♪</span></button>
<!-- Game Container --> <noscript><div class="no-script">This interactive portfolio needs JavaScript. <a href="https://git.bachmannj.ch/Fretux">Browse the projects on Gitea instead.</a></div></noscript>
<div id="game-container">
<canvas id="game-canvas"></canvas> <!-- Game Container -->
<div id="content-overlay"> <div id="game-container">
<h1 id="section-title"></h1> <canvas id="game-canvas" tabindex="0" aria-label="Interactive snowy portfolio world"></canvas>
<p id="section-content"></p> <div id="block-info" class="hidden">
</div> <h1>Welcome!</h1>
<p>Welcome to my portfolio! Here, you'll discover the projects I've worked on, along with insights about my skills and background. If you're curious about my work or have any questions, feel free to reach out! Enjoy exploring!</p>
<!-- "?" Block Explanatory Text --> </div>
<div id="block-info" class="hidden"> </div>
<h1>Welcome!</h1>
<p>Welcome to my portfolio! Here, you'll discover the projects I've worked on, along with insights about my skills and background. If you're curious about my work or have any questions, feel free to reach out! Enjoy exploring!</p> <section id="projects">
</div> <div class="project">
</div> <h3 class="project-name">The_Tower</h3>
<p class="project-description">A simple tower defense game made in Unity.</p>
<section id="projects"> <p class="long-description" data-project-name="The_Tower">A Unity tower defense game where enemies attack a tower. The C# project includes enemy spawning, bullets, cannon effects, and upgrade cards.</p>
<div class="project"> <a class="link" href="https://git.bachmannj.ch/Fretux/The_Tower" data-project-name="The_Tower">Source code</a>
<h3 class="project-name">TRLStats</h3> <p class="tags">C#</p>
<p class="project-description">An interactive JavaScript and Python application designed to calculate damage statistics for the popular Roblox game 'The Red Lake'.</p> </div>
<img src="img/Project_TrlStats.png" alt="TRLStats"> <div class="project">
<p class="long-description" data-project-name="TRLStats">TRLStats was my first project, a damage calculator for the Roblox game 'The Red Lake'. This project combines Python, Flask for backend processing, and JavaScript to dynamically display data extracted from JSON files.</p> <h3 class="project-name">PortfolioWebsite</h3>
<a class="link" href="https://fretux.ch/trlstats" data-project-name="TRLStats">Link to the Website</a> <p class="project-description">An interactive portfolio built as a snowy 2D game.</p>
<p class="tags">HTML</p> <p class="long-description" data-project-name="PortfolioWebsite">This website is a small canvas platform game. Walk through the snowy world to discover projects and enter the house to learn about its creator.</p>
<p class="tags">CSS</p> <a class="link" href="https://git.bachmannj.ch/Fretux/PortfolioWebsite" data-project-name="PortfolioWebsite">Source code</a>
<p class="tags">Javascript</p> <p class="tags">JavaScript</p>
<p class="tags">Python</p> </div>
<p class="tags">JSON</p> <div class="project">
</div> <h3 class="project-name">anomaly-front-stats</h3>
<div class="project"> <p class="project-description">A web calculator for Anomaly Front stats and weapons.</p>
<h3 class="project-name">SBB-History</h3> <p class="long-description" data-project-name="anomaly-front-stats">A Flask website for calculating Anomaly Front stats, browsing and filtering weapon data, and sending suggestions. It follows the earlier TRL Stats project.</p>
<p class="project-description">A Website about the creation of the SBB (Swiss Train Corporation) with an <a class="link" href="https://git.bachmannj.ch/Fretux/anomaly-front-stats" data-project-name="anomaly-front-stats">Source code</a>
interactable map!</p> <p class="tags">Python / Flask</p>
<img src="img/SBB-History.png" alt="SBB-History"> </div>
<p class="long-description" data-project-name="SBB-History">For a School Project, me and 2 friends had to create a page about the economic history of a part of the swiss, which we chose to do about the "SBB", the Swiss Train Coorporation, founded in 1902. It is a simple Website, using HTML, CSS, and JavaScript, alongside the "Maptiler" API for the interactive map.</p> <div class="project">
<a class="link" href="https://sbb-history.fretux.ch" data-project-name="SBB-History">Link to the Website</a> <h3 class="project-name">AdapativeMobs</h3>
<p class="tags">HTML</p> <p class="project-description">A Minecraft mod that makes hostile mobs adapt to world progression.</p>
<p class="tags">CSS</p> <p class="long-description" data-project-name="AdapativeMobs">A server-side Minecraft Forge mod that scales vanilla hostile mobs as the world advances. Mobs can gain stronger attributes, better equipment, extra spawn pressure, and tactical behavior.</p>
<p class="tags">Javascript</p> <a class="link" href="https://git.bachmannj.ch/Fretux/AdapativeMobs" data-project-name="AdapativeMobs">Source code</a>
</div> <p class="tags">Java</p>
<div class="project"> </div>
<h3 class="project-name">KnockedBack</h3> <div class="project">
<p class="project-description">A minecraft forge mod for 1.20.1 that revamps dying!</p> <h3 class="project-name">WebsitePortfolio2</h3>
<img src="img/Knockedback.png" alt="KnockedBack"> <p class="project-description">A Next.js portfolio with project, about, and contact pages.</p>
<p class="long-description" data-project-name="KnockedBack">For a long time, I had wanted a mod that really changed how dying worked in PVP, to make it more tactical. However, there was none. So, I looked into the possibility of making my own minecraft mod, initially with neoforge, but after multiple setbacks, I decided to go with normal forge, as it was easier.</p> <p class="long-description" data-project-name="WebsitePortfolio2">A separate portfolio website built with Next.js. It presents Frederik's work through dedicated project, about, and contact pages.</p>
<a class="link" href="https://www.curseforge.com/minecraft/mc-mods/knockedback" data-project-name="KnockedBack">Link to the Mod</a> <a class="link" href="https://git.bachmannj.ch/Fretux/WebsitePortfolio2" data-project-name="WebsitePortfolio2">Source code</a>
<p class="tags">Java</p> <p class="tags">JavaScript</p>
</div> </div>
<div class="pagination-controls"> <div class="project">
<button id="prev-button" disabled>Previous</button> <h3 class="project-name">Shoppy</h3>
<button id="next-button">Next</button> <p class="project-description">A mobile shopping assistant for checking products in the store.</p>
</div> <p class="long-description" data-project-name="Shoppy">An Expo and React Native app concept for scanning product barcodes and judging them against personal goals such as allergies, nutrition, budget, and sustainability.</p>
<!-- More projects here --> <a class="link" href="https://git.bachmannj.ch/Fretux/Shoppy" data-project-name="Shoppy">Source code</a>
</section> <p class="tags">JavaScript</p>
<div id="detailsContainer" </div>
style="display:none; position:fixed; top:20%; left:20%; width:60%; height:60%; background:white; padding:20px; overflow:auto; box-shadow: 0 0 10px rgba(0,0,0,0.5);"> <div class="project">
<!-- Details will be loaded here --> <h3 class="project-name">linux_distro_roguelite_promotion</h3>
</div> <p class="project-description">The pre-registration site and backend for Linux Distro Roguelite.</p>
<p class="long-description" data-project-name="linux_distro_roguelite_promotion">A Quarkus backend and static pre-registration page for a free game. Players sign up, choose platforms, and unlock rewards through community milestones.</p>
<!-- NPC Dialog Overlay (initially hidden via CSS) --> <a class="link" href="https://git.bachmannj.ch/Fretux/linux_distro_roguelite_promotion" data-project-name="linux_distro_roguelite_promotion">Source code</a>
<div id="npc-dialog" class="hidden"> <p class="tags">Java</p>
<div id="npc-dialog-content"> </div>
<p id="npc-dialog-text">Hello, I'm Frederik Spirgi (aka Fretux), the creator of this website! Feel free to ask me anything—I'm here to share my journey and the technologies behind this interactive experience!</p> <div class="project">
<button id="dialog-option-1">Tell me more about your background!</button> <h3 class="project-name">linuxdistro-roguelite</h3>
<br> <p class="project-description">A Linux distribution themed roguelite made in Unity.</p>
<button id="dialog-option-2">What technologies did you use to build this site?</button> <p class="long-description" data-project-name="linuxdistro-roguelite">The Unity game project for Linux Distro Roguelite. Its scenes and scripts cover combat, game runs, progression, and a gambling scene.</p>
<br> <a class="link" href="https://git.bachmannj.ch/Fretux/linuxdistro-roguelite" data-project-name="linuxdistro-roguelite">Source code</a>
<button id="dialog-option-3">Why did you create this interactive website?</button> <p class="tags">Unity / C#</p>
<br> </div>
<h6>(You can leave the house by pressing Enter again)</h6> <div class="project">
</div> <h3 class="project-name">MindMotion</h3>
</div> <p class="project-description">A Minecraft mod adding Sanity, Insanity, and Tempo systems.</p>
<p class="long-description" data-project-name="MindMotion">A configurable Minecraft gameplay mod inspired by Deepwoken. Sanity, Insanity, and Tempo respond to the player's surroundings and actions, adding decisions to exploration and combat.</p>
<div id="mobile-controls"> <a class="link" href="https://git.bachmannj.ch/Fretux/MindMotion" data-project-name="MindMotion">Source code</a>
<button id="move-left-button"><</button> <p class="tags">Java</p>
<button id="jump-button">^</button> </div>
<button id="move-right-button">></button> <div class="project">
<button id="interact-button">Interact</button> <h3 class="project-name">Ailments</h3>
<button id="interact-character-button" class="hidden">Interact with Character</button> <p class="project-description">A Forge library for combat effects such as Bleed and Fear.</p>
</div> <p class="long-description" data-project-name="Ailments">A Minecraft Forge library mod that provides reusable effects including Soul Rot, Bleed, Fracture, Fear, Charm, Taunt, Overcharm, and Winded for other mods.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Ailments" data-project-name="Ailments">Source code</a>
<p class="tags">Java</p>
<!-- Snowfall Container --> </div>
<div id="snow-container"></div> <div class="project">
<script type="text/javascript" src="script1.js"></script> <h3 class="project-name">KnockedBack</h3>
<script type="text/javascript" src="Tutorial.js"></script> <p class="project-description">A Minecraft mod that turns lethal hits into knockdowns.</p>
<script type="text/javascript" src="darkmode.js"></script> <p class="long-description" data-project-name="KnockedBack">A Minecraft Forge mod that replaces many instant deaths with a temporary knocked-out state. Teammates can rescue and carry a downed player, while opponents can execute them.</p>
</body> <a class="link" href="https://git.bachmannj.ch/Fretux/KnockedBack" data-project-name="KnockedBack">Source code</a>
</html> <a class="link" href="https://www.curseforge.com/minecraft/mc-mods/knockedback" data-project-name="KnockedBack">Download mod</a>
<p class="tags">Java</p>
</div>
<div class="project">
<h3 class="project-name">Ascend</h3>
<p class="project-description">A soulslike Minecraft RPG progression mod.</p>
<p class="long-description" data-project-name="Ascend">A Minecraft mod inspired by Deepwoken. Players earn Ascend levels, invest skill points in attributes, and gain Knowledge through play.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Ascend" data-project-name="Ascend">Source code</a>
<p class="tags">Java</p>
</div>
<div class="project">
<h3 class="project-name">SkillEngine</h3>
<p class="project-description">A framework for adding custom skill trees to Forge mods.</p>
<p class="long-description" data-project-name="SkillEngine">A modular Minecraft Forge framework for other mods to define skill nodes, requirements, icons, and unlocks. It also handles the skill tree interface and server synchronization.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/SkillEngine" data-project-name="SkillEngine">Source code</a>
<p class="tags">Java</p>
</div>
<div class="project">
<h3 class="project-name">KrisHD_AI</h3>
<p class="project-description">Elsewhere, an AI chat app with personas and roleplay sessions.</p>
<p class="long-description" data-project-name="KrisHD_AI">Elsewhere is an Express and SQLite chat app with user accounts, custom personas, roleplay conversations, and a persona market. Conversations can branch and be compared.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/KrisHD_AI" data-project-name="KrisHD_AI">Source code</a>
<a class="link" href="https://ai.krishd.ch/" data-project-name="KrisHD_AI">Live website</a>
<p class="tags">JavaScript</p>
</div>
<div class="project">
<h3 class="project-name">Discord_bot</h3>
<p class="project-description">A Discord bot with moderation, roles, and virtual currency.</p>
<p class="long-description" data-project-name="Discord_bot">A Discord.js bot for community servers. Its commands cover moderation, role management, utility features, and a virtual currency system.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Discord_bot" data-project-name="Discord_bot">Source code</a>
<p class="tags">JavaScript</p>
</div>
<div class="project">
<h3 class="project-name">Portfolio2</h3>
<p class="project-description">Another repo of the Next.js portfolio website.</p>
<p class="long-description" data-project-name="Portfolio2">A second repository for the Next.js portfolio site, with the same home page as WebsitePortfolio2 and dedicated project, about, and contact pages.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Portfolio2" data-project-name="Portfolio2">Source code</a>
<p class="tags">JavaScript</p>
</div>
<div class="project">
<h3 class="project-name">Autoclicker</h3>
<p class="project-description">A desktop autoclicker with a configurable hotkey.</p>
<p class="long-description" data-project-name="Autoclicker">A Python desktop autoclicker built with Tkinter and pynput. Configure a hotkey, then start or stop clicking from the app or the keyboard.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Autoclicker" data-project-name="Autoclicker">Source code</a>
<p class="tags">Python</p>
</div>
<div class="project">
<h3 class="project-name">Gambling_API</h3>
<p class="project-description">A multiplayer virtual-chip poker game for the web.</p>
<p class="long-description" data-project-name="Gambling_API">A browser-based multiplayer poker game with Express, WebSockets, and SQLite. Players register accounts, join lobbies, and play with virtual chips.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/Gambling_API" data-project-name="Gambling_API">Source code</a>
<p class="tags">JavaScript</p>
</div>
<div class="project">
<h3 class="project-name">TARS</h3>
<p class="project-description">A Discord bot for a private server's moderation and utilities.</p>
<p class="long-description" data-project-name="TARS">A Python Discord bot with moderation tools, reports, reminders, server utilities, fun commands, and a perk shop for server boosters.</p>
<a class="link" href="https://git.bachmannj.ch/Fretux/TARS" data-project-name="TARS">Source code</a>
<p class="tags">Python</p>
</div>
</section>
<!-- NPC Dialog Overlay (initially hidden via CSS) -->
<div id="npc-dialog" class="hidden" role="dialog" aria-label="About Fretux">
<div id="npc-dialog-content">
<div class="dialog-heading"><span>About Fretux</span><button id="npc-dialog-close" type="button" aria-label="Close dialog">×</button></div>
<p id="npc-dialog-text">Hello, I'm Frederik Spirgi (aka Fretux), the creator of this website! Feel free to ask me anything—I'm here to share my journey and the technologies behind this interactive experience!</p>
<button id="dialog-option-1" type="button">My background</button>
<button id="dialog-option-2" type="button">How this site was built</button>
<button id="dialog-option-3" type="button">Why an interactive portfolio?</button>
<p class="dialog-hint">Press Escape to close the conversation.</p>
</div>
</div>
<div id="mobile-controls">
<button id="move-left-button" type="button" aria-label="Move left">←</button>
<button id="jump-button" type="button" aria-label="Jump">↑</button>
<button id="move-right-button" type="button" aria-label="Move right">→</button>
<button id="interact-button" type="button">Use</button>
<button id="interact-character-button" class="hidden" type="button">Talk</button>
</div>
<!-- Snowfall Container -->
<div id="snow-container"></div>
<script type="text/javascript" src="audio.js"></script>
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="darkmode.js"></script>
</body>
</html>
+659 -755
View File
File diff suppressed because it is too large Load Diff
+376 -477
View File
@@ -1,478 +1,377 @@
html, body { :root {
margin: 0; color-scheme: light;
padding: 0; --ink: #182b3d;
height: 100%; --muted: #4a6071;
overflow-x: hidden; --paper: #eef6f8;
background: linear-gradient(180deg, #8fc9f2 0%, #eef8ff 100%); --panel: #f3f8f9;
font-family: "Trebuchet MS", Verdana, sans-serif; --line: #547389;
} --accent: #265d82;
}
#welcome-screen {
position: fixed; * { box-sizing: border-box; }
top: 0;
left: 0; html, body {
width: 100%; width: 100%;
height: 100%; min-height: 100%;
color: #fff; margin: 0;
display: flex; }
justify-content: center;
align-items: center; body {
z-index: 10; overflow: hidden;
background-color: #B3C0CA; background: #b9d9ed;
opacity: 1; color: var(--ink);
transition: opacity 0.5s ease; font-family: "Courier New", monospace;
overflow: hidden; }
}
body.dark-mode {
#welcome-screen.hidden { color-scheme: dark;
opacity: 0; --ink: #edf3f5;
pointer-events: none; --muted: #bdced7;
} --paper: #1d2e3d;
--panel: #243747;
#main-content { --line: #8ba8b9;
opacity: 0; --accent: #a9d9ef;
visibility: hidden; background: #111d2a;
position: relative; }
top: 0;
left: 0; button { font: inherit; cursor: pointer; }
width: 100%; button:focus-visible, a:focus-visible, canvas:focus-visible {
height: auto; outline: 3px solid #d48439;
background-color: #B3C0CA; outline-offset: 3px;
transition: opacity 0.5s ease; }
display: flex;
flex-direction: column; .hidden { display: none; }
align-items: center;
padding: 20px; .no-script {
color: #fff; position: fixed;
overflow-y: auto; inset: 0;
} z-index: 100;
display: grid;
#main-content.visible { place-content: center;
opacity: 1; gap: 12px;
visibility: visible; padding: 24px;
transition: opacity 0.5s ease; background: #e6f3fa;
} color: #182b3d;
text-align: center;
#welcome-content { }
display: flex; .no-script a { color: #265d82; }
flex-direction: column;
align-items: center; #welcome-screen {
text-align: center; position: fixed;
transition: background-color 0.5s ease; inset: 0;
} z-index: 10;
display: grid;
#welcome-text { place-items: center;
color: white; overflow-y: auto;
font-size: 24px; padding: 70px 22px 32px;
margin-bottom: 20px; background:
text-align: center; radial-gradient(circle at 20% 18%, rgba(255,255,255,.56) 0 2px, transparent 3px),
font-family: 'Pacifico', cursive; radial-gradient(circle at 76% 32%, rgba(255,255,255,.55) 0 2px, transparent 3px),
} linear-gradient(#91bfdc, #d9edf7 78%, #f5fafb);
transition: opacity .7s ease;
#enter-button { }
cursor: pointer; body.dark-mode #welcome-screen {
width: 200px; background:
height: auto; radial-gradient(circle at 20% 18%, rgba(255,255,255,.48) 0 2px, transparent 3px),
transition: transform 0.3s ease; radial-gradient(circle at 76% 32%, rgba(255,255,255,.42) 0 2px, transparent 3px),
} linear-gradient(#152738, #304b60 78%, #627d8c);
}
#enter-button:hover { #welcome-screen.hidden {
transform: scale(1.1); display: grid;
} opacity: 0;
pointer-events: none;
#snow-container { }
position: fixed;
top: 0; #welcome-content {
left: 0; transition: opacity .5s ease, transform .7s ease;
width: 100%; }
height: 100%; #welcome-screen.hidden #welcome-content {
pointer-events: none; opacity: 0;
z-index: 1; transform: translateY(-14px);
overflow: hidden; }
}
#welcome-content {
.snowflake { width: min(620px, 100%);
position: absolute; text-align: center;
top: -10px; }
background-color: white; .welcome-penguin {
border-radius: 50%; display: block;
opacity: 0.8; width: clamp(96px, 15vw, 142px);
pointer-events: none; height: clamp(96px, 15vw, 142px);
animation: fall linear infinite; margin: 0 auto 18px;
z-index: -1 object-fit: contain;
} image-rendering: pixelated;
}
.paused .snowflake { .eyebrow {
animation-play-state: paused; margin: 0 0 12px;
} font-size: .9rem;
font-weight: bold;
@keyframes fall { letter-spacing: .28em;
to { text-transform: uppercase;
transform: translateY(100vh); }
} #welcome-text {
} margin: 0;
font-size: clamp(2.25rem, 6vw, 4.4rem);
p { font-weight: bold;
font-size: 18px line-height: 1.08;
} letter-spacing: -.055em;
}
.paused { .welcome-intro {
animation-play-state: paused; margin: 21px 0 28px;
} color: var(--muted);
font-size: clamp(.94rem, 1.5vw, 1.08rem);
.hidden { line-height: 1.6;
display: none }
} #enter-button {
min-width: 172px;
.snowflake:nth-child(odd) { min-height: 54px;
animation-duration: 4s; padding: 9px 22px;
width: 10px; border: 3px solid #162b3c;
height: 10px; border-radius: 2px;
} background: #244d69;
box-shadow: 5px 5px 0 #162b3c;
.snowflake:nth-child(even) { color: #fff;
animation-duration: 6s; font-size: 1.08rem;
width: 8px; font-weight: bold;
height: 8px; transition: transform .12s ease, box-shadow .12s ease;
} }
#enter-button:hover {
.content-section { transform: translate(2px, 2px);
max-width: 900px; box-shadow: 3px 3px 0 #162b3c;
margin: 20px auto; }
padding: 20px; #enter-button span { margin-left: 15px; }
background: #f9f9f9; .control-guide {
border-radius: 10px; max-width: 520px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin: 34px auto 0;
transition: transform 0.5s, opacity 3s; padding-top: 16px;
opacity: 0; border-top: 1px solid rgba(24,43,61,.4);
transform: translateY(20px); color: var(--muted);
} font-size: .78rem;
line-height: 1.8;
.content-section.visible { }
transform: translateY(0);
opacity: 1; .dark-mode-toggle {
} position: fixed;
top: 14px;
h1, h2 { right: 14px;
text-align: center; z-index: 40;
color: #333; display: grid;
} place-items: center;
width: 42px;
p { height: 42px;
text-align: center; padding: 0;
color: #555; border: 2px solid var(--line);
} border-radius: 2px;
background: var(--paper);
.content-section-top { color: var(--ink);
max-width: 900px; }
margin: 20px auto; .dark-mode-toggle:hover { background: var(--panel); }
padding: 20px; .theme-icon { font-size: 1.4rem; line-height: 1; }
background: #f9f9f9; .theme-label {
border-radius: 10px; position: absolute;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); width: 1px;
transform: translateY(20px); height: 1px;
} overflow: hidden;
clip-path: inset(50%);
.content-section:nth-child(even) { white-space: nowrap;
margin-left: auto; }
margin-right: 50px; body.menu-open .dark-mode-toggle {
} opacity: 0;
pointer-events: none;
.content-section:nth-child(odd) { }
margin-right: auto;
margin-left: 50px; #sound-toggle {
} position: fixed;
bottom: 14px;
@media (max-width: 768px) { left: 14px;
.content-section { z-index: 20;
margin: 20px auto; display: none;
} place-items: center;
} width: 42px;
height: 42px;
#game-container { padding: 0;
display: none; border: 2px solid var(--line);
position: relative; border-radius: 2px;
width: 100%; background: var(--paper);
height: 100vh; color: var(--ink);
overflow: hidden; font-size: 1.45rem;
background: #8fc9f2; }
} body.game-started #sound-toggle { display: grid; }
#sound-toggle[hidden], body.menu-open #sound-toggle { display: none; }
#game-canvas { #sound-toggle:hover { background: var(--panel); }
display: block; #sound-toggle[aria-pressed="false"]::after {
width: 100%; content: "";
height: 100%; position: absolute;
} width: 29px;
height: 2px;
.tutorial { background: currentColor;
position: fixed; transform: rotate(-45deg);
top: 0; }
left: 0;
width: 100vw; #game-container {
height: 100vh; position: relative;
background-color: rgba(0, 0, 0, 0.8); display: none;
color: white; width: 100%;
display: flex; height: 100vh;
align-items: center; height: 100dvh;
justify-content: center; overflow: hidden;
z-index: 1000; background: #b4d8ed;
} opacity: 0;
transition: opacity .75s ease;
.tutorial-content { }
background: #333; #game-container.visible { opacity: 1; }
padding: 20px; #game-canvas {
border-radius: 10px; display: block;
text-align: center; width: 100%;
max-width: 500px; height: 100%;
} touch-action: none;
}
.tutorial-content button {
margin-top: 20px; #block-info {
padding: 10px 20px; position: absolute;
font-size: 1rem; z-index: 5;
color: white; width: min(300px, calc(100vw - 30px));
background: #007bff; padding: 16px;
border: none; border: 2px solid var(--line);
border-radius: 5px; border-radius: 2px;
cursor: pointer; background: var(--panel);
} color: var(--ink);
}
.tutorial.hidden { #block-info h1 { margin: 0 0 8px; font-size: 1.1rem; }
display: none; #block-info p { margin: 0; color: var(--muted); font-size: .78rem; line-height: 1.5; }
} body.menu-open #block-info { display: none; }
#block-info.hidden { #projects { display: none; }
display: none;
} #snow-container {
position: fixed;
#block-info { inset: 0;
background: rgba(248, 252, 255, 0.94); z-index: 2;
border: 1px solid rgba(255, 255, 255, 0.72); overflow: hidden;
border-radius: 22px; pointer-events: none;
padding: 18px; }
width: 280px; body.menu-open #snow-container { display: none; }
text-align: center; .snowflake {
font-family: "Trebuchet MS", Verdana, sans-serif; position: absolute;
color: #102033; top: -12px;
font-size: 14px; width: 6px;
z-index: 10; height: 6px;
animation: fade-in 0.5s ease-out; background: #fff;
position: absolute; opacity: .8;
box-shadow: 0 24px 60px rgba(9, 24, 43, 0.24); animation: fall linear infinite;
backdrop-filter: blur(12px); }
} .paused .snowflake { animation-play-state: paused; }
@keyframes fall { to { transform: translateY(110vh); } }
#block-info h1 {
margin: 0 0 8px; #npc-dialog {
color: #102033; position: fixed;
} top: clamp(20px, 8vh, 80px);
left: 50%;
#block-info p { z-index: 30;
margin: 0; width: min(540px, calc(100vw - 30px));
color: #496071; max-height: calc(100dvh - 40px);
line-height: 1.45; overflow-y: auto;
} padding: 20px;
border: 3px solid var(--line);
@keyframes fade-in { border-radius: 2px;
from { background: var(--panel);
opacity: 0; color: var(--ink);
transform: translateY(20px); transform: translateX(-50%);
} }
to { .dialog-heading {
opacity: 1; display: flex;
transform: translateY(0); align-items: center;
} justify-content: space-between;
} gap: 14px;
font-size: 1.05rem;
#projects { font-weight: bold;
flex-direction: row; }
justify-content: space-between; #npc-dialog-close {
align-items: stretch; width: 34px;
flex-wrap: wrap; height: 34px;
padding: 10px; border: 2px solid var(--line);
background-color: #5a322a; border-radius: 2px;
height: 100%; background: var(--paper);
display: none color: var(--ink);
} font-size: 1.3rem;
}
.project { #npc-dialog-text {
flex: 1; margin: 18px 0 20px;
margin: 10px; color: var(--muted);
padding: 10px; font-size: .86rem;
background-color: white; line-height: 1.6;
border-radius: 5px; }
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); #npc-dialog-content > button:not(#npc-dialog-close) {
text-align: left; display: block;
overflow: hidden; width: 100%;
word-wrap: break-word; min-height: 42px;
} margin-top: 8px;
padding: 9px 12px;
.project h3 { border: 2px solid var(--line);
font-size: 1.5em; border-radius: 2px;
margin-bottom: 0.5em; background: var(--paper);
} color: var(--ink);
font-size: .82rem;
.project-description { font-weight: bold;
font-size: 1em; text-align: left;
line-height: 1.4; }
} #npc-dialog-content > button:not(#npc-dialog-close):hover { background: var(--panel); }
.dialog-hint { margin: 17px 0 0; color: var(--muted); font-size: .72rem; }
button {
margin-top: 10px; #mobile-controls { display: none; }
padding: 10px 16px;
color: white; @media (max-width: 767px) {
background: #102033; #welcome-screen { padding: 70px 20px 26px; }
border: none; #welcome-text { font-size: clamp(2.15rem, 11vw, 3.4rem); }
cursor: pointer; .control-guide { margin-top: 27px; font-size: .72rem; }
border-radius: 14px; #sound-toggle { top: 14px; right: 64px; bottom: auto; left: auto; }
box-shadow: 0 10px 24px rgba(9, 24, 43, 0.16); body.game-started #mobile-controls {
font-family: inherit; position: fixed;
transition: transform 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease; right: 12px;
} bottom: max(12px, env(safe-area-inset-bottom));
left: 12px;
button:hover { z-index: 6;
transform: translateY(-1px); display: flex;
box-shadow: 0 14px 30px rgba(9, 24, 43, 0.22); align-items: end;
} gap: 7px;
pointer-events: none;
#turn-device-sideways { }
display: none; #mobile-controls button {
} display: grid;
place-items: center;
#mobile-controls { min-width: 52px;
position: fixed; height: 52px;
bottom: 14px; padding: 0 10px;
width: 100%; border: 2px solid #a4c1d2;
display: flex; border-radius: 2px;
justify-content: center; background: #18374d;
gap: 10px; color: #fff;
z-index: 20; font-size: 1.25rem;
pointer-events: none; font-weight: bold;
} pointer-events: auto;
touch-action: none;
#interact-character-button { }
position: absolute; #mobile-controls button:active { background: #326789; }
bottom: 66px; #mobile-controls .hidden, body.menu-open #mobile-controls { display: none; }
left: 50%; #interact-button { margin-left: auto; font-size: .84rem !important; }
transform: translateX(-50%); #interact-character-button {
background: #2f7dd3; position: absolute;
} right: 0;
bottom: 61px;
#interact-button, background: #265d82 !important;
#move-left-button, font-size: .84rem !important;
#jump-button, }
#move-right-button { #npc-dialog { padding: 16px; }
display: inline-block; }
padding: 12px 18px;
font-size: 16px; @media (max-width: 390px) {
background: rgba(16, 32, 51, 0.88); #mobile-controls button { min-width: 45px; height: 48px; }
border: 1px solid rgba(255, 255, 255, 0.24); }
backdrop-filter: blur(10px);
pointer-events: auto; @media (prefers-reduced-motion: reduce) {
} *, *::before, *::after {
animation-duration: .01ms !important;
#mobile-controls button:active { transition-duration: .01ms !important;
background-color: #2f7dd3; }
transform: translateY(1px) scale(0.98); .snowflake { display: none; }
}
#npc-dialog {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 600px;
background: rgba(248, 252, 255, 0.94);
padding: 24px;
border-radius: 26px;
border: 1px solid rgba(255, 255, 255, 0.72);
box-shadow: 0 30px 80px rgba(9, 24, 43, 0.28);
backdrop-filter: blur(16px);
z-index: 1000;
}
#npc-dialog p {
color: #102033;
line-height: 1.5;
}
#npc-dialog button {
width: 100%;
background: #102033;
}
@media (min-width: 768px) {
#mobile-controls {
display: none;
}
}
@media screen and (max-height: 480px) {
#welcome-text {
display: none;
}
#enter-button {
display: none;
}
#turn-device-sideways {
display: block;
}
}
.dark-mode {
filter: brightness(0.9);
}
.dark-mode a {
color: #BB86FC;
}
.dark-mode button {
background-color: #333;
color: #fff;
border-color: #444;
}
.dark-mode #welcome-screen {
background-color: #333;
}
.dark-mode #game-container {
background-color: #222;
}
.dark-mode .content-section,
.dark-mode .project {
background-color: #1e1e1e;
color: #fff;
}
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: black;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.dark-mode-toggle.dark-mode {
background: white;
}
.dark-mode body {
background-color: #000;
}
.dark-mode #main-content {
background-color: #000;
}
.dark-mode #welcome-screen {
background-color: #333;
} }