diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index 7e8fefe..72aa2b2 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -365,6 +365,11 @@ var ams = Number(data.carryAnswerMs); ansSec.value = String(Number.isFinite(ams) ? Math.round(ams / 1000) : 5); } + var sessLen = el('quiz-carry-session-len'); + if (sessLen) { + var sl = parseInt(String(data.carrySessionLength), 10); + sessLen.value = String(Number.isFinite(sl) && sl >= 0 ? sl : 0); + } setMsg('quiz-carry-settings-msg', '', ''); }).catch(function (e) { setMsg('quiz-carry-settings-msg', e.message || 'โหลดไม่ได้', 'error'); @@ -416,11 +421,16 @@ if (ansSec > 300) ansSec = 300; var carryReadMs = readSec * 1000; var carryAnswerMs = ansSec * 1000; + var sessEl = el('quiz-carry-session-len'); + var carrySessionLength = sessEl ? parseInt(String(sessEl.value || '0'), 10) : 0; + if (!Number.isFinite(carrySessionLength) || carrySessionLength < 0) carrySessionLength = 0; + if (carrySessionLength > 500) carrySessionLength = 500; gameQuizFetch('PUT', { carryQuestions: carryQuestions, carryReadMs: carryReadMs, carryAnswerMs: carryAnswerMs, + carrySessionLength: carrySessionLength, }).then(function () { setMsg('quiz-carry-settings-msg', 'บันทึกแล้ว', 'ok'); }).catch(function (e) { diff --git a/www/html/Admin/api/game-quiz-settings.php b/www/html/Admin/api/game-quiz-settings.php index f15cf3f..7fb7a8e 100644 --- a/www/html/Admin/api/game-quiz-settings.php +++ b/www/html/Admin/api/game-quiz-settings.php @@ -46,7 +46,7 @@ function merge_quiz_settings_disk_from_put(string $rawBody): bool } } } - $mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'questions', 'carryQuestions', 'battleQuizMcq']; + $mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'questions', 'carryQuestions', 'battleQuizMcq']; foreach ($mergeKeys as $k) { if (array_key_exists($k, $patch)) { $base[$k] = $patch[$k]; diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index f87202a..570e03d 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -175,6 +175,10 @@ นับจากเมื่อหยิบได้ · สูงสุด 300 วิ +

รายการคำถาม

diff --git a/www/html/Game/public/img/quiz-carry/btn-ready.png b/www/html/Game/public/img/quiz-carry/btn-ready.png new file mode 100644 index 0000000..6b50bfc Binary files /dev/null and b/www/html/Game/public/img/quiz-carry/btn-ready.png differ diff --git a/www/html/Game/public/img/quiz-carry/btn-start.png b/www/html/Game/public/img/quiz-carry/btn-start.png new file mode 100644 index 0000000..cdc7776 Binary files /dev/null and b/www/html/Game/public/img/quiz-carry/btn-start.png differ diff --git a/www/html/Game/public/img/quiz-carry/howto.png b/www/html/Game/public/img/quiz-carry/howto.png new file mode 100644 index 0000000..e45cc2d Binary files /dev/null and b/www/html/Game/public/img/quiz-carry/howto.png differ diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 260b7a4..fa7b08a 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -569,6 +569,15 @@ let quizCarryOptionRevealAt = 0; let quizCarryAnswerCloseAt = 0; let quizCarryCarryTimingMs = { carryReadMs: 3000, carryAnswerMs: 5000 }; + /** พรีวิว (รวม editor embed): จำนวนข้อที่เล่นก่อนโอเวอร์เลย์จบ — 0 = ไม่จบอัตโนมัติ */ + let quizCarrySessionLength = 0; + let quizCarryRoundsCompleted = 0; + let quizCarrySessionEnded = false; + /** embed พรีวิว: รอ Ready / START ของโฮสต์ก่อนนับ 3–2–1 */ + let quizCarryPregameActive = false; + let playHostId = null; + /** ซิงก์จากเซิร์ฟเวอร์: socketId → กด Ready แล้ว */ + let quizCarryLobbyReadyMap = {}; /** jump_survive — กล้องตามตัวในกรอบ + แพลตฟอร์มเลื่อนขึ้น (scroll) ไม่ลากทั้งฉาก */ let jumpSurviveCamCenterX = 0; @@ -789,6 +798,12 @@ return Math.max(minV, Math.min(maxV, Math.floor(v))); } + function clampCarrySessionLen(n, def) { + const v = Number(n); + if (Number.isNaN(v)) return def; + return Math.max(0, Math.min(500, Math.floor(v))); + } + function buildQuizPoolFromMap(md) { if (!md || !Array.isArray(md.quizQuestions)) return []; return md.quizQuestions @@ -1157,6 +1172,7 @@ if (mapData.gameType === 'balloon_boss') { applyBalloonBossSpawnLayoutPlay(); } + if (quizCarryPregameActive && mapData && mapData.gameType === 'quiz_carry') updateQuizCarryPregameHud(); } function pickRandomWalkableCellInAnswerGrid(grid, o) { @@ -1863,6 +1879,120 @@ return !!(previewMode && editorEmbedReturn && isQuizCarry() && quizCarryEmbedCountdownEndAt > Date.now()); } + function isQuizCarryEmbedLobbyBlockingMovement() { + return !!(previewMode && editorEmbedReturn && isQuizCarry() && quizCarryPregameActive); + } + + function quizCarryPregameHumanIds() { + const ids = []; + if (myId != null && !isPreviewBotId(myId)) ids.push(String(myId)); + others.forEach((_, id) => { + if (!isPreviewBotId(id)) ids.push(String(id)); + }); + return ids; + } + + function quizCarryPregameBotCount() { + let n = 0; + others.forEach((_, id) => { + if (isPreviewBotId(id)) n++; + }); + return n; + } + + function quizCarryPregameReadyNumerator() { + let n = quizCarryPregameBotCount(); + quizCarryPregameHumanIds().forEach((id) => { + if (quizCarryLobbyReadyMap[id]) n++; + }); + return n; + } + + function quizCarryPregameTotalPlayers() { + return quizCarryPregameHumanIds().length + quizCarryPregameBotCount(); + } + + function isMePlayHost() { + if (myId == null) return false; + if (playHostId == null) return true; + return String(playHostId) === String(myId); + } + + function hideQuizCarryPregameOverlay() { + const ov = document.getElementById('quiz-carry-pregame-overlay'); + if (ov) { + ov.classList.add('is-hidden'); + ov.setAttribute('aria-hidden', 'true'); + } + } + + function showQuizCarryPregameOverlay() { + const ov = document.getElementById('quiz-carry-pregame-overlay'); + if (ov) { + ov.classList.remove('is-hidden'); + ov.setAttribute('aria-hidden', 'false'); + } + updateQuizCarryPregameHud(); + } + + function quizCarrySyncGuestReadyIfNeeded() { + if (!quizCarryPregameActive || myId == null || isMePlayHost()) return; + const sid = String(myId); + if (quizCarryLobbyReadyMap[sid]) return; + quizCarryLobbyReadyMap[sid] = true; + if (socket && socket.connected) socket.emit('quiz-carry-lobby-ready', { ready: true }); + } + + function updateQuizCarryPregameHud() { + if (!quizCarryPregameActive) return; + const st = document.getElementById('quiz-carry-pregame-status'); + const primary = document.getElementById('quiz-carry-pregame-primary'); + const primaryImg = document.getElementById('quiz-carry-pregame-primary-img'); + const num = quizCarryPregameReadyNumerator(); + const tot = Math.max(1, quizCarryPregameTotalPlayers()); + if (st) { + st.textContent = 'Ready Status : ' + num + '/' + tot; + } + const humans = quizCarryPregameHumanIds(); + const humansReady = humans.length > 0 && humans.every((id) => !!quizCarryLobbyReadyMap[id]); + const meReady = !!(myId && quizCarryLobbyReadyMap[String(myId)]); + if (primaryImg) { + primaryImg.src = humansReady ? '/Game/img/quiz-carry/btn-start.png' : '/Game/img/quiz-carry/btn-ready.png'; + primaryImg.alt = humansReady ? 'START' : 'READY'; + } + if (primary) { + primary.classList.toggle('is-start-phase', humansReady); + primary.classList.toggle('is-read-only', !isMePlayHost()); + primary.classList.toggle('is-pressed', isMePlayHost() && meReady && !humansReady); + primary.disabled = !isMePlayHost(); + primary.setAttribute('aria-pressed', humansReady ? 'false' : (meReady ? 'true' : 'false')); + primary.title = isMePlayHost() + ? (humansReady ? 'START' : (meReady ? 'ยกเลิก READY' : 'READY')) + : (humansReady ? 'START (โฮสต์เท่านั้น)' : 'READY (โฮสต์เท่านั้น)'); + } + } + + function beginQuizCarryEmbedPregame() { + if (!(previewMode && editorEmbedReturn && isQuizCarry())) return; + quizCarryPregameActive = true; + quizCarryLobbyReadyMap = {}; + quizCarryPregameHumanIds().forEach((id) => { quizCarryLobbyReadyMap[id] = false; }); + showQuizCarryPregameOverlay(); + if (socket && socket.connected) socket.emit('quiz-carry-lobby-sync-request'); + quizCarrySyncGuestReadyIfNeeded(); + updateQuizCarryPregameHud(); + } + + function endQuizCarryEmbedPregameAndStart() { + if (!quizCarryPregameActive) return; + quizCarryPregameActive = false; + hideQuizCarryPregameOverlay(); + quizCarryPickNextQuestion(); + if (!(previewMode && editorEmbedReturn && quizCarryEmbedCountdownEndAt > Date.now())) { + quizCarryRestoreEmbedPhaseLabel(); + } + } + function quizCarryOptionsPickableNow() { if (!isQuizCarry() || !quizCarryCurrent) return true; if (!quizCarryOptionRevealAt || quizCarryOptionRevealAt <= 0) return true; @@ -1888,6 +2018,8 @@ } function updateQuizCarryCarryPhaseHud() { + if (quizCarrySessionEnded) return; + if (quizCarryPregameActive) return; if (!isQuizCarry() || !quizCarryCurrent) return; const phaseEl = document.getElementById('quiz-game-phase-label'); if (!phaseEl) return; @@ -1906,6 +2038,8 @@ } function tickQuizCarryRoundTimers() { + if (quizCarrySessionEnded) return; + if (quizCarryPregameActive) return; if (!isQuizCarry() || !mapData || !quizCarryCurrent) return; if (quizCarryEmbedCountdownEndAt > Date.now()) return; if (quizCarryEmbedPendingQuestion) return; @@ -1913,7 +2047,9 @@ if (Date.now() < quizCarryAnswerCloseAt) return; quizCarryAnswerCloseAt = 0; quizCarryOptionRevealAt = 0; - showQuizCarryToast('หมดเวลา — ข้อถัดไป', false); + const limT = previewMode && isQuizCarry() ? quizCarrySessionLength : 0; + const willEndT = limT > 0 && (quizCarryRoundsCompleted + 1) >= limT; + showQuizCarryToast(willEndT ? 'หมดเวลา — ครบชุดคำถาม' : 'หมดเวลา — ข้อถัดไป', false); me.quizCarryHeld = null; others.forEach((o) => { if (!o) return; @@ -1921,7 +2057,7 @@ o.botPath = []; }); renderPlayQuizScoreboard(playLiveQuizScores); - quizCarryPickNextQuestion(); + quizCarryAfterRoundResolved(); } /** นับ 3–2–1: ไม่โชว์ข้อความคำถามใน overlay — มีแค่เลข */ @@ -1982,6 +2118,8 @@ } function quizCarryPickNextQuestion() { + if (quizCarrySessionEnded) return; + if (quizCarryPregameActive) return; if (!quizCarryPool.length) { quizCarryCurrent = null; quizCarryEmbedPendingQuestion = null; @@ -2036,6 +2174,91 @@ quizCarryApplyPhaseTimersForCurrentQuestion(); } + /** หลังจบข้อหนึ่งข้อ (ถูกหรือหมดเวลา): พรีวิวนับรอบจนครบ carrySessionLength แล้วจบเซสชัน */ + function quizCarryAfterRoundResolved() { + if (!isQuizCarry()) return; + if (!previewMode) { + quizCarryPickNextQuestion(); + return; + } + quizCarryRoundsCompleted++; + const lim = quizCarrySessionLength; + if (lim > 0 && quizCarryRoundsCompleted >= lim) { + showQuizCarrySessionCompleteOverlay(); + return; + } + quizCarryPickNextQuestion(); + } + + function showQuizCarrySessionCompleteOverlay() { + if (quizCarrySessionEnded) return; + quizCarrySessionEnded = true; + quizCarryPregameActive = false; + hideQuizCarryPregameOverlay(); + quizCarryCurrent = null; + quizCarryEmbedPendingQuestion = null; + quizCarryEmbedCountdownEndAt = 0; + quizCarryEmbedCountdownStartAt = 0; + quizCarryOptionRevealAt = 0; + quizCarryAnswerCloseAt = 0; + hideQuizCarryEmbedCountdownOverlay(); + me.quizCarryHeld = null; + others.forEach((o) => { + if (!o) return; + o.quizCarryHeld = null; + o.botPath = []; + }); + playQuizText = 'ครบชุดคำถามแล้ว'; + const qEl = document.getElementById('quiz-game-question'); + if (qEl) qEl.textContent = playQuizText; + const phaseEl = document.getElementById('quiz-game-phase-label'); + if (phaseEl) phaseEl.textContent = 'ครบ ' + String(quizCarryRoundsCompleted) + ' ข้อ'; + const ov = document.getElementById('gauntlet-ended-overlay'); + const msgEl = document.getElementById('gauntlet-ended-message'); + const titleEl = document.getElementById('gauntlet-ended-title'); + const listEl = document.getElementById('gauntlet-ended-rankings'); + const btn = document.getElementById('btn-gauntlet-ended-lobby'); + if (!ov || !msgEl || !listEl) return; + if (titleEl) titleEl.textContent = 'เกมจบ · Game over'; + msgEl.textContent = 'ครบจำนวนคำถามที่ตั้งไว้ (' + String(quizCarryRoundsCompleted) + ' ข้อ) · Quiz carry session finished'; + listEl.innerHTML = ''; + const ranks = []; + if (myId != null) { + ranks.push({ + id: myId, + nickname: (me.nickname || nick || 'คุณ').trim() || 'คุณ', + score: Math.max(0, Number(playLiveQuizScores[myId]) || 0), + }); + } + others.forEach((o, id) => { + if (!o) return; + ranks.push({ + id, + nickname: (o.nickname && String(o.nickname).trim()) ? String(o.nickname).trim() : String(id), + score: Math.max(0, Number(playLiveQuizScores[id]) || 0), + }); + }); + ranks.sort((a, b) => b.score - a.score || String(a.nickname).localeCompare(String(b.nickname), 'th')); + ranks.forEach((r, i) => { + const li = document.createElement('li'); + const isMe = myId != null && String(r.id) === String(myId); + li.textContent = `${i + 1}. ${(r && r.nickname) || '—'} — ${Math.max(0, Number(r && r.score) || 0)}`; + if (isMe) li.className = 'gauntlet-ended-me'; + listEl.appendChild(li); + }); + ov.classList.remove('is-hidden'); + function goLobby() { + window.location.href = 'room-lobby.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(nick); + } + if (btn) { + btn.onclick = () => { + if (previewMode && editorEmbedReturn) ov.classList.add('is-hidden'); + else goLobby(); + }; + } + renderPlayQuizScoreboard(playLiveQuizScores); + } + function showQuizCarryToast(msg, ok) { const el = document.getElementById('play-quiz-feedback'); if (!el) return; @@ -2052,6 +2275,8 @@ */ function tryQuizCarryInteractionForPlayer(actorId, ox, oy, opts) { opts = opts || {}; + if (quizCarryPregameActive) return false; + if (quizCarrySessionEnded) return false; if (!isQuizCarry() || !mapData || !quizCarryCurrent) return false; const md = mapData; const footprint = quizTilesFootprintPlay(ox, oy); @@ -2073,10 +2298,12 @@ const ok = held === correct; if (ok) { playLiveQuizScores[actorId] = (playLiveQuizScores[actorId] || 0) + 1; - showQuizCarryToast('ถูกต้อง +1 แต้ม — สุ่มคำถามใหม่', true); + const lim = previewMode && isQuizCarry() ? quizCarrySessionLength : 0; + const willEnd = lim > 0 && (quizCarryRoundsCompleted + 1) >= lim; + showQuizCarryToast(willEnd ? 'ถูกต้อง +1 แต้ม — ครบชุดคำถาม' : 'ถูกต้อง +1 แต้ม — สุ่มคำถามใหม่', true); me.quizCarryHeld = null; others.forEach((o) => { if (o) o.quizCarryHeld = null; }); - quizCarryPickNextQuestion(); + quizCarryAfterRoundResolved(); } else { showQuizCarryToast('ผิด — คืนป้ายที่โซนตัวเลือกแล้ว กด F หยิบใหม่ได้', false); } @@ -2115,6 +2342,8 @@ } function resetQuizCarryPlayState() { + quizCarryPregameActive = false; + hideQuizCarryPregameOverlay(); quizCarryPool = []; quizCarryCurrent = null; quizCarryEmbedPendingQuestion = null; @@ -2122,6 +2351,8 @@ quizCarryEmbedCountdownEndAt = 0; quizCarryOptionRevealAt = 0; quizCarryAnswerCloseAt = 0; + quizCarryRoundsCompleted = 0; + quizCarrySessionEnded = false; hideQuizCarryEmbedCountdownOverlay(); me.quizCarryHeld = null; others.forEach((o) => { @@ -2132,6 +2363,9 @@ } async function loadQuizCarryPoolAndStart() { + quizCarryRoundsCompleted = 0; + quizCarrySessionEnded = false; + quizCarrySessionLength = 0; let pool = []; try { const r = await fetch(BASE + '/api/quiz-settings'); @@ -2141,6 +2375,7 @@ carryReadMs: clampPreviewMs(s.carryReadMs, 3000, 0, 120000), carryAnswerMs: clampPreviewMs(s.carryAnswerMs, 5000, 1000, 300000), }; + quizCarrySessionLength = clampCarrySessionLen(s.carrySessionLength, 0); const carryOnly = s && Array.isArray(s.carryQuestions) && s.carryQuestions.length > 0; const source = carryOnly ? s.carryQuestions : (s && Array.isArray(s.questions) ? s.questions : []); for (let i = 0; i < source.length; i++) { @@ -2160,7 +2395,11 @@ if (qEl) qEl.textContent = playQuizText; return; } - quizCarryPickNextQuestion(); + if (previewMode && editorEmbedReturn) { + beginQuizCarryEmbedPregame(); + } else { + quizCarryPickNextQuestion(); + } if (!(previewMode && editorEmbedReturn && quizCarryEmbedCountdownEndAt > Date.now())) { quizCarryRestoreEmbedPhaseLabel(); } @@ -2252,6 +2491,8 @@ } function stepQuizCarryPreviewBots() { + if (quizCarrySessionEnded) return; + if (quizCarryPregameActive) return; if (!previewFillBots || !isQuizCarry() || !mapData || !quizCarryCurrent) return; const w = mapData.width || 20, h = mapData.height || 15; const nowPf = performance.now(); @@ -5062,6 +5303,7 @@ const prevWasSpaceShooter = mapData && mapData.gameType === 'space_shooter'; const prevWasBalloonBoss = mapData && mapData.gameType === 'balloon_boss'; mapData = gameMapData; + if (res && res.hostId != null) playHostId = res.hostId; if (mapData.gameType !== 'space_shooter') { spaceShooterGameEnded = false; if (prevWasSpaceShooter) { @@ -5336,6 +5578,23 @@ quizCarryHeld: null, }); if (previewFillBots) rebalancePreviewBots(); + if (quizCarryPregameActive && isQuizCarry()) updateQuizCarryPregameHud(); + }); + socket.on('host-changed', (d) => { + if (d && d.hostId != null) playHostId = d.hostId; + if (quizCarryPregameActive && isQuizCarry()) updateQuizCarryPregameHud(); + }); + socket.on('quiz-carry-lobby-sync', (d) => { + if (!d || typeof d.readyMap !== 'object') return; + quizCarryLobbyReadyMap = { ...d.readyMap }; + if (quizCarryPregameActive && isQuizCarry()) { + quizCarrySyncGuestReadyIfNeeded(); + updateQuizCarryPregameHud(); + } + }); + socket.on('quiz-carry-lobby-started', () => { + if (!(previewMode && editorEmbedReturn && isQuizCarry())) return; + endQuizCarryEmbedPregameAndStart(); }); socket.on('user-move', (data) => { if (data && myId != null && data.id === myId) { @@ -7630,7 +7889,7 @@ requestAnimationFrame(tick); return; } - if (isQuizCarryEmbedCountdownBlockingMovement()) { + if (isQuizCarryEmbedLobbyBlockingMovement() || isQuizCarryEmbedCountdownBlockingMovement()) { me.isWalking = false; playPath = []; enforceQuizBattleLaneOnMePlay(); @@ -7758,6 +8017,32 @@ } }); })(); + (function wireQuizCarryPregameOverlay() { + const primary = document.getElementById('quiz-carry-pregame-primary'); + if (!primary) return; + primary.addEventListener('click', () => { + if (!quizCarryPregameActive || myId == null) return; + if (!isMePlayHost()) return; + const humans = quizCarryPregameHumanIds(); + const humansReady = humans.length > 0 && humans.every((id) => !!quizCarryLobbyReadyMap[id]); + if (humansReady) { + /* ผู้เล่นจริงคนเดียว + พรีวิว: ไม่ต้องรอ ACK เซิร์ฟ (ห้องที่ map บนเซิร์ฟยังไม่ใช่ quiz_carry จะเคยล้มเหลว) */ + if (humans.length === 1 && isMePlayHost()) { + endQuizCarryEmbedPregameAndStart(); + return; + } + socket.emit('quiz-carry-lobby-start', {}, (r) => { + if (!r || !r.ok) showQuizCarryToast((r && r.error) || 'START ไม่ได้', false); + }); + return; + } + const sid = String(myId); + const next = !quizCarryLobbyReadyMap[sid]; + quizCarryLobbyReadyMap[sid] = next; + updateQuizCarryPregameHud(); + socket.emit('quiz-carry-lobby-ready', { ready: next }); + }); + })(); window.addEventListener('beforeunload', () => socket.emit('leave-space')); window.addEventListener('resize', () => { if (!mapData || !canvas) return; diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 444c2cf..ab6484f 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -186,6 +186,126 @@ z-index: 10120; top: max(52px, calc(env(safe-area-inset-top) + 46px)); } + /* พรีวิว embed quiz_carry: How to play + Ready / START ก่อน 3–2–1 */ + #quiz-carry-pregame-overlay { + position: fixed; + inset: 0; + z-index: 10078; + display: flex; + align-items: center; + justify-content: center; + padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left)); + box-sizing: border-box; + } + #quiz-carry-pregame-overlay.is-hidden { display: none !important; } + .quiz-carry-pregame-backdrop { + position: absolute; + inset: 0; + background: rgba(4, 6, 14, 0.78); + backdrop-filter: blur(10px); + } + .quiz-carry-pregame-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + .quiz-carry-pregame-card { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + align-items: stretch; + max-width: min(94vw, 900px); + width: 100%; + max-height: min(94vh, 900px); + overflow: hidden; + background: rgba(10, 12, 26, 0.55); + border: none; + border-radius: 4px; + box-shadow: none; + pointer-events: auto; + } + .quiz-carry-pregame-art { + flex: 1 1 auto; + min-height: 0; + margin: 0; + padding: 0; + overflow: auto; + background: transparent; + display: flex; + align-items: flex-start; + justify-content: center; + } + .quiz-carry-pregame-howto-img { + display: block; + width: 100%; + height: auto; + max-height: min(62vh, 560px); + object-fit: contain; + object-position: top center; + border: none; + outline: none; + box-shadow: none; + } + .quiz-carry-pregame-footer { + flex: 0 0 auto; + padding: 0.85rem 1rem 1rem; + background: linear-gradient(180deg, rgba(8, 10, 22, 0.2), rgba(8, 10, 22, 0.96)); + border-top: none; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.65rem; + } + .quiz-carry-pregame-status { + margin: 0; + text-align: center; + font: 700 clamp(0.8rem, 2.4vw, 0.95rem) / 1.35 system-ui, "Kanit", sans-serif; + color: #fbcfe8; + text-shadow: 0 0 16px rgba(244, 114, 182, 0.5); + letter-spacing: 0.02em; + } + .quiz-carry-pregame-actions { + display: flex; + flex-wrap: wrap; + gap: 0.85rem 1.25rem; + justify-content: center; + align-items: center; + } + .quiz-carry-pregame-primary-btn { + background: transparent; + border: none; + padding: 0; + cursor: pointer; + line-height: 0; + border-radius: 6px; + transition: transform 0.12s ease, filter 0.12s ease, opacity 0.15s ease; + } + .quiz-carry-pregame-primary-btn:hover:not(:disabled):not(.is-read-only) { + transform: scale(1.03); + filter: brightness(1.08); + } + .quiz-carry-pregame-primary-btn.is-pressed:not(.is-start-phase) { + filter: brightness(1.12) drop-shadow(0 0 12px rgba(94, 234, 212, 0.55)); + } + .quiz-carry-pregame-primary-btn.is-read-only, + .quiz-carry-pregame-primary-btn:disabled { + opacity: 0.38; + pointer-events: none; + cursor: default; + filter: grayscale(0.2); + } + .quiz-carry-pregame-primary-btn img { + display: block; + width: min(220px, 42vw); + height: auto; + } #play-quiz-scoreboard { position: fixed; top: max(56px, env(safe-area-inset-top)); @@ -696,6 +816,23 @@ +