🎮ArcadeLab

دوري الهوكي 2026 — Three.js

by GoldenGlider99
1066 lines42.1 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>دوري الهوكي 2026 — Three.js</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800;900&display=swap" rel="stylesheet">
<script>
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Cairo','sans-serif'] } } } };
</script>
<style>
  html,body{height:100%;}
  body{
    font-family:'Cairo',sans-serif;
    overscroll-behavior:none;
    -webkit-tap-highlight-color:transparent;
  }
  #scene{ touch-action:none; display:block; }
  .badge{
    display:flex;align-items:center;justify-content:center;border-radius:9999px;
    font-weight:900;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.7);
    border:2px solid rgba(255,255,255,.28);
    box-shadow:inset 0 -6px 12px rgba(0,0,0,.25), 0 4px 14px -4px rgba(0,0,0,.6);
    flex:none;
  }
  .screen{ position:absolute; inset:0; overflow-y:auto; }
  .glass{ background:rgba(15,23,42,.82); backdrop-filter:blur(14px); border:1px solid rgba(148,163,184,.18); }
  .club-opt{ transition:all .18s ease; }
  .club-opt:hover{ transform:translateY(-4px); border-color:rgba(251,191,36,.7); }
  .club-opt.sel{ border-color:#fbbf24; box-shadow:0 0 0 2px rgba(251,191,36,.45), 0 12px 30px -12px rgba(251,191,36,.5); }
  @keyframes popIn{ from{opacity:0; transform:scale(.86) translateY(12px);} to{opacity:1; transform:scale(1) translateY(0);} }
  .pop{ animation:popIn .38s cubic-bezier(.34,1.56,.64,1) both; }
  @keyframes flashRed{ 0%{opacity:.75} 100%{opacity:0} }
  .flash{ animation:flashRed .7s ease-out forwards; }
  .bracket-col{ min-width:190px; }
  ::-webkit-scrollbar{width:8px;height:8px}
  ::-webkit-scrollbar-thumb{background:#334155;border-radius:8px}
  .tabular-nums{font-variant-numeric:tabular-nums;}
</style>
</head>
<body class="bg-slate-950 text-slate-100 select-none">

<canvas id="scene" class="fixed inset-0 z-0"></canvas>

<!-- ======================= HUD ======================= -->
<div id="hud" class="fixed inset-0 z-20 pointer-events-none hidden">
  <div class="absolute top-2 left-1/2 -translate-x-1/2 bg-slate-900/85 backdrop-blur-md border border-slate-700 rounded-2xl px-3 sm:px-5 py-2 flex items-center gap-3 sm:gap-5 shadow-2xl">
    <div class="flex items-center gap-2">
      <div id="hudYouBadge"></div>
      <span id="hudYouName" class="text-xs sm:text-sm font-bold max-w-[80px] truncate"></span>
    </div>
    <div class="text-center px-1">
      <div id="hudScore" class="text-xl sm:text-2xl font-black tabular-nums leading-none">0 - 0</div>
      <div id="hudTimer" class="text-[11px] text-amber-400 font-bold tabular-nums mt-0.5">1:30</div>
    </div>
    <div class="flex items-center gap-2">
      <span id="hudAiName" class="text-xs sm:text-sm font-bold max-w-[80px] truncate"></span>
      <div id="hudAiBadge"></div>
    </div>
  </div>

  <button onclick="quitMatch()" class="pointer-events-auto absolute top-2 left-2 w-10 h-10 rounded-xl bg-slate-900/85 border border-slate-700 text-slate-300 hover:text-white hover:border-rose-500 flex items-center justify-center transition">
    <i class="fa-solid fa-xmark"></i>
  </button>

  <div id="centerWrap" class="absolute inset-0 flex items-center justify-center">
    <div id="centerMsg" class="text-center"></div>
  </div>

  <div id="flashLayer" class="absolute inset-0 opacity-0 pointer-events-none" style="background:radial-gradient(circle,rgba(251,191,36,.6),transparent 70%)"></div>
</div>

<!-- ======================= SCREENS ======================= -->
<div id="screens" class="fixed inset-0 z-30">

  <!-- START -->
  <section id="screen-start" class="screen flex items-center justify-center p-4" style="background:radial-gradient(circle at 50% 0%,#1e293b,#020617 65%)">
    <div class="w-full max-w-3xl text-center pop">
      <div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/25 text-amber-400 text-xs font-bold mb-5">
        <i class="fa-solid fa-hockey-puck"></i><span>الموسم الرياضي 2025 / 2026</span>
      </div>
      <h1 class="text-4xl sm:text-6xl font-black tracking-tight mb-3 bg-gradient-to-b from-white to-slate-400 bg-clip-text text-transparent">
        دوري الهوكي الأوروبي
      </h1>
      <p class="text-slate-400 mb-8 max-w-xl mx-auto text-sm sm:text-base">
        اختر الدوري، اسحب القرعة، والعب مباريات خروج المغلوب — 90 ثانية لكل مباراة!
      </p>

      <div class="glass rounded-3xl p-5 sm:p-7 mb-6">
        <h3 class="text-sm font-bold text-slate-300 mb-3 text-right"><i class="fa-solid fa-gauge-high ml-2 text-amber-400"></i>مستوى الصعوبة</h3>
        <div id="diffGrid" class="grid grid-cols-3 gap-3"></div>
      </div>

      <div class="glass rounded-3xl p-5 sm:p-7 mb-6">
        <h3 class="text-sm font-bold text-slate-300 mb-3 text-right"><i class="fa-solid fa-trophy ml-2 text-amber-400"></i>اختر الدوري</h3>
        <div id="leagueGrid" class="grid grid-cols-2 sm:grid-cols-3 gap-3"></div>
      </div>

      <button id="btnGoClub" disabled
        class="w-full sm:w-auto px-12 py-4 rounded-2xl font-black text-lg transition
               bg-gradient-to-r from-amber-500 to-orange-600 text-slate-950 shadow-lg shadow-amber-900/40
               disabled:opacity-30 disabled:cursor-not-allowed hover:brightness-110">
        متابعة <i class="fa-solid fa-arrow-left mr-2"></i>
      </button>
    </div>
  </section>

  <!-- CLUB SELECT -->
  <section id="screen-club" class="screen hidden items-center justify-center p-4" style="background:radial-gradient(circle at 50% 0%,#1e293b,#020617 65%)">
    <div class="w-full max-w-4xl pop">
      <div class="flex items-center justify-between mb-5">
        <button onclick="showScreen('start')" class="px-4 py-2 rounded-xl bg-slate-800/80 border border-slate-700 text-sm hover:border-slate-500 transition">
          <i class="fa-solid fa-arrow-right ml-1"></i> رجوع
        </button>
        <h2 id="clubTitle" class="text-xl sm:text-2xl font-black"></h2>
      </div>
      <p class="text-center text-slate-400 text-sm mb-5">اختر النادي الذي ستلعب به</p>
      <div id="clubGrid" class="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-7"></div>
      <button id="btnGoDraw" disabled
        class="w-full px-12 py-4 rounded-2xl font-black text-lg transition
               bg-gradient-to-r from-emerald-500 to-teal-600 text-white shadow-lg shadow-emerald-900/40
               disabled:opacity-30 disabled:cursor-not-allowed hover:brightness-110">
        <i class="fa-solid fa-shuffle ml-2"></i> إجراء القرعة
      </button>
    </div>
  </section>

  <!-- DRAW / BRACKET -->
  <section id="screen-draw" class="screen hidden items-center justify-center p-4" style="background:radial-gradient(circle at 50% 0%,#1e293b,#020617 65%)">
    <div class="w-full max-w-6xl pop">
      <div class="text-center mb-6">
        <h2 class="text-2xl sm:text-3xl font-black mb-1">جدول البطولة</h2>
        <p id="roundLabel" class="text-amber-400 font-bold text-sm"></p>
      </div>
      <div id="bracket" class="flex flex-row-reverse justify-center gap-4 overflow-x-auto pb-4 mb-6"></div>
      <div class="text-center">
        <button id="btnPlay" onclick="beginMatch()"
          class="px-14 py-4 rounded-2xl font-black text-lg bg-gradient-to-r from-rose-500 to-red-600 text-white
                 shadow-lg shadow-rose-900/40 hover:brightness-110 transition">
          <i class="fa-solid fa-play ml-2"></i> ابدأ المباراة
        </button>
      </div>
    </div>
  </section>

  <!-- RESULT -->
  <section id="screen-result" class="screen hidden items-center justify-center p-4" style="background:radial-gradient(circle at 50% 0%,#1e293b,#020617 65%)">
    <div class="w-full max-w-lg text-center pop glass rounded-3xl p-8">
      <div id="resultIcon" class="text-6xl mb-4"></div>
      <h2 id="resultTitle" class="text-3xl font-black mb-2"></h2>
      <p id="resultSub" class="text-slate-400 mb-6"></p>
      <div id="resultScore" class="text-5xl font-black tabular-nums mb-8"></div>
      <button id="btnResult" class="w-full px-8 py-4 rounded-2xl font-black text-lg bg-gradient-to-r from-amber-500 to-orange-600 text-slate-950 hover:brightness-110 transition"></button>
    </div>
  </section>

</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
/* ==================================================================
   1)  DATA
   ================================================================== */
const LEAGUES = [
  { id:'epl', name:'الإنجليزي', full:'الدوري الإنجليزي الممتاز', flag:'🏴', accent:'#8b5cf6', clubs:[
    { ar:'أرسنال',          en:'Arsenal',        c1:'#EF0107', c2:'#063672' },
    { ar:'مانشستر سيتي',    en:'Man City',       c1:'#6CABDD', c2:'#1C2C5B' },
    { ar:'ليفربول',         en:'Liverpool',      c1:'#C8102E', c2:'#00B2A9' },
    { ar:'مانشستر يونايتد', en:'Man United',     c1:'#DA291C', c2:'#FBE122' },
    { ar:'تشيلسي',          en:'Chelsea',        c1:'#034694', c2:'#EEF0F4' },
    { ar:'توتنهام',         en:'Tottenham',      c1:'#132257', c2:'#FFFFFF' },
  ]},
  { id:'laliga', name:'الإسباني', full:'الدوري الإسباني', flag:'🇪🇸', accent:'#ef4444', clubs:[
    { ar:'ريال مدريد',      en:'Real Madrid',    c1:'#FEBE10', c2:'#00529F' },
    { ar:'برشلونة',         en:'Barcelona',      c1:'#A50044', c2:'#004D98' },
    { ar:'أتلتيكو مدريد',   en:'Atletico',       c1:'#CB3524', c2:'#262E62' },
    { ar:'أتلتيك بيلباو',   en:'Athletic',       c1:'#EE2523', c2:'#000000' },
    { ar:'ريال سوسيداد',    en:'Sociedad',       c1:'#0067B1', c2:'#FFFFFF' },
    { ar:'إشبيلية',         en:'Sevilla',        c1:'#D8042B', c2:'#FFFFFF' },
  ]},
  { id:'seriea', name:'الإيطالي', full:'الدوري الإيطالي', flag:'🇮🇹', accent:'#3b82f6', clubs:[
    { ar:'إنتر ميلان',      en:'Inter',          c1:'#0068A8', c2:'#000000' },
    { ar:'إيه سي ميلان',    en:'Milan',          c1:'#FB090B', c2:'#000000' },
    { ar:'يوفنتوس',         en:'Juventus',       c1:'#2b2b2b', c2:'#FFFFFF' },
    { ar:'نابولي',          en:'Napoli',         c1:'#12A0D7', c2:'#003C82' },
    { ar:'روما',            en:'Roma',           c1:'#8E1F2F', c2:'#F0BC42' },
    { ar:'أتالانتا',        en:'Atalanta',       c1:'#1D71B8', c2:'#000000' },
  ]},
  { id:'bundesliga', name:'الألماني', full:'الدوري الألماني', flag:'🇩🇪', accent:'#10b981', clubs:[
    { ar:'بايرن ميونخ',     en:'Bayern',         c1:'#DC052D', c2:'#0066B2' },
    { ar:'دورتموند',        en:'Dortmund',       c1:'#FDE100', c2:'#000000' },
    { ar:'ليفركوزن',        en:'Leverkusen',     c1:'#E32221', c2:'#000000' },
    { ar:'لايبزيغ',         en:'Leipzig',        c1:'#DD0741', c2:'#001F47' },
    { ar:'فرانكفورت',       en:'Frankfurt',      c1:'#E1000F', c2:'#000000' },
    { ar:'شتوتغارت',        en:'Stuttgart',      c1:'#E32219', c2:'#FFFFFF' },
  ]},
  { id:'ligue1', name:'الفرنسي', full:'الدوري الفرنسي', flag:'🇫🇷', accent:'#06b6d4', clubs:[
    { ar:'باريس سان جيرمان',en:'PSG',            c1:'#004170', c2:'#DA291C' },
    { ar:'مارسيليا',        en:'Marseille',      c1:'#2FAEE0', c2:'#FFFFFF' },
    { ar:'موناكو',          en:'Monaco',         c1:'#E63946', c2:'#FFFFFF' },
    { ar:'ليون',            en:'Lyon',           c1:'#1B3282', c2:'#DA291C' },
    { ar:'ليل',             en:'Lille',          c1:'#E01E13', c2:'#0A2E6E' },
    { ar:'نيس',             en:'Nice',           c1:'#C8102E', c2:'#000000' },
  ]},
  { id:'superlig', name:'التركي', full:'الدوري التركي الممتاز', flag:'🇹🇷', accent:'#f43f5e', clubs:[
    { ar:'غلطة سراي',       en:'Galatasaray',    c1:'#FDB912', c2:'#A90432' },
    { ar:'فنربخشة',         en:'Fenerbahce',     c1:'#FFED00', c2:'#00296B' },
    { ar:'بشكتاش',          en:'Besiktas',       c1:'#2b2b2b', c2:'#FFFFFF' },
    { ar:'طرابزون سبور',    en:'Trabzonspor',    c1:'#7A1230', c2:'#5BB8E6' },
    { ar:'باشاكشهير',       en:'Basaksehir',     c1:'#F97316', c2:'#1E3A8A' },
    { ar:'سامسون سبور',     en:'Samsunspor',     c1:'#E30613', c2:'#FFFFFF' },
  ]},
];

const DIFFS = {
  easy:   { label:'بسيط',  icon:'fa-face-smile',  speed:7.5,  react:0.34, error:2.6, color:'from-emerald-500 to-teal-600' },
  medium: { label:'متوسط', icon:'fa-face-meh',    speed:12.5, react:0.17, error:1.2, color:'from-amber-500 to-orange-600' },
  hard:   { label:'صعب',   icon:'fa-face-grimace',speed:19.0, react:0.07, error:0.45,color:'from-rose-500 to-red-600' },
};

/* ==================================================================
   2)  STATE
   ================================================================== */
const ui = {
  difficulty: 'medium',
  leagueId: null,
  club: null,
};

let tour = null;          // { rounds:[], roundIndex:0 }
let matchCtx = null;      // { you, opp }

function shuffle(a){ a=a.slice(); for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];} return a; }
const rnd = (a,b)=> a + Math.random()*(b-a);

