Commit c5374e12 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: redesign backgammon game UI with real player data + polished visuals

- Player cards now use store player data (avatar_url, display_name, level)
- Opponent shows bot icon or opponent data for multiplayer
- Active turn highlight on player cards with green glow border
- Cube value display in match info area
- Professional dark gradient background with proper hierarchy
- Controls bar with proper spacing and button styling
- Doubling offer dialog with backdrop blur and better layout
- Turn badge repositioned to top-center with pill shape
- Emote panel centered with backdrop blur
- All interactive elements have proper touch targets (40px+)
- Safe area inset for bottom bar
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 873b7000
...@@ -38,50 +38,70 @@ export function mountGame(el, p) { ...@@ -38,50 +38,70 @@ export function mountGame(el, p) {
match = createMatch(matchLength, variant); match = createMatch(matchLength, variant);
if (!useCube) match.cube = null; if (!useCube) match.cube = null;
const player = store.get('player') || {};
const myName = player.display_name || player.username || 'أنت';
const myAvatar = player.avatar_url
? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
: emoji('person', '👤', 18);
const myLevel = player.level || 1;
const oppName = mode === 'bot' ? 'بوت' : (params.opponentName || 'خصم');
const oppAvatar = params.opponentAvatar
? `<img src="${params.opponentAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
: (mode === 'bot' ? '🤖' : emoji('person', '👤', 18));
const oppLevel = params.opponentLevel || (mode === 'bot' ? '—' : 1);
const cubeVal = match.cube ? match.cube.value : 1;
el.innerHTML = ` el.innerHTML = `
<div class="bgg-container"> <div class="bgg-container">
<div class="bgg-top-bar"> <div class="bgg-top-bar">
<div class="bgg-player-card"> <div class="bgg-player-card bgg-opp-card" id="opp-card">
<div class="bgg-avatar">🎯</div> <div class="bgg-avatar bgg-avatar-opp">${oppAvatar}</div>
<div class="bgg-player-info"> <div class="bgg-player-info">
<div class="bgg-name" id="name-top">${mode === 'bot' ? 'بوت' : 'خصم'}</div> <div class="bgg-name" id="name-top">${oppName}</div>
<div class="bgg-pips" id="pips-top">167 نقطة</div> <div class="bgg-meta"><span class="bgg-level">Lv.${oppLevel}</span><span class="bgg-pips" id="pips-top">167</span></div>
</div> </div>
<div class="bgg-score" id="score-top">0</div> <div class="bgg-score" id="score-top">0</div>
</div> </div>
<div class="bgg-match-info">
<span class="bgg-cube-display" id="cube-display">×${cubeVal}</span>
<span class="bgg-match-length">${matchLength} نقاط</span>
</div>
</div> </div>
<div class="bgg-board-area"> <div class="bgg-board-area">
<canvas id="bg-canvas"></canvas> <canvas id="bg-canvas"></canvas>
<div class="bgg-turn-badge" id="turn-indicator">دورك</div>
</div> </div>
<div class="bgg-controls"> <div class="bgg-controls">
<button class="bgg-roll-btn" id="btn-roll">${emoji('game_die', '🎲', 22)} ارمي النرد</button> <button class="bgg-action-btn bgg-quit" id="btn-quit">✕</button>
<button class="bgg-cube-btn" id="btn-double" style="display:none;">×2 ضاعف</button> <button class="bgg-action-btn bgg-emote-btn" id="btn-emote">${emoji('sparkles', '✨', 18)}</button>
<button class="bgg-roll-btn" id="btn-roll">${emoji('game_die', '🎲', 20)} ارمِ النرد</button>
<button class="bgg-cube-btn" id="btn-double" style="display:none;">×2</button>
</div> </div>
<div class="bgg-double-offer" id="double-offer" style="display:none;"> <div class="bgg-double-offer" id="double-offer" style="display:none;">
<p>الخصم يضاعف! ×<strong id="double-val">2</strong></p> <div class="bgg-double-icon">×<span id="double-val">2</span></div>
<button class="bgg-accept-btn" id="btn-accept-double">قبول</button> <p>الخصم يضاعف!</p>
<button class="bgg-decline-btn" id="btn-decline-double">رفض</button> <div class="bgg-double-actions">
<button class="bgg-decline-btn" id="btn-decline-double">رفض</button>
<button class="bgg-accept-btn" id="btn-accept-double">قبول</button>
</div>
</div> </div>
<div class="bgg-bottom-bar"> <div class="bgg-bottom-bar">
<div class="bgg-player-card"> <div class="bgg-player-card bgg-my-card" id="my-card">
<div class="bgg-avatar">👤</div> <div class="bgg-avatar bgg-avatar-me">${myAvatar}</div>
<div class="bgg-player-info"> <div class="bgg-player-info">
<div class="bgg-name" id="name-bottom">أنت</div> <div class="bgg-name" id="name-bottom">${myName}</div>
<div class="bgg-pips" id="pips-bottom">167 نقطة</div> <div class="bgg-meta"><span class="bgg-level">Lv.${myLevel}</span><span class="bgg-pips" id="pips-bottom">167</span></div>
</div> </div>
<div class="bgg-score bgg-my-score" id="score-bottom">0</div> <div class="bgg-score bgg-my-score" id="score-bottom">0</div>
</div> </div>
<div class="bgg-actions">
<button class="bgg-action-btn" id="btn-emote">${emoji('sparkles', '✨', 18)}</button>
<button class="bgg-action-btn bgg-quit" id="btn-quit">✕</button>
</div>
</div> </div>
<div class="bgg-turn-badge" id="turn-indicator">دورك</div>
<div class="bgg-emote-panel" id="emote-panel" style="display:none;"></div> <div class="bgg-emote-panel" id="emote-panel" style="display:none;"></div>
</div> </div>
${getStyles()} ${getStyles()}
...@@ -96,12 +116,12 @@ export function mountGame(el, p) { ...@@ -96,12 +116,12 @@ export function mountGame(el, p) {
canvas.addEventListener('click', onCanvasClick); canvas.addEventListener('click', onCanvasClick);
canvas.addEventListener('touchstart', onCanvasTouch, { passive: false }); canvas.addEventListener('touchstart', onCanvasTouch, { passive: false });
el.querySelector('#btn-roll').addEventListener('click', onRollClick); el.querySelector('#btn-roll')?.addEventListener('click', onRollClick);
el.querySelector('#btn-double').addEventListener('click', onDoubleClick); el.querySelector('#btn-double')?.addEventListener('click', onDoubleClick);
el.querySelector('#btn-accept-double').addEventListener('click', onAcceptDouble); el.querySelector('#btn-accept-double')?.addEventListener('click', onAcceptDouble);
el.querySelector('#btn-decline-double').addEventListener('click', onDeclineDouble); el.querySelector('#btn-decline-double')?.addEventListener('click', onDeclineDouble);
el.querySelector('#btn-emote').addEventListener('click', toggleEmotePanel); el.querySelector('#btn-emote')?.addEventListener('click', toggleEmotePanel);
el.querySelector('#btn-quit').addEventListener('click', onQuit); el.querySelector('#btn-quit')?.addEventListener('click', onQuit);
window.addEventListener('resize', resizeCanvas); window.addEventListener('resize', resizeCanvas);
if (mode === 'live') { if (mode === 'live') {
...@@ -251,11 +271,21 @@ function updateUI() { ...@@ -251,11 +271,21 @@ function updateUI() {
const topColor = myColor === WHITE ? BLACK : WHITE; const topColor = myColor === WHITE ? BLACK : WHITE;
const isMyTurn = game.turn === myColor; const isMyTurn = game.turn === myColor;
setText('#pips-top', getPipCount(game.state, topColor) + ' نقطة'); setText('#pips-top', getPipCount(game.state, topColor));
setText('#pips-bottom', getPipCount(game.state, myColor) + ' نقطة'); setText('#pips-bottom', getPipCount(game.state, myColor));
setText('#score-top', String(match.scores[topColor])); setText('#score-top', String(match.scores[topColor]));
setText('#score-bottom', String(match.scores[myColor])); setText('#score-bottom', String(match.scores[myColor]));
// Cube display
const cubeEl = container.querySelector('#cube-display');
if (cubeEl && match.cube) cubeEl.textContent = ${match.cube.value}`;
// Active turn highlight on cards
const oppCard = container.querySelector('#opp-card');
const myCard = container.querySelector('#my-card');
if (oppCard) oppCard.classList.toggle('bgg-active-turn', !isMyTurn);
if (myCard) myCard.classList.toggle('bgg-active-turn', isMyTurn);
// Roll button // Roll button
const rollBtn = container.querySelector('#btn-roll'); const rollBtn = container.querySelector('#btn-roll');
rollBtn.style.display = (isMyTurn && !game.dice && !isAnimating && !isRolling) ? '' : 'none'; rollBtn.style.display = (isMyTurn && !game.dice && !isAnimating && !isRolling) ? '' : 'none';
...@@ -662,109 +692,173 @@ function getStyles() { ...@@ -662,109 +692,173 @@ function getStyles() {
return `<style> return `<style>
.bgg-container { .bgg-container {
display:flex;flex-direction:column;height:100%; display:flex;flex-direction:column;height:100%;
background:#0a0f14;color:#f8fafc;overflow:hidden; background:linear-gradient(180deg,#060a12 0%,#0c1424 40%,#0a1018 100%);
color:#f8fafc;overflow:hidden;
font-family:var(--font-ar,'IBM Plex Sans Arabic',sans-serif); font-family:var(--font-ar,'IBM Plex Sans Arabic',sans-serif);
position:relative; position:relative;
} }
.bgg-top-bar,.bgg-bottom-bar {
display:flex;align-items:center;padding:8px 12px;gap:8px; /* ── Player Bars ── */
background:rgba(0,0,0,0.4); .bgg-top-bar {
display:flex;align-items:center;padding:10px 14px;gap:8px;
background:linear-gradient(180deg,rgba(0,0,0,0.6),rgba(0,0,0,0.3));
border-bottom:1px solid rgba(255,255,255,0.04);
}
.bgg-bottom-bar {
display:flex;align-items:center;padding:10px 14px;gap:8px;
background:linear-gradient(0deg,rgba(0,0,0,0.6),rgba(0,0,0,0.3));
border-top:1px solid rgba(255,255,255,0.04);
padding-bottom:max(10px, env(safe-area-inset-bottom, 0px));
}
.bgg-player-card {
display:flex;align-items:center;gap:10px;flex:1;
padding:6px 10px;border-radius:12px;
background:rgba(255,255,255,0.02);
border:1.5px solid transparent;
transition:border-color 0.3s,box-shadow 0.3s;
}
.bgg-player-card.bgg-active-turn {
border-color:rgba(52,211,153,0.4);
box-shadow:0 0 12px rgba(52,211,153,0.1);
} }
.bgg-bottom-bar { justify-content:space-between; }
.bgg-player-card { display:flex;align-items:center;gap:8px;flex:1; }
.bgg-avatar { .bgg-avatar {
width:34px;height:34px;border-radius:50%; width:36px;height:36px;border-radius:50%;
display:flex;align-items:center;justify-content:center; display:flex;align-items:center;justify-content:center;
background:rgba(255,255,255,0.06);font-size:16px; background:linear-gradient(135deg,#1a2540,#0e1830);
border:2px solid rgba(255,255,255,0.08); font-size:16px;overflow:hidden;flex-shrink:0;
} border:2px solid rgba(255,255,255,0.1);
.bgg-player-info { flex:1; } box-shadow:0 2px 8px rgba(0,0,0,0.3);
.bgg-name { font-size:13px;font-weight:700; } }
.bgg-pips { font-size:11px;color:#64748b; } .bgg-avatar-me { border-color:rgba(228,172,56,0.5); }
.bgg-score { font-size:18px;font-weight:800;color:#94a3b8;min-width:20px;text-align:center; } .bgg-avatar-opp { border-color:rgba(139,92,246,0.4); }
.bgg-my-score { color:#d4940a; } .bgg-player-info { flex:1;min-width:0; }
.bgg-name { font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
.bgg-meta { display:flex;align-items:center;gap:8px;margin-top:1px; }
.bgg-level { font-size:10px;color:#64748b;font-weight:600; }
.bgg-pips { font-size:10px;color:#94a3b8;font-weight:500; }
.bgg-pips::before { content:'⬡ ';opacity:0.5; }
.bgg-score {
font-size:20px;font-weight:800;color:#475569;min-width:24px;text-align:center;
font-variant-numeric:tabular-nums;
}
.bgg-my-score { color:var(--gold,#E4AC38); }
.bgg-match-info {
display:flex;flex-direction:column;align-items:center;gap:2px;
}
.bgg-cube-display {
font-size:14px;font-weight:800;color:#a78bfa;
background:rgba(139,92,246,0.12);border:1px solid rgba(139,92,246,0.25);
border-radius:6px;padding:2px 8px;
}
.bgg-match-length { font-size:9px;color:#64748b; }
/* ── Board ── */
.bgg-board-area { .bgg-board-area {
flex:1;display:flex;align-items:center;justify-content:center;padding:4px; flex:1;display:flex;align-items:center;justify-content:center;
padding:4px 6px;position:relative;min-height:0;
}
#bg-canvas {
border-radius:8px;touch-action:none;cursor:pointer;
box-shadow:0 4px 24px rgba(0,0,0,0.5),0 0 0 1px rgba(255,255,255,0.04);
}
.bgg-turn-badge {
position:absolute;top:8px;left:50%;transform:translateX(-50%);
font-size:11px;font-weight:700;padding:4px 12px;border-radius:20px;
backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
pointer-events:none;transition:all 0.3s;
} }
#bg-canvas { border-radius:6px;touch-action:none;cursor:pointer; } .bgg-turn-mine { background:rgba(16,185,129,0.2);color:#34D399;border:1px solid rgba(52,211,153,0.3); }
.bgg-turn-opp { background:rgba(239,68,68,0.12);color:#f87171;border:1px solid rgba(239,68,68,0.2); }
/* ── Controls ── */
.bgg-controls { .bgg-controls {
display:flex;align-items:center;justify-content:center;gap:10px;padding:8px; display:flex;align-items:center;justify-content:center;gap:10px;
min-height:52px; padding:10px 14px;
background:linear-gradient(180deg,rgba(0,0,0,0.2),rgba(0,0,0,0.4));
min-height:56px;
} }
.bgg-roll-btn { .bgg-roll-btn {
display:flex;align-items:center;gap:6px; display:flex;align-items:center;gap:6px;
padding:12px 28px;border-radius:14px;border:none; padding:13px 32px;border-radius:14px;border:none;
background:linear-gradient(135deg,#d4940a,#8B4513); background:linear-gradient(135deg,#E4AC38,#D4940A);
color:#fff;font-size:15px;font-weight:700;cursor:pointer; color:#fff;font-size:15px;font-weight:800;cursor:pointer;
box-shadow:0 4px 16px rgba(212,148,10,0.4); box-shadow:0 4px 16px rgba(228,172,56,0.35),inset 0 1px 0 rgba(255,255,255,0.2);
transition:transform 0.12s; transition:transform 0.12s var(--ease-spring,cubic-bezier(0.34,1.56,0.64,1)),box-shadow 0.15s;
} }
.bgg-roll-btn:active { transform:scale(0.92); } .bgg-roll-btn:active { transform:scale(0.92);box-shadow:0 2px 8px rgba(228,172,56,0.3); }
.bgg-cube-btn { .bgg-cube-btn {
padding:8px 16px;border-radius:10px;border:none; padding:10px 16px;border-radius:10px;border:none;
background:rgba(139,92,246,0.15);border:1.5px solid rgba(139,92,246,0.4); background:rgba(139,92,246,0.12);border:1.5px solid rgba(139,92,246,0.35);
color:#a78bfa;font-size:13px;font-weight:700;cursor:pointer; color:#a78bfa;font-size:15px;font-weight:800;cursor:pointer;
transition:transform 0.12s;
} }
.bgg-cube-btn:active { transform:scale(0.9); } .bgg-cube-btn:active { transform:scale(0.9); }
.bgg-double-offer {
position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
background:rgba(10,15,20,0.95);border:1.5px solid #d4940a;
border-radius:16px;padding:20px 24px;text-align:center;z-index:20;
}
.bgg-double-offer p { margin:0 0 12px;font-size:14px;color:#e2e8f0; }
.bgg-accept-btn,.bgg-decline-btn {
padding:10px 20px;border-radius:10px;border:none;
font-weight:700;font-size:13px;cursor:pointer;margin:0 4px;
}
.bgg-accept-btn { background:#10b981;color:#fff; }
.bgg-decline-btn { background:#ef4444;color:#fff; }
.bgg-actions { display:flex;gap:6px; }
.bgg-action-btn { .bgg-action-btn {
width:36px;height:36px;border-radius:50%;border:none; width:40px;height:40px;border-radius:50%;border:none;
background:rgba(255,255,255,0.05);color:#94a3b8; background:rgba(255,255,255,0.04);color:#94a3b8;
font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center; font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;
border:1px solid rgba(255,255,255,0.06);
transition:background 0.15s;
} }
.bgg-action-btn:active { background:rgba(255,255,255,0.1); } .bgg-action-btn:active { background:rgba(255,255,255,0.1); }
.bgg-quit { color:#ef4444; } .bgg-quit { color:#ef4444;border-color:rgba(239,68,68,0.15); }
.bgg-emote-btn { color:#f8fafc; }
.bgg-turn-badge { /* ── Doubling Offer ── */
position:absolute;top:50%;left:4px;transform:translateY(-50%); .bgg-double-offer {
writing-mode:vertical-rl;font-size:10px;font-weight:700; position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
padding:6px 4px;border-radius:6px; background:rgba(10,15,25,0.96);border:1.5px solid rgba(228,172,56,0.4);
border-radius:20px;padding:24px 28px;text-align:center;z-index:20;
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
box-shadow:0 16px 48px rgba(0,0,0,0.6);
}
.bgg-double-icon {
font-size:28px;font-weight:900;color:#E4AC38;margin-bottom:8px;
}
.bgg-double-offer p { margin:0 0 16px;font-size:14px;color:#e2e8f0; }
.bgg-double-actions { display:flex;gap:10px;justify-content:center; }
.bgg-accept-btn,.bgg-decline-btn {
padding:11px 24px;border-radius:12px;border:none;
font-weight:700;font-size:14px;cursor:pointer;
transition:transform 0.12s;
} }
.bgg-turn-mine { background:rgba(16,185,129,0.15);color:#34D399; } .bgg-accept-btn { background:linear-gradient(135deg,#10b981,#059669);color:#fff;box-shadow:0 3px 12px rgba(16,185,129,0.3); }
.bgg-turn-opp { background:rgba(239,68,68,0.1);color:#f87171; } .bgg-decline-btn { background:rgba(239,68,68,0.15);color:#f87171;border:1.5px solid rgba(239,68,68,0.3); }
.bgg-accept-btn:active,.bgg-decline-btn:active { transform:scale(0.93); }
/* ── Emotes ── */
.bgg-emote-panel { .bgg-emote-panel {
position:absolute;bottom:56px;right:12px; position:absolute;bottom:72px;left:50%;transform:translateX(-50%);
background:rgba(10,15,20,0.95);border:1px solid rgba(255,255,255,0.08); background:rgba(10,15,25,0.96);border:1px solid rgba(255,255,255,0.08);
border-radius:14px;padding:12px;z-index:20;max-width:240px; border-radius:16px;padding:14px;z-index:20;
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
box-shadow:0 12px 40px rgba(0,0,0,0.5);
} }
.bgg-emotes { display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px; } .bgg-emotes { display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px; }
.bgg-emote-btn { .bgg-emote-btn {
width:36px;height:36px;border-radius:8px;border:none; width:40px;height:40px;border-radius:10px;border:none;
background:rgba(255,255,255,0.05);font-size:20px;cursor:pointer; background:rgba(255,255,255,0.04);font-size:22px;cursor:pointer;
transition:transform 0.1s,background 0.15s;
} }
.bgg-emote-btn:active { background:rgba(255,255,255,0.12); } .bgg-emote-btn:active { transform:scale(0.88);background:rgba(255,255,255,0.12); }
.bgg-phrases { display:flex;flex-direction:column;gap:4px; } .bgg-phrases { display:flex;flex-direction:column;gap:4px; }
.bgg-phrase-btn { .bgg-phrase-btn {
padding:6px 10px;border-radius:6px;border:none; padding:7px 12px;border-radius:8px;border:none;
background:rgba(255,255,255,0.03);color:#94a3b8; background:rgba(255,255,255,0.03);color:#94a3b8;
font-size:12px;cursor:pointer;text-align:right; font-size:12px;cursor:pointer;text-align:right;
transition:background 0.15s;
} }
.bgg-phrase-btn:active { background:rgba(255,255,255,0.08); } .bgg-phrase-btn:active { background:rgba(255,255,255,0.08); }
/* ── Bubble ── */
.bgg-bubble { .bgg-bubble {
position:absolute;bottom:80px;right:50px;font-size:36px; position:absolute;bottom:100px;left:50%;transform:translateX(-50%);
pointer-events:none;animation:bubblePop 2.2s ease-out forwards; font-size:36px;pointer-events:none;
animation:bubblePop 2.2s var(--ease-out,cubic-bezier(0.16,1,0.3,1)) forwards;
} }
@keyframes bubblePop { @keyframes bubblePop {
0%{opacity:1;transform:translateY(0) scale(1)} 0%{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}
100%{opacity:0;transform:translateY(-40px) scale(1.2)} 100%{opacity:0;transform:translateX(-50%) translateY(-50px) scale(1.3)}
} }
</style>`; </style>`;
} }
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