Commit 51e59856 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: UI polish — touch targets, board sizing, login logo, bot cards

- Login: show brand logo via assetImg() instead of plain text, larger register link
- Bot select: 44px back button, difficulty color bars, fallback initials when portrait fails
- Chess board: raise max-width 400→500px, reduce padding — fills more screen
- Chess controls: min-height 44px, better contrast (#e2e8f0), larger border-radius
- Ludo board: raise cap 360→420px to fill available space
- Ludo exit button: 44px circular touch target instead of rectangular
- Tab bar items: min 44px touch targets with centered content
- HUD bell button: 36→44px
- Game menu: staggered entrance animation on buttons/chips
- Fix 9px font in ludo panels → 10px minimum
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 93eeb5c8
...@@ -119,8 +119,8 @@ html, body { ...@@ -119,8 +119,8 @@ html, body {
} }
.hud-btn { .hud-btn {
width: 36px; width: 44px;
height: 36px; height: 44px;
border-radius: 50%; border-radius: 50%;
background: var(--bg-elevated); background: var(--bg-elevated);
display: flex; display: flex;
...@@ -218,13 +218,16 @@ html, body { ...@@ -218,13 +218,16 @@ html, body {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 2px; gap: 2px;
padding: var(--s-1) var(--s-3); padding: var(--s-2) var(--s-3);
border-radius: var(--r-md); border-radius: var(--r-md);
color: var(--text-muted); color: var(--text-muted);
font-size: 10px; font-size: 10px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: color var(--dur-fast), transform var(--dur-fast); transition: color var(--dur-fast), transform var(--dur-fast);
min-width: 44px;
min-height: 44px;
justify-content: center;
} }
.tab-item:active { transform: scale(0.9); } .tab-item:active { transform: scale(0.9); }
......
...@@ -4,11 +4,12 @@ import * as scene from '../../../core/scene.js'; ...@@ -4,11 +4,12 @@ import * as scene from '../../../core/scene.js';
import * as net from '../../../core/net.js'; import * as net from '../../../core/net.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import { t } from '../../../core/i18n.js'; import { t } from '../../../core/i18n.js';
import { assetImg } from '../../../core/theme.js';
export function mountLogin(el) { export function mountLogin(el) {
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:var(--s-6);"> <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:var(--s-6);">
<div style="font-size:36px;font-weight:800;color:var(--gold);margin-bottom:var(--s-2);">EL3AB</div> <div style="margin-bottom:var(--s-4);">${assetImg('logo', '<span style="font-size:42px;font-weight:900;color:var(--gold);font-family:var(--font-lat);">EL3AB</span>', 120, 60)}</div>
<p style="color:var(--text-secondary);margin-bottom:var(--s-8);font-size:14px;">${t('auth.login')}</p> <p style="color:var(--text-secondary);margin-bottom:var(--s-8);font-size:14px;">${t('auth.login')}</p>
<form id="login-form" style="width:100%;max-width:320px;display:flex;flex-direction:column;gap:var(--s-3);"> <form id="login-form" style="width:100%;max-width:320px;display:flex;flex-direction:column;gap:var(--s-3);">
<input class="input" type="email" id="login-email" placeholder="${t('auth.email')}" autocomplete="email" required> <input class="input" type="email" id="login-email" placeholder="${t('auth.email')}" autocomplete="email" required>
...@@ -16,8 +17,8 @@ export function mountLogin(el) { ...@@ -16,8 +17,8 @@ export function mountLogin(el) {
<div id="login-error" style="color:var(--error);font-size:13px;min-height:20px;"></div> <div id="login-error" style="color:var(--error);font-size:13px;min-height:20px;"></div>
<button type="submit" class="btn btn-primary w-full" id="login-btn">${t('auth.login.btn')}</button> <button type="submit" class="btn btn-primary w-full" id="login-btn">${t('auth.login.btn')}</button>
</form> </form>
<p style="margin-top:var(--s-6);color:var(--text-secondary);font-size:13px;"> <p style="margin-top:var(--s-6);color:var(--text-secondary);font-size:14px;">
${t('auth.no_account')} <a href="#" id="goto-register" style="color:var(--gold);text-decoration:none;font-weight:600;">${t('auth.register')}</a> ${t('auth.no_account')} <a href="#" id="goto-register" style="color:var(--gold);text-decoration:none;font-weight:700;">${t('auth.register')}</a>
</p> </p>
</div> </div>
`; `;
......
...@@ -250,7 +250,7 @@ export class ChessBoard { ...@@ -250,7 +250,7 @@ export class ChessBoard {
this.container = container; this.container = container;
this.wrapper = document.createElement('div'); this.wrapper = document.createElement('div');
this.wrapper.style.cssText = 'position:relative;width:100%;max-width:400px;margin:0 auto;aspect-ratio:1;'; this.wrapper.style.cssText = 'position:relative;width:100%;max-width:500px;margin:0 auto;aspect-ratio:1;';
container.appendChild(this.wrapper); container.appendChild(this.wrapper);
boardInstance = this; boardInstance = this;
...@@ -261,7 +261,7 @@ export class ChessBoard { ...@@ -261,7 +261,7 @@ export class ChessBoard {
setupCanvas() { setupCanvas() {
const containerH = this.container.clientHeight || 500; const containerH = this.container.clientHeight || 500;
const containerW = this.container.clientWidth || this.wrapper.clientWidth || 360; const containerW = this.container.clientWidth || this.wrapper.clientWidth || 360;
const size = Math.min(containerW - 8, containerH - 8, 500); const size = Math.min(containerW - 4, containerH - 4, 500);
this.squareSize = size / 8; this.squareSize = size / 8;
this.wrapper.style.maxWidth = size + 'px'; this.wrapper.style.maxWidth = size + 'px';
const { canvas, ctx } = createCanvas(this.wrapper, size, size); const { canvas, ctx } = createCanvas(this.wrapper, size, size);
......
...@@ -116,7 +116,7 @@ export function mountGame(el, params) { ...@@ -116,7 +116,7 @@ export function mountGame(el, params) {
</div> </div>
<!-- Board --> <!-- Board -->
<div id="board-container" style="flex:1;display:flex;align-items:center;justify-content:center;padding:2px 4px;position:relative;min-height:0;"> <div id="board-container" style="flex:1;display:flex;align-items:center;justify-content:center;padding:2px 4px;position:relative;min-height:0;max-height:calc(100vw + 20px);">
<div id="bot-thinking" style="display:none;position:absolute;top:8px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.8);color:#E4AC38;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:600;z-index:10;"> <div id="bot-thinking" style="display:none;position:absolute;top:8px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.8);color:#E4AC38;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:600;z-index:10;">
${t('game.thinking')} <span class="pulse">${emoji('thinking_dots', '●●●', 12)}</span> ${t('game.thinking')} <span class="pulse">${emoji('thinking_dots', '●●●', 12)}</span>
</div> </div>
...@@ -158,8 +158,8 @@ export function mountGame(el, params) { ...@@ -158,8 +158,8 @@ export function mountGame(el, params) {
</div> </div>
</div> </div>
<style> <style>
.ctrl-btn { flex:1;background:#1e1e3a;border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:12px;font-weight:600;padding:8px;border-radius:6px;cursor:pointer;font-family:inherit;transition:background 0.15s; } .ctrl-btn { flex:1;background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;font-size:13px;font-weight:600;padding:10px 8px;border-radius:8px;cursor:pointer;font-family:inherit;transition:background 0.15s;min-height:44px; }
.ctrl-btn:active { background:#2a2a5a;transform:scale(0.97); } .ctrl-btn:active { background:#2a2a5a;transform:scale(0.95); }
.chess-clock.low-time { color:#EF4444!important;animation:clockPulse 1s infinite; } .chess-clock.low-time { color:#EF4444!important;animation:clockPulse 1s infinite; }
@keyframes clockPulse { 0%,100%{opacity:1}50%{opacity:0.5} } @keyframes clockPulse { 0%,100%{opacity:1}50%{opacity:0.5} }
</style> </style>
......
...@@ -33,7 +33,7 @@ function renderPanel(p) { ...@@ -33,7 +33,7 @@ function renderPanel(p) {
</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 style="font-size:11px;font-weight:600;color:#f8fafc;">${p.name}</span>
${p.level ? `<span style="font-size:9px;color:#64748b;">${p.level}</span>` : ''} ${p.level ? `<span style="font-size:10px;color:#64748b;">${p.level}</span>` : ''}
</div> </div>
</div> </div>
`; `;
...@@ -87,7 +87,7 @@ export function mountGame(el, params) { ...@@ -87,7 +87,7 @@ export function mountGame(el, params) {
${renderPanel(panels[3])} ${renderPanel(panels[3])}
</div> </div>
<div id="dice-area" style="display:flex;align-items:center;gap:12px;padding:10px 16px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);justify-content:center;"> <div id="dice-area" style="display:flex;align-items:center;gap:12px;padding:10px 16px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);justify-content:center;">
<button class="btn btn-secondary" id="exit-btn" style="min-height:40px;padding:8px 12px;font-size:12px;color:#EF4444;">✕</button> <button class="btn btn-secondary" id="exit-btn" style="min-height:44px;min-width:44px;padding:0;font-size:14px;color:#EF4444;border-radius:50%;">✕</button>
<div id="dice-box" style="width:50px;height:50px;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);"> <div id="dice-box" style="width:50px;height:50px;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);">
</div> </div>
<button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:12px 32px;min-height:48px;" disabled>ارمِ النرد</button> <button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:12px 32px;min-height:48px;" disabled>ارمِ النرد</button>
...@@ -107,7 +107,7 @@ export function mountGame(el, params) { ...@@ -107,7 +107,7 @@ export function mountGame(el, params) {
const wrap = el.querySelector('#ludo-wrap'); const wrap = el.querySelector('#ludo-wrap');
const availableH = wrap.clientHeight || 300; const availableH = wrap.clientHeight || 300;
const availableW = wrap.clientWidth || 360; const availableW = wrap.clientWidth || 360;
boardSize = Math.min(availableW - 8, availableH - 8, 360); boardSize = Math.min(availableW - 8, availableH - 8, 420);
cellSize = boardSize / 15; cellSize = boardSize / 15;
const { canvas: c, ctx: cx } = createCanvas(wrap, boardSize, boardSize); const { canvas: c, ctx: cx } = createCanvas(wrap, boardSize, boardSize);
c.style.cssText = 'border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.4);max-width:100%;max-height:100%;'; c.style.cssText = 'border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.4);max-width:100%;max-height:100%;';
......
...@@ -3,18 +3,20 @@ import * as audio from '../../../core/audio.js'; ...@@ -3,18 +3,20 @@ import * as audio from '../../../core/audio.js';
import * as net from '../../../core/net.js'; import * as net from '../../../core/net.js';
import { t } from '../../../core/i18n.js'; import { t } from '../../../core/i18n.js';
const DIFFICULTY_COLORS = ['#34D399', '#34D399', '#FBBF24', '#FBBF24', '#F97316', '#F97316', '#EF4444'];
export async function mountBotSelect(el, params) { export async function mountBotSelect(el, params) {
el.innerHTML = ` el.innerHTML = `
<div style="padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-4);"> <div style="padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-4);">
<div style="display:flex;align-items:center;gap:var(--s-3);"> <div style="display:flex;align-items:center;gap:var(--s-3);">
<button class="btn btn-secondary" id="back-btn" style="width:36px;height:36px;padding:0;">←</button> <button class="btn btn-secondary" id="back-btn" style="width:44px;height:44px;padding:0;font-size:18px;">←</button>
<h2 style="font-size:18px;font-weight:700;">${t('play.select_bot')}</h2> <h2 style="font-size:18px;font-weight:700;">${t('play.select_bot')}</h2>
</div> </div>
<div id="bots-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--s-3);"> <div id="bots-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--s-3);">
<div class="skeleton" style="height:120px;"></div> <div class="skeleton" style="height:140px;border-radius:var(--r-xl);"></div>
<div class="skeleton" style="height:120px;"></div> <div class="skeleton" style="height:140px;border-radius:var(--r-xl);"></div>
<div class="skeleton" style="height:120px;"></div> <div class="skeleton" style="height:140px;border-radius:var(--r-xl);"></div>
<div class="skeleton" style="height:120px;"></div> <div class="skeleton" style="height:140px;border-radius:var(--r-xl);"></div>
</div> </div>
</div> </div>
`; `;
...@@ -35,18 +37,22 @@ export async function mountBotSelect(el, params) { ...@@ -35,18 +37,22 @@ export async function mountBotSelect(el, params) {
function renderBots(el, bots, params) { function renderBots(el, bots, params) {
const grid = el.querySelector('#bots-grid'); const grid = el.querySelector('#bots-grid');
grid.innerHTML = bots.map(bot => ` grid.innerHTML = bots.map((bot, idx) => {
<div class="card bot-card" data-id="${bot.id}" style="cursor:pointer;padding:var(--s-3);"> const diffColor = DIFFICULTY_COLORS[Math.min(idx, DIFFICULTY_COLORS.length - 1)];
<div style="width:48px;height:48px;border-radius:var(--r-full);background:var(--bg-elevated);margin:0 auto var(--s-2);display:flex;align-items:center;justify-content:center;font-size:20px;overflow:hidden;"> const initial = (bot.name_ar || bot.name || '?')[0];
${bot.portrait_url ? `<img src="https://stockfishapi.caprover.al-arcade.com${bot.portrait_url}" style="width:100%;height:100%;object-fit:cover;">` : '♟'} return `
<div class="card bot-card" data-id="${bot.id}" style="cursor:pointer;padding:var(--s-3);position:relative;overflow:hidden;">
<div style="position:absolute;top:0;left:0;right:0;height:3px;background:${diffColor};"></div>
<div style="width:56px;height:56px;border-radius:var(--r-full);background:var(--bg-elevated);margin:var(--s-2) auto var(--s-2);display:flex;align-items:center;justify-content:center;font-size:20px;overflow:hidden;border:2px solid ${diffColor};">
${bot.portrait_url ? `<img src="https://stockfishapi.caprover.al-arcade.com${bot.portrait_url}" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';this.parentNode.querySelector('.bot-fallback').style.display='flex'"><span class="bot-fallback" style="display:none;font-size:22px;font-weight:800;color:${diffColor};">${initial}</span>` : `<span style="font-size:22px;font-weight:800;color:${diffColor};">${initial}</span>`}
</div> </div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:14px;font-weight:600;">${bot.name_ar || bot.name}</div> <div style="font-size:14px;font-weight:700;">${bot.name_ar || bot.name}</div>
<div style="font-size:11px;color:var(--text-secondary);">${bot.style_ar || bot.style}</div> <div style="font-size:11px;color:var(--text-secondary);margin-top:2px;">${bot.style_ar || bot.style}</div>
<div style="font-size:11px;color:var(--gold);margin-top:2px;">${bot.elo_min}-${bot.elo_max}</div> <div style="font-size:11px;color:${diffColor};font-weight:600;margin-top:4px;font-family:var(--font-lat);">${bot.elo_min}-${bot.elo_max}</div>
</div> </div>
</div> </div>
`).join(''); `;}).join('');
grid.querySelectorAll('.bot-card').forEach(card => { grid.querySelectorAll('.bot-card').forEach(card => {
card.addEventListener('click', () => { card.addEventListener('click', () => {
......
...@@ -308,6 +308,18 @@ function showGameMenu(menu, game) { ...@@ -308,6 +308,18 @@ function showGameMenu(menu, game) {
</div> </div>
`; `;
// Staggered entrance for menu buttons
const menuBtns = menu.querySelectorAll('.gm-btn, .gm-chip');
menuBtns.forEach((btn, i) => {
btn.style.opacity = '0';
btn.style.transform = 'translateY(12px)';
setTimeout(() => {
btn.style.transition = 'opacity 0.25s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)';
btn.style.opacity = '1';
btn.style.transform = 'translateY(0)';
}, 80 + i * 60);
});
menu.querySelector('#menu-close').addEventListener('click', () => { menu.querySelector('#menu-close').addEventListener('click', () => {
audio.play('click'); audio.play('click');
menu.classList.add('hidden'); menu.classList.add('hidden');
......
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