function badge(club, size){
  return `<div class="badge" style="width:${size}px;height:${size}px;font-size:${Math.round(size*0.4)}px;background:linear-gradient(135deg,${club.c1},${club.c2})">${club.en[0]}</div>`;
}

/* ==================================================================
   3)  UI BUILDERS
   ================================================================== */
const diffGrid = document.getElementById('diffGrid');
Object.entries(DIFFS).forEach(([k,d])=>{
  const b = document.createElement('button');
  b.dataset.diff = k;
  b.className = 'py-4 rounded-2xl border border-slate-700 bg-slate-800/60 hover:border-slate-500 transition flex flex-col items-center gap-1';
  b.innerHTML = `<i class="fa-solid ${d.icon} text-2xl mb-1"></i><span class="font-bold text-sm">${d.label}</span>`;
  b.onclick = ()=>{
    ui.difficulty = k;
    [...diffGrid.children].forEach(c=>{
      const on = c.dataset.diff === k;
      c.classList.toggle('border-amber-400', on);
      c.classList.toggle('bg-amber-500/10', on);
      c.classList.toggle('text-amber-300', on);
      c.classList.toggle('border-slate-700', !on);
      c.classList.toggle('bg-slate-800/60', !on);
    });
  };
  diffGrid.appendChild(b);
});
diffGrid.children[1].click();

