update minigame 1 1.2

This commit is contained in:
2026-06-07 13:51:49 +00:00
parent bd362a0dda
commit e611c72514
6 changed files with 579 additions and 138 deletions
File diff suppressed because one or more lines are too long
+59 -6
View File
@@ -36,7 +36,7 @@
</select>
</label>
</div>
<div class="game-type-hint editor-card__hint" id="game-type-hint" role="note" aria-labelledby="editor-card-game-label"></div>
<div class="game-type-hint editor-card__hint editor-collapsible-hint" id="game-type-hint" role="note" aria-labelledby="editor-card-game-label" hidden></div>
<div id="editor-quiz-carry-countdown-wrap" class="editor-card__row editor-card__row--wrap" style="display:none;gap:0.65rem 1rem;align-items:center;margin-top:0.45rem;" aria-label="หยิบมาวาง พรีวิวนับถอยหลัง">
<label class="editor-field-nogrow" style="display:flex;align-items:center;gap:0.35rem;cursor:pointer;">
<input type="checkbox" id="carry-embed-countdown-highlight" checked>
@@ -83,7 +83,7 @@
<label class="editor-footprint-row__field" title="กล่องตรวจทับโซน «ห้ามซ้อนผู้เล่น» — สูงจากเท้าขึ้นมา · 0 = เต็มสูงตัว" aria-describedby="editor-character-collision-hint">× สูง <input type="number" id="block-player-separation-h" value="0" min="0" max="4" step="1" aria-label="โซนห้ามซ้อน สูงกล่องตรวจจากเท้า (ช่องกริด)"></label>
</div>
<button type="button" id="btn-new">สร้างกริดใหม่</button>
<p id="editor-character-collision-hint" class="editor-card__hint" style="margin:0.4rem 0 0;width:100%;max-width:44rem;line-height:1.45;">
<p id="editor-character-collision-hint" class="editor-card__hint editor-collapsible-hint" style="margin:0.4rem 0 0;width:100%;max-width:44rem;line-height:1.45;" hidden>
<strong>ชนกำแพง กว้าง×สูง</strong> ใช้แค่ตรวจชน <strong>กำแพง</strong> (ช่องวาด «กำแพง») เท่านั้น
· <strong>interactive</strong> · <strong>โซนห้ามซ้อน</strong> · <strong>hub / ส่งคำตอบ quiz carry</strong> · โซนคำถาม/ตัวเลือก ฯลฯ ยังใช้พื้นที่ตัวละครตาม
<strong>สูง (แนวตั้ง) × กว้าง (แนวนอน)</strong> เหมือนเดิม — ในเกม (<code>play.js</code>) แยกฟังก์ชันชนกำแพงกับ footprint โซนแล้ว · <strong>ห้ามซ้อน กว้าง×สูง</strong> = กำหนดกล่องตรวจทับโซนห้ามซ้อน (ชิดเท้า+กลางแนวนอน เหมือนชนกำแพง) ไม่ใช่การขยายรอบร่าง
@@ -93,7 +93,7 @@
<section class="editor-card editor-card--wide" aria-labelledby="editor-card-draw-label">
<h2 class="editor-card__title" id="editor-card-draw-label">เครื่องมือวาด</h2>
<p class="editor-draw-build-hint" style="margin:0 0 0.4rem;font-size:0.74rem;color:#9ece6a;line-height:1.35;">มีโหมด <strong>รูปบนกริด</strong> ในเมนู «โหมดวาด» — ถ้าไม่เห็นข้อความนี้หรือไม่มีเมนูนั้น = หน้าเว็บยังเป็นไฟล์เก่า (ลอง <strong>Ctrl+F5</strong> หรืออัปโหลด <code>editor.html</code> / <code>js/editor.js</code> ขึ้นเซิร์ฟใหม่)</p>
<p class="editor-draw-build-hint editor-collapsible-hint" style="margin:0 0 0.4rem;font-size:0.74rem;color:#9ece6a;line-height:1.35;" hidden>มีโหมด <strong>รูปบนกริด</strong> ในเมนู «โหมดวาด» — ถ้าไม่เห็นข้อความนี้หรือไม่มีเมนูนั้น = หน้าเว็บยังเป็นไฟล์เก่า (ลอง <strong>Ctrl+F5</strong> หรืออัปโหลด <code>editor.html</code> / <code>js/editor.js</code> ขึ้นเซิร์ฟใหม่)</p>
<div class="editor-card__row editor-card__row--wrap">
<label class="editor-field-drawmode">โหมดวาด
<select id="draw-mode" title="จัดกลุ่มตามประเภท — ตัวเลือกที่ไม่ใช้กับโหมดเกมจะถูกซ่อน">
@@ -331,6 +331,8 @@
<label class="editor-bg-scroll-check"><input type="checkbox" id="editor-gauntlet-runway-bg-enabled" checked> เปิดเลื่อน / Enable runway BG</label>
<label>ความเร็ว px/s <input type="number" id="editor-gauntlet-runway-bg-speed" min="8" max="400" step="4" value="48" style="width:5rem" title="ความเร็วเลื่อนแนวนอน (จอ)"></label>
<label title="จุดเกิด (คอลัมน์ซ้ายสุด) เทียบตำแหน่งในแถบรูป START — เก็บเป็น runwaySpawnAlignFrac ในแมป">จุดเกิด vs START (0.020.95) <input type="number" id="editor-gauntlet-runway-bg-align-frac" min="0.02" max="0.95" step="0.01" value="0.32" style="width:4.2rem"></label>
<label title="เส้นจบในรูป FINISH (5) — 0=ซ้ายสุด · คลิกที่พรีวิวรูป 5 ได้">จุดจบในรูป 5 (0–0.92) <input type="number" id="editor-gauntlet-runway-bg-finish-line-frac" min="0" max="0.92" step="0.01" value="0.34" style="width:4.2rem"></label>
<label title="ตำแหน่งเส้นจบบนจอ (0=ซ้าย 0.55≈กลาง) ก่อนหยุดเลื่อน — finishStopViewportFrac">จุดจบบนจอ (0.08–0.85) <input type="number" id="editor-gauntlet-runway-bg-finish-stop-vp" min="0.08" max="0.85" step="0.01" value="0.55" style="width:4.2rem"></label>
</div>
<div class="editor-card__row editor-card__row--wrap" style="margin-top:0.35rem">
<label>รูปเริ่ม (1) <input type="file" id="editor-gauntlet-runway-bg-start-file" accept="image/*"></label>
@@ -345,13 +347,19 @@
<figure style="margin:0;text-align:center"><figcaption style="font-size:0.72rem;opacity:0.9">2 LOOP</figcaption><img id="editor-gauntlet-runway-bg-prev-1" width="128" height="72" alt="" style="display:block;width:128px;height:72px;object-fit:cover;border:1px solid #3d3f52;border-radius:6px;background:#1a1b26"></figure>
<figure style="margin:0;text-align:center"><figcaption style="font-size:0.72rem;opacity:0.9">3 LOOP</figcaption><img id="editor-gauntlet-runway-bg-prev-2" width="128" height="72" alt="" style="display:block;width:128px;height:72px;object-fit:cover;border:1px solid #3d3f52;border-radius:6px;background:#1a1b26"></figure>
<figure style="margin:0;text-align:center"><figcaption style="font-size:0.72rem;opacity:0.9">4 LOOP</figcaption><img id="editor-gauntlet-runway-bg-prev-3" width="128" height="72" alt="" style="display:block;width:128px;height:72px;object-fit:cover;border:1px solid #3d3f52;border-radius:6px;background:#1a1b26"></figure>
<figure style="margin:0;text-align:center"><figcaption style="font-size:0.72rem;opacity:0.9">5 FINISH</figcaption><img id="editor-gauntlet-runway-bg-prev-4" width="128" height="72" alt="" style="display:block;width:128px;height:72px;object-fit:cover;border:1px solid #3d3f52;border-radius:6px;background:#1a1b26"></figure>
<figure class="editor-runway-prev-figure editor-runway-prev-figure--finish-pick" style="margin:0;text-align:center">
<figcaption style="font-size:0.72rem;opacity:0.9">5 FINISH · คลิกกำหนดจุดจบ</figcaption>
<div class="editor-runway-prev-pick" id="editor-gauntlet-runway-bg-prev-pick-4" title="คลิกบนรูปเพื่อตั้งเส้นจบ (แถบ FINISH)">
<img id="editor-gauntlet-runway-bg-prev-4" width="128" height="72" alt="Runway FINISH — คลิกกำหนดจุดจบ" style="display:block;width:128px;height:72px;object-fit:cover;border:1px solid #3d3f52;border-radius:6px;background:#1a1b26;cursor:crosshair">
<span class="editor-runway-finish-line-mark" id="editor-gauntlet-runway-finish-line-mark" aria-hidden="true"></span>
</div>
</figure>
</div>
<p id="editor-gauntlet-runway-bg-upload-summary" class="legend" style="margin:0.4rem 0 0;line-height:1.5;font-size:0.82rem" aria-live="polite"></p>
</div>
</section>
<section class="editor-card editor-card--help" aria-labelledby="editor-card-help-label">
<section class="editor-card editor-card--help editor-collapsible-hint" aria-labelledby="editor-card-help-label" hidden>
<h2 class="editor-card__title" id="editor-card-help-label">วิธีใช้บนกริด</h2>
<ul class="editor-help-list legend editor-legend-short">
<li><strong>คลิกซ้าย</strong> = วาด · <strong>คลิกขวา</strong> = ลบ · ลากค้างได้</li>
@@ -416,7 +424,7 @@
</div>
</div>
<script src="js/version.js?v=0.0258"></script>
<script src="js/editor.js?v=0.0338"></script>
<script src="js/editor.js?v=0.0340"></script>
<div class="version-tag">v —</div>
<!-- ===== QB overlay layout: map เต็มจอ + แถบเครื่องมือลอย ดัก/พับ/ปิดเปิดได้ (เพิ่มทับ ไม่แตะ editor.js) ===== -->
@@ -467,6 +475,27 @@
.qb-ctrl-btn.on { border-color:#22d3ee; background:rgba(34,211,238,.18); }
#qb-tb-reopen { position:fixed; z-index:42; top:10px; left:10px; display:none; box-shadow:0 6px 20px rgba(0,0,0,.4); }
#qb-tb-reopen.show { display:inline-grid; }
/* คำแนะนำ Editor — ซ่อนเป็นค่าเริ่มต้น กดปุ่ม 💡 ในแถบหัวเพื่อเปิด */
.editor-toolbar-wrap:not(.editor-hints-visible) .editor-collapsible-hint { display: none !important; }
</style>
<style>
.editor-runway-prev-pick { position: relative; display: inline-block; }
.editor-runway-finish-line-mark {
position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
background: #ffeb3b; box-shadow: 0 0 6px rgba(255, 235, 59, 0.95);
pointer-events: none; z-index: 2;
}
.editor-runway-finish-line-mark::before,
.editor-runway-finish-line-mark::after {
content: ''; position: absolute; left: 50%; transform: translateX(-50%);
border-left: 5px solid transparent; border-right: 5px solid transparent;
}
.editor-runway-finish-line-mark::before {
top: -1px; border-bottom: 7px solid #ffeb3b;
}
.editor-runway-finish-line-mark::after {
bottom: -1px; border-top: 7px solid #ffeb3b;
}
</style>
<button id="qb-tb-reopen" class="qb-ctrl-btn" type="button">🛠️ เครื่องมือ</button>
<script>
@@ -478,6 +507,7 @@
var hdr = document.createElement('div');
hdr.id = 'qb-tb-head';
hdr.innerHTML = '<b>🛠️ เครื่องมือฉาก</b>'+
'<button type="button" class="qb-ctrl-btn" id="qb-tb-hints-toggle" title="แสดง/ซ่อนคำแนะนำ (โหมดเกม · ชนกำแพง · วิธีใช้กริด)">💡</button>'+
'<button type="button" class="qb-ctrl-btn" data-dock="top" title="ดักบน">⬆</button>'+
'<button type="button" class="qb-ctrl-btn" data-dock="left" title="ดักซ้าย">⬅</button>'+
'<button type="button" class="qb-ctrl-btn" data-dock="right" title="ดักขวา">➡</button>'+
@@ -500,6 +530,29 @@
document.getElementById('qb-tb-collapse').addEventListener('click', function(){ setHidden(true); });
reopen.addEventListener('click', function(){ setHidden(false); });
var hintsBtn = document.getElementById('qb-tb-hints-toggle');
function setHintsVisible(on) {
tw.classList.toggle('editor-hints-visible', !!on);
if (hintsBtn) {
hintsBtn.classList.toggle('on', !!on);
hintsBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
hintsBtn.title = on ? 'ซ่อนคำแนะนำ' : 'แสดงคำแนะนำ (โหมดเกม · ชนกำแพง · วิธีใช้กริด)';
}
tw.querySelectorAll('.editor-collapsible-hint').forEach(function(el) {
if (on) el.removeAttribute('hidden');
else el.setAttribute('hidden', '');
});
try { localStorage.setItem('qbEdHintsVisible', on ? '1' : '0'); } catch (e) { /* ignore */ }
}
if (hintsBtn) {
hintsBtn.addEventListener('click', function() {
setHintsVisible(!tw.classList.contains('editor-hints-visible'));
});
}
var hintsOn = false;
try { hintsOn = localStorage.getItem('qbEdHintsVisible') === '1'; } catch (e) { /* ignore */ }
setHintsVisible(hintsOn);
// การ์ดแต่ละใบ กดหัวข้อเพื่อพับ/เปิด
function keyFor(card, title){ return title.id || (title.textContent||'').trim().slice(0,40); }
var cards = tw.querySelectorAll('.editor-card');
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+99
View File
@@ -561,10 +561,14 @@
const EDITOR_GAUNTLET_RUNWAY_BG_DEFAULT_LOOP3 = BASE + '/img/editor-bg-mno9kb07/loop3.png';
const EDITOR_GAUNTLET_RUNWAY_BG_DEFAULT_LOOP4 = BASE + '/img/editor-bg-mno9kb07/loop4.png';
const EDITOR_GAUNTLET_RUNWAY_BG_DEFAULT_FINISH = BASE + '/img/editor-bg-mno9kb07/finish.png';
const EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC = 0.34;
const EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP = 0.55;
let editorGauntletRunwayBgConfig = {
enabled: true,
speedPxPerSec: 48,
runwaySpawnAlignFrac: 0.32,
finishLineInFinishArtFrac: EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC,
finishStopViewportFrac: EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP,
startImage: null,
loopImage2: null,
loopImage3: null,
@@ -804,6 +808,8 @@
enabled: true,
speedPxPerSec: 48,
runwaySpawnAlignFrac: 0.32,
finishLineInFinishArtFrac: EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC,
finishStopViewportFrac: EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP,
startImage: null,
loopImage2: null,
loopImage3: null,
@@ -820,10 +826,16 @@
const sp = Math.max(8, Math.min(400, Math.floor(Number(raw.speedPxPerSec)) || 48));
const afRaw = Number(raw.runwaySpawnAlignFrac);
const af = Number.isFinite(afRaw) ? Math.max(0.02, Math.min(0.95, afRaw)) : 0.32;
const flRaw = Number(raw.finishLineInFinishArtFrac);
const fl = Number.isFinite(flRaw) ? Math.max(0, Math.min(0.92, flRaw)) : EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC;
const fsvRaw = Number(raw.finishStopViewportFrac);
const fsv = Number.isFinite(fsvRaw) ? Math.max(0.08, Math.min(0.85, fsvRaw)) : EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP;
editorGauntletRunwayBgConfig = {
enabled: raw.enabled !== false,
speedPxPerSec: sp,
runwaySpawnAlignFrac: af,
finishLineInFinishArtFrac: fl,
finishStopViewportFrac: fsv,
startImage: typeof raw.startImage === 'string' && raw.startImage.length ? raw.startImage : null,
loopImage2: typeof raw.loopImage2 === 'string' && raw.loopImage2.length ? raw.loopImage2 : null,
loopImage3: typeof raw.loopImage3 === 'string' && raw.loopImage3.length ? raw.loopImage3 : null,
@@ -862,10 +874,58 @@
const v = editorGauntletRunwayBgConfig.runwaySpawnAlignFrac;
af.value = String(Number.isFinite(v) ? Math.max(0.02, Math.min(0.95, v)) : 0.32);
}
const fsvEl = document.getElementById('editor-gauntlet-runway-bg-finish-stop-vp');
if (fsvEl) {
const sv = editorGauntletRunwayBgConfig.finishStopViewportFrac;
fsvEl.value = String(Number.isFinite(sv) ? Math.max(0.08, Math.min(0.85, sv)) : EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP);
}
syncEditorGauntletRunwayFinishLineUi();
syncEditorGauntletRunwayBgUploadSummary();
syncEditorGauntletRunwayBgThumbnails();
}
/** แปลงตำแหน่งคลิกบนพรีวิว (object-fit:cover) → เศษแนวนอนในงานศิลป์รูป FINISH */
function editorRunwayFinishArtFracFromPickClick(imgEl, clientX) {
if (!imgEl) return EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC;
const rect = imgEl.getBoundingClientRect();
const natW = imgEl.naturalWidth || 1;
const natH = imgEl.naturalHeight || 1;
const dispW = Math.max(1, rect.width);
const dispH = Math.max(1, rect.height);
const scale = Math.max(dispW / natW, dispH / natH);
const drawnW = natW * scale;
const offsetX = (dispW - drawnW) * 0.5;
const artX = (clientX - rect.left - offsetX) / Math.max(1e-6, drawnW);
return Math.max(0, Math.min(0.92, artX));
}
function editorRunwayFinishLineMarkLeftPct(imgEl, artFrac) {
if (!imgEl) return artFrac * 100;
const natW = imgEl.naturalWidth || 1;
const natH = imgEl.naturalHeight || 1;
const dispW = Math.max(1, imgEl.clientWidth || imgEl.width || 128);
const dispH = Math.max(1, imgEl.clientHeight || imgEl.height || 72);
const scale = Math.max(dispW / natW, dispH / natH);
const drawnW = natW * scale;
const offsetX = (dispW - drawnW) * 0.5;
const px = offsetX + drawnW * Math.max(0, Math.min(0.92, artFrac));
return (px / dispW) * 100;
}
function syncEditorGauntletRunwayFinishLineUi() {
const finIn = document.getElementById('editor-gauntlet-runway-bg-finish-line-frac');
let frac = Number(editorGauntletRunwayBgConfig.finishLineInFinishArtFrac);
if (!Number.isFinite(frac)) frac = EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC;
frac = Math.max(0, Math.min(0.92, frac));
editorGauntletRunwayBgConfig.finishLineInFinishArtFrac = frac;
if (finIn) finIn.value = String(Math.round(frac * 100) / 100);
const mark = document.getElementById('editor-gauntlet-runway-finish-line-mark');
const img = document.getElementById('editor-gauntlet-runway-bg-prev-4');
if (mark && img) {
mark.style.left = editorRunwayFinishLineMarkLeftPct(img, frac) + '%';
}
}
function syncEditorGauntletRunwayBgThumbnails() {
if (mapId !== EDITOR_GAUNTLET_RUNWAY_BG_MAP_ID) return;
const keys = ['startImage', 'loopImage2', 'loopImage3', 'loopImage4', 'finishImage'];
@@ -884,7 +944,15 @@
const src = (typeof custom === 'string' && custom.length) ? custom : defaults[i];
el.alt = 'Runway ' + labels[i] + ' — slot ' + (i + 1);
el.src = src;
if (i === 4) {
const onFinLoad = function () {
el.removeEventListener('load', onFinLoad);
syncEditorGauntletRunwayFinishLineUi();
};
el.addEventListener('load', onFinLoad);
}
}
syncEditorGauntletRunwayFinishLineUi();
}
function describeRunwaySlotStoredValue(val, fileName) {
@@ -933,10 +1001,16 @@
};
const afEl = document.getElementById('editor-gauntlet-runway-bg-align-frac');
const afv = Math.max(0.02, Math.min(0.95, Number(afEl && afEl.value) || 0.32));
const flEl = document.getElementById('editor-gauntlet-runway-bg-finish-line-frac');
const flv = Math.max(0, Math.min(0.92, Number(flEl && flEl.value) || EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC));
const fsvEl = document.getElementById('editor-gauntlet-runway-bg-finish-stop-vp');
const fsv = Math.max(0.08, Math.min(0.85, Number(fsvEl && fsvEl.value) || EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP));
return {
enabled: !!(en && en.checked),
speedPxPerSec: sp,
runwaySpawnAlignFrac: afv,
finishLineInFinishArtFrac: flv,
finishStopViewportFrac: fsv,
startImage: s('startImage'),
loopImage2: s('loopImage2'),
loopImage3: s('loopImage3'),
@@ -3604,6 +3678,8 @@
enabled: true,
speedPxPerSec: editorGauntletRunwayBgConfig.speedPxPerSec,
runwaySpawnAlignFrac: editorGauntletRunwayBgConfig.runwaySpawnAlignFrac,
finishLineInFinishArtFrac: editorGauntletRunwayBgConfig.finishLineInFinishArtFrac,
finishStopViewportFrac: editorGauntletRunwayBgConfig.finishStopViewportFrac,
startImage: editorGauntletRunwayBgConfig.startImage,
loopImage2: editorGauntletRunwayBgConfig.loopImage2,
loopImage3: editorGauntletRunwayBgConfig.loopImage3,
@@ -3629,6 +3705,29 @@
editorGauntletRunwayBgConfig.runwaySpawnAlignFrac = Math.max(0.02, Math.min(0.95, Number(afIn.value) || 0.32));
});
}
const flIn = document.getElementById('editor-gauntlet-runway-bg-finish-line-frac');
if (flIn) {
flIn.addEventListener('change', () => {
editorGauntletRunwayBgConfig.finishLineInFinishArtFrac = Math.max(0, Math.min(0.92, Number(flIn.value) || EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_LINE_FRAC));
syncEditorGauntletRunwayFinishLineUi();
});
}
const fsvIn = document.getElementById('editor-gauntlet-runway-bg-finish-stop-vp');
if (fsvIn) {
fsvIn.addEventListener('change', () => {
editorGauntletRunwayBgConfig.finishStopViewportFrac = Math.max(0.08, Math.min(0.85, Number(fsvIn.value) || EDITOR_GAUNTLET_RUNWAY_DEFAULT_FINISH_STOP_VP));
});
}
const finPick = document.getElementById('editor-gauntlet-runway-bg-prev-pick-4');
const finImg = document.getElementById('editor-gauntlet-runway-bg-prev-4');
if (finPick && finImg) {
finPick.addEventListener('click', (e) => {
if (e.target === finPick) return;
const frac = editorRunwayFinishArtFracFromPickClick(finImg, e.clientX);
editorGauntletRunwayBgConfig.finishLineInFinishArtFrac = frac;
syncEditorGauntletRunwayFinishLineUi();
});
}
function wireFile(id, key) {
const el = document.getElementById(id);
if (!el) return;
+206 -50
View File
@@ -1078,24 +1078,7 @@
&& Number.isFinite(worldAlign.zDraw)
&& Number.isFinite(worldAlign.mapWpx)
&& Number.isFinite(worldAlign.mapHpx);
if (gauntletCrownRunwayBgFinishLatched && gauntletCrownRunwayBgImageSlotReadyPlay(finish)) {
if (align) {
const { camX, camY, zDraw, mapWpx, mapHpx } = worldAlign;
ctx.fillStyle = '#1a1b26';
ctx.fillRect(0, 0, canvasW, canvasH);
ctx.save();
/* เมทริกซ์เดียวกับ worldToScreen + คลิปโลก [0,map] — รูปจบครอบทั้งแผนที่ที่ world origin */
ctx.setTransform(zDraw, 0, 0, zDraw, canvasW / 2 - camX * zDraw, canvasH / 2 - camY * zDraw);
ctx.beginPath();
ctx.rect(0, 0, mapWpx, mapHpx);
ctx.clip();
drawImageCoverCanvasPlay(ctx, finish, Math.max(1, Math.round(mapWpx)), Math.max(1, Math.round(mapHpx)));
ctx.restore();
} else {
drawImageCoverCanvasPlay(ctx, finish, Math.max(1, Math.round(canvasW)), Math.max(1, Math.round(canvasH)));
}
return;
}
/* หลัง latch ยังวาดแถบรันเวย์ที่ scroll ค้าง — อย่า switch เป็น cover เต็มแผนที่ (ทำให้ FINISH กระโดดซ้าย) */
/* ใช้ความกว้างมองเห็นแบบ float ให้เท่ากับ canvas.width/zDraw — ห้าม Math.round ไม่งั้นซูมแล้วแถบ runway กว้างไม่ตรง worldToScreen (แผนหลุดจากตัว) */
const cwR = align
? Math.max(1e-6, worldAlign.worldMaxX - worldAlign.worldMinX)
@@ -1175,6 +1158,27 @@
return screenFrac <= gauntletCrownRunwayFinishStopViewportFracPlay() + 0.008;
}
/** จัด scroll ให้เส้น FINISH ตรง finishStopViewportFrac บนจอ (เรียกตอนเริ่ม freeze) */
function gauntletCrownRunwaySnapScrollToFinishStopPlay() {
if (!mapData || !canvas || !gauntletCrownRunwayBgDrawActivePlay()) return;
const g = gauntletCrownRunwayScrollGeometryPlay();
if (!g) return;
const raw = mapData.gauntletCrownRunwayBg && typeof mapData.gauntletCrownRunwayBg === 'object' ? mapData.gauntletCrownRunwayBg : {};
let lineInArt = Number(raw.finishLineInFinishArtFrac);
if (!Number.isFinite(lineInArt)) {
lineInArt = gauntletCrownRunwayBgMapEligiblePlay() ? 0.34 : 0.12;
}
lineInArt = Math.max(0, Math.min(0.92, lineInArt));
const finishLineWorldX = g.finishStart + g.w4Safe * lineInArt;
const stopVp = gauntletCrownRunwayFinishStopViewportFracPlay();
const targetScrollView = finishLineWorldX - stopVp * g.cwWorld;
const zGaRaw = computePlayCameraZDrawPlay();
const gCam = getGauntletCrownHeistGroupCameraCenterPxPlay(tileSize, canvas.width, canvas.height, zGaRaw);
if (!gCam) return;
const worldMinX = gCam.px - canvas.width / (2 * zGaRaw);
gauntletCrownRunwayBgScrollPx = targetScrollView - worldMinX;
}
function resetGauntletCrownRunwaySpawnScrollSnap() {
gauntletCrownRunwaySpawnScrollSnapped = false;
}
@@ -1286,6 +1290,7 @@
}
if (gauntletCrownRunwayBgMapEligiblePlay() && gauntletCrownRunwayScrollStripStopZoneReachedPlay()) {
gauntletCrownRunwaySnapScrollToFinishStopPlay();
gauntletCrownRunwayBgStripFreezeSinceMs = Date.now();
try {
syncGauntletCrownJumpButton();
@@ -3883,6 +3888,10 @@
delete panel.dataset.qmPlaqueTry;
}
}
panel.classList.toggle(
'quiz-map-question-panel--tf-mock-qz',
!!(isQuiz() && isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'live' && isMissionMockHudPlay()),
);
const kickerEl = document.getElementById('quiz-map-question-kicker');
const missionLiveQ = isQuiz() && isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'live';
const inReadOrAnswer = playQuizPhaseLocal === 'read' || playQuizPhaseLocal === 'answer';
@@ -5352,7 +5361,8 @@
} else if (isQuizQuestionMissionHudActivePlay()) {
const qmc = getQuizQuestionMissionMapCenterWorldPxPlay();
if (qmc) {
zDraw = Math.min(canvas.width / qmc.mwPx, canvas.height / qmc.mhPx) * 0.96;
/* ซูมออกเล็กน้อยให้เห็นพื้นหลัง/โซน SAFE-SCAM ครบแบบ mock (0.96 เคย crop ขอบล่างบนจอแคบ) */
zDraw = Math.min(canvas.width / qmc.mwPx, canvas.height / qmc.mhPx) * 0.90;
}
} else if (isQuizCarry()) {
const mwPx = w * tileSize;
@@ -7276,9 +7286,64 @@
/** HUD กลางจอ (เวลา / แผ่นคำถาม) — ชื่อไฟล์คู่กับ public/img/QUESTION บนดิสก์ → URL /Game/img/QUESTION */
function questionMissionHudAssetUrl(file) {
if (isMissionMockHudPlay()) return missionMockHudAssetUrl(file);
return questionMissionAssetUrl(file);
}
/** สกิน HUD แบบ mock Minigame 17 (fix 1920×1080 + score-bar / profile) */
function getMissionMockHudSkinPlay() {
if (isQuizQuestionMissionUiMapPlay()) return 'question_mission';
if (isStackTowerMissionUiMapPlay()) return 'stack_tower';
if (isJumpSurviveMissionUiMapPlay()) return 'jumper';
if (isSpaceShooterMissionUiMapPlay()) return 'violent_crime';
if (isMegaVirusMissionShellMapPlay()) return 'mega_virus';
if (isQuizCarry() && quizCarryEmbedMissionFlowActive()) return 'quiz_carry';
return '';
}
function isMissionMockHudPlay() {
return !!getMissionMockHudSkinPlay();
}
function missionMockHudAssetUrl(file) {
const skin = getMissionMockHudSkinPlay();
const f = String(file || '').replace(/^\//, '');
if (skin === 'jumper') return jumperAssetUrl(f);
if (skin === 'violent_crime') return violentCrimeAssetUrl(f);
if (skin === 'mega_virus') return megaVirusAssetUrl(f);
if (skin === 'stack_tower') return stackTowerAssetUrl(f);
if (skin === 'quiz_carry') return BASE + '/img/quiz-carry/' + f;
return questionMissionAssetUrl(f);
}
function applyMissionMockHudCssVarsPlay(root) {
if (!root) return;
if (!isMissionMockHudPlay()) {
root.style.removeProperty('--mmh-score-bar');
root.style.removeProperty('--mmh-score-avatar');
root.style.removeProperty('--mmh-mic-on');
delete root.dataset.missionMockSkin;
return;
}
const skin = getMissionMockHudSkinPlay();
root.dataset.missionMockSkin = skin;
root.style.setProperty('--mmh-score-bar', "url('" + missionMockHudAssetUrl('score-bar.png') + "')");
root.style.setProperty('--mmh-score-avatar', "url('" + missionMockHudAssetUrl('score-avartar.png') + "')");
root.style.setProperty('--mmh-mic-on', "url('" + missionMockHudAssetUrl('btn-mic_on.png') + "')");
const micImg = document.getElementById('play-cyber-mic-img');
if (micImg) {
micImg.classList.remove('is-hidden');
micImg.setAttribute('aria-hidden', 'false');
const micSrc = missionMockHudAssetUrl('btn-mic_on.png');
if (micImg.getAttribute('src') !== micSrc) micImg.setAttribute('src', micSrc);
}
const frameOverlay = document.getElementById('play-cyber-profile-frame-overlay');
if (frameOverlay) {
const frameSrc = missionMockHudAssetUrl('Avartar.png');
if (frameOverlay.getAttribute('src') !== frameSrc) frameOverlay.setAttribute('src', frameSrc);
}
}
function isQuizQuestionMissionHudActivePlay() {
return isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'live';
}
@@ -12224,7 +12289,7 @@
applyCyberHudScoreAvatarImgWithRetry(this, avRow, 3);
};
if (tfMockSummary) {
/* mng8a80o: portrait อยู่ใต้ player-frame.png (mockup .portrait + .frame z-index 3) */
/* mission mock: หน้าใน gcm-av-clip z3 ทับ player-frame z2 (กลางกรอบทึบ) */
const avClip = document.createElement('div');
avClip.className = 'gcm-av-clip';
avClip.appendChild(img);
@@ -17446,6 +17511,11 @@
canvas.width = Math.max(320, cw);
canvas.height = Math.max(240, ch);
syncQuizCarryEmbedCountdownLayout();
const hud = document.getElementById('play-cyber-hud');
if (hud && hud.classList.contains('play-cyber-hud--quiz-tf-mock')) {
_quizTfScaleLast = 0;
updateQuizTfMockScale(hud);
}
}
canvas.addEventListener('click', (e) => {
@@ -18674,7 +18744,7 @@
function cyberHudScoreAvatarCacheKey(row) {
const cid = row.characterId ? String(row.characterId) : '';
const tint = resolveCyberHudAvatarTintPlay(row);
return [row.isMe ? 'me' : String(row.id), cid, tint.head, tint.hair, tint.body, 'faceV2'].join('|');
return [row.isMe ? 'me' : String(row.id), cid, tint.head, tint.hair, tint.body, 'faceV4'].join('|');
}
function findPlayCyberHudScoreAvatarSrcForPeer(peerId) {
@@ -18731,9 +18801,12 @@
const sy = sh * 0.02;
const sw2 = sw * 0.8;
const sh2 = sh * 0.5;
const cropSize = Math.min(sw2, sh2);
const sxCenter = sx + (sw2 - cropSize) * 0.5;
ctx.clearRect(0, 0, 120, 120);
try {
ctx.drawImage(drawSrc, sx, sy, sw2, sh2, 0, 0, 120, 120);
/* crop สี่เหลี่ยมจัตุรัสจากหัว → 120×120 ไม่ยืด */
ctx.drawImage(drawSrc, sxCenter, sy, cropSize, cropSize, 0, 0, 120, 120);
} catch (e) {
return '';
}
@@ -18749,6 +18822,58 @@
return !!(img && img.src && img.src !== defaultAvatarImg.src && img.complete && img.naturalWidth > 8 && img.naturalHeight > 8);
}
/** โปรไฟล์มุมขวาบน — mission mock ใช้หน้า crop สี่เหลี่ยม ไม่ดึง sprite เต็มตัว */
function setCyberHudProfilePortraitImg(portraitImg) {
if (!portraitImg) return;
const cid = me.characterId || getPlayCharacterId();
if (!cid) {
portraitImg.src = defaultAvatarImg.src;
return;
}
const row = {
id: myId,
characterId: cid,
isMe: true,
playTint: me.playTint || resolvePlayTintForPeer(String(myId != null ? myId : 'me')),
};
if (isMissionMockHudPlay()) {
const tint = resolveCyberHudAvatarTintPlay(row);
const cacheKey = cyberHudScoreAvatarCacheKey(row) + '|profile';
const cached = cyberHudScoreAvatarUrlCache.get(cacheKey);
if (cached) {
portraitImg.src = cached;
return;
}
const faceUrl = composeCyberHudFacePortraitDataUrl(cid, tint);
if (faceUrl) {
portraitImg.src = faceUrl;
cyberHudScoreAvatarUrlCache.set(cacheKey, faceUrl);
return;
}
}
setCyberHudScoreAvatarImg(portraitImg, row);
}
function applyCyberHudProfilePortraitImgWithRetry(portraitImg, attempt) {
attempt = attempt || 0;
setCyberHudProfilePortraitImg(portraitImg);
if (cyberHudScoreAvatarImgLooksReady(portraitImg)) return;
if (attempt >= 16) return;
const cid = me.characterId || getPlayCharacterId();
const delay = 40 + attempt * 90;
setTimeout(function () {
if (cid) {
preloadPlayTintForAvatar(cid, resolveCyberHudAvatarTintPlay({
id: myId,
characterId: cid,
isMe: true,
playTint: me.playTint || resolvePlayTintForPeer(String(myId != null ? myId : 'me')),
}));
}
applyCyberHudProfilePortraitImgWithRetry(portraitImg, attempt + 1);
}, delay);
}
/** แถบ Cyber SCORE + สรุปผล — หน้า idle down + tint ต่อผู้เล่น */
function setCyberHudScoreAvatarImg(avImg, row) {
const cid = row.characterId ? String(row.characterId) : '';
@@ -18901,24 +19026,50 @@
let _quizTfMockActivePrev = false;
function updateQuizTfMockScale(root) {
root = root || document.getElementById('play-cyber-hud');
if (!root) return;
let cw;
let ch;
if (document.documentElement.classList.contains('play-preview-editor-embed')) {
const host = root.offsetParent || root.parentElement || document.querySelector('.game-wrap');
cw = (host && host.clientWidth) || window.innerWidth;
ch = (host && host.clientHeight) || window.innerHeight;
} else {
cw = window.innerWidth;
ch = window.innerHeight;
}
if (!root || !root.classList.contains('play-cyber-hud--quiz-tf-mock')) return;
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;
/* เขียนเฉพาะเมื่อค่าต่างจริง (กันการเขียน style ซ้ำ → invalidate ทุกเฟรม) */
if (Math.abs(s - _quizTfScaleLast) < 0.0005) return;
root.style.position = 'fixed';
root.style.left = Math.round(r.left) + 'px';
root.style.top = Math.round(r.top) + 'px';
root.style.width = Math.round(cw) + 'px';
root.style.height = Math.round(ch) + 'px';
root.style.right = 'auto';
root.style.bottom = 'auto';
root.style.transform = 'none';
const stage = root.querySelector('.play-cyber-hud-mock-stage');
if (stage) stage.setAttribute('aria-hidden', 'false');
if (Math.abs(s - _quizTfScaleLast) < 0.0005
&& root.dataset.qtfStackW === String(Math.round(cw))
&& root.dataset.qtfStackH === String(Math.round(ch))) {
return;
}
_quizTfScaleLast = s;
root.dataset.qtfStackW = String(Math.round(cw));
root.dataset.qtfStackH = String(Math.round(ch));
root.style.setProperty('--qtf-scale', String(s));
}
function clearQuizTfMockHudBoundsPlay(root) {
root = root || document.getElementById('play-cyber-hud');
if (!root) return;
const stage = root.querySelector('.play-cyber-hud-mock-stage');
if (stage) stage.setAttribute('aria-hidden', 'true');
root.style.removeProperty('left');
root.style.removeProperty('top');
root.style.removeProperty('width');
root.style.removeProperty('height');
root.style.removeProperty('right');
root.style.removeProperty('bottom');
root.style.removeProperty('transform');
delete root.dataset.qtfStackW;
delete root.dataset.qtfStackH;
}
/* mng8a80o: สรุปผลภารกิจ (06-summary) — scale popup 1451×851 ตามสูตรเดียวกับ HUD (min vw/1920, vh/1080) */
function updateGcmTfMockScale(ov) {
ov = ov || document.getElementById('gauntlet-crown-mission-overlay');
@@ -18952,7 +19103,9 @@
try {
const ro = new ResizeObserver(recompute);
const host = document.querySelector('.game-wrap') || document.documentElement;
const stack = document.getElementById('play-canvas-stack');
if (host) ro.observe(host);
if (stack) ro.observe(stack);
} catch (e) { /* ignore */ }
}
}
@@ -18970,6 +19123,12 @@
root.classList.remove('play-cyber-hud--jump-mission-hide-score');
root.classList.remove('play-cyber-hud--ss-self-integrity-left');
root.classList.remove('play-cyber-hud--last-light-no-time');
root.classList.remove('play-cyber-hud--quiz-tf-mock');
clearQuizTfMockHudBoundsPlay(root);
root.style.removeProperty('--qtf-scale');
_quizTfScaleLast = 0;
_quizTfMockActivePrev = false;
document.documentElement.classList.remove('play-mission-mock-hud');
}
}
if (stackEl) stackEl.classList.toggle('play-cyber-vignette', !!on);
@@ -18982,23 +19141,26 @@
!!(isQuizQuestionMissionHudActivePlay() || isStackTowerMissionHudActivePlay() || isJumpSurviveMissionHudActivePlay()
|| isSpaceShooterMissionHudActivePlay()),
);
/* mng8a80o เท่านั้น — สไตล์ HUD ตาม mockup Minigame-1 (score-bar.png / Avartar.png / time.png) แยกจากคลาสร่วม */
root.classList.toggle('play-cyber-hud--quiz-tf-mock', !!isQuizQuestionMissionUiMapPlay());
/* Minigame 17 mission — HUD fix 1920×1080 ตาม mock (score-bar / profile qb-room) */
const missionMockOn = !!isMissionMockHudPlay();
root.classList.toggle('play-cyber-hud--quiz-tf-mock', missionMockOn);
document.documentElement.classList.toggle('play-mission-mock-hud', missionMockOn && on);
applyMissionMockHudCssVarsPlay(root);
const tfMockOn = root.classList.contains('play-cyber-hud--quiz-tf-mock');
if (tfMockOn) {
/* คำนวณ scale เฉพาะตอนเพิ่งเข้าโหมด — ไม่อ่าน layout ทุกเฟรม (กันกระตุก); ที่เหลือพึ่ง resize/ResizeObserver */
ensureQuizTfScaleResizeListener();
updateQuizTfMockScale(root);
if (!_quizTfMockActivePrev) {
ensureQuizTfScaleResizeListener();
updateQuizTfMockScale(root);
/* เฟรมถัดไป layout นิ่งแล้ว วัดอีกครั้งให้แม่น */
requestAnimationFrame(function () {
const r = document.getElementById('play-cyber-hud');
if (r && r.classList.contains('play-cyber-hud--quiz-tf-mock')) updateQuizTfMockScale(r);
});
}
} else if (_quizTfMockActivePrev) {
clearQuizTfMockHudBoundsPlay(root);
root.style.removeProperty('--qtf-scale');
_quizTfScaleLast = 0;
document.documentElement.classList.remove('play-mission-mock-hud');
}
_quizTfMockActivePrev = tfMockOn;
root.classList.toggle(
@@ -19152,13 +19314,7 @@
}
if (portrait) {
const cid = me.characterId || getPlayCharacterId();
setCyberHudScoreAvatarImg(portrait, {
id: myId,
characterId: cid,
isMe: true,
playTint: me.playTint || resolvePlayTintForPeer(String(myId != null ? myId : 'me')),
});
applyCyberHudProfilePortraitImgWithRetry(portrait, 0);
}
if (statusEl) {
if (isSpaceShooterMissionHudActivePlay()) {
@@ -19325,8 +19481,8 @@
const spaceMissionLiveHud = isSpaceShooterMissionHudActivePlay();
/** แถบ SCORE แบบ mock mng8a80o (แนวนอน อวาตาร์+ชื่อ | คะแนน) — ภารกิจคำถาม + Stack Tower + Jumper mnptfts2 + Space mnpz6rkp */
const cyberQmMockHud = quizMissionHud || stackMissionHud || jumpMissionLiveHud || spaceMissionLiveHud;
/** mng8a80o เท่านั้น — layout แถวแบบ mockup: [avatar | ชื่อบน/คะแนนล่าง] (ใช้ meta column) */
const tfMockHud = isQuizQuestionMissionUiMapPlay();
/** mission mock HUD — layout แถว: [avatar | ชื่อบน/คะแนนล่าง] (meta column) */
const tfMockHud = isMissionMockHudPlay();
const stackTeamPts = Math.max(0, Number(stackMini && stackMini.score) || 0);
const rows = [];
rows.push({
+214 -81
View File
@@ -160,10 +160,10 @@
align-self: stretch !important;
}
/* Stack Tower: portrait มุมขวา (DOM) — โฟกัสประมาณคอขึ้น */
html.play-stack-tower-pixel-canvas .play-cyber-self-frame {
html.play-stack-tower-pixel-canvas .play-cyber-profile-frame {
overflow: hidden;
}
html.play-stack-tower-pixel-canvas .play-cyber-self-frame img#play-cyber-portrait-img {
html.play-stack-tower-pixel-canvas .play-cyber-profile-frame img#play-cyber-portrait-img {
width: 72px;
height: 96px;
object-fit: cover;
@@ -493,6 +493,37 @@
#quiz-map-question-panel .quiz-map-question-kicker.is-hidden {
display: none !important;
}
/* mng8a80o — ข้อความคำถามแบบ mock qz-sms (ไม่มีกรอบทอง) */
#quiz-map-question-panel.quiz-map-question-panel--tf-mock-qz {
background: transparent !important;
border: none !important;
box-shadow: none !important;
border-radius: 0;
padding: 0 24px;
justify-content: flex-start;
align-items: center;
text-align: center;
}
#quiz-map-question-panel.quiz-map-question-panel--tf-mock-qz .quiz-map-question-kicker {
font-family: 'NotoSansThai', 'Kanit', sans-serif;
font-size: 22px;
font-weight: 700;
letter-spacing: 1px;
text-transform: none;
color: #cfeeff;
margin: 0 0 8px;
text-shadow: 0 0 8px rgba(120, 220, 255, 0.5);
}
#quiz-map-question-panel.quiz-map-question-panel--tf-mock-qz #quiz-map-question-text {
font-family: 'NotoSansThai', 'Kanit', sans-serif;
font-size: clamp(1.35rem, 2.8vw, 34px);
font-weight: 700;
line-height: 1.35;
color: #fcf005;
text-shadow:
0 2px 4px rgba(0, 0, 0, 0.85),
0 0 12px rgba(252, 240, 5, 0.35);
}
#quiz-map-question-panel.is-hidden { display: none !important; }
#quiz-map-question-panel::-webkit-scrollbar {
display: none;
@@ -1905,13 +1936,13 @@
transform: scale(1.38);
transform-origin: center 8%;
}
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-self-frame {
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-profile-frame {
overflow: hidden;
}
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-self-frame img {
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-profile-frame .play-cyber-portrait-img {
object-fit: cover;
object-position: center 6%;
transform: scale(1.36);
transform: translateX(-50%) scale(1.1);
transform-origin: center 10%;
}
#play-cyber-hud.play-cyber-hud--question-mission .play-cyber-time-sub {
@@ -1968,7 +1999,7 @@
flex: 1 1 auto;
min-width: 0;
}
#play-cyber-hud.play-cyber-hud--ss-self-integrity-left .play-cyber-self-frame {
#play-cyber-hud.play-cyber-hud--ss-self-integrity-left .play-cyber-profile-frame {
order: 2;
flex-shrink: 0;
}
@@ -2374,7 +2405,7 @@
0 2px 12px rgba(0, 0, 0, 0.88);
}
/* Last Light: OPERATOR + ไมค์ / สถานะ */
#play-cyber-hud.play-cyber-hud--gauntlet-crown-strip .play-cyber-self-frame {
#play-cyber-hud.play-cyber-hud--gauntlet-crown-strip .play-cyber-profile-frame {
padding: 3px;
border-radius: 12px;
background: rgba(0, 12, 22, 0.55);
@@ -2391,7 +2422,7 @@
align-items: flex-start;
justify-content: center;
}
#play-cyber-hud.play-cyber-hud--gauntlet-crown-strip .play-cyber-self-frame img {
#play-cyber-hud.play-cyber-hud--gauntlet-crown-strip .play-cyber-profile-frame .play-cyber-portrait-img {
width: 64px;
height: 64px;
max-width: none;
@@ -2821,20 +2852,52 @@
align-items: center;
gap: 0.35rem;
}
.play-cyber-self-frame {
padding: 4px;
background: linear-gradient(145deg, rgba(0, 60, 70, 0.9), rgba(0, 20, 35, 0.95));
border: 1px solid rgba(0, 255, 240, 0.45);
border-radius: 6px;
box-shadow:
0 0 24px rgba(0, 200, 255, 0.2),
inset 0 0 20px rgba(0, 255, 240, 0.06);
}
/* wrapper โปร่ง (display:contents) — เกมอื่นไม่เปลี่ยน; เฉพาะ quiz-tf-mock จะใช้เป็นกล่อง clip หน้า */
/* โหมดทั่วไป — clip โปร่ง; mission mock ใช้ viewport แบบ qb-room-profile */
.play-cyber-portrait-clip {
display: contents;
}
.play-cyber-self-frame img {
.play-cyber-profile-wrap {
display: block;
flex-shrink: 0;
pointer-events: none;
}
.play-cyber-profile-frame {
position: relative;
width: clamp(72px, 12vmin, 96px);
height: clamp(71px, 11.8vmin, 95px);
overflow: hidden;
background: url('/Game/img/btn-profile-bg.png') center/100% 100% no-repeat;
border: none;
box-shadow: none;
}
.play-cyber-profile-frame .play-cyber-portrait-clip {
display: block;
position: absolute;
left: 14%;
top: 6%;
width: 78%;
height: 75%;
overflow: hidden;
border-radius: 12px;
}
.play-cyber-profile-frame .play-cyber-portrait-img {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: contain;
object-position: center center;
transform: none;
transform-origin: center center;
image-rendering: pixelated;
image-rendering: crisp-edges;
pointer-events: none;
background: transparent;
}
.play-cyber-portrait-img:not(.qb-room-profile-avatar) {
display: block;
width: 72px;
height: 72px;
@@ -2842,6 +2905,12 @@
image-rendering: pixelated;
background: rgba(0, 8, 16, 0.9);
}
.play-cyber-mic-img {
display: none;
pointer-events: none;
object-fit: contain;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.5));
}
.play-cyber-self-status {
font-size: 0.58rem;
letter-spacing: 0.14em;
@@ -2933,20 +3002,36 @@
responsive-faithful: สัดส่วนเหมือน mockup บนจอ 16:9 แต่ยังเล่นมือถือได้
(ไม่กระทบ Stack/Jumper/Space ที่ใช้ --question-mission ร่วมกัน)
========================================================= */
/* mock stage — โหมดปกติไม่กระทบ layout (display:contents) */
.play-cyber-hud-mock-stage {
display: contents;
}
/* ====== fix layout 1920×1080 แล้ว scale ทั้งก้อน (เทียบ scale.js ของ mockup) ======
--qtf-scale ตั้งจาก JS = min(W/1920, H/1080) → ทุกชิ้นเป็น px คงที่ ตรง mockup เป๊ะบนจอ 16:9
selector มี html + .play-cyber-hud ซ้ำ เพื่อชนะ rule editor-embed (position:absolute; inset:0) */
HUD ยึดกรอบ #play-canvas-stack (JS) · stage 1920×1080 scale กลางแบบ letterbox */
html #play-cyber-hud.play-cyber-hud.play-cyber-hud--quiz-tf-mock {
width: 1920px;
height: 1080px;
position: fixed;
inset: auto;
z-index: 55;
overflow: visible;
pointer-events: none;
box-sizing: border-box;
}
html #play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-hud-mock-stage {
display: block;
position: absolute;
left: 50%;
top: 50%;
width: 1920px;
height: 1080px;
transform: translate(-50%, -50%) scale(var(--qtf-scale, 1));
transform-origin: center center;
pointer-events: none;
}
html.play-mission-mock-hud .play-canvas-stack {
overflow: visible;
}
/* ---- SCORE panel: score-bar.png 156×433 @ (2,16) (เหมือน mockup) ---- */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-scoreboard {
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-hud-mock-stage .play-cyber-scoreboard {
position: absolute;
left: 2px;
top: 16px;
@@ -2954,7 +3039,7 @@
height: 433px;
aspect-ratio: auto;
padding: 0;
background: url('/Game/img/QUESTION/score-bar.png') no-repeat left top / 156px 433px;
background: var(--mmh-score-bar, url('/Game/img/QUESTION/score-bar.png')) no-repeat left top / 156px 433px;
border: none;
border-radius: 0;
box-shadow: none;
@@ -2998,37 +3083,53 @@
min-width: 0;
height: 100%;
}
/* avatar (≈70px) : กรอบ score-avartar.png กลางทึบ navy → ใช้เป็น "พื้นหลัง", หน้าตัวละครวาง "ทับบน" + clip ในขอบ
(มองไม่เห็นถ้าวางหน้าใต้กรอบ เพราะกลางกรอบทึบ — ต่างจาก mockup ที่ <img> ว่าง) */
/* avatar 70×70 — กลางกรอบ score-avartar ทึบ → หน้าต้องอยู่ z3 ทับกรอบ z2 (ขอบ cyan โผล่รอบนอก inset 4px) */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-qm-av-wrap {
position: relative;
height: 100%;
aspect-ratio: 1 / 1;
width: auto;
flex: 0 0 auto;
display: block;
width: 70px;
height: 70px;
min-width: 70px;
min-height: 70px;
flex: 0 0 70px;
border: none;
border-radius: 0;
overflow: visible;
background: url('/Game/img/QUESTION/score-avartar.png') no-repeat center / 100% 100%;
background: transparent;
}
/* clip = ช่อง navy ในกรอบ (เว้นขอบ cyan ~4px/70≈6%) — หน้าตัวละครถูก crop ในนี้ วางทับ navy */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-qm-av-clip {
position: absolute;
inset: 7%;
overflow: hidden;
border-radius: 12%;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-score-row--qm-mock .play-cyber-score-av--qm {
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-qm-av-wrap::after {
content: '';
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
background: var(--mmh-score-avatar, url('/Game/img/QUESTION/score-avartar.png')) no-repeat center / contain;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-qm-av-clip {
position: absolute;
inset: 4px;
z-index: 3;
overflow: hidden;
border-radius: 6px;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-score-row--qm-mock .play-cyber-score-av--qm,
#play-cyber-hud.play-cyber-hud--quiz-tf-mock.play-cyber-hud--question-mission .play-cyber-score-row--qm-mock .play-cyber-score-av--qm {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
min-width: 0;
min-height: 0;
border: none;
border-radius: 0;
transform: none;
border-radius: 6px;
object-fit: cover;
object-position: center 30%;
transform: scale(0.86) translateY(-5px) !important;
transform-origin: center center;
background: transparent;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* meta: ชื่อบน / คะแนนล่าง (ตาม mockup .meta) */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-qm-meta {
@@ -3097,27 +3198,35 @@
display: none !important;
}
/* ---- Host @ (1729,21) 201×201 + face window 104×104@(49,49) + mic 121@(1796,205) (mockup) ---- */
/* ---- Host: mg-host @ มุมขวาบน (Avartar.png ทับหน้า + ช่อง 104×104) ---- */
.play-cyber-profile-frame-overlay {
display: none;
pointer-events: none;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-self {
position: absolute;
left: 1729px;
top: 21px;
right: auto;
width: 201px;
height: 201px;
height: auto;
display: block;
gap: 0;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-self-frame {
position: absolute;
inset: 0;
padding: 0;
border: none;
border-radius: 0;
box-shadow: none;
background: url('/Game/img/QUESTION/Avartar.png') no-repeat center / 201px 201px;
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-profile-wrap {
display: block;
position: relative;
width: 201px;
height: 201px;
flex-shrink: 0;
pointer-events: none;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-profile-frame {
width: 201px;
height: 201px;
min-width: 201px;
min-height: 201px;
background: transparent;
overflow: visible;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-portrait-clip {
@@ -3129,35 +3238,47 @@
height: 104px;
overflow: hidden;
border-radius: 8px;
z-index: 3;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-self-frame img {
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-profile-frame .play-cyber-portrait-img,
#play-cyber-hud.play-cyber-hud--quiz-tf-mock.play-cyber-hud--question-mission .play-cyber-profile-frame .play-cyber-portrait-img {
position: absolute;
inset: 0;
left: 0;
top: 0;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: cover;
object-position: center 40%;
transform: scale(0.92) translateY(-3px) !important;
transform-origin: center center;
border-radius: 8px;
background: transparent;
}
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-profile-frame-overlay {
display: block;
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 26%;
transform: scale(1.32);
transform-origin: center 32%;
border-radius: 0;
background: transparent;
z-index: 2;
object-fit: contain;
pointer-events: none;
}
/* mockup มุมขวาบนมีแค่กรอบ + mic — ซ่อน status/op widgets ให้สะอาด */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-self-status,
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-op-widgets {
display: none !important;
}
/* mic 121px @ (1796,205) → relative to host (1729,21) = (67,184) */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-self::after {
content: '';
/* mic 121px @ (1796,205) — เทียบ mg-mic */
#play-cyber-hud.play-cyber-hud--quiz-tf-mock .play-cyber-mic-img {
display: block;
position: absolute;
left: 67px;
top: 184px;
width: 121px;
height: 121px;
background: url('/Game/img/QUESTION/btn-mic_on.png') no-repeat center / contain;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.5));
pointer-events: none;
}
/* ====== Popup HOW TO PLAY (mng8a80o) — fix stage 1920×1080 แล้ว scale ทั้งก้อนตาม mockup 01-howto ======
@@ -3337,7 +3458,7 @@
margin: 0 auto;
object-fit: contain;
}
/* กรอบ avatar — mockup: portrait z2 ใต้ frame img z3 (137×137) */
/* กรอบ avatar 137×137 — กลาง player-frame ทึบ → หน้า z3 ทับกรอบ z2 */
#gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-frame {
position: relative;
width: 137px;
@@ -3353,8 +3474,11 @@
}
#gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-av-clip {
position: absolute;
inset: 10px;
z-index: 4;
left: 10px;
top: 6px;
right: 10px;
bottom: 16px;
z-index: 3;
overflow: hidden;
border-radius: 14%;
background: transparent;
@@ -3362,20 +3486,23 @@
#gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-frame img.gcm-av {
position: absolute;
inset: 0;
display: block;
width: 100%;
max-height: 100%;
height: 100%;
object-fit: cover;
object-position: center 34%;
transform: translateY(-11px);
object-position: center 32%;
transform: scale(0.88) translateY(-4px);
transform-origin: center center;
border-radius: 0;
image-rendering: auto;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
#gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-frame img.gcm-frame-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 3;
z-index: 2;
pointer-events: none;
object-fit: contain;
}
@@ -3684,6 +3811,7 @@
</div>
</div>
<div id="play-cyber-hud" class="play-cyber-hud is-hidden" aria-hidden="true">
<div class="play-cyber-hud-mock-stage" aria-hidden="true">
<aside class="play-cyber-scoreboard" aria-label="SCORE">
<div id="play-cyber-crown-score-head" class="play-cyber-crown-score-head is-hidden" aria-hidden="true">
<img id="play-cyber-crown-score-img" alt="" width="120" height="32" decoding="async" />
@@ -3708,11 +3836,15 @@
</div>
</div>
<div class="play-cyber-self">
<div class="play-cyber-self-frame">
<span class="play-cyber-portrait-clip">
<img id="play-cyber-portrait-img" alt="" width="72" height="72" />
</span>
<div class="qb-room-profile-wrap play-cyber-profile-wrap" aria-hidden="true">
<div class="qb-room-profile-frame play-cyber-profile-frame">
<div class="qb-room-profile-avatar-viewport play-cyber-portrait-clip">
<img id="play-cyber-portrait-img" class="qb-room-profile-avatar play-cyber-portrait-img" alt="" decoding="async" />
</div>
<img id="play-cyber-profile-frame-overlay" class="play-cyber-profile-frame-overlay" alt="" decoding="async" aria-hidden="true" />
</div>
</div>
<img id="play-cyber-mic-img" class="play-cyber-mic-img is-hidden" alt="" decoding="async" aria-hidden="true" />
<div id="play-cyber-self-status" class="play-cyber-self-status"></div>
<div id="play-cyber-op-widgets" class="play-cyber-op-widgets" aria-hidden="true">
<div id="play-cyber-voice-chip" class="play-cyber-voice-chip"></div>
@@ -3725,6 +3857,7 @@
<div class="play-cyber-corruption-scanlines"></div>
<div class="play-cyber-corruption-text">RAM ERROR · CORRUPTING · DELETE INITIATED · 01001101 01110011 01100101 01100011</div>
</div>
</div>
</div>
<!-- ภารกิจคำถาม mng8a80o ช่วง live: virtual joystick (QUESTION/btn-joystick-*.png) -->
<div id="quiz-question-mission-joystick" class="quiz-q-mission-joystick is-hidden" aria-hidden="true">
@@ -3974,7 +4107,7 @@
<script src="/app-base.js?v=2"></script>
<script src="/Game/socket.io/socket.io.js"></script>
<script src="js/version.js?v=0.0306"></script>
<script src="js/play.js?v=0.00607144418"></script>
<script src="js/play.js?v=0.00607144429"></script>
<div class="version-tag">v —</div>
</body>
</html>