Интеграция: ветка sprint3-visual (полировка воды/неба/анимаций/UI, свежие скриншоты)

This commit is contained in:
2026-09-01 19:19:34 +03:00
12 changed files with 302 additions and 63 deletions
+302 -63
View File
@@ -16,56 +16,86 @@ button{font-family:inherit;cursor:pointer}
/* ---------- HUD ---------- */
#hud{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;
padding:calc(8px + env(safe-area-inset-top)) 10px 8px;z-index:10;pointer-events:none}
#coins{background:rgba(6,25,45,.55);padding:8px 14px;border-radius:999px;color:#ffe9a8;font-weight:700;
font-size:17px;border:1px solid rgba(255,255,255,.15)}
#coins{background:linear-gradient(rgba(9,32,58,.72),rgba(6,25,45,.62));padding:8px 15px;border-radius:999px;color:#ffe9a8;font-weight:700;
font-size:17px;border:1px solid rgba(255,233,168,.22);
box-shadow:0 3px 12px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.12);text-shadow:0 1px 2px rgba(0,0,0,.4)}
.hudbtns{display:flex;gap:8px;pointer-events:auto}
.hudbtns button{width:46px;height:46px;font-size:21px;border-radius:14px;
border:1px solid rgba(255,255,255,.15);background:rgba(6,25,45,.55);color:#fff}
.hudbtns button:active{background:rgba(30,60,95,.8)}
border:1px solid rgba(255,255,255,.16);background:linear-gradient(rgba(9,32,58,.72),rgba(6,25,45,.62));color:#fff;
box-shadow:0 3px 10px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.1);
transition:background .15s,transform .12s,box-shadow .12s}
.hudbtns button:hover{background:rgba(30,60,95,.8);transform:translateY(-1px)}
.hudbtns button:active{background:rgba(30,60,95,.85);transform:translateY(1px);box-shadow:0 1px 4px rgba(0,0,0,.3)}
#btnRecall{position:fixed;right:12px;bottom:calc(16px + env(safe-area-inset-bottom));width:54px;height:54px;
border-radius:50%;font-size:24px;background:rgba(6,25,45,.65);color:#fff;
border:1px solid rgba(255,255,255,.2);z-index:10}
#btnRecall:active{background:rgba(30,60,95,.85)}
border-radius:50%;font-size:24px;background:linear-gradient(rgba(9,32,58,.8),rgba(6,25,45,.7));color:#fff;
border:1px solid rgba(255,255,255,.22);z-index:10;
box-shadow:0 4px 14px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.12);
transition:background .15s,transform .12s}
#btnRecall:hover{background:rgba(30,60,95,.85);transform:scale(1.05)}
#btnRecall:active{background:rgba(30,60,95,.9);transform:scale(.96)}
#toast{position:fixed;left:50%;bottom:calc(92px + env(safe-area-inset-bottom));transform:translateX(-50%);
background:rgba(6,25,45,.9);color:#fff;padding:10px 18px;border-radius:999px;font-size:15px;font-weight:600;
background:linear-gradient(rgba(9,32,58,.94),rgba(5,20,38,.92));color:#fff;padding:11px 20px;border-radius:999px;font-size:15px;font-weight:600;
z-index:30;opacity:0;transition:opacity .25s;pointer-events:none;white-space:nowrap;
border:1px solid rgba(255,255,255,.15)}
border:1px solid rgba(255,255,255,.18);
box-shadow:0 6px 20px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.1);
max-width:92vw;overflow:hidden;text-overflow:ellipsis}
/* ---------- Overlays ---------- */
.overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
background:rgba(6,20,38,.6);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);z-index:20;padding:16px}
.panel{position:relative;background:linear-gradient(#14406b,#0d2f4e);border:2px solid #2e6da3;border-radius:20px;
background:rgba(4,16,32,.62);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:20;padding:16px}
.panel{position:relative;background:linear-gradient(165deg,#1a4d7d 0%,#123a61 55%,#0d2f4e 100%);
border:1px solid rgba(120,180,235,.35);border-radius:22px;
padding:22px;width:min(92vw,370px);max-height:86vh;overflow-y:auto;color:#eaf4ff;text-align:center;
box-shadow:0 10px 40px rgba(0,0,0,.5)}
.title{font-size:34px;font-weight:800;letter-spacing:1px;margin-bottom:10px}
box-shadow:0 24px 60px rgba(0,0,0,.55),0 4px 14px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.14)}
.panel::-webkit-scrollbar{width:8px}
.panel::-webkit-scrollbar-thumb{background:rgba(255,255,255,.18);border-radius:99px}
.panel::-webkit-scrollbar-track{background:transparent}
.title{font-size:34px;font-weight:800;letter-spacing:1px;margin-bottom:10px;
text-shadow:0 2px 10px rgba(0,0,0,.4),0 1px 0 rgba(255,255,255,.15)}
.panel p{font-size:14.5px;line-height:1.5;color:#bcd6ee;margin-bottom:4px}
.big{width:100%;margin-top:16px;padding:15px;font-size:19px;font-weight:800;color:#04263d;
background:linear-gradient(#ffd76e,#f5a623);border:none;border-radius:14px;box-shadow:0 4px 0 #b97708}
.big:active{transform:translateY(2px);box-shadow:0 2px 0 #b97708}
.panelhead{font-size:22px;font-weight:800;margin-bottom:4px}
background:linear-gradient(#ffe08a,#f5a623);border:none;border-radius:14px;
box-shadow:0 4px 0 #b97708,0 8px 18px rgba(245,166,35,.35);
transition:transform .12s,box-shadow .12s,filter .12s}
.big:hover{filter:brightness(1.07)}
.big:active{transform:translateY(2px);box-shadow:0 2px 0 #b97708,0 4px 10px rgba(245,166,35,.3)}
.panelhead{font-size:22px;font-weight:800;margin-bottom:4px;text-shadow:0 1px 6px rgba(0,0,0,.35)}
#btnCloseShop{position:absolute;top:12px;right:12px;width:36px;height:36px;border-radius:50%;border:none;
background:rgba(255,255,255,.12);color:#fff;font-size:17px}
background:rgba(255,255,255,.12);color:#fff;font-size:17px;transition:background .15s,transform .12s}
#btnCloseShop:hover{background:rgba(255,255,255,.22);transform:rotate(90deg)}
#btnCloseShop:active{transform:rotate(90deg) scale(.92)}
.tabs{display:flex;gap:8px;margin:10px 0 6px}
.tabs button{flex:1;padding:11px;border-radius:12px;border:none;background:rgba(255,255,255,.1);
color:#cfe4f7;font-weight:700;font-size:15px}
.tabs button.active{background:#f5a623;color:#04263d}
color:#cfe4f7;font-weight:700;font-size:15px;box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
transition:background .15s,color .15s,transform .12s,box-shadow .15s}
.tabs button:hover{background:rgba(255,255,255,.17)}
.tabs button:active{transform:scale(.96)}
.tabs button.active{background:linear-gradient(#ffd76e,#f5a623);color:#04263d;
box-shadow:0 3px 12px rgba(245,166,35,.4),inset 0 1px 0 rgba(255,255,255,.35)}
.section-title{font-size:12px;letter-spacing:1.5px;color:#8fb8dd;text-transform:uppercase;margin:14px 0 8px;text-align:left}
.item{display:flex;align-items:center;justify-content:space-between;gap:10px;background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:10px 12px;margin-bottom:8px;text-align:left}
border:1px solid rgba(255,255,255,.09);border-radius:12px;padding:10px 12px;margin-bottom:8px;text-align:left;
transition:background .15s,border-color .15s,transform .12s}
.item:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.2);transform:translateY(-1px)}
.itemname{font-weight:700;font-size:15px}
.itemdesc{font-size:12px;color:#9fc3e2;margin-top:2px;line-height:1.35}
.buybtn{min-width:96px;padding:10px 10px;border-radius:10px;border:none;font-weight:700;font-size:13px;
background:#2e6da3;color:#fff;white-space:nowrap;flex-shrink:0}
background:#2e6da3;color:#fff;white-space:nowrap;flex-shrink:0;
box-shadow:0 2px 6px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.15);transition:filter .12s,transform .12s,background .15s}
.buybtn:hover{filter:brightness(1.15)}
.buybtn:active{transform:scale(.96)}
.buybtn.owned{background:rgba(255,255,255,.14)}
.buybtn.current{background:#2ecc71;color:#04263d}
.buybtn:disabled{opacity:.45}
.buybtn.owned:hover{filter:none;background:rgba(255,255,255,.22)}
.buybtn.current{background:#2ecc71;color:#04263d;box-shadow:0 2px 8px rgba(46,204,113,.4)}
.buybtn:disabled{opacity:.45;cursor:default}
.statsline{font-size:13px;color:#9fc3e2;margin-bottom:6px}
.jrow{display:flex;align-items:center;gap:10px;padding:9px 10px;background:rgba(255,255,255,.05);
border-radius:10px;margin-bottom:6px;font-size:14px;text-align:left}
border:1px solid rgba(255,255,255,.06);border-radius:10px;margin-bottom:6px;font-size:14px;text-align:left;
transition:background .15s,border-color .15s}
.jrow:hover{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.14)}
.jrow .je{font-size:22px;width:30px;text-align:center}
.jrow .jn{flex:1}
.jrow .jc{color:#9fc3e2;font-weight:600;font-size:13px}
@@ -78,17 +108,20 @@ button{font-family:inherit;cursor:pointer}
.jrow.qdone .jc{color:#2ecc71;font-weight:700}
.streak{display:flex;gap:6px;justify-content:center;margin:8px 0 4px}
.st-dot{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;
background:rgba(255,255,255,.08);color:#5d7f9e;border:2px solid rgba(255,255,255,.12)}
background:rgba(255,255,255,.08);color:#5d7f9e;border:2px solid rgba(255,255,255,.12);
transition:transform .15s,box-shadow .15s}
.st-dot.on{background:linear-gradient(#ffd76e,#f5a623);color:#04263d;border-color:#ffd75e}
.st-dot.cur{box-shadow:0 0 12px rgba(255,215,94,.85);transform:scale(1.12)}
#card .bigemoji{font-size:74px;margin:4px 0 2px;display:block;animation:bob 1.2s ease-in-out infinite}
#card .bigemoji{font-size:74px;margin:4px 0 2px;display:block;animation:bob 1.2s ease-in-out infinite;
filter:drop-shadow(0 6px 10px rgba(0,0,0,.4))}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
#card .rarity{font-weight:800;font-size:13px;letter-spacing:1.5px;text-transform:uppercase}
#card .cname{font-size:24px;font-weight:800;margin:2px 0 6px}
#card .cval{font-size:20px;font-weight:800;color:#ffe9a8;margin-bottom:6px}
#card .rarity{display:inline-block;font-weight:800;font-size:12px;letter-spacing:1.5px;text-transform:uppercase;
padding:5px 14px;border-radius:999px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15)}
#card .cname{font-size:24px;font-weight:800;margin:6px 0 6px;text-shadow:0 1px 6px rgba(0,0,0,.4)}
#card .cval{font-size:20px;font-weight:800;color:#ffe9a8;margin-bottom:6px;text-shadow:0 1px 4px rgba(0,0,0,.3)}
#card .csub{font-size:13px;color:#9fc3e2;margin-bottom:4px}
.record{display:inline-block;background:linear-gradient(#ffd76e,#f5a623);color:#04263d;font-weight:800;font-size:14px;letter-spacing:1px;padding:4px 14px;border-radius:999px;animation:recpulse .8s ease-in-out infinite}
.record{display:inline-block;background:linear-gradient(#ffd76e,#f5a623);color:#04263d;font-weight:800;font-size:14px;letter-spacing:1px;padding:4px 14px;border-radius:999px;animation:recpulse .8s ease-in-out infinite;box-shadow:0 0 14px rgba(255,215,94,.5)}
@keyframes recpulse{0%,100%{transform:scale(1)}50%{transform:scale(1.09)}}
.shiny-badge{display:inline-block;background:linear-gradient(#fff6c9,#ffd75e);color:#6b4a00;font-weight:800;font-size:14px;letter-spacing:1px;padding:4px 14px;border-radius:999px;margin-left:8px;
border:1px solid #ffd75e;box-shadow:0 0 14px rgba(255,215,94,.75);animation:recpulse .8s ease-in-out infinite}
@@ -103,8 +136,32 @@ button{font-family:inherit;cursor:pointer}
.twobtns{display:flex;gap:10px;margin-top:16px}
.twobtns .big{margin-top:0;width:auto;flex:1}
.big.danger{background:linear-gradient(#ff8a7a,#e74c3c);box-shadow:0 4px 0 #96281b;color:#fff}
.big.danger:active{box-shadow:0 2px 0 #96281b}
.big.danger{background:linear-gradient(#ff8a7a,#e74c3c);box-shadow:0 4px 0 #96281b,0 8px 18px rgba(231,76,60,.35);color:#fff}
.big.danger:hover{filter:brightness(1.08)}
.big.danger:active{box-shadow:0 2px 0 #96281b,0 4px 10px rgba(231,76,60,.3)}
/* ---------- Адаптив: узкие и низкие экраны ---------- */
@media (max-width:420px){
.title{font-size:27px}
.panel{padding:16px;border-radius:18px}
.panelhead{font-size:19px}
.panel p{font-size:13.5px}
.big{padding:13px;font-size:17px;border-radius:12px}
#card .bigemoji{font-size:58px}
#card .cname{font-size:21px}
.hudbtns button{width:42px;height:42px;font-size:19px;border-radius:12px}
#coins{font-size:15px;padding:7px 12px}
.tabs button{padding:10px 6px;font-size:13.5px}
.jrow .je{font-size:20px;width:26px}
}
@media (max-height:620px){
.panel{max-height:92vh;padding:14px 16px}
.title{font-size:24px;margin-bottom:6px}
.panel p{font-size:13px;line-height:1.4}
#card .bigemoji{font-size:50px;margin:2px 0 0}
.big{margin-top:10px;padding:11px;font-size:16px}
.tabs button{padding:8px}
}
</style>
</head>
<body>
@@ -556,6 +613,7 @@ let stateBoss=false, bossPhase=0; // M4: босс-бой (Осётр-босс, 3
let ambient=[]; // фоновые рыбы
let clouds=[], sparkles=[];
let particles=[], ripples=[];
let bobberTrail=[]; // визуал: след поплавка при забросе
const rod=()=>RODS[save.rod];
const lure=()=>LURES[save.lure];
@@ -581,6 +639,15 @@ function initSparkles(){
for(let i=0;i<24;i++){
sparkles.push({x:Math.random()*W,y:waterY+rand(10,H-waterY-10),ph:rand(0,6.28)});
}
initStars();
}
/* Визуал: звёзды для ночного неба (только отрисовка, на механику не влияют) */
let stars=[];
function initStars(){
stars=[];
for(let i=0;i<46;i++){
stars.push({x:Math.random()*W,y:Math.random()*Math.max(10,waterY-14),r:rand(0.6,1.7),ph:rand(0,6.28),sp:rand(0.8,2.2)});
}
}
/* =========================================================
@@ -911,30 +978,100 @@ const SKY_PHASES={ // M3: градиенты неба по фазе суток
evening:['#e44d26','#f16529'],
night:['#0c1445','#1a237e']
};
/* Визуал: плавные переходы неба/освещения — зона перехода в КОНЦЕ фазы
(ночь темнеет к концу вечера, рассвет — в конце ночи; на стыке t=0 чистое утро) */
const SKY_SEQ=['morning','day','evening','night'];
const SKY_BOUNDS=[0.25,0.5,0.75,1.0]; // переход к следующей фазе завершается на границе
const SKY_ZONE=0.08; // ширина переходной зоны
function hexLerp(a,b,k){
const pa=parseInt(a.slice(1),16),pb=parseInt(b.slice(1),16);
const r=Math.round(((pa>>16)&255)+(((pb>>16)&255)-((pa>>16)&255))*k);
const g=Math.round(((pa>>8)&255)+(((pb>>8)&255)-((pa>>8)&255))*k);
const bl=Math.round((pa&255)+((pb&255)-(pa&255))*k);
return 'rgb('+r+','+g+','+bl+')';
}
function skyColors(){
const t=dayTime;
for(let i=0;i<4;i++){
const b=SKY_BOUNDS[i];
if(t<b||i===3){
const a=SKY_PHASES[SKY_SEQ[i]], c=SKY_PHASES[SKY_SEQ[(i+1)%4]];
const z0=b-SKY_ZONE;
if(t>=z0){ const k=Math.max(0,Math.min(1,(t-z0)/SKY_ZONE)); return [hexLerp(a[0],c[0],k),hexLerp(a[1],c[1],k)]; }
return [a[0],a[1]];
}
}
return [SKY_PHASES.morning[0],SKY_PHASES.morning[1]];
}
/* Числовые величины (ночная доля и т.п.) с теми же переходами */
function phaseLerp(vals){
const t=dayTime;
for(let i=0;i<4;i++){
const b=SKY_BOUNDS[i];
if(t<b||i===3){
const z0=b-SKY_ZONE;
if(t>=z0){ const k=Math.max(0,Math.min(1,(t-z0)/SKY_ZONE)); return vals[i]+(vals[(i+1)%4]-vals[i])*k; }
return vals[i];
}
}
return vals[0];
}
/* Ночная доля освещения 0..1 (1 = глубокая ночь) — вода, звёзды, фонарь */
function nightMix(){ return phaseLerp([0,0,0.4,1]); }
let luminary={x:0,y:0,a:0,night:false}; // визуал: где солнце/луна и её яркость (для дорожки на воде)
function drawSky(){
const cols=SKY_PHASES[dayPhase];
const cols=skyColors();
const nm=nightMix();
const g=ctx.createLinearGradient(0,0,0,waterY);
g.addColorStop(0,cols[0]); g.addColorStop(1,cols[1]);
ctx.fillStyle=g; ctx.fillRect(0,0,W,waterY);
// M3: солнце/луна по дуге
// звёзды — проступают с ночью, мерцают
if(nm>0.15){
for(const st of stars){
const a=nm*Math.max(0,Math.sin(time*st.sp+st.ph))*0.9;
if(a>0.03){
ctx.fillStyle='rgba(255,255,255,'+a.toFixed(3)+')';
ctx.beginPath(); ctx.arc(st.x,st.y,st.r,0,7); ctx.fill();
}
}
}
// M3: солнце/луна по дуге (визуал: мягкое свечение, гаснут/всходят в сумерках без наложения)
const ang=dayTime*2*Math.PI;
const bx=W/2+Math.cos(ang)*W*0.4, by=H*0.15-Math.sin(ang)*H*0.1;
if(dayPhase!=='night'){
ctx.fillStyle='rgba(255,230,120,0.35)';
ctx.beginPath(); ctx.arc(bx,by,34,0,7); ctx.fill();
luminary.x=bx; luminary.y=by; luminary.a=0; luminary.night=false;
if(nm<0.45){
const a=1-nm/0.45; // солнце садится к ночи
luminary.a=a;
ctx.globalAlpha=Math.max(0,a);
const glow=ctx.createRadialGradient(bx,by,6,bx,by,64);
glow.addColorStop(0,'rgba(255,225,130,0.5)'); glow.addColorStop(1,'rgba(255,225,130,0)');
ctx.fillStyle=glow; ctx.fillRect(bx-64,by-64,128,128);
ctx.fillStyle='#ffd700';
ctx.beginPath(); ctx.arc(bx,by,20,0,7); ctx.fill();
}else{
// луна — полумесяц (тень вырезается цветом неба)
ctx.fillStyle='rgba(245,245,220,0.22)';
ctx.beginPath(); ctx.arc(bx,by,28,0,7); ctx.fill();
ctx.fillStyle='rgba(255,255,255,0.5)';
ctx.beginPath(); ctx.arc(bx-6,by-6,7,0,7); ctx.fill();
ctx.globalAlpha=1;
}else if(nm>0.55){
const a=(nm-0.55)/0.45; // луна всходит в глубине сумерек
luminary.a=a; luminary.night=true;
ctx.globalAlpha=Math.max(0,Math.min(1,a));
const glow=ctx.createRadialGradient(bx,by,8,bx,by,56);
glow.addColorStop(0,'rgba(220,230,255,0.32)'); glow.addColorStop(1,'rgba(220,230,255,0)');
ctx.fillStyle=glow; ctx.fillRect(bx-56,by-56,112,112);
ctx.fillStyle='#f5f5dc';
ctx.beginPath(); ctx.arc(bx,by,18,0,7); ctx.fill();
// кратеры
ctx.fillStyle='rgba(120,130,150,0.35)';
ctx.beginPath(); ctx.arc(bx+6,by+3,3.2,0,7); ctx.fill();
ctx.beginPath(); ctx.arc(bx-4,by+7,2.2,0,7); ctx.fill();
ctx.beginPath(); ctx.arc(bx-2,by-6,2.6,0,7); ctx.fill();
// серп — вырез цветом неба в точке луны
ctx.fillStyle=cols[0];
ctx.beginPath(); ctx.arc(bx+8,by-5,15,0,7); ctx.fill();
ctx.beginPath(); ctx.arc(bx+9,by-6,13.5,0,7); ctx.fill();
ctx.globalAlpha=1;
}
// облака (ночью приглушены)
ctx.fillStyle='rgba(255,255,255,'+(dayPhase==='night'?0.25:dayPhase==='evening'?0.6:0.9)+')';
ctx.fillStyle='rgba(255,255,255,'+(0.9-0.68*nm).toFixed(3)+')';
for(const c of clouds){
ctx.save(); ctx.translate(c.x,c.y); ctx.scale(c.s,c.s);
ctx.beginPath();
@@ -943,45 +1080,83 @@ function drawSky(){
}
}
function drawWater(){
const nm=nightMix();
// градиент воды: день ↔ ночь, плавно (визуал)
const top=hexLerp('#2f9bd8','#0d2246',nm), bot=hexLerp('#0a2f52','#041226',nm);
const g=ctx.createLinearGradient(0,waterY,0,H);
g.addColorStop(0,'#2f9bd8'); g.addColorStop(1,'#0a2f52');
g.addColorStop(0,top); g.addColorStop(0.55,hexLerp('#1f6fb0','#091c3a',nm)); g.addColorStop(1,bot);
ctx.fillStyle=g; ctx.fillRect(0,waterY,W,H-waterY);
// дорожка света от солнца/луны — мерцающие блики под светилом
if(luminary.a>0.05){
const depth=Math.min(1,Math.max(0,(luminary.y)/(waterY||1))); // светило низко — дорожка длиннее
const len=(H-waterY)*(0.18+0.3*depth);
const base=(luminary.night?0.12:0.2)*luminary.a;
ctx.fillStyle=luminary.night?'rgba(200,220,255,1)':'rgba(255,235,170,1)';
for(let y=waterY+6;y<waterY+len;y+=7){
const k=(y-waterY)/len;
const wob=Math.sin(time*2.2+y*0.55);
const a=Math.max(0,base*(1-k)*(0.45+0.55*wob));
if(a<0.02) continue;
ctx.globalAlpha=a;
const wdt=14+k*30+Math.sin(time*1.7+y)*4;
ctx.fillRect(luminary.x-wdt/2,y,wdt,2);
}
ctx.globalAlpha=1;
}
// блики
for(const s of sparkles){
const a=Math.max(0,Math.sin(time*1.5+s.ph))*0.25;
const a=Math.max(0,Math.sin(time*1.5+s.ph))*0.25*(1-nm*0.5);
if(a>0.02){ ctx.fillStyle='rgba(255,255,255,'+a.toFixed(3)+')'; ctx.fillRect(s.x,s.y,3,1.5); }
}
// волна на поверхности
ctx.strokeStyle='rgba(255,255,255,0.55)'; ctx.lineWidth=2;
// волна на поверхности + вторая, глубинная (визуал)
ctx.strokeStyle='rgba(255,255,255,'+(0.55-nm*0.25).toFixed(3)+')'; ctx.lineWidth=2;
ctx.beginPath();
for(let x=0;x<=W;x+=8){
const y=waterY+Math.sin(x*0.05+time*2)*2.5;
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
}
ctx.stroke();
// M3: тёмный оверлей ночью (opacity 0→0.3, зависит от фазы)
const nd={morning:0.05,day:0,evening:0.16,night:0.3}[dayPhase];
if(nd>0){ ctx.fillStyle='rgba(4,10,38,'+nd+')'; ctx.fillRect(0,waterY,W,H-waterY); }
ctx.strokeStyle='rgba(255,255,255,'+(0.2-nm*0.1).toFixed(3)+')'; ctx.lineWidth=1.5;
ctx.beginPath();
for(let x=0;x<=W;x+=10){
const y=waterY+7+Math.sin(x*0.04-time*1.4+1.7)*3;
if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
}
function drawFishShape(x,y,size,dir,hue,alpha){
ctx.stroke();
}
function drawFishShape(x,y,size,dir,hue,alpha,wob){
ctx.save(); ctx.translate(x,y); ctx.scale(dir,1);
const w=wob||0;
ctx.fillStyle='hsla('+hue+',55%,38%,'+alpha+')';
ctx.beginPath(); ctx.ellipse(0,0,size,size*0.45,0,0,7); ctx.fill();
// хвост качается (визуал)
ctx.save(); ctx.translate(-size*0.8,0); ctx.rotate(w);
ctx.beginPath();
ctx.moveTo(-size*0.8,0); ctx.lineTo(-size*1.45,-size*0.42); ctx.lineTo(-size*1.45,size*0.42);
ctx.moveTo(0,0); ctx.lineTo(-size*0.65,-size*0.42); ctx.lineTo(-size*0.65,size*0.42);
ctx.closePath(); ctx.fill();
ctx.restore();
// блик на спинке + глаз (визуал: живость, у силуэтов едва видны)
ctx.fillStyle='rgba(255,255,255,'+(alpha*0.22).toFixed(3)+')';
ctx.beginPath(); ctx.ellipse(-size*0.1,-size*0.16,size*0.55,size*0.18,-0.25,0,7); ctx.fill();
ctx.fillStyle='rgba(10,25,40,'+(alpha*0.8).toFixed(3)+')';
ctx.beginPath(); ctx.arc(size*0.6,-size*0.07,size*0.09,0,7); ctx.fill();
ctx.restore();
}
function drawAmbientFish(){
for(const f of ambient){
const y=waterY+20+f.depth*(H-waterY-50)+Math.sin(time*2+f.ph)*2;
drawFishShape(f.x,y,f.size,f.dir,f.hue,0.22+0.2*(1-f.depth));
drawFishShape(f.x,y,f.size,f.dir,f.hue,0.22+0.2*(1-f.depth),Math.sin(time*5+f.ph)*0.28);
}
}
function drawBoat(){
const bx=boatX, by=waterY+Math.sin(time*1.5)*1.5;
// блик-отражение под лодкой (визуал)
ctx.fillStyle='rgba(0,20,40,0.28)';
ctx.beginPath(); ctx.ellipse(bx,by+17,34,4.5,0,0,7); ctx.fill();
// лодка
ctx.fillStyle='#b0622d';
const bg=ctx.createLinearGradient(bx-32,by,bx+32,by+15);
bg.addColorStop(0,'#c9773c'); bg.addColorStop(1,'#96521f');
ctx.fillStyle=bg;
ctx.beginPath();
ctx.moveTo(bx-32,by); ctx.lineTo(bx+32,by); ctx.lineTo(bx+21,by+15); ctx.lineTo(bx-21,by+15);
ctx.closePath(); ctx.fill();
@@ -1004,7 +1179,19 @@ function drawBoat(){
ctx.strokeStyle='#5d4023'; ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(bx-24,by); ctx.lineTo(bx-24,by-22); ctx.stroke();
ctx.fillStyle='#f5a623';
ctx.beginPath(); ctx.moveTo(bx-24,by-22); ctx.lineTo(bx-12,by-18); ctx.lineTo(bx-24,by-14); ctx.closePath(); ctx.fill();
const fl=Math.sin(time*4)*1.5; // флажок колышется (визуал)
ctx.beginPath(); ctx.moveTo(bx-24,by-22); ctx.lineTo(bx-12,by-18+fl); ctx.lineTo(bx-24,by-14); ctx.closePath(); ctx.fill();
// фонарь на носу ночью (визуал: тёплое пятно света)
if(nightMix()>0.35){
const a=(nightMix()-0.35)/0.65;
const lx=bx-24,ly=by-20;
const lg=ctx.createRadialGradient(lx,ly,2,lx,ly,46);
lg.addColorStop(0,'rgba(255,214,120,'+(0.4*a).toFixed(3)+')');
lg.addColorStop(1,'rgba(255,214,120,0)');
ctx.fillStyle=lg; ctx.fillRect(lx-46,ly-46,92,92);
ctx.fillStyle='rgba(255,224,150,'+(0.9*a).toFixed(3)+')';
ctx.beginPath(); ctx.arc(lx,ly,2.5,0,7); ctx.fill();
}
}
function drawLineTo(x,y,sag){
ctx.strokeStyle='rgba(230,240,255,0.8)'; ctx.lineWidth=1.2;
@@ -1014,14 +1201,23 @@ function drawLineTo(x,y,sag){
}
function drawBobber(){
const x=bobber.x,y=bobber.y;
// тёмная кромка воды под поплавком (визуал)
ctx.fillStyle='rgba(0,20,40,0.25)';
ctx.beginPath(); ctx.ellipse(x,y+6,7,1.8,0,0,7); ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath(); ctx.arc(x,y,5.5,0,7); ctx.fill();
ctx.fillStyle='#e74c3c';
ctx.beginPath(); ctx.arc(x,y,5.5,Math.PI,0); ctx.fill();
// блик (визуал)
ctx.fillStyle='rgba(255,255,255,0.85)';
ctx.beginPath(); ctx.arc(x-1.8,y-1.8,1.4,0,7); ctx.fill();
ctx.strokeStyle='rgba(255,255,255,0.9)'; ctx.lineWidth=1.5;
ctx.beginPath(); ctx.arc(x,y,5.5,0,7); ctx.stroke();
}
function drawLineAndBobber(){
// след поплавка: копим в забросе, чистим в остальных состояниях (визуал)
if(state==='CAST'){ bobberTrail.push({x:bobber.x,y:bobber.y}); if(bobberTrail.length>14) bobberTrail.shift(); }
else if(bobberTrail.length) bobberTrail.length=0;
if(state==='IDLE'||state==='CHARGE'||state==='MENU'||state==='CARD'){
// болтается грузило
const a=state==='CHARGE'?Math.sin(time*10)*power*4:Math.sin(time*1.2)*1.5;
@@ -1036,12 +1232,22 @@ function drawLineAndBobber(){
ctx.beginPath(); ctx.moveTo(tip.x,tip.y);
ctx.lineTo(tip.x+10+Math.sin(time*20)*6,tip.y+26); ctx.stroke();
ctx.globalAlpha=Math.min(1,snapT*2);
ctx.shadowColor='rgba(255,120,60,0.9)'; ctx.shadowBlur=14; // вспышка (визуал)
ctx.font='34px system-ui'; ctx.textAlign='center';
ctx.fillText('💥',castTo.x,waterY-20);
ctx.shadowBlur=0;
ctx.globalAlpha=1;
return;
}
if(state==='CAST'){ drawLineTo(bobber.x,bobber.y,14); drawBobber(); return; }
if(state==='CAST'){
// след: затухающие точки по траектории (визуал)
for(let i=0;i<bobberTrail.length;i++){
const tp=bobberTrail[i];
ctx.fillStyle='rgba(255,255,255,'+(i/bobberTrail.length*0.35).toFixed(3)+')';
ctx.beginPath(); ctx.arc(tp.x,tp.y,1.6,0,7); ctx.fill();
}
drawLineTo(bobber.x,bobber.y,14); drawBobber(); return;
}
const sag=state==='REEL'?(reel.phase==='pull'?4:12):18+Math.sin(time*2)*3;
drawLineTo(bobber.x,bobber.y,sag);
drawBobber();
@@ -1061,7 +1267,12 @@ function drawHookedFish(){
const y=waterY+26+Math.sin(time*5)*3;
const dir=Math.sin(time*2.5)>0?1:-1;
const fs=8+(reel.w/sp.wMax)*10+sp.rarity;
drawFishShape(x,y,reel.boss?fs*1.5:fs,dir,sp.hue,0.75); // силуэт крупнее у тяжёлых, босс ×1.5
if(reel.boss){ // босс: золотая аура (визуал)
const gg=ctx.createRadialGradient(x,y,4,x,y,fs*2.6);
gg.addColorStop(0,'rgba(255,215,94,0.35)'); gg.addColorStop(1,'rgba(255,215,94,0)');
ctx.fillStyle=gg; ctx.fillRect(x-fs*2.6,y-fs*2.6,fs*5.2,fs*5.2);
}
drawFishShape(x,y,reel.boss?fs*1.5:fs,dir,sp.hue,0.75,Math.sin(time*9)*0.3); // силуэт крупнее у тяжёлых, босс ×1.5
}
function drawParticles(){
for(const p of particles){
@@ -1080,11 +1291,21 @@ function drawMeters(){
if(state!=='REEL'||!reel) return;
const r=rod(), sp=reel.sp;
const pulling=reel.phase==='pull';
// M4: босс-бой — золотая полоса фазы над метрами
// M4: босс-бой — золотая полоса фазы над метрами (визуал: пульс + точки фаз)
if(reel.boss){
ctx.font='800 16px system-ui'; ctx.textAlign='center';
const pulse=1+Math.sin(time*5)*0.08;
ctx.textAlign='center';
ctx.font='800 '+(16*pulse).toFixed(1)+'px system-ui';
ctx.fillStyle='#ffd75e';
ctx.shadowColor='rgba(255,180,40,0.8)'; ctx.shadowBlur=10;
ctx.fillText('⚠️ БОСС-РЫБА · ФАЗА '+bossPhase+'/3',W/2,84);
ctx.shadowBlur=0;
for(let i=1;i<=3;i++){
ctx.beginPath();
ctx.arc(W/2-20+i*20,94,4,0,7);
ctx.fillStyle=i<=bossPhase?'#ffd75e':'rgba(255,255,255,0.25)';
ctx.fill();
}
}
// --- нагрузка (вертикально справа) ---
const bw=14, bh=H*0.36, bx=W-30, by=H*0.46-bh/2;
@@ -1093,8 +1314,10 @@ function drawMeters(){
const col=ratio<0.5?'#2ecc71':ratio<0.8?'#f1c40f':'#e74c3c';
if(ratio>0.01){
ctx.fillStyle=col;
if(ratio>0.8){ ctx.shadowColor='rgba(231,76,60,0.85)'; ctx.shadowBlur=12; } // перегруз: тревожное свечение
const fh=bh*ratio;
rr(bx+2,by+bh-2-fh,bw-4,Math.max(2,fh-2),5); ctx.fill();
ctx.shadowBlur=0;
}
ctx.strokeStyle='rgba(255,255,255,0.6)'; ctx.lineWidth=1.5;
ctx.beginPath(); ctx.moveTo(bx-3,by); ctx.lineTo(bx+bw+3,by); ctx.stroke();
@@ -1106,7 +1329,9 @@ function drawMeters(){
// rem = доля оставшейся дистанции: полоска «сгорает» к лодке, рыбка на её конце
const rem=Math.max(0,Math.min(1,reel.d/reel.d0));
if(rem>0.01){
ctx.fillStyle='#3fa7e8';
const dg=ctx.createLinearGradient(dx,0,dx+dw,0);
dg.addColorStop(0,'#2e86c8'); dg.addColorStop(1,'#3fa7e8');
ctx.fillStyle=dg;
rr(dx+2,dy+2,Math.max(2,(dw-4)*rem),dh-4,5); ctx.fill();
}
// рыбка-маркер на конце полоски
@@ -1119,10 +1344,14 @@ function drawMeters(){
ctx.font='700 15px system-ui'; ctx.textAlign='center';
if(pulling){
ctx.fillStyle='#ff6b5e';
ctx.shadowColor='rgba(255,60,40,'+(0.5+0.4*Math.sin(time*10)).toFixed(3)+')'; ctx.shadowBlur=8;
ctx.fillText('⚡ РЫБА ТЯНЕТ — ОТПУСТИ! ⚡',W/2,dy-30);
ctx.shadowBlur=0;
}else{
ctx.fillStyle='#7ef0a8';
ctx.shadowColor='rgba(0,0,0,0.6)'; ctx.shadowBlur=4;
ctx.fillText('Держи палец — втягивай!',W/2,dy-30);
ctx.shadowBlur=0;
}
}
function drawPowerBar(){
@@ -1132,10 +1361,18 @@ function drawPowerBar(){
const g=ctx.createLinearGradient(x,0,x+w,0);
g.addColorStop(0,'#2ecc71'); g.addColorStop(0.6,'#f1c40f'); g.addColorStop(1,'#e74c3c');
ctx.fillStyle=g;
if(power>0.01){ rr(x+2,y+2,Math.max(2,(w-4)*power),h-4,6); ctx.fill(); }
if(power>0.01){
const fw=Math.max(2,(w-4)*power);
rr(x+2,y+2,fw,h-4,6); ctx.fill();
// бегунок на конце заливки (визуал)
ctx.fillStyle='rgba(255,255,255,0.9)';
ctx.beginPath(); ctx.arc(x+2+fw-3,y+h/2,4,0,7); ctx.fill();
}
ctx.font='700 14px system-ui'; ctx.textAlign='center';
ctx.fillStyle='#fff';
ctx.shadowColor='rgba(0,0,0,0.65)'; ctx.shadowBlur=4;
ctx.fillText('Отпусти — бросок!',W/2,y-10);
ctx.shadowBlur=0;
}
function drawHint(){
if(state!=='IDLE'||save.casts>=5) return;
@@ -1143,7 +1380,9 @@ function drawHint(){
ctx.globalAlpha=a;
ctx.font='700 17px system-ui'; ctx.textAlign='center';
ctx.fillStyle='#fff';
ctx.shadowColor='rgba(0,0,0,0.65)'; ctx.shadowBlur=5;
ctx.fillText('👆 Держи палец — заряжай бросок',W/2,H-70);
ctx.shadowBlur=0;
ctx.globalAlpha=1;
}
function draw(){
@@ -1155,8 +1394,8 @@ function draw(){
drawSky();
drawWater();
drawAmbientFish();
if(biteFish&&biteFish.mode!=='out') drawFishShape(biteFish.x,biteFish.y,12+biteFish.sp.rarity*2.5,biteFish.dir,biteFish.sp.hue,0.8);
else if(biteFish&&biteFish.mode==='out') drawFishShape(biteFish.x,biteFish.y,12+biteFish.sp.rarity*2.5,biteFish.dir,biteFish.sp.hue,0.5);
if(biteFish&&biteFish.mode!=='out') drawFishShape(biteFish.x,biteFish.y,12+biteFish.sp.rarity*2.5,biteFish.dir,biteFish.sp.hue,0.8,Math.sin(time*8)*0.25);
else if(biteFish&&biteFish.mode==='out') drawFishShape(biteFish.x,biteFish.y,12+biteFish.sp.rarity*2.5,biteFish.dir,biteFish.sp.hue,0.5,Math.sin(time*8)*0.25);
drawBoat();
drawLineAndBobber();
drawHookedFish();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 133 KiB