๐ŸŽฎArcadeLab

Ultimate Fishing Simulator

by LuckyStar74
399 lines26.5 KB
โ–ถ Play

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Ultimate Fishing Simulator</title>
<style>
:root{--bg:#081827;--panel:#10283b;--line:#28536a;--text:#f0fbff;--muted:#a6c5d5;--mint:#56edbd;--blue:#38bce8;--gold:#ffd166}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(ellipse at top,#123d52 0,#081827 58%);color:var(--text);font:15px/1.4 system-ui,sans-serif}
button{font:inherit;font-weight:750;border:0;border-radius:11px;padding:11px 13px;color:#061d2a;background:var(--mint);cursor:pointer;transition:filter .15s,transform .1s;touch-action:manipulation}
button:hover{filter:brightness(1.08)}
button:active{transform:scale(.98)}
button:disabled{opacity:.4;cursor:not-allowed}
#app{max-width:1000px;margin:auto;min-height:100vh;padding:12px;display:flex;flex-direction:column;gap:12px}
.top{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:10px 4px}
.brand{display:flex;align-items:center;gap:11px}
.logo{width:48px;height:48px;display:grid;place-items:center;background:linear-gradient(145deg,#6fffd0,#1bb4cb);border-radius:15px;font-size:26px;box-shadow:0 6px 25px #1bc8ba35}
.brand h1{font-size:clamp(20px,4vw,28px);line-height:1.1;margin:0;letter-spacing:-.7px}
.sub{color:var(--muted);font-size:12px;margin-top:5px}
.wallet{display:flex;gap:8px;flex-wrap:wrap}
.pill{padding:9px 12px;border:1px solid #2b5a70;border-radius:12px;background:#102b3e;font-weight:800}
.pill span{color:var(--gold)}
.scene-wrap{position:relative;border:1px solid #2a5a6e;border-radius:18px;overflow:hidden;background:#70d9e7;box-shadow:0 15px 35px #0003}
canvas{display:block;width:100%;height:clamp(220px,35vw,310px)}
.scene-label{position:absolute;top:12px;left:12px;border:1px solid #ffffff40;background:#08283cbd;padding:8px 11px;border-radius:10px;font-weight:750;font-size:13px}
.weather{position:absolute;right:12px;top:12px;background:#08283cbd;padding:8px 11px;border-radius:10px;font-size:12px}
.scene-tip{position:absolute;bottom:12px;left:12px;right:12px;text-align:center;font-weight:800;text-shadow:0 2px 6px #002b40;font-size:14px;pointer-events:none}
.nav{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:7px}
.nav button{background:#143349;border:1px solid #2a5065;color:#b9d7e4;padding:11px 5px;font-size:12px}
.nav button.active{background:var(--blue);color:#062033;border-color:var(--blue)}
.layout{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(220px,.8fr);gap:12px}
.panel{background:linear-gradient(145deg,#112d42,#0d2436);border:1px solid #244a60;border-radius:16px;padding:16px;min-width:0}
.panel h2{font-size:17px;margin:0 0 12px;letter-spacing:-.2px}
.muted{color:var(--muted)}
.tiny{font-size:12px}
.row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.row.wrap{flex-wrap:wrap}
.big{font-size:24px;font-weight:900;letter-spacing:-.5px}
.stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.stat{background:#0a2030;border:1px solid #21455a;border-radius:12px;padding:11px}
.stat .muted{font-size:11px}
.stat .big{font-size:20px}
.actions{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:12px}
.gold{background:var(--gold);color:#392700}
.bar{height:13px;background:#061723;border-radius:20px;overflow:hidden;border:1px solid #285069;margin:9px 0}
.bar>div{height:100%;width:0;background:linear-gradient(90deg,#42dfb1,#e9e76b);transition:width .12s}
.status{min-height:38px;padding:10px 11px;background:#0a2030;border:1px solid #20455b;border-radius:10px;color:#c5e8f3;font-size:13px}
.hidden{display:none!important}
.cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}
.item{background:#0b2233;border:1px solid #24495d;border-radius:13px;padding:12px;min-width:0}
.item .emoji{font-size:27px;margin-bottom:5px}
.item button{width:100%;margin-top:10px;font-size:12px;padding:10px 7px}
.item p{font-size:12px;color:var(--muted);margin:7px 0}
.rarity{font-size:11px;font-weight:850;text-transform:uppercase;letter-spacing:.5px}
.fish-row{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid #214255}
.fish-row:last-child{border-bottom:0}
.fish-emoji{font-size:28px;width:35px;text-align:center}
.fish-info{flex:1;min-width:0}
.fish-info strong{display:block}
.fish-info small{color:var(--muted)}
.empty{text-align:center;padding:20px 8px;color:var(--muted)}
.right-stack{display:flex;flex-direction:column;gap:12px}
.progress-label{font-size:12px;color:var(--muted)}
.tag{font-size:11px;padding:4px 7px;border-radius:7px;background:#21485a;color:#c9f4ff}
.notice{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);width:max-content;max-width:calc(100% - 28px);padding:12px 16px;background:#e6fff7;color:#073128;border-radius:12px;box-shadow:0 8px 35px #0006;font-weight:800;z-index:9;text-align:center}
.notice.hide{display:none}
footer{text-align:center;color:#7395a8;font-size:11px;padding:6px 0 14px}
@media(max-width:650px){
#app{padding:9px;gap:9px}.layout{grid-template-columns:1fr}.panel{padding:13px}
.nav{gap:5px}.nav button{font-size:11px;padding:10px 2px}
.weather{font-size:11px}.wallet{gap:5px}.pill{padding:8px;font-size:12px}
.brand h1{font-size:21px}.logo{width:42px;height:42px}
.stats{gap:6px}.stat{padding:9px}.stat .big{font-size:18px}
}
@media(max-width:370px){
.cards{grid-template-columns:1fr}.nav button{font-size:10px}
.actions{gap:6px}.actions button{padding:10px 6px}
}
</style>
</head>
<body>
<div id="app">
<header class="top">
 <div class="brand"><div class="logo">๐ŸŽฃ</div><div>
 <h1>Ultimate Fishing</h1><div class="sub">CAST โ€ข CATCH โ€ข COLLECT</div></div></div>
 <div class="wallet"><div class="pill">๐Ÿช™ <span id="coins">100</span></div><div class="pill">โญ Level <span id="level">1</span></div></div>
</header>

<div class="scene-wrap">
 <canvas id="scene" aria-label="Animated fishing lake"></canvas>
 <div class="scene-label" id="placeBadge">๐ŸŒฟ Peaceful Lake</div>
 <div class="weather" id="weather">โ˜€๏ธ Clear skies</div>
 <div class="scene-tip" id="sceneTip">Your next big catch is out there.</div>
</div>

<nav class="nav" aria-label="Game sections">
 <button class="active" data-tab="fish">๐ŸŽฃ Fish</button>
 <button data-tab="shop">๐Ÿ›’ Shop</button>
 <button data-tab="places">๐Ÿ—บ๏ธ Places</button>
 <button data-tab="book">๐Ÿ“– Fish Book</button>
 <button data-tab="quests">๐Ÿ† Rewards</button>
</nav>

<main>
<section id="tab-fish">
 <div class="layout">
  <div class="panel">
   <div class="row wrap"><div><h2 id="locationTitle">๐ŸŒฟ Peaceful Lake</h2>
   <div class="muted tiny" id="locationDesc">A calm spot to learn the art of fishing.</div></div>
   <span class="tag" id="rodName">Basic Rod</span></div>
   <div style="margin-top:17px" class="row"><strong id="phase">Ready to cast</strong><span class="progress-label" id="phaseHint">Tap CAST to begin</span></div>
   <div class="bar"><div id="bar"></div></div>
   <div class="status" id="status" aria-live="polite">Cast your line and wait for a bite. Reel in when the bobber flashes!</div>
   <div class="actions"><button id="castBtn">๐ŸŽฏ CAST LINE</button><button id="reelBtn" class="gold" disabled>๐Ÿช REEL IN!</button></div>
   <div class="muted tiny" style="margin-top:10px">Tip: You have a short window to reel in after the bite. Spacebar also works.</div>
  </div>
  <div class="right-stack">
   <div class="panel"><h2>๐Ÿ“Š Angler Stats</h2><div class="stats">
    <div class="stat"><div class="big" id="caughtCount">0</div><div class="muted">Fish caught</div></div>
    <div class="stat"><div class="big" id="speciesCount">0/19</div><div class="muted">Species found</div></div>
    <div class="stat"><div class="big" id="rareCount">0</div><div class="muted">Rare+ fish</div></div>
   </div></div>
   <div class="panel"><h2>๐ŸŽ’ Your Keepnet</h2><div class="row">
    <span class="muted tiny" id="bagText">No fish in your keepnet yet.</span><strong id="bagValue">๐Ÿช™ 0</strong>
   </div><button id="sellBtn" class="gold" style="width:100%;margin-top:12px">๐Ÿ’ฐ Sell All Fish</button></div>
  </div>
 </div>
 <div class="panel" style="margin-top:12px"><div class="row"><h2>โœจ Latest Catch</h2><span class="tag">Catch log</span></div><div id="lastCatch" class="empty">Your first catch will appear here.</div></div>
</section>

<section id="tab-shop" class="hidden">
 <div class="panel"><h2>๐Ÿ›’ Rod Shop</h2><p class="muted tiny">Better rods improve rare-fish odds. Tap an owned rod to equip it.</p><div id="rodCards" class="cards"></div></div>
 <div class="panel" style="margin-top:12px"><h2>โš™๏ธ Upgrades</h2><div id="upgradeCards" class="cards"></div></div>
</section>

<section id="tab-places" class="hidden">
 <div class="panel"><h2>๐Ÿ—บ๏ธ Explore the world</h2><p class="muted tiny">Unlock new waters to discover fish that don't live in the lake.</p><div id="placeCards" class="cards"></div></div>
</section>

<section id="tab-book" class="hidden">
 <div class="panel"><div class="row"><h2>๐Ÿ“– Fish Collection</h2><span class="tag" id="bookProgress">0 discovered</span></div><div id="fishBook" class="cards"></div></div>
</section>

<section id="tab-quests" class="hidden">
 <div class="layout">
  <div class="panel"><h2>๐Ÿ† Achievements</h2><div id="achievementList"></div></div>
  <div class="right-stack">
   <div class="panel"><h2>๐ŸŽ Daily Gift</h2><p class="muted tiny">Claim 150 free coins once each day.</p><button id="dailyBtn" class="gold" style="width:100%">Claim 150 Coins</button><div class="muted tiny" id="dailyText" style="margin-top:8px"></div></div>
   <div class="panel"><h2>๐Ÿ“ˆ Records</h2><div id="records" class="muted tiny"></div></div>
  </div>
 </div>
</section>
</main>
<footer>Made for ArcadeLab ยท Your progress saves on this browser when storage is available.</footer>
</div>
<div id="toast" class="notice hide" role="status"></div>

<script>
(()=>{
'use strict';
const $=id=>document.getElementById(id);
const species=[
{name:'Minnow',emoji:'๐ŸŸ',rarity:'Common',value:8,weight:1,places:[0,2]},
{name:'Bluegill',emoji:'๐Ÿ ',rarity:'Common',value:12,weight:1.1,places:[0]},
{name:'Carp',emoji:'๐ŸŸ',rarity:'Common',value:16,weight:1.3,places:[0,2]},
{name:'Perch',emoji:'๐Ÿ ',rarity:'Uncommon',value:25,weight:1.5,places:[0,3]},
{name:'Rainbow Trout',emoji:'๐ŸŸ',rarity:'Uncommon',value:32,weight:1.8,places:[0,2,3]},
{name:'Golden Koi',emoji:'๐Ÿ ',rarity:'Rare',value:90,weight:2.5,places:[0]},
{name:'Piranha',emoji:'๐Ÿก',rarity:'Uncommon',value:40,weight:2,places:[2]},
{name:'Arowana',emoji:'๐Ÿ‰',rarity:'Epic',value:180,weight:3,places:[2]},
{name:'River Croaker',emoji:'๐ŸŸ',rarity:'Rare',value:75,weight:2.5,places:[2]},
{name:'Clownfish',emoji:'๐Ÿ ',rarity:'Common',value:20,weight:1,places:[1]},
{name:'Tuna',emoji:'๐ŸŸ',rarity:'Uncommon',value:50,weight:2,places:[1]},
{name:'Swordfish',emoji:'๐Ÿ—ก๏ธ',rarity:'Epic',value:220,weight:4,places:[1]},
{name:'Great White Shark',emoji:'๐Ÿฆˆ',rarity:'Legendary',value:500,weight:5,places:[1]},
{name:'Kraken Hatchling',emoji:'๐Ÿ™',rarity:'Mythical',value:1600,weight:8,places:[1]},
{name:'Ice Cod',emoji:'๐ŸŸ',rarity:'Common',value:22,weight:1.3,places:[3]},
{name:'Snow Salmon',emoji:'๐ŸŸ',rarity:'Rare',value:100,weight:2.5,places:[3]},
{name:'Frost Dragonfish',emoji:'๐Ÿ‰',rarity:'Legendary',value:650,weight:5,places:[3]},
{name:'Glacier Leviathan',emoji:'๐Ÿ‹',rarity:'Mythical',value:2000,weight:9,places:[3]},
{name:'Ancient Goldfish',emoji:'โœจ',rarity:'Mythical',value:1200,weight:4,places:[0]}
];
const places=[
{name:'Peaceful Lake',emoji:'๐ŸŒฟ',cost:0,desc:'A calm spot to learn the art of fishing.',weather:'โ˜€๏ธ Clear skies'},
{name:'Tropical Ocean',emoji:'๐ŸŒŠ',cost:400,desc:'Deep blue water hides enormous ocean fish.',weather:'๐ŸŒž Sunny & breezy'},
{name:'Jungle River',emoji:'๐ŸŒด',cost:700,desc:'A wild river with rare freshwater creatures.',weather:'๐ŸŒฟ Warm & leafy'},
{name:'Frozen Lake',emoji:'โ„๏ธ',cost:1100,desc:'Ice-cold water conceals legendary catches.',weather:'โ„๏ธ Snow flurries'}
];
const rods=[
{name:'Basic Rod',cost:0,luck:1,emoji:'๐ŸŽฃ',desc:'Reliable for a beginner.'},
{name:'Bamboo Rod',cost:180,luck:1.35,emoji:'๐ŸŽ‹',desc:'A little extra luck.'},
{name:'Carbon Rod',cost:550,luck:1.8,emoji:'๐Ÿช',desc:'Great for uncommon catches.'},
{name:'Golden Rod',cost:1300,luck:2.5,emoji:'โœจ',desc:'A serious rare-fish boost.'},
{name:'Cosmic Rod',cost:3500,luck:3.6,emoji:'๐ŸŒŒ',desc:'The ultimate angler tool.'}
];
const rarity=['Common','Uncommon','Rare','Epic','Legendary','Mythical'];
const colors={Common:'#b5cfdd',Uncommon:'#5ce5a1',Rare:'#56c6ff',Epic:'#d59bff',Legendary:'#ffd166',Mythical:'#ff86d7'};
const fresh=()=>({coins:100,xp:0,caught:[],inventory:[],rod:0,owned:[0],place:0,unlocked:[0],speed:0,power:0,total:0,rare:0,best:-1,claimed:'',achievements:[]});
let s=fresh();
try{
 const old=localStorage.getItem('ultimateFishingSave');
 if(old){const parsed=JSON.parse(old);s={...fresh(),...parsed};if(!Array.isArray(s.inventory))s.inventory=[];if(!Array.isArray(s.caught))s.caught=[];if(!Array.isArray(s.owned))s.owned=[0];if(!Array.isArray(s.unlocked))s.unlocked=[0];}
}catch(e){}
let mode='ready',timer=null,deadline=0,biteStart=0,currentFish=null,barTimer=null,toastTimer=null,anim=0,ctx=null,canvas=$('scene'),c=canvas.getContext('2d');
function save(){try{localStorage.setItem('ultimateFishingSave',JSON.stringify(s))}catch(e){}}
function fmt(n){return Math.floor(n).toLocaleString()}
function rod(){return rods[s.rod]||rods[0]}
function say(t){$('status').textContent=t}
function toast(t){$('toast').textContent=t;$('toast').classList.remove('hide');clearTimeout(toastTimer);toastTimer=setTimeout(()=>$('toast').classList.add('hide'),2100)}
function sound(freq=520){
 try{if(!ctx)ctx=new(window.AudioContext||window.webkitAudioContext)();if(ctx.state==='suspended')ctx.resume();const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.frequency.value=freq;g.gain.value=.035;o.start();g.gain.exponentialRampToValueAtTime(.001,ctx.currentTime+.16);o.stop(ctx.currentTime+.16)}catch(e){}
}
function setMode(m){
 mode=m;
 $('phase').textContent=({ready:'Ready to cast',waiting:'Line in the waterโ€ฆ',bite:'๐ŸŸ FISH ON!',caught:'Catch landed!',miss:'Fish escaped!'})[m]||m;
 $('castBtn').disabled=m!=='ready';$('reelBtn').disabled=m!=='bite';
 $('bar').style.width=m==='bite'?'100%':'0%';
 $('phaseHint').textContent=m==='bite'?'REEL NOW!':m==='waiting'?'Be patientโ€ฆ':m==='caught'?'Nice catch!':'Tap CAST to begin';
}
function reset(){clearTimeout(timer);clearInterval(barTimer);currentFish=null;setMode('ready');$('sceneTip').textContent='Your next big catch is out there.'}
function chooseFish(){
 const avail=species.filter(f=>f.places.includes(s.place));
 const weights=avail.map(f=>{let ri=rarity.indexOf(f.rarity);return [0.53,.25,.13,.06,.025,.005][ri]*Math.pow(rod().luck,ri*.65)});
 let n=Math.random()*weights.reduce((a,b)=>a+b,0);
 for(let i=0;i<avail.length;i++){n-=weights[i];if(n<=0)return avail[i]}
 return avail[0]
}
function cast(){
 if(mode!=='ready')return;
 setMode('waiting');say('Your bobber is driftingโ€ฆ wait for a bite.');$('sceneTip').textContent='Casting your lineโ€ฆ';sound(340);
 const delay=Math.max(900,2600-s.speed*420)+Math.random()*1900;
 timer=setTimeout(()=>{
  if(mode!=='waiting')return;
  currentFish=chooseFish();mode='bite';biteStart=Date.now();deadline=biteStart+Math.max(1000,2600+s.power*400);
  setMode('bite');say('A fish is biting! Tap REEL IN right now!');$('sceneTip').textContent='SPLASH! REEL IN NOW!';sound(820);
  clearInterval(barTimer);
  barTimer=setInterval(()=>{
   if(mode!=='bite'){clearInterval(barTimer);return}
   const left=Math.max(0,(deadline-Date.now())/(deadline-biteStart)*100);$('bar').style.width=left+'%';
   if(Date.now()>=deadline){clearInterval(barTimer);setMode('miss');say('Too slow โ€” the fish got away. Try another cast!');$('sceneTip').textContent='The fish escaped ๐Ÿ’ฆ';sound(170);timer=setTimeout(reset,1100)}
  },40)
 },delay)
}
function reel(){
 if(mode!=='bite'||!currentFish)return;
 clearInterval(barTimer);
 const f=currentFish;
 const weight=+(f.weight*(.7+Math.random()*.8)*(1+s.power*.08)).toFixed(1);
 const price=Math.round(f.value*weight/f.weight*(Math.random()<.1?1.5:1));
 s.inventory.push({...f,weight,price});
 if(!s.caught.includes(f.name))s.caught.push(f.name);
 s.total++;s.xp+=10+rarity.indexOf(f.rarity)*8;
 if(rarity.indexOf(f.rarity)>=2)s.rare++;
 s.best=Math.max(s.best,rarity.indexOf(f.rarity));
 setMode('caught');say('You caught '+f.name+'! Sell it or keep fishing.');
 $('sceneTip').textContent='GREAT CATCH! '+f.emoji;
 $('lastCatch').innerHTML=`<div class="fish-row"><div class="fish-emoji">${f.emoji}</div><div class="fish-info"><strong style="color:${colors[f.rarity]}">${f.name}</strong><small>${f.rarity} ยท ${weight} kg</small></div><strong>๐Ÿช™ ${fmt(price)}</strong></div>`;
 sound(660);checkAchievements();save();render();timer=setTimeout(()=>{if(mode==='caught')reset()},1350)
}
function sell(){
 if(!s.inventory.length){toast('Your keepnet is empty โ€” catch some fish first!');return}
 const total=s.inventory.reduce((n,f)=>n+f.price,0);
 s.coins+=total;s.inventory=[];save();render();say('Sold your catch for '+fmt(total)+' coins!');toast('Sold fish for ๐Ÿช™ '+fmt(total));sound(540);checkAchievements();render()
}
function buyRod(i){
 const r=rods[i];
 if(s.owned.includes(i)){s.rod=i;save();render();toast('Equipped '+r.name);return}
 if(s.coins<r.cost){toast('Not enough coins โ€” sell your fish first.');return}
 s.coins-=r.cost;s.owned.push(i);s.rod=i;save();render();toast('Bought '+r.name+'!');sound(600)
}
function upgrade(type){
 const level=s[type],cost=Math.round((type==='speed'?150:200)*Math.pow(2,level));
 if(level>=4){toast('Upgrade already maxed!');return}
 if(s.coins<cost){toast('Not enough coins yet!');return}
 s.coins-=cost;s[type]++;save();render();toast(type==='speed'?'Fish will bite sooner!':'You have more time to reel!');sound(600)
}
function travel(i){
 const p=places[i];
 if(!s.unlocked.includes(i)){
  if(s.coins<p.cost){toast('You need '+p.cost+' coins to unlock this spot.');return}
  s.coins-=p.cost;s.unlocked.push(i);toast('Unlocked '+p.name+'!')
 }
 s.place=i;reset();save();render();say('Welcome to '+p.name+'.');sound(480)
}
const achDefs=()=>[
['First Catch',s.total>=1,50],['Getting Hooked',s.total>=10,100],
['Master Angler',s.total>=30,250],['Rare Hunter',s.rare>=5,200],
['Legend Finder',s.best>=4,350],['Fish Collector',s.caught.length>=10,300],
['Big Wallet',s.coins>=1000,200]
];
function checkAchievements(){
 let changed=false;
 achDefs().forEach(([name,ok,reward])=>{
  if(ok&&!s.achievements.includes(name)){s.achievements.push(name);s.coins+=reward;toast('๐Ÿ† '+name+'! +'+reward+' coins');changed=true}
 });
 if(changed)save()
}
function daily(){
 const today=new Date().toISOString().slice(0,10);
 if(s.claimed===today){toast('Daily gift already claimed. Come back tomorrow!');return}
 s.claimed=today;s.coins+=150;save();render();toast('Daily gift claimed: +150 coins!');sound(700)
}
function showTab(tab){
 document.querySelectorAll('.nav button').forEach(b=>b.classList.toggle('active',b.dataset.tab===tab));
 document.querySelectorAll('main section').forEach(sec=>sec.classList.toggle('hidden',sec.id!=='tab-'+tab));
 render()
}
document.querySelectorAll('.nav button').forEach(b=>b.addEventListener('click',()=>showTab(b.dataset.tab)));
$('castBtn').addEventListener('click',cast);
$('reelBtn').addEventListener('click',reel);
$('sellBtn').addEventListener('click',sell);
$('dailyBtn').addEventListener('click',daily);
document.addEventListener('keydown',e=>{
 if(e.code==='Space'&&!e.repeat){
  const active=document.querySelector('main section:not(.hidden)');
  if(active&&active.id==='tab-fish'){e.preventDefault();if(mode==='bite')reel();else if(mode==='ready')cast()}
 }
});
function render(){
 $('coins').textContent=fmt(s.coins);$('level').textContent=1+Math.floor(s.xp/100);
 $('caughtCount').textContent=s.total;$('speciesCount').textContent=s.caught.length+'/'+species.length;$('rareCount').textContent=s.rare;
 $('rodName').textContent=rod().name;$('placeBadge').textContent=places[s.place].emoji+' '+places[s.place].name;
 $('weather').textContent=places[s.place].weather;$('locationTitle').textContent=places[s.place].emoji+' '+places[s.place].name;
 $('locationDesc').textContent=places[s.place].desc;
 $('bagText').textContent=s.inventory.length?s.inventory.length+' fish waiting to sell':'No fish in your keepnet yet.';
 $('bagValue').textContent='๐Ÿช™ '+fmt(s.inventory.reduce((n,f)=>n+f.price,0));$('sellBtn').disabled=!s.inventory.length;
 $('rodCards').innerHTML=rods.map((r,i)=>`<div class="item"><div class="emoji">${r.emoji}</div><strong>${r.name}</strong><p>${r.desc}</p><div class="rarity" style="color:var(--gold)">๐Ÿ€ Luck ร—${r.luck}</div><button data-rod="${i}" ${s.rod===i?'disabled':''}>${s.rod===i?'Equipped':s.owned.includes(i)?'Equip Rod':'๐Ÿช™ '+fmt(r.cost)}</button></div>`).join('');
 $('rodCards').querySelectorAll('[data-rod]').forEach(b=>b.addEventListener('click',()=>buyRod(+b.dataset.rod)));
 $('upgradeCards').innerHTML=[{key:'speed',title:'Faster Bites',emoji:'โšก',desc:'Fish bite sooner.',max:s.speed},{key:'power',title:'Reel Power',emoji:'๐Ÿ’ช',desc:'More time to reel in.',max:s.power}].map(u=>{
  const cost=Math.round((u.key==='speed'?150:200)*Math.pow(2,u.max));
  return `<div class="item"><div class="emoji">${u.emoji}</div><strong>${u.title}</strong><p>${u.desc}</p><div class="rarity">Level ${u.max}/4</div><button data-upgrade="${u.key}" ${u.max>=4?'disabled':''}>${u.max>=4?'MAX LEVEL':'Upgrade ยท ๐Ÿช™ '+fmt(cost)}</button></div>`
 }).join('');
 $('upgradeCards').querySelectorAll('[data-upgrade]').forEach(b=>b.addEventListener('click',()=>upgrade(b.dataset.upgrade)));
 $('placeCards').innerHTML=places.map((p,i)=>`<div class="item"><div class="emoji">${p.emoji}</div><strong>${p.name}</strong><p>${p.desc}</p><div class="rarity">${s.unlocked.includes(i)?'UNLOCKED':'NEW DESTINATION'}</div><button data-place="${i}" ${s.place===i?'disabled':''}>${s.place===i?'You are here':s.unlocked.includes(i)?'Travel here':'๐Ÿ”’ Unlock ยท ๐Ÿช™ '+fmt(p.cost)}</button></div>`).join('');
 $('placeCards').querySelectorAll('[data-place]').forEach(b=>b.addEventListener('click',()=>travel(+b.dataset.place)));
 $('bookProgress').textContent=s.caught.length+' discovered';
 $('fishBook').innerHTML=species.map(f=>{
  const found=s.caught.includes(f.name);
  return `<div class="item"><div class="emoji">${found?f.emoji:'โ”'}</div><strong>${found?f.name:'Unknown Species'}</strong><p style="color:${found?colors[f.rarity]:'var(--muted)'}">${found?f.rarity:'Not discovered yet'}</p><div class="tiny muted">${found?'Base value: '+f.value+' coins':'Catch it to reveal it'}</div></div>`
 }).join('');
 $('achievementList').innerHTML=achDefs().map(([name,ok,reward])=>`<div class="fish-row"><div class="fish-emoji">${s.achievements.includes(name)?'๐Ÿ†':'๐Ÿ”’'}</div><div class="fish-info"><strong>${name}</strong><small>Reward: ${reward} coins</small></div><span class="tag">${s.achievements.includes(name)?'Done':ok?'Ready':'Locked'}</span></div>`).join('');
 const today=new Date().toISOString().slice(0,10);
 $('dailyBtn').disabled=s.claimed===today;$('dailyText').textContent=s.claimed===today?'Already claimed today.':'Ready to claim!';
 $('records').innerHTML=`<p>Total catches: <b>${s.total}</b></p><p>Rare+ catches: <b>${s.rare}</b></p><p>Species discovered: <b>${s.caught.length}/${species.length}</b></p><p>Best rarity: <b>${s.best>=0?rarity[s.best]:'None yet'}</b></p><p>XP: <b>${s.xp}</b></p>`
}
function resizeCanvas(){
 const rect=canvas.getBoundingClientRect(),dpr=Math.min(window.devicePixelRatio||1,2);
 canvas.width=Math.max(1,Math.floor(rect.width*dpr));canvas.height=Math.max(1,Math.floor(rect.height*dpr));c.setTransform(dpr,0,0,dpr,0,0)
}
function draw(){
 const w=canvas.clientWidth,h=canvas.clientHeight;
 if(!w||!h){requestAnimationFrame(draw);return}
 anim+=.016;const waterY=h*.43;c.clearRect(0,0,w,h);
 let sky=c.createLinearGradient(0,0,0,h);
 sky.addColorStop(0,s.place===3?'#a6c8e3':s.place===1?'#63d5ed':'#7ce0eb');sky.addColorStop(1,'#d0f7ee');
 c.fillStyle=sky;c.fillRect(0,0,w,h);
 c.fillStyle=s.place===3?'#9bb9cc':s.place===2?'#397c68':'#65a99a';
 c.beginPath();c.moveTo(0,waterY);
 for(let x=0;x<=w+30;x+=30)c.lineTo(x,waterY-30-Math.sin(x*.012+1)*25-Math.sin(x*.027)*13);
 c.lineTo(w,waterY);c.closePath();c.fill();
 c.fillStyle=s.place===3?'#d8eff9':'#a7d4b7';c.beginPath();c.moveTo(0,waterY+5);
 for(let x=0;x<=w+30;x+=30)c.lineTo(x,waterY+3-Math.sin(x*.009+3)*16);
 c.lineTo(w,waterY+10);c.closePath();c.fill();
 c.fillStyle='#fff1ad';c.beginPath();c.arc(w*.82,h*.19,Math.min(24,w*.045),0,Math.PI*2);c.fill();
 c.fillStyle='#fff7cc22';c.beginPath();c.arc(w*.82,h*.19,39,0,Math.PI*2);c.fill();
 let wg=c.createLinearGradient(0,waterY,0,h);
 wg.addColorStop(0,s.place===3?'#a3d9eb':s.place===1?'#26b5d1':'#35b8cf');wg.addColorStop(1,s.place===3?'#3d83a6':'#08658a');
 c.fillStyle=wg;c.fillRect(0,waterY,w,h-waterY);
 for(let i=0;i<30;i++){
  let x=((i*73+anim*(15+i%4)*5)%(w+70))-30,y=waterY+12+((i*37)%(h-waterY-15));
  c.strokeStyle=`rgba(226,255,255,${.12+(i%4)*.035})`;c.lineWidth=1+(i%3)*.5;
  c.beginPath();c.ellipse(x,y,8+(i%5)*4,1.5,0,0,Math.PI*2);c.stroke()
 }
 c.fillStyle=s.place===3?'#d7edf2':'#c4a16a';c.beginPath();c.moveTo(0,h*.8);c.quadraticCurveTo(w*.16,h*.72,w*.34,h*.83);c.lineTo(w*.34,h);c.lineTo(0,h);c.closePath();c.fill();
 c.fillStyle='#80583c';c.fillRect(w*.12,h*.76,w*.29,9);c.fillStyle='#bd8b55';
 for(let i=0;i<7;i++)c.fillRect(w*.12+i*w*.041,h*.76,2,16);
 const px=w*.23,py=h*.72;
 c.fillStyle='#183e4e';c.beginPath();c.ellipse(px,py+16,20,6,0,0,Math.PI*2);c.fill();
 c.fillStyle='#244f64';c.fillRect(px-10,py-7,20,22);
 c.fillStyle='#f0bc91';c.beginPath();c.arc(px,py-19,10,0,Math.PI*2);c.fill();
 c.fillStyle='#f2d65c';c.beginPath();c.arc(px,py-22,11,Math.PI,Math.PI*2);c.fill();c.fillRect(px-12,py-22,24,4);
 c.strokeStyle='#533e2e';c.lineWidth=3;c.beginPath();c.moveTo(px+5,py-6);c.lineTo(px+18,py-22);c.lineTo(px+30,py-31);c.stroke();
 c.strokeStyle='#eaf9f5';c.lineWidth=1;c.beginPath();c.moveTo(px+30,py-31);c.quadraticCurveTo(w*.53,h*.45,w*.67,h*.66);c.stroke();
 const bx=w*.67,by=h*.66+Math.sin(anim*2)*2;
 c.fillStyle='#fff';c.beginPath();c.ellipse(bx,by+7,12,4,0,0,Math.PI*2);c.fill();
 c.fillStyle=mode==='bite'?'#fff05d':'#f05261';c.beginPath();c.ellipse(bx,by,5,9,0,0,Math.PI*2);c.fill();
 if(mode==='bite'){c.strokeStyle='#fff5a0';c.lineWidth=2;c.beginPath();c.arc(bx,by,15+Math.sin(anim*9)*5,0,Math.PI*2);c.stroke();c.fillStyle='#fff';c.font='bold 19px system-ui';c.fillText('!',bx+11,by-15)}
 if(s.place===3){c.fillStyle='#e7fbff';for(let i=0;i<18;i++){let x=(i*97+anim*4)%(w+10),y=(i*43)%(waterY-5);c.globalAlpha=.6;c.fillRect(x,y,2,2)}c.globalAlpha=1}
 requestAnimationFrame(draw)
}
window.addEventListener('resize',resizeCanvas);
resizeCanvas();render();setMode('ready');draw();
})();
</script>
</body>
</html>

Game Source: Ultimate Fishing Simulator

Creator: LuckyStar74

Libraries: none

Complexity: complex (399 lines, 26.5 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: ultimate-fishing-simulator-luckystar74" to link back to the original. Then publish at arcadelab.ai/publish.