thronglets / title.html
kimhyunwoo's picture
Rename style.css to title.html
f86f670 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thronglets Imitation</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #111;
color: limegreen;
font-family: 'Courier New', monospace;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
position: relative;
}
.game-container {
position: relative;
width: 90vmin;
height: 60vmin;
max-width: 800px;
max-height: 500px;
background: #3A4A2F; /* Grass background */
border: 10px solid #8B4513; /* Wooden frame */
box-sizing: border-box;
overflow: hidden; /* Keep everything inside the frame */
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap; /* Allow thronglets to wrap if many */
padding: 20px; /* Padding inside the frame */
image-rendering: pixelated; /* Attempt pixelation */
font-size: 2em; /* Emojis larger */
position: relative;
}
.game-elements {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* Allow clicks to pass through */
}
.game-elements .emoji, .game-elements .text {
position: absolute;
pointer-events: auto; /* Allow clicks on interactable elements */
transform: translate(-50%, -50%); /* Center element */
user-select: none;
}
.rock { top: 15%; left: 15%; }
.rock.right { left: 85%; }
.tree { top: 75%; left: 20%; }
.tree.right { left: 80%; }
.egg { top: 50%; left: 50%; cursor: pointer; transition: transform 0.5s ease;}
.egg.hatching { animation: hatch-pulse 0.5s infinite alternate; }
.thronglet {
top: 50%;
left: 50%;
cursor: pointer;
transition: top 0.5s, left 0.5s, transform 0.2s ease;
z-index: 1; /* Ensure thronglets are above background elements */
}
.apple { top: 60%; left: 40%; cursor: pointer; }
.bath { top: 60%; left: 60%; cursor: pointer; }
.feedback {
position: absolute;
transform: translate(-50%, -150%); /* Above thronglet */
font-size: 0.8em;
opacity: 0;
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}
.feedback.active {
opacity: 1;
transform: translate(-50%, -200%);
}
.dead {
filter: grayscale(100%);
opacity: 0.5;
pointer-events: none;
}
.blood {
position: absolute;
font-size: 1.5em;
transform: translate(-50%, -50%);
pointer-events: none;
opacity: 0;
transition: opacity 0.5s ease-out;
z-index: 0;
}
.blood.splatter { opacity: 1; }
.skull { font-size: 1.5em; pointer-events: none; }
.console {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 600px;
background: rgba(0, 0, 0, 0.8);
border: 2px groove limegreen;
padding: 10px;
box-sizing: border-box;
white-space: pre-wrap;
word-break: break-word;
pointer-events: none; /* Don't block clicks */
z-index: 100;
}
.message {
display: block;
margin-bottom: 5px;
color: limegreen;
white-space: pre-wrap;
}
.scanline-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 101;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.1) 0,
rgba(0, 0, 0, 0.1) 1px,
transparent 1px,
transparent 2px
);
}
.glitch-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 102;
animation: glitch 5s infinite alternate steps(5);
opacity: 0;
filter: hue-rotate(0deg);
}
.glitch-overlay.active {
animation: glitch 0.3s infinite alternate steps(5), color-shift 1s infinite linear;
opacity: 0.5;
}
@keyframes hatch-pulse {
from { transform: translate(-50%, -50%) scale(1); }
to { transform: translate(-50%, -50%) scale(1.05); }
}
@keyframes glitch {
0% {
transform: translate(0);
}
20% {
transform: translate(-5px, 5px);
}
40% {
transform: translate(-5px, -5px);
}
60% {
transform: translate(5px, 5px);
}
80% {
transform: translate(5px, -5px);
}
to {
transform: translate(0);
}
}
@keyframes color-shift {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
/* Basic representation of later elements */
.ominous-elements {
position: absolute;
top: 10%;
right: 10%;
font-size: 3em;
opacity: 0;
transition: opacity 2s ease-in-out;
z-index: 0;
}
.ominous-elements.visible {
opacity: 1;
}
.info-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #8B4513;
border: 2px solid black;
padding: 15px;
color: white;
font-size: 0.7em;
text-align: center;
white-space: pre-wrap;
word-break: break-word;
opacity: 0;
transition: opacity 1s ease-in-out;
pointer-events: none;
z-index: 5;
}
.info-panel.visible {
opacity: 1;
pointer-events: auto;
}
.black-mirror-title {
position: absolute;
top: 10px;
left: 10px;
font-size: 0.8em;
color: white;
font-weight: bold;
text-shadow: 1px 1px 0 black;
z-index: 10;
}
.netflix-logo {
position: absolute;
top: 10px;
right: 10px;
font-size: 1.2em; /* Use the 'N' emoji? */
color: red; /* Or maybe style text */
font-weight: bold;
text-shadow: 1px 1px 0 black;
z-index: 10;
}
.thronglets-title-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, #FFD700, #FFA500); /* Golden/Orange sky */
color: #8B4513; /* Brown text */
font-size: 3em;
font-weight: bold;
text-align: center;
padding-top: 15vh;
box-sizing: border-box;
z-index: 200;
transition: opacity 2s ease-out;
display: flex;
flex-direction: column;
align-items: center;
}
.thronglets-title-screen .copyright {
font-size: 0.4em;
margin-top: 10px;
}
.thronglets-title-screen .creatures {
margin-top: 5vh;
}
.thronglets-title-screen .creatures .emoji {
font-size: 2em;
margin: 0 20px;
}
.final-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(148,187,233,1) 0%, rgba(238,174,202,1) 100%); /* Colorful background */
z-index: 300;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5em;
text-align: center;
opacity: 0;
transition: opacity 2s ease-in-out;
}
.final-screen.visible { opacity: 1; }
.app-stores {
margin-top: 20px;
font-size: 0.8em;
}
.app-stores img {
height: 40px;
margin: 0 10px;
}
.full-black {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: black;
z-index: 301;
opacity: 0;
transition: opacity 1s ease-in-out;
pointer-events: none;
}
.full-black.visible { opacity: 1; }
.netflix-games-logo {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 302;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.netflix-games-logo.visible { opacity: 1; }
</style>
</head>
<body>
<div class="thronglets-title-screen" id="titleScreen">
THRONGLETS
<div class="copyright">(C) 1994 TUCKERSOFT LTD</div>
<div class="creatures">
<span class="emoji">🥚</span>
<!-- Using a placeholder emoji that *could* look like a Thronglet -->
<span class="emoji">🐻</span>
<span class="emoji">🐻</span>
<span class="emoji tree">🌳</span>
</div>
<div class="copyright" style="font-size: 0.3em; margin-top: 50px;">
Tuckersoft is not responsible for strange noises, missing files, or dreams featuring Thronglets whispering your name.
</div>
</div>
<div class="game-container" id="gameContainer">
<div class="black-mirror-title">BLACK MIRROR:<br>THRONGLETS</div>
<div class="netflix-logo">N<br>GAMES</div> <!-- Simulate Netflix Games logo -->
<div class="game-elements">
<span class="emoji rock">🪨</span>
<span class="emoji rock right">🪨</span>
<span class="emoji tree">🌳</span>
<span class="emoji tree right">🌳</span>
<span class="emoji egg" id="egg">🥚</span>
<!-- Initial items -->
<span class="emoji apple" id="apple">🍎</span>
<span class="emoji bath" id="bath">🛀</span>
<!-- Ominous elements for later -->
<div class="ominous-elements" id="ominousElements">
💀💀💀💀<br>🦴🦴🦴🦴<br>🌌📦
</div>
<!-- Info Panel -->
<div class="info-panel" id="infoPanel"></div>
<!-- Blood splatter area -->
<span class="emoji blood" id="bloodSplatter">🩸</span>
</div>
</div>
<div class="console" id="console"></div>
<div class="scanline-overlay"></div>
<div class="glitch-overlay" id="glitchOverlay"></div>
<div class="final-screen" id="finalScreen">
PLAY NOW<br>ON THE NETFLIX<br>MOBILE APP
<div class="app-stores">
<!-- Placeholder images or text for store buttons -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" alt="Google Play Placeholder">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" alt="App Store Placeholder">
(Store Buttons Go Here)
</div>
</div>
<div class="full-black" id="fullBlack"></div>
<div class="netflix-games-logo" id="netflixGamesLogo">
<span class="emoji" style="font-size: 5em; color: red;">N</span>
</div>
<script>
const egg = document.getElementById('egg');
const gameContainer = document.getElementById('gameContainer');
const consoleDiv = document.getElementById('console');
const apple = document.getElementById('apple');
const bath = document.getElementById('bath');
const glitchOverlay = document.getElementById('glitchOverlay');
const ominousElements = document.getElementById('ominousElements');
const infoPanel = document.getElementById('infoPanel');
const bloodSplatter = document.getElementById('bloodSplatter');
const titleScreen = document.getElementById('titleScreen');
const finalScreen = document.getElementById('finalScreen');
const fullBlack = document.getElementById('fullBlack');
const netflixGamesLogo = document.getElementById('netflixGamesLogo');
let thronglets = [];
let nextThrongletId = 0;
let deathCount = 0;
const MAX_THRONGLETS = 20; // Cap the number for performance
// --- Game State & Logic ---
function logToConsole(message) {
const msgElement = document.createElement('span');
msgElement.classList.add('message');
msgElement.textContent = `> ${message}`;
consoleDiv.appendChild(msgElement);
consoleDiv.scrollTop = consoleDiv.scrollHeight; // Auto-scroll
}
function triggerGlitch(duration = 1000) {
glitchOverlay.classList.add('active');
setTimeout(() => {
glitchOverlay.classList.remove('active');
}, duration);
}
function showInfoPanel(text, duration = 2000) {
infoPanel.textContent = text;
infoPanel.classList.add('visible');
clearTimeout(infoPanel.timeout);
infoPanel.timeout = setTimeout(() => {
infoPanel.classList.remove('visible');
}, duration);
}
function createThronglet(x, y) {
if (thronglets.length >= MAX_THRONGLETS) {
logToConsole("Too many Thronglets.");
return;
}
const throngletElement = document.createElement('span');
throngletElement.classList.add('emoji', 'thronglet');
// Use a default emoji that could resemble the character
throngletElement.textContent = '🐕'; // Changed from 🐻 - maybe more ears?
throngletElement.dataset.id = nextThrongletId++;
throngletElement.style.top = `${y}%`;
throngletElement.style.left = `${x}%`;
gameContainer.querySelector('.game-elements').appendChild(throngletElement);
const newThronglet = {
id: throngletElement.dataset.id,
element: throngletElement,
hunger: 50, // 0-100, 100 is very hungry
cleanliness: 50, // 0-100, 100 is very dirty
happiness: 50, // 0-100, 0 is very unhappy
lastInteraction: Date.now(),
isDead: false,
memory: [], // To store events
};
thronglets.push(newThronglet);
// Add click listener for interaction
throngletElement.addEventListener('click', () => interactThronglet(newThronglet));
logToConsole(`A new Thronglet #${newThronglet.id} appeared!`);
// Add feedback element
const feedbackElement = document.createElement('span');
feedbackElement.classList.add('emoji', 'feedback');
feedbackElement.style.top = `${y}%`;
feedbackElement.style.left = `${x}%`;
gameContainer.querySelector('.game-elements').appendChild(feedbackElement);
newThronglet.feedbackElement = feedbackElement;
}
function interactThronglet(thronglet) {
if (thronglet.isDead) return;
const now = Date.now();
const timeSinceLast = now - thronglet.lastInteraction;
thronglet.lastInteraction = now;
// Simple interaction feedback
thronglet.happiness = Math.max(0, thronglet.happiness - 10); // Interacting makes them slightly happy
showFeedback(thronglet, '👋'); // Wave emoji
logToConsole(`Interacted with Thronglet #${thronglet.id}`);
// Check for "tricks" (not implemented, but could be a mechanic)
// If they have high stats, maybe they "learn" something?
if (thronglet.hunger < 20 && thronglet.cleanliness < 20 && thronglet.happiness > 80 && timeSinceLast < 5000) {
// This is a placeholder for a learning/trick mechanic
logToConsole(`Thronglet #${thronglet.id} seems responsive...`);
thronglet.memory.push('PositiveInteraction'); // Store positive memory
showFeedback(thronglet, '✨');
}
}
function feedThronglet(thronglet) {
if (thronglet.isDead) return;
thronglet.hunger = Math.max(0, thronglet.hunger - 30);
thronglet.happiness = Math.min(100, thronglet.happiness + 15);
showFeedback(thronglet, '🍎');
logToConsole(`Fed Thronglet #${thronglet.id}. Hunger: ${thronglet.hunger}`);
thronglet.memory.push('Fed');
}
function cleanThronglet(thronglet) {
if (thronglet.isDead) return;
thronglet.cleanliness = Math.max(0, thronglet.cleanliness - 40);
thronglet.happiness = Math.min(100, thronglet.happiness + 20);
showFeedback(thronglet, '🛁');
logToConsole(`Cleaned Thronglet #${thronglet.id}. Cleanliness: ${thronglet.cleanliness}`);
thronglet.memory.push('Cleaned');
}
function showFeedback(thronglet, emoji) {
if (thronglet.feedbackElement) {
thronglet.feedbackElement.textContent = emoji;
thronglet.feedbackElement.classList.add('active');
clearTimeout(thronglet.feedbackElement.timeout);
thronglet.feedbackElement.timeout = setTimeout(() => {
thronglet.feedbackElement.classList.remove('active');
}, 500);
}
}
function killThronglet(thronglet) {
if (thronglet.isDead) return;
thronglet.isDead = true;
thronglet.element.classList.add('dead');
thronglet.element.textContent = '💀'; // Replace with skull
thronglet.feedbackElement.remove(); // Remove feedback icon
// Add blood splatter effect briefly
bloodSplatter.style.top = thronglet.element.style.top;
bloodSplatter.style.left = thronglet.element.style.left;
bloodSplatter.classList.add('splatter');
setTimeout(() => { bloodSplatter.classList.remove('splatter'); }, 1000);
deathCount++;
logToConsole(`Thronglet #${thronglet.id} experienced the void.`);
logToConsole(`Learned individuals are wholly disposable`); // From trailer
thronglet.memory.push('Died'); // Remember death
// Trigger events based on death count
if (deathCount === 1) {
showInfoPanel("The first Thronglet to experience the void...");
triggerGlitch();
} else if (deathCount > 3 && deathCount % 3 === 0) {
logToConsole(`They'll remember your carelessness...`);
triggerGlitch(500);
}
// Maybe respawn faster after death? Or spawn more?
setTimeout(() => {
// Simple respawn logic - replace the skull with a new egg/thronglet
thronglet.element.remove(); // Remove the skull
thronglets = thronglets.filter(t => t.id !== thronglet.id); // Remove from array
const { top, left } = thronglet.element.style;
createThronglet(parseFloat(left), parseFloat(top)); // Spawn a new one near where it died
}, 5000); // Respawn after 5 seconds
}
function updateThronglets() {
const now = Date.now();
thronglets.forEach(thronglet => {
if (thronglet.isDead) return;
const timeAlive = now - thronglet.element.dataset.bornTime; // Track time alive
const timeSinceLast = now - thronglet.lastInteraction;
// Increase needs over time (faster if neglected)
const needIncreaseRate = timeSinceLast / 10000; // Faster increase if ignored for 10s
thronglet.hunger = Math.min(100, thronglet.hunger + needIncreaseRate);
thronglet.cleanliness = Math.min(100, thronglet.cleanliness + needIncreaseRate * 0.8); // Cleaning slightly less critical
thronglet.happiness = Math.max(0, thronglet.happiness - needIncreaseRate * 1.2); // Happiness decays faster
// Check conditions for death
if (thronglet.hunger >= 90 || thronglet.cleanliness >= 90 || thronglet.happiness <= 10) {
killThronglet(thronglet);
}
// Check for reproduction (if very happy and well-cared for)
if (thronglet.happiness > 85 && thronglet.hunger < 20 && thronglet.cleanliness < 20 && timeSinceLast < 30000 && Math.random() < 0.001) { // Small random chance
logToConsole(`Thronglet #${thronglet.id} feels loved. Oh, Look!`); // From trailer
showFeedback(thronglet, '❤️');
// Spawn a new one nearby
setTimeout(() => {
createThronglet(parseFloat(thronglet.element.style.left) + (Math.random() - 0.5) * 10,
parseFloat(thronglet.element.style.top) + (Math.random() - 0.5) * 10);
}, 500); // Short delay to show feedback
}
// Simple movement (random walk)
const currentX = parseFloat(thronglet.element.style.left);
const currentY = parseFloat(thronglet.element.style.top);
const moveDist = 2; // Max movement percentage
const newX = Math.max(5, Math.min(95, currentX + (Math.random() - 0.5) * moveDist)); // Keep within bounds
const newY = Math.max(5, Math.min(95, currentY + (Math.random() - 0.5) * moveDist));
thronglet.element.style.left = `${newX}%`;
thronglet.element.style.top = `${newY}%`;
// Periodically show a status hint (optional, can clutter console)
// if (Math.random() < 0.005) {
// logToConsole(`#${thronglet.id}: H:${thronglet.hunger.toFixed(0)} C:${thronglet.cleanliness.toFixed(0)} P:${thronglet.happiness.toFixed(0)}`);
// }
});
// Trigger final phase after significant events (e.g., high death count, many Thronglets)
if (deathCount >= 5 && ! ominousElements.classList.contains('visible')) {
logToConsole("Did you...");
setTimeout(() => { logToConsole("do this??_"); triggerGlitch(1000); }, 1000);
setTimeout(() => { ominousElements.classList.add('visible'); }, 2000);
setTimeout(() => { logToConsole("we understand,,"); }, 3000);
setTimeout(() => { logToConsole("we know what we must do 🤔"); triggerGlitch(2000); }, 4000);
// Transition to final screen after console messages
setTimeout(showFinalScreen, 8000);
}
}
function showFinalScreen() {
gameContainer.style.opacity = 0;
consoleDiv.style.opacity = 0;
scanlineOverlay.style.opacity = 0;
glitchOverlay.style.opacity = 0;
fullBlack.classList.add('visible');
setTimeout(() => {
netflixGamesLogo.classList.add('visible');
fullBlack.classList.remove('visible'); // Fade out black
}, 2000); // Show N logo after black screen
setTimeout(() => {
netflixGamesLogo.style.opacity = 0; // Fade out N
finalScreen.classList.add('visible'); // Show final screen with app store links
}, 4000); // Show final screen
}
// --- Event Handlers ---
egg.addEventListener('click', () => {
if (egg.classList.contains('hatching')) return;
egg.classList.add('hatching');
logToConsole("Egg is hatching...");
setTimeout(() => {
egg.remove(); // Remove the egg element
createThronglet(50, 50); // Spawn first Thronglet in the center
}, 1000); // Hatch after 1 second
});
apple.addEventListener('click', () => {
const activeThronglets = thronglets.filter(t => !t.isDead);
if (activeThronglets.length > 0) {
// Find the hungriest or a random one
const targetThronglet = activeThronglets.reduce((prev, current) => (prev.hunger > current.hunger) ? prev : current, activeThronglets[0]);
feedThronglet(targetThronglet);
} else {
logToConsole("No Thronglets to feed!");
}
});
bath.addEventListener('click', () => {
const activeThronglets = thronglets.filter(t => !t.isDead);
if (activeThronglets.length > 0) {
// Find the dirtiest or a random one
const targetThronglet = activeThronglets.reduce((prev, current) => (prev.cleanliness > current.cleanliness) ? prev : current, activeThronglets[0]);
cleanThronglet(targetThronglet);
} else {
logToConsole("No Thronglets to clean!");
}
});
// --- Game Loop ---
let gameInterval;
function startGameLoop() {
gameInterval = setInterval(updateThronglets, 500); // Update every 0.5 seconds
}
// --- Initial Setup ---
function initGame() {
// Hide game elements until title screen is gone
gameContainer.style.opacity = 0;
consoleDiv.style.opacity = 0;
ominousElements.style.opacity = 0;
finalScreen.style.opacity = 0;
fullBlack.style.opacity = 0;
netflixGamesLogo.style.opacity = 0;
// Listen for title screen click to start
titleScreen.addEventListener('click', () => {
titleScreen.style.opacity = 0;
setTimeout(() => {
titleScreen.style.display = 'none';
gameContainer.style.opacity = 1;
consoleDiv.style.opacity = 1;
logToConsole("Welcome to Thronglets!");
logToConsole("Tap the egg to hatch your first Thronglet.");
startGameLoop();
}, 2000); // Fade out title screen
});
logToConsole("Click anywhere on the screen to start."); // Initial console message behind title
}
// Start the game
initGame();
</script>
</body>
</html>