diff --git a/www/html/Game/public/img/gauntlet-assets/score.png b/www/html/Game/public/img/gauntlet-assets/score.png index 7d629c9..db271f3 100644 Binary files a/www/html/Game/public/img/gauntlet-assets/score.png and b/www/html/Game/public/img/gauntlet-assets/score.png differ diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 4a6509d..0311eff 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -960,6 +960,7 @@ return alignWorld; } + /** * คอลัมน์บนกริดที่เส้น FINISH ต้องแนบ (map world px) * gauntletCrownRunwayBg.finishAlignMapTileX | finishAlignMapTileFrac — ถ้าไม่ตั้ง ใช้ตำแหน่งตัวนำ @@ -9589,7 +9590,7 @@ if (mg2Jump) { const hud = document.getElementById('play-cyber-hud'); if (hud && hud.classList.contains('play-cyber-hud--quiz-tf-mock')) updateQuizTfMockScale(hud); - else syncMg2MockJumpButtonBoundsPlay(); + syncMg2MockJumpButtonBoundsPlay(); } else { btn.classList.remove('mg2-jump'); } @@ -11134,13 +11135,80 @@ const jm = Math.max(4, jumpTicksMax || 16); const a = Math.max(0, Math.min(jm, Number(air) || 0)); const t = a / jm; - const peakAt = 0.28; - if (t >= peakAt) { - const span = 1 - peakAt; - const u = span > 0 ? (t - peakAt) / span : 0; - return Math.max(0, 1 - u * u * 1.2); + /* อาร์คกระโดดนุ่มแบบ design (rn-jump): sine โค้งสมมาตร ease-in-out peak กลางอากาศ + เดิม: ขาขึ้น quadratic / ขาลง linear (อสมมาตร peak ~72% เวลา → ขาลงกระตุก) */ + const p = Math.max(0, Math.min(1, 1 - t)); /* 0 = เริ่มกระโดด, 1 = แตะพื้น */ + return Math.sin(Math.PI * p); + } + + /* ===== Minigame-2: ชื่อ+คะแนนใต้ตัวละคร + เลข -10 เป็น DOM overlay (sync world→screen) แทนวาดบน canvas ===== + เรียกทุกเฟรมจาก draw() (ส่ง worldToScreen/zDraw/tileSize เข้ามา) — container map buffer→display ด้วย transform:scale */ + const gauntletDomLabelPool = new Map(); /* id -> .gnt-label (pool ไว้ reuse) */ + const gauntletPenaltySeenById = new Map(); /* id -> ts penalty ล่าสุด (กัน spawn float ซ้ำ) */ + function syncGauntletNameDom(worldToScreenFn, zDrawVal, tileSizePx) { + const ov = document.getElementById('gauntlet-name-overlay'); + if (!ov) return; + const live = isGauntletCrownHeistMapPlay() && gauntletCrownPregamePhase === 'live'; + if (!live || typeof worldToScreenFn !== 'function') { + if (!ov.classList.contains('is-hidden')) { + ov.classList.add('is-hidden'); + gauntletDomLabelPool.forEach((el) => { if (el.parentNode) el.parentNode.removeChild(el); }); + gauntletDomLabelPool.clear(); + } + return; } - return Math.min(1, t / peakAt); + ov.classList.remove('is-hidden'); + const canvas = document.getElementById('game-canvas'); + const stage = document.getElementById('play-canvas-stage'); + if (!canvas || !stage) return; + const cr = canvas.getBoundingClientRect(); + const srect = stage.getBoundingClientRect(); + const dispScale = canvas.width > 0 ? cr.width / canvas.width : 1; + ov.style.left = Math.round(cr.left - srect.left) + 'px'; + ov.style.top = Math.round(cr.top - srect.top) + 'px'; + ov.style.width = canvas.width + 'px'; + ov.style.height = canvas.height + 'px'; + ov.style.transform = 'scale(' + dispScale + ')'; + const fp = (typeof getCharacterFootprintWH === 'function') ? getCharacterFootprintWH(mapData) : { cw: 1, ch: 1 }; + const cwv = fp.cw || 1, chv = fp.ch || 1; + function v(n) { const x = Number(n); return Number.isFinite(x) ? x : 0; } + const seen = new Set(); + function place(id, ent, isMe) { + if (!ent) return; + if (typeof shouldGauntletCrownHeistSkipAvatarDrawPlay === 'function' + && shouldGauntletCrownHeistSkipAvatarDrawPlay(ent, isMe, 0, 0)) return; + seen.add(id); + const air = isMe ? (meGauntletJumpVis || 0) : ((ent.gauntletJumpVis != null ? ent.gauntletJumpVis : ent.gauntletJumpTicks) || 0); + const liftWorldY = air > 0 ? gauntletLiftHeightNorm(air, gauntletRuntimeJumpTicks) * tileSizePx * 0.52 : 0; + const cxWorld = (v(ent.x) + cwv * 0.5) * tileSizePx; + const cyBottomWorld = (v(ent.y) + chv) * tileSizePx - liftWorldY; + const scr = worldToScreenFn(cxWorld, cyBottomWorld); + const sx = scr[0], sy = scr[1]; + /* ชื่อ/คะแนนใต้ตัวละคร: ตัดออกตาม mock — mock โชว์ชื่อ+คะแนนเฉพาะ "แถบคะแนนบน" เท่านั้น ไม่มีใต้ตัวละคร + (กันดูเหมือน 2 layer ทับกัน) — คงไว้เฉพาะ -10 penalty float ที่ mock มี */ + const pen = Number(ent.gauntletCrownPenaltyFxUntil) || 0; + /* -10 float ต้องอยู่ "ใต้แถบคะแนนบน" เท่านั้น — กันผู้เล่นที่จุดเริ่ม (project ใกล้แถบคะแนน เช่น AFK) ทำ float โผล่ทับ SCORE label */ + var sbEl = document.querySelector('.play-cyber-scoreboard'); + var sbBottomBuf = sbEl ? ((sbEl.getBoundingClientRect().bottom - cr.top) / (dispScale || 1) + 6) : 0; + if (pen > (gauntletPenaltySeenById.get(id) || 0) && (sy - 28) > sbBottomBuf) { + gauntletPenaltySeenById.set(id, pen); + /* -10 float = design asset score-.png (รูป "-10" สำเร็จรูป) ไม่ใช่ text สไตล์เอง */ + const pf = document.createElement('img'); + pf.className = 'gnt-pen'; + pf.src = GAUNTLET_CROWN_SCORE_POP_URL; + pf.alt = '-10'; + pf.decoding = 'async'; + pf.style.left = Math.round(sx) + 'px'; + pf.style.top = Math.round(sy - 28) + 'px'; + ov.appendChild(pf); + setTimeout(function () { if (pf.parentNode) pf.parentNode.removeChild(pf); }, 900); + } else if (pen > (gauntletPenaltySeenById.get(id) || 0)) { + /* อยู่บริเวณแถบคะแนน — บันทึกว่าเห็นแล้ว ไม่ต้องสร้าง float (กัน spawn ซ้ำตอนเลื่อนลงมา) */ + gauntletPenaltySeenById.set(id, pen); + } + } + if (others && typeof others.forEach === 'function') others.forEach((o, id) => place(id, o, false)); + place('__me', me, true); } function getGauntletObsDrawPositionsAt(nowMs) { @@ -11454,6 +11522,42 @@ if (socket && socket.connected) socket.emit('gauntlet-crown-lobby-ready', { ready: true }); } + /** Minigame-2 mno9kb07 — HOW TO PLAY ใช้ layout mock 01-howto (canvas = mg-bg) */ + function gauntletCrownHowtoUsesMg2LayoutPlay() { + return !!isGauntletCrownHeistMapPlay(); + } + function getGauntletCrownHowtoStatusEl() { + if (gauntletCrownHowtoUsesMg2LayoutPlay()) { + return document.getElementById('gauntlet-crown-howto-status-mg2'); + } + return document.getElementById('gauntlet-crown-howto-status'); + } + function getGauntletCrownHowtoBtnEl() { + if (gauntletCrownHowtoUsesMg2LayoutPlay()) { + return document.getElementById('btn-gch-ready-mg2'); + } + return document.getElementById('btn-gch-ready'); + } + function setPlayMg2HowtoLivePlay(on) { + document.documentElement.classList.toggle('play-mg2-howto-live', !!on); + } + function clearGchMg2OverlayBoundsPlay(ov) { + ov = ov || document.getElementById('gauntlet-crown-howto-overlay'); + if (!ov) return; + ['position', 'left', 'top', 'width', 'height', 'right', 'bottom', 'transform'].forEach((p) => { + ov.style.removeProperty(p); + }); + } + function dismissGauntletCrownHowtoOverlayPlay() { + const howto = document.getElementById('gauntlet-crown-howto-overlay'); + if (howto) { + howto.classList.add('is-hidden'); + setPlayMg2HowtoLivePlay(false); + clearGchMg2OverlayBoundsPlay(howto); + } + gauntletCrownHowtoVisible = false; + } + /** ภารกิจคำถาม mng8a80o — Ready Status / ปุ่ม แบบเดียวกับ Mega Virus (gauntlet-crown-lobby-sync) */ function updateQuizQuestionMissionHowtoHud() { if (!isQuizQuestionMissionUiMapPlay() || quizQuestionMissionPhase !== 'howto') return; @@ -11476,8 +11580,8 @@ } function updateGauntletCrownHowtoHud() { - const st = document.getElementById('gauntlet-crown-howto-status'); - const btn = document.getElementById('btn-gch-ready'); + const st = getGauntletCrownHowtoStatusEl(); + const btn = getGauntletCrownHowtoBtnEl(); if (!btn || !usesCrownLobbyShellPlay() || gauntletCrownPregamePhase !== 'howto') return; const humans = quizCarryPregameHumanIds(); const tot = Math.max(1, quizCarryPregameTotalPlayers()); @@ -11488,6 +11592,15 @@ } const humansReady = humans.length > 0 && humans.every((id) => !!gauntletCrownLobbyReadyMap[id]); btn.classList.toggle('is-start-phase', humansReady); + if (gauntletCrownHowtoUsesMg2LayoutPlay()) { + const btnImg = document.getElementById('btn-gch-ready-mg2-img'); + if (btnImg) { + btnImg.src = humansReady + ? (missionMockHudAssetUrl('btn-start.png') || (BASE + '/img/gauntlet-assets/btn-start.png')) + : (missionMockHudAssetUrl('btn-ready.png') || (BASE + '/img/gauntlet-assets/btn-ready.png')); + btnImg.alt = humansReady ? 'START' : 'READY'; + } + } btn.classList.toggle('is-read-only', !isMePlayHost()); btn.disabled = !isMePlayHost(); btn.setAttribute('aria-pressed', humansReady ? 'false' : ((myId && gauntletCrownLobbyReadyMap[String(myId)]) ? 'true' : 'false')); @@ -11507,9 +11620,7 @@ applyGauntletPreviewSpawnLayout(false); } trySnapGauntletCrownRunwayScrollToSpawnsPlay(); - const howto = document.getElementById('gauntlet-crown-howto-overlay'); - if (howto) howto.classList.add('is-hidden'); - gauntletCrownHowtoVisible = false; + dismissGauntletCrownHowtoOverlayPlay(); const cd = document.getElementById('gauntlet-crown-countdown'); const numEl = document.getElementById('gauntlet-crown-countdown-num'); function applyMegaBalloonLiveStartPlay() { @@ -11600,6 +11711,11 @@ if (!ov) return; gauntletCrownHowtoVisible = true; ov.classList.remove('is-hidden'); + if (isGauntletCrownHeistMapPlay()) { + setPlayMg2HowtoLivePlay(true); + updateGchTfMockScale(ov); + trySnapGauntletCrownRunwayScrollToSpawnsPlay(); + } if (socket && socket.connected) socket.emit('gauntlet-crown-lobby-sync-request'); gauntletCrownSyncGuestReadyIfNeeded(); updateGauntletCrownHowtoHud(); @@ -11683,7 +11799,7 @@ if (nick !== 0) return nick; return String(a.id).localeCompare(String(b.id)); }); - const top = baseRows.slice(0, 5); + const top = baseRows.slice(0, 6); /* Last Light รองรับ 6 ผู้เล่น — summary ต้องครบ 6 */ const ranked = top.map((row, idx) => { const pos = idx + 1; const rankBonus = gauntletCrownRankBonusLocal(pos); @@ -11757,6 +11873,17 @@ eliminated: !!o.gauntletEliminated, }); }); + /* รวมตัวเรา (human) ถ้ายังไม่อยู่ — บาง mission (preview/จาก server) มีแต่บอท ทำให้ตัวเราตกหายจาก summary */ + if (myId != null && !seen.has(String(myId))) { + seen.add(String(myId)); + baseRows.push({ + id: myId, + nickname: (me.nickname || nick || 'คุณ').trim() || 'คุณ', + characterId: me.characterId ?? null, + baseScore: me.gauntletEliminated ? 0 : Math.max(0, Number(me.gauntletScore) | 0), + eliminated: !!me.gauntletEliminated, + }); + } baseRows.sort((a, b) => { if (b.baseScore !== a.baseScore) return b.baseScore - a.baseScore; @@ -11764,7 +11891,7 @@ if (nick !== 0) return nick; return String(a.id).localeCompare(String(b.id)); }); - const top = baseRows.slice(0, 5); + const top = baseRows.slice(0, 6); /* Last Light รองรับ 6 ผู้เล่น — summary ต้องครบ 6 รวมตัวเรา (เดิม 5 ตัดคนคะแนนต่ำสุด/ตายออก) */ const ranked = top.map((row, idx) => { const pos = idx + 1; const rankBonus = gauntletCrownRankBonusLocal(pos); @@ -19075,6 +19202,10 @@ /* ตัวละครที่ไม่มีไฟล์เลเยอร์สี (เช่น defaultChar) — compose คืน Image ดิบ → crop หน้าจากรูปดิบตรง ๆ กันช่องว่าง */ let drawSrc = srcCanvas; if (!drawSrc && raw && raw.complete && raw.naturalWidth > 0 && raw.naturalHeight > 0) { + /* ตัวที่มีไฟล์เลเยอร์สี (tintable) แต่ composite ยังไม่พร้อม หรือ API characters ยังโหลดไม่เสร็จ → + คืน '' ให้ applyCyberHudScoreAvatarImgWithRetry retry รอเลเยอร์โหลด (avatar bot จะไม่ถูกรับจนกว่าจะเป็น data URL ที่ย้อมสีแล้ว) + อย่า fallback raw ที่ "ไม่มีสี" ไม่งั้น avatar ทุกคนเหมือนกัน = "สีไม่ขึ้น"; ใช้ raw เฉพาะตัวที่ API ยืนยันว่าไม่มีเลเยอร์ (defaultChar) */ + if (playCharLayerApi.status !== 'done' || playCharLayerFromApi(cid) === true) return ''; drawSrc = raw; } if (!drawSrc) return ''; @@ -19336,6 +19467,7 @@ if (Math.abs(s - _quizTfScaleLast) < 0.0005 && root.dataset.qtfStackW === String(Math.round(cw)) && root.dataset.qtfStackH === String(Math.round(ch))) { + syncMg2MockJumpButtonBoundsPlay(r, s); return; } _quizTfScaleLast = s; @@ -19406,10 +19538,33 @@ const s = missionMockOverlayScalePlay(); if (s > 0 && isFinite(s)) ov.style.setProperty('--gcm-tf-scale', String(s)); } - /* popup HOW TO PLAY (01-howto) — scale stage 1920×1080 ตามสูตรเดียวกัน */ + /* popup HOW TO PLAY (01-howto) — scale stage 1920×1080 · MG2 ยึด #play-canvas-stack เหมือน HUD */ function updateGchTfMockScale(ov) { ov = ov || document.getElementById('gauntlet-crown-howto-overlay'); if (!ov) return; + const mg2 = ov.classList.contains('gch-mg2-mock'); + if (mg2) { + const stack = document.getElementById('play-canvas-stack'); + if (!stack) return; + const r = stack.getBoundingClientRect(); + const cw = Math.max(1, r.width || stack.clientWidth || 0); + const ch = Math.max(1, r.height || stack.clientHeight || 0); + const s = Math.min(cw / 1920, ch / 1080); + if (!(s > 0 && isFinite(s))) return; + ov.style.position = 'fixed'; + ov.style.left = Math.round(r.left) + 'px'; + ov.style.top = Math.round(r.top) + 'px'; + ov.style.width = Math.round(cw) + 'px'; + ov.style.height = Math.round(ch) + 'px'; + ov.style.right = 'auto'; + ov.style.bottom = 'auto'; + ov.style.transform = 'none'; + ov.style.setProperty('--gch-tf-scale', String(s)); + const stage = ov.querySelector('.gch-mg2-stage'); + if (stage) stage.setAttribute('aria-hidden', 'false'); + return; + } + clearGchMg2OverlayBoundsPlay(ov); const s = missionMockOverlayScalePlay(); if (s > 0 && isFinite(s)) ov.style.setProperty('--gch-tf-scale', String(s)); } @@ -19503,7 +19658,8 @@ /** แถบ SCORE แนวนอน (mno9kb07) — ใช้รูปแบบเดียวกับ quiz_carry / พรีวิว editor */ root.classList.toggle( 'play-cyber-hud--gauntlet-crown-strip', - !!(on && (isGauntletCrownHeistMapPlay() || isQuizCarry())), + !!(on && (isGauntletCrownHeistMapPlay() || isQuizCarry()) + && !(missionMockOn && getMissionMockHudSkinPlay() === 'gauntlet_crown')), ); root.classList.toggle('play-cyber-hud--score-flush-left', !!(on && isQuizCarry())); root.classList.toggle('play-cyber-hud--jump-mission-hide-score', !!isJumpSurviveMissionUiMapPlay()); @@ -19895,7 +20051,9 @@ return a.y - b.y; }); - const cap = stripScoreHud ? (isQuizCarry() ? 8 : 6) : 8; + /* Minigame-2 รองรับสูงสุด 6 ผู้เล่น (GAUNTLET_MAX_PLAYERS) — แถบคะแนนต้องโชว์ครบ 6 รวมตัวเรา + (เดิม cap 5 ตาม mockup ที่วาดตัวอย่าง 5 คน ทำให้ตัวเราคะแนนต่ำสุดถูกตัดออก) */ + const cap = mg2MockHud ? 6 : (stripScoreHud ? (isQuizCarry() ? 8 : 6) : 8); let hudRows = rows.slice(0, Math.min(cap, rows.length)); if (stripScoreHud) { const mi = hudRows.findIndex((row) => row.isMe); @@ -20719,11 +20877,17 @@ ctx.font = '10px sans-serif'; ctx.textAlign = 'center'; const nameBase = name || ''; + /* gauntlet (Last Light) live: ชื่อ/คะแนน/-10 ใช้ DOM overlay (syncGauntletNameDom) — ไม่วาดบน canvas */ + /* crown map (Minigame-2): ไม่วาดชื่อ/คะแนน/-10 ใต้ตัวละครบน canvas "ทุกเฟส" (ตรง mock — โชว์เฉพาะแถบคะแนนบน) + เดิมเช็คเฉพาะ phase==='live' → ตอน summary/howto canvas วาดชื่อกลับมา ทับแถบคะแนน (ดูเหมือน avatar ซ้อน) */ + const gntDomLabel = isGauntletCrownHeistMapPlay(); const withScore = isGauntlet() && typeof gauntletScoreLabel === 'number' && gauntletScoreLabel >= 0 ? (nameBase + ' · ' + gauntletScoreLabel) : nameBase; - ctx.fillText(withScore, sx, sy + 10); - if (isGauntletCrownHeistMapPlay() + if (!gntDomLabel) ctx.fillText(withScore, sx, sy + 10); + if (!gntDomLabel + && isGauntletCrownHeistMapPlay() + && gauntletCrownPregamePhase === 'live' && typeof crownPenaltyFxUntil === 'number' && crownPenaltyFxUntil > timeMs) { const penImg = ensureGauntletCrownScorePenaltyImgPlay(); @@ -20847,7 +21011,10 @@ const ga = _quizGhostScreenAnchorPlay(axO, ayO, worldToScreen, zDraw); drawQuizWrongGhostOverlayPlay(ga.sx, ga.sy, ga.sizeH, id, peerName); } else { - drawAvatar(axO, ayO, false, peerName, o.characterId, faceDirOther, otherWalk, ot, (o.gauntletJumpVis != null ? o.gauntletJumpVis : o.gauntletJumpTicks) || 0, o.gauntletScore || 0, quizCarrySignForEntity(o), isGauntletCrownHeistMapPlay() ? (o.gauntletCrownPenaltyFxUntil || 0) : 0); + drawAvatar(axO, ayO, false, peerName, o.characterId, faceDirOther, otherWalk, ot, (o.gauntletJumpVis != null ? o.gauntletJumpVis : o.gauntletJumpTicks) || 0, + (isGauntletCrownHeistMapPlay() && gauntletCrownPregamePhase === 'live') ? (o.gauntletScore || 0) : undefined, + quizCarrySignForEntity(o), + (isGauntletCrownHeistMapPlay() && gauntletCrownPregamePhase === 'live') ? (o.gauntletCrownPenaltyFxUntil || 0) : 0); } if (botOut) ctx.restore(); } else { @@ -20863,11 +21030,16 @@ const gam = _quizGhostScreenAnchorPlay(axMe, ayMe, worldToScreen, zDraw); drawQuizWrongGhostOverlayPlay(gam.sx, gam.sy, gam.sizeH, myId, me.nickname + meTag); } else { - drawAvatar(axMe, ayMe, true, me.nickname + meTag, me.characterId, faceDirMe, meWalking, mt, meGauntletJumpVis, me.gauntletScore || 0, quizCarrySignForEntity(me), isGauntletCrownHeistMapPlay() ? (me.gauntletCrownPenaltyFxUntil || 0) : 0); + drawAvatar(axMe, ayMe, true, me.nickname + meTag, me.characterId, faceDirMe, meWalking, mt, meGauntletJumpVis, + (isGauntletCrownHeistMapPlay() && gauntletCrownPregamePhase === 'live') ? (me.gauntletScore || 0) : undefined, + quizCarrySignForEntity(me), + (isGauntletCrownHeistMapPlay() && gauntletCrownPregamePhase === 'live') ? (me.gauntletCrownPenaltyFxUntil || 0) : 0); } if (isJumpSurvive() && jumpSurviveEliminated) ctx.restore(); } }); + /* Minigame-2: อัปเดต DOM overlay (ชื่อ/คะแนน/-10) ให้ตรงตำแหน่งตัวละครบน canvas */ + try { syncGauntletNameDom(worldToScreen, zDraw, tileSize); } catch (eGntDom) { /* ignore */ } } /* ไอคอนคำถามพิเศษในฉาก (วาดทับ map/ผู้เล่น) + ตรวจการเดินชน */ if (specialQuizIcon) { @@ -20896,10 +21068,8 @@ ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; if (isGauntletCrownHeistMapPlay()) { - ctx.fillText('พรมแดงสุดท้าย | คะแนน ' + (me.gauntletScore || 0) + ' · ชน -10 · ขอบซ้ายตาย · สูงสุด 6 คน', 10, 24); - ctx.font = '12px sans-serif'; - ctx.fillStyle = '#c0caf5'; - ctx.fillText('Space / W / ↑ = กระโดด · เลน + เลเซอร์ · จบเกมมีโบนัสอันดับ + เกรดทีม', 10, 42); + /* Minigame-2 (Last Light): ใช้ cyber HUD + DOM overlay แล้ว — ไม่วาด debug text (คะแนน · ชน -10 · ขอบซ้ายตาย) บน canvas + (เดิมโผล่มุมซ้ายบนทับ SCORE label เวลา useCyberPlayHud() = false ใน preview embed) */ } else { ctx.fillText('พรมแดงสุดท้าย | คะแนน: ' + (me.gauntletScore || 0) + ' (ข้าม 1 ชิ้น +1) · สูงสุด 6 คน', 10, 24); ctx.font = '12px sans-serif'; @@ -21508,9 +21678,7 @@ })(); (function wireGauntletCrownHowtoPrimary() { - const btn = document.getElementById('btn-gch-ready'); - if (!btn) return; - btn.addEventListener('click', () => { + function onHowtoPrimaryClick() { if (isJumpSurviveMissionUiMapPlay()) { if (jumpSurviveMissionPhase !== 'howto') return; const humans = quizCarryPregameHumanIds(); @@ -21654,6 +21822,10 @@ gauntletCrownLobbyReadyMap[sid] = next; updateGauntletCrownHowtoHud(); socket.emit('gauntlet-crown-lobby-ready', { ready: next }); + } + ['btn-gch-ready', 'btn-gch-ready-mg2'].forEach((id) => { + const btn = document.getElementById(id); + if (btn) btn.addEventListener('click', onHowtoPrimaryClick); }); })(); diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index c6e0ac5..b517701 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -21,6 +21,9 @@ + + +