Compare commits
5
Commits
7bc83a782d
...
185845f969
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
185845f969 | ||
|
|
3a4c2579a5 | ||
|
|
ac69e456d9 | ||
|
|
5385e10a41 | ||
|
|
762a698db4 |
@@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
"Always maintain a minimum distance of 10 feet (3 meters) from sea turtles on land.",
|
||||
"Crowding sea turtles causes them severe stress and can disrupt their natural nesting or resting behaviors.",
|
||||
"Do not use flash photography near sea turtles; the bright flash disorients them and can disrupt nesting mothers.",
|
||||
"Filing in beach holes and removing beach chairs prevents hatchlings from getting trapped on their way to the ocean."
|
||||
"Filling in beach holes and removing beach chairs prevents hatchlings from getting trapped on their way to the ocean."
|
||||
];
|
||||
|
||||
const PREDATOR_EMOJIS = ['🦀', '🦅', '🦝'];
|
||||
@@ -61,6 +61,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let beachingProgress = 0; // 0 to 100
|
||||
let beachingInterval = null;
|
||||
let beachingTouristsInterval = null;
|
||||
// Earliest time (ms) the next beaching event may trigger. Keeps beaching to
|
||||
// an occasional event rather than something that interrupts every minute.
|
||||
const BEACHING_COOLDOWN_MS = 5 * 60 * 1000;
|
||||
let nextBeachingAllowedAt = 0;
|
||||
|
||||
// ==========================================
|
||||
// 2. DOM ELEMENTS
|
||||
@@ -119,6 +123,75 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
sad: document.getElementById('turtle-mouth-sad')
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// AUDIO: Turtle voice lines + ambient water SFX
|
||||
// ==========================================
|
||||
const MEDIA_PATH = 'media/';
|
||||
|
||||
// Voice lines keyed to the speech bubble they accompany (file name → trigger).
|
||||
const TURT_VOX = {
|
||||
deepBlue: 'turtvox001_another_day_in_the_deep_blue.m4a',
|
||||
bestHuman: 'turtvox002_best_human_ever.m4a',
|
||||
bittenShell: 'turtvox003_bitten_shell_but_ok.m4a',
|
||||
somebodysComing: 'turtvox004_somebodys_coming.m4a',
|
||||
ateBagToxic: 'turtvox005_ate_a_bag_toxic.m4a',
|
||||
propellerStrike: 'turtvox006_propeller_strike_fatal_wound.m4a',
|
||||
heehee: 'turtvox006_heeheehee.m4a',
|
||||
morePlease: 'turtvox008_more_please.m4a',
|
||||
hehe: 'turtvox09_hehe_that_tickles.m4a',
|
||||
yummySeagrass: 'turtvox011_yummy_seagrass.m4a',
|
||||
letsPlay: 'turtvox012_another_turtle_lets_play.m4a',
|
||||
swimSafe: 'turtvox013_swim_safe_friend.m4a',
|
||||
applyingSalve: 'turtvox014_applying_salve.m4a',
|
||||
grewALittle: 'turtvox15_I_think_I_grew_a_little.m4a',
|
||||
crunchyCrab: 'turtvox016_crunchy_crab.m4a',
|
||||
softJellyfish: 'turtvox017_soft_jellyfish.m4a'
|
||||
};
|
||||
|
||||
const WATER_SFX = [
|
||||
'water_sfx_001.m4a',
|
||||
'water_sfx_002.m4a',
|
||||
'water_sfx_003.m4a',
|
||||
'turtvox008_glub_glub_glub.m4a'
|
||||
];
|
||||
|
||||
// Play a single turtle voice line. Autoplay is allowed because the player has
|
||||
// already tapped through the start screen before any voice line fires.
|
||||
let currentVoice = null;
|
||||
function playVoice(key) {
|
||||
const file = TURT_VOX[key];
|
||||
if (!file) return;
|
||||
try {
|
||||
if (currentVoice) currentVoice.pause();
|
||||
currentVoice = new Audio(MEDIA_PATH + file);
|
||||
currentVoice.volume = 0.9;
|
||||
currentVoice.play().catch(() => {});
|
||||
} catch (e) { /* audio unsupported */ }
|
||||
}
|
||||
|
||||
// Ambient underwater sound — loops continuously through the water SFX clips
|
||||
// for the whole time the player is in the ocean scene.
|
||||
let ambientAudio = null;
|
||||
let ambientIndex = 0;
|
||||
function startAmbientWater() {
|
||||
if (ambientAudio) return;
|
||||
ambientIndex = 0;
|
||||
ambientAudio = new Audio(MEDIA_PATH + WATER_SFX[0]);
|
||||
ambientAudio.volume = 0.3;
|
||||
ambientAudio.addEventListener('ended', () => {
|
||||
if (!ambientAudio) return;
|
||||
ambientIndex = (ambientIndex + 1) % WATER_SFX.length;
|
||||
ambientAudio.src = MEDIA_PATH + WATER_SFX[ambientIndex];
|
||||
ambientAudio.play().catch(() => {});
|
||||
});
|
||||
ambientAudio.play().catch(() => {});
|
||||
}
|
||||
function stopAmbientWater() {
|
||||
if (!ambientAudio) return;
|
||||
ambientAudio.pause();
|
||||
ambientAudio = null;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
const btnStartGame = document.getElementById('btn-start-game');
|
||||
const btnShowFactsMain = document.getElementById('btn-show-facts-main');
|
||||
@@ -136,6 +209,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const btnHeal = document.getElementById('btn-action-heal');
|
||||
const feedDrawer = document.getElementById('feed-options-drawer');
|
||||
|
||||
// Name inputs / dynamic name labels
|
||||
const inputTurtleNameStart = document.getElementById('input-turtle-name-start');
|
||||
const beachingTurtleName = document.getElementById('beaching-turtle-name');
|
||||
const bonusTurtleName = document.getElementById('bonus-turtle-name');
|
||||
|
||||
// HUD Outputs
|
||||
const displayTurtleName = document.getElementById('display-turtle-name');
|
||||
const displayTurtleSpecies = document.getElementById('display-turtle-species');
|
||||
@@ -491,15 +569,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
turtleAgeVal.textContent = formatAge(turtleAge);
|
||||
applyGrowthStage();
|
||||
if (wholeDays > 0 && Math.random() > 0.7) {
|
||||
triggerThoughtBubble(getGrowthThought(), 2200);
|
||||
const growth = getGrowthThought();
|
||||
triggerThoughtBubble(growth.text, 2200, growth.voice);
|
||||
}
|
||||
}
|
||||
|
||||
saveProgress();
|
||||
|
||||
// Beaching happens on a regular cycle for healthy adults — roughly
|
||||
// every few minutes of active play.
|
||||
if (turtleAge > 7 && stats.health > 50 && Math.random() < 0.18) {
|
||||
// Beaching is an occasional event for healthy adults. A cooldown plus a
|
||||
// low per-tick chance keeps it to roughly once every several minutes
|
||||
// rather than interrupting play every minute.
|
||||
if (turtleAge > 7 && stats.health > 50 &&
|
||||
Date.now() >= nextBeachingAllowedAt && Math.random() < 0.04) {
|
||||
nextBeachingAllowedAt = Date.now() + BEACHING_COOLDOWN_MS;
|
||||
changeState(STATES.BEACHING);
|
||||
}
|
||||
}, 10000);
|
||||
@@ -533,10 +615,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (currentState !== STATES.OCEAN) return;
|
||||
spawnAmbientBubble();
|
||||
}, 1200);
|
||||
|
||||
// 7. Ambient underwater sound for the ocean scene
|
||||
startAmbientWater();
|
||||
}
|
||||
|
||||
function clearOceanLoops() {
|
||||
Object.values(oceanLoops).forEach(loop => clearInterval(loop));
|
||||
stopAmbientWater();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -568,7 +654,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function sanitizeName(n) {
|
||||
if (typeof n !== 'string') return 'Shelly';
|
||||
// Strip control chars and HTML-relevant punctuation; cap at 12 like the input.
|
||||
const cleaned = n.replace(/[ | ||||