diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index cad2af2..0c0f66a 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -520,11 +520,26 @@ var bw = bwEl ? parseInt(String(bwEl.value || '2'), 10) : 2; if (!Number.isFinite(bw)) bw = 2; bw = Math.max(0, Math.min(12, Math.round(bw))); + var qfMinEl = el('quiz-carry-theme-qfont-min'); + var qfMaxEl = el('quiz-carry-theme-qfont-max'); + var qMin = qfMinEl ? parseInt(String(qfMinEl.value || '10'), 10) : 10; + var qMax = qfMaxEl ? parseInt(String(qfMaxEl.value || '24'), 10) : 24; + if (!Number.isFinite(qMin)) qMin = 10; + if (!Number.isFinite(qMax)) qMax = 24; + qMin = Math.max(10, Math.min(40, Math.round(qMin))); + qMax = Math.max(14, Math.min(56, Math.round(qMax))); + if (qMax < qMin) { + var swap = qMin; + qMin = qMax; + qMax = swap; + } return { panelBg: (sBg && sBg.trim()) ? sBg.trim().slice(0, 120) : quizCarryRgbToRgbaString(fbBg), panelBorder: (sBr && sBr.trim()) ? sBr.trim().slice(0, 120) : quizCarryRgbToRgbaString(fbBr), textColor: (sTx && sTx.trim()) ? sTx.trim().slice(0, 120) : quizCarryRgbToRgbaString(fbTx), borderWidthPx: bw, + questionFontMinPx: qMin, + questionFontMaxPx: qMax, }; } @@ -1223,7 +1238,14 @@ gameQuizFetch('GET').then(function (data) { if (opts.themeOverride && typeof opts.themeOverride === 'object') { var ov = opts.themeOverride; - if (ov.panelBg != null || ov.borderWidthPx != null || ov.panelBorder != null || ov.textColor != null) { + if ( + ov.panelBg != null || + ov.borderWidthPx != null || + ov.panelBorder != null || + ov.textColor != null || + ov.questionFontMinPx != null || + ov.questionFontMaxPx != null + ) { data.carryMapPanelTheme = ov; } else if (ov.carryMapPanelTheme && typeof ov.carryMapPanelTheme === 'object') { data.carryMapPanelTheme = ov.carryMapPanelTheme; @@ -1279,6 +1301,16 @@ var bw = parseInt(String(th.borderWidthPx), 10); bwInp.value = String(Number.isFinite(bw) && bw >= 0 ? Math.min(12, bw) : 2); } + var qfMinInp = el('quiz-carry-theme-qfont-min'); + var qfMaxInp = el('quiz-carry-theme-qfont-max'); + if (qfMinInp) { + var qm = parseInt(String(th.questionFontMinPx), 10); + qfMinInp.value = String(Number.isFinite(qm) ? Math.max(10, Math.min(40, qm)) : 10); + } + if (qfMaxInp) { + var qx = parseInt(String(th.questionFontMaxPx), 10); + qfMaxInp.value = String(Number.isFinite(qx) ? Math.max(14, Math.min(56, qx)) : 24); + } quizCarryEcdFillForm(data.carryEmbedCountdownTheme || {}); quizCarryPlaqueFillAllFromApi(data); if (opts.clearMsg !== false) setMsg('quiz-carry-settings-msg', '', ''); diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index 9ac2165..4102d59 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -226,6 +226,15 @@ +
+ + +
+

ในเกมใช้สูตรเดียวกับป้ายคำตอบบนพื้น: clamp(10, 24, tileSize×zoom×0.24×carryChoicePlaqueMapScale) แล้ว clamp อีกชั้นด้วยค่าสองช่องนี้ · English: Same px formula as floor answer plaques; theme min/max clamp on top.

@@ -661,6 +670,6 @@ - + diff --git a/www/html/Game/data/quiz-settings.json b/www/html/Game/data/quiz-settings.json index 28e683f..75dc47e 100644 --- a/www/html/Game/data/quiz-settings.json +++ b/www/html/Game/data/quiz-settings.json @@ -9,11 +9,13 @@ "panelBg": "rgba(255, 0, 0, 0)", "panelBorder": "rgba(255, 255, 255, 0)", "textColor": "rgba(241, 245, 249, 1)", - "borderWidthPx": 0 + "borderWidthPx": 0, + "questionFontMinPx": 10, + "questionFontMaxPx": 24 }, "carryEmbedCountdownTheme": { - "overlayBackdrop": "rgba(8, 10, 20, 0.42)", - "innerBg": "rgba(12, 14, 28, 0.82)", + "overlayBackdrop": "rgba(8, 10, 20, 0)", + "innerBg": "rgba(12, 14, 28, 0)", "innerBorder": "rgba(122, 162, 247, 0.45)", "innerBorderWpx": 1, "innerRadiusPx": 12, diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index a2e4edc..1ab4127 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -406,17 +406,10 @@ for (let li = 0; li < PLAY_LAYER_ORDER.length; li++) { const layerName = PLAY_LAYER_ORDER[li]; if (diskLayers && layerName !== 'shadow' && !diskLayers.has(layerName)) continue; + /* ห้าม fallback face จากทิศ down เมื่อเป็น up/left/right — จะได้ตาอยู่หลังหัวเพราะวางหน้าหน้าบนร่างหันหลัง */ const urls = layerName === 'shadow' ? shadowUrlCandidates(id, dir, frameIndex) - : (layerName === 'face' && dir !== 'down' - ? (() => { - const u = layerUrlCandidates(id, dir, layerName, frameIndex); - layerUrlCandidates(id, 'down', layerName, frameIndex).forEach((x) => { - if (u.indexOf(x) === -1) u.push(x); - }); - return u; - })() - : layerUrlCandidates(id, dir, layerName, frameIndex)); + : layerUrlCandidates(id, dir, layerName, frameIndex); const r = resolvePlayLayerImage(urls); /* เงาโหลดช้า/404 บ่อย — อย่าให้บล็อกทั้งคอมโพส (ไม่งั้นตกไป fallback แถบแนวตั้งแทนเลเยอร์จริง) */ if (r.status === 'pending' && layerName === 'shadow') { @@ -446,8 +439,8 @@ /* เหมือนเดิม: มีเลเยอร์แยกไฟล์เมื่อ API บอก hasLayerFiles หรือ probe เจอ — gen สีทำบน canvas จาก *_layer_* เท่านั้น */ const charHasLayerFiles = apiFlag === true || (apiFlag == null && playCharAnyDirectionLayered(characterId)); - /* ct2: ยกเลิก composite เก่าที่เคย cache จาก fallback ย้อมทั้งสไปรต์ */ - const cacheKey = [characterId, dir, frameIndex, tint.head, tint.hair, tint.body, 'ct2'].join('|'); + /* ct3: ไม่ใช้ face จาก down บนทิศอื่น (แก้ตาหลังหัว) */ + const cacheKey = [characterId, dir, frameIndex, tint.head, tint.hair, tint.body, 'ct3'].join('|'); const hit = playLayerCompositeCache.get(cacheKey); if (hit) return hit; @@ -810,6 +803,12 @@ textEl.style.removeProperty('--qmap-text-shadow'); textEl.style.removeProperty('color'); textEl.style.removeProperty('text-shadow'); + textEl.style.removeProperty('font-size'); + textEl.style.removeProperty('line-height'); + textEl.style.removeProperty('width'); + textEl.style.removeProperty('box-sizing'); + textEl.style.removeProperty('max-height'); + textEl.style.removeProperty('overflow'); } /** ค่าเริ่มต้นแผงคำถามบนแผนที่ (ตรงกับ server defaultCarryMapPanelTheme) — ใช้เมื่อยังไม่โหลดจาก API */ @@ -819,9 +818,74 @@ panelBorder: 'rgba(255, 214, 102, 0.7)', borderWidthPx: 2, textColor: '#f1f5f9', + questionFontMinPx: 10, + questionFontMaxPx: 24, }; } + /** ตรงกับ drawQuizCarryChoiceLabels — carryChoicePlaqueMapScale */ + function quizCarryPlaqueMapScaleClampedPlay() { + const sc = Number(quizCarryPlaqueMapScale); + if (!Number.isFinite(sc)) return 1.25; + return Math.max(0.85, Math.min(2.5, sc)); + } + + /** + * ขนาดตัวอักษรแผงคำถาม DOM ให้เท่าป้ายคำตอบบนพื้น: Math.max(10, Math.min(24, tileSize*zoom*0.24*ps)) + * แล้ว clamp ด้วย questionFontMinPx / questionFontMaxPx จากธีม (ถ้าต้องการใหญ่กว่าป้ายได้โดยยกเพดาน max) + */ + function quizMapQuestionFontPxLikeCarryPlaques(th, zoomVal) { + const ts = tileSize * zoomVal; + const ps = quizCarryPlaqueMapScaleClampedPlay(); + const base = Math.max(10, Math.min(24, ts * 0.24 * ps)); + const fb = defaultCarryMapPanelThemePlay(); + let mn = Number(th && th.questionFontMinPx); + let mx = Number(th && th.questionFontMaxPx); + if (!Number.isFinite(mn)) mn = fb.questionFontMinPx; + if (!Number.isFinite(mx)) mx = fb.questionFontMaxPx; + mn = Math.round(Math.max(8, Math.min(40, mn))); + mx = Math.round(Math.max(10, Math.min(72, mx))); + if (mx < mn) { + const s = mn; + mn = mx; + mx = s; + } + return Math.max(mn, Math.min(mx, base)); + } + + /** + * ลด px ทีละ 1 จนเนื้อหาไม่ล้นกล่อง (ไม่ใช้ scrollbar) + * zoom out กล่องเตี้ยมาก: อนุญาตต่ำกว่า questionFontMinPx ของธีมได้ถึง hardMin (≥6) ไม่ให้บรรทัดถูกตัด + * แผงใช้ justify-content:flex-start ใน CSS — ไม่จัดกึ่งกลางแนวตั้งแล้วโดน overflow ตัดบน-ล่าง + */ + function fitQuizMapQuestionFontToPanel(panel, textEl, startPx, minPx) { + const cs = window.getComputedStyle(panel); + const padT = parseFloat(cs.paddingTop) || 0; + const padB = parseFloat(cs.paddingBottom) || 0; + const padL = parseFloat(cs.paddingLeft) || 0; + const padR = parseFloat(cs.paddingRight) || 0; + const availH = Math.max(12, panel.clientHeight - padT - padB); + const availW = Math.max(24, panel.clientWidth - padL - padR); + const themeMin = Math.max(8, Math.floor(Number(minPx) || 8)); + const hardMin = Math.min(themeMin, Math.max(6, Math.floor(availH / 7))); + let px = Math.round(Math.min(80, Math.max(hardMin, Number(startPx) || hardMin))); + textEl.style.setProperty('line-height', '1.28', 'important'); + textEl.style.setProperty('width', '100%', 'important'); + textEl.style.setProperty('box-sizing', 'border-box', 'important'); + textEl.style.removeProperty('max-height'); + textEl.style.setProperty('overflow', 'hidden', 'important'); + for (let i = 0; i < 96 && px > hardMin; i++) { + textEl.style.setProperty('font-size', String(px) + 'px', 'important'); + const sh = textEl.scrollHeight; + const sw = textEl.scrollWidth; + if (sh <= availH + 2 && sw <= availW + 2) break; + px -= 1; + } + textEl.style.setProperty('font-size', String(px) + 'px', 'important'); + textEl.style.setProperty('max-height', availH + 'px', 'important'); + } + + /** ธีมจาก quiz-settings / join API — ถ้า JSON ไม่มีฟอนต์ให้ใช้ค่าเริ่มต้น */ function parseCarryMapPanelThemeObject(raw) { let t = raw; if (typeof t === 'string') { @@ -834,14 +898,71 @@ if (!t || typeof t !== 'object') return null; const bw = Number(t.borderWidthPx); const borderWidthPx = Number.isFinite(bw) ? Math.max(0, Math.min(12, Math.round(bw))) : 2; + const fb = defaultCarryMapPanelThemePlay(); + let qMin = Number(t.questionFontMinPx); + let qMax = Number(t.questionFontMaxPx); + if (!Number.isFinite(qMin)) qMin = fb.questionFontMinPx; + if (!Number.isFinite(qMax)) qMax = fb.questionFontMaxPx; + qMin = Math.round(Math.max(10, Math.min(40, qMin))); + qMax = Math.round(Math.max(14, Math.min(56, qMax))); + if (qMax < qMin) { + const s = qMin; + qMin = qMax; + qMax = s; + } return { panelBg: String(t.panelBg || '').trim().slice(0, 120), panelBorder: String(t.panelBorder || '').trim().slice(0, 120), borderWidthPx, textColor: String(t.textColor || '').trim().slice(0, 120), + questionFontMinPx: qMin, + questionFontMaxPx: qMax, }; } + /** + * ชั้นทับจาก carryMapPanelTheme ในไฟล์แมป — ฟอนต์ใส่เฉพาะเมื่อ JSON มี key จริง + * (กันธีมแมปมีแค่สีแล้วไปทับขนาดฟอนต์จาก Admin เป็นค่าเริ่ม 16/24) + */ + function parseCarryMapPanelThemeMapOverlay(raw) { + let t = raw; + if (typeof t === 'string') { + try { + t = JSON.parse(t); + } catch (e) { + t = null; + } + } + if (!t || typeof t !== 'object') return null; + const bw = Number(t.borderWidthPx); + const borderWidthPx = Number.isFinite(bw) ? Math.max(0, Math.min(12, Math.round(bw))) : 2; + const out = { + panelBg: String(t.panelBg || '').trim().slice(0, 120), + panelBorder: String(t.panelBorder || '').trim().slice(0, 120), + borderWidthPx, + textColor: String(t.textColor || '').trim().slice(0, 120), + }; + const hasMin = Object.prototype.hasOwnProperty.call(t, 'questionFontMinPx'); + const hasMax = Object.prototype.hasOwnProperty.call(t, 'questionFontMaxPx'); + if (hasMin || hasMax) { + const fb = defaultCarryMapPanelThemePlay(); + let qMin = hasMin ? Number(t.questionFontMinPx) : fb.questionFontMinPx; + let qMax = hasMax ? Number(t.questionFontMaxPx) : fb.questionFontMaxPx; + if (!Number.isFinite(qMin)) qMin = fb.questionFontMinPx; + if (!Number.isFinite(qMax)) qMax = fb.questionFontMaxPx; + qMin = Math.round(Math.max(10, Math.min(40, qMin))); + qMax = Math.round(Math.max(14, Math.min(56, qMax))); + if (qMax < qMin) { + const s = qMin; + qMin = qMax; + qMax = s; + } + out.questionFontMinPx = qMin; + out.questionFontMaxPx = qMax; + } + return out; + } + function setQuizCarryMapPanelThemeFromApi(s) { quizCarryMapPanelTheme = null; if (!s || typeof s !== 'object') return; @@ -1103,14 +1224,16 @@ } } - /** ธีมจากแผนที่ (ถ้ามี) ชนะค่าจาก API — ใส่ carryMapPanelTheme ใน JSON แมปได้โดยตรง */ + /** default ← quiz-settings/API ← แมป (สีจากแมปทับได้; ฟอนต์จากแมปเฉพาะเมื่อแมประบุ key) */ function getEffectiveCarryMapPanelThemeForApply() { + const d = defaultCarryMapPanelThemePlay(); + const api = quizCarryMapPanelTheme != null && typeof quizCarryMapPanelTheme === 'object' ? quizCarryMapPanelTheme : null; + let merged = api ? { ...d, ...api } : { ...d }; if (mapData && mapData.carryMapPanelTheme != null) { - const parsed = parseCarryMapPanelThemeObject(mapData.carryMapPanelTheme); - if (parsed) return parsed; + const mp = parseCarryMapPanelThemeMapOverlay(mapData.carryMapPanelTheme); + if (mp) merged = { ...merged, ...mp }; } - if (quizCarryMapPanelTheme != null) return quizCarryMapPanelTheme; - return defaultCarryMapPanelThemePlay(); + return merged; } function applyQuizCarryMapPanelThemeIfNeeded(panel, textEl) { @@ -1185,6 +1308,10 @@ panel.classList.remove('is-hidden'); panel.setAttribute('aria-hidden', 'false'); applyQuizCarryMapPanelThemeIfNeeded(panel, textEl); + const thFont = getEffectiveCarryMapPanelThemeForApply(); + const startFontPx = quizMapQuestionFontPxLikeCarryPlaques(thFont, zoom); + const mnFont = Math.max(8, Math.round(Number(thFont.questionFontMinPx) || 10)); + fitQuizMapQuestionFontToPanel(panel, textEl, startFontPx, mnFont); } function carryEmbedCountdownAnchorResolved() { @@ -2397,11 +2524,7 @@ if (!choices || !choices.length) return; const maxSlots = choices.length; const ts = tileSize * zoom; - const ps = (() => { - const sc = Number(quizCarryPlaqueMapScale); - if (!Number.isFinite(sc)) return 1.25; - return Math.max(0.85, Math.min(2.5, sc)); - })(); + const ps = quizCarryPlaqueMapScaleClampedPlay(); ctx.save(); for (let i = 0; i < Math.min(choices.length, maxSlots); i++) { if (quizCarryOptionHeldByAnyone(i)) continue; @@ -7378,6 +7501,11 @@ return s; } + /** ร่าง (cw×ch) ของผู้เล่นที่ (px,py) ครอบคลุมช่อง (tx,ty) หรือไม่ — ใช้กับ blockPlayer แทนการเช็คแค่มุมซ้ายบน */ + function playEntityFootprintContainsTile(px, py, tx, ty) { + return quizTilesFootprintPlay(px, py).has(tx + ',' + ty); + } + /** Footprint ชนกำแพง (objects=1) เท่านั้น — hub / interactive / blockPlayer / quiz ใช้ quizTilesFootprintPlay = เต็ม characterCells */ function quizTilesWallCollisionFootprintPlay(px, py) { const s = new Set(); @@ -7488,7 +7616,7 @@ const tx = +p[0], ty = +p[1]; if (!bp[ty] || bp[ty][tx] !== 1) continue; for (const [, o] of others) { - if (Math.floor(o.x) === tx && Math.floor(o.y) === ty) return false; + if (playEntityFootprintContainsTile(o.x, o.y, tx, ty)) return false; } } } @@ -7528,8 +7656,9 @@ if (!bp[ty] || bp[ty][tx] !== 1) continue; for (const [, peer] of others) { if (o && peer === o) continue; - if (Math.floor(peer.x) === tx && Math.floor(peer.y) === ty) return false; + if (playEntityFootprintContainsTile(peer.x, peer.y, tx, ty)) return false; } + if (o && playEntityFootprintContainsTile(me.x, me.y, tx, ty)) return false; } } if (isQuiz()) { @@ -8830,11 +8959,7 @@ const raw = String(signText || '').trim(); if (!raw && !imgUrlSan && !gridHeldDraw) return; ctx.save(); - const heldPs = (() => { - const sc = Number(quizCarryPlaqueMapScale); - if (!Number.isFinite(sc)) return 1.25; - return Math.max(0.85, Math.min(2.5, sc)); - })(); + const heldPs = quizCarryPlaqueMapScaleClampedPlay(); const bodyH = Math.max(18, feetSy - spriteTopY); const midBodyY = spriteTopY + bodyH * 0.48; const bodyCx = sxFeet; diff --git a/www/html/Game/public/js/room-lobby.js b/www/html/Game/public/js/room-lobby.js index 28a28aa..2927049 100644 --- a/www/html/Game/public/js/room-lobby.js +++ b/www/html/Game/public/js/room-lobby.js @@ -897,7 +897,7 @@ if (bp && bp[ty] && bp[ty][tx] === 1) { for (const [id, p] of peers) { if (id === socket.id) continue; - if (Math.floor(p.x) === tx && Math.floor(p.y) === ty) return false; + if (quizTilesFootprintLobby(p.x, p.y).has(tx + ',' + ty)) return false; } } if (mapData.gameType === 'quiz' && quizModeActive && quizPlayerLocal && !quizPlayerLocal.eliminated) { diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index 54523e4..95d5eaf 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -123,10 +123,13 @@ box-sizing: border-box; padding: clamp(6px, 1.5vw, 14px); display: flex; - align-items: center; - justify-content: center; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; text-align: center; - overflow: auto; + overflow: hidden; + scrollbar-width: none; + -ms-overflow-style: none; --qmap-bg: rgba(12, 14, 28, 0.88); --qmap-border: rgba(255, 214, 102, 0.7); --qmap-border-w: 2px; @@ -137,17 +140,37 @@ box-shadow: var(--qmap-shadow); } #quiz-map-question-panel.is-hidden { display: none !important; } + #quiz-map-question-panel::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + } + /* ขนาดตั้งต้น — โหมด quiz_carry ถูกทับด้วย play.js ตามสัดส่วนกล่อง + ธีม */ #quiz-map-question-text { margin: 0; - font-size: clamp(0.78rem, 2.4vmin, 1.12rem); + font-size: 22px; font-weight: 600; line-height: 1.45; --qmap-text: #f1f5f9; --qmap-text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); color: var(--qmap-text); text-shadow: var(--qmap-text-shadow); + width: 100%; + box-sizing: border-box; + flex: 0 1 auto; + min-height: 0; max-height: 100%; - overflow: auto; + overflow: hidden; + overflow-wrap: anywhere; + word-break: break-word; + white-space: pre-line; + scrollbar-width: none; + -ms-overflow-style: none; + } + #quiz-map-question-text::-webkit-scrollbar { + display: none; + width: 0; + height: 0; } #quiz-carry-embed-countdown { position: fixed; @@ -196,11 +219,21 @@ #quiz-carry-embed-countdown-q { display: none; margin: 0; - font: 600 clamp(0.88rem, 3.2vmin, 1.28rem) / 1.45 system-ui, "Kanit", sans-serif; + font: 600 clamp(14px, 3.2vmin, 20px) / 1.45 system-ui, "Kanit", sans-serif; color: #f8fafc; text-shadow: 0 2px 20px rgba(0, 0, 0, 0.75); max-height: min(32vh, 220px); - overflow: auto; + overflow: hidden; + overflow-wrap: anywhere; + word-break: break-word; + white-space: pre-line; + scrollbar-width: none; + -ms-overflow-style: none; + } + #quiz-carry-embed-countdown-q::-webkit-scrollbar { + display: none; + width: 0; + height: 0; } #quiz-carry-embed-countdown-num { font: 800 min(var(--carry-ecd-screen-vw, 28vw), var(--carry-ecd-screen-max, 132px)) / 1.05 system-ui, "Kanit", sans-serif; @@ -980,7 +1013,7 @@ - +
v —
diff --git a/www/html/Game/public/room-lobby.html b/www/html/Game/public/room-lobby.html index 80c33c5..1a6201b 100644 --- a/www/html/Game/public/room-lobby.html +++ b/www/html/Game/public/room-lobby.html @@ -676,10 +676,13 @@ box-sizing: border-box; padding: clamp(6px, 1.5vw, 14px); display: flex; - align-items: center; - justify-content: center; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; text-align: center; - overflow: auto; + overflow: hidden; + scrollbar-width: none; + -ms-overflow-style: none; --qmap-bg: rgba(12, 14, 28, 0.88); --qmap-border: rgba(255, 214, 102, 0.7); --qmap-border-w: 2px; @@ -692,6 +695,11 @@ #quiz-map-question-panel.is-hidden { display: none !important; } + #quiz-map-question-panel::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + } /* ตอนเล่น quiz ซ่อนปุ่ม READY กลางล่าง — ไม่ให้ทับ/แย่งโฟกัสกับแถบคำถาม */ body.room-lobby--quiz-active .room-lobby-ready-fixed { visibility: hidden; @@ -700,15 +708,29 @@ #quiz-map-question-text { margin: 0; font-family: 'Kanit', system-ui, sans-serif; - font-size: clamp(0.78rem, 2.4vmin, 1.12rem); + font-size: clamp(14px, 2.5vmin, 18px); font-weight: 600; line-height: 1.45; --qmap-text: #f1f5f9; --qmap-text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); color: var(--qmap-text); text-shadow: var(--qmap-text-shadow); + width: 100%; + box-sizing: border-box; + flex: 0 1 auto; + min-height: 0; max-height: 100%; - overflow: auto; + overflow: hidden; + overflow-wrap: anywhere; + word-break: break-word; + white-space: pre-line; + scrollbar-width: none; + -ms-overflow-style: none; + } + #quiz-map-question-text::-webkit-scrollbar { + display: none; + width: 0; + height: 0; } #quiz-feedback-banner { position: fixed; @@ -1021,7 +1043,7 @@ - +
v —
diff --git a/www/html/Game/server.js b/www/html/Game/server.js index 837c671..e594fd8 100644 --- a/www/html/Game/server.js +++ b/www/html/Game/server.js @@ -123,6 +123,8 @@ function defaultCarryMapPanelTheme() { panelBorder: 'rgba(255, 214, 102, 0.7)', borderWidthPx: 2, textColor: '#f1f5f9', + questionFontMinPx: 10, + questionFontMaxPx: 24, }; } @@ -141,6 +143,17 @@ function sanitizeCssColorToken(input, fallback) { function sanitizeCarryMapPanelTheme(raw) { const d = defaultCarryMapPanelTheme(); if (!raw || typeof raw !== 'object') return d; + let qMin = Number(raw.questionFontMinPx); + let qMax = Number(raw.questionFontMaxPx); + if (!Number.isFinite(qMin)) qMin = d.questionFontMinPx; + if (!Number.isFinite(qMax)) qMax = d.questionFontMaxPx; + qMin = Math.round(Math.max(10, Math.min(40, qMin))); + qMax = Math.round(Math.max(14, Math.min(56, qMax))); + if (qMax < qMin) { + const t = qMin; + qMin = qMax; + qMax = t; + } return { panelBg: sanitizeCssColorToken(raw.panelBg, d.panelBg), panelBorder: sanitizeCssColorToken(raw.panelBorder, d.panelBorder), @@ -150,6 +163,8 @@ function sanitizeCarryMapPanelTheme(raw) { return Math.max(0, Math.min(12, Math.round(w))); })(), textColor: sanitizeCssColorToken(raw.textColor, d.textColor), + questionFontMinPx: qMin, + questionFontMaxPx: qMax, }; }