Commit 6abd77c5 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(game): a real loading screen; tournament tiebreak names

- The splash is a game loading screen: the crown dealt onto spinning light
  rays, the wordmark, a chunky gold loading bar and a rotating tip.
- index.php shows the crown and a loading bar before any script runs, so a slow
  phone never sees a blank page.
- Tiebreak names (Buchholz, Sonneborn-Berger, ...) were raw keys on the
  tournament page.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 1b6fe1dc
...@@ -34,7 +34,13 @@ header('Expires: 0'); ...@@ -34,7 +34,13 @@ header('Expires: 0');
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance
--> -->
<div id="game"> <div id="game">
<div id="scene-container"></div> <div id="scene-container">
<!-- Shown before any script runs; the first scene replaces it. -->
<div id="boot-splash" style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;">
<img src="/public/assets/game/crown.webp" width="140" height="140" alt="EL3AB" style="width:140px;height:140px;object-fit:contain;filter:drop-shadow(0 10px 10px rgba(0,0,0,.55));">
<div style="width:220px;height:16px;border-radius:9px;background:rgba(0,0,0,.55);box-shadow:inset 0 3px 5px rgba(0,0,0,.7),0 0 0 2px #2A1205;overflow:hidden;"><div style="height:100%;width:35%;border-radius:inherit;background:linear-gradient(180deg,#FFE47E,#F2A01A);"></div></div>
</div>
</div>
</div> </div>
<div id="hud"></div> <div id="hud"></div>
<div id="overlay"></div> <div id="overlay"></div>
......
...@@ -982,7 +982,22 @@ const strings = { ...@@ -982,7 +982,22 @@ const strings = {
'settings.credits': 'حقوق الرسومات والأصوات', 'settings.credits': 'حقوق الرسومات والأصوات',
'settings.credits_body': 'شكراً لمن صنعوا الأيقونات والأصوات والخامات التي تزين طاولة العب.', 'settings.credits_body': 'شكراً لمن صنعوا الأيقونات والأصوات والخامات التي تزين طاولة العب.',
'settings.credits_sounds': 'أصوات الورق والزهر والقطع', 'settings.credits_sounds': 'أصوات الورق والزهر والقطع',
'settings.credits_wood': 'خشب الطاولة' 'settings.credits_wood': 'خشب الطاولة',
'tournament.tb_buchholz': 'بوخهولز',
'tournament.tb_buchholz_cut_1': 'بوخهولز ناقص الأدنى',
'tournament.tb_buchholz_median': 'بوخهولز الوسيط',
'tournament.tb_sonneborn_berger': 'سونبورن-برجر',
'tournament.tb_direct_encounter': 'المواجهة المباشرة',
'tournament.tb_wins': 'عدد الانتصارات',
'tournament.tb_progressive': 'النقاط التراكمية',
'tournament.tb_koya': 'كويا',
'tournament.tb_aro': 'متوسط تصنيف الخصوم',
'tournament.tb_black_games': 'المباريات بالأسود',
'splash.tip_1': 'نصيحة: في اللودو، الستة تُخرج قطعة وتعطيك رمية إضافية',
'splash.tip_2': 'نصيحة: اضغط على صندوق المكافأة كل يوم ليكبر رصيدك',
'splash.tip_3': 'نصيحة: في الشطرنج سيطر على المركز مبكراً',
'splash.tip_4': 'نصيحة: في الدومينو احتفظ بالدوبل الكبير للنهاية بحذر',
'splash.tip_5': 'نصيحة: في الطاولة لا تترك قطعة وحيدة أمام خصمك'
}, },
en: { en: {
'app.name': 'EL3AB', 'app.name': 'EL3AB',
...@@ -1965,7 +1980,22 @@ const strings = { ...@@ -1965,7 +1980,22 @@ const strings = {
'settings.credits': 'Art and sound credits', 'settings.credits': 'Art and sound credits',
'settings.credits_body': 'Thanks to the people who made the icons, sounds and textures on El3ab\'s table.', 'settings.credits_body': 'Thanks to the people who made the icons, sounds and textures on El3ab\'s table.',
'settings.credits_sounds': 'card, dice and piece sounds', 'settings.credits_sounds': 'card, dice and piece sounds',
'settings.credits_wood': 'the table wood' 'settings.credits_wood': 'the table wood',
'tournament.tb_buchholz': 'Buchholz',
'tournament.tb_buchholz_cut_1': 'Buchholz cut 1',
'tournament.tb_buchholz_median': 'Median Buchholz',
'tournament.tb_sonneborn_berger': 'Sonneborn-Berger',
'tournament.tb_direct_encounter': 'Direct encounter',
'tournament.tb_wins': 'Number of wins',
'tournament.tb_progressive': 'Progressive score',
'tournament.tb_koya': 'Koya',
'tournament.tb_aro': 'Average opponent rating',
'tournament.tb_black_games': 'Games with Black',
'splash.tip_1': 'Tip: in ludo a six brings a piece out and rolls again',
'splash.tip_2': 'Tip: open the reward chest every day to grow your coins',
'splash.tip_3': 'Tip: in chess, take the centre early',
'splash.tip_4': 'Tip: in domino, don\'t get stuck holding the big doubles',
'splash.tip_5': 'Tip: in backgammon, don\'t leave a single checker exposed'
} }
}; };
......
...@@ -6,19 +6,26 @@ import * as scene from '../../../core/scene.js'; ...@@ -6,19 +6,26 @@ import * as scene from '../../../core/scene.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';
import { assetImg } from '../../../core/theme.js'; import { assetImg } from '../../../core/theme.js';
import { icon } from '../../../core/icons.js'; import { icon, crown } from '../../../core/icons.js';
import * as juice from '../../../core/juice.js'; import * as juice from '../../../core/juice.js';
const TIPS = ['splash.tip_1', 'splash.tip_2', 'splash.tip_3', 'splash.tip_4', 'splash.tip_5'];
export function mountSplash(el) { export function mountSplash(el) {
const tip = TIPS[Math.floor(Math.random() * TIPS.length)];
el.innerHTML = ` el.innerHTML = `
<div class="sp"> <div class="sp">
<div class="sp-crown">${assetImg('logo', icon('crown', 104), 104, 104)}</div> <div class="sp-stage" aria-hidden="true">
<span class="sp-rays"></span>
<span class="sp-crown">${crown(150)}</span>
</div>
<div class="sp-word"> <div class="sp-word">
<span class="sp-word-lat" dir="ltr">${t('app.name')}</span>
<span class="sp-word-ar">${t('auth.wordmark_ar')}</span> <span class="sp-word-ar">${t('auth.wordmark_ar')}</span>
<span class="sp-word-lat" dir="ltr">${t('app.name')}</span>
</div> </div>
<p class="sp-tagline">${t('app.tagline')}</p> <p class="sp-tagline">${t('app.tagline')}</p>
<div class="sp-rule" role="progressbar" aria-label="${t('common.loading')}"><span></span></div> <div class="sp-bar" role="progressbar" aria-label="${t('common.loading')}"><span></span></div>
<p class="sp-tip">${t(tip)}</p>
</div> </div>
<style> <style>
.sp { .sp {
...@@ -27,41 +34,57 @@ export function mountSplash(el) { ...@@ -27,41 +34,57 @@ export function mountSplash(el) {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: var(--s-3); gap: 10px;
padding: var(--s-8) var(--gutter); padding: 32px var(--gutter);
text-align: center; text-align: center;
} }
/* The crown is dealt onto the table and lands with a small stamp. */ .sp-stage { position: relative; width: 240px; height: 210px; display: flex; align-items: center; justify-content: center; }
.sp-crown { display: flex; color: var(--gold); margin-bottom: var(--s-3); animation: spCrown 720ms cubic-bezier(.34, 1.56, .64, 1) both; } .sp-rays {
position: absolute; inset: -30px;
border-radius: 50%;
background: repeating-conic-gradient(from 0deg, rgba(255, 214, 110, 0.3) 0deg 9deg, transparent 9deg 20deg);
-webkit-mask: radial-gradient(circle, #000 15%, transparent 66%);
mask: radial-gradient(circle, #000 15%, transparent 66%);
animation: spSpin 16s linear infinite, spRays 900ms var(--ease-out) both;
}
@keyframes spSpin { to { transform: rotate(360deg); } }
@keyframes spRays { from { opacity: 0; scale: 0.4; } }
/* The crown is dealt onto the table and lands with a stamp. */
.sp-crown { position: relative; display: flex; filter: drop-shadow(0 12px 10px rgba(0, 0, 0, 0.55)); animation: spCrown 820ms var(--ease-pop) both, idleBob 3.2s ease-in-out 900ms infinite; }
@keyframes spCrown { @keyframes spCrown {
from { opacity: 0; transform: translateY(-90px) rotate(-16deg) scale(0.6); } from { opacity: 0; transform: translateY(-140px) rotate(-20deg) scale(0.4); }
60% { opacity: 1; } 60% { opacity: 1; }
} }
.sp-word { display: flex; flex-direction: column; align-items: center; gap: 2px; animation: spRise var(--dur-slow) var(--ease-out) 380ms both; } .sp-word { display: flex; flex-direction: column; align-items: center; animation: bounceIn 560ms var(--ease-out) 420ms both; }
.sp-word-ar { font-family: var(--font-display); font-size: 64px; line-height: 0.95; color: var(--gold-soft); text-shadow: var(--text-outline), 0 6px 14px rgba(0, 0, 0, 0.5); }
.sp-word-lat { font-family: var(--font-display); font-size: 26px; line-height: 1; letter-spacing: 0.18em; color: var(--text-primary); text-shadow: var(--text-outline); }
.sp-tagline { font-family: var(--font-display); font-size: var(--fs-md); color: var(--text-secondary); text-shadow: var(--text-shadow); animation: spRise var(--dur-slow) var(--ease-out) 620ms both; }
@keyframes spRise { from { opacity: 0; transform: translateY(10px); } } @keyframes spRise { from { opacity: 0; transform: translateY(10px); } }
.sp-word-lat { font-size: var(--fs-3xl); font-weight: 900; line-height: 1; letter-spacing: 0.08em; color: var(--text-primary); }
.sp-word-ar { font-size: var(--fs-xl); font-weight: 800; line-height: 1.2; color: var(--gold); }
.sp-tagline { font-size: var(--fs-sm); color: var(--text-secondary); animation: spRise var(--dur-slow) var(--ease-out) 520ms both; }
/* The loading rule: a short hairline that fills once, in gold. */ /* A chunky game loading bar that fills with gold. */
.sp-rule { .sp-bar {
width: 96px; width: min(260px, 72vw);
height: 2px; height: 18px;
margin-top: var(--s-8); margin-top: 22px;
border-radius: var(--r-full); border-radius: 10px;
background: var(--back-700); background: rgba(0, 0, 0, 0.55);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.7), 0 0 0 2px var(--outline), 0 1px 0 3px rgba(255, 220, 160, 0.15);
overflow: hidden; overflow: hidden;
} }
.sp-rule span { .sp-bar span {
display: block; display: block;
height: 100%; height: 100%;
background: var(--gold); width: 100%;
border-radius: inherit;
background: linear-gradient(180deg, var(--btn-gold-1), var(--btn-gold-2));
box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.6);
transform: scaleX(0); transform: scaleX(0);
transform-origin: 0 50%; transform-origin: 0 50%;
animation: spFill 1500ms var(--ease-out) forwards; animation: spFill 1500ms var(--ease-in-out) forwards;
} }
[dir="rtl"] .sp-rule span { transform-origin: 100% 50%; } [dir="rtl"] .sp-bar span { transform-origin: 100% 50%; }
@keyframes spFill { to { transform: scaleX(1); } } @keyframes spFill { to { transform: scaleX(1); } }
.sp-tip { max-width: 30ch; font-size: var(--fs-sm); font-weight: 700; color: var(--text-muted); text-shadow: var(--text-shadow); animation: spRise var(--dur-slow) var(--ease-out) 800ms both; }
</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