const leagueGrid = document.getElementById('leagueGrid');
LEAGUES.forEach(l=>{
  const b = document.createElement('button');
  b.dataset.lid = l.id;
  b.className = 'py-4 rounded-2xl border border-slate-700 bg-slate-800/60 hover:border-slate-500 transition flex flex-col items-center gap-1';
  b.innerHTML = `<span class="text-2xl">${l.flag}</span><span class="font-bold text-sm">${l.name}</span>`;
  b.onclick = ()=>{
    ui.leagueId = l.id;
    [...leagueGrid.children].forEach(c=>{
      const on = c.dataset.lid === l.id;
      c.classList.toggle('border-amber-400', on);
      c.classList.toggle('bg-amber-500/10', on);
      c.classList.toggle('border-slate-700', !on);
      c.classList.toggle('bg-slate-800/60', !on);
    });
    document.getElementById('btnGoClub').disabled = false;
  };
  leagueGrid.appendChild(b);
});

document.getElementById('btnGoClub').onclick = ()=>{
  buildClubScreen();
  showScreen('club');
};

function buildClubScreen(){
  const league = LEAGUES.find(l=>l.id===ui.leagueId);
  document.getElementById('clubTitle').innerHTML = `${league.flag} ${league.full}`;
  const grid = document.getElementById('clubGrid');
  grid.innerHTML = '';
  league.clubs.forEach(c=>{
    const b = document.createElement('button');
    b.className = 'club-opt rounded-2xl border-2 border-slate-700 bg-slate-900/70 p-4 flex flex-col items-center gap-2';
    b.innerHTML = `${badge(c,56)}<span class="font-bold text-sm mt-1">${c.ar}</span><span class="text-[11px] text-slate-500">${c.en}</span>`;
    b.onclick = ()=>{
      ui.club = c;
      [...grid.children].forEach(x=>x.classList.remove('sel'));
      b.classList.add('sel');
      document.getElementById('btnGoDraw').disabled = false;
    };
    grid.appendChild(b);
  });
  document.getElementById('btnGoDraw').disabled = true;
}

/* ==================================================================
   4)  TOURNAMENT / DRAW
   ================================================================== */
function makeTeam(c){
  return { ...c, strength: rnd(1,5) };
}

