mirror of
https://github.com/isledecomp/isle.pizza.git
synced 2026-01-10 18:21:15 +00:00
999 lines
38 KiB
HTML
999 lines
38 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
<title>LEGO® Island</title>
|
|
<link rel="icon" type="image/png" href="favicon.png">
|
|
<style>
|
|
html {
|
|
height: 100%;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background-color: #000000; /* Completely black */
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow-y: auto;
|
|
padding: 10px;
|
|
box-sizing: border-box;
|
|
font-family: Arial, sans-serif;
|
|
}
|
|
|
|
#canvas-wrapper {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100dvh;
|
|
background-color: #000000;
|
|
outline: none;
|
|
place-items: center;
|
|
|
|
touch-action: none;
|
|
-webkit-touch-callout: none;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
-khtml-user-select: none;
|
|
-webkit-user-drag: none;
|
|
user-drag: none;
|
|
}
|
|
|
|
/* Both canvas and overlay will share these sizing rules and grid placement */
|
|
#canvas,
|
|
#loading-gif-overlay {
|
|
grid-column: 1 / -1;
|
|
grid-row: 1 / -1;
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: calc(100dvh * (640 / 480));
|
|
max-height: calc(100dvw * (480 / 640));
|
|
aspect-ratio: 640 / 480;
|
|
box-sizing: border-box;
|
|
outline: none;
|
|
}
|
|
|
|
#canvas {
|
|
background-color: #000000;
|
|
border: none;
|
|
z-index: 1; /* Canvas is below the loading overlay */
|
|
}
|
|
|
|
#loading-gif-overlay {
|
|
background-color: #000000;
|
|
border: none;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.quote-block {
|
|
max-width: 80%;
|
|
text-align: center;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.quote-block .quote-text {
|
|
font-size: 0.5em;
|
|
color: #f0f0f0;
|
|
margin-bottom: 10px;
|
|
font-style: italic;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.quote-block .quote-attribution {
|
|
font-size: 0.1em;
|
|
color: #c0c0c0;
|
|
text-align: right;
|
|
}
|
|
|
|
.loading-info-text {
|
|
margin-top: 15px; /* Space below the quote block */
|
|
padding: 10px 15px;
|
|
max-width: 280px; /* Slightly adjust if needed, to align nicely with quote block */
|
|
width: 80%; /* Similar to quote block or adjust */
|
|
font-size: 0.8em; /* Smaller, informative text */
|
|
color: #b0b0b0; /* A lighter gray, like the attribution */
|
|
line-height: 1.5;
|
|
text-align: center; /* Text inside this block will be left-aligned for readability */
|
|
border-top: 1px dashed #444; /* Optional separator */
|
|
padding-top: 15px; /* Space above text if border-top is used */
|
|
}
|
|
|
|
.loading-info-text p {
|
|
margin: 0 0 8px 0; /* Spacing between paragraphs in this block */
|
|
}
|
|
.loading-info-text p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.status-message-bar {
|
|
margin-top: 20px; /* Space below the info text */
|
|
padding: 8px 12px;
|
|
width: 85%; /* Will be right-aligned by parent flex #loading-gif-overlay */
|
|
max-width: 340px; /* Control its max width */
|
|
background-color: #181818; /* A very dark gray */
|
|
color: #c0c0c0; /* Light gray text for general status */
|
|
font-family: 'Consolas', 'Menlo', 'Courier New', Courier, monospace; /* Monospaced for a "status" feel */
|
|
font-size: 0.75em;
|
|
border-radius: 4px;
|
|
text-align: center; /* Text within the bar itself */
|
|
line-height: 1.4;
|
|
border: 1px solid #303030; /* Subtle border */
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.status-message-bar code {
|
|
color: #FFD700; /* LEGO yellow-ish for emphasis */
|
|
background-color: #2a2a2a; /* Slightly lighter background for the code part */
|
|
padding: 1px 5px;
|
|
border-radius: 3px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
#main-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 25px;
|
|
background-color: #000000; /* Completely black */
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
max-width: 95vw;
|
|
box-shadow: none;
|
|
width: 900px; /* Give a max-width for larger screens */
|
|
max-width: 95vw;
|
|
}
|
|
|
|
#top-content {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 20px;
|
|
width: 100%;
|
|
}
|
|
|
|
.video-container {
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
#install-video {
|
|
max-width: 100%;
|
|
width: 300px;
|
|
height: auto; /* Height determined by width and aspect-ratio */
|
|
display: block;
|
|
aspect-ratio: 1 / 1; /* Based on your 260x260 ffprobe data */
|
|
border: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#sound-toggle-emoji {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
font-size: 26px;
|
|
color: white;
|
|
text-shadow: 0 0 3px black;
|
|
cursor: pointer;
|
|
opacity: 0.7;
|
|
transition: opacity 0.2s ease-in-out;
|
|
z-index: 10;
|
|
padding: 2px;
|
|
user-select: none;
|
|
}
|
|
|
|
#sound-toggle-emoji:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
#island-logo-img {
|
|
max-width: 100%;
|
|
width: 400px; /* CSS width */
|
|
height: auto; /* Height determined by width and aspect-ratio */
|
|
display: block;
|
|
aspect-ratio: 567 / 198; /* Based on your HTML width/height for the logo */
|
|
}
|
|
|
|
#controls-wrapper {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
align-items: flex-end;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
width: 100%;
|
|
max-width: 700px;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.control-img {
|
|
cursor: pointer;
|
|
height: auto; /* Height determined by width/max-width and aspect-ratio */
|
|
max-width: 18%; /* Responsive width constraint */
|
|
display: block; /* Consistent box model */
|
|
transition: transform 0.1s ease-in-out;
|
|
}
|
|
|
|
/* Specific aspect ratios for each control image based on your HTML attributes */
|
|
#run-game-btn { aspect-ratio: 135 / 164; }
|
|
#configure-btn { aspect-ratio: 130 / 147; }
|
|
#free-stuff-btn { aspect-ratio: 134 / 149; }
|
|
#read-me-btn { aspect-ratio: 134 / 149; }
|
|
#cancel-btn { aspect-ratio: 93 / 145; }
|
|
|
|
.control-img:hover {
|
|
transform: scale(1.08);
|
|
}
|
|
|
|
.footer-disclaimer {
|
|
font-size: 0.7em;
|
|
color: #888888;
|
|
text-align: center;
|
|
line-height: 1.4;
|
|
max-width: 600px;
|
|
width: 90%;
|
|
}
|
|
|
|
.footer-disclaimer p {
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.page-content {
|
|
display: none; /* Initially hidden */
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
color: #f0f0f0;
|
|
width: 100%;
|
|
}
|
|
|
|
.page-back-button {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: left;
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: white;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
opacity: 0.8;
|
|
transition: all 0.2s ease-in-out;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.page-back-button:hover {
|
|
opacity: 1;
|
|
color: #FFD700;
|
|
}
|
|
|
|
.page-inner-content {
|
|
max-width: 700px;
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
|
|
.page-inner-content h1 {
|
|
color: #FFD700; /* LEGO yellow */
|
|
font-size: 2.5em;
|
|
margin-bottom: 20px;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.page-inner-content p {
|
|
color: #c0c0c0;
|
|
line-height: 1.6;
|
|
font-size: 1.1em;
|
|
margin-bottom: 15px;
|
|
text-align: left;
|
|
}
|
|
|
|
.page-inner-content a {
|
|
color: #FFD700;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.page-inner-content a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* REVISED Styles for Configure Page */
|
|
#configure-page .page-inner-content {
|
|
display: flex;
|
|
background-color: #181818;
|
|
border: 1px solid #303030;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.config-art-panel {
|
|
flex: 0 0 180px; /* Fixed width for the image panel */
|
|
border-radius: 8px 0 0 8px; /* Apply radius here */
|
|
}
|
|
|
|
.config-art-panel img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
border-radius: 8px 0 0 8px; /* Ensure image respects radius */
|
|
}
|
|
|
|
.config-form {
|
|
flex-grow: 1;
|
|
padding: 25px;
|
|
}
|
|
|
|
.config-section {
|
|
margin-bottom: 25px;
|
|
}
|
|
.config-section:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.config-legend {
|
|
color: #FFD700;
|
|
font-size: 1.1em;
|
|
font-weight: bold;
|
|
margin: 0 0 15px 0;
|
|
padding-bottom: 10px;
|
|
border-bottom: 1px solid #444;
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 20px 30px;
|
|
}
|
|
|
|
.form-group-label {
|
|
position: relative; /* Needed for tooltip positioning */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: #e0e0e0;
|
|
font-weight: bold;
|
|
font-size: 0.9em;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.tooltip-trigger {
|
|
position: relative;
|
|
cursor: pointer;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 50%;
|
|
background-color: #444;
|
|
color: #eee;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
user-select: none;
|
|
}
|
|
|
|
.tooltip-content {
|
|
position: absolute;
|
|
bottom: 140%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 220px;
|
|
background-color: #2a2a2a;
|
|
color: #f0f0f0;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
font-size: 0.85em;
|
|
font-weight: normal;
|
|
line-height: 1.4;
|
|
text-align: left;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
|
z-index: 20;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity 0.2s, visibility 0.2s;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tooltip-trigger:hover > .tooltip-content,
|
|
.tooltip-trigger.active > .tooltip-content {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
.option-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.option-item {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.option-item input {
|
|
position: absolute;
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.option-item label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
color: #c0c0c0;
|
|
}
|
|
|
|
.option-item label::before {
|
|
content: '';
|
|
width: 14px;
|
|
height: 14px;
|
|
margin-right: 10px;
|
|
background-color: #333;
|
|
border: 1px solid #555;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.option-item input:checked + label::before {
|
|
background-color: #FFD700;
|
|
border-color: #fff;
|
|
box-shadow: 0 0 5px #FFD700;
|
|
}
|
|
|
|
.radio-group .option-item label::before { border-radius: 50%; }
|
|
.checkbox-group .option-item label::before { border-radius: 3px; }
|
|
|
|
input[type="range"] {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 100%;
|
|
height: 6px;
|
|
background: #444;
|
|
outline: none;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
background: #FFD700;
|
|
cursor: pointer;
|
|
border-radius: 50%;
|
|
border: 2px solid #000;
|
|
}
|
|
|
|
input[type="range"]::-moz-range-thumb {
|
|
width: 18px;
|
|
height: 18px;
|
|
background: #FFD700;
|
|
cursor: pointer;
|
|
border-radius: 50%;
|
|
border: 2px solid #000;
|
|
}
|
|
|
|
.select-wrapper { position: relative; }
|
|
|
|
.select-wrapper::after {
|
|
content: '▼';
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 12px;
|
|
transform: translateY(-50%);
|
|
color: #FFD700;
|
|
pointer-events: none;
|
|
}
|
|
|
|
select {
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
display: block;
|
|
width: 100%;
|
|
padding: 10px 15px;
|
|
font-size: 0.9em;
|
|
color: #c0c0c0;
|
|
background-color: #333;
|
|
border: 1px solid #555;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
#install-video { width: 260px; }
|
|
#island-logo-img { width: 360px; }
|
|
.control-img { max-width: 19%; }
|
|
#sound-toggle-emoji { font-size: 24px; top: 8px; left: 8px; }
|
|
|
|
.loading-info-text {
|
|
max-width: 90%;
|
|
font-size: 0.75em;
|
|
}
|
|
|
|
.page-inner-content h1 { font-size: 2em; }
|
|
.page-inner-content p { font-size: 1em; }
|
|
|
|
.config-art-panel {
|
|
display: none; /* Hide the image panel on smaller screens */
|
|
}
|
|
#configure-page .page-inner-content {
|
|
background-color: transparent;
|
|
border: none;
|
|
padding: 0;
|
|
}
|
|
.config-form {
|
|
background-color: #181818;
|
|
border: 1px solid #303030;
|
|
border-radius: 8px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
#install-video { width: 90%; max-width: 280px; }
|
|
#island-logo-img { width: 90%; max-width: 320px; }
|
|
.control-img { max-width: 45%; margin: 3px 0; }
|
|
#sound-toggle-emoji { font-size: 22px; top: 6px; left: 6px; }
|
|
|
|
.loading-info-text {
|
|
max-width: 95%;
|
|
font-size: 0.7em;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.page-content .page-back-button { font-size: 22px; }
|
|
|
|
.form-grid {
|
|
grid-template-columns: 1fr;
|
|
gap: 25px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Main launcher interface -->
|
|
<div id="main-container">
|
|
<!-- Main launcher content -->
|
|
<div id="top-content">
|
|
<div class="video-container">
|
|
<video id="install-video" width="260" height="260" autoplay loop playsinline muted>
|
|
<source src="install.mp4" type="video/mp4">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
<span id="sound-toggle-emoji" title="Unmute Audio">🔇</span>
|
|
</div>
|
|
<img id="island-logo-img" width="567" height="198" src="island.webp" alt="Lego Island Logo">
|
|
</div>
|
|
|
|
<div id="controls-wrapper">
|
|
<img class="control-img" width="135" height="164" id="run-game-btn" src="run_game_off.webp" alt="Run Game"
|
|
data-off="run_game_off.webp" data-on="run_game_on.webp">
|
|
<img class="control-img" width="130" height="147" id="configure-btn" src="configure_off.webp" alt="Configure"
|
|
data-off="configure_off.webp" data-on="configure_on.webp" data-target="#configure-page">
|
|
<img class="control-img" width="134" height="149" id="free-stuff-btn" src="free_stuff_off.webp" alt="Free Stuff"
|
|
data-off="free_stuff_off.webp" data-on="free_stuff_on.webp" data-target="#free-stuff-page">
|
|
<img class="control-img" width="134" height="149" id="read-me-btn" src="read_me_off.webp" alt="Read Me"
|
|
data-off="read_me_off.webp" data-on="read_me_on.webp" data-target="#read-me-page">
|
|
<img class="control-img" width="93" height="145" id="cancel-btn" src="cancel_off.webp" alt="Cancel"
|
|
data-off="cancel_off.webp" data-on="cancel_on.webp" onclick="location.href = 'https://legoisland.org';">
|
|
</div>
|
|
|
|
<!-- Content Pages (now inside main-container) -->
|
|
<div id="read-me-page" class="page-content">
|
|
<span class="page-back-button" role="button" aria-label="Go back to main menu">← Back</span>
|
|
<div class="page-inner-content">
|
|
<h1>Read Me</h1>
|
|
<p>Welcome to the LEGO Island web port project! This is a recreation of the classic 1997 PC game, rebuilt to run in modern web browsers using Emscripten.</p>
|
|
<p>This incredible project stands on the shoulders of giants. It was made possible by the original <a href="https://github.com/isledecomp/isle" target="_blank" rel="noopener noreferrer">decompilation project</a>, which was then adapted into a <a href="https://github.com/isledecomp/isle-portable" target="_blank" rel="noopener noreferrer">portable version</a>. This represents a year-long effort, involving thousands of hours of work from many awesome contributors dedicated to preserving this piece of gaming history.</p>
|
|
<p>Our goal is to make this classic accessible to everyone. The project is still in development, so you may encounter bugs. Your patience and feedback are greatly appreciated!</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="configure-page" class="page-content">
|
|
<span class="page-back-button" role="button" aria-label="Go back to main menu">← Back</span>
|
|
<div class="page-inner-content">
|
|
<div class="config-art-panel">
|
|
<img src="shark.webp" alt="LEGO Island Shark and Brickster">
|
|
</div>
|
|
<form class="config-form">
|
|
<div class="config-section">
|
|
<h3 class="config-legend">Detail</h3>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-group-label">Island Model Quality</label>
|
|
<div class="radio-group option-list">
|
|
<div class="option-item">
|
|
<input type="radio" id="gfx-low" name="Island Quality" value="0">
|
|
<label for="gfx-low">Low</label>
|
|
</div>
|
|
<div class="option-item">
|
|
<input type="radio" id="gfx-med" name="Island Quality" value="1">
|
|
<label for="gfx-med">Medium</label>
|
|
</div>
|
|
<div class="option-item">
|
|
<input type="radio" id="gfx-high" name="Island Quality" value="2" checked>
|
|
<label for="gfx-high">High</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-group-label">Island Texture Quality</label>
|
|
<div class="radio-group option-list">
|
|
<div class="option-item">
|
|
<input type="radio" id="tex-low" name="Island Texture" value="0">
|
|
<label for="tex-low">Low</label>
|
|
</div>
|
|
<div class="option-item">
|
|
<input type="radio" id="tex-high" name="Island Texture" value="1" checked>
|
|
<label for="tex-high">High</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-group-label" for="max-lod">
|
|
Maximum LOD
|
|
<span class="tooltip-trigger">?
|
|
<span class="tooltip-content">Maximum Level of Detail (LOD). A higher setting will cause higher quality textures to be drawn regardless of distance.</span>
|
|
</span>
|
|
</label>
|
|
<input type="range" id="max-lod" name="Max LOD" min="0" max="5" step="0.1" value="3.6">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-group-label" for="max-allowed-extras">
|
|
Maximum actors (5..40)
|
|
<span class="tooltip-trigger">?
|
|
<span class="tooltip-content">Maximum number of LEGO actors to exist in the world at a time. The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable.</span>
|
|
</span>
|
|
</label>
|
|
<input type="range" id="max-allowed-extras" name="Max Allowed Extras" min="5" max="40" value="20">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="config-section">
|
|
<h3 class="config-legend">Graphics</h3>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-group-label">Renderer</label>
|
|
<div class="select-wrapper">
|
|
<select id="renderer-select" name="3D Device ID">
|
|
<option value="0 0x682656f3 0x0 0x0 0x2000000" selected>Software</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="config-section">
|
|
<h3 class="config-legend">Sound</h3>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-group-label">Options</label>
|
|
<div class="checkbox-group option-list">
|
|
<div class="option-item">
|
|
<input type="checkbox" id="check-music" name="Music" checked>
|
|
<label for="check-music">Music</label>
|
|
</div>
|
|
<div class="option-item">
|
|
<input type="checkbox" id="check-3d-sound" name="3DSound" checked>
|
|
<label for="check-3d-sound">3D Sound</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="free-stuff-page" class="page-content">
|
|
<span class="page-back-button" role="button" aria-label="Go back to main menu">← Back</span>
|
|
<div class="page-inner-content">
|
|
<h1>Free Stuff</h1>
|
|
<p>This section will soon have links to cool wallpapers, behind-the-scenes info, and other resources related to LEGO Island.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Single footer (now inside main-container) -->
|
|
<div class="footer-disclaimer">
|
|
<p>LEGO® and LEGO Island™ are trademarks of The LEGO Group.</p>
|
|
<p>This is an unofficial fan project and is not affiliated with or endorsed by The LEGO Group.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Emscripten canvas and loading overlay -->
|
|
<div id="canvas-wrapper">
|
|
<div id="loading-gif-overlay">
|
|
<img src="cdspin.gif" alt="Loading game...">
|
|
<div class="quote-block"> <p class="quote-text">"Whoops! You have to put the CD in your computer"</p>
|
|
<p class="quote-attribution">- The Infomaniac (1997)</p>
|
|
</div>
|
|
<div class="loading-info-text">
|
|
<p>"Hello! Hola! Aloha! How ya doin'? YO!" It's your pal, the Infomaniac, with a 2025 update! No need to search for that CD case, my friend!</p>
|
|
<p>This amazing LEGO Island adventure is now streaming directly from... well, from a really, really big digital box of bricks! Keep an eye on the status below!</p>
|
|
</div>
|
|
<div id="emscripten-status-message" class="status-message-bar">
|
|
Loading LEGO® Island... please wait! <code>0%</code>
|
|
</div>
|
|
</div>
|
|
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
|
</div>
|
|
|
|
<script type='text/javascript'>
|
|
var Module = {
|
|
arguments: ['--ini', '/config/isle.ini'],
|
|
running: false,
|
|
preRun: function() {
|
|
Module["addRunDependency"]("isle");
|
|
Module.running = true;
|
|
},
|
|
canvas: (function() {
|
|
return document.getElementById('canvas');
|
|
})(),
|
|
onExit: function() {
|
|
window.location.reload();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// --- Elements ---
|
|
const video = document.getElementById('install-video');
|
|
const soundToggleEmoji = document.getElementById('sound-toggle-emoji');
|
|
const mainContainer = document.getElementById('main-container');
|
|
const topContent = document.getElementById('top-content');
|
|
const controlsWrapper = document.getElementById('controls-wrapper');
|
|
const footer = document.querySelector('.footer-disclaimer');
|
|
const allPages = document.querySelectorAll('.page-content');
|
|
const pageButtons = document.querySelectorAll('[data-target]');
|
|
const backButtons = document.querySelectorAll('.page-back-button');
|
|
|
|
// --- Sound Toggle ---
|
|
function updateSoundEmojiState() {
|
|
soundToggleEmoji.textContent = video.muted ? '🔇' : '🔊';
|
|
soundToggleEmoji.title = video.muted ? 'Unmute Audio' : 'Mute Audio';
|
|
}
|
|
|
|
if (video && soundToggleEmoji) {
|
|
updateSoundEmojiState();
|
|
soundToggleEmoji.addEventListener('click', function() {
|
|
video.muted = !video.muted;
|
|
updateSoundEmojiState();
|
|
});
|
|
video.addEventListener('volumechange', updateSoundEmojiState);
|
|
}
|
|
|
|
// --- Control Image Hover ---
|
|
const imageControls = document.querySelectorAll('.control-img');
|
|
imageControls.forEach(control => {
|
|
const hoverImage = new Image();
|
|
if (control.dataset.on) {
|
|
hoverImage.src = control.dataset.on;
|
|
}
|
|
control.addEventListener('mouseover', function() { if(this.dataset.on) {this.src = this.dataset.on;} });
|
|
control.addEventListener('mouseout', function() { if(this.dataset.off) {this.src = this.dataset.off;} });
|
|
});
|
|
|
|
// --- Emscripten Launch Logic ---
|
|
const runGameButton = document.getElementById('run-game-btn');
|
|
const emscriptenCanvas = document.getElementById('canvas');
|
|
const canvasWrapper = document.getElementById('canvas-wrapper');
|
|
const loadingGifOverlay = document.getElementById('loading-gif-overlay');
|
|
const statusMessageBar = document.getElementById('emscripten-status-message');
|
|
|
|
runGameButton.addEventListener('click', function() {
|
|
if (!Module.running) return;
|
|
video.muted = true;
|
|
updateSoundEmojiState();
|
|
this.src = this.dataset.on;
|
|
|
|
mainContainer.style.display = 'none';
|
|
canvasWrapper.style.display = 'grid';
|
|
emscriptenCanvas.style.display = 'block';
|
|
|
|
document.documentElement.style.overflow = 'hidden';
|
|
document.documentElement.style.overscrollBehavior = 'none';
|
|
|
|
Module["removeRunDependency"]("isle");
|
|
emscriptenCanvas.focus();
|
|
});
|
|
|
|
let progressUpdates = 0;
|
|
emscriptenCanvas.addEventListener('presenterProgress', function(event) {
|
|
// Intro animation is ready
|
|
if (event.detail.objectName == 'Lego_Smk' && event.detail.tickleState == 1) {
|
|
loadingGifOverlay.style.display = 'none';
|
|
}
|
|
else if (progressUpdates < 1003) {
|
|
progressUpdates++;
|
|
const percent = (progressUpdates / 1003 * 100).toFixed();
|
|
statusMessageBar.innerHTML = 'Loading LEGO® Island... please wait! <code>' + percent + '%</code>';
|
|
}
|
|
});
|
|
|
|
// --- Page Navigation Logic ---
|
|
function showPage(pageId, pushState = true) {
|
|
const page = document.querySelector(pageId);
|
|
if (!page) return;
|
|
|
|
// Hide main content
|
|
topContent.style.display = 'none';
|
|
controlsWrapper.style.display = 'none';
|
|
|
|
// Show selected page
|
|
page.style.display = 'flex';
|
|
|
|
if (pushState) {
|
|
const newPath = pageId.replace('-page', '');
|
|
history.pushState({ page: pageId }, '', newPath);
|
|
}
|
|
}
|
|
|
|
function showMainMenu() {
|
|
// Hide all pages
|
|
allPages.forEach(p => p.style.display = 'none');
|
|
|
|
// Show main content
|
|
topContent.style.display = 'flex';
|
|
controlsWrapper.style.display = 'flex';
|
|
}
|
|
|
|
pageButtons.forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
const targetId = e.currentTarget.dataset.target;
|
|
showPage(targetId);
|
|
});
|
|
});
|
|
|
|
backButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
history.back();
|
|
});
|
|
});
|
|
|
|
window.addEventListener('popstate', (e) => {
|
|
if (e.state && e.state.page && e.state.page !== 'main') {
|
|
showPage(e.state.page, false);
|
|
} else {
|
|
showMainMenu();
|
|
}
|
|
});
|
|
|
|
// --- OPFS Config Manager ---
|
|
const configManager = {
|
|
form: document.querySelector('.config-form'),
|
|
filePath: 'isle.ini',
|
|
|
|
async init() {
|
|
if (!this.form) return;
|
|
await this.loadConfig();
|
|
this.form.addEventListener('change', () => this.saveConfig());
|
|
},
|
|
|
|
async getFileHandle() {
|
|
try {
|
|
const root = await navigator.storage.getDirectory();
|
|
return await root.getFileHandle(this.filePath, { create: true });
|
|
} catch (e) {
|
|
console.error("OPFS not available or permission denied.", e);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
async saveConfig() {
|
|
const handle = await this.getFileHandle();
|
|
if (!handle) return;
|
|
|
|
let iniContent = '[isle]\n';
|
|
const elements = this.form.elements;
|
|
|
|
for (const element of elements) {
|
|
if (!element.name) continue;
|
|
|
|
let value;
|
|
switch (element.type) {
|
|
case 'checkbox':
|
|
value = element.checked ? 'YES' : 'NO';
|
|
iniContent += `${element.name}=${value}\n`;
|
|
break;
|
|
case 'radio':
|
|
if (element.checked) {
|
|
value = element.value;
|
|
iniContent += `${element.name}=${value}\n`;
|
|
}
|
|
break;
|
|
default:
|
|
value = element.value;
|
|
iniContent += `${element.name}=${value}\n`;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const writable = await handle.createWritable();
|
|
await writable.write(iniContent);
|
|
await writable.close();
|
|
console.log('Config saved to', this.filePath);
|
|
},
|
|
|
|
async loadConfig() {
|
|
const handle = await this.getFileHandle();
|
|
if (!handle) return;
|
|
|
|
const file = await handle.getFile();
|
|
const text = await file.text();
|
|
if (!text) {
|
|
console.log('No existing config file found, using defaults.');
|
|
await this.saveConfig();
|
|
return;
|
|
}
|
|
|
|
const config = {};
|
|
const lines = text.split('\n');
|
|
for (const line of lines) {
|
|
if (line.startsWith('[') || !line.includes('=')) continue;
|
|
const [key, ...valueParts] = line.split('=');
|
|
const value = valueParts.join('=').trim();
|
|
config[key.trim()] = value;
|
|
}
|
|
|
|
this.applyConfigToForm(config);
|
|
console.log('Config loaded from', this.filePath);
|
|
},
|
|
|
|
applyConfigToForm(config) {
|
|
const elements = this.form.elements;
|
|
for(const key in config) {
|
|
const element = elements[key];
|
|
if(!element) continue;
|
|
|
|
const value = config[key];
|
|
|
|
if (element.type === 'checkbox') {
|
|
element.checked = (value === 'YES');
|
|
} else if (element.nodeName === 'RADIO') { // radio nodelist
|
|
for(const radio of element) {
|
|
if(radio.value === value) {
|
|
radio.checked = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
element.value = value;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
configManager.init();
|
|
|
|
// Handle initial page load with a hash
|
|
const initialHash = window.location.hash;
|
|
if (initialHash) {
|
|
const initialPageId = initialHash + '-page';
|
|
if (document.querySelector(initialPageId)) {
|
|
const urlPath = window.location.pathname;
|
|
history.replaceState({ page: 'main' }, '', urlPath);
|
|
showPage(initialPageId, true);
|
|
}
|
|
} else {
|
|
history.replaceState({ page: 'main' }, '', window.location.pathname);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script src="isle.js" async></script>
|
|
</body>
|
|
</html>
|