Fisch 2D
by MegaFlare512346 lines28.9 KB
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Fisch 2D</title>
<style>
*{
box-sizing:border-box;
margin:0;
padding:0;
user-select:none;
-webkit-tap-highlight-color:transparent;
}
html,body{
width:100%;
height:100%;
overflow:hidden;
background:#020817;
font-family:Arial,sans-serif;
touch-action:none;
color:white;
}
canvas{
position:fixed;
inset:0;
width:100%;
height:100%;
}
button{
border:0;
border-radius:13px;
padding:10px 14px;
font-size:15px;
font-weight:bold;
background:#18bfff;
color:#001522;
box-shadow:0 4px #05658b;
pointer-events:auto;
}
button:active{
transform:translateY(3px);
box-shadow:0 1px #05658b;
}
.hidden{display:none!important}
/* ================= LOADING ================= */
#loading{
position:fixed;
inset:0;
z-index:100;
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
background:linear-gradient(135deg,#020817,#062a45,#130d32);
}
.logo{
font-size:42px;
font-weight:900;
text-shadow:0 0 20px #19cfff;
margin-bottom:10px;
}
#loadText{
font-size:18px;
font-weight:bold;
margin-bottom:18px;
}
.loadBox{
width:min(85vw,430px);
height:22px;
border:2px solid #29d8ff;
border-radius:20px;
overflow:hidden;
background:#02111e;
}
#loadBar{
width:0%;
height:100%;
background:linear-gradient(90deg,#079fff,#ff263f);
}
#loadPercent{
margin-top:8px;
font-weight:bold;
}
/* ================= LOBBY ================= */
#lobby{
position:fixed;
inset:0;
z-index:90;
display:none;
background:linear-gradient(135deg,#04152d,#10103b,#2b0714);
}
.lobbyTitle{
text-align:center;
margin-top:25px;
font-size:34px;
text-shadow:0 0 18px #ff2745;
}
.lobbySub{
text-align:center;
margin-top:6px;
color:#aeeeff;
}
.lobbyCard{
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
width:min(90vw,500px);
padding:22px;
border:2px solid #1bd3ff;
border-radius:22px;
background:#03182cf0;
text-align:center;
}
.lobbyCard button{
width:100%;
margin-top:12px;
font-size:19px;
}
/* ================= UI ================= */
#ui{
position:fixed;
inset:0;
z-index:10;
pointer-events:none;
}
.top{
display:flex;
gap:6px;
align-items:center;
padding:8px;
}
.box{
background:#021827dd;
border:1px solid #20d7ff;
border-radius:12px;
padding:7px 9px;
font-size:13px;
font-weight:bold;
}
.spacer{flex:1}
.top button{
pointer-events:auto;
padding:9px 11px;
}
#dateEvent{
position:fixed;
top:58px;
left:50%;
transform:translateX(-50%);
background:#05192bed;
border:1px solid #ff3150;
border-radius:18px;
padding:7px 14px;
font-size:13px;
font-weight:bold;
white-space:nowrap;
}
#hint{
position:fixed;
top:94px;
left:50%;
transform:translateX(-50%);
background:#03182deb;
border:1px solid #35dcff;
border-radius:20px;
padding:8px 14px;
font-weight:bold;
white-space:nowrap;
}
#message{
position:fixed;
top:135px;
left:50%;
transform:translateX(-50%);
background:#031827f5;
border:1px solid white;
border-radius:20px;
padding:9px 15px;
font-weight:bold;
opacity:0;
transition:.2s;
white-space:nowrap;
}
#buttons{
position:fixed;
bottom:14px;
left:0;
right:0;
display:flex;
justify-content:center;
}
#start{
font-size:19px;
padding:13px 28px;
}
/* ================= MENU ================= */
#menu{
position:fixed;
z-index:50;
left:50%;
top:50%;
transform:translate(-50%,-50%);
width:min(92vw,470px);
max-height:82vh;
overflow:auto;
display:none;
background:#031727fa;
border:2px solid #1bd5ff;
border-radius:21px;
padding:16px;
pointer-events:auto;
}
#menu h2{
text-align:center;
margin-bottom:12px;
}
.item{
background:#072b45;
border:1px solid #167294;
border-radius:14px;
padding:12px;
margin:8px 0;
text-align:center;
}
.bigFish{font-size:32px}
.close{
width:100%;
margin-top:8px;
background:#e72e4b;
color:white;
}
.event{
border-color:#ff3154;
background:#35101b;
}
.progress{
height:13px;
background:#020b13;
border-radius:20px;
overflow:hidden;
margin:8px 0;
border:1px solid #35dfff;
}
.progress div{
height:100%;
background:linear-gradient(90deg,#11bfff,#ff2444);
}
#tokenToast{
position:fixed;
left:50%;
bottom:80px;
transform:translateX(-50%);
background:#10152c;
border:1px solid #ffd83d;
padding:10px 15px;
border-radius:15px;
display:none;
z-index:60;
font-weight:bold;
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<!-- ================= LOADING ================= -->
<div id="loading">
<div class="logo">🎣 FISCH 2D</div>
<div id="loadText">Carregando sistemas...</div>
<div class="loadBox">
<div id="loadBar"></div>
</div>
<div id="loadPercent">0%</div>
</div>
<!-- ================= LOBBY ================= -->
<div id="lobby">
<div class="lobbyTitle">FISCH 2D</div>
<div class="lobbySub">
🌊 Temporada especial
</div>
<div class="lobbyCard">
<h2>🎮 LOBBY</h2>
<p style="margin-top:8px">
Dia 1 até 5 de agosto
</p>
<button id="playLobby">
🎣 JOGAR
</button>
</div>
</div>
<!-- ================= GAME ================= -->
<div id="ui" class="hidden">
<div class="top">
<div class="box">
⭐ Lv <span id="level">1</span>
</div>
<div class="box">
XP <span id="xp">0</span>/<span id="need">100</span>
</div>
<div class="box">
💰 <span id="coins">0</span>
</div>
<div class="spacer"></div>
<button id="inventory">🎒</button>
<button id="shop">🏪</button>
<button id="events">🎪</button>
</div>
<div id="dateEvent">
📅 Dia 1 até 5 de agosto
</div>
<div id="hint">
🎯 TOQUE NO PEIXE!
</div>
<div id="message"></div>
<div id="buttons">
<button id="start">▶️ COMEÇAR</button>
</div>
</div>
<!-- ================= MENU ================= -->
<div id="menu">
<h2 id="menuTitle">🎒 INVENTÁRIO</h2>
<div id="menuContent"></div>
<button class="close" id="close">
FECHAR
</button>
</div>
<div id="tokenToast"></div>
<script>
/* =====================================================
CANVAS
===================================================== */
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
let W=innerWidth;
let H=innerHeight;
let DPR=devicePixelRatio||1;
function resize(){
W=innerWidth;
H=innerHeight;
DPR=devicePixelRatio||1;
canvas.width=W*DPR;
canvas.height=H*DPR;
canvas.style.width=W+"px";
canvas.style.height=H+"px";
ctx.setTransform(DPR,0,0,DPR,0,0);
}
addEventListener("resize",resize);
resize();
/* =====================================================
SAVE
===================================================== */
let save;
try{
save=JSON.parse(
localStorage.getItem("Fisch2D_Save")
);
}catch(e){
save=null;
}
if(!save){
save={
level:1,
xp:0,
coins:0,
caught:[],
total:0,
event1:0,
event2:0,
event3:0,
tokens:0,
superTokens:0,
king:false,
lings:false
};
}
function normalizeSave(){
if(!Array.isArray(save.caught))
save.caught=[];
const defaults={
level:1,
xp:0,
coins:0,
total:0,
event1:0,
event2:0,
event3:0,
tokens:0,
superTokens:0,
king:false,
lings:false
};
for(const k in defaults){
if(typeof save[k]==="undefined")
save[k]=defaults[k];
}
}
normalizeSave();
function saveGame(){
try{
localStorage.setItem(
"Fisch2D_Save",
JSON.stringify(save)
);
}catch(e){}
}
/* =====================================================
EVENTO ATIVO
IMPORTANTE:
O GAME NORMAL COMEÇA SEM EVENTO.
===================================================== */
let activeEvent=0;
/* =====================================================
PEIXES
===================================================== */
const fishTypes=[
{
name:"Clow",
emoji:"🐟",
xp:30,
chance:90,
value:35,
speed:5
},
{
name:"Winder",
emoji:"🐠",
xp:50,
chance:70,
value:55,
speed:6
},
{
name:"Xisper",
emoji:"🐡",
xp:70,
chance:55,
value:80,
speed:7
},
{
name:"Fhsg",
emoji:"🐟",
xp:90,
chance:35,
value:100,
speed:8
},
{
name:"Klingh",
emoji:"🐠",
xp:120,
chance:20,
value:150,
speed:9
},
{
name:"Ukitronn",
emoji:"🦈",
xp:200,
chance:1,
value:300,
speed:10
},
{
name:"Ukinato",
emoji:"🐉",
xp:300,
chance:.002,
value:500,
speed:11
},
{
name:"Unki",
emoji:"👾",
xp:500,
chance:.0001,
value:900,
speed:12
},
{
name:"Rei",
emoji:"👑",
xp:1000,
chance:.0000001,
value:2000,
speed:13
}
];
/* =====================================================
TAMANHO
===================================================== */
function getFishSize(){
let r=Math.random()*100;
if(save.king){
if(r<10)
return {
multiplier:4,
text:"4× MAIOR",
rarity:"MEGA"
};
if(r<40)
return {
multiplier:3,
text:"3× MAIOR",
rarity:"GIGANTE"
};
return {
multiplier:2,
text:"2× MAIOR",
rarity:"GRANDE"
};
}
if(r<.001){
return {
multiplier:4,
text:"4× MAIOR",
rarity:"MEGA"
};
}
if(r<3.001){
return {
multiplier:3,
text:"3× MAIOR",
rarity:"GIGANTE"
};
}
if(r<33.001){
return {
multiplier:2,
text:"2× MAIOR",
rarity:"GRANDE"
};
}
return {
multiplier:1,
text:"NORMAL",
rarity:"NORMAL"
};
}
/* =====================================================
XP
===================================================== */
function xpNeeded(){
const l=save.level;
if(l<=10) return 100;
if(l<=50) return 300;
if(l<=200) return 450;
if(l<=500) return 700;
if(l<=700) return 700;
if(l<=1000) return 900;
if(l<=2000) return 1000;
if(l<=2500) return 1200;
if(l<=5000) return 1500;
if(l<=10000) return 2000;
return 3000;
}
function addXP(amount){
save.xp+=amount;
/*
A cada 25 XP:
+R$1
*/
const reais=Math.floor(save.xp/25);
if(reais>0){
save.coins+=reais;
save.xp-=reais*25;
}
let leveled=false;
while(save.xp>=xpNeeded()){
save.xp-=xpNeeded();
save.level++;
leveled=true;
}
if(leveled){
message(
"⬆️ NÍVEL "+save.level+"!"
);
}
updateUI();
saveGame();
}
/* =====================================================
UI
===================================================== */
function updateUI(){
document.getElementById("level").textContent=
save.level;
document.getElementById("xp").textContent=
save.xp;
document.getElementById("need").textContent=
xpNeeded();
document.getElementById("coins").textContent=
Math.floor(save.coins);
}
/* =====================================================
MESSAGE
===================================================== */
let messageTimer;
function message(text){
const el=document.getElementById("message");
el.textContent=text;
el.style.opacity=1;
clearTimeout(messageTimer);
messageTimer=setTimeout(()=>{
el.style.opacity=0;
},1800);
}
/* =====================================================
GAME
===================================================== */
let gameRunning=false;
let activeFish=[];
let bubbles=[];
let particles=[];
let spawnTimer=0;
let waveTime=0;
/* =====================================================
BOLHAS
===================================================== */
function createBubbles(){
bubbles=[];
for(let i=0;i<55;i++){
bubbles.push({
x:Math.random()*W,
y:H*.38+
Math.random()*H*.58,
r:1+
Math.random()*4,
speed:.2+
Math.random()*.8
});
}
}
createBubbles();
/* =====================================================
ESCOLHER PEIXE
===================================================== */
function chooseFish(){
let list=fishTypes.map(f=>{
let chance=f.chance;
if(save.lings && chance<20)
chance=20;
return {
fish:f,
chance:chance
};
});
let total=list.reduce(
(sum,x)=>sum+x.chance,
0
);
let r=Math.random()*total;
for(const item of list){
r-=item.chance;
if(r<=0)
return item.fish;
}
return fishTypes[0];
}
/* =====================================================
SPAWN
MÁXIMO DE 4
===================================================== */
function spawnFish(){
if(!gameRunning)
return;
if(activeFish.length>=4)
return;
const type=chooseFish();
const size=getFishSize();
const fromLeft=Math.random()<.5;
activeFish.push({
type:type,
x:fromLeft?-100:W+100,
y:H*.48+
Math.random()*H*.38,
direction:fromLeft?1:-1,
speed:type.speed+
Math.random()*2.5,
baseSize:30,
multiplier:size.multiplier,
sizeText:size.text,
sizeRarity:size.rarity,
phase:Math.random()*10
});
}
/* =====================================================
PEGAR PEIXE
===================================================== */
function catchFish(f){
const index=activeFish.indexOf(f);
if(index===-1)
return;
activeFish.splice(index,1);
save.caught.push({
name:f.type.name,
multiplier:f.multiplier
});
save.total++;
/*
PROGRESSO DOS EVENTOS
Só aumenta quando o evento estiver ativo.
*/
if(activeEvent===1){
save.event1++;
}
if(activeEvent===2){
save.event2++;
}
/*
EVENTO 3 pode continuar contando normalmente.
*/
save.event3++;
addXP(f.type.xp);
save.coins+=Math.round(
f.type.value*f.multiplier
);
/* EVENTO 1 */
if(activeEvent===1 && save.event1>=600){
save.event1=0;
save.coins+=1000;
message(
"🎪 EVENTO 1 COMPLETO! +R$1000"
);
}
/* EVENTO 2 */
if(activeEvent===2 && save.event2>=5000){
save.event2=0;
save.tokens++;
message(
"⚡ EVENTO 2 COMPLETO! +1 TOKEN"
);
}
/* EVENTO 3 */
if(save.event3>=30){
const tokens=
Math.floor(save.event3/30);
save.event3%=30;
save.tokens+=tokens;
showToken(
"🎟️ +"+tokens+" TOKEN(S)!"
);
}
createParticles(f.x,f.y);
let extra="";
if(f.multiplier>1)
extra=" 🔥 "+f.sizeText;
message(
f.type.emoji+
" "+f.type.name+
" +"+f.type.xp+
" XP"+
extra
);
saveGame();
updateUI();
}
/* =====================================================
TOUCH
===================================================== */
function touchGame(e){
if(!gameRunning)
return;
const rect=canvas.getBoundingClientRect();
const x=
(e.clientX-rect.left)*
W/rect.width;
const y=
(e.clientY-rect.top)*
H/rect.height;
for(
let i=activeFish.length-1;
i>=0;
i--
){
const f=activeFish[i];
const size=
f.baseSize*f.multiplier;
const dx=x-f.x;
const dy=y-f.y;
const distance=
Math.sqrt(dx*dx+dy*dy);
if(distance<size+28){
catchFish(f);
return;
}
}
}
canvas.addEventListener(
"pointerdown",
touchGame
);
/* =====================================================
PARTICULAS
===================================================== */
function createParticles(x,y){
for(let i=0;i<18;i++){
particles.push({
x:x,
y:y,
vx:(Math.random()-.5)*5,
vy:(Math.random()-.5)*5,
life:1
});
}
}
/* =====================================================
COMEÇAR JOGO NORMAL
===================================================== */
document.getElementById("start").onclick=()=>{
if(gameRunning)
return;
/*
IMPORTANTE:
sempre começa no modo normal.
Nenhum evento é ativado automaticamente.
*/
activeEvent=0;
gameRunning=true;
document.getElementById("start").textContent=
"🎣 PESCANDO...";
message(
"🌊 MODO NORMAL!"
);
for(let i=0;i<4;i++){
setTimeout(
spawnFish,
i*250
);
}
};
/* =====================================================
MENU
===================================================== */
const menu=document.getElementById("menu");
const menuTitle=document.getElementById("menuTitle");
const menuContent=document.getElementById("menuContent");
function openMenu(title,html){
menuTitle.textContent=title;
menuContent.innerHTML=html;
menu.style.display="block";
}
document.getElementById("close").onclick=()=>{
menu.style.display="none";
};
/* =====================================================
INVENTÁRIO
===================================================== */
document.getElementById("inventory").onclick=()=>{
const counts={};
save.caught.forEach(f=>{
const name=
typeof f==="string"
?f
:f.name;
const multiplier=
typeof f==="string"
?1
:f.multiplier;
if(!counts[name]){
counts[name]={
normal:0,
big:0,
giant:0,
mega:0
};
}
if(multiplier===1)
counts[name].normal++;
if(multiplier===2)
counts[name].big++;
if(multiplier===3)
counts[name].giant++;
if(multiplier===4)
counts[name].mega++;
});
let html=`
<div class="item">
🎣 <b>PEIXES CAPTURADOS</b>
<br><br>
Total: ${save.total}
</div>
`;
fishTypes.forEach(f=>{
if(!counts[f.name])
return;
const c=counts[f.name];
html+=`
<div class="item">
<div class="bigFish">
${f.emoji}
</div>
<b>${f.name}</b>
<br>
⭐ ${f.xp} XP
<br><br>
Normal: ${c.normal}
<br>
🔵 2×: ${c.big}
<br>
🟣 3×: ${c.giant}
<br>
🟡 4×: ${c.mega}
</div>
`;
});
html+=`
<div class="item">
🎟️ Tokens: ${save.tokens}
<br>
🟨 Tokens Super: ${save.superTokens}
</div>
`;
openMenu(
"🎒 INVENTÁRIO",
html
);
};
/* =====================================================
LOJA
===================================================== */
document.getElementById("shop").onclick=()=>{
let html=`
<div class="item">
<b>👑 KING</b>
<br><br>
Aumenta a chance de peixes grandes.
<br>
🎯 Até 99% de chance.
<br>
💰 R$30
<br>
<button onclick="buyKing()">
${save.king?"COMPRADO":"COMPRAR"}
</button>
</div>
<div class="item">
<b>🪽 LINGS</b>
<br><br>
Peixes abaixo de 20%
passam para 20%.
<br>
💰 R$25
<br>
<button onclick="buyLings()">
${save.lings?"COMPRADO":"COMPRAR"}
</button>
</div>
<div class="item">
<b>💵 PRESOS</b>
<br><br>
Preso 1: R$30
<br>
⏱️ 50 segundos
<br><br>
Preso 2: R$25
<br>
⏱️ 10 segundos
</div>
<div class="item">
💡 A cada 25 XP você ganha R$1.
</div>
`;
openMenu(
"🏪 LOJA",
html
);
};
/* =====================================================
COMPRAS
===================================================== */
window.buyKing=function(){
if(save.king){
message("👑 Você já possui KING!");
return;
}
if(save.coins<30){
message("❌ Dinheiro insuficiente!");
return;
}
save.coins-=30;
save.king=true;
saveGame();
updateUI();
message("👑 KING comprado!");
document.getElementById("shop").click();
};
window.buyLings=function(){
if(save.lings){
message("🪽 Você já possui LINGS!");
return;
}
if(save.coins<25){
message("❌ Dinheiro insuficiente!");
return;
}
save.coins-=25;
save.lings=true;
saveGame();
updateUI();
message("🪽 LINGS comprado!");
document.getElementById("shop").click();
};
/* =====================================================
EVENTOS
===================================================== */
document.getElementById("events").onclick=()=>{
const p1=Math.min(
100,
save.event1/600*100
);
const p2=Math.min(
100,
save.event2/5000*100
);
let html=`
<div class="item event">
<b>🎪 EVENTO 1 — SÓ ENTRA</b>
<br><br>
<button onclick="startEvent(1)">
ENTRAR NO EVENTO
</button>
<br><br>
Pegue <b>600 peixes</b>.
<br>
🏆 Recompensa: R$1000
<br><br>
Progresso:
<div class="progress">
<div style="width:${p1}%"></div>
</div>
${save.event1}/600
<br><br>
🎟️ A cada 100 peixes:
senha aleatória.
</div>
<div class="item event">
<b>⚡ EVENTO 2 — REFLEXO</b>
<br><br>
<button onclick="startEvent(2)">
ENTRAR NO EVENTO
</button>
<br><br>
Peixes passam
<b>4× mais rápido</b>.
<br>
Pegue <b>5000 peixes</b>.
<br>
🏆 Recompensa: Tokens
<br><br>
Progresso:
<div class="progress">
<div style="width:${p2}%"></div>
</div>
${save.event2}/5000
<br><br>
🎟️ A cada 500:
token aleatório.
</div>
<div class="item event">
<b>🎟️ EVENTO 3 — TOKENS</b>
<br><br>
A cada 30 peixes:
+1 Token.
<br><br>
Tokens atuais:
<b>${save.tokens}</b>
</div>
<div class="item">
<b>🟨 TOKENS SUPER</b>
<br><br>
<input id="superCode"
inputmode="numeric"
maxlength="4"
placeholder="Insira código"
style="
width:100%;
padding:12px;
border-radius:12px;
border:1px solid #25d9ff;
background:#02111c;
color:white;
text-align:center;
font-size:18px;
">
<button onclick="redeemSuper()">
RESGATAR
</button>
<br><br>
🟨 Tokens Super:
${save.superTokens}
</div>
`;
openMenu(
"🎪 EVENTOS",
html
);
};
/* =====================================================
ATIVAR EVENTO
===================================================== */
window.startEvent=function(eventNumber){
if(!gameRunning){
message(
"🎣 Comece o jogo primeiro!"
);
return;
}
activeEvent=eventNumber;
activeFish=[];
spawnTimer=0;
if(eventNumber===1){
message(
"🎪 EVENTO 1 ATIVO — PISTA NOVA!"
);
}
if(eventNumber===2){
message(
"⚡ EVENTO 2 ATIVO — PISTA RÁPIDA!"
);
}
menu.style.display="none";
};
/* =====================================================
TOKENS SUPER
===================================================== */
window.redeemSuper=function(){
const input=document.getElementById(
"superCode"
);
if(!input)
return;
const code=input.value.trim();
const rewards={
"9264":{
chance:99,
money:100000
},
"9153":{
chance:30,
money:200000
},
"8253":{
chance:4,
money:10000
},
"2838":{
chance:.1,
money:1000
}
};
if(!rewards[code]){
message("❌ Código inválido!");
return;
}
const reward=rewards[code];
if(Math.random()*100<reward.chance){
save.coins+=reward.money;
save.superTokens++;
message(
"🟨 TOKEN SUPER! +R$"+
reward.money.toLocaleString("pt-BR")
);
}else{
message(
"😢 O código não ativou."
);
}
saveGame();
updateUI();
};
/* =====================================================
DESENHO DO MAPA
===================================================== */
function draw(){
waveTime+=.02;
ctx.clearRect(0,0,W,H);
/* =================================================
MODO NORMAL
================================================= */
let skyTop="#59d9ff";
let skyBottom="#1375aa";
let seaTop="#09add7";
let seaMid="#0573aa";
let seaBottom="#022c50";
/* =================================================
EVENTO 1
PISTA DIFERENTE
================================================= */
if(activeEvent===1){
skyTop="#ff9b58";
skyBottom="#9c315d";
seaTop="#c4498c";
seaMid="#6b2b82";
seaBottom="#24153f";
}
/* =================================================
EVENTO 2
PISTA DIFERENTE
================================================= */
if(activeEvent===2){
skyTop="#ff314f";
skyBottom="#4b1238";
seaTop="#1d8cff";
seaMid="#153b91";
seaBottom="#080e3b";
}
/* CÉU */
const sky=ctx.createLinearGradient(
0,0,0,H*.4
);
sky.addColorStop(
0,
skyTop
);
sky.addColorStop(
1,
skyBottom
);
ctx.fillStyle=sky;
ctx.fillRect(
0,0,W,H*.4
);
/* SOL */
ctx.fillStyle=
activeEvent===1
?"#ffdb68"
:"#ffe68a";
ctx.beginPath();
ctx.arc(
W*.82,
H*.14,
34,
0,
Math.PI*2
);
ctx.fill();
/* MAR */
const sea=ctx.createLinearGradient(
0,H*.35,
0,H
);
sea.addColorStop(0,seaTop);
sea.addColorStop(.45,seaMid);
sea.addColorStop(1,seaBottom);
ctx.fillStyle=sea;
ctx.fillRect(
0,
H*.35,
W,
H*.65
);
/* =================================================
PISTA EVENTO 1
================================================= */
if(activeEvent===1){
ctx.fillStyle="#ffcf72";
ctx.beginPath();
ctx.ellipse(
W*.72,
H*.43,
145,
27,
0,
0,
Math.PI*2
);
ctx.fill();
ctx.fillStyle="#8fda67";
for(let i=0;i<6;i++){
ctx.beginPath();
ctx.arc(
W*.65+i*25,
H*.39,
18,
0,
Math.PI*2
);
ctx.fill();
}
}
/* =================================================
PISTA EVENTO 2
================================================= */
if(activeEvent===2){
ctx.strokeStyle="#ff3355aa";
ctx.lineWidth=5;
for(let i=0;i<7;i++){
ctx.beginPath();
ctx.moveTo(
i*W/7,
H*.45
);
ctx.lineTo(
i*W/7+W*.12,
H
);
ctx.stroke();
}
}
/* ONDAS */
ctx.strokeStyle=
activeEvent===2
?"#ff6e85aa"
:"#b4f6ff88";
ctx.lineWidth=2;
for(
let y=H*.36;
y<H*.43;
y+=12
){
ctx.beginPath();
for(
let x=0;
x<=W;
x+=18
){
const yy=
y+
Math.sin(
x*.025+
waveTime*2+
y
)*3;
if(x===0)
ctx.moveTo(x,yy);
else
ctx.lineTo(x,yy);
}
ctx.stroke();
}
/* ILHA NORMAL */
ctx.fillStyle=
activeEvent===1
?"#e8a14f"
:"#d5a45a";
ctx.beginPath();
ctx.ellipse(
W*.13,
H*.39,
100,
24,
0,
0,
Math.PI*2
);
ctx.fill();
ctx.fillStyle=
activeEvent===2
?"#c93152"
:"#29964e";
for(let i=0;i<5;i++){
ctx.beginPath();
ctx.arc(
W*.06+i*25,
H*.35,
22,
0,
Math.PI*2
);
ctx.fill();
}
/* BOLHAS */
bubbles.forEach(b=>{
b.y-=b.speed;
if(b.y<H*.37){
b.y=
H*.95+
Math.random()*50;
}
ctx.strokeStyle="#c2f8ff88";
ctx.lineWidth=1;
ctx.beginPath();
ctx.arc(
b.x,
b.y,
b.r,
0,
Math.PI*2
);
ctx.stroke();
});
/* =================================================
PEIXES
================================================= */
activeFish.forEach(f=>{
/*
SOMENTE EVENTO 2:
4× velocidade.
*/
const speedMultiplier=
activeEvent===2
?4
:1;
f.x+=
f.speed*
f.direction*
speedMultiplier;
f.y+=
Math.sin(
waveTime*3+
f.phase
)*.35;
const size=
f.baseSize*
f.multiplier;
ctx.save();
ctx.translate(
f.x,
f.y
);
if(f.direction<0)
ctx.scale(-1,1);
if(f.multiplier===4){
ctx.shadowBlur=25;
ctx.shadowColor="#ffd83d";
}
else if(f.multiplier===3){
ctx.shadowBlur=18;
ctx.shadowColor="#bd6cff";
}
else if(f.multiplier===2){
ctx.shadowBlur=12;
ctx.shadowColor="#39ddff";
}
ctx.font=
size+"px Arial";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillText(
f.type.emoji,
0,
0
);
ctx.shadowBlur=0;
if(f.multiplier>1){
ctx.font=
Math.max(
10,
size*.28
)+
"px Arial";
ctx.fillStyle=
f.multiplier===4
?"#ffd83d"
:f.multiplier===3
?"#d59aff"
:"#7deaff";
ctx.fillText(
f.sizeText,
0,
-size*.75
);
}
ctx.restore();
});
/* REMOVE PEIXES */
activeFish=
activeFish.filter(f=>
f.x>-180 &&
f.x<W+180
);
/* SPAWN */
if(gameRunning){
spawnTimer++;
if(
spawnTimer>=20 &&
activeFish.length<4
){
spawnTimer=0;
spawnFish();
}
}
/* PARTICULAS */
particles=
particles.filter(p=>{
p.x+=p.vx;
p.y+=p.vy;
p.vy+=.08;
p.life-=.035;
ctx.globalAlpha=
Math.max(0,p.life);
ctx.fillStyle="#fff";
ctx.beginPath();
ctx.arc(
p.x,
p.y,
4,
0,
Math.PI*2
);
ctx.fill();
ctx.globalAlpha=1;
return p.life>0;
});
requestAnimationFrame(draw);
}
/* =====================================================
TOKEN TOAST
===================================================== */
function showToken(text){
const el=document.getElementById(
"tokenToast"
);
el.textContent=text;
el.style.display="block";
setTimeout(()=>{
el.style.display="none";
},1800);
}
/* =====================================================
LOADING
===================================================== */
let loadingStart=Date.now();
function loadingLoop(){
const elapsed=
Date.now()-loadingStart;
const percent=
Math.min(
100,
elapsed/10000*100
);
document.getElementById(
"loadBar"
).style.width=
percent+"%";
document.getElementById(
"loadPercent"
).textContent=
Math.floor(percent)+"%";
if(percent<30){
document.getElementById(
"loadText"
).textContent=
"Carregando sistemas...";
}
else if(percent<70){
document.getElementById(
"loadText"
).textContent=
"Carregando tudo...";
}
else{
document.getElementById(
"loadText"
).textContent=
"Carregando resto...";
}
if(percent>=100){
document.getElementById(
"loading"
).style.display="none";
document.getElementById(
"lobby"
).style.display="block";
return;
}
requestAnimationFrame(
loadingLoop
);
}
loadingLoop();
/* =====================================================
ENTRAR NO GAME
===================================================== */
document.getElementById("playLobby").onclick=()=>{
document.getElementById(
"lobby"
).style.display="none";
document.getElementById(
"ui"
).classList.remove("hidden");
/*
GARANTE MODO NORMAL
*/
activeEvent=0;
updateUI();
message(
"🌊 MODO NORMAL!"
);
};
/* =====================================================
INICIAR
===================================================== */
draw();
updateUI();
</script>
</body>
</html>Game Source: Fisch 2D
Creator: MegaFlare51
Libraries: none
Complexity: complex (2346 lines, 28.9 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: fisch-2d-megaflare51-mtiyv2w8" to link back to the original. Then publish at arcadelab.ai/publish.