document.getElementById('btnGoDraw').onclick = ()=>{
  const league = LEAGUES.find(l=>l.id===ui.leagueId);
  const you = makeTeam(ui.club);

  // 6 clubs of the league + 2 wildcards from other leagues = 8
  const others = league.clubs.filter(c=>c.en !== ui.club.en).map(makeTeam);
  const pool = LEAGUES.filter(l=>l.id!==league.id).flatMap(l=>l.clubs.map(makeTeam));
  const wildcards = shuffle(pool).slice(0,2);

  const eight = shuffle([you, ...others, ...wildcards]);

  tour = {
    roundIndex: 0,
    you,
    rounds: [
      { name:'ربع النهائي', matches: [
        {a:eight[0],b:eight[1],sa:null,sb:null,winner:null},
        {a:eight[2],b:eight[3],sa:null,sb:null,winner:null},
        {a:eight[4],b:eight[5],sa:null,sb:null,winner:null},
        {a:eight[6],b:eight[7],sa:null,sb:null,winner:null},
      ]},
      { name:'نصف النهائي', matches: [] },
      { name:'النهائي',     matches: [] },
    ]
  };
  renderBracket();
  showScreen('draw');
};

function yourMatch(){
  const r = tour.rounds[tour.roundIndex];
  return r.matches.find(m => m.a.en === tour.you.en || m.b.en === tour.you.en);
}

function renderBracket(){
  const wrap = document.getElementById('bracket');
  wrap.innerHTML = '';
  tour.rounds.forEach((round, ri)=>{
    const col = document.createElement('div');
    col.className = 'bracket-col flex-1 flex flex-col gap-3';
    col.innerHTML = `<div class="text-center text-xs font-bold mb-1 ${ri===tour.roundIndex?'text-amber-400':'text-slate-500'}">${round.name}</div>`;
    if (round.matches.length === 0){
      col.innerHTML += `<div class="flex-1 flex items-center justify-center text-slate-700 text-3xl"><i class="fa-solid fa-hourglass-half"></i></div>`;
    }
    round.matches.forEach((m, mi)=>{
      const isYours = (m.a.en === tour.you.en || m.b.en === tour.you.en);
      const card = document.createElement('div');
      card.className = `rounded-xl border p-2 text-xs ${isYours && !m.winner ? 'border-amber-400/70 bg-amber-500/10' : 'border-slate-700 bg-slate-900/70'} pop`;
      card.style.animationDelay = (mi*45)+'ms';
      const row = (t, s) => {
        const win = m.winner && m.winner.en === t.en;
        const lose = m.winner && m.winner.en !== t.en;
        return `<div class="flex items-center gap-2 py-1 ${lose?'opacity-40':''}">
          ${badge(t, 22)}
          <span class="flex-1 truncate font-bold ${win?'text-emerald-400':''}">${t.ar}</span>
          <span class="tabular-nums font-black ${win?'text-emerald-400':'text-slate-500'}">${s===null?'-':s}</span>
        </div>`;
      };
      card.innerHTML = row(m.a, m.sa) + `<div class="h-px bg-slate-700/70 my-0.5"></div>` + row(m.b, m.sb);
      col.appendChild(card);
    });
    wrap.appendChild(col);
  });

  const r = tour.rounds[tour.roundIndex];
  document.getElementById('roundLabel').textContent =
    (r.matches.some(m=>m.a.en===tour.you.en||m.b.en===tour.you.en) || r.matches.length===0)
      ? `دور: ${r.name}` : r.name;
}

/* ==================================================================
   5)  THREE.JS SCENE
   ================================================================== */
const RINK = { w:20, l:30, hw:10, hl:15, goal:3 };
const PUCK_R = 0.5, PAD_R = 1.0, PUCK_MAX = 44;

let scene, camera, renderer, clock;
let puckMesh, youMesh, aiMesh, netMeshes = [];
let puck = { x:0, z:0, vx:0, vz:0 };
let you  = { x:0, z:11, vx:0, vz:0, tx:0, tz:11 };
let ai   = { x:0, z:-11, vx:0, vz:0, px:0, pz:-11, tx:0, tz:-11, timer:0 };

const game = {
  active:false, phase:'idle', timeLeft:90, countdown:0, goalTimer:0,
  playerScore:0, aiScore:0, overtime:false, pendingEnd:false,
};

const pointer = { x:0, y:0, has:false };
const raycaster = new THREE.Raycaster();
const groundPlane = new THREE.Plane(new THREE.Vector3(0,1,0), -0.35);
const ndc = new THREE.Vector2();
const hitPoint = new THREE.Vector3();

function makeIceTexture(){
  const W=800, H=1200;
  const c = document.createElement('canvas'); c.width=W; c.height=H;
  const g = c.getContext('2d');
  g.fillStyle = '#f3faff'; g.fillRect(0,0,W,H);
  const grd = g.createLinearGradient(0,0,0,H);
  grd.addColorStop(0,'rgba(186,230,253,.7)');
  grd.addColorStop(.5,'rgba(255,255,255,0)');
  grd.addColorStop(1,'rgba(186,230,253,.7)');
  g.fillStyle = grd; g.fillRect(0,0,W,H);

  const toY = z => (z + 15) / 30 * H;
  const toX = x => (x + 10) / 20 * W;

  g.fillStyle = '#3b82f6';
  g.fillRect(0, toY(-5)-5, W, 10);
  g.fillRect(0, toY(5)-5, W, 10);

  g.fillStyle = '#ef4444';
  g.fillRect(0, toY(0)-6, W, 12);

  g.strokeStyle = '#ef4444'; g.lineWidth = 5;
  g.beginPath(); g.arc(toX(0), toY(0), 130, 0, Math.PI*2); g.stroke();

  [[-6,-8],[6,-8],[-6,8],[6,8]].forEach(([x,z])=>{
    g.beginPath(); g.arc(toX(x), toY(z), 88, 0, Math.PI*2); g.stroke();
    g.fillStyle = '#ef4444'; g.beginPath(); g.arc(toX(x), toY(z), 9, 0, Math.PI*2); g.fill();
  });

  g.strokeStyle = '#60a5fa'; g.lineWidth = 5;
  g.beginPath(); g.arc(toX(0), toY(-15), 110, 0, Math.PI); g.stroke();
  g.beginPath(); g.arc(toX(0), toY(15), 110, Math.PI, Math.PI*2); g.stroke();

  const tex = new THREE.CanvasTexture(c);
  tex.anisotropy = 4;
  return tex;
}

