Commit 2e3f0ecf authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: ludo — 10 major visual enhancements for premium board game feel

1. Rich board background with radial gradient texture (warm parchment)
2. Center triangles with radial gradient glow + white diamond center
3. 3D pawns with gradient bodies, specular highlights, drop shadows
4. Safe squares with radial glow + shadowed gold stars
5. Home columns with gradient cells + directional arrows
6. Home zones with linear gradient fill + corner dot decorations
7. Animated turn indicator arrow on active player panel + glow name
8. Ambient corner glow showing current player's color on board
9. Decorated dice area with gold accent, gradient background, radial glow
10. Embossed board frame with wood-tone border + inner gold accent

Also: roll button pulses when available, panels have shadow glow on active
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 58001eb9
...@@ -28,12 +28,13 @@ let isHost = false; ...@@ -28,12 +28,13 @@ let isHost = false;
function renderPanel(p) { function renderPanel(p) {
return ` return `
<div class="pp" id="pp-${p.i}" style="--pc:${p.color};"> <div class="pp" id="pp-${p.i}" style="--pc:${p.color};">
<div class="pp-avatar" style="width:28px;height:28px;border-radius:50%;background:#2a2a4a;border:2px solid ${p.color};display:flex;align-items:center;justify-content:center;font-size:14px;overflow:hidden;"> <div class="pp-turn-arrow">▶</div>
<div class="pp-avatar" style="width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,#2a2a4a,#1a1a3a);border:2.5px solid ${p.color};display:flex;align-items:center;justify-content:center;font-size:14px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.3);">
${p.avatar} ${p.avatar}
</div> </div>
<div style="display:flex;flex-direction:column;"> <div style="display:flex;flex-direction:column;">
<span style="font-size:11px;font-weight:600;color:#f8fafc;">${p.name}</span> <span class="pp-name" style="font-size:11px;font-weight:700;color:#f8fafc;">${p.name}</span>
${p.level ? `<span style="font-size:10px;color:#64748b;">${p.level}</span>` : ''} ${p.level ? `<span style="font-size:9px;color:#64748b;">${p.level}</span>` : ''}
</div> </div>
<div class="pp-dice" id="dice-${p.i}"></div> <div class="pp-dice" id="dice-${p.i}"></div>
</div> </div>
...@@ -79,39 +80,44 @@ export function mountGame(el, params) { ...@@ -79,39 +80,44 @@ export function mountGame(el, params) {
}); });
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;height:100%;background:#1a1a2e;justify-content:flex-end;"> <div style="display:flex;flex-direction:column;height:100%;background:linear-gradient(180deg,#0d0d1a 0%,#1a1a2e 50%,#0d0d1a 100%);justify-content:flex-end;">
<div style="display:flex;justify-content:space-between;padding:6px 12px;background:#0f0f1e;direction:ltr;"> <div style="display:flex;justify-content:space-between;padding:8px 12px;background:rgba(15,15,30,0.9);border-bottom:1px solid rgba(255,255,255,0.04);direction:ltr;">
${renderPanel(panels[1])} ${renderPanel(panels[1])}
${renderPanel(panels[2])} ${renderPanel(panels[2])}
</div> </div>
<div id="ludo-wrap" style="flex:1;display:flex;align-items:center;justify-content:center;padding:4px;min-height:0;"></div> <div id="ludo-wrap" style="flex:1;display:flex;align-items:center;justify-content:center;padding:6px;min-height:0;"></div>
<div style="display:flex;justify-content:space-between;padding:6px 12px;background:#0f0f1e;direction:ltr;"> <div style="display:flex;justify-content:space-between;padding:8px 12px;background:rgba(15,15,30,0.9);border-top:1px solid rgba(255,255,255,0.04);direction:ltr;">
${renderPanel(panels[0])} ${renderPanel(panels[0])}
${renderPanel(panels[3])} ${renderPanel(panels[3])}
</div> </div>
<div id="dice-area" style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);justify-content:center;padding-bottom:max(12px, env(safe-area-inset-bottom, 0px));"> <div id="dice-area" style="display:flex;align-items:center;gap:12px;padding:14px 16px;background:linear-gradient(180deg,#12122a,#0a0a1a);border-top:1px solid rgba(228,172,56,0.15);justify-content:center;padding-bottom:max(14px, env(safe-area-inset-bottom, 0px));position:relative;overflow:hidden;">
<button class="btn btn-secondary" id="exit-btn" style="min-height:48px;min-width:48px;padding:0;font-size:14px;color:#EF4444;border-radius:50%;">✕</button> <div style="position:absolute;inset:0;background:radial-gradient(ellipse at 50% 0%,rgba(228,172,56,0.06) 0%,transparent 70%);pointer-events:none;"></div>
<div id="dice-box" style="width:52px;height:52px;background:#f8fafc;border-radius:10px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:6px;box-shadow:0 3px 10px rgba(0,0,0,0.4),inset 0 1px 0 rgba(255,255,255,0.8);transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1);"> <button class="btn btn-secondary" id="exit-btn" style="min-height:44px;min-width:44px;padding:0;font-size:13px;color:#EF4444;border-radius:50%;background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.2);">✕</button>
<div id="dice-box" style="width:56px;height:56px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:12px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:7px;box-shadow:0 4px 12px rgba(0,0,0,0.4),inset 0 2px 0 rgba(255,255,255,0.9),0 0 0 2px rgba(228,172,56,0.15);transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1);">
</div> </div>
<button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:14px 32px;min-height:52px;border-radius:12px;" disabled>ارمِ النرد</button> <button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:14px 32px;min-height:52px;border-radius:14px;background:linear-gradient(135deg,#E4AC38,#F59E0B);box-shadow:0 4px 14px rgba(228,172,56,0.3);font-weight:800;" disabled>ارمِ النرد</button>
</div> </div>
</div> </div>
<style> <style>
.pp{display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:8px;border:2px solid transparent;transition:all 0.3s cubic-bezier(0.34,1.56,0.64,1);} .pp{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:10px;border:2px solid transparent;transition:all 0.3s cubic-bezier(0.34,1.56,0.64,1);position:relative;}
.pp.active{border-color:var(--pc);background:rgba(255,255,255,0.05);animation:panelPulse 2s ease-in-out infinite;} .pp.active{border-color:var(--pc);background:rgba(255,255,255,0.06);animation:panelPulse 2s ease-in-out infinite;box-shadow:0 0 12px color-mix(in srgb, var(--pc) 20%, transparent);}
.pp-dot{width:10px;height:10px;border-radius:50%;background:var(--pc);} .pp-turn-arrow{position:absolute;left:-2px;top:50%;transform:translateY(-50%) scale(0);font-size:10px;color:var(--pc);transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1);filter:drop-shadow(0 0 4px var(--pc));}
.pp span{font-size:12px;font-weight:600;color:#94a3b8;transition:color 0.3s;} .pp.active .pp-turn-arrow{transform:translateY(-50%) scale(1);animation:arrowBounce 1s ease-in-out infinite;}
.pp.active span{color:#f8fafc;} .pp-name{transition:color 0.3s,text-shadow 0.3s;}
.pp-dice{width:28px;height:28px;background:#f8fafc;border-radius:5px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:3px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transition:all 0.2s cubic-bezier(0.34,1.56,0.64,1);opacity:0;transform:scale(0);margin-left:auto;} .pp.active .pp-name{color:#fff !important;text-shadow:0 0 8px color-mix(in srgb, var(--pc) 50%, transparent);}
.pp-dice{width:28px;height:28px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:6px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:3px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transition:all 0.2s cubic-bezier(0.34,1.56,0.64,1);opacity:0;transform:scale(0);margin-left:auto;}
.pp-dice.visible{opacity:1;transform:scale(1);} .pp-dice.visible{opacity:1;transform:scale(1);}
.pp-dice.rolling{animation:diceShake 0.08s infinite alternate;opacity:1;transform:scale(1);} .pp-dice.rolling{animation:diceShake 0.08s infinite alternate;opacity:1;transform:scale(1);}
.pp-dice.landed{opacity:1;transform:scale(1.15);box-shadow:0 0 12px rgba(228,172,56,0.5);} .pp-dice.landed{opacity:1;transform:scale(1.15);box-shadow:0 0 12px rgba(228,172,56,0.5);}
.pp.active .pp-avatar{animation:avatarBounce 2s ease-in-out infinite;} .pp.active .pp-avatar{animation:avatarBounce 2s ease-in-out infinite;box-shadow:0 0 12px color-mix(in srgb, var(--pc) 40%, transparent);}
@keyframes panelPulse{0%,100%{box-shadow:0 0 0 0 transparent;}50%{box-shadow:0 0 12px 2px color-mix(in srgb, var(--pc) 30%, transparent);}} @keyframes panelPulse{0%,100%{box-shadow:0 0 8px 0 transparent;}50%{box-shadow:0 0 16px 3px color-mix(in srgb, var(--pc) 25%, transparent);}}
@keyframes avatarBounce{0%,100%{transform:scale(1);}50%{transform:scale(1.08);}} @keyframes avatarBounce{0%,100%{transform:scale(1);}50%{transform:scale(1.1);}}
@keyframes diceShake{from{transform:rotate(-12deg) scale(0.9);}to{transform:rotate(12deg) scale(0.9);}} @keyframes arrowBounce{0%,100%{transform:translateY(-50%) scale(1) translateX(0);}50%{transform:translateY(-50%) scale(1) translateX(3px);}}
@keyframes diceShake{from{transform:rotate(-15deg) scale(0.85);}to{transform:rotate(15deg) scale(0.85);}}
@keyframes dicePopIn{0%{transform:scale(0) rotate(-30deg);opacity:0;}60%{transform:scale(1.3) rotate(5deg);opacity:1;}100%{transform:scale(1) rotate(0deg);opacity:1;}} @keyframes dicePopIn{0%{transform:scale(0) rotate(-30deg);opacity:0;}60%{transform:scale(1.3) rotate(5deg);opacity:1;}100%{transform:scale(1) rotate(0deg);opacity:1;}}
@keyframes diceSix{0%,100%{box-shadow:0 0 8px rgba(228,172,56,0.4);}50%{box-shadow:0 0 18px rgba(228,172,56,0.8);}} @keyframes diceSix{0%,100%{box-shadow:0 0 8px rgba(228,172,56,0.4);}50%{box-shadow:0 0 20px rgba(228,172,56,0.9);}}
@keyframes rollBtnPulse{0%,100%{box-shadow:0 4px 14px rgba(228,172,56,0.3);}50%{box-shadow:0 4px 24px rgba(228,172,56,0.6);}}
#roll-btn:not([disabled]){animation:rollBtnPulse 2s ease-in-out infinite;}
</style> </style>
`; `;
...@@ -669,6 +675,14 @@ async function animateMove(el, move) { ...@@ -669,6 +675,14 @@ async function animateMove(el, move) {
afterMove(el, move); afterMove(el, move);
} }
function darkenColor(hex, amount) {
const num = parseInt(hex.replace('#', ''), 16);
const r = Math.max(0, ((num >> 16) & 0xFF) - amount);
const g = Math.max(0, ((num >> 8) & 0xFF) - amount);
const b = Math.max(0, (num & 0xFF) - amount);
return `rgb(${r},${g},${b})`;
}
function fireworkBurst(x, y, color) { function fireworkBurst(x, y, color) {
const lighten = (hex, pct) => { const lighten = (hex, pct) => {
const num = parseInt(hex.replace('#', ''), 16); const num = parseInt(hex.replace('#', ''), 16);
...@@ -744,35 +758,67 @@ function drawBoard() { ...@@ -744,35 +758,67 @@ function drawBoard() {
const cs = cellSize; const cs = cellSize;
clear(ctx, boardSize, boardSize); clear(ctx, boardSize, boardSize);
// Background // === ENHANCEMENT 1: Rich board background with subtle texture ===
ctx.fillStyle = '#FAFAFA'; const bgGrad = ctx.createRadialGradient(boardSize/2, boardSize/2, 0, boardSize/2, boardSize/2, boardSize*0.7);
bgGrad.addColorStop(0, '#FFFFF8');
bgGrad.addColorStop(1, '#F0EDE6');
ctx.fillStyle = bgGrad;
ctx.fillRect(0, 0, boardSize, boardSize); ctx.fillRect(0, 0, boardSize, boardSize);
// Home zones — with inner white area // === ENHANCEMENT 10: Board border frame with emboss ===
// [playerIdx, gridCol, gridRow]: Red(BL), Green(TL), Yellow(TR), Blue(BR) ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 6;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 2;
ctx.strokeStyle = '#8B7355';
ctx.lineWidth = 3;
ctx.strokeRect(1.5, 1.5, boardSize-3, boardSize-3);
ctx.restore();
// Inner gold border
ctx.strokeStyle = '#D4A843';
ctx.lineWidth = 1.5;
ctx.strokeRect(4, 4, boardSize-8, boardSize-8);
// === ENHANCEMENT 6: Home zones with gradient fill ===
[[0,0,9],[1,0,0],[2,9,0],[3,9,9]].forEach(([p,c,r]) => { [[0,0,9],[1,0,0],[2,9,0],[3,9,9]].forEach(([p,c,r]) => {
ctx.fillStyle = COLORS[p]; const hx = c*cs, hy = r*cs, hw = 6*cs;
ctx.fillRect(c*cs, r*cs, 6*cs, 6*cs); const homeGrad = ctx.createLinearGradient(hx, hy, hx+hw, hy+hw);
// Inner white area homeGrad.addColorStop(0, COLORS[p]);
ctx.fillStyle = '#FAFAFA'; homeGrad.addColorStop(1, COLORS_LIGHT[p]);
ctx.fillStyle = homeGrad;
ctx.fillRect(hx, hy, hw, hw);
// Inner white area with subtle shadow
const ins = cs*0.85; const ins = cs*0.85;
ctx.fillRect(c*cs+ins, r*cs+ins, 6*cs-ins*2, 6*cs-ins*2); ctx.save();
// Inner colored border ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = 4;
ctx.shadowInset = true;
ctx.fillStyle = '#FEFEFA';
ctx.fillRect(hx+ins, hy+ins, hw-ins*2, hw-ins*2);
ctx.restore();
// Decorative inner border with rounded feel
ctx.strokeStyle = COLORS[p]; ctx.strokeStyle = COLORS[p];
ctx.lineWidth = 2.5; ctx.lineWidth = 2.5;
ctx.strokeRect(c*cs+ins+3, r*cs+ins+3, 6*cs-ins*2-6, 6*cs-ins*2-6); ctx.strokeRect(hx+ins+2, hy+ins+2, hw-ins*2-4, hw-ins*2-4);
// Corner dots decoration
const dotR = cs * 0.15;
ctx.fillStyle = COLORS[p];
[[hx+ins+8,hy+ins+8],[hx+hw-ins-8,hy+ins+8],[hx+ins+8,hy+hw-ins-8],[hx+hw-ins-8,hy+hw-ins-8]].forEach(([dx,dy]) => {
ctx.beginPath(); ctx.arc(dx, dy, dotR, 0, Math.PI*2); ctx.fill();
});
}); });
// Cross paths (white) // Cross paths with subtle warmth
ctx.fillStyle = '#fff'; ctx.fillStyle = '#FDFCF9';
ctx.fillRect(6*cs,0,3*cs,6*cs); ctx.fillRect(6*cs,0,3*cs,6*cs);
ctx.fillRect(6*cs,9*cs,3*cs,6*cs); ctx.fillRect(6*cs,9*cs,3*cs,6*cs);
ctx.fillRect(0,6*cs,6*cs,3*cs); ctx.fillRect(0,6*cs,6*cs,3*cs);
ctx.fillRect(9*cs,6*cs,6*cs,3*cs); ctx.fillRect(9*cs,6*cs,6*cs,3*cs);
ctx.fillRect(6*cs,6*cs,3*cs,3*cs); ctx.fillRect(6*cs,6*cs,3*cs,3*cs);
// Grid lines // === ENHANCEMENT 5: Grid lines with depth ===
ctx.strokeStyle = '#ddd'; ctx.strokeStyle = 'rgba(0,0,0,0.08)';
ctx.lineWidth = 0.5; ctx.lineWidth = 0.5;
for (let i = 0; i <= 15; i++) { for (let i = 0; i <= 15; i++) {
if (i >= 6 && i <= 9) { ctx.beginPath(); ctx.moveTo(i*cs,0); ctx.lineTo(i*cs,boardSize); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,i*cs); ctx.lineTo(boardSize,i*cs); ctx.stroke(); } if (i >= 6 && i <= 9) { ctx.beginPath(); ctx.moveTo(i*cs,0); ctx.lineTo(i*cs,boardSize); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,i*cs); ctx.lineTo(boardSize,i*cs); ctx.stroke(); }
...@@ -784,33 +830,105 @@ function drawBoard() { ...@@ -784,33 +830,105 @@ function drawBoard() {
ctx.beginPath(); ctx.moveTo((i+9)*cs,6*cs); ctx.lineTo((i+9)*cs,9*cs); ctx.stroke(); ctx.beginPath(); ctx.moveTo((i+9)*cs,6*cs); ctx.lineTo((i+9)*cs,9*cs); ctx.stroke();
} }
// Home columns // === ENHANCEMENT 5: Home columns with gradient cells ===
for (let p = 0; p < 4; p++) HOME_COLUMNS[p].forEach(([col,row]) => { ctx.fillStyle = COLORS_LIGHT[p]; ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2); }); for (let p = 0; p < 4; p++) {
HOME_COLUMNS[p].forEach(([col,row], i) => {
// Safe squares const cellGrad = ctx.createLinearGradient(col*cs, row*cs, col*cs+cs, row*cs+cs);
SAFE_SQUARES.forEach(idx => { const [col,row] = SHARED_PATH[idx]; ctx.fillStyle = '#FFF9C4'; ctx.fillRect(col*cs+1,row*cs+1,cs-2,cs-2); ctx.fillStyle = '#F9A825'; ctx.font = `${cs*0.45}px sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('★', col*cs+cs/2, row*cs+cs/2); }); cellGrad.addColorStop(0, COLORS_LIGHT[p]);
cellGrad.addColorStop(1, COLORS[p] + '40');
// Center — 4 colored triangles forming a square (finish area) ctx.fillStyle = cellGrad;
const cx = 7.5*cs, cy = 7.5*cs, hs = 1.5*cs; ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2);
// Red triangle (bottom → points up) — player 0 // Arrow indicator toward center
ctx.fillStyle = COLORS[0]; ctx.beginPath(); ctx.fillStyle = COLORS[p] + '60';
ctx.moveTo(cx-hs, cy+hs); ctx.lineTo(cx+hs, cy+hs); ctx.lineTo(cx, cy); ctx.closePath(); ctx.fill(); ctx.font = `${cs*0.35}px sans-serif`;
// Green triangle (left → points right) — player 1 ctx.textAlign = 'center';
ctx.fillStyle = COLORS[1]; ctx.beginPath(); ctx.textBaseline = 'middle';
ctx.moveTo(cx-hs, cy-hs); ctx.lineTo(cx-hs, cy+hs); ctx.lineTo(cx, cy); ctx.closePath(); ctx.fill(); const arrows = ['→','↓','←','↑'];
// Yellow triangle (top → points down) — player 2 ctx.fillText(arrows[p], col*cs+cs/2, row*cs+cs/2);
ctx.fillStyle = COLORS[2]; ctx.beginPath(); });
ctx.moveTo(cx-hs, cy-hs); ctx.lineTo(cx+hs, cy-hs); ctx.lineTo(cx, cy); ctx.closePath(); ctx.fill(); }
// Blue triangle (right → points left) — player 3
ctx.fillStyle = COLORS[3]; ctx.beginPath(); // === ENHANCEMENT 4: Safe squares with glow effect ===
ctx.moveTo(cx+hs, cy-hs); ctx.lineTo(cx+hs, cy+hs); ctx.lineTo(cx, cy); ctx.closePath(); ctx.fill(); SAFE_SQUARES.forEach(idx => {
// Center border const [col,row] = SHARED_PATH[idx];
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; // Glow background
ctx.strokeRect(cx-hs, cy-hs, hs*2, hs*2); const safeGrad = ctx.createRadialGradient(col*cs+cs/2, row*cs+cs/2, 0, col*cs+cs/2, row*cs+cs/2, cs*0.6);
safeGrad.addColorStop(0, '#FFF9C4');
// Start position colored squares — where each player's piece enters the board safeGrad.addColorStop(1, '#FFF176');
ctx.fillStyle = safeGrad;
ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2);
// Star with shadow
ctx.save();
ctx.shadowColor = '#F9A825';
ctx.shadowBlur = 4;
ctx.fillStyle = '#F57F17';
ctx.font = `bold ${cs*0.5}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('★', col*cs+cs/2, row*cs+cs/2);
ctx.restore();
});
// === ENHANCEMENT 2: Center triangles with gradient glow ===
const ccx = 7.5*cs, ccy = 7.5*cs, hs = 1.5*cs;
// Draw each triangle with gradient
const triPoints = [
{ p: 0, pts: [[ccx-hs,ccy+hs],[ccx+hs,ccy+hs],[ccx,ccy]] },
{ p: 1, pts: [[ccx-hs,ccy-hs],[ccx-hs,ccy+hs],[ccx,ccy]] },
{ p: 2, pts: [[ccx-hs,ccy-hs],[ccx+hs,ccy-hs],[ccx,ccy]] },
{ p: 3, pts: [[ccx+hs,ccy-hs],[ccx+hs,ccy+hs],[ccx,ccy]] },
];
triPoints.forEach(({p, pts}) => {
const grad = ctx.createRadialGradient(ccx, ccy, 0, ccx, ccy, hs);
grad.addColorStop(0, '#fff');
grad.addColorStop(0.3, COLORS_LIGHT[p]);
grad.addColorStop(1, COLORS[p]);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pts[0][0], pts[0][1]);
ctx.lineTo(pts[1][0], pts[1][1]);
ctx.lineTo(pts[2][0], pts[2][1]);
ctx.closePath();
ctx.fill();
});
// Center diamond highlight
ctx.save();
ctx.shadowColor = 'rgba(255,255,255,0.8)';
ctx.shadowBlur = 6;
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.beginPath();
ctx.arc(ccx, ccy, cs*0.3, 0, Math.PI*2);
ctx.fill();
ctx.restore();
// Outer border of center
ctx.strokeStyle = 'rgba(255,255,255,0.9)';
ctx.lineWidth = 2;
ctx.strokeRect(ccx-hs, ccy-hs, hs*2, hs*2);
// Start position colored squares with glow
const startColors = [[6,13,'#FFCDD2'],[1,6,'#C8E6C9'],[8,1,'#FFF9C4'],[13,8,'#BBDEFB']]; const startColors = [[6,13,'#FFCDD2'],[1,6,'#C8E6C9'],[8,1,'#FFF9C4'],[13,8,'#BBDEFB']];
startColors.forEach(([col,row,color]) => { ctx.fillStyle = color; ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2); }); startColors.forEach(([col,row,color]) => {
ctx.fillStyle = color;
ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2);
// Arrow showing entry direction
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.font = `${cs*0.4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('▶', col*cs+cs/2, row*cs+cs/2);
});
// === ENHANCEMENT 8: Ambient corner glow for current player ===
if (game && !game.gameOver) {
const cp = game.currentPlayer;
const corners = [[0,boardSize],[0,0],[boardSize,0],[boardSize,boardSize]];
const [gx, gy] = corners[cp];
const ambGrad = ctx.createRadialGradient(gx, gy, 0, gx, gy, cs*5);
ambGrad.addColorStop(0, COLORS[cp] + '25');
ambGrad.addColorStop(1, 'transparent');
ctx.fillStyle = ambGrad;
ctx.fillRect(0, 0, boardSize, boardSize);
}
// Move preview — draw ghost circles at destination for highlighted pieces // Move preview — draw ghost circles at destination for highlighted pieces
if (highlightedPieces.length > 0 && validMoves && validMoves.length > 0) { if (highlightedPieces.length > 0 && validMoves && validMoves.length > 0) {
...@@ -881,19 +999,52 @@ function drawBoard() { ...@@ -881,19 +999,52 @@ function drawBoard() {
const bounceY = piece._bounceOffset || 0; const bounceY = piece._bounceOffset || 0;
const drawY = oy + bounceY; const drawY = oy + bounceY;
// === ENHANCEMENT 7: Highlight glow ring for selectable ===
if (isHighlighted) { if (isHighlighted) {
ctx.save();
ctx.shadowColor = '#E4AC38';
ctx.shadowBlur = 8;
ctx.beginPath(); ctx.arc(ox, drawY, r + 3, 0, Math.PI*2); ctx.beginPath(); ctx.arc(ox, drawY, r + 3, 0, Math.PI*2);
ctx.strokeStyle = '#E4AC38'; ctx.lineWidth = 2.5; ctx.stroke(); ctx.strokeStyle = '#E4AC38'; ctx.lineWidth = 2.5; ctx.stroke();
ctx.restore();
ctx.beginPath(); ctx.arc(ox, drawY, r + 6, 0, Math.PI*2); ctx.beginPath(); ctx.arc(ox, drawY, r + 6, 0, Math.PI*2);
ctx.strokeStyle = 'rgba(228,172,56,0.3)'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.strokeStyle = 'rgba(228,172,56,0.25)'; ctx.lineWidth = 1.5; ctx.stroke();
} }
ctx.beginPath(); ctx.arc(ox, oy + 2, r, 0, Math.PI*2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fill(); // === ENHANCEMENT 3: 3D pawn with gradient body ===
ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill(); // Drop shadow
ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill(); ctx.save();
ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.shadowColor = 'rgba(0,0,0,0.35)';
ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); ctx.shadowBlur = 3;
ctx.beginPath(); ctx.arc(ox - r*0.2, drawY - r*0.35, r*0.18, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill(); ctx.shadowOffsetY = 2;
ctx.beginPath(); ctx.ellipse(ox, oy + r*0.5, r*0.7, r*0.3, 0, 0, Math.PI*2);
ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.fill();
ctx.restore();
// Base (wider ellipse) with gradient
const baseGrad = ctx.createLinearGradient(ox-r, drawY+r*0.2, ox+r, drawY+r*0.6);
baseGrad.addColorStop(0, COLORS_LIGHT[pIdx]);
baseGrad.addColorStop(0.5, COLORS[pIdx]);
baseGrad.addColorStop(1, darkenColor(COLORS[pIdx], 40));
ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.35, r*0.82, r*0.42, 0, 0, Math.PI*2);
ctx.fillStyle = baseGrad; ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.7)'; ctx.lineWidth = 1.2; ctx.stroke();
// Head (circle) with radial gradient for 3D
const headGrad = ctx.createRadialGradient(ox - r*0.15, drawY - r*0.3, 0, ox, drawY - r*0.1, r*0.7);
headGrad.addColorStop(0, COLORS_LIGHT[pIdx]);
headGrad.addColorStop(0.6, COLORS[pIdx]);
headGrad.addColorStop(1, darkenColor(COLORS[pIdx], 50));
ctx.beginPath(); ctx.arc(ox, drawY - r*0.1, r*0.6, 0, Math.PI*2);
ctx.fillStyle = headGrad; ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 1.5; ctx.stroke();
// Shine highlight (top-left specular)
ctx.beginPath(); ctx.arc(ox - r*0.18, drawY - r*0.3, r*0.2, 0, Math.PI*2);
ctx.fillStyle = 'rgba(255,255,255,0.7)'; ctx.fill();
// Smaller secondary shine
ctx.beginPath(); ctx.arc(ox - r*0.05, drawY - r*0.45, r*0.08, 0, Math.PI*2);
ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill();
}); });
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment