minigame 2 add more design 2.9
This commit is contained in:
@@ -2,23 +2,20 @@
|
||||
'use strict';
|
||||
|
||||
function checkLoginStatus() {
|
||||
const isLoggedIn = localStorage.getItem('isLoggedIn');
|
||||
var isLoggedIn = localStorage.getItem('isLoggedIn');
|
||||
if (!isLoggedIn || isLoggedIn !== 'true') {
|
||||
window.location.href = '/Login/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!checkLoginStatus()) {
|
||||
return;
|
||||
}
|
||||
if (!checkLoginStatus()) return;
|
||||
|
||||
const BASE = '/Game';
|
||||
/** mapId ฉาก LobbyA (/Game/data/maps/mlsbbxfe.json — ไม่ใช่ mn8nx46h ที่เป็น LobbyB) */
|
||||
const LOBBY_A_MAP_ID = 'mlsbbxfe';
|
||||
/** ผู้เล่นจริง + Bot รวมกัน = 6 ที่นั่ง (เลขปุ่ม = จำนวนผู้เล่น, ที่เหลือเป็น Bot) */
|
||||
const LOBBY_TOTAL_SLOTS = 6;
|
||||
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
var SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game';
|
||||
var LOBBY_A_MAP_ID = 'mlsbbxfe';
|
||||
var LOBBY_TOTAL_SLOTS = 6;
|
||||
|
||||
var selectedSlot = 3;
|
||||
var isPrivate = false;
|
||||
@@ -55,7 +52,7 @@
|
||||
}
|
||||
|
||||
btnBack?.addEventListener('click', function () {
|
||||
window.location.href = '/Main-Menu/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
|
||||
});
|
||||
|
||||
inputPrivatePin?.addEventListener('input', function (e) {
|
||||
@@ -63,6 +60,16 @@
|
||||
updateConfirmButtonEnabled();
|
||||
});
|
||||
|
||||
function updateModeImages() {
|
||||
[tabPublic, tabPrivate].forEach(function (btn) {
|
||||
if (!btn) return;
|
||||
var img = btn.querySelector('.create-mode-btn-img');
|
||||
if (!img) return;
|
||||
var isActive = btn.classList.contains('create-mode-btn--active');
|
||||
img.src = isActive ? btn.getAttribute('data-img-h') : btn.getAttribute('data-img');
|
||||
});
|
||||
}
|
||||
|
||||
function setTab(privateMode) {
|
||||
isPrivate = privateMode;
|
||||
tabPublic?.classList.toggle('create-mode-btn--active', !privateMode);
|
||||
@@ -70,21 +77,30 @@
|
||||
tabPublic?.setAttribute('aria-selected', privateMode ? 'false' : 'true');
|
||||
tabPrivate?.setAttribute('aria-selected', privateMode ? 'true' : 'false');
|
||||
if (privateBlock) privateBlock.classList.toggle('create-view--hidden', !privateMode);
|
||||
updateModeImages();
|
||||
updateConfirmButtonEnabled();
|
||||
}
|
||||
|
||||
tabPublic?.addEventListener('click', function () { setTab(false); });
|
||||
tabPrivate?.addEventListener('click', function () { setTab(true); });
|
||||
|
||||
function selectSlot(n) {
|
||||
selectedSlot = Math.min(6, Math.max(1, parseInt(n, 10) || 3));
|
||||
var buttons = playerBtnsWrap ? playerBtnsWrap.querySelectorAll('.create-num-btn') : [];
|
||||
function updatePlayerImages() {
|
||||
if (!playerBtnsWrap) return;
|
||||
var buttons = playerBtnsWrap.querySelectorAll('.create-num-btn');
|
||||
buttons.forEach(function (btn) {
|
||||
var v = parseInt(btn.getAttribute('data-slot'), 10);
|
||||
var on = v === selectedSlot;
|
||||
btn.classList.toggle('create-num-btn--active', on);
|
||||
var img = btn.querySelector('.create-num-btn-img');
|
||||
if (img) {
|
||||
img.src = on ? btn.getAttribute('data-img-h') : btn.getAttribute('data-img');
|
||||
}
|
||||
btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
function selectSlot(n) {
|
||||
selectedSlot = Math.min(6, Math.max(1, parseInt(n, 10) || 3));
|
||||
updatePlayerImages();
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
@@ -94,12 +110,20 @@
|
||||
for (var i = 1; i <= 6; i++) {
|
||||
var b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'create-num-btn' + (i === selectedSlot ? ' create-num-btn--active' : '');
|
||||
b.className = 'create-num-btn';
|
||||
b.setAttribute('data-slot', String(i));
|
||||
b.setAttribute('data-img', 'IMAGE/player-' + i + '.png');
|
||||
b.setAttribute('data-img-h', 'IMAGE/player-' + i + '-h.png');
|
||||
b.setAttribute('aria-label', 'เลือก ' + i + ' ผู้เล่น รวม ' + LOBBY_TOTAL_SLOTS + ' ที่นั่ง (Bot ' + botCountForSlot(i) + ' ตัว)');
|
||||
b.setAttribute('title', i + ' ผู้เล่น + ' + botCountForSlot(i) + ' Bot');
|
||||
b.setAttribute('aria-pressed', i === selectedSlot ? 'true' : 'false');
|
||||
b.textContent = String(i);
|
||||
|
||||
var img = document.createElement('img');
|
||||
img.className = 'create-num-btn-img';
|
||||
img.alt = String(i);
|
||||
img.decoding = 'async';
|
||||
img.src = i === selectedSlot ? 'IMAGE/player-' + i + '-h.png' : 'IMAGE/player-' + i + '.png';
|
||||
b.appendChild(img);
|
||||
|
||||
b.addEventListener('click', function () {
|
||||
selectSlot(this.getAttribute('data-slot'));
|
||||
});
|
||||
@@ -148,7 +172,7 @@
|
||||
isPrivate: isPrivate,
|
||||
maxPlayers: maxPlayersForSlot(selectedSlot),
|
||||
};
|
||||
fetch(BASE + '/api/spaces', {
|
||||
fetch(SERVER + '/api/spaces', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
@@ -169,15 +193,12 @@
|
||||
return;
|
||||
}
|
||||
if (isPrivate) {
|
||||
try {
|
||||
localStorage.setItem('roomPinHint_' + sid, getPrivatePinDigits());
|
||||
} catch (e) { /* ignore */ }
|
||||
try { localStorage.setItem('roomPinHint_' + sid, getPrivatePinDigits()); } catch (e) {}
|
||||
}
|
||||
try {
|
||||
localStorage.setItem('lastCreatedSpaceId', sid);
|
||||
localStorage.setItem('lastCreatedSpaceName', name);
|
||||
} catch (e) { /* ignore */ }
|
||||
/* room-lobby ใช้ ?space= เป็น spaceId ของห้องที่เพิ่งสร้าง ไม่ใช่ mapId (LobbyA = mapId ใน POST เท่านั้น) */
|
||||
} catch (e) {}
|
||||
var lobbyUrl = BASE + '/room-lobby.html?space=' + encodeURIComponent(sid) + '&nick=' + encodeURIComponent(getNick());
|
||||
lobbyUrl += '&displayRoom=' + encodeURIComponent(name);
|
||||
try {
|
||||
@@ -185,9 +206,8 @@
|
||||
var prevSpaceName = localStorage.getItem('prevSpaceName');
|
||||
if (caseId) lobbyUrl += '&caseId=' + encodeURIComponent(caseId);
|
||||
if (prevSpaceName) lobbyUrl += '&prevSpaceName=' + encodeURIComponent(prevSpaceName);
|
||||
} catch (e) { /* ignore */ }
|
||||
/* replace = ลบ Create Room ออกจาก history — Back จาก Loading กลับ Main-Menu */
|
||||
window.location.replace('/Loading/?redirect=' + encodeURIComponent(lobbyUrl));
|
||||
} catch (e) {}
|
||||
window.location.replace((typeof appPath === 'function' ? appPath('/Loading/') : '/Loading/') + '?redirect=' + encodeURIComponent(lobbyUrl));
|
||||
})
|
||||
.catch(function () {
|
||||
if (statusEl) statusEl.textContent = 'เชื่อมต่อเซิร์ฟเวอร์ไม่ได้';
|
||||
|
||||
@@ -7,64 +7,78 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css?v=0.021">
|
||||
<link rel="stylesheet" href="style.css?v=1.006">
|
||||
</head>
|
||||
<body>
|
||||
<div class="create-bg" aria-hidden="true">
|
||||
<div class="create-bg-grid"></div>
|
||||
<img src="/Main-Menu/bg.png" alt="" class="create-bg-img">
|
||||
<img src="../Main-Menu/bg.png" alt="" class="create-bg-img">
|
||||
</div>
|
||||
<div class="create-char-layer" aria-hidden="true">
|
||||
<img src="/Main-Menu/char-main.png" alt="" class="create-char-img">
|
||||
<img src="../Main-Menu/char-main.png" alt="" class="create-char-img">
|
||||
</div>
|
||||
<div class="create-vignette" aria-hidden="true"></div>
|
||||
|
||||
<div class="create-ui">
|
||||
<button type="button" class="menu-btn-back" id="btn-back" aria-label="ย้อนกลับ">
|
||||
<img src="/Main-Menu/IMAGE/btn-back.png" alt="ย้อนกลับ" class="menu-btn-back-img" role="presentation" decoding="async">
|
||||
<img src="../Main-Menu/IMAGE/btn-back.png" alt="ย้อนกลับ" class="menu-btn-back-img" decoding="async">
|
||||
</button>
|
||||
|
||||
<main class="create-content">
|
||||
<h1 class="create-title">สร้างห้องเกม</h1>
|
||||
<img src="IMAGE/txt-createroom.png" alt="สร้างห้องเกม" class="create-title-img" decoding="async">
|
||||
<img src="IMAGE/line.png" alt="" class="create-title-line" aria-hidden="true" decoding="async">
|
||||
|
||||
<section class="create-panel" aria-label="ฟอร์มสร้างห้อง">
|
||||
<div class="create-field">
|
||||
<label class="create-label" for="create-room-name">ชื่อห้อง :</label>
|
||||
|
||||
<!-- ชื่อห้อง -->
|
||||
<div class="create-field create-field--inline create-field--room">
|
||||
<img src="IMAGE/txt-roomname.png" alt="ชื่อห้อง :" class="create-label-img" decoding="async">
|
||||
<div class="create-input-shell">
|
||||
<input type="text" id="create-room-name" class="create-input" maxlength="48" autocomplete="off" spellcheck="false" placeholder="ตั้งชื่อห้อง">
|
||||
<input type="text" id="create-room-name" class="create-input" maxlength="48" autocomplete="off" spellcheck="false" placeholder="ตั้งชื่อห้อง" aria-label="ชื่อห้อง">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="create-field">
|
||||
<span class="create-label" id="mode-label">โหมด :</span>
|
||||
<div class="create-mode-row" role="tablist" aria-labelledby="mode-label">
|
||||
<button type="button" class="create-mode-btn create-mode-btn--active" id="tab-public" role="tab" aria-selected="true">ห้องสาธารณะ</button>
|
||||
<button type="button" class="create-mode-btn" id="tab-private" role="tab" aria-selected="false">ห้องส่วนตัว</button>
|
||||
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
|
||||
|
||||
<!-- โหมด -->
|
||||
<div class="create-field create-field--inline create-field--mode">
|
||||
<img src="IMAGE/txt-mode.png" alt="โหมด :" class="create-label-img" decoding="async">
|
||||
<div class="create-mode-row" role="tablist" aria-label="โหมดห้อง">
|
||||
<button type="button" class="create-mode-btn create-mode-btn--active" id="tab-public" role="tab" aria-selected="true" data-img="IMAGE/btn-publish.png" data-img-h="IMAGE/btn-publish-h.png">
|
||||
<img src="IMAGE/btn-publish-h.png" alt="ห้องสาธารณะ" class="create-mode-btn-img" decoding="async">
|
||||
</button>
|
||||
<button type="button" class="create-mode-btn" id="tab-private" role="tab" aria-selected="false" data-img="IMAGE/btn-private.png" data-img-h="IMAGE/btn-private-h.png">
|
||||
<img src="IMAGE/btn-private.png" alt="ห้องส่วนตัว" class="create-mode-btn-img" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PASSWORD (ซ่อน default) -->
|
||||
<div id="create-private-block" class="create-private-block create-view--hidden" role="tabpanel">
|
||||
<div class="create-pass-row">
|
||||
<div>
|
||||
<span class="create-label create-label--inline">PASSWORD :</span>
|
||||
<span class="create-pass-sub">(รหัสผ่าน 4 ตัว)</span>
|
||||
</div>
|
||||
<div class="create-input-shell create-input-shell--pin">
|
||||
<input type="text" id="create-private-pin" class="create-input create-input--pin" placeholder="0000" maxlength="4" inputmode="numeric" autocomplete="off" aria-label="รหัสผ่าน 4 หลัก">
|
||||
</div>
|
||||
<img src="IMAGE/txt-password.png" alt="PASSWORD : (รหัสผ่าน 4 ตัว)" class="create-label-img create-label-img--password" decoding="async">
|
||||
<div class="create-input-shell create-input-shell--pin">
|
||||
<input type="text" id="create-private-pin" class="create-input create-input--pin" placeholder="0000" maxlength="4" inputmode="numeric" autocomplete="off" aria-label="รหัสผ่าน 4 หลัก">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="create-field create-field--players">
|
||||
<span class="create-label" id="players-label">จำนวนผู้เล่น :</span>
|
||||
<div class="create-num-grid" id="create-player-btns" role="group" aria-labelledby="players-label"></div>
|
||||
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
|
||||
|
||||
<!-- จำนวนผู้เล่น -->
|
||||
<div class="create-field create-field--inline create-field--players">
|
||||
<img src="IMAGE/txt-players.png" alt="จำนวนผู้เล่น :" class="create-label-img" decoding="async">
|
||||
<div class="create-num-grid" id="create-player-btns" role="group" aria-label="จำนวนผู้เล่น"></div>
|
||||
</div>
|
||||
|
||||
<div class="create-summary" id="create-summary" aria-live="polite">สรุปจำนวน : 3 ผู้เล่น + 3 Bot</div>
|
||||
<img src="IMAGE/line.png" alt="" class="create-line" aria-hidden="true" decoding="async">
|
||||
|
||||
<!-- สรุป -->
|
||||
<div class="create-summary-wrap">
|
||||
<div class="create-summary" id="create-summary" aria-live="polite">สรุปจำนวน : 3 ผู้เล่น + 3 Bot</div>
|
||||
</div>
|
||||
|
||||
<!-- ยืนยัน -->
|
||||
<button type="button" class="create-confirm" id="btn-confirm" aria-label="ยืนยันสร้างห้อง">
|
||||
<span class="create-confirm-glow"></span>
|
||||
<span class="create-confirm-text">ยืนยัน</span>
|
||||
<img src="IMAGE/btn-confirm.png" alt="ยืนยัน" class="create-confirm-img" decoding="async">
|
||||
</button>
|
||||
|
||||
<p class="create-status" id="create-status" role="status"></p>
|
||||
@@ -72,6 +86,41 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="create-room.js?v=0.028"></script>
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="create-room.js?v=1.004"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var content = document.querySelector('.create-content');
|
||||
if(!content) return;
|
||||
var tid = 0;
|
||||
function autoFit(){
|
||||
clearTimeout(tid);
|
||||
tid = setTimeout(function(){
|
||||
var isLand = window.matchMedia('(orientation:landscape) and (max-width:1200px)').matches
|
||||
|| window.matchMedia('(orientation:landscape) and (max-height:620px)').matches;
|
||||
if(!isLand){ content.style.zoom = ''; return; }
|
||||
content.style.zoom = '1';
|
||||
requestAnimationFrame(function(){
|
||||
var vpH = window.innerHeight;
|
||||
var rect = content.getBoundingClientRect();
|
||||
var need = rect.top + rect.height;
|
||||
if(need > vpH){
|
||||
var s = Math.max(0.4, (vpH * 0.97) / need);
|
||||
content.style.zoom = String(Math.floor(s * 1000) / 1000);
|
||||
} else {
|
||||
content.style.zoom = '1';
|
||||
}
|
||||
});
|
||||
}, 30);
|
||||
}
|
||||
autoFit();
|
||||
window.addEventListener('resize', autoFit);
|
||||
window.addEventListener('orientationchange', function(){ setTimeout(autoFit, 300); });
|
||||
var panel = document.querySelector('.create-panel');
|
||||
if(panel){
|
||||
new MutationObserver(function(){ setTimeout(autoFit, 80); }).observe(panel, {childList:true, subtree:true, attributes:true, attributeFilter:['class']});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+794
-225
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1177,16 +1177,22 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* ค่าลบจาก screen Y ใน worldToScreen — ให้บล็อก/กริดเลื่อนตามฉาก BG ที่ stackTowerScrollBgPx เลื่อน (ซิงก์กับ drawStackTowerScrollBgFullCanvas)
|
||||
* ค่าลบจาก screen Y ใน worldToScreen — ซิงก์กับ drawStackTowerScrollBgFullCanvas (auto + boost ตามความสูงชั้น + post-50)
|
||||
*/
|
||||
function getStackTowerWorldLayerScrollScreenOffsetYPlay(zPan) {
|
||||
if (!isStackTowerMissionUiMapPlay() || !stackTowerScrollBgDrawActive() || !stackTowerScrollBgImagesReady()) return 0;
|
||||
if (stackTowerScrollBgOff) return 0;
|
||||
if (!isStackTowerMissionUiMapPlay() || !stackMini) return 0;
|
||||
const zPn = Number(zPan) > 0 ? zPan : zoom;
|
||||
const zRef = stackTowerScrollBgZoomRefPlay > 0 ? stackTowerScrollBgZoomRefPlay : zPn;
|
||||
const bgZm = Math.max(0.28, Math.min(4.25, zPn / zRef));
|
||||
const raw = stackTowerScrollBgPx / bgZm;
|
||||
return stackTowerScrollBgFlowDown ? -raw : raw;
|
||||
const chPx = Math.max(1, Math.round(canvas && canvas.height ? canvas.height : 720));
|
||||
const boostScreen = getStackTowerBgScrollHeightBoostPx() * zPn;
|
||||
const post50Screen = getStackTowerPost50BgScrollExtraPxPlay(chPx);
|
||||
let autoSigned = 0;
|
||||
if (stackTowerScrollBgDrawActive() && stackTowerScrollBgImagesReady() && !stackTowerScrollBgOff) {
|
||||
autoSigned = stackTowerScrollBgFlowDown ? -stackTowerScrollBgPx : stackTowerScrollBgPx;
|
||||
}
|
||||
const totalScreen = autoSigned + boostScreen + post50Screen;
|
||||
return totalScreen / bgZm;
|
||||
}
|
||||
|
||||
/** โหลดจาก mapData.gridImageLibrary — ดัชนีตรงกับ gridImageCells[y][x] */
|
||||
@@ -4498,7 +4504,7 @@
|
||||
const L = m.layers[li];
|
||||
const lw = (L.w != null ? L.w : m.initialWidthTiles) * tileSize * getStackTowerBlockWorldScalePlay();
|
||||
const cxW = L.cx * tileSize;
|
||||
const yb = floorY - (li + 1 - hidPop) * layerWorldH;
|
||||
const yb = floorY - (li + 1) * layerWorldH;
|
||||
const xl = cxW - lw / 2;
|
||||
const [sx, sy] = worldToScreen(xl, yb);
|
||||
const drawW = lw * zoom;
|
||||
@@ -4690,8 +4696,7 @@
|
||||
const u = Math.max(0, Math.min(1, (nt - t0) / dur));
|
||||
const alpha = 1 - u * u;
|
||||
const worldCx = m.topCenterX * tileSize;
|
||||
const hidHm = getStackTowerVisualHiddenLayerCountPlay();
|
||||
const stackTopY = floorY - (nLay - hidHm) * layerWorldH;
|
||||
const stackTopY = floorY - nLay * layerWorldH;
|
||||
const worldCy = stackTopY - tileSize * 1.55 - u * tileSize * 0.5;
|
||||
const [sx, sy] = worldToScreen(worldCx, worldCy);
|
||||
const im = ensureStackTowerHeartMinusImgPlay();
|
||||
@@ -8658,7 +8663,10 @@
|
||||
return Math.min(lh * 20, tiers * lh * 0.98);
|
||||
}
|
||||
|
||||
/** progress ≥50% + ชั้นพอ: ไม่วาดครึ่งล่างของกอง — ซ้อนเฉพาะครึ่งบนจากพื้น (เฉพาะภาพ ไม่แก้ logic เกม) */
|
||||
/**
|
||||
* progress ≥50% + ชั้นพอ: ไม่วาดชั้นล่าง (ข้าม draw) — ตำแหน่ง Y ยังใช้ชั้นจริง (floorY-(i+1)*h)
|
||||
* เพื่อไม่ให้กองลอยหลุดจากฐานแมปเมื่อเทียบกริด
|
||||
*/
|
||||
function getStackTowerVisualHiddenLayerCountPlay() {
|
||||
if (!isStackTowerMissionHudActivePlay() || !stackMini) return 0;
|
||||
const p = Number(stackMini.progressPct) || 0;
|
||||
@@ -8668,11 +8676,15 @@
|
||||
return Math.floor(n / 2);
|
||||
}
|
||||
|
||||
/** world px ตามความสูงหอ (mnn93hpi) — ใช้เลื่อนกล้องให้บล็อก/ฉากไปกับมุมมอง */
|
||||
/**
|
||||
* world px ตามความสูงหอจริง (mnn93hpi) — คูณ z แล้วได้พิกเซลจอสำหรับเลื่อนแถบ BG
|
||||
* ใช้ n × layerWorldH (ความสูงชั้นตามบล็อก) คล้มด้วย maxPx จากแมป
|
||||
*/
|
||||
function getStackTowerBgScrollHeightBoostPx() {
|
||||
if (!isStackTowerMissionUiMapPlay() || !stackMini) return 0;
|
||||
const cfg = getStackTowerCameraFollowPlayConfig();
|
||||
if (!cfg.enabled || cfg.pxPerLayer <= 0) return 0;
|
||||
const lh = Math.max(10, Number(stackMini.layerWorldH) || Math.max(14, (tileSize || 32) * 0.31));
|
||||
const n = stackMini.layers ? stackMini.layers.length : 0;
|
||||
let layerCount = n;
|
||||
if (stackFall) {
|
||||
@@ -8680,10 +8692,10 @@
|
||||
const u = Math.min(1, Math.max(0, (performance.now() - stackFall.t0) / dur));
|
||||
layerCount = n + u * 0.9;
|
||||
}
|
||||
return Math.min(cfg.maxPx, layerCount * cfg.pxPerLayer);
|
||||
return Math.min(cfg.maxPx, layerCount * lh);
|
||||
}
|
||||
|
||||
/** กล้อง stack: จุดกลางจาก land / release ของแมปเท่านั้น (Tower ไม่เลื่อน cy ตามหอ / post-50 / ตามบล็อก) */
|
||||
/** กล้อง stack: จุดกลางจาก land / release ของแมป (Tower ไม่เลื่อน cy ตามกอง) */
|
||||
function getStackCameraCentersPx() {
|
||||
const w = mapData.width || 20, h = mapData.height || 15;
|
||||
const ts = tileSize;
|
||||
@@ -9297,14 +9309,13 @@
|
||||
const swingLiftDraw = getStackTowerSwingLiftWorldPx(m.layers.length, layerWorldH);
|
||||
const swingDrawY = m.swingWorldY - swingLiftDraw;
|
||||
const hidLayers = towerHudLive ? getStackTowerVisualHiddenLayerCountPlay() : 0;
|
||||
const visualLiftWorld = hidLayers * layerWorldH;
|
||||
|
||||
const nLay = m.layers.length;
|
||||
for (let i = hidLayers; i < nLay; i++) {
|
||||
const L = m.layers[i];
|
||||
const lw = (L.w != null ? L.w : m.initialWidthTiles) * tileSize * wMul;
|
||||
const cxW = L.cx * tileSize;
|
||||
const yb = floorY - (i + 1 - hidLayers) * layerWorldH;
|
||||
const yb = floorY - (i + 1) * layerWorldH;
|
||||
const xl = cxW - lw / 2;
|
||||
const [sx, sy] = worldToScreen(xl, yb);
|
||||
const drawW = lw * zoom;
|
||||
@@ -9331,7 +9342,7 @@
|
||||
const Ltop = m.layers[ti];
|
||||
const lwT = (Ltop.w != null ? Ltop.w : m.initialWidthTiles) * tileSize * wMul;
|
||||
const cxWT = Ltop.cx * tileSize;
|
||||
const ybT = floorY - (ti + 1 - hidLayers) * layerWorldH;
|
||||
const ybT = floorY - (ti + 1) * layerWorldH;
|
||||
const xlT = cxWT - lwT / 2;
|
||||
const [sxT, syT] = worldToScreen(xlT, ybT);
|
||||
const drawWT = lwT * zoom;
|
||||
@@ -9356,8 +9367,7 @@
|
||||
const xSlide = rotSign * ue * tileSize * 0.62;
|
||||
const cxW = s.xLeft0 + s.twWorld / 2 + xSlide;
|
||||
const cyW = s.yTop0 + s.stripH * 0.5 + yFall;
|
||||
const cyWDraw = isStackTowerMissionUiMapPlay() ? cyW + visualLiftWorld : cyW;
|
||||
const [cxs, cys] = worldToScreen(cxW, cyWDraw);
|
||||
const [cxs, cys] = worldToScreen(cxW, cyW);
|
||||
const drawW = s.twWorld * zoom;
|
||||
const drawH = s.stripH * zoom;
|
||||
const hue = (m.layers.length * 47) % 360;
|
||||
@@ -9371,9 +9381,8 @@
|
||||
} else {
|
||||
const twWorld = m.widthTiles * tileSize * wMul;
|
||||
const drawStripPx = stripH * zoom;
|
||||
const swingDrawYDraw = isStackTowerMissionUiMapPlay() ? swingDrawY + visualLiftWorld : swingDrawY;
|
||||
const cableTopY = isStackTowerMissionUiMapPlay()
|
||||
? (swingDrawYDraw - tileSize * 6)
|
||||
? (swingDrawY - tileSize * 6)
|
||||
: Math.max(0, swingDrawY - tileSize * 6);
|
||||
const craneX = m.craneWorldX != null ? m.craneWorldX : m.topCenterX * tileSize;
|
||||
let blockLeftWorld;
|
||||
@@ -9385,12 +9394,11 @@
|
||||
const u = stackEaseDropPhys(t, stackFall.sloppyN);
|
||||
blockLeftWorld = stackFall.xLeft0 + (stackFall.xLeft1 - stackFall.xLeft0) * u;
|
||||
blockTopWorld = stackFall.y0 + (stackFall.y1 - stackFall.y0) * u;
|
||||
if (isStackTowerMissionUiMapPlay()) blockTopWorld += visualLiftWorld;
|
||||
fallTiltRad = stackFall.tiltMax * Math.sin(Math.PI * u);
|
||||
} else {
|
||||
const swingXW = (m.topCenterX + m.swingAmp * Math.sin(m.phase)) * tileSize;
|
||||
blockLeftWorld = swingXW - twWorld / 2;
|
||||
blockTopWorld = isStackTowerMissionUiMapPlay() ? swingDrawYDraw : swingDrawY;
|
||||
blockTopWorld = swingDrawY;
|
||||
}
|
||||
const cxMid = blockLeftWorld + twWorld / 2;
|
||||
const [tcx, tcy] = worldToScreen(craneX, cableTopY);
|
||||
@@ -9398,7 +9406,7 @@
|
||||
let slingBx = bcx;
|
||||
let slingBy = bcy;
|
||||
if (stackFall && stackFall.releaseAttachWorldX != null) {
|
||||
const attY = stackFall.releaseAttachWorldY + (isStackTowerMissionUiMapPlay() ? visualLiftWorld : 0);
|
||||
const attY = stackFall.releaseAttachWorldY;
|
||||
const p = worldToScreen(stackFall.releaseAttachWorldX, attY);
|
||||
slingBx = p[0];
|
||||
slingBy = p[1];
|
||||
@@ -16381,7 +16389,8 @@
|
||||
}
|
||||
if (isStackTowerMissionUiMapPlay() && stackMini && canvas && zDraw > 0) {
|
||||
const nLay0 = stackMini.layers ? stackMini.layers.length : 0;
|
||||
if (nLay0 === 0) {
|
||||
const hasMotion = !!(stackFall || stackMini.settling);
|
||||
if (nLay0 === 0 && !hasMotion) {
|
||||
const rawFrac = mapData && mapData.stackTowerFloorScreenFrac;
|
||||
const frac = Number(rawFrac);
|
||||
/** ค่าน้อย = พื้นขึ้นบนจอ (จุดเริ่มตรงฐานศิลป์) — แมป override ได้ที่ stackTowerFloorScreenFrac */
|
||||
|
||||
@@ -3202,7 +3202,7 @@
|
||||
</div>
|
||||
<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.0376"></script>
|
||||
<script src="js/play.js?v=0.0381"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="loading-bar-fill" id="loading-bar-fill"></div>
|
||||
<span class="loading-pct" id="loading-pct">0%</span>
|
||||
</div>
|
||||
<p class="loading-status">กำลังเข้าสู่การสืบสวน...</p>
|
||||
<img src="IMAGE/loading-txt.png" alt="กำลังเข้าสู่การสืบสวน..." class="loading-status-img" decoding="async">
|
||||
</div>
|
||||
<div class="loading-carousel">
|
||||
<button type="button" class="loading-arrow loading-arrow--prev" id="btn-prev" aria-label="สไลด์ก่อนหน้า">
|
||||
@@ -40,7 +40,9 @@
|
||||
</div>
|
||||
<p class="loading-page-ind" id="slide-page">1 / 7</p>
|
||||
<button type="button" class="loading-skip" id="btn-skip">ข้าม</button>
|
||||
<button type="button" class="loading-debug-freeze" id="btn-debug-freeze">Freeze: OFF</button>
|
||||
</main>
|
||||
<script src="loading.js?v=0.02"></script>
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="loading.js?v=0.03"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+76
-11
@@ -3,40 +3,58 @@
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var redirect = params.get('redirect');
|
||||
var debugMode = params.get('debug') === '1' || localStorage.getItem('debugLoadingFreeze') === '1';
|
||||
if (debugMode) document.body.classList.add('loading-debug-mode');
|
||||
function ap(p) {
|
||||
return typeof appPath === 'function' ? appPath(p) : p;
|
||||
}
|
||||
if (!redirect) {
|
||||
window.location.replace('/Main-Menu/');
|
||||
return;
|
||||
redirect = ap('/Main-Menu/');
|
||||
}
|
||||
|
||||
var safe = false;
|
||||
try {
|
||||
var u = new URL(redirect, window.location.origin);
|
||||
if (u.origin === window.location.origin && /^\/Game\/room-lobby\.html$/i.test(u.pathname)) {
|
||||
var suffix = '/Game/room-lobby.html';
|
||||
if (u.origin === window.location.origin && (u.pathname === suffix || u.pathname.endsWith(suffix))) {
|
||||
safe = true;
|
||||
}
|
||||
} catch (e) {
|
||||
safe = false;
|
||||
}
|
||||
if (!safe) {
|
||||
window.location.replace('/Game/lobby.html');
|
||||
return;
|
||||
redirect = ap('/Game/lobby.html');
|
||||
}
|
||||
|
||||
var totalSlides = 7;
|
||||
var slides = [
|
||||
't-01.png',
|
||||
't-02.png',
|
||||
't-03.png',
|
||||
't-04.png',
|
||||
't-05.png',
|
||||
't-06.png',
|
||||
't-07.png'
|
||||
];
|
||||
var totalSlides = slides.length;
|
||||
var idx = 0;
|
||||
var slideImg = document.getElementById('slide-img');
|
||||
var pageEl = document.getElementById('slide-page');
|
||||
var fill = document.getElementById('loading-bar-fill');
|
||||
var pctEl = document.getElementById('loading-pct');
|
||||
var barWrap = document.getElementById('loading-bar-wrap');
|
||||
var debugFreezeBtn = document.getElementById('btn-debug-freeze');
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : String(n);
|
||||
}
|
||||
|
||||
function normalizeIndex(i) {
|
||||
return ((i % totalSlides) + totalSlides) % totalSlides;
|
||||
}
|
||||
|
||||
function showSlide(i) {
|
||||
idx = (i + totalSlides * 10) % totalSlides;
|
||||
if (slideImg) slideImg.src = 'IMAGE/t-' + pad2(idx + 1) + '.png?v=1';
|
||||
idx = normalizeIndex(i);
|
||||
if (slideImg) slideImg.src = 'IMAGE/' + slides[idx] + '?v=1';
|
||||
if (pageEl) pageEl.textContent = (idx + 1) + ' / ' + totalSlides;
|
||||
}
|
||||
|
||||
@@ -46,10 +64,25 @@
|
||||
document.getElementById('btn-next')?.addEventListener('click', function () {
|
||||
showSlide(idx + 1);
|
||||
});
|
||||
showSlide(0);
|
||||
|
||||
var durationMs = 6500;
|
||||
var freezeLoading = params.get('freeze') === '1' || localStorage.getItem('debugLoadingFreeze') === '1';
|
||||
var freezePctRaw = parseInt(
|
||||
params.get('freezePct') || localStorage.getItem('debugLoadingFreezePct') || '0',
|
||||
10
|
||||
);
|
||||
var freezePct = Math.max(0, Math.min(100, Number.isNaN(freezePctRaw) ? 0 : freezePctRaw));
|
||||
var customDurationRaw = parseInt(
|
||||
params.get('durationMs') || localStorage.getItem('debugLoadingDurationMs') || '',
|
||||
10
|
||||
);
|
||||
if (!Number.isNaN(customDurationRaw) && customDurationRaw > 0) {
|
||||
durationMs = customDurationRaw;
|
||||
}
|
||||
var startTs = null;
|
||||
var navigated = false;
|
||||
var currentPct = freezePct;
|
||||
|
||||
function goLobby() {
|
||||
if (navigated) return;
|
||||
@@ -60,14 +93,46 @@
|
||||
|
||||
document.getElementById('btn-skip')?.addEventListener('click', goLobby);
|
||||
|
||||
function setDebugFreezeLabel() {
|
||||
if (!debugFreezeBtn) return;
|
||||
var on = localStorage.getItem('debugLoadingFreeze') === '1';
|
||||
debugFreezeBtn.textContent = on ? 'Freeze: ON' : 'Freeze: OFF';
|
||||
}
|
||||
|
||||
if (debugFreezeBtn) {
|
||||
debugFreezeBtn.addEventListener('click', function () {
|
||||
var on = localStorage.getItem('debugLoadingFreeze') === '1';
|
||||
if (on) {
|
||||
localStorage.removeItem('debugLoadingFreeze');
|
||||
localStorage.removeItem('debugLoadingFreezePct');
|
||||
} else {
|
||||
localStorage.setItem('debugLoadingFreeze', '1');
|
||||
localStorage.setItem('debugLoadingFreezePct', String(currentPct));
|
||||
}
|
||||
setDebugFreezeLabel();
|
||||
window.location.reload();
|
||||
});
|
||||
setDebugFreezeLabel();
|
||||
}
|
||||
|
||||
function paintProgress(p) {
|
||||
currentPct = p;
|
||||
if (fill) fill.style.setProperty('--loading-fill-scale', String(p / 100));
|
||||
if (pctEl) pctEl.textContent = p + '%';
|
||||
if (barWrap) barWrap.setAttribute('aria-valuenow', String(p));
|
||||
}
|
||||
|
||||
if (freezeLoading) {
|
||||
paintProgress(freezePct);
|
||||
return;
|
||||
}
|
||||
|
||||
function tick(ts) {
|
||||
if (navigated) return;
|
||||
if (startTs == null) startTs = ts;
|
||||
var t = Math.min(1, (ts - startTs) / durationMs);
|
||||
var p = Math.min(100, Math.floor(t * 100));
|
||||
if (fill) fill.style.width = p + '%';
|
||||
if (pctEl) pctEl.textContent = p + '%';
|
||||
if (barWrap) barWrap.setAttribute('aria-valuenow', String(p));
|
||||
paintProgress(p);
|
||||
if (t >= 1) {
|
||||
goLobby();
|
||||
return;
|
||||
|
||||
+73
-45
@@ -36,50 +36,53 @@ body.loading-page {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(14px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
|
||||
gap: clamp(0.75rem, 2.5vh, 1.25rem);
|
||||
padding: max(10px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(14px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
|
||||
gap: clamp(0.4rem, 1.35vh, 0.85rem);
|
||||
}
|
||||
|
||||
.loading-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
gap: 0rem;
|
||||
width: 100%;
|
||||
max-width: min(92vw, 420px);
|
||||
max-width: min(95vw, 680px);
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
width: clamp(56px, 14vw, 88px);
|
||||
height: auto;
|
||||
width: auto;
|
||||
height: clamp(52px, 22vh, 223px);
|
||||
max-height: 22vh;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 16px rgba(34, 211, 238, 0.65));
|
||||
filter: drop-shadow(0 0 14px rgba(34, 211, 238, 0.55));
|
||||
}
|
||||
|
||||
.loading-bar-wrap {
|
||||
--loading-bar-height: min(16.2vh, 141px);
|
||||
--loading-bar-fill-inset-x: 5.7%;
|
||||
--loading-bar-fill-inset-y: 25.5%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: clamp(36px, 8vw, 44px);
|
||||
border-radius: 6px;
|
||||
border: 2px solid transparent;
|
||||
background:
|
||||
linear-gradient(#1a1030, #1a1030) padding-box,
|
||||
linear-gradient(90deg, #22d3ee, #e879f9, #22d3ee) border-box;
|
||||
box-shadow:
|
||||
0 0 18px rgba(236, 72, 153, 0.45),
|
||||
0 0 24px rgba(34, 211, 238, 0.25),
|
||||
inset 0 0 20px rgba(0, 0, 0, 0.35);
|
||||
width: calc(var(--loading-bar-height) * (658 / 141));
|
||||
max-width: 95vw;
|
||||
height: var(--loading-bar-height);
|
||||
background: url(IMAGE/loading-bg.png) center / 100% 100% no-repeat;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
margin-top: -3rem;
|
||||
}
|
||||
|
||||
.loading-bar-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, rgba(168, 85, 247, 0.85), rgba(236, 72, 153, 0.9));
|
||||
border-radius: 4px 0 0 4px;
|
||||
left: var(--loading-bar-fill-inset-x);
|
||||
top: var(--loading-bar-fill-inset-y);
|
||||
bottom: var(--loading-bar-fill-inset-y);
|
||||
width: calc(100% - (var(--loading-bar-fill-inset-x) * 2));
|
||||
background: url(IMAGE/loading.png) left center / 100% 100% no-repeat;
|
||||
border-radius: 4px;
|
||||
transform-origin: left center;
|
||||
transform: scaleX(var(--loading-fill-scale, 0));
|
||||
}
|
||||
|
||||
.loading-pct {
|
||||
@@ -89,20 +92,20 @@ body.loading-page {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
font-size: clamp(0.95rem, 2.8vw, 1.15rem);
|
||||
font-size: clamp(1.05rem, 3vw, 2.55rem);
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 12px rgba(255, 255, 255, 0.5);
|
||||
text-shadow: 0 0 10px rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.loading-status {
|
||||
margin: 0;
|
||||
font-size: clamp(0.88rem, 2.2vw, 1.05rem);
|
||||
font-weight: 600;
|
||||
color: #67e8f9;
|
||||
text-align: center;
|
||||
text-shadow: 0 0 14px rgba(34, 211, 238, 0.55);
|
||||
letter-spacing: 0.02em;
|
||||
.loading-status-img {
|
||||
display: block;
|
||||
width: min(50vw, 380px);
|
||||
max-width: 92%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 10px rgba(34, 211, 238, 0.42));
|
||||
margin-top: -1.2rem;
|
||||
}
|
||||
|
||||
.loading-carousel {
|
||||
@@ -110,9 +113,9 @@ body.loading-page {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: clamp(4px, 1.2vw, 12px);
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
max-width: min(96vw, 920px);
|
||||
max-width: min(96vw, 990px);
|
||||
}
|
||||
|
||||
.loading-arrow {
|
||||
@@ -123,6 +126,7 @@ body.loading-page {
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: transform 0.15s, filter 0.2s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-arrow:hover,
|
||||
@@ -146,7 +150,7 @@ body.loading-page {
|
||||
.loading-slide-frame {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-height: min(52vh, 520px);
|
||||
max-height: min(54vh, 551px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -154,24 +158,25 @@ body.loading-page {
|
||||
|
||||
.loading-slide-img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: min(52vh, 520px);
|
||||
max-width: min(96vw, 921px);
|
||||
max-height: min(54vh, 551px);
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 8px 32px rgba(0, 0, 0, 0.45));
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.loading-page-ind {
|
||||
margin: 0;
|
||||
font-size: clamp(0.8rem, 2vw, 0.95rem);
|
||||
margin: -2.35rem 0 0;
|
||||
font-size: clamp(0.78rem, 1.8vw, 1.5rem);
|
||||
font-weight: 600;
|
||||
color: rgba(248, 250, 252, 0.9);
|
||||
text-shadow: 0 0 10px rgba(34, 211, 238, 0.35);
|
||||
color: #7feeff;
|
||||
text-shadow: 0 0 10px rgba(102, 240, 255, 0.52);
|
||||
}
|
||||
|
||||
.loading-skip {
|
||||
margin-top: 0.25rem;
|
||||
display: none;
|
||||
margin-top: 0.15rem;
|
||||
padding: 0.4rem 1rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.45);
|
||||
border-radius: 8px;
|
||||
@@ -186,3 +191,26 @@ body.loading-page {
|
||||
border-color: #22d3ee;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.loading-debug-freeze {
|
||||
/* display: block; */
|
||||
display: none;
|
||||
position: fixed;
|
||||
right: max(10px, env(safe-area-inset-right));
|
||||
bottom: max(10px, env(safe-area-inset-bottom));
|
||||
z-index: 999;
|
||||
pointer-events: auto !important;
|
||||
padding: 0.35rem 0.65rem;
|
||||
border: 1px solid rgba(34, 211, 238, 0.55);
|
||||
border-radius: 8px;
|
||||
background: rgba(12, 24, 44, 0.72);
|
||||
color: #a5f3fc;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-debug-freeze:hover {
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
|
||||
+102
-30
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css?v=0.0156">
|
||||
<link rel="stylesheet" href="style.css?v=0.0170">
|
||||
</head>
|
||||
<body class="lobby-page">
|
||||
<div class="lobby-bg" role="img" aria-label="พื้นหลัง">
|
||||
@@ -21,7 +21,7 @@
|
||||
<img src="IMAGE/profile-bg.png" alt="" class="lobby-profile-bg" role="presentation" decoding="async">
|
||||
<div class="lobby-profile-inner">
|
||||
<div class="lobby-avatar-wrap">
|
||||
<img src="/Main-Menu/char-main.png" alt="" class="lobby-avatar-mask" id="lobby-profile-avatar" width="64" height="64" decoding="async">
|
||||
<img src="../Main-Menu/char-main.png" alt="" class="lobby-avatar-mask" id="lobby-profile-avatar" width="64" height="64" decoding="async">
|
||||
</div>
|
||||
<div class="lobby-profile-info">
|
||||
<p class="lobby-profile-name" id="lobby-profile-name">MONE</p>
|
||||
@@ -61,24 +61,94 @@
|
||||
|
||||
<aside class="lobby-leaderboard" aria-label="กระดานผู้นำ">
|
||||
<img src="IMAGE/leaderboard-bg.png" alt="" class="lobby-leaderboard-bg" role="presentation" decoding="async">
|
||||
<div class="lobby-leaderboard-inner">
|
||||
<ul class="lobby-leaderboard-list">
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-1.png" alt="" role="presentation" decoding="async">
|
||||
<span>Mixie Kim</span>
|
||||
<span>9999</span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-2.png" alt="" role="presentation" decoding="async">
|
||||
<span>Golden L.</span>
|
||||
<span>9951</span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-3.png" alt="" role="presentation" decoding="async">
|
||||
<span>Lilly 991</span>
|
||||
<span>9552</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="lobby-leaderboard-mask">
|
||||
<div class="lobby-leaderboard-inner">
|
||||
<div class="lobby-leaderboard-scroll">
|
||||
<ul class="lobby-leaderboard-list">
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-1.png" alt="" class="lobby-rank-icon" role="presentation" decoding="async">
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Mixie Kim</span>
|
||||
<span class="lobby-rank-case">(คดี : มหาเศรษฐีนามปลอม)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9999</span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-2.png" alt="" class="lobby-rank-icon" role="presentation" decoding="async">
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Golden L.</span>
|
||||
<span class="lobby-rank-case">(คดี : โจรกรรมไซเบอร์)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9951</span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="IMAGE/leaderboard-3.png" alt="" class="lobby-rank-icon" role="presentation" decoding="async">
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Lilly 991</span>
|
||||
<span class="lobby-rank-case">(คดี : ปั่นแอพพลวงเกม)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9552</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">4</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Nova Byte</span>
|
||||
<span class="lobby-rank-case">(คดี : ฟิชชิงองค์กร)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9410</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">5</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Aster R.</span>
|
||||
<span class="lobby-rank-case">(คดี : แก๊งค์หลอกลงทุน)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9280</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">6</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Hexa One</span>
|
||||
<span class="lobby-rank-case">(คดี : เจาะระบบธนาคาร)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9145</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">7</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Mint C.</span>
|
||||
<span class="lobby-rank-case">(คดี : ปลอมเพจขายสินค้า)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">9032</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">8</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Q-Logic</span>
|
||||
<span class="lobby-rank-case">(คดี : หลอกโอนผ่านแชท)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">8918</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">9</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Rin 47</span>
|
||||
<span class="lobby-rank-case">(คดี : ลักข้อมูลส่วนบุคคล)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">8804</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="lobby-rank-num">10</span>
|
||||
<div class="lobby-rank-info">
|
||||
<span class="lobby-rank-name">Skyline X</span>
|
||||
<span class="lobby-rank-case">(คดี : ฟอกเงินคริปโต)</span>
|
||||
</div>
|
||||
<span class="lobby-rank-score">8690</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="lobby-leaderboard-scroll-thumb" id="lobby-leaderboard-scroll-thumb" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -89,10 +159,10 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="lobby-footer-center">
|
||||
<button type="button" class="lobby-btn-start" id="btn-start-mission" aria-label="ไปเมนูหลัก" data-href="/Main-Menu/">
|
||||
<button type="button" class="lobby-btn-start" id="btn-start-mission" aria-label="ไปเมนูหลัก" data-href="../Main-Menu/">
|
||||
<img src="IMAGE/btn-start-mission.png" alt="" class="lobby-btn-start-img" role="presentation" decoding="async">
|
||||
</button>
|
||||
<button type="button" class="lobby-btn-quiz" id="btn-quiz-battle" aria-label="ห้องเกมตอบคำถาม" data-href="/Quiz-Battle/">
|
||||
<button type="button" class="lobby-btn-quiz" id="btn-quiz-battle" aria-label="ห้องเกมตอบคำถาม" data-href="../Quiz-Battle/">
|
||||
<img src="IMAGE/btn-quix%20battle.png" alt="" class="lobby-btn-quiz-img" role="presentation" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
@@ -104,12 +174,13 @@
|
||||
</footer>
|
||||
|
||||
<div id="lobby-guide-dim" class="lobby-guide-dim hidden" aria-hidden="true"></div>
|
||||
<div id="lobby-guide-focus" class="lobby-guide-focus hidden" aria-hidden="true"></div>
|
||||
<div class="lobby-guide-bar hidden" id="lobby-guide-bar" role="dialog" aria-modal="true" aria-labelledby="guide-img-label">
|
||||
<span id="guide-img-label" class="sr-only">คำแนะนำทีละขั้น</span>
|
||||
<div class="lobby-guide-frame">
|
||||
<img src="/Main-Lobby/IMAGE/guide/guide-01.png" alt="คำแนะนำ" class="lobby-guide-img" id="guide-img" decoding="async">
|
||||
<img src="IMAGE/guide/guide-01.png" alt="คำแนะนำ" class="lobby-guide-img" id="guide-img" decoding="async">
|
||||
<button type="button" class="lobby-guide-next" id="btn-guide-next" aria-label="ถัดไป">
|
||||
<img src="/Main-Lobby/IMAGE/guide/btn-next.png" alt="" decoding="async">
|
||||
<img src="IMAGE/guide/btn-next.png" alt="" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,21 +190,21 @@
|
||||
<div id="lobby-ai-chat-panel" class="lobby-ai-chat-panel lobby-ai-chat-hidden" role="dialog" aria-label="เทพความรู้">
|
||||
<div class="lobby-ai-chat-header">
|
||||
<span class="lobby-ai-chat-title">
|
||||
<img src="/Game/img/ai-chat-header.png" alt="คุยกับ AI" decoding="async">
|
||||
<img src="/Game/img/ai-chat-title-label.png" alt="เทพความรู้" class="lobby-ai-chat-title-label" decoding="async">
|
||||
<img src="../Game/img/ai-chat-header.png" alt="คุยกับ AI" decoding="async">
|
||||
<img src="../Game/img/ai-chat-title-label.png" alt="เทพความรู้" class="lobby-ai-chat-title-label" decoding="async">
|
||||
</span>
|
||||
<button type="button" class="lobby-ai-chat-close" id="lobby-ai-chat-close-btn" title="ปิดแชท AI" aria-label="ปิดแชท AI">
|
||||
<img src="/Game/img/chat-close-btn.png" alt="ปิด" id="lobby-ai-chat-toggle-img" width="50" height="50" decoding="async">
|
||||
<img src="../Game/img/chat-close-btn.png" alt="ปิด" id="lobby-ai-chat-toggle-img" width="50" height="50" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
<div id="lobby-ai-chat-messages" class="lobby-ai-chat-messages"></div>
|
||||
<form id="lobby-ai-chat-form" class="lobby-ai-chat-form">
|
||||
<input id="lobby-ai-chat-input" type="text" placeholder="Type a here..." autocomplete="off" aria-label="ข้อความถึง AI">
|
||||
<button type="submit" class="lobby-ai-chat-send" title="ส่ง" aria-label="ส่ง">
|
||||
<img src="/Game/img/chat-btn-send.png" alt="" decoding="async">
|
||||
<img src="../Game/img/chat-btn-send.png" alt="" decoding="async">
|
||||
</button>
|
||||
</form>
|
||||
<p class="lobby-ai-chat-admin-link"><a href="/Game/ai-admin.html" target="_blank" rel="noopener noreferrer">ตั้งค่า AI (Admin)</a></p>
|
||||
<p class="lobby-ai-chat-admin-link"><a href="../Game/ai-admin.html" target="_blank" rel="noopener noreferrer">ตั้งค่า AI (Admin)</a></p>
|
||||
</div>
|
||||
|
||||
<div id="lobby-howto-overlay" class="lobby-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="lobby-howto-title">
|
||||
@@ -150,6 +221,7 @@
|
||||
|
||||
<div id="lobby-toast" class="lobby-toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<script src="lobby.js?v=0.0156"></script>
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="lobby.js?v=0.0158"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+238
-24
@@ -1,15 +1,29 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var BASE = '/Game';
|
||||
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
var SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game';
|
||||
var CHAR_KEY = 'gameCharacterId';
|
||||
var GUIDE_KEY = 'mainLobbyGuideDone';
|
||||
var MAX_GUIDE = 5;
|
||||
var ML_GUIDE = '/Main-Lobby/IMAGE/guide';
|
||||
var ML_GUIDE = typeof appPath === 'function' ? appPath('/Main-Lobby/IMAGE/guide') : '/Main-Lobby/IMAGE/guide';
|
||||
|
||||
/**
|
||||
* ตำแหน่งแถบคำแนะนำ + จุดไฮไลต์ต่อ guide-01…05 (ปรับลำดับให้ตรงไฟล์รูป)
|
||||
* position: 'bottom' | 'top' — ตาม mock แถบล่าง/บน
|
||||
* highlight: selector ของปุ่มใน lobby (null = ไม่วงกรอบ)
|
||||
*/
|
||||
var GUIDE_STEPS = [
|
||||
{ position: 'bottom', highlight: null, pad: 10 },
|
||||
{ position: 'top', highlight: '.lobby-footer-center', pad: 10 },
|
||||
{ position: 'top', highlight: '#btn-ai-chat', pad: 8 },
|
||||
{ position: 'bottom', highlight: '#btn-cloth', pad: 10 },
|
||||
{ position: 'bottom', highlight: '#btn-daily', pad: 10 },
|
||||
];
|
||||
|
||||
function checkLogin() {
|
||||
if (localStorage.getItem('isLoggedIn') !== 'true') {
|
||||
window.location.href = '/Login/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -58,8 +72,8 @@
|
||||
var enc = encodeURIComponent(id);
|
||||
var q = '?ch=' + encodeURIComponent(id);
|
||||
return [
|
||||
BASE + '/img/characters/' + enc + '_down.png' + q,
|
||||
BASE + '/img/characters/' + enc + '_down_0.png' + q
|
||||
SERVER + '/img/characters/' + enc + '_down.png' + q,
|
||||
SERVER + '/img/characters/' + enc + '_down_0.png' + q
|
||||
];
|
||||
}
|
||||
|
||||
@@ -84,7 +98,7 @@
|
||||
/** ดึง COINS จากเซิร์ฟ (บัญชี guest สร้างอัตโนมัติถ้ายังไม่มี) */
|
||||
function syncCoinsFromServer() {
|
||||
var key = ensurePlayerKey();
|
||||
fetch('/Admin/api/player-coins.php?playerKey=' + encodeURIComponent(key), { credentials: 'omit' })
|
||||
fetch((typeof appPath === 'function' ? appPath('/Admin/api/player-coins.php') : '/Admin/api/player-coins.php') + '?playerKey=' + encodeURIComponent(key), { credentials: 'omit' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d || !d.ok) return;
|
||||
@@ -145,6 +159,101 @@
|
||||
});
|
||||
}
|
||||
|
||||
// คำนวณ offset จากความสูงจอด้วย linear interpolation:
|
||||
// 1080 -> 18cqh, 650 -> 25cqh
|
||||
function applyCharacterFootOffsetByViewport() {
|
||||
var root = document.documentElement;
|
||||
if (!root) return;
|
||||
var h = Math.max(1, window.innerHeight || 0);
|
||||
var h1 = 650;
|
||||
var y1 = 25;
|
||||
var h2 = 1080;
|
||||
var y2 = 18;
|
||||
var t = (h - h1) / (h2 - h1);
|
||||
var cqhValue = y1 + ((y2 - y1) * t);
|
||||
// กันหลุดช่วงเมื่อจอเล็ก/ใหญ่เกินช่วงอ้างอิง
|
||||
cqhValue = Math.max(18, Math.min(25, cqhValue));
|
||||
root.style.setProperty('--lobby-character-foot-offset', cqhValue.toFixed(3) + 'cqh');
|
||||
}
|
||||
|
||||
function syncLeaderboardScrollbarThumb() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
var thumbEl = document.getElementById('lobby-leaderboard-scroll-thumb');
|
||||
if (!scrollEl || !thumbEl) return;
|
||||
|
||||
var styles = window.getComputedStyle(thumbEl);
|
||||
var topInset = parseFloat(styles.top) || 0;
|
||||
var minThumb = parseFloat(styles.minHeight) || 18;
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) {
|
||||
thumbEl.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
thumbEl.style.display = 'block';
|
||||
|
||||
var trackHeight = scrollEl.clientHeight;
|
||||
var visibleRatio = scrollEl.clientHeight / Math.max(1, scrollEl.scrollHeight);
|
||||
var thumbHeight = Math.max(minThumb, Math.round(trackHeight * visibleRatio));
|
||||
var maxY = Math.max(0, trackHeight - topInset - thumbHeight);
|
||||
var y = topInset + (maxY * (scrollEl.scrollTop / maxScroll));
|
||||
|
||||
thumbEl.style.height = thumbHeight + 'px';
|
||||
thumbEl.style.transform = 'translateY(' + y.toFixed(2) + 'px)';
|
||||
}
|
||||
|
||||
// วาง leaderboard ให้ต่อจากปุ่ม daily โดยไม่ทับกัน
|
||||
function placeLeaderboardBelowDaily() {
|
||||
var dailyBtn = document.getElementById('btn-daily');
|
||||
var leaderboard = document.querySelector('.lobby-leaderboard');
|
||||
var lobbyUi = document.querySelector('.lobby-ui');
|
||||
if (!dailyBtn || !leaderboard || !lobbyUi) return;
|
||||
|
||||
var boardStyle = window.getComputedStyle(leaderboard);
|
||||
if (boardStyle.position !== 'absolute') {
|
||||
leaderboard.style.top = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var uiRect = lobbyUi.getBoundingClientRect();
|
||||
var dailyRect = dailyBtn.getBoundingClientRect();
|
||||
var boardRect = leaderboard.getBoundingClientRect();
|
||||
|
||||
var cssGap = boardStyle.getPropertyValue('--lobby-daily-leaderboard-gap').trim();
|
||||
var gapPx = 8;
|
||||
if (cssGap) {
|
||||
var n = parseFloat(cssGap);
|
||||
if (!Number.isNaN(n)) {
|
||||
if (cssGap.endsWith('cqh')) gapPx = (uiRect.height * n) / 100;
|
||||
else if (cssGap.endsWith('px') || /^[+-]?\d+(\.\d+)?$/.test(cssGap)) gapPx = n;
|
||||
else if (cssGap.endsWith('vh')) gapPx = ((window.innerHeight || 0) * n) / 100;
|
||||
else if (cssGap.endsWith('%')) gapPx = (uiRect.height * n) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
var nextTop = (dailyRect.bottom - uiRect.top) + gapPx;
|
||||
var maxTop = Math.max(0, uiRect.height - boardRect.height - Math.max(0, gapPx));
|
||||
var clampedTop = Math.max(0, Math.min(nextTop, maxTop));
|
||||
|
||||
leaderboard.style.top = clampedTop.toFixed(2) + 'px';
|
||||
}
|
||||
|
||||
var leaderboardLayoutRaf = 0;
|
||||
function scheduleLeaderboardPlacement() {
|
||||
if (leaderboardLayoutRaf) cancelAnimationFrame(leaderboardLayoutRaf);
|
||||
leaderboardLayoutRaf = requestAnimationFrame(function () {
|
||||
leaderboardLayoutRaf = 0;
|
||||
placeLeaderboardBelowDaily();
|
||||
syncLeaderboardScrollbarThumb();
|
||||
});
|
||||
}
|
||||
|
||||
function bindLeaderboardScrollSync() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
if (!scrollEl || scrollEl.dataset.scrollSyncBound === '1') return;
|
||||
scrollEl.addEventListener('scroll', syncLeaderboardScrollbarThumb, { passive: true });
|
||||
scrollEl.dataset.scrollSyncBound = '1';
|
||||
}
|
||||
|
||||
function applyProfile() {
|
||||
var name = (localStorage.getItem('playerName') || '').trim() || 'MONE';
|
||||
var coins = localStorage.getItem('jdCoins') || '0';
|
||||
@@ -160,7 +269,7 @@
|
||||
var cid = getSelectedCharacterId();
|
||||
if (!cid) {
|
||||
av.onerror = null;
|
||||
av.src = '/Main-Menu/char-main.png';
|
||||
av.src = typeof appPath === 'function' ? appPath('/Main-Menu/char-main.png') : '/Main-Menu/char-main.png';
|
||||
} else {
|
||||
var urls = characterDownUrls(cid);
|
||||
var avStep = 0;
|
||||
@@ -169,7 +278,7 @@
|
||||
if (avStep === 1) av.src = urls[1];
|
||||
else {
|
||||
av.onerror = null;
|
||||
av.src = '/Main-Menu/char-main.png';
|
||||
av.src = typeof appPath === 'function' ? appPath('/Main-Menu/char-main.png') : '/Main-Menu/char-main.png';
|
||||
}
|
||||
};
|
||||
av.src = urls[0];
|
||||
@@ -181,19 +290,78 @@
|
||||
var guideImg = document.getElementById('guide-img');
|
||||
var guideBar = document.getElementById('lobby-guide-bar');
|
||||
var guideDim = document.getElementById('lobby-guide-dim');
|
||||
var guideFocus = document.getElementById('lobby-guide-focus');
|
||||
|
||||
function getGuideStepConfig(step) {
|
||||
return GUIDE_STEPS[step - 1] || { position: 'bottom', highlight: null, pad: 10 };
|
||||
}
|
||||
|
||||
function hideGuideFocus() {
|
||||
if (!guideFocus) return;
|
||||
guideFocus.classList.add('hidden');
|
||||
guideFocus.style.left = '0';
|
||||
guideFocus.style.top = '0';
|
||||
guideFocus.style.width = '0';
|
||||
guideFocus.style.height = '0';
|
||||
}
|
||||
|
||||
function scheduleGuideFocusUpdate() {
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(updateGuideFocus);
|
||||
});
|
||||
}
|
||||
|
||||
function updateGuideFocus() {
|
||||
if (!guideFocus || !guideBar || guideBar.classList.contains('hidden')) {
|
||||
hideGuideFocus();
|
||||
return;
|
||||
}
|
||||
var cfg = getGuideStepConfig(guideStep);
|
||||
if (!cfg.highlight) {
|
||||
hideGuideFocus();
|
||||
return;
|
||||
}
|
||||
var target = document.querySelector(cfg.highlight);
|
||||
if (!target) {
|
||||
hideGuideFocus();
|
||||
return;
|
||||
}
|
||||
var pad = typeof cfg.pad === 'number' ? cfg.pad : 8;
|
||||
var r = target.getBoundingClientRect();
|
||||
if (r.width < 4 || r.height < 4) {
|
||||
hideGuideFocus();
|
||||
return;
|
||||
}
|
||||
var cs = window.getComputedStyle(target);
|
||||
var br = parseInt(cs.borderRadius, 10) || 0;
|
||||
var radius = Math.max(br + 4, 14);
|
||||
|
||||
guideFocus.classList.remove('hidden');
|
||||
guideFocus.style.left = (r.left - pad) + 'px';
|
||||
guideFocus.style.top = (r.top - pad) + 'px';
|
||||
guideFocus.style.width = (r.width + pad * 2) + 'px';
|
||||
guideFocus.style.height = (r.height + pad * 2) + 'px';
|
||||
guideFocus.style.borderRadius = radius + 'px';
|
||||
}
|
||||
|
||||
function syncGuideBodyClass() {
|
||||
if (!guideBar) return;
|
||||
var open = !guideBar.classList.contains('hidden');
|
||||
if (open) {
|
||||
document.body.classList.add('lobby-guide-active');
|
||||
document.body.classList.toggle('lobby-guide-top-step', guideStep >= 4);
|
||||
var cfg = getGuideStepConfig(guideStep);
|
||||
document.body.classList.toggle('lobby-guide-top-step', cfg.position === 'top');
|
||||
var hasFocus = !!cfg.highlight;
|
||||
if (guideDim) guideDim.classList.toggle('hidden', hasFocus);
|
||||
} else {
|
||||
document.body.classList.remove('lobby-guide-active');
|
||||
document.body.classList.remove('lobby-guide-top-step');
|
||||
if (guideDim) guideDim.classList.add('hidden');
|
||||
}
|
||||
if (guideDim) {
|
||||
guideDim.classList.toggle('hidden', !open);
|
||||
if (open) {
|
||||
scheduleGuideFocusUpdate();
|
||||
} else {
|
||||
hideGuideFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +370,7 @@
|
||||
if (n < 1 || n > MAX_GUIDE) {
|
||||
guideBar.classList.add('hidden');
|
||||
guideBar.classList.remove('lobby-guide-bar--top');
|
||||
hideGuideFocus();
|
||||
try {
|
||||
localStorage.setItem(GUIDE_KEY, '1');
|
||||
} catch (e) { /* ignore */ }
|
||||
@@ -209,14 +378,22 @@
|
||||
return;
|
||||
}
|
||||
guideStep = n;
|
||||
var cfg = getGuideStepConfig(guideStep);
|
||||
guideImg.src = ML_GUIDE + '/guide-' + (n < 10 ? '0' + n : String(n)) + '.png';
|
||||
guideImg.alt = 'คำแนะนำ ขั้นที่ ' + n;
|
||||
guideBar.classList.toggle('lobby-guide-bar--top', n >= 4);
|
||||
guideBar.classList.toggle('lobby-guide-bar--top', cfg.position === 'top');
|
||||
var nextBtn = document.getElementById('btn-guide-next');
|
||||
if (nextBtn) {
|
||||
nextBtn.setAttribute('aria-label', n >= MAX_GUIDE ? 'รับทราบ' : 'ถัดไป');
|
||||
}
|
||||
syncGuideBodyClass();
|
||||
guideImg.onload = function () {
|
||||
scheduleGuideFocusUpdate();
|
||||
};
|
||||
guideImg.onerror = function () {
|
||||
scheduleGuideFocusUpdate();
|
||||
};
|
||||
scheduleGuideFocusUpdate();
|
||||
}
|
||||
|
||||
function initGuide() {
|
||||
@@ -253,9 +430,19 @@
|
||||
e.preventDefault();
|
||||
guideBar.classList.add('hidden');
|
||||
guideBar.classList.remove('lobby-guide-bar--top');
|
||||
hideGuideFocus();
|
||||
syncGuideBodyClass();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (guideBar && !guideBar.classList.contains('hidden')) {
|
||||
scheduleGuideFocusUpdate();
|
||||
}
|
||||
});
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(scheduleGuideFocusUpdate, 350);
|
||||
});
|
||||
|
||||
/* ใช้ capture + data-href บน .lobby-footer-center — ลดโอกาสถูกเลเยอร์อื่นแย่งคลิกก่อนถึงปุ่ม */
|
||||
var footerCenter = document.querySelector('.lobby-footer-center');
|
||||
if (footerCenter) {
|
||||
@@ -280,7 +467,7 @@
|
||||
});
|
||||
|
||||
document.getElementById('btn-myprofile')?.addEventListener('click', function () {
|
||||
window.location.href = '/Main-Menu/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
|
||||
});
|
||||
|
||||
function getMlAiSessionId() {
|
||||
@@ -302,21 +489,22 @@
|
||||
var aiChatToggleImg = document.getElementById('lobby-ai-chat-toggle-img');
|
||||
var btnAiChat = document.getElementById('btn-ai-chat');
|
||||
|
||||
function updateAiChatHeaderUi() {
|
||||
if (!aiChatPanel || !aiChatToggleImg || !aiChatCloseBtn) return;
|
||||
function syncAiChatPanelUi() {
|
||||
if (!aiChatPanel) return;
|
||||
var hidden = aiChatPanel.classList.contains('lobby-ai-chat-hidden');
|
||||
if (!hidden) {
|
||||
aiChatToggleImg.src = BASE + '/img/chat-close-btn.png';
|
||||
aiChatToggleImg.alt = 'ปิด';
|
||||
aiChatCloseBtn.setAttribute('title', 'ปิดแชท AI');
|
||||
aiChatCloseBtn.setAttribute('aria-label', 'ปิดแชท AI');
|
||||
}
|
||||
if (btnAiChat) btnAiChat.style.display = hidden ? '' : 'none';
|
||||
if (hidden || !aiChatToggleImg || !aiChatCloseBtn) return;
|
||||
aiChatToggleImg.src = BASE + '/img/chat-close-btn.png';
|
||||
aiChatToggleImg.alt = 'ปิด';
|
||||
aiChatCloseBtn.setAttribute('title', 'ปิดแชท AI');
|
||||
aiChatCloseBtn.setAttribute('aria-label', 'ปิดแชท AI');
|
||||
}
|
||||
|
||||
if (aiChatCloseBtn && aiChatPanel) {
|
||||
aiChatCloseBtn.addEventListener('click', function () {
|
||||
aiChatPanel.classList.add('lobby-ai-chat-hidden');
|
||||
aiChatPanel.classList.remove('chat-panel-collapsed');
|
||||
syncAiChatPanelUi();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -325,7 +513,7 @@
|
||||
if (aiChatPanel.classList.contains('lobby-ai-chat-hidden')) {
|
||||
aiChatPanel.classList.remove('lobby-ai-chat-hidden');
|
||||
aiChatPanel.classList.remove('chat-panel-collapsed');
|
||||
updateAiChatHeaderUi();
|
||||
syncAiChatPanelUi();
|
||||
var inp = document.getElementById('lobby-ai-chat-input');
|
||||
if (inp) {
|
||||
try {
|
||||
@@ -334,9 +522,11 @@
|
||||
}
|
||||
} else {
|
||||
aiChatPanel.classList.add('lobby-ai-chat-hidden');
|
||||
syncAiChatPanelUi();
|
||||
}
|
||||
});
|
||||
}
|
||||
syncAiChatPanelUi();
|
||||
|
||||
var aiChatForm = document.getElementById('lobby-ai-chat-form');
|
||||
var aiChatInput = document.getElementById('lobby-ai-chat-input');
|
||||
@@ -387,7 +577,7 @@
|
||||
appendAiMessage(text, false);
|
||||
setAiChatWaiting(true);
|
||||
aiChatInput.value = '';
|
||||
fetch(BASE + '/api/ai-chat', {
|
||||
fetch(SERVER + '/api/ai-chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: text, sessionId: getMlAiSessionId() }),
|
||||
@@ -428,14 +618,38 @@
|
||||
if (e.target === howto) closeHowto();
|
||||
});
|
||||
|
||||
applyCharacterFootOffsetByViewport();
|
||||
syncMainLobbyCharacterUi();
|
||||
bindLeaderboardScrollSync();
|
||||
scheduleLeaderboardPlacement();
|
||||
initGuide();
|
||||
|
||||
window.addEventListener('pageshow', function () {
|
||||
applyCharacterFootOffsetByViewport();
|
||||
syncMainLobbyCharacterUi();
|
||||
scheduleLeaderboardPlacement();
|
||||
});
|
||||
window.addEventListener('resize', function () {
|
||||
applyCharacterFootOffsetByViewport();
|
||||
bindLeaderboardScrollSync();
|
||||
scheduleLeaderboardPlacement();
|
||||
});
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(function () {
|
||||
applyCharacterFootOffsetByViewport();
|
||||
scheduleLeaderboardPlacement();
|
||||
}, 250);
|
||||
});
|
||||
window.addEventListener('load', function () {
|
||||
bindLeaderboardScrollSync();
|
||||
scheduleLeaderboardPlacement();
|
||||
});
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState === 'visible') syncMainLobbyCharacterUi();
|
||||
if (document.visibilityState === 'visible') {
|
||||
applyCharacterFootOffsetByViewport();
|
||||
syncMainLobbyCharacterUi();
|
||||
scheduleLeaderboardPlacement();
|
||||
}
|
||||
});
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key == null || e.key === CHAR_KEY || e.key === 'playerName') {
|
||||
|
||||
+998
-235
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Main Menu — JD JUSTICE DIVERS</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="style.css?v=1.2">
|
||||
</head>
|
||||
<body>
|
||||
<!-- พื้นหลังจาก Login -->
|
||||
@@ -43,6 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="menu.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
function checkLoginStatus() {
|
||||
const isLoggedIn = localStorage.getItem('isLoggedIn');
|
||||
if (!isLoggedIn || isLoggedIn !== 'true') {
|
||||
window.location.href = '/Login/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -18,22 +18,22 @@
|
||||
|
||||
// ปุ่มต่างๆ
|
||||
document.getElementById('btn-back')?.addEventListener('click', function () {
|
||||
window.location.href = '/Main-Lobby/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Lobby/') : '/Main-Lobby/';
|
||||
});
|
||||
|
||||
document.getElementById('btn-create-room')?.addEventListener('click', function () {
|
||||
window.location.href = '/Create%20Room/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Create%20Room/') : '/Create%20Room/';
|
||||
});
|
||||
|
||||
document.getElementById('btn-join')?.addEventListener('click', function () {
|
||||
window.location.href = '/Join%20Room/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Join%20Room/') : '/Join%20Room/';
|
||||
});
|
||||
|
||||
document.getElementById('btn-exit-game')?.addEventListener('click', function () {
|
||||
if (confirm('คุณต้องการออกจากเกมหรือไม่?')) {
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
localStorage.removeItem('loginType');
|
||||
window.location.href = '/Login/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
+918
-18
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user