function initThree(){
  const canvas = document.getElementById('scene');
  renderer = new THREE.WebGLRenderer({ canvas, antialias:true, alpha:false });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x050b18);
  scene.fog = new THREE.Fog(0x050b18, 55, 110);

  camera = new THREE.PerspectiveCamera(62, window.innerWidth/window.innerHeight, 0.1, 300);
  camera.position.set(0, 32, 19);
  camera.lookAt(0, 0, 0);

  clock = new THREE.Clock();

  /* ---- lights ---- */
  scene.add(new THREE.HemisphereLight(0xbfdbfe, 0x0b1220, 1.15));
  const amb = new THREE.AmbientLight(0xffffff, 0.42); scene.add(amb);

  const dir = new THREE.DirectionalLight(0xffffff, 1.35);
  dir.position.set(10, 34, 14);
  dir.castShadow = true;
  dir.shadow.mapSize.set(1024,1024);
  dir.shadow.camera.left = -24; dir.shadow.camera.right = 24;
  dir.shadow.camera.top = 24;   dir.shadow.camera.bottom = -24;
  dir.shadow.camera.near = 1;   dir.shadow.camera.far = 90;
  dir.shadow.bias = -0.0008;
  scene.add(dir);

  const rim = new THREE.DirectionalLight(0x60a5fa, 0.6);
  rim.position.set(-14, 16, -20);
  scene.add(rim);

  /* ---- ice ---- */
  const iceGeo = new THREE.PlaneGeometry(RINK.w, RINK.l);
  const iceMat = new THREE.MeshStandardMaterial({
    map: makeIceTexture(), roughness: 0.28, metalness: 0.06, color: 0xffffff
  });
  const ice = new THREE.Mesh(iceGeo, iceMat);
  ice.rotation.x = -Math.PI/2;
  ice.receiveShadow = true;
  scene.add(ice);

  /* ---- outer floor for shadow catch ---- */
  const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(120,120),
    new THREE.MeshStandardMaterial({ color:0x0a1424, roughness:1 })
  );
  floor.rotation.x = -Math.PI/2;
  floor.position.y = -0.6;
  scene.add(floor);

  /* ---- boards ---- */
  const boardMat = new THREE.MeshStandardMaterial({ color:0xe2e8f0, roughness:0.55, metalness:0.15 });
  const trimMat  = new THREE.MeshStandardMaterial({ color:0x1d4ed8, roughness:0.4, metalness:0.4, emissive:0x1e3a8a, emissiveIntensity:0.4 });
  const BH = 1.1, BT = 0.55;

  function board(x, z, w, d){
    const b = new THREE.Mesh(new THREE.BoxGeometry(w, BH, d), boardMat);
    b.position.set(x, BH/2, z);
    b.castShadow = true; b.receiveShadow = true;
    scene.add(b);
    const t = new THREE.Mesh(new THREE.BoxGeometry(w, 0.12, d), trimMat);
    t.position.set(x, BH, z);
    scene.add(t);
  }

  board(-RINK.hw - BT/2, 0, BT, RINK.l + BT*2);
  board( RINK.hw + BT/2, 0, BT, RINK.l + BT*2);

  const segW = RINK.hw - RINK.goal;
  const segX = RINK.goal + segW/2;
  board( segX, -RINK.hl - BT/2, segW, BT);
  board(-segX, -RINK.hl - BT/2, segW, BT);
  board( segX,  RINK.hl + BT/2, segW, BT);
  board(-segX,  RINK.hl + BT/2, segW, BT);

  /* ---- goals ---- */
  const postMat = new THREE.MeshStandardMaterial({ color:0xf8fafc, roughness:0.25, metalness:0.7 });
  const netMat  = new THREE.MeshBasicMaterial({ color:0x93c5fd, transparent:true, opacity:0.16, side:THREE.DoubleSide });
  const edgeMat = new THREE.LineBasicMaterial({ color:0x60a5fa, transparent:true, opacity:0.75 });

  [1,-1].forEach(sgn=>{
    const gx = 0, gz = sgn * (RINK.hl + 0.65);
    const grp = new THREE.Group();

    const box = new THREE.BoxGeometry(RINK.goal, 1.9, 1.3);
    const net = new THREE.Mesh(box, netMat);
    net.position.set(gx, 0.95, gz);
    grp.add(net);

    const edges = new THREE.LineSegments(new THREE.EdgesGeometry(box), edgeMat);
    edges.position.copy(net.position);
    grp.add(edges);

    [-1,1].forEach(s=>{
      const post = new THREE.Mesh(new THREE.CylinderGeometry(0.11,0.11,1.9,10), postMat);
      post.position.set(s*RINK.goal, 0.95, sgn*RINK.hl);
      post.castShadow = true;
      grp.add(post);
    });
    scene.add(grp);
    netMeshes.push(grp);
  });

  /* ---- puck ---- */
  const puckGeo = new THREE.CylinderGeometry(PUCK_R, PUCK_R, 0.28, 28);
  const puckMat = new THREE.MeshStandardMaterial({ color:0x0f172a, roughness:0.55, metalness:0.35 });
  puckMesh = new THREE.Mesh(puckGeo, puckMat);
  puckMesh.castShadow = true;
  puckMesh.position.set(0, 0.25, 0);
  scene.add(puckMesh);

  const puckRing = new THREE.Mesh(
    new THREE.TorusGeometry(PUCK_R*0.85, 0.05, 8, 24),
    new THREE.MeshStandardMaterial({ color:0x64748b, roughness:0.3, metalness:0.8 })
  );
  puckRing.rotation.x = Math.PI/2;
  puckRing.position.y = 0.15;
  puckMesh.add(puckRing);

  /* ---- paddles ---- */
  youMesh = makePaddle(0xfbbf24);
  aiMesh  = makePaddle(0xef4444);
  scene.add(youMesh, aiMesh);

  window.addEventListener('resize', onResize);
  onResize();

  window.addEventListener('pointermove', e=>{
    pointer.x = e.clientX; pointer.y = e.clientY; pointer.has = true;
  }, { passive:true });

  animate();
}

function makePaddle(color){
  const g = new THREE.Group();
  const base = new THREE.Mesh(
    new THREE.CylinderGeometry(PAD_R, PAD_R*0.92, 0.36, 36),
    new THREE.MeshStandardMaterial({ color, roughness:0.35, metalness:0.55, emissive:color, emissiveIntensity:0.12 })
  );
  base.position.y = 0.2;
  base.castShadow = true;

  const knob = new THREE.Mesh(
    new THREE.CylinderGeometry(PAD_R*0.52, PAD_R*0.68, 0.34, 30),
    new THREE.MeshStandardMaterial({ color:0xf8fafc, roughness:0.4, metalness:0.4 })
  );
  knob.position.y = 0.53;
  knob.castShadow = true;

  const ring = new THREE.Mesh(
    new THREE.TorusGeometry(PAD_R*0.98, 0.07, 8, 40),
    new THREE.MeshStandardMaterial({ color:0xffffff, roughness:0.2, metalness:0.9, emissive:0xffffff, emissiveIntensity:0.3 })
  );
  ring.rotation.x = Math.PI/2;
  ring.position.y = 0.02;

  g.add(base, knob, ring);
  return g;
}

function onResize(){
  const w = window.innerWidth, h = window.innerHeight;
  const aspect = w/h;
  renderer.setSize(w, h);
  camera.aspect = aspect;
  let fov = 62;
  if (aspect < 1.6){
    const need = Math.atan(Math.tan(THREE.MathUtils.degToRad(62)/2) * (1.6/aspect));
    fov = Math.min(96, THREE.MathUtils.radToDeg(need*2));
  }
  camera.fov = fov;
  camera.updateProjectionMatrix();
}

/* ==================================================================
   6)  SOUND (tiny synth)
   ================================================================== */
let actx = null;
function beep(freq, dur, type='sine', vol=0.055){
  try{
    if(!actx) actx = new (window.AudioContext||window.webkitAudioContext)();
    if (actx.state === 'suspended') actx.resume();
    const o = actx.createOscillator(), g = actx.createGain();
    o.type = type; o.frequency.setValueAtTime(freq, actx.currentTime);
    g.gain.setValueAtTime(vol, actx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + dur);
    o.connect(g); g.connect(actx.destination);
    o.start(); o.stop(actx.currentTime + dur + 0.02);
  }catch(e){}
}

/* ==================================================================
   7)  MATCH FLOW
   ================================================================== */
function beginMatch(){
  const m = yourMatch();
  if(!m) return;
  const opp = (m.a.en === tour.you.en) ? m.b : m.a;
  matchCtx = { you: tour.you, opp };

  game.active = true;
  game.phase = 'countdown';
  game.countdown = 3.0;
  game.timeLeft = 90;
  game.playerScore = 0;
  game.aiScore = 0;
  game.overtime = false;
  game.pendingEnd = false;

  resetPuck(0);
  you.x = 0; you.z = 11; you.vx = 0; you.vz = 0; you.tx = 0; you.tz = 11;
  ai.x = 0; ai.z = -11; ai.vx = 0; ai.vz = 0; ai.tx = 0; ai.tz = -11; ai.timer = 0;

  // colours
  youMesh.children[0].material.color.set(matchCtx.you.c1);
  youMesh.children[0].material.emissive.set(matchCtx.you.c1);
  aiMesh.children[0].material.color.set(matchCtx.opp.c1);
  aiMesh.children[0].material.emissive.set(matchCtx.opp.c1);

  // HUD
  document.getElementById('hudYouBadge').innerHTML = badge(matchCtx.you, 28);
  document.getElementById('hudAiBadge').innerHTML  = badge(matchCtx.opp, 28);
  document.getElementById('hudYouName').textContent = matchCtx.you.ar;
  document.getElementById('hudAiName').textContent  = matchCtx.opp.ar;
  updateHUD();

  document.getElementById('screens').style.display = 'none';
  document.getElementById('hud').classList.remove('hidden');
  setCenterMsg('3');
}

function resetPuck(){
  puck.x = 0; puck.z = 0; puck.vx = 0; puck.vz = 0;
  puckMesh.position.set(0, 0.25, 0);
  puckMesh.visible = true;
  // spread paddles back
  you.x = 0; you.z = 11; you.tx = 0; you.tz = 11;
  ai.x = 0;  ai.z = -11; ai.tx = 0; ai.tz = -11;
}

function updateHUD(){
  document.getElementById('hudScore').textContent = `${game.playerScore} - ${game.aiScore}`;
  const t = Math.max(0, Math.ceil(game.timeLeft));
  document.getElementById('hudTimer').textContent = game.overtime
    ? 'وقت إضافي ⏱'
    : `${Math.floor(t/60)}:${String(t%60).padStart(2,'0')}`;
}

function setCenterMsg(html, sub){
  const el = document.getElementById('centerMsg');
  el.innerHTML = html
    ? `<div class="text-6xl sm:text-8xl font-black text-white drop-shadow-[0_8px_30px_rgba(0,0,0,.9)]">${html}</div>
       ${sub?`<div class="mt-3 text-lg font-bold text-amber-300">${sub}</div>`:''}`
    : '';
}

