- Replaced hardcoded SPECIES_LENGTH with runtime Box3 measurement (model added to parent group BEFORE measuring, so full GLB hierarchy transforms are included in world matrices) - All 9 species now render at correct target size (0.13-0.32u) - Added optional chaining (?.) to all Alpine template expressions referencing store.selectedFish to prevent null-reference errors - Guarded getFishValue/canBreed calls with null checks
193 lines
10 KiB
HTML
193 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Aquarium</title>
|
|
</head>
|
|
<body class="bg-aqua-900">
|
|
<canvas id="aquarium" class="fixed inset-0 w-full h-full"></canvas>
|
|
|
|
<!-- UI Overlay -->
|
|
<div id="game-ui" class="fixed inset-0 pointer-events-none" x-data="gameUI()" x-init="init()">
|
|
|
|
<!-- Top HUD Bar -->
|
|
<div class="absolute top-0 left-0 right-0 h-12 flex items-center justify-between px-4 pointer-events-auto bg-aqua-900/70 backdrop-blur-sm border-b border-aqua-700/50">
|
|
<!-- Left: Money + Fish count -->
|
|
<div class="flex items-center gap-4">
|
|
<div class="flex items-center gap-1.5 text-yellow-400 font-semibold">
|
|
<span class="text-lg">$</span>
|
|
<span x-text="formatMoney(store.money)"></span>
|
|
</div>
|
|
<div class="flex items-center gap-1.5 text-aqua-200">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6.5 12c.94-3.46 4.94-6 8.5-6 3.56 0 7.56 2.54 8.5 6-.94 3.47-4.94 6-8.5 6-3.56 0-7.56-2.53-8.5-6Z"/><path d="M6 12H2"/><path d="M6 12l3-3"/><path d="M6 12l3 3"/></svg>
|
|
<span x-text="store.fishCount + ' / ' + store.fishCapacity"></span>
|
|
</div>
|
|
</div>
|
|
<!-- Right: Tank tier + buttons -->
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs text-aqua-300 px-2 py-0.5 rounded bg-aqua-800/80 border border-aqua-700" x-text="store.tankName + ' Tank'"></span>
|
|
<button @click="store.showShop = true"
|
|
class="text-xs px-3 py-1.5 rounded-lg bg-aqua-700 hover:bg-aqua-600 text-white transition-colors cursor-pointer">
|
|
🐟 Shop
|
|
</button>
|
|
<button @click="store.showSettings = true"
|
|
class="text-xs px-2 py-1.5 rounded-lg bg-aqua-800 hover:bg-aqua-700 text-aqua-300 transition-colors cursor-pointer">
|
|
⚙️
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fish Info Panel (right side) -->
|
|
<div x-if="store.selectedFish" x-transition.opacity.duration.200ms
|
|
class="absolute right-4 top-16 w-72 pointer-events-auto"
|
|
x-cloak>
|
|
<div class="bg-aqua-900/90 backdrop-blur-md rounded-xl border border-aqua-700/60 p-4 shadow-xl">
|
|
<!-- Header -->
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div>
|
|
<h3 class="text-white font-semibold text-sm" x-text="store.selectedFish?.name"></h3>
|
|
<div class="flex items-center gap-1.5 mt-1">
|
|
<span class="text-[10px] px-1.5 py-0.5 rounded font-medium"
|
|
:class="rarityBg(store.selectedFish?.genetics.rarity)"
|
|
x-text="store.selectedFish?.genetics.rarity.toUpperCase()"></span>
|
|
<span class="text-[10px] text-aqua-300" x-text="'(' + rarityMult(store.selectedFish?.genetics.rarity) + 'x value)'"></span>
|
|
</div>
|
|
</div>
|
|
<button @click="store.selectedFish = null" class="text-aqua-400 hover:text-white cursor-pointer">✕</button>
|
|
</div>
|
|
<!-- Genetics -->
|
|
<div class="grid grid-cols-2 gap-x-3 gap-y-1.5 text-xs mb-3">
|
|
<div class="text-aqua-300">Gender</div>
|
|
<div class="text-white" x-text="store.selectedFish?.genetics.gender === 'male' ? '♂ Male' : '♀ Female'"></div>
|
|
<div class="text-aqua-300">Size</div>
|
|
<div class="text-white" x-text="store.selectedFish?.genetics.sizeMult.toFixed(2) + 'x'"></div>
|
|
<div class="text-aqua-300">Speed</div>
|
|
<div class="text-white" x-text="store.selectedFish?.genetics.speedMult.toFixed(2) + 'x'"></div>
|
|
<div class="text-aqua-300">Pattern</div>
|
|
<div class="text-white capitalize" x-text="store.selectedFish?.genetics.pattern"></div>
|
|
<div class="text-aqua-300">Age</div>
|
|
<div class="text-white" x-text="formatAge(store.selectedFish?.age)"></div>
|
|
</div>
|
|
<!-- Value -->
|
|
<div class="text-sm mb-3">
|
|
<span class="text-aqua-300">Sell value: </span>
|
|
<span class="text-yellow-400 font-bold" x-text="store.selectedFish ? formatMoney(getFishValue(store.selectedFish)) : ''"></span>
|
|
</div>
|
|
<!-- Actions -->
|
|
<div class="flex gap-2">
|
|
<button @click="requestSell()"
|
|
class="flex-1 text-xs py-2 rounded-lg bg-red-900/60 hover:bg-red-800 text-red-200 border border-red-800 transition-colors cursor-pointer">
|
|
Sell
|
|
</button>
|
|
<button @click="requestBreed()"
|
|
class="flex-1 text-xs py-2 rounded-lg bg-aqua-700 hover:bg-aqua-600 text-white border border-aqua-600 transition-colors cursor-pointer"
|
|
x-disabled="!store.selectedFish || !canBreed(store.selectedFish)">
|
|
Breed →
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shop Modal -->
|
|
<div x-show="store.showShop" x-transition.opacity.duration.200ms
|
|
class="absolute inset-0 flex items-center justify-center pointer-events-auto bg-black/60 backdrop-blur-sm"
|
|
x-cloak>
|
|
<div class="bg-aqua-900/95 border border-aqua-700 rounded-2xl w-[500px] max-h-[80vh] overflow-y-auto p-6 shadow-2xl">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-white font-bold text-lg">🐟 Fish Shop</h2>
|
|
<button @click="store.showShop = false" class="text-aqua-400 hover:text-white cursor-pointer text-xl">✕</button>
|
|
</div>
|
|
<!-- Fish grid -->
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<template x-for="sp in shopSpecies" :key="sp">
|
|
<div class="bg-aqua-800/60 rounded-xl border border-aqua-700/50 p-3">
|
|
<div class="text-white font-medium text-sm mb-1" x-text="speciesNames[sp]"></div>
|
|
<div class="text-yellow-400 text-xs mb-2" x-text="formatMoney(fishPrices[sp])"></div>
|
|
<div class="text-[10px] text-aqua-400 mb-2 leading-tight" x-html="rarityOdds(sp)"></div>
|
|
<button @click="buyFish(sp)"
|
|
:disabled="store.money < fishPrices[sp] || store.fishCount >= store.fishCapacity"
|
|
class="w-full text-xs py-1.5 rounded-lg bg-aqua-600 hover:bg-aqua-500 text-white transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed">
|
|
<span x-text="store.fishCount >= store.fishCapacity ? 'Tank Full' : 'Buy'"></span>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- Footer -->
|
|
<div class="flex justify-between text-xs text-aqua-400">
|
|
<span x-text="'Fish: ' + store.fishCount + ' / ' + store.fishCapacity"></span>
|
|
<span x-text="'Balance: ' + formatMoney(store.money)"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modal -->
|
|
<div x-show="store.showSettings" x-transition.opacity.duration.200ms
|
|
class="absolute inset-0 flex items-center justify-center pointer-events-auto bg-black/60 backdrop-blur-sm"
|
|
x-cloak>
|
|
<div class="bg-aqua-900/95 border border-aqua-700 rounded-2xl w-80 p-6 shadow-2xl">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-white font-bold">⚙️ Settings</h2>
|
|
<button @click="store.showSettings = false" class="text-aqua-400 hover:text-white cursor-pointer">✕</button>
|
|
</div>
|
|
<!-- Tank upgrade -->
|
|
<div class="mb-4" x-show="store.nextTierCost !== null">
|
|
<div class="text-xs text-aqua-300 mb-2">Tank: <span x-text="store.tankName"></span></div>
|
|
<button @click="upgradeTank()"
|
|
:disabled="store.money < store.nextTierCost"
|
|
class="w-full text-xs py-2 rounded-lg bg-aqua-600 hover:bg-aqua-500 text-white transition-colors cursor-pointer disabled:opacity-40">
|
|
<span x-text="'Upgrade to ' + nextTierName() + ' — $' + store.nextTierCost"></span>
|
|
</button>
|
|
</div>
|
|
<div class="text-xs text-aqua-400 mb-4" x-show="store.nextTierCost === null">
|
|
Tank is at MAX size 🎉
|
|
</div>
|
|
<!-- Reset -->
|
|
<button @click="store.showResetConfirm = true"
|
|
class="w-full text-xs py-2 rounded-lg bg-red-900/40 hover:bg-red-900/60 text-red-300 border border-red-900 transition-colors cursor-pointer">
|
|
Reset Game
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reset Confirmation -->
|
|
<div x-show="store.showResetConfirm" x-transition.opacity.duration.150ms
|
|
class="absolute inset-0 flex items-center justify-center pointer-events-auto bg-black/70 z-50"
|
|
x-cloak>
|
|
<div class="bg-aqua-900 border border-red-900 rounded-2xl w-80 p-6 shadow-2xl">
|
|
<h3 class="text-white font-bold mb-2">⚠️ Reset Game</h3>
|
|
<p class="text-sm text-aqua-300 mb-4">This will permanently delete ALL your fish, money, and progress.</p>
|
|
<p class="text-xs text-red-400 mb-4">Type <code class="bg-red-900/50 px-1 rounded">DELETE</code> to confirm:</p>
|
|
<input type="text" x-model="store.resetConfirmText"
|
|
class="w-full mb-4 px-3 py-2 rounded-lg bg-aqua-800 border border-aqua-700 text-white text-sm"
|
|
placeholder="DELETE" @keyup.enter="confirmReset()" />
|
|
<div class="flex gap-2">
|
|
<button @click="store.showResetConfirm = false; store.resetConfirmText = ''"
|
|
class="flex-1 text-xs py-2 rounded-lg bg-aqua-800 hover:bg-aqua-700 text-white cursor-pointer">Cancel</button>
|
|
<button @click="confirmReset()" :disabled="store.resetConfirmText !== 'DELETE'"
|
|
class="flex-1 text-xs py-2 rounded-lg bg-red-800 hover:bg-red-700 text-white cursor-pointer disabled:opacity-40">Reset</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toasts -->
|
|
<div class="absolute top-14 right-4 flex flex-col gap-2 items-end">
|
|
<template x-for="toast in store.toasts" :key="toast.id">
|
|
<div class="px-3 py-2 rounded-lg text-xs font-medium shadow-lg backdrop-blur-sm border transition-all duration-300"
|
|
:class="{
|
|
'bg-green-900/80 border-green-700 text-green-200': toast.type === 'success',
|
|
'bg-aqua-800/80 border-aqua-600 text-aqua-200': toast.type === 'info',
|
|
'bg-yellow-900/80 border-yellow-700 text-yellow-200': toast.type === 'warning',
|
|
'bg-purple-900/80 border-purple-700 text-purple-200': toast.type === 'rare',
|
|
}"
|
|
x-text="toast.text">
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script type="module" src="/src/main.ts"></script>
|
|
</body>
|
|
</html>
|