Commit 31a8e2dc authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: redesign domino UX — premium hand tiles, smart auto-fit, cohesive screens

Hand tiles (components/hand.js):
- Proper PIP_POSITIONS matching board tiles (position-based, not flex-wrap)
- Dynamic sizing: tiles scale to always fit viewport width
- Fan layout for 5+ tiles (card-like spread with arc lift)
- Slide-in animation for newly drawn tiles
- Premium domino look: gradient bg, 3D pips, edge shine, realistic border

Board (canvas/board.js):
- Smooth animated auto-pan + auto-zoom when chain exceeds viewport
- Lerp-based transitions (no jarring jumps on new tiles)
- Vignette overlay for depth
- Manual pan updates target state for smooth resume

Tile renderer (canvas/tile-renderer.js):
- Gradient fill (cream to warm), thicker border, top-edge shine
- 3D pips with specular highlights
- Better shadow depth and double-tile accent

Drag proxy (components/drag.js):
- Matches hand tile visual style (position-based pips)
- Valid/invalid state with colored borders + glow
- Spring animation on snap/bounce-back

Room screens (scenes/room.js):
- Unified design language across menu/bot-picker/target-picker/lobby
- Floating icon animation, consistent card styles
- Touch-responsive buttons with spring scale

Game layout (scenes/game.js):
- Polished opponent bar with avatar border glow
- Score bar with colored indicators
- Control buttons with active states and pulse animation
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9964877a
...@@ -23,11 +23,13 @@ export class DominoBoard { ...@@ -23,11 +23,13 @@ export class DominoBoard {
this.activeEndpoint = null; this.activeEndpoint = null;
this.animatingTile = null; this.animatingTile = null;
// Pan & zoom state // Pan & zoom state — smooth animated transitions
this.pan = { x: 0, y: 0 }; this.pan = { x: 0, y: 0 };
this.targetPan = { x: 0, y: 0 };
this.zoom = 1; this.zoom = 1;
this.targetZoom = 1; this.targetZoom = 1;
this._zoomAnimFrame = null; this._animFrame = null;
this._isAnimating = false;
this._setupInteraction(); this._setupInteraction();
} }
...@@ -35,34 +37,60 @@ export class DominoBoard { ...@@ -35,34 +37,60 @@ export class DominoBoard {
setChain(chain) { setChain(chain) {
this.chain = chain; this.chain = chain;
this.layout = computeLayout(chain, this.width, this.height); this.layout = computeLayout(chain, this.width, this.height);
this._autoZoom(); this._autoFit();
this.draw(); this.draw();
} }
_autoZoom() { _autoFit() {
if (this.chain.length < 4) { if (this.chain.length < 2) {
this.targetZoom = 1; this.targetZoom = 1;
this.targetPan = { x: 0, y: 0 };
} else { } else {
const bounds = this.layout.bounds; const bounds = this.layout.bounds;
this.targetZoom = getAutoZoom(bounds, this.width, this.height); this.targetZoom = getAutoZoom(bounds, this.width, this.height);
// Auto-center on the chain
const chainCenterX = (bounds.minX + bounds.maxX) / 2;
const chainCenterY = (bounds.minY + bounds.maxY) / 2;
this.targetPan = {
x: chainCenterX - this.width / 2,
y: chainCenterY - this.height / 2
};
} }
this._animateZoom(); this._startSmoothTransition();
} }
_animateZoom() { _startSmoothTransition() {
if (this._zoomAnimFrame) cancelAnimationFrame(this._zoomAnimFrame); if (this._isAnimating) return;
const animate = () => { this._isAnimating = true;
const diff = this.targetZoom - this.zoom; this._animate();
if (Math.abs(diff) < 0.005) { }
this.zoom = this.targetZoom;
this.draw(); _animate() {
return; const panDiffX = this.targetPan.x - this.pan.x;
} const panDiffY = this.targetPan.y - this.pan.y;
this.zoom += diff * 0.08; const zoomDiff = this.targetZoom - this.zoom;
const threshold = 0.003;
const settled = Math.abs(panDiffX) < 0.5 && Math.abs(panDiffY) < 0.5 && Math.abs(zoomDiff) < threshold;
if (settled) {
this.pan.x = this.targetPan.x;
this.pan.y = this.targetPan.y;
this.zoom = this.targetZoom;
this._isAnimating = false;
this.draw(); this.draw();
this._zoomAnimFrame = requestAnimationFrame(animate); return;
}; }
animate();
// Smooth lerp (ease-out feel)
const speed = 0.1;
this.pan.x += panDiffX * speed;
this.pan.y += panDiffY * speed;
this.zoom += zoomDiff * speed;
this.draw();
this._animFrame = requestAnimationFrame(() => this._animate());
} }
setGhost(tile, end, valid) { setGhost(tile, end, valid) {
...@@ -70,7 +98,6 @@ export class DominoBoard { ...@@ -70,7 +98,6 @@ export class DominoBoard {
const ep = end === 'left' ? this.layout.endpoints.left : this.layout.endpoints.right; const ep = end === 'left' ? this.layout.endpoints.left : this.layout.endpoints.right;
if (!ep) { this.ghost = null; this.draw(); return; } if (!ep) { this.ghost = null; this.draw(); return; }
// Ghost matches the orientation it would have when placed
const dbl = tile.left === tile.right; const dbl = tile.left === tile.right;
const lastTileIdx = end === 'left' ? 0 : this.layout.tiles.length - 1; const lastTileIdx = end === 'left' ? 0 : this.layout.tiles.length - 1;
const lt = this.layout.tiles[lastTileIdx]; const lt = this.layout.tiles[lastTileIdx];
...@@ -124,7 +151,6 @@ export class DominoBoard { ...@@ -124,7 +151,6 @@ export class DominoBoard {
const canvasX = (screenX - rect.left) * (this.width / rect.width); const canvasX = (screenX - rect.left) * (this.width / rect.width);
const canvasY = (screenY - rect.top) * (this.height / rect.height); const canvasY = (screenY - rect.top) * (this.height / rect.height);
// Convert screen coords to world coords (accounting for pan/zoom)
const cx = this.width / 2 + this.pan.x; const cx = this.width / 2 + this.pan.x;
const cy = this.height / 2 + this.pan.y; const cy = this.height / 2 + this.pan.y;
const worldX = (canvasX - this.width / 2) / this.zoom + cx; const worldX = (canvasX - this.width / 2) / this.zoom + cx;
...@@ -134,12 +160,10 @@ export class DominoBoard { ...@@ -134,12 +160,10 @@ export class DominoBoard {
const rightEp = this.layout.endpoints.right; const rightEp = this.layout.endpoints.right;
const radius = getSnapRadius(); const radius = getSnapRadius();
// Empty board: tap anywhere on canvas = center placement
if (!leftEp && !rightEp) { if (!leftEp && !rightEp) {
return { end: 'right', value: null }; return { end: 'right', value: null };
} }
// Check which endpoints are active
const checkLeft = this.activeEndpoint === 'left' || this.activeEndpoint === 'both'; const checkLeft = this.activeEndpoint === 'left' || this.activeEndpoint === 'both';
const checkRight = this.activeEndpoint === 'right' || this.activeEndpoint === 'both'; const checkRight = this.activeEndpoint === 'right' || this.activeEndpoint === 'both';
...@@ -172,18 +196,20 @@ export class DominoBoard { ...@@ -172,18 +196,20 @@ export class DominoBoard {
const startX = this.width / 2; const startX = this.width / 2;
const startY = this.height + 40; const startY = this.height + 40;
const duration = 350; const duration = 320;
const startTime = performance.now(); const startTime = performance.now();
const animate = (now) => { const animate = (now) => {
const t = Math.min((now - startTime) / duration, 1); const t = Math.min((now - startTime) / duration, 1);
// Ease out cubic
const ease = 1 - Math.pow(1 - t, 3); const ease = 1 - Math.pow(1 - t, 3);
const x = startX + (ep.x - startX) * ease; const x = startX + (ep.x - startX) * ease;
const y = startY + (ep.y - startY) * ease; const y = startY + (ep.y - startY) * ease;
const scale = 1.4 + (1 - 1.4) * ease; const scale = 1.3 + (1 - 1.3) * ease;
const alpha = 0.4 + 0.6 * ease;
this.animatingTile = { tile, x, y, w, h, rotation: 0, scale }; this.animatingTile = { tile, x, y, w, h, rotation: 0, scale, alpha };
this.draw(); this.draw();
if (t < 1) { if (t < 1) {
...@@ -200,8 +226,18 @@ export class DominoBoard { ...@@ -200,8 +226,18 @@ export class DominoBoard {
const ctx = this.ctx; const ctx = this.ctx;
clear(ctx, this.width, this.height); clear(ctx, this.width, this.height);
// Dark background matching app theme // Board background with subtle felt texture
ctx.fillStyle = '#0f1623'; ctx.fillStyle = '#0b1520';
ctx.fillRect(0, 0, this.width, this.height);
// Vignette
const vg = ctx.createRadialGradient(
this.width/2, this.height/2, this.height * 0.3,
this.width/2, this.height/2, this.height * 0.7
);
vg.addColorStop(0, 'transparent');
vg.addColorStop(1, 'rgba(0,0,0,0.3)');
ctx.fillStyle = vg;
ctx.fillRect(0, 0, this.width, this.height); ctx.fillRect(0, 0, this.width, this.height);
// Subtle pattern // Subtle pattern
...@@ -216,8 +252,7 @@ export class DominoBoard { ...@@ -216,8 +252,7 @@ export class DominoBoard {
ctx.translate(-cx - this.pan.x, -cy - this.pan.y); ctx.translate(-cx - this.pan.x, -cy - this.pan.y);
if (this.chain.length === 0) { if (this.chain.length === 0) {
// Empty board indicator ctx.fillStyle = 'rgba(255,255,255,0.06)';
ctx.fillStyle = 'rgba(255,255,255,0.08)';
ctx.font = '600 13px system-ui, sans-serif'; ctx.font = '600 13px system-ui, sans-serif';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
...@@ -232,10 +267,10 @@ export class DominoBoard { ...@@ -232,10 +267,10 @@ export class DominoBoard {
drawTile(ctx, lt.x, lt.y, lt.w, lt.h, lt.tile, { rotation: 0 }); drawTile(ctx, lt.x, lt.y, lt.w, lt.h, lt.tile, { rotation: 0 });
} }
// Draw endpoint indicators (when active for tap-to-confirm) // Endpoint indicators
this._drawEndpointIndicators(ctx); this._drawEndpointIndicators(ctx);
// Ghost tile (during drag) // Ghost tile
if (this.ghost) { if (this.ghost) {
drawTile(ctx, this.ghost.x, this.ghost.y, this.ghost.w, this.ghost.h, drawTile(ctx, this.ghost.x, this.ghost.y, this.ghost.w, this.ghost.h,
this.ghost.tile, { rotation: this.ghost.rotation, ghost: true, invalid: !this.ghost.valid }); this.ghost.tile, { rotation: this.ghost.rotation, ghost: true, invalid: !this.ghost.valid });
...@@ -245,6 +280,7 @@ export class DominoBoard { ...@@ -245,6 +280,7 @@ export class DominoBoard {
if (this.animatingTile) { if (this.animatingTile) {
const at = this.animatingTile; const at = this.animatingTile;
ctx.save(); ctx.save();
ctx.globalAlpha = at.alpha ?? 1;
ctx.translate(at.x, at.y); ctx.translate(at.x, at.y);
ctx.scale(at.scale, at.scale); ctx.scale(at.scale, at.scale);
ctx.translate(-at.x, -at.y); ctx.translate(-at.x, -at.y);
...@@ -256,12 +292,11 @@ export class DominoBoard { ...@@ -256,12 +292,11 @@ export class DominoBoard {
} }
_drawPattern(ctx) { _drawPattern(ctx) {
// Subtle grid dots ctx.fillStyle = 'rgba(255,255,255,0.015)';
ctx.fillStyle = 'rgba(255,255,255,0.02)'; for (let i = 0; i < this.width; i += 24) {
for (let i = 0; i < this.width; i += 20) { for (let j = 0; j < this.height; j += 24) {
for (let j = 0; j < this.height; j += 20) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(i, j, 1, 0, Math.PI * 2); ctx.arc(i, j, 0.8, 0, Math.PI * 2);
ctx.fill(); ctx.fill();
} }
} }
...@@ -305,6 +340,8 @@ export class DominoBoard { ...@@ -305,6 +340,8 @@ export class DominoBoard {
isPanning = true; isPanning = true;
this.pan.x = lastPanX - dx / this.zoom; this.pan.x = lastPanX - dx / this.zoom;
this.pan.y = lastPanY - dy / this.zoom; this.pan.y = lastPanY - dy / this.zoom;
this.targetPan.x = this.pan.x;
this.targetPan.y = this.pan.y;
this.draw(); this.draw();
} }
}); });
...@@ -325,7 +362,7 @@ export class DominoBoard { ...@@ -325,7 +362,7 @@ export class DominoBoard {
} }
destroy() { destroy() {
if (this._zoomAnimFrame) cancelAnimationFrame(this._zoomAnimFrame); if (this._animFrame) cancelAnimationFrame(this._animFrame);
this.canvas.remove(); this.canvas.remove();
} }
} }
......
const TILE_BG = '#F5F0E8'; const TILE_BG = '#FFFDF5';
const TILE_BORDER = '#8B7355'; const TILE_BG_END = '#F2ECE0';
const TILE_BORDER = '#9B8360';
const PIP_COLOR = '#1a1a1a'; const PIP_COLOR = '#1a1a1a';
const DOUBLE_ACCENT = '#EDE5D4'; const DOUBLE_ACCENT = '#F5EFE0';
const PIP_POSITIONS = { const PIP_POSITIONS = {
0: [], 0: [],
...@@ -23,74 +24,95 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) { ...@@ -23,74 +24,95 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) {
const hw = w / 2; const hw = w / 2;
const hh = h / 2; const hh = h / 2;
const r = 3; const r = 3.5;
// Shadow // Shadow
if (!ghost) { if (!ghost) {
ctx.shadowColor = 'rgba(0,0,0,0.4)'; ctx.shadowColor = 'rgba(0,0,0,0.45)';
ctx.shadowBlur = 3; ctx.shadowBlur = 4;
ctx.shadowOffsetY = 1; ctx.shadowOffsetY = 2;
ctx.shadowOffsetX = 1;
} }
if (highlight) { if (highlight) {
ctx.shadowColor = invalid ? 'rgba(239,68,68,0.7)' : 'rgba(228,172,56,0.7)'; ctx.shadowColor = invalid ? 'rgba(239,68,68,0.7)' : 'rgba(228,172,56,0.7)';
ctx.shadowBlur = 12; ctx.shadowBlur = 14;
} }
// Tile body // Tile body with gradient
ctx.beginPath(); ctx.beginPath();
ctx.roundRect(-hw, -hh, w, h, r); ctx.roundRect(-hw, -hh, w, h, r);
ctx.fillStyle = ghost ? (invalid ? 'rgba(239,68,68,0.15)' : 'rgba(228,172,56,0.2)') : TILE_BG; if (ghost) {
ctx.fillStyle = invalid ? 'rgba(239,68,68,0.15)' : 'rgba(228,172,56,0.2)';
} else {
const grad = ctx.createLinearGradient(-hw, -hh, hw, hh);
grad.addColorStop(0, TILE_BG);
grad.addColorStop(1, TILE_BG_END);
ctx.fillStyle = grad;
}
ctx.fill(); ctx.fill();
// Reset shadow before stroke
ctx.shadowColor = 'transparent'; ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
ctx.shadowOffsetY = 0; ctx.shadowOffsetY = 0;
ctx.shadowOffsetX = 0;
// Border
ctx.strokeStyle = ghost ? (invalid ? '#ef4444' : '#E4AC38') : TILE_BORDER; ctx.strokeStyle = ghost ? (invalid ? '#ef4444' : '#E4AC38') : TILE_BORDER;
ctx.lineWidth = ghost ? 1.5 : 1; ctx.lineWidth = ghost ? 1.5 : 1.2;
ctx.stroke(); ctx.stroke();
// Inner highlight (top edge shine)
if (!ghost) {
ctx.beginPath();
ctx.moveTo(-hw + r + 1, -hh + 1.2);
ctx.lineTo(hw - r - 1, -hh + 1.2);
ctx.strokeStyle = 'rgba(255,255,255,0.6)';
ctx.lineWidth = 0.8;
ctx.stroke();
}
if (!ghost) { if (!ghost) {
const isDouble = tile.left === tile.right; const isDouble = tile.left === tile.right;
const isLandscape = w > h; // wider than tall = landscape orientation const isLandscape = w > h;
if (isDouble) { if (isDouble) {
ctx.fillStyle = DOUBLE_ACCENT; ctx.fillStyle = DOUBLE_ACCENT;
ctx.beginPath(); ctx.beginPath();
ctx.roundRect(-hw + 1, -hh + 1, w - 2, h - 2, r - 1); ctx.roundRect(-hw + 1.5, -hh + 1.5, w - 3, h - 3, r - 1);
ctx.fill(); ctx.fill();
ctx.strokeStyle = TILE_BORDER; ctx.strokeStyle = TILE_BORDER;
ctx.lineWidth = 1; ctx.lineWidth = 1.2;
ctx.beginPath(); ctx.beginPath();
ctx.roundRect(-hw, -hh, w, h, r); ctx.roundRect(-hw, -hh, w, h, r);
ctx.stroke(); ctx.stroke();
} }
if (isLandscape) { if (isLandscape) {
// Tile is wider than tall → left half = tile.left, right half = tile.right
// Divider: vertical line at center // Divider: vertical line at center
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(0, -hh + 2); ctx.moveTo(0, -hh + 3);
ctx.lineTo(0, hh - 2); ctx.lineTo(0, hh - 3);
ctx.strokeStyle = '#B8A88A'; ctx.strokeStyle = 'rgba(155,131,96,0.5)';
ctx.lineWidth = 0.8; ctx.lineWidth = 0.8;
ctx.stroke(); ctx.stroke();
const spread = Math.min(hh * 0.6, 5.5); const spread = Math.min(hh * 0.55, 6);
const pipR = Math.min(2.2, hh * 0.12); const pipR = Math.min(2.5, hh * 0.14);
drawPips(ctx, -hw / 2, 0, spread, pipR, tile.left); drawPips(ctx, -hw / 2, 0, spread, pipR, tile.left);
drawPips(ctx, hw / 2, 0, spread, pipR, tile.right); drawPips(ctx, hw / 2, 0, spread, pipR, tile.right);
} else { } else {
// Tile is taller than wide → top half = tile.left, bottom half = tile.right
// Divider: horizontal line at center // Divider: horizontal line at center
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(-hw + 2, 0); ctx.moveTo(-hw + 3, 0);
ctx.lineTo(hw - 2, 0); ctx.lineTo(hw - 3, 0);
ctx.strokeStyle = '#B8A88A'; ctx.strokeStyle = 'rgba(155,131,96,0.5)';
ctx.lineWidth = 0.8; ctx.lineWidth = 0.8;
ctx.stroke(); ctx.stroke();
const spread = Math.min(hw * 0.6, 5.5); const spread = Math.min(hw * 0.55, 6);
const pipR = Math.min(2.2, hw * 0.12); const pipR = Math.min(2.5, hw * 0.14);
drawPips(ctx, 0, -hh / 2, spread, pipR, tile.left); drawPips(ctx, 0, -hh / 2, spread, pipR, tile.left);
drawPips(ctx, 0, hh / 2, spread, pipR, tile.right); drawPips(ctx, 0, hh / 2, spread, pipR, tile.right);
} }
...@@ -101,19 +123,29 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) { ...@@ -101,19 +123,29 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) {
function drawPips(ctx, cx, cy, spread, radius, value) { function drawPips(ctx, cx, cy, spread, radius, value) {
const positions = PIP_POSITIONS[value] || []; const positions = PIP_POSITIONS[value] || [];
ctx.fillStyle = PIP_COLOR;
for (const [px, py] of positions) { for (const [px, py] of positions) {
const x = cx + px * spread;
const y = cy + py * spread;
// Pip with subtle 3D effect
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = PIP_COLOR;
ctx.fill();
// Specular highlight
ctx.beginPath(); ctx.beginPath();
ctx.arc(cx + px * spread, cy + py * spread, radius, 0, Math.PI * 2); ctx.arc(x - radius * 0.25, y - radius * 0.25, radius * 0.35, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.fill(); ctx.fill();
} }
} }
export function drawEndpointGlow(ctx, x, y, radius, valid) { export function drawEndpointGlow(ctx, x, y, radius, valid) {
const color = valid ? 'rgba(228,172,56,0.45)' : 'rgba(239,68,68,0.4)'; const color = valid ? 'rgba(228,172,56,0.4)' : 'rgba(239,68,68,0.35)';
const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius); const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, color); gradient.addColorStop(0, color);
gradient.addColorStop(0.5, valid ? 'rgba(228,172,56,0.12)' : 'rgba(239,68,68,0.08)'); gradient.addColorStop(0.4, valid ? 'rgba(228,172,56,0.12)' : 'rgba(239,68,68,0.08)');
gradient.addColorStop(1, 'transparent'); gradient.addColorStop(1, 'transparent');
ctx.beginPath(); ctx.beginPath();
...@@ -125,16 +157,16 @@ export function drawEndpointGlow(ctx, x, y, radius, valid) { ...@@ -125,16 +157,16 @@ export function drawEndpointGlow(ctx, x, y, radius, valid) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2); ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fillStyle = valid ? '#E4AC38' : '#ef4444'; ctx.fillStyle = valid ? '#E4AC38' : '#ef4444';
ctx.globalAlpha = 0.85; ctx.globalAlpha = 0.8;
ctx.fill(); ctx.fill();
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
// Dashed ring // Animated-feel dashed ring
ctx.beginPath(); ctx.beginPath();
ctx.arc(x, y, radius * 0.55, 0, Math.PI * 2); ctx.arc(x, y, radius * 0.5, 0, Math.PI * 2);
ctx.strokeStyle = valid ? 'rgba(228,172,56,0.35)' : 'rgba(239,68,68,0.3)'; ctx.strokeStyle = valid ? 'rgba(228,172,56,0.3)' : 'rgba(239,68,68,0.25)';
ctx.lineWidth = 1.5; ctx.lineWidth = 1.5;
ctx.setLineDash([4, 4]); ctx.setLineDash([5, 5]);
ctx.stroke(); ctx.stroke();
ctx.setLineDash([]); ctx.setLineDash([]);
} }
...@@ -2,6 +2,16 @@ import * as audio from '../../../core/audio.js'; ...@@ -2,6 +2,16 @@ import * as audio from '../../../core/audio.js';
import * as juice from '../../../core/juice.js'; import * as juice from '../../../core/juice.js';
import { canPlay } from '../logic/rules.js'; import { canPlay } from '../logic/rules.js';
const PIP_POSITIONS = {
0: [],
1: [[0, 0]],
2: [[-1, -1], [1, 1]],
3: [[-1, -1], [0, 0], [1, 1]],
4: [[-1, -1], [1, -1], [-1, 1], [1, 1]],
5: [[-1, -1], [1, -1], [0, 0], [-1, 1], [1, 1]],
6: [[-1, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [1, 1]]
};
export class DominoDrag { export class DominoDrag {
constructor(options = {}) { constructor(options = {}) {
this.board = options.board; this.board = options.board;
...@@ -40,33 +50,29 @@ export class DominoDrag { ...@@ -40,33 +50,29 @@ export class DominoDrag {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'drag-proxy'; el.className = 'drag-proxy';
el.innerHTML = ` el.innerHTML = `
<div style="width:40px;height:70px;background:#fffff0;border:2px solid #10b981;border-radius:6px; <div class="drag-tile">
display:flex;flex-direction:column;align-items:center;justify-content:space-between;padding:5px 4px; <div class="drag-half">${this._renderPips(tile.left)}</div>
box-shadow:0 8px 24px rgba(0,0,0,0.4);transform:scale(1.1);"> <div class="drag-divider"></div>
<div style="display:flex;flex-wrap:wrap;width:22px;height:22px;align-items:center;justify-content:center;gap:1px;"> <div class="drag-half">${this._renderPips(tile.right)}</div>
${this._renderPips(tile.left)}
</div>
<div style="width:80%;height:1px;background:#bbb;"></div>
<div style="display:flex;flex-wrap:wrap;width:22px;height:22px;align-items:center;justify-content:center;gap:1px;">
${this._renderPips(tile.right)}
</div>
</div> </div>
`; `;
el.style.cssText = ` el.style.cssText = `
position:fixed;z-index:9999;pointer-events:none; position:fixed;z-index:9999;pointer-events:none;
left:${x - 20}px;top:${y - 35}px; left:${x - 24}px;top:${y - 42}px;
transition:transform 0.1s;
`; `;
document.body.appendChild(el); document.body.appendChild(el);
this.proxyEl = el; this.proxyEl = el;
} }
_renderPips(value) { _renderPips(value) {
let s = ''; const positions = PIP_POSITIONS[value] || [];
for (let i = 0; i < value; i++) { const spread = 7;
s += '<span style="width:5px;height:5px;border-radius:50%;background:#1a1a1a;"></span>'; const pipSize = 4.5;
} return positions.map(([px, py]) => {
return s; const cx = 20 + px * spread;
const cy = 18 + py * spread;
return `<span class="drag-pip" style="left:${cx - pipSize/2}px;top:${cy - pipSize/2}px;width:${pipSize}px;height:${pipSize}px;"></span>`;
}).join('');
} }
_onMove(e) { _onMove(e) {
...@@ -74,36 +80,36 @@ export class DominoDrag { ...@@ -74,36 +80,36 @@ export class DominoDrag {
const x = e.clientX; const x = e.clientX;
const y = e.clientY; const y = e.clientY;
this.proxyEl.style.left = (x - 20) + 'px'; this.proxyEl.style.left = (x - 24) + 'px';
this.proxyEl.style.top = (y - 35) + 'px'; this.proxyEl.style.top = (y - 42) + 'px';
const hit = this.board.hitTestEndpoints(x, y); const hit = this.board.hitTestEndpoints(x, y);
const { leftEnd, rightEnd } = this.getEnds();
if (hit) { if (hit) {
const endValue = hit.value; const endValue = hit.value;
const tileMatches = this.tile.left === endValue || this.tile.right === endValue; const tileMatches = this.tile.left === endValue || this.tile.right === endValue;
const tileEl = this.proxyEl.querySelector('.drag-tile');
if (tileMatches) { if (tileMatches) {
this.nearEnd = hit.end; this.nearEnd = hit.end;
this.isValid = true; this.isValid = true;
this.board.setGhost(this.tile, hit.end, true); this.board.setGhost(this.tile, hit.end, true);
this.proxyEl.style.transform = 'scale(1.15)'; tileEl.classList.add('drag-valid');
this.proxyEl.querySelector('div').style.borderColor = '#10b981'; tileEl.classList.remove('drag-invalid');
} else { } else {
this.nearEnd = hit.end; this.nearEnd = hit.end;
this.isValid = false; this.isValid = false;
this.board.setGhost(this.tile, hit.end, false); this.board.setGhost(this.tile, hit.end, false);
this.proxyEl.style.transform = 'scale(0.95)'; tileEl.classList.remove('drag-valid');
this.proxyEl.querySelector('div').style.borderColor = '#ef4444'; tileEl.classList.add('drag-invalid');
} }
} else { } else {
if (this.nearEnd) { if (this.nearEnd) {
this.board.clearGhost(); this.board.clearGhost();
this.nearEnd = null; this.nearEnd = null;
this.isValid = false; this.isValid = false;
this.proxyEl.style.transform = 'scale(1.1)'; const tileEl = this.proxyEl.querySelector('.drag-tile');
this.proxyEl.querySelector('div').style.borderColor = '#10b981'; tileEl.classList.remove('drag-valid', 'drag-invalid');
} }
} }
} }
...@@ -117,11 +123,10 @@ export class DominoDrag { ...@@ -117,11 +123,10 @@ export class DominoDrag {
document.removeEventListener('pointercancel', this._onUp); document.removeEventListener('pointercancel', this._onUp);
document.removeEventListener('touchend', this._onUp); document.removeEventListener('touchend', this._onUp);
// Re-check hit from proxy position (touchend may not update pointer coords)
if (!this.nearEnd && this.proxyEl) { if (!this.nearEnd && this.proxyEl) {
const left = parseInt(this.proxyEl.style.left) || 0; const left = parseInt(this.proxyEl.style.left) || 0;
const top = parseInt(this.proxyEl.style.top) || 0; const top = parseInt(this.proxyEl.style.top) || 0;
const hit = this.board.hitTestEndpoints(left + 20, top + 35); const hit = this.board.hitTestEndpoints(left + 24, top + 42);
if (hit) { if (hit) {
const tileMatches = this.tile.left === hit.value || this.tile.right === hit.value; const tileMatches = this.tile.left === hit.value || this.tile.right === hit.value;
if (tileMatches) { if (tileMatches) {
...@@ -155,7 +160,7 @@ export class DominoDrag { ...@@ -155,7 +160,7 @@ export class DominoDrag {
if (!this.proxyEl) { callback(); return; } if (!this.proxyEl) { callback(); return; }
this.proxyEl.style.transition = 'all 0.15s cubic-bezier(0.16,1,0.3,1)'; this.proxyEl.style.transition = 'all 0.15s cubic-bezier(0.16,1,0.3,1)';
this.proxyEl.style.opacity = '0'; this.proxyEl.style.opacity = '0';
this.proxyEl.style.transform = 'scale(0.5)'; this.proxyEl.querySelector('.drag-tile').style.transform = 'scale(0.5)';
setTimeout(callback, 150); setTimeout(callback, 150);
} }
...@@ -163,7 +168,7 @@ export class DominoDrag { ...@@ -163,7 +168,7 @@ export class DominoDrag {
if (!this.proxyEl) { callback(); return; } if (!this.proxyEl) { callback(); return; }
this.proxyEl.style.transition = 'all 0.25s cubic-bezier(0.34,1.56,0.64,1)'; this.proxyEl.style.transition = 'all 0.25s cubic-bezier(0.34,1.56,0.64,1)';
this.proxyEl.style.opacity = '0'; this.proxyEl.style.opacity = '0';
this.proxyEl.style.transform = 'translateY(40px) scale(0.7)'; this.proxyEl.querySelector('.drag-tile').style.transform = 'translateY(40px) scale(0.7)';
setTimeout(callback, 250); setTimeout(callback, 250);
} }
...@@ -178,7 +183,51 @@ export class DominoDrag { ...@@ -178,7 +183,51 @@ export class DominoDrag {
} }
getStyle() { getStyle() {
return `.drag-proxy { will-change: transform, left, top; }`; return `
.drag-proxy { will-change: transform, left, top; }
.drag-tile {
width: 48px;
height: 84px;
background: linear-gradient(160deg, #FFFDF5 0%, #F5EFE0 100%);
border: 2px solid #E4AC38;
border-radius: 7px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
box-shadow: 0 10px 30px rgba(0,0,0,0.5), 0 0 20px rgba(228,172,56,0.3);
transform: scale(1.15);
transition: transform 0.15s cubic-bezier(0.34,1.56,0.64,1),
border-color 0.15s, box-shadow 0.15s;
}
.drag-tile.drag-valid {
border-color: #10b981;
transform: scale(1.2);
box-shadow: 0 10px 30px rgba(0,0,0,0.5), 0 0 20px rgba(16,185,129,0.4);
}
.drag-tile.drag-invalid {
border-color: #ef4444;
transform: scale(0.95);
box-shadow: 0 6px 20px rgba(0,0,0,0.5), 0 0 12px rgba(239,68,68,0.3);
}
.drag-half {
position: relative;
width: 100%;
flex: 1;
}
.drag-divider {
width: 78%;
height: 1.5px;
background: linear-gradient(90deg, transparent, #B8A080, transparent);
flex-shrink: 0;
}
.drag-pip {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #3a3a3a, #0a0a0a);
box-shadow: inset 0 1px 1px rgba(255,255,255,0.15);
}
`;
} }
destroy() { destroy() {
......
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
const PIP_POSITIONS = {
0: [],
1: [[0, 0]],
2: [[-1, -1], [1, 1]],
3: [[-1, -1], [0, 0], [1, 1]],
4: [[-1, -1], [1, -1], [-1, 1], [1, 1]],
5: [[-1, -1], [1, -1], [0, 0], [-1, 1], [1, 1]],
6: [[-1, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [1, 1]]
};
export class DominoHand { export class DominoHand {
constructor(container, options = {}) { constructor(container, options = {}) {
this.container = container; this.container = container;
...@@ -10,14 +20,14 @@ export class DominoHand { ...@@ -10,14 +20,14 @@ export class DominoHand {
this.validTileIds = new Set(); this.validTileIds = new Set();
this.selectedTileId = null; this.selectedTileId = null;
this.disabled = false; this.disabled = false;
this._prevTileIds = [];
container.style.cssText = ` container.style.cssText = `
display:flex;gap:8px;padding:10px 12px;overflow-x:auto; display:flex;gap:0;padding:8px 6px;
background:#0f0f1e; background:linear-gradient(180deg, #0c1a2a 0%, #0a1420 100%);
border-top:1px solid rgba(255,255,255,0.06); border-top:1px solid rgba(228,172,56,0.1);
min-height:96px;align-items:center;justify-content:center; min-height:100px;align-items:flex-end;justify-content:center;
flex-wrap:wrap;scroll-snap-type:x proximity; overflow:visible;position:relative;
-webkit-overflow-scrolling:touch;
`; `;
} }
...@@ -44,30 +54,79 @@ export class DominoHand { ...@@ -44,30 +54,79 @@ export class DominoHand {
render() { render() {
const hand = this.tiles; const hand = this.tiles;
const count = hand.length;
// Smart sizing: scale tiles to always fit viewport
const containerW = this.container.clientWidth || 360;
const maxTileW = 44;
const minTileW = 30;
const idealGap = 6;
const maxHandW = containerW - 16;
// Calculate tile width to fit all tiles with gaps
let tileW = Math.min(maxTileW, (maxHandW - idealGap * (count - 1)) / count);
tileW = Math.max(minTileW, tileW);
const tileH = tileW * 1.8;
const gap = Math.min(idealGap, (maxHandW - tileW * count) / Math.max(count - 1, 1));
// Fan angle for many tiles (like holding cards)
const useFan = count > 5;
const maxFanAngle = 2.5; // degrees per tile from center
const maxLift = 4; // px arc lift at center
const newIds = hand.map(t => t.id);
const addedIds = new Set(newIds.filter(id => !this._prevTileIds.includes(id)));
this._prevTileIds = newIds;
this.container.innerHTML = hand.map((tile, i) => { this.container.innerHTML = hand.map((tile, i) => {
const playable = this.validTileIds.has(tile.id); const playable = this.validTileIds.has(tile.id);
const selected = this.selectedTileId === tile.id; const selected = this.selectedTileId === tile.id;
const isNew = addedIds.has(tile.id);
// Fan geometry
let angle = 0, lift = 0;
if (useFan) {
const center = (count - 1) / 2;
const offset = i - center;
angle = offset * maxFanAngle;
lift = -Math.abs(offset) * (maxLift / (count / 2));
}
const transform = selected
? `translateY(-16px) scale(1.12)`
: `rotate(${angle}deg) translateY(${lift}px)`;
const animDelay = isNew ? `animation:tileSlideIn 0.3s cubic-bezier(0.34,1.56,0.64,1) both;` : '';
return ` return `
<div class="dh-tile ${playable ? 'dh-playable' : ''} ${selected ? 'dh-selected' : ''}" <div class="dh-tile ${playable ? 'dh-playable' : ''} ${selected ? 'dh-selected' : ''}"
data-id="${tile.id}" data-idx="${i}" data-id="${tile.id}" data-idx="${i}"
style="scroll-snap-align:center;${this.disabled ? 'pointer-events:none;' : ''}"> style="width:${tileW}px;height:${tileH}px;transform:${transform};margin:0 ${gap/2}px;${this.disabled ? 'pointer-events:none;' : ''}${animDelay}">
<div class="dh-top">${this._renderPipGrid(tile.left)}</div> <div class="dh-half dh-top">
${this._renderPips(tile.left, tileW)}
</div>
<div class="dh-divider"></div> <div class="dh-divider"></div>
<div class="dh-bottom">${this._renderPipGrid(tile.right)}</div> <div class="dh-half dh-bottom">
${this._renderPips(tile.right, tileW)}
</div>
</div>`; </div>`;
}).join(''); }).join('');
this._bindEvents(); this._bindEvents();
} }
_renderPipGrid(value) { _renderPips(value, tileW) {
if (value === 0) return ''; if (value === 0) return '';
let dots = ''; const positions = PIP_POSITIONS[value] || [];
for (let i = 0; i < value; i++) { const halfW = tileW / 2;
dots += '<span class="dh-pip"></span>'; const spread = halfW * 0.45;
} const pipSize = Math.max(3.5, tileW * 0.1);
return dots;
return positions.map(([px, py]) => {
const cx = halfW + px * spread;
const cy = halfW * 0.82 + py * spread;
return `<span class="dh-pip" style="left:${cx - pipSize/2}px;top:${cy - pipSize/2}px;width:${pipSize}px;height:${pipSize}px;"></span>`;
}).join('');
} }
_bindEvents() { _bindEvents() {
...@@ -118,51 +177,74 @@ export class DominoHand { ...@@ -118,51 +177,74 @@ export class DominoHand {
getStyle() { getStyle() {
return ` return `
.dh-tile { .dh-tile {
width:34px;height:68px; background: linear-gradient(160deg, #FFFDF5 0%, #F5EFE0 100%);
aspect-ratio:1/2; border: 1.5px solid #C4AD82;
background:#F5F0E8; border-radius: 6px;
border:2px solid rgba(255,255,255,0.15); display: flex;
border-radius:6px; flex-direction: column;
display:flex;flex-direction:column; align-items: center;
align-items:center;justify-content:space-between; justify-content: space-between;
padding:5px 4px; padding: 0;
cursor:pointer; cursor: pointer;
transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1), transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
border-color 0.15s,opacity 0.15s,box-shadow 0.15s; border-color 0.2s, opacity 0.2s, box-shadow 0.2s,
opacity:0.4; filter 0.2s;
user-select:none; opacity: 0.45;
touch-action:none; user-select: none;
flex-shrink:0; touch-action: none;
box-shadow:0 2px 6px rgba(0,0,0,0.3); flex-shrink: 0;
box-shadow: 0 3px 8px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.7);
position: relative;
transform-origin: center bottom;
filter: brightness(0.7);
} }
.dh-tile.dh-playable { .dh-tile.dh-playable {
opacity:1; opacity: 1;
border-color:#E4AC38; border-color: #E4AC38;
box-shadow:0 0 8px rgba(228,172,56,0.25); box-shadow: 0 3px 8px rgba(0,0,0,0.5), 0 0 12px rgba(228,172,56,0.3),
inset 0 1px 0 rgba(255,255,255,0.7);
filter: brightness(1);
}
.dh-tile.dh-playable:active {
transform: translateY(-4px) scale(0.96) !important;
} }
.dh-tile.dh-playable:active { transform:scale(0.94); }
.dh-tile.dh-selected { .dh-tile.dh-selected {
transform:translateY(-12px) scale(1.08); transform: translateY(-16px) scale(1.12) !important;
border-color:#E4AC38; border-color: #E4AC38;
box-shadow:0 6px 20px rgba(228,172,56,0.5); box-shadow: 0 8px 24px rgba(228,172,56,0.5), 0 0 20px rgba(228,172,56,0.3),
inset 0 1px 0 rgba(255,255,255,0.7);
z-index: 10;
filter: brightness(1.05);
} }
@keyframes tileGlow { @keyframes tileGlow {
0%,100% { box-shadow:0 0 6px rgba(228,172,56,0.2); } 0%, 100% { box-shadow: 0 3px 8px rgba(0,0,0,0.5), 0 0 8px rgba(228,172,56,0.2), inset 0 1px 0 rgba(255,255,255,0.7); }
50% { box-shadow:0 0 14px rgba(228,172,56,0.45); } 50% { box-shadow: 0 3px 8px rgba(0,0,0,0.5), 0 0 16px rgba(228,172,56,0.4), inset 0 1px 0 rgba(255,255,255,0.7); }
}
.dh-tile.dh-playable:not(.dh-selected) { animation: tileGlow 2s ease-in-out infinite; }
.dh-tile.dh-selected { animation: none; }
@keyframes tileSlideIn {
from { transform: translateY(40px) scale(0.6); opacity: 0; }
to { opacity: 1; }
}
.dh-half {
position: relative;
width: 100%;
flex: 1;
} }
.dh-tile.dh-playable { animation:tileGlow 2s ease-in-out infinite; } .dh-divider {
.dh-tile.dh-selected { animation:none; } width: 78%;
.dh-divider { width:80%;height:1px;background:#B8A88A;flex-shrink:0; } height: 1.5px;
.dh-top, .dh-bottom { background: linear-gradient(90deg, transparent, #B8A080, transparent);
display:flex;flex-wrap:wrap; flex-shrink: 0;
width:24px;height:28px; margin: 0 auto;
align-items:center;justify-content:center;
gap:1px;
} }
.dh-pip { .dh-pip {
width:5px;height:5px; position: absolute;
border-radius:50%; border-radius: 50%;
background:#1a1a1a; background: radial-gradient(circle at 35% 35%, #3a3a3a, #0a0a0a);
box-shadow: inset 0 1px 1px rgba(255,255,255,0.15), 0 0.5px 1px rgba(0,0,0,0.3);
} }
`; `;
} }
......
...@@ -105,62 +105,62 @@ function dealNewRound() { ...@@ -105,62 +105,62 @@ function dealNewRound() {
function buildLayout(mode) { function buildLayout(mode) {
const isLive = mode === 'live'; const isLive = mode === 'live';
return ` return `
<div id="domino-wrap" style="display:flex;flex-direction:column;height:100%;background:#0a0a14;position:relative;"> <div id="domino-wrap" style="display:flex;flex-direction:column;height:100%;background:linear-gradient(180deg,#070d14 0%,#0a1420 100%);position:relative;overflow:hidden;">
<!-- Opponent bar --> <!-- Opponent bar -->
<div id="domino-opp-bar" style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div id="domino-opp-bar" style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(15,20,32,0.95);border-bottom:1px solid rgba(228,172,56,0.08);backdrop-filter:blur(8px);z-index:5;">
<div style="display:flex;align-items:center;gap:8px;"> <div style="display:flex;align-items:center;gap:10px;">
<div id="opp-avatar" style="width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#E4AC38,#F59E0B);display:flex;align-items:center;justify-content:center;font-size:14px;">${isLive ? '👤' : '🤖'}</div> <div id="opp-avatar" style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#1a2a44,#0f1e36);border:2px solid rgba(228,172,56,0.3);display:flex;align-items:center;justify-content:center;font-size:16px;">${isLive ? '👤' : '🤖'}</div>
<div> <div>
<div id="opp-name" style="font-size:13px;font-weight:700;color:#f8fafc;">${isLive ? 'خصم' : 'بوت'}</div> <div id="opp-name" style="font-size:13px;font-weight:700;color:#f8fafc;">${isLive ? 'خصم' : 'بوت'}</div>
<div id="opp-count" style="font-size:11px;color:#94a3b8;">7 قطع</div> <div id="opp-count" style="font-size:11px;color:#94a3b8;">7 قطع</div>
</div> </div>
</div> </div>
<div style="display:flex;align-items:center;gap:8px;"> <div style="display:flex;align-items:center;gap:8px;">
<div id="bot-thinking" style="font-size:11px;color:#E4AC38;display:none;">يفكر...</div> <div id="bot-thinking" style="font-size:11px;color:#E4AC38;display:none;"><span class="dg-dots">يفكر</span></div>
${isLive ? '<div id="conn-dot" style="width:8px;height:8px;border-radius:50%;background:#4ade80;"></div>' : ''} ${isLive ? '<div id="conn-dot" style="width:8px;height:8px;border-radius:50%;background:#4ade80;box-shadow:0 0 6px rgba(74,222,128,0.4);"></div>' : ''}
<div id="boneyard-count" style="font-size:11px;color:#94a3b8;background:rgba(255,255,255,0.05);padding:4px 10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);">المخزن: 14</div> <div id="boneyard-count" style="font-size:11px;color:#E4AC38;background:rgba(228,172,56,0.06);padding:5px 10px;border-radius:10px;border:1px solid rgba(228,172,56,0.12);font-weight:600;">المخزن: 14</div>
</div> </div>
</div> </div>
<!-- Emote display area --> <!-- Emote display area -->
<div id="emote-display" style="position:absolute;top:56px;left:50%;transform:translateX(-50%);z-index:50;pointer-events:none;"></div> <div id="emote-display" style="position:absolute;top:60px;left:50%;transform:translateX(-50%);z-index:50;pointer-events:none;"></div>
<!-- Canvas board --> <!-- Canvas board -->
<div id="domino-board" style="flex:1;min-height:0;padding:4px;position:relative;"> <div id="domino-board" style="flex:1;min-height:0;padding:0;position:relative;">
<!-- Boneyard visual pile --> <!-- Boneyard visual pile -->
<div id="boneyard-pile" style="position:absolute;left:8px;top:50%;transform:translateY(-50%);z-index:10;display:flex;flex-direction:column;align-items:center;gap:4px;pointer-events:none;"> <div id="boneyard-pile" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);z-index:10;display:flex;flex-direction:column;align-items:center;gap:4px;pointer-events:none;">
<div id="boneyard-stack" style="position:relative;width:28px;height:44px;"> <div id="boneyard-stack" style="position:relative;width:26px;height:40px;">
<div style="position:absolute;top:0;left:0;width:26px;height:42px;background:#d4c9a8;border:2px solid rgba(255,255,255,0.15);border-radius:4px;box-shadow:0 2px 6px rgba(0,0,0,0.4);"></div> <div style="position:absolute;top:0;left:0;width:24px;height:38px;background:#d4c9a8;border:1.5px solid rgba(155,131,96,0.4);border-radius:3px;box-shadow:0 2px 4px rgba(0,0,0,0.4);"></div>
<div style="position:absolute;top:2px;left:2px;width:26px;height:42px;background:#e8dfc4;border:2px solid rgba(255,255,255,0.12);border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.3);"></div> <div style="position:absolute;top:2px;left:2px;width:24px;height:38px;background:#e8dfc4;border:1.5px solid rgba(155,131,96,0.3);border-radius:3px;box-shadow:0 2px 3px rgba(0,0,0,0.3);"></div>
<div style="position:absolute;top:4px;left:4px;width:26px;height:42px;background:#F5F0E8;border:2px solid rgba(255,255,255,0.1);border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,0.3);"></div> <div style="position:absolute;top:4px;left:4px;width:24px;height:38px;background:#F5F0E8;border:1.5px solid rgba(155,131,96,0.2);border-radius:3px;box-shadow:0 2px 6px rgba(0,0,0,0.3);"></div>
</div> </div>
<div id="boneyard-pile-count" style="font-size:11px;font-weight:700;color:#E4AC38;text-shadow:0 1px 3px rgba(0,0,0,0.6);"></div> <div id="boneyard-pile-count" style="font-size:10px;font-weight:700;color:#E4AC38;text-shadow:0 1px 2px rgba(0,0,0,0.8);"></div>
</div> </div>
</div> </div>
<!-- Score bar --> <!-- Score bar -->
<div id="score-bar" style="display:flex;align-items:center;justify-content:center;gap:12px;padding:6px 12px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.04);"> <div id="score-bar" style="display:flex;align-items:center;justify-content:center;gap:10px;padding:6px 12px;background:rgba(15,20,32,0.9);border-top:1px solid rgba(255,255,255,0.04);">
<span style="font-size:12px;color:#E4AC38;">أنت: <b id="my-score">0</b></span> <span style="font-size:12px;color:#4ade80;font-weight:600;">أنت: <b id="my-score">0</b></span>
<span style="font-size:11px;color:#334155;">|</span> <span style="width:1px;height:12px;background:rgba(255,255,255,0.1);"></span>
<span style="font-size:12px;color:#94a3b8;">خصم: <b id="opp-score">0</b></span> <span style="font-size:12px;color:#94a3b8;">خصم: <b id="opp-score">0</b></span>
<span style="font-size:11px;color:#334155;">|</span> <span style="width:1px;height:12px;background:rgba(255,255,255,0.1);"></span>
<span id="target-display" style="font-size:11px;color:#64748b;">${emoji('target', '🎯', 12)} ${state.targetScore}</span> <span id="target-display" style="font-size:11px;color:#64748b;">${emoji('target', '🎯', 12)} ${state.targetScore}</span>
<span style="font-size:11px;color:#334155;">|</span> <span style="width:1px;height:12px;background:rgba(255,255,255,0.1);"></span>
<span style="font-size:11px;color:#64748b;">ج<span id="round-num">1</span></span> <span style="font-size:11px;color:#64748b;">ج<span id="round-num">1</span></span>
</div> </div>
<!-- Status --> <!-- Turn status -->
<div id="turn-status" style="text-align:center;padding:5px;font-size:13px;font-weight:600;color:#E4AC38;">دورك!</div> <div id="turn-status" style="text-align:center;padding:6px;font-size:13px;font-weight:700;color:#E4AC38;letter-spacing:0.3px;">دورك!</div>
<!-- Hand area --> <!-- Hand area -->
<div id="domino-hand-area"></div> <div id="domino-hand-area"></div>
<!-- Controls --> <!-- Controls -->
<div id="domino-controls" style="display:flex;gap:6px;padding:8px 12px;padding-bottom:calc(8px + env(safe-area-inset-bottom,0px));background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);"> <div id="domino-controls" style="display:flex;gap:6px;padding:8px 12px;padding-bottom:calc(8px + env(safe-area-inset-bottom,0px));background:rgba(10,20,32,0.95);border-top:1px solid rgba(228,172,56,0.06);">
<button class="btn btn-secondary" id="btn-resign" style="flex:0.6;font-size:11px;min-height:44px;background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.2);color:#fca5a5;border-radius:12px;">استسلام</button> <button class="btn btn-secondary dg-ctrl-btn" id="btn-resign" style="flex:0.5;font-size:11px;background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.15);color:#fca5a5;">استسلام</button>
<button class="btn btn-secondary" id="btn-emote" style="flex:0.5;font-size:16px;min-height:44px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:12px;">😄</button> <button class="btn btn-secondary dg-ctrl-btn" id="btn-emote" style="flex:0.4;font-size:18px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);">😄</button>
<button class="btn btn-secondary" id="btn-draw" style="flex:1;font-size:13px;min-height:44px;background:rgba(228,172,56,0.1);border:1px solid rgba(228,172,56,0.25);color:#E4AC38;border-radius:12px;font-weight:700;">سحب</button> <button class="btn btn-secondary dg-ctrl-btn dg-draw-btn" id="btn-draw" style="flex:1;font-size:14px;font-weight:700;background:rgba(228,172,56,0.08);border:1px solid rgba(228,172,56,0.2);color:#E4AC38;">سحب</button>
<button class="btn btn-secondary" id="btn-pass" style="flex:0.7;font-size:12px;min-height:44px;background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.2);color:#fde68a;border-radius:12px;display:none;">تمرير</button> <button class="btn btn-secondary dg-ctrl-btn" id="btn-pass" style="flex:0.6;font-size:12px;background:rgba(251,191,36,0.06);border:1px solid rgba(251,191,36,0.15);color:#fde68a;display:none;">تمرير</button>
</div> </div>
</div> </div>
`; `;
...@@ -169,10 +169,28 @@ function buildLayout(mode) { ...@@ -169,10 +169,28 @@ function buildLayout(mode) {
function injectStyles(el) { function injectStyles(el) {
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = (hand?.getStyle() || '') + (drag?.getStyle() || '') + ` style.textContent = (hand?.getStyle() || '') + (drag?.getStyle() || '') + `
@keyframes fadeIn { from{opacity:0} to{opacity:1} } @keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
@keyframes emoteFloat { 0%{transform:translateX(-50%) scale(0);opacity:0} 20%{transform:translateX(-50%) scale(1.2);opacity:1} 80%{opacity:1} 100%{transform:translateX(-50%) translateY(-20px);opacity:0} } @keyframes emoteFloat {
.emote-bubble { animation: emoteFloat 2s ease forwards; position:absolute;left:50%;font-size:36px; } 0%{transform:translateX(-50%) scale(0);opacity:0}
@keyframes drawPulse { 0%,100%{box-shadow:0 0 4px rgba(228,172,56,0.2)} 50%{box-shadow:0 0 14px rgba(228,172,56,0.5)} } 15%{transform:translateX(-50%) scale(1.3);opacity:1}
75%{opacity:1}
100%{transform:translateX(-50%) translateY(-30px) scale(0.8);opacity:0}
}
.emote-bubble { animation: emoteFloat 2.2s cubic-bezier(0.34,1.56,0.64,1) forwards; position:absolute;left:50%;font-size:40px; }
@keyframes drawPulse {
0%,100%{box-shadow:0 0 4px rgba(228,172,56,0.15);border-color:rgba(228,172,56,0.2)}
50%{box-shadow:0 0 18px rgba(228,172,56,0.4);border-color:rgba(228,172,56,0.5)}
}
.dg-ctrl-btn {
min-height:42px;border-radius:12px;
transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.2s, opacity 0.2s;
}
.dg-ctrl-btn:active { transform:scale(0.93); }
.dg-dots::after {
content:'...';
animation:dotsAnim 1.2s steps(3) infinite;
}
@keyframes dotsAnim { 0%{content:'.'} 33%{content:'..'} 66%{content:'...'} }
`; `;
el.appendChild(style); el.appendChild(style);
} }
......
...@@ -22,25 +22,57 @@ export function mountRoom(el, params) { ...@@ -22,25 +22,57 @@ export function mountRoom(el, params) {
function renderMenu(el) { function renderMenu(el) {
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:20px;padding:24px;background:linear-gradient(180deg,#0a0a14 0%,#0f0f1e 100%);"> <div class="domino-menu">
<div style="font-size:48px;">${emoji('domino_tile', '🁣', 48)}</div> <div class="dm-hero">
<div style="font-size:22px;font-weight:800;color:#f8fafc;">دومينو</div> <div class="dm-icon">${emoji('domino_tile', '🁣', 56)}</div>
<div style="font-size:13px;color:#E4AC38;text-align:center;max-width:260px;">أول من يوصل الهدف يفوز!</div> <h1 class="dm-title">دومينو</h1>
<p class="dm-subtitle">أول من يوصل الهدف يفوز!</p>
<div style="display:flex;flex-direction:column;gap:10px;width:100%;max-width:300px;margin-top:12px;"> </div>
<button class="btn btn-primary" id="btn-bot" style="min-height:56px;border-radius:16px;font-size:16px;font-weight:700;background:linear-gradient(135deg,#E4AC38,#d4940a);display:flex;align-items:center;justify-content:center;gap:8px;">
${emoji('robot', '🤖', 20)} ضد البوت <div class="dm-buttons">
<button class="dm-btn dm-btn-primary" id="btn-bot">
<span class="dm-btn-icon">${emoji('robot', '🤖', 22)}</span>
<span class="dm-btn-label">ضد البوت</span>
</button> </button>
<button class="btn btn-primary" id="btn-online" style="min-height:56px;border-radius:16px;font-size:16px;font-weight:700;background:linear-gradient(135deg,#06b6d4,#0891b2);display:flex;align-items:center;justify-content:center;gap:8px;"> <button class="dm-btn dm-btn-online" id="btn-online">
${emoji('globe', '🌍', 20)} أونلاين <span class="dm-btn-icon">${emoji('globe', '🌍', 22)}</span>
<span class="dm-btn-label">أونلاين</span>
</button> </button>
<button class="btn btn-secondary" id="btn-friend" style="min-height:48px;border-radius:14px;font-size:14px;font-weight:600;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;display:flex;align-items:center;justify-content:center;gap:8px;"> <button class="dm-btn dm-btn-friend" id="btn-friend">
${emoji('handshake', '🤝', 18)} تحدي صديق <span class="dm-btn-icon">${emoji('handshake', '🤝', 20)}</span>
<span class="dm-btn-label">تحدي صديق</span>
</button> </button>
</div> </div>
<button class="btn btn-secondary" id="btn-back" style="margin-top:12px;font-size:13px;color:#64748b;background:none;border:none;">رجوع</button> <button class="dm-back" id="btn-back">رجوع</button>
</div> </div>
<style>
.domino-menu {
display:flex;flex-direction:column;align-items:center;justify-content:center;
height:100%;padding:24px;
background:linear-gradient(180deg, #070d14 0%, #0a1420 40%, #0d1a2a 100%);
}
.dm-hero { text-align:center;margin-bottom:28px; }
.dm-icon { font-size:56px;margin-bottom:12px;animation:dmFloat 3s ease-in-out infinite; }
.dm-title { font-size:26px;font-weight:800;color:#f8fafc;margin:0; }
.dm-subtitle { font-size:13px;color:#E4AC38;margin:8px 0 0;opacity:0.9; }
.dm-buttons { display:flex;flex-direction:column;gap:12px;width:100%;max-width:300px; }
.dm-btn {
display:flex;align-items:center;justify-content:center;gap:10px;
min-height:56px;border-radius:16px;font-size:16px;font-weight:700;
border:none;cursor:pointer;
transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.2s;
box-shadow:0 4px 16px rgba(0,0,0,0.3);
}
.dm-btn:active { transform:scale(0.95); }
.dm-btn-primary { background:linear-gradient(135deg,#E4AC38 0%,#d4940a 100%);color:#1a1a1a; }
.dm-btn-online { background:linear-gradient(135deg,#06b6d4 0%,#0891b2 100%);color:#fff; }
.dm-btn-friend { background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;min-height:50px;font-size:14px;box-shadow:none; }
.dm-btn-icon { display:flex; }
.dm-btn-label { flex:none; }
.dm-back { margin-top:20px;font-size:13px;color:#64748b;background:none;border:none;cursor:pointer;padding:8px 16px; }
@keyframes dmFloat { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-6px)} }
</style>
`; `;
el.querySelector('#btn-bot').addEventListener('click', () => { el.querySelector('#btn-bot').addEventListener('click', () => {
...@@ -66,40 +98,47 @@ function renderMenu(el) { ...@@ -66,40 +98,47 @@ function renderMenu(el) {
function renderBotPicker(el) { function renderBotPicker(el) {
const levels = [ const levels = [
{ key: 'beginner', label: 'مبتدئ', desc: 'يلعب عشوائي', icon: '😊', color: '#4ade80' }, { key: 'beginner', label: 'مبتدئ', desc: 'يلعب عشوائي', icon: '😊', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' },
{ key: 'intermediate', label: 'متوسط', desc: 'يفضل النقاط العالية', icon: '🧐', color: '#fbbf24' }, { key: 'intermediate', label: 'متوسط', desc: 'يفضل النقاط العالية', icon: '🧐', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' },
{ key: 'expert', label: 'خبير', desc: 'استراتيجي ومخادع', icon: '🧠', color: '#f87171' } { key: 'expert', label: 'خبير', desc: 'استراتيجي ومخادع', icon: '🧠', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' }
]; ];
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:20px;padding:24px;background:linear-gradient(180deg,#0a0a14 0%,#0f0f1e 100%);"> <div class="domino-menu">
<div style="font-size:20px;font-weight:700;color:#f8fafc;">اختر مستوى البوت</div> <div class="dm-hero">
<div style="display:flex;flex-direction:column;gap:10px;width:100%;max-width:300px;"> <h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">اختر مستوى البوت</h2>
<p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">كل مستوى له استراتيجية مختلفة</p>
</div>
<div class="dm-buttons">
${levels.map(l => ` ${levels.map(l => `
<button class="btn-level" data-level="${l.key}" style=" <button class="dm-level-btn" data-level="${l.key}" style="background:${l.bg};border:1px solid ${l.border};">
display:flex;align-items:center;gap:12px;padding:16px; <span class="dm-level-icon">${l.icon}</span>
background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08); <div class="dm-level-info">
border-radius:14px;cursor:pointer;width:100%;text-align:right;
transition:transform 0.1s,border-color 0.2s;
">
<span style="font-size:28px;">${l.icon}</span>
<div style="flex:1;">
<div style="font-size:15px;font-weight:700;color:${l.color};">${l.label}</div> <div style="font-size:15px;font-weight:700;color:${l.color};">${l.label}</div>
<div style="font-size:12px;color:#94a3b8;margin-top:2px;">${l.desc}</div> <div style="font-size:12px;color:#94a3b8;margin-top:2px;">${l.desc}</div>
</div> </div>
<div style="font-size:18px;color:#475569;">←</div> <span style="font-size:16px;color:#475569;">←</span>
</button> </button>
`).join('')} `).join('')}
</div> </div>
<button class="btn btn-secondary" id="btn-back-bot" style="margin-top:8px;font-size:13px;color:#64748b;background:none;border:none;">رجوع</button> <button class="dm-back" id="btn-back-bot">رجوع</button>
</div> </div>
<style>
.dm-level-btn {
display:flex;align-items:center;gap:14px;padding:16px 18px;
border-radius:14px;cursor:pointer;width:100%;text-align:right;
transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.2s;
}
.dm-level-btn:active { transform:scale(0.97); }
.dm-level-icon { font-size:30px;flex-shrink:0; }
.dm-level-info { flex:1; }
</style>
`; `;
el.querySelectorAll('.btn-level').forEach(btn => { el.querySelectorAll('.dm-level-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
audio.play('click'); audio.play('click');
const level = btn.dataset.level; renderTargetPicker(el, btn.dataset.level);
renderTargetPicker(el, level);
}); });
}); });
...@@ -111,37 +150,34 @@ function renderBotPicker(el) { ...@@ -111,37 +150,34 @@ function renderBotPicker(el) {
function renderTargetPicker(el, botLevel) { function renderTargetPicker(el, botLevel) {
const targets = [ const targets = [
{ value: 50, label: '50 نقطة', desc: 'مباراة سريعة', icon: '⚡', color: '#4ade80' }, { value: 50, label: '50 نقطة', desc: 'مباراة سريعة (~5 دقائق)', icon: '⚡', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' },
{ value: 100, label: '100 نقطة', desc: 'كلاسيك', icon: '🎯', color: '#fbbf24' }, { value: 100, label: '100 نقطة', desc: 'كلاسيك (~10 دقائق)', icon: '🎯', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' },
{ value: 150, label: '150 نقطة', desc: 'مباراة طويلة', icon: '🔥', color: '#f87171' } { value: 150, label: '150 نقطة', desc: 'مباراة طويلة (~15 دقيقة)', icon: '🔥', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' }
]; ];
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:20px;padding:24px;background:linear-gradient(180deg,#0a0a14 0%,#0f0f1e 100%);"> <div class="domino-menu">
<div style="font-size:20px;font-weight:700;color:#f8fafc;">اختر الهدف</div> <div class="dm-hero">
<div style="font-size:13px;color:#94a3b8;">أول من يوصل النقاط يفوز</div> <h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">اختر الهدف</h2>
<div style="display:flex;flex-direction:column;gap:10px;width:100%;max-width:300px;"> <p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">أول من يوصل النقاط يفوز بالمباراة</p>
</div>
<div class="dm-buttons">
${targets.map(t => ` ${targets.map(t => `
<button class="btn-target" data-target="${t.value}" style=" <button class="dm-level-btn" data-target="${t.value}" style="background:${t.bg};border:1px solid ${t.border};">
display:flex;align-items:center;gap:12px;padding:16px; <span class="dm-level-icon">${t.icon}</span>
background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.08); <div class="dm-level-info">
border-radius:14px;cursor:pointer;width:100%;text-align:right;
transition:transform 0.1s,border-color 0.2s;
">
<span style="font-size:28px;">${t.icon}</span>
<div style="flex:1;">
<div style="font-size:15px;font-weight:700;color:${t.color};">${t.label}</div> <div style="font-size:15px;font-weight:700;color:${t.color};">${t.label}</div>
<div style="font-size:12px;color:#94a3b8;margin-top:2px;">${t.desc}</div> <div style="font-size:12px;color:#94a3b8;margin-top:2px;">${t.desc}</div>
</div> </div>
<div style="font-size:18px;color:#475569;">←</div> <span style="font-size:16px;color:#475569;">←</span>
</button> </button>
`).join('')} `).join('')}
</div> </div>
<button class="btn btn-secondary" id="btn-back-target" style="margin-top:8px;font-size:13px;color:#64748b;background:none;border:none;">رجوع</button> <button class="dm-back" id="btn-back-target">رجوع</button>
</div> </div>
`; `;
el.querySelectorAll('.btn-target').forEach(btn => { el.querySelectorAll('.dm-level-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
audio.play('click'); audio.play('click');
const targetScore = parseInt(btn.dataset.target); const targetScore = parseInt(btn.dataset.target);
...@@ -159,24 +195,29 @@ function renderTargetPicker(el, botLevel) { ...@@ -159,24 +195,29 @@ function renderTargetPicker(el, botLevel) {
function renderLobby(el, { challengeId, friendId, friendName }) { function renderLobby(el, { challengeId, friendId, friendName }) {
const isHost = !challengeId; const isHost = !challengeId;
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:16px;padding:24px;background:linear-gradient(180deg,#0a0a14 0%,#0f0f1e 100%);"> <div class="domino-menu">
<div style="font-size:40px;">${emoji('domino_tile', '🁣', 40)}</div> <div class="dm-hero">
<div style="font-size:18px;font-weight:700;color:#f8fafc;"> <div style="font-size:44px;margin-bottom:12px;">${emoji('domino_tile', '🁣', 44)}</div>
${isHost ? 'بانتظار الصديق...' : `تحدي من ${friendName || 'صديق'}`} <h2 style="font-size:18px;font-weight:700;color:#f8fafc;margin:0;">
${isHost ? 'بانتظار الصديق...' : `تحدي من ${friendName || 'صديق'}`}
</h2>
</div> </div>
<div id="lobby-status" style="display:flex;flex-direction:column;align-items:center;gap:8px;"> <div id="lobby-status" style="display:flex;flex-direction:column;align-items:center;gap:12px;">
<div class="pulse" style="width:60px;height:60px;border-radius:50%;border:3px solid #E4AC38;display:flex;align-items:center;justify-content:center;"> <div style="width:64px;height:64px;border-radius:50%;border:3px solid rgba(228,172,56,0.4);display:flex;align-items:center;justify-content:center;animation:dmPulseRing 2s ease-in-out infinite;">
${emoji('clock', '⏳', 24)} ${emoji('clock', '⏳', 26)}
</div> </div>
<div style="font-size:13px;color:#E4AC38;" id="lobby-msg">${isHost ? 'أرسل الدعوة لصديقك' : 'اضغط قبول للبدء'}</div> <div style="font-size:13px;color:#E4AC38;" id="lobby-msg">${isHost ? 'أرسل الدعوة لصديقك' : 'اضغط قبول للبدء'}</div>
</div> </div>
<div style="display:flex;gap:10px;margin-top:12px;"> <div style="display:flex;gap:10px;margin-top:20px;">
${!isHost ? `<button class="btn btn-primary" id="btn-accept" style="min-height:48px;padding:0 24px;border-radius:14px;font-size:15px;font-weight:700;background:linear-gradient(135deg,#3b82f6,#2563eb);">قبول</button>` : ''} ${!isHost ? `<button class="dm-btn dm-btn-online" id="btn-accept" style="min-height:48px;padding:0 28px;font-size:15px;">قبول</button>` : ''}
<button class="btn btn-secondary" id="btn-cancel-lobby" style="min-height:48px;padding:0 24px;border-radius:14px;font-size:14px;background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);color:#fca5a5;">إلغاء</button> <button class="dm-btn dm-btn-friend" id="btn-cancel-lobby" style="min-height:48px;padding:0 20px;font-size:14px;border-color:rgba(239,68,68,0.3);color:#fca5a5;">إلغاء</button>
</div> </div>
</div> </div>
<style>
@keyframes dmPulseRing { 0%,100%{border-color:rgba(228,172,56,0.4);transform:scale(1)} 50%{border-color:rgba(228,172,56,0.7);transform:scale(1.05)} }
</style>
`; `;
if (isHost) { if (isHost) {
......
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