/* ---------- pointer → rink ---------- */
function pointerToRink(){
  if(!pointer.has) return null;
  ndc.x = (pointer.x / window.innerWidth) * 2 - 1;
  ndc.y = -(pointer.y / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(ndc, camera);
  if (raycaster.ray.intersectPlane(groundPlane, hitPoint)) return hitPoint;
  return null;
}

/* ---------- paddles ---------- */
function updateYouPaddle(dt){
  const p = pointerToRink();
  let tx = you.x, tz = you.z;
  if (p){
    tx = THREE.MathUtils.clamp(p.x, -RINK.hw + PAD_R, RINK.hw - PAD_R);
    tz = THREE.MathUtils.clamp(p.z,  0.9,          RINK.hl - PAD_R - 0.3);
  }
  const dx = tx - you.x, dz = tz - you.z;
  const d = Math.hypot(dx, dz);
  const maxStep = 85 * dt;
  const px = you.x, pz = you.z;
  if (d > 0.0005){
    const step = Math.min(d, maxStep);
    you.x += (dx/d) * step;
    you.z += (dz/d) * step;
  }
  you.vx = (you.x - px) / Math.max(dt, 0.0001);
  you.vz = (you.z - pz) / Math.max(dt, 0.0001);
  youMesh.position.set(you.x, 0, you.z);
}

function updateAIPaddle(dt){
  const diff = DIFFS[ui.difficulty];
  ai.timer -= dt;

  if (ai.timer <= 0){
    ai.timer = diff.react;
    let tx, tz;

    if (puck.z < 1.2){
      // aim point behind the puck, opposite to the player's goal
      const gx = 0 - puck.x;
      const gz = RINK.hl - puck.z;
      const gl = Math.hypot(gx, gz) || 1;
      tx = puck.x - (gx/gl) * (PAD_R + PUCK_R + 0.35);
      tz = puck.z - (gz/gl) * (PAD_R + PUCK_R + 0.35);
      tz = Math.min(tz, -0.95);
      tx += (Math.random()-0.5) * diff.error * 2;
      tz += (Math.random()-0.5) * diff.error;
    } else {
      tx = puck.x * 0.55 + (Math.random()-0.5) * diff.error * 1.4;
      tz = -RINK.hl + 2.6 + (Math.random()-0.5) * diff.error;
    }

    ai.tx = THREE.MathUtils.clamp(tx, -RINK.hw + PAD_R, RINK.hw - PAD_R);
    ai.tz = THREE.MathUtils.clamp(tz, -RINK.hl + PAD_R + 0.3, -0.95);
  }

  const dx = ai.tx - ai.x, dz = ai.tz - ai.z;
  const d = Math.hypot(dx, dz);
  const px = ai.x, pz = ai.z;
  if (d > 0.02){
    const step = Math.min(d, diff.speed * dt);
    ai.x += (dx/d) * step;
    ai.z += (dz/d) * step;
  }
  ai.vx = (ai.x - px) / Math.max(dt, 0.0001);
  ai.vz = (ai.z - pz) / Math.max(dt, 0.0001);
  aiMesh.position.set(ai.x, 0, ai.z);
}

/* ---------- puck physics ---------- */
function collidePaddle(pad, pvx, pvz){
  const dx = puck.x - pad.x;
  const dz = puck.z - pad.z;
  const dist = Math.hypot(dx, dz);
  const minD = PUCK_R + PAD_R;
  if (dist >= minD || dist < 0.0001) return;

  const nx = dx / dist, nz = dz / dist;
  puck.x = pad.x + nx * minD;
  puck.z = pad.z + nz * minD;

  const rvx = puck.vx - pvx;
  const rvz = puck.vz - pvz;
  const rel = rvx*nx + rvz*nz;

  if (rel < 0){
    const e = 1.06;
    puck.vx -= (1+e) * rel * nx;
    puck.vz -= (1+e) * rel * nz;
  }
  // paddle momentum push
  puck.vx += pvx * 0.55;
  puck.vz += pvz * 0.55;

  // ensure a minimum kick away
  const out = puck.vx*nx + puck.vz*nz;
  if (out < 2.5){
    puck.vx += nx * (2.5 - out);
    puck.vz += nz * (2.5 - out);
  }

  const sp = Math.hypot(puck.vx, puck.vz);
  if (sp > PUCK_MAX){ puck.vx *= PUCK_MAX/sp; puck.vz *= PUCK_MAX/sp; }

  beep(160 + Math.min(240, sp*8), 0.06, 'square', 0.035);
}

function updatePuck(dt){
  const damp = Math.pow(0.9885, dt*60);
  puck.vx *= damp; puck.vz *= damp;
  if (Math.abs(puck.vx) < 0.04) puck.vx = 0;
  if (Math.abs(puck.vz) < 0.04) puck.vz = 0;

  puck.x += puck.vx * dt;
  puck.z += puck.vz * dt;

  const inMouth = Math.abs(puck.x) < RINK.goal;

  // side walls
  if (puck.x > RINK.hw - PUCK_R){ puck.x = RINK.hw - PUCK_R; puck.vx = -Math.abs(puck.vx)*0.94; beep(110,0.05,'sine',0.03); }
  if (puck.x < -RINK.hw + PUCK_R){ puck.x = -RINK.hw + PUCK_R; puck.vx = Math.abs(puck.vx)*0.94; beep(110,0.05,'sine',0.03); }

  // end walls (only outside goal mouth)
  if (!inMouth){
    if (puck.z > RINK.hl - PUCK_R){ puck.z = RINK.hl - PUCK_R; puck.vz = -Math.abs(puck.vz)*0.94; beep(110,0.05,'sine',0.03); }
    if (puck.z < -RINK.hl + PUCK_R){ puck.z = -RINK.hl + PUCK_R; puck.vz = Math.abs(puck.vz)*0.94; beep(110,0.05,'sine',0.03); }
  }

  // paddles
  collidePaddle(you, you.vx, you.vz);
  collidePaddle(ai,  ai.vx,  ai.vz);

  // mesh
  puckMesh.position.set(puck.x, 0.25, puck.z);

  // GOAL?
  if (Math.abs(puck.x) < RINK.goal){
    if (puck.z > RINK.hl + 0.4)      return scoreGoal('ai');
    if (puck.z < -RINK.hl - 0.4)     return scoreGoal('player');
  }
}

function scoreGoal(side){
  if (game.phase !== 'playing') return;
  if (side === 'player') game.playerScore++; else game.aiScore++;

  game.phase = 'goal';
  game.goalTimer = 1.35;
  game.pendingEnd = game.overtime;

  puck.vx = 0; puck.vz = 0; puck.x = 0; puck.z = 0;
  puckMesh.visible = false;

  setCenterMsg('هدف!', side === 'player' ? `${matchCtx.you.ar} يسجل` : `${matchCtx.opp.ar} يسجل`);

  const fl = document.getElementById('flashLayer');
  fl.style.background = side === 'player'
    ? 'radial-gradient(circle,rgba(16,185,129,.65),transparent 70%)'
    : 'radial-gradient(circle,rgba(244,63,94,.65),transparent 70%)';
  fl.classList.remove('flash');
  void fl.offsetWidth;
  fl.classList.add('flash');

  beep(side==='player'?880:220, 0.22, 'triangle', 0.07);
  setTimeout(()=>beep(side==='player'?1320:170, 0.28, 'sine', 0.05), 110);
  updateHUD();
}

/* ---------- game loop ---------- */
function updateGame(dt){
  if (!game.active) return;

  if (game.phase === 'countdown'){
    game.countdown -= dt;
    if (game.countdown > 0.6){
      setCenterMsg(String(Math.ceil(game.countdown - 0.6)));
    } else if (game.countdown > 0){
      setCenterMsg('انطلق!', `${matchCtx.you.ar} ضد ${matchCtx.opp.ar}`);
    } else {
      setCenterMsg('');
      game.phase = 'playing';
    }
    updateYouPaddle(dt);
    updateAIPaddle(dt);
    return;
  }

  if (game.phase === 'goal'){
    game.goalTimer -= dt;
    if (game.goalTimer <= 0){
      setCenterMsg('');
      if (game.pendingEnd){ endMatch(); return; }
      resetPuck();
      game.phase = 'playing';
    }
    updateYouPaddle(dt);
    updateAIPaddle(dt);
    return;
  }

  if (game.phase !== 'playing') return;

  // timer
  if (game.timeLeft > 0){
    game.timeLeft -= dt;
    if (game.timeLeft <= 0){
      game.timeLeft = 0;
      if (game.playerScore !== game.aiScore){ endMatch(); return; }
      game.overtime = true;
      setCenterMsg('وقت إضافي', 'الهدف الذهبي يحسم المباراة');
      setTimeout(()=>{ if(game.phase==='playing') setCenterMsg(''); }, 1800);
    }
  }

  updateYouPaddle(dt);
  updateAIPaddle(dt);
  updatePuck(dt);
  updateHUD();
}

function endMatch(){
  game.phase = 'ended';
  game.active = false;

  const won = game.playerScore > game.aiScore;
  const ps = game.playerScore, as = game.aiScore;

  // record result in the bracket
  const round = tour.rounds[tour.roundIndex];
  const m = yourMatch();
  const opp = (m.a.en === tour.you.en) ? m.b : m.a;

  if (m.a.en === tour.you.en){ m.sa = ps; m.sb = as; }
  else { m.sb = ps; m.sa = as; }
  m.winner = won ? tour.you : opp;

  // simulate the remaining matches in this round
  round.matches.forEach(o=>{ if (o !== m && !o.winner) simulateMatch(o); });

  document.getElementById('hud').classList.add('hidden');
  const screens = document.getElementById('screens');
  screens.style.display = 'block';

  if (!won){
    showResult(false, ps, as, `خروج من ${round.name}`);
    return;
  }

  // build the next round
  const winners = round.matches.map(x=>x.winner);
  if (tour.roundIndex < 2){
    const next = tour.rounds[tour.roundIndex + 1];
    next.matches = [];
    for (let i = 0; i < winners.length; i += 2){
      next.matches.push({ a: winners[i], b: winners[i+1], sa:null, sb:null, winner:null });
    }
    tour.roundIndex++;
    showResult(true, ps, as, `تأهلت إلى ${tour.rounds[tour.roundIndex].name}`);
  } else {
    showResult(true, ps, as, '🏆 بطل البطولة!');
  }
}

function simulateMatch(m){
  const pa = m.a.strength / (m.a.strength + m.b.strength);
  const aWins = Math.random() < pa;
  const wScore = 3 + Math.floor(Math.random()*4);
  const lScore = Math.floor(Math.random()*Math.min(wScore, 3));
  if (aWins){ m.sa = wScore; m.sb = lScore; m.winner = m.a; }
  else      { m.sb = wScore; m.sa = lScore; m.winner = m.b; }
}

/* ==================================================================
   8)  RESULT SCREEN
   ================================================================== */
let resultAction = null;

function showResult(won, ps, as, sub){
  const isChampion = won && tour.roundIndex === 2;
  const icon  = document.getElementById('resultIcon');
  const title = document.getElementById('resultTitle');
  const subEl = document.getElementById('resultSub');
  const score = document.getElementById('resultScore');
  const btn   = document.getElementById('btnResult');

  if (isChampion){
    icon.textContent = '🏆';
    title.textContent = 'أنت البطل!';
    title.className = 'text-3xl font-black mb-2 text-amber-400';
    btn.textContent = 'بطولة جديدة';
    btn.className = 'w-full px-8 py-4 rounded-2xl font-black text-lg bg-gradient-to-r from-amber-400 to-yellow-500 text-slate-950 hover:brightness-110 transition';
    resultAction = () => location.reload();
  } else if (won){
    icon.textContent = '✅';
    title.textContent = 'فوز!';
    title.className = 'text-3xl font-black mb-2 text-emerald-400';
    btn.textContent = 'متابعة البطولة';
    btn.className = 'w-full px-8 py-4 rounded-2xl font-black text-lg bg-gradient-to-r from-emerald-500 to-teal-600 text-white hover:brightness-110 transition';
    resultAction = () => { renderBracket(); showScreen('draw'); };
  } else {
    icon.textContent = '❌';
    title.textContent = 'خسارة';
    title.className = 'text-3xl font-black mb-2 text-rose-400';
    btn.textContent = 'بطولة جديدة';
    btn.className = 'w-full px-8 py-4 rounded-2xl font-black text-lg bg-gradient-to-r from-rose-500 to-red-600 text-white hover:brightness-110 transition';
    resultAction = () => location.reload();
  }

  subEl.textContent = `${matchCtx.you.ar} ضد ${matchCtx.opp.ar} — ${sub}`;
  score.innerHTML = `<span class="${won?'text-emerald-400':'text-rose-400'}">${ps}</span>
                     <span class="text-slate-600">-</span>
                     <span class="text-slate-300">${as}</span>`;
  showScreen('result');
  beep(won?740:180, 0.3, 'triangle', 0.06);
}

document.getElementById('btnResult').onclick = ()=> resultAction && resultAction();

/* ==================================================================
   9)  SCREEN SWITCHER + LOOP
   ================================================================== */
function showScreen(id){
  document.querySelectorAll('.screen').forEach(s=>{
    s.classList.add('hidden');
    s.classList.remove('flex');
  });
  const el = document.getElementById('screen-' + id);
  el.classList.remove('hidden');
  el.classList.add('flex');
}

function quitMatch(){
  if (!confirm('هل تريد الخروج من المباراة؟ سيتم اعتبارها خسارة.')) return;
  game.active = false;
  game.phase = 'ended';
  document.getElementById('hud').classList.add('hidden');
  document.getElementById('screens').style.display = 'block';
  showResult(false, game.playerScore, game.aiScore, 'انسحاب');
}

function animate(){
  requestAnimationFrame(animate);
  const dt = Math.min(clock.getDelta(), 0.045);
  updateGame(dt);
  renderer.render(scene, camera);
}

/* ---------- boot ---------- */
initThree();
showScreen('start');
</script>
</body>
</html>

Game Source: دوري الهوكي 2026 — Three.js

Creator: GoldenGlider99

Libraries: three

Complexity: complex (1066 lines, 42.1 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 2026-three-js-goldenglider99" to link back to the original. Then publish at arcadelab.ai/publish.