update design
@@ -0,0 +1,34 @@
|
||||
@echo off
|
||||
set SRC=https://srv1361159.hstgr.cloud
|
||||
set ROOT=%~dp0..
|
||||
echo Download app-base.js and index.html ...
|
||||
echo.
|
||||
|
||||
curl --ssl-no-revoke -fsSL "%SRC%/app-base.js" -o "%ROOT%\app-base.js"
|
||||
if errorlevel 1 goto try_powershell
|
||||
curl --ssl-no-revoke -fsSL "%SRC%/Create%%20Room/index.html" -o "%~dp0index.html"
|
||||
if errorlevel 1 goto try_powershell
|
||||
|
||||
echo.
|
||||
echo OK - press Ctrl+Shift+R on Create Room page
|
||||
goto end
|
||||
|
||||
:try_powershell
|
||||
echo curl failed, trying PowerShell...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0FIX-NOW.ps1"
|
||||
if errorlevel 1 goto manual
|
||||
goto end
|
||||
|
||||
:manual
|
||||
echo.
|
||||
echo AUTO DOWNLOAD FAILED - do this manually:
|
||||
echo 1) Open in browser: %SRC%/Create%%20Room/
|
||||
echo 2) Ctrl+U view source, Ctrl+A Ctrl+C
|
||||
echo 3) Notepad - paste - Save As:
|
||||
echo %ROOT%\Create Room\index.html
|
||||
echo 4) Open %SRC%/app-base.js - Save As:
|
||||
echo %ROOT%\app-base.js
|
||||
echo.
|
||||
|
||||
:end
|
||||
pause
|
||||
@@ -0,0 +1,25 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$src = 'https://srv1361159.hstgr.cloud'
|
||||
$root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$cr = Join-Path $root 'Create Room'
|
||||
|
||||
[System.Net.ServicePointManager]::CheckCertificateRevocationList = $false
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
function Save-Url($url, $path) {
|
||||
Write-Host " $url"
|
||||
Write-Host " -> $path"
|
||||
try {
|
||||
Invoke-WebRequest -Uri $url -OutFile $path -UseBasicParsing
|
||||
} catch {
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile($url, $path)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host 'Downloading...'
|
||||
Save-Url "$src/app-base.js" (Join-Path $root 'app-base.js')
|
||||
Save-Url "$src/Create%20Room/index.html" (Join-Path $cr 'index.html')
|
||||
Write-Host ''
|
||||
Write-Host 'Done. Open Create Room and Ctrl+Shift+R'
|
||||
Read-Host 'Press Enter'
|
||||
@@ -0,0 +1,13 @@
|
||||
Paste this block as the FIRST <script> before other scripts in index.html
|
||||
(if download fails, edit index.html in Notepad and add at line ~89):
|
||||
|
||||
<script>
|
||||
(function(){var P='https://srv1361159.hstgr.cloud/Game',h=(location.hostname||'').toLowerCase();
|
||||
if(h!=='localhost'&&h!=='127.0.0.1')return;
|
||||
try{localStorage.setItem('isLoggedIn','true');localStorage.setItem('loginType','guest');
|
||||
if(!localStorage.getItem('playerName'))localStorage.setItem('playerName','DevTester');}catch(e){}
|
||||
window.GAME_API_FETCH_BASE=P;window.__JD_GAME_API_BASE__=P;
|
||||
if(!window.fetch||window.__JD_INLINE__)return;window.__JD_INLINE__=1;
|
||||
var f=window.fetch.bind(window);var bad=/^https?:\/\/(?:localhost|127\.0\.0\.1):(3004|3001|3010|13010)\/Game/i;
|
||||
window.fetch=function(u,o){if(typeof u==='string'&&bad.test(u))u=P+u.slice(u.indexOf('/Game')+5);return f(u,o);};})();
|
||||
</script>
|
||||
@@ -1,6 +1,8 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (typeof ensureLocalDevLogin === 'function') ensureLocalDevLogin();
|
||||
|
||||
function checkLoginStatus() {
|
||||
var isLoggedIn = localStorage.getItem('isLoggedIn');
|
||||
if (!isLoggedIn || isLoggedIn !== 'true') {
|
||||
@@ -13,7 +15,33 @@
|
||||
if (!checkLoginStatus()) return;
|
||||
|
||||
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
var SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game';
|
||||
|
||||
var PROD_GAME_API = 'https://srv1361159.hstgr.cloud/Game';
|
||||
|
||||
function resolveCreateRoomApiBase() {
|
||||
if (typeof resolveGameApiBase === 'function') {
|
||||
return resolveGameApiBase().replace(/\/$/, '');
|
||||
}
|
||||
if (typeof window.__JD_GAME_API_BASE__ === 'string' && window.__JD_GAME_API_BASE__) {
|
||||
return window.__JD_GAME_API_BASE__.replace(/\/$/, '');
|
||||
}
|
||||
var pageGame = BASE.replace(/\/$/, '');
|
||||
try {
|
||||
var h = (location.hostname || '').toLowerCase();
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
try {
|
||||
if (localStorage.getItem('jdUseLocalGameApi') === '1') {
|
||||
var port = localStorage.getItem('jdGameNodePort') || '13010';
|
||||
return 'http://127.0.0.1:' + String(port).replace(/\D/g, '') + '/Game';
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return PROD_GAME_API;
|
||||
}
|
||||
} catch (e2) { /* ignore */ }
|
||||
return pageGame;
|
||||
}
|
||||
|
||||
var SERVER = resolveCreateRoomApiBase();
|
||||
var LOBBY_A_MAP_ID = 'mlsbbxfe';
|
||||
var LOBBY_TOTAL_SLOTS = 6;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</div>
|
||||
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="create-room.js?v=1.004"></script>
|
||||
<script src="create-room.js?v=20260527"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var content = document.querySelector('.create-content');
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* AppServ localhost — ใช้ Game API + Socket บน production
|
||||
* ไม่ต้องรัน node server.js บน Windows (หลีกเลี่ยง EACCES port 3001/3010)
|
||||
* ปิดโหมดนี้: localStorage.setItem('jdUseLocalGameApi','1') แล้วรัน node ที่พอร์ต 13010
|
||||
*/
|
||||
(function (g) {
|
||||
'use strict';
|
||||
var loc = g.location || {};
|
||||
var h = (loc.hostname || '').toLowerCase();
|
||||
if (h !== 'localhost' && h !== '127.0.0.1') return;
|
||||
|
||||
var PROD = 'https://srv1361159.hstgr.cloud';
|
||||
var PROD_GAME = PROD + '/Game';
|
||||
|
||||
function useLocalNode() {
|
||||
try { return localStorage.getItem('jdUseLocalGameApi') === '1'; } catch (e) { return false; }
|
||||
}
|
||||
|
||||
function localApiBase() {
|
||||
var port = '13010';
|
||||
try {
|
||||
var p = localStorage.getItem('jdGameNodePort');
|
||||
if (p) port = String(p).replace(/\D/g, '') || port;
|
||||
} catch (e) { /* ignore */ }
|
||||
return 'http://127.0.0.1:' + port + '/Game';
|
||||
}
|
||||
|
||||
var apiBase = useLocalNode() ? localApiBase() : PROD_GAME;
|
||||
var nodeOrigin = useLocalNode()
|
||||
? apiBase.replace(/\/Game\/?$/, '')
|
||||
: PROD;
|
||||
|
||||
g.__JD_GAME_API_BASE__ = apiBase;
|
||||
g.GAME_API_FETCH_BASE = apiBase;
|
||||
g.GAME_NODE_ORIGIN = nodeOrigin;
|
||||
g.GAME_SERVER = nodeOrigin;
|
||||
|
||||
g.ensureLocalDevLogin = function () {
|
||||
try {
|
||||
if (localStorage.getItem('isLoggedIn') !== 'true') {
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
localStorage.setItem('loginType', 'guest');
|
||||
}
|
||||
if (!(localStorage.getItem('playerName') || '').trim()) {
|
||||
localStorage.setItem('playerName', 'DevTester');
|
||||
}
|
||||
if (!(localStorage.getItem('roomLobbyDisplayName') || '').trim()) {
|
||||
localStorage.setItem('roomLobbyDisplayName', 'DevTester');
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
};
|
||||
g.ensureLocalDevLogin();
|
||||
|
||||
g.resolveGameApiBase = function () {
|
||||
return String(g.__JD_GAME_API_BASE__ || apiBase).replace(/\/$/, '');
|
||||
};
|
||||
|
||||
if (g.__JD_LOCAL_DEV_FETCH_PATCHED__ || typeof g.fetch !== 'function') return;
|
||||
var nativeFetch = g.fetch.bind(g);
|
||||
var badPort = /https?:\/\/(?:localhost|127\.0\.0\.1):(3004|3001|3010|13010)\/Game/i;
|
||||
|
||||
g.fetch = function jdLocalDevFetch(input, init) {
|
||||
if (typeof input === 'string') {
|
||||
if (badPort.test(input)) {
|
||||
input = PROD_GAME + input.slice(input.indexOf('/Game') + 5);
|
||||
} else if (!useLocalNode() && input.indexOf('/Game/api') >= 0 && input.indexOf('://') < 0) {
|
||||
var base = typeof g.appPath === 'function' ? g.appPath('/Game') : '/Game';
|
||||
if (input.indexOf(base + '/api') === 0) {
|
||||
input = PROD_GAME + input.slice(base.length);
|
||||
} else if (input.indexOf('/Game/api') === 0) {
|
||||
input = PROD_GAME + input.slice('/Game'.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nativeFetch(input, init);
|
||||
};
|
||||
g.__JD_LOCAL_DEV_FETCH_PATCHED__ = true;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
@@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
set SRC=https://srv1361159.hstgr.cloud
|
||||
set ROOT=%~dp0..
|
||||
echo Sync from production...
|
||||
echo.
|
||||
|
||||
curl -fsSL "%SRC%/app-base.js" -o "%ROOT%\app-base.js" || goto fail
|
||||
curl -fsSL "%SRC%/Create%%20Room/jd-local-dev.js" -o "%~dp0jd-local-dev.js" || goto fail
|
||||
curl -fsSL "%SRC%/Create%%20Room/create-room.js" -o "%~dp0create-room.js" || goto fail
|
||||
curl -fsSL "%SRC%/Create%%20Room/index.html" -o "%~dp0index.html" || goto fail
|
||||
if not exist "%ROOT%\Game\js" mkdir "%ROOT%\Game\js"
|
||||
curl -fsSL "%SRC%/Game/js/game-http-base.js" -o "%ROOT%\Game\js\game-http-base.js" || goto fail
|
||||
|
||||
echo.
|
||||
echo OK — Ctrl+Shift+R ที่ Create Room
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
:fail
|
||||
echo Download failed. Run FIX-NOW.bat instead (only app-base.js).
|
||||
pause
|
||||
exit /b 1
|
||||
@@ -0,0 +1 @@
|
||||
public/css
|
||||
@@ -0,0 +1 @@
|
||||
public/js
|
||||
@@ -0,0 +1,14 @@
|
||||
If room-lobby is BLACK on localhost, paste this as FIRST script in room-lobby.html (before app-base.js):
|
||||
|
||||
<script>
|
||||
(function(){var P='https://srv1361159.hstgr.cloud',PG=P+'/Game',h=(location.hostname||'').toLowerCase();
|
||||
if(h!=='localhost'&&h!=='127.0.0.1')return;
|
||||
window.GAME_NODE_ORIGIN=P;window.GAME_SERVER=P;window.GAME_API_FETCH_BASE=PG;
|
||||
if(window.fetch&&!window.__JD_RL_FETCH__){window.__JD_RL_FETCH__=1;var f=window.fetch.bind(window);
|
||||
var bad=/^https?:\/\/(?:localhost|127\.0\.0\.1):(3004|3001|3010|13010)\/Game/i;
|
||||
window.fetch=function(u,o){if(typeof u==='string'&&bad.test(u))u=PG+u.slice(u.indexOf('/Game')+5);return f(u,o);};}
|
||||
document.write('<script src="'+P+'/Game/socket.io/socket.io.js"><\/script>');})();
|
||||
</script>
|
||||
|
||||
PowerShell (if SSL error, add -UseBasicParsing):
|
||||
Invoke-WebRequest "https://srv1361159.hstgr.cloud/Game/room-lobby.html" -OutFile "C:\AppServ\www\srv1361159.hstgr.cloud\Game\room-lobby.html" -UseBasicParsing
|
||||
@@ -138,7 +138,7 @@
|
||||
</script>
|
||||
<div class="container">
|
||||
<h1>ตัวละคร (4 ทิศ)</h1>
|
||||
<p class="character-page-links"><a href="index.html">กลับหน้าหลัก</a> · <a href="lobby.html">ล็อบบี้</a></p>
|
||||
<p class="character-page-links"><a href="index.html">กลับหน้าหลัก</a> · <a href="/Main-Menu/">เมนูหลัก</a> · <a href="/Main-Lobby/">Main Lobby</a></p>
|
||||
|
||||
<section class="char-upload char-upload-simple">
|
||||
<h2>อัปโหลดตัวละคร (บน · ล่าง · ซ้าย · ขวา)</h2>
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src:
|
||||
url('../../../FONTS/NotoSansThai-ExtraBold.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.customize-popup-dialog,
|
||||
.customize-popup-dialog * {
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.customize-popup-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10090;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.customize-popup-overlay.is-hidden,
|
||||
.customize-popup-overlay.hidden,
|
||||
#room-cz-overlay.is-hidden,
|
||||
#room-cz-overlay.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* room-lobby ใช้ #room-cz-overlay + คลาส customize-popup (production) */
|
||||
#room-cz-overlay.customize-popup-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10090;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.customize-popup-dialog {
|
||||
position: relative;
|
||||
width: min(1600px, calc(100vw - 24px));
|
||||
max-height: calc(100dvh - 24px);
|
||||
min-height: 0;
|
||||
--cc-scale: 1;
|
||||
--cc-layout-shift-x: calc(64px * var(--cc-scale));
|
||||
}
|
||||
|
||||
.customize-popup-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: calc(14px * var(--cc-scale));
|
||||
min-height: 0;
|
||||
transform: translateX(var(--cc-layout-shift-x));
|
||||
}
|
||||
|
||||
.customize-popup-char-col {
|
||||
position: relative;
|
||||
width: min(500px, 36vw);
|
||||
min-width: 330px;
|
||||
height: calc(860px * var(--cc-scale));
|
||||
padding-top: calc(18px * var(--cc-scale));
|
||||
flex: 0 0 auto;
|
||||
--cc-char-preview-left: 40%;
|
||||
--cc-char-preview-top: calc(130px * var(--cc-scale));
|
||||
--cc-char-preview-size: calc(800px * var(--cc-scale));
|
||||
}
|
||||
|
||||
.customize-popup-char-bg {
|
||||
position: absolute;
|
||||
left: var(--cc-char-preview-left);
|
||||
top: var(--cc-char-preview-top);
|
||||
transform: translateX(-50%);
|
||||
width: var(--cc-char-preview-size);
|
||||
height: var(--cc-char-preview-size);
|
||||
aspect-ratio: 789 / 789;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.customize-popup-char-preview {
|
||||
position: absolute;
|
||||
left: var(--cc-char-preview-left);
|
||||
top: var(--cc-char-preview-top);
|
||||
transform: translateX(-50%);
|
||||
width: var(--cc-char-preview-size);
|
||||
height: var(--cc-char-preview-size);
|
||||
object-fit: contain;
|
||||
image-rendering: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.customize-popup-player-name {
|
||||
position: absolute;
|
||||
left: var(--cc-char-preview-left);
|
||||
top: calc(114px * var(--cc-scale));
|
||||
transform: translateX(-50%);
|
||||
width: var(--cc-char-preview-size);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
font-size: calc(55px * var(--cc-scale));
|
||||
font-weight: 700;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.customize-popup-coin-wrap {
|
||||
position: absolute;
|
||||
top: calc(8px * var(--cc-scale));
|
||||
left: calc(0px * var(--cc-scale));
|
||||
width: calc(237px * var(--cc-scale));
|
||||
height: calc(97px * var(--cc-scale));
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.customize-popup-coin-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-coin-val {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 58%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #fff;
|
||||
font-size: calc(30px * var(--cc-scale));
|
||||
font-weight: 700;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
text-shadow: 0 0 8px rgba(54, 255, 255, 0.45);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.customize-popup-panel-shell {
|
||||
position: relative;
|
||||
width: min(
|
||||
990px,
|
||||
calc((100dvh - 24px) * (990 / 951)),
|
||||
calc(100vw - 24px - min(500px, 36vw) - calc(14px * var(--cc-scale)))
|
||||
);
|
||||
aspect-ratio: 990 / 951;
|
||||
max-height: calc(100dvh - 24px);
|
||||
min-width: 0;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
--cc-shell-scale: var(--cc-scale);
|
||||
}
|
||||
|
||||
.customize-popup-cloth-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.customize-popup-close {
|
||||
position: absolute;
|
||||
top: calc(78px * var(--cc-shell-scale));
|
||||
right: calc(86px * var(--cc-shell-scale));
|
||||
width: calc(84px * var(--cc-shell-scale));
|
||||
height: calc(84px * var(--cc-shell-scale));
|
||||
border: none;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.customize-popup-panel {
|
||||
position: absolute;
|
||||
top: calc(165px * var(--cc-shell-scale));
|
||||
left: calc(118px * var(--cc-shell-scale));
|
||||
right: calc(119px * var(--cc-shell-scale));
|
||||
bottom: calc(39px * var(--cc-shell-scale));
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.customize-popup-dialog {
|
||||
width: min(1280px, calc(100vw - 24px));
|
||||
}
|
||||
.customize-popup-char-col {
|
||||
width: min(430px, 34vw);
|
||||
min-width: 300px;
|
||||
height: calc(740px * var(--cc-scale));
|
||||
}
|
||||
.customize-popup-tabs {
|
||||
--customize-tab-line-shift-x: calc(12px * var(--cc-shell-scale));
|
||||
--customize-tab-line-extend-left: calc(10px * var(--cc-shell-scale));
|
||||
--customize-tab-right-line-shift-x: calc(-12px * var(--cc-shell-scale));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 820px) and (min-width: 961px) {
|
||||
.customize-popup-content {
|
||||
gap: calc(10px * var(--cc-scale));
|
||||
transform: translateX(calc(36px * var(--cc-scale)));
|
||||
}
|
||||
.customize-popup-char-col {
|
||||
width: min(420px, 32vw);
|
||||
min-width: 280px;
|
||||
height: calc(700px * var(--cc-scale));
|
||||
}
|
||||
.customize-popup-panel-shell {
|
||||
width: min(
|
||||
920px,
|
||||
calc((100dvh - 24px) * (990 / 951)),
|
||||
calc(100vw - 24px - min(420px, 32vw) - calc(10px * var(--cc-scale)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.customize-popup-content {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transform: none;
|
||||
}
|
||||
.customize-popup-coin-wrap {
|
||||
left: calc(8px * var(--cc-scale));
|
||||
}
|
||||
.customize-popup-char-col {
|
||||
width: min(520px, 84vw);
|
||||
min-width: 0;
|
||||
height: calc(760px * var(--cc-scale));
|
||||
}
|
||||
.customize-popup-panel-shell {
|
||||
width: min(920px, calc(100vw - 24px));
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.customize-popup-theme-row {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: calc(97px * var(--cc-shell-scale));
|
||||
margin-bottom: calc(-3px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-theme-label {
|
||||
position: absolute;
|
||||
left: calc(43px * var(--cc-shell-scale));
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
width: calc(100px * var(--cc-shell-scale));
|
||||
height: calc(79px * var(--cc-shell-scale));
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-theme-frame {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: calc(97px * var(--cc-shell-scale));
|
||||
width: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.customize-popup-theme-frame > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-color-list {
|
||||
position: absolute;
|
||||
top: calc(16px * var(--cc-shell-scale));
|
||||
right: calc(18px * var(--cc-shell-scale));
|
||||
bottom: calc(16px * var(--cc-shell-scale));
|
||||
left: calc(170px * var(--cc-shell-scale));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(16px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-color-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: calc(46px * var(--cc-shell-scale));
|
||||
height: calc(43px * var(--cc-shell-scale));
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
isolation: isolate;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.customize-popup-color-btn > .customize-popup-color-swatch {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-color-btn > .customize-popup-color-swatch--hex {
|
||||
border-radius: calc(8px * var(--cc-shell-scale));
|
||||
border: calc(2px * var(--cc-shell-scale)) solid rgba(0, 0, 0, 0.35);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.customize-popup-color-btn > .customize-popup-color-highlight {
|
||||
position: absolute !important;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: calc(78px * var(--cc-shell-scale));
|
||||
height: calc(75px * var(--cc-shell-scale));
|
||||
transform: translate(-50%, -50%);
|
||||
object-fit: contain;
|
||||
display: none !important;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.customize-popup-color-btn.is-active > .customize-popup-color-highlight {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.customize-popup-tabs {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
--customize-tab-gap: calc(-5px * var(--cc-shell-scale));
|
||||
--customize-tab-line-bottom: calc(4px * var(--cc-shell-scale)); /* ระยะจากล่างของเส้นใต้แท็บ */
|
||||
--customize-tab-line-shift-x: calc(25px * var(--cc-shell-scale));
|
||||
--customize-tab-line-extend-left: calc(21px * var(--cc-shell-scale));
|
||||
--customize-tab-line-extend-right: calc(0px * var(--cc-shell-scale));
|
||||
--customize-tab-left-shift-x: calc(0px * var(--cc-shell-scale));
|
||||
--customize-tab-mid-shift-x: calc(0px * var(--cc-shell-scale));
|
||||
--customize-tab-right-shift-x: calc(0px * var(--cc-shell-scale));
|
||||
--customize-tab-mid-line-shift-x: calc(0px * var(--cc-shell-scale)); /* เส้นใต้ปุ่มกลาง(ทรงผม) ขยับซ้าย/ขวา */
|
||||
--customize-tab-right-line-shift-x: calc(-28px * var(--cc-shell-scale)); /* เส้นใต้ปุ่มขวา(ชุด) ขยับซ้าย/ขวา */
|
||||
gap: 0;
|
||||
margin: calc(6px * var(--cc-shell-scale)) 0 calc(8px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn + .customize-popup-tab-btn {
|
||||
margin-left: var(--customize-tab-gap);
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn {
|
||||
position: relative;
|
||||
width: calc(256px * var(--cc-shell-scale));
|
||||
height: calc(100px * var(--cc-shell-scale));
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:first-child {
|
||||
transform: translateX(var(--customize-tab-left-shift-x));
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:nth-child(2) {
|
||||
transform: translateX(var(--customize-tab-mid-shift-x));
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:last-child {
|
||||
transform: translateX(var(--customize-tab-right-shift-x));
|
||||
}
|
||||
|
||||
.customize-popup-tab-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn-line {
|
||||
position: absolute;
|
||||
left: calc(
|
||||
(-0.5 * var(--customize-tab-gap))
|
||||
+ var(--customize-tab-line-shift-x)
|
||||
- var(--customize-tab-line-extend-left)
|
||||
);
|
||||
bottom: var(--customize-tab-line-bottom) !important;
|
||||
width: calc(
|
||||
100% + var(--customize-tab-gap)
|
||||
+ var(--customize-tab-line-extend-left)
|
||||
+ var(--customize-tab-line-extend-right)
|
||||
);
|
||||
height: auto;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:first-child .customize-popup-tab-btn-line {
|
||||
left: calc(var(--customize-tab-line-shift-x) - var(--customize-tab-line-extend-left));
|
||||
width: calc(
|
||||
100% + (0.5 * var(--customize-tab-gap))
|
||||
+ var(--customize-tab-line-extend-left)
|
||||
+ var(--customize-tab-line-extend-right)
|
||||
);
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:nth-child(2) .customize-popup-tab-btn-line {
|
||||
left: calc(
|
||||
(-0.5 * var(--customize-tab-gap))
|
||||
+ var(--customize-tab-line-shift-x)
|
||||
- var(--customize-tab-line-extend-left)
|
||||
+ var(--customize-tab-mid-line-shift-x)
|
||||
);
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn:last-child .customize-popup-tab-btn-line {
|
||||
left: calc(
|
||||
(-0.5 * var(--customize-tab-gap))
|
||||
+ var(--customize-tab-line-shift-x)
|
||||
- var(--customize-tab-line-extend-left)
|
||||
+ var(--customize-tab-right-line-shift-x)
|
||||
);
|
||||
width: calc(
|
||||
100% + (0.5 * var(--customize-tab-gap))
|
||||
+ var(--customize-tab-line-extend-left)
|
||||
+ var(--customize-tab-line-extend-right)
|
||||
);
|
||||
}
|
||||
|
||||
.customize-popup-tab-btn.is-active .customize-popup-tab-btn-line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customize-popup-items-wrap {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding-right: calc(2px * var(--cc-shell-scale));
|
||||
padding-left: calc(2px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-items {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: calc(12px * var(--cc-shell-scale));
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.customize-popup-items::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.customize-popup-item {
|
||||
position: relative;
|
||||
min-height: calc(171px * var(--cc-shell-scale));
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.customize-popup-item-bg {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: calc(181px * var(--cc-shell-scale));
|
||||
height: calc(171px * var(--cc-shell-scale));
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.customize-popup-item-face {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 50%;
|
||||
top: calc(30px * var(--cc-shell-scale));
|
||||
transform: translateX(-50%);
|
||||
width: calc(101px * var(--cc-shell-scale));
|
||||
height: calc(101px * var(--cc-shell-scale));
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-item-price {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 50%;
|
||||
bottom: calc(-9px * var(--cc-shell-scale));
|
||||
transform: translateX(-50%);
|
||||
width: calc(121px * var(--cc-shell-scale));
|
||||
height: calc(51px * var(--cc-shell-scale));
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-item-price-text {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 54%;
|
||||
bottom: calc(5px * var(--cc-shell-scale));
|
||||
transform: translateX(-50%);
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
font-size: calc(16px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-item:first-child .customize-popup-item-price,
|
||||
.customize-popup-item:first-child .customize-popup-item-price-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.customize-popup-item-highlight {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: calc(191px * var(--cc-shell-scale));
|
||||
height: calc(181px * var(--cc-shell-scale));
|
||||
transform: translate(-50%, -50%);
|
||||
object-fit: contain;
|
||||
display: none;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.customize-popup-item.is-active .customize-popup-item-highlight {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.customize-popup-scroll {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: calc(-17px * var(--cc-shell-scale));
|
||||
bottom: 0;
|
||||
width: calc(14px * var(--cc-shell-scale));
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.customize-popup-scroll.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.customize-popup-scroll-track {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 999px;
|
||||
background: #1b2740;
|
||||
border: 1px solid #22324f;
|
||||
box-shadow: inset 0 0 6px rgba(8, 13, 24, 0.45);
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.customize-popup-scroll-thumb {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
border-radius: 999px;
|
||||
background: #f04cdc;
|
||||
border: 1px solid #ffb8f5;
|
||||
box-shadow: 0 0 6px rgba(240, 76, 220, 0.55);
|
||||
pointer-events: auto;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.customize-popup-scroll-thumb.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.customize-popup-confirm {
|
||||
align-self: center;
|
||||
width: calc(351px * var(--cc-shell-scale));
|
||||
height: calc(128px * var(--cc-shell-scale));
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
margin-top: calc(8px * var(--cc-shell-scale));
|
||||
margin-bottom: calc(-42px * var(--cc-shell-scale));
|
||||
}
|
||||
|
||||
.customize-popup-confirm img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
/* Shared profile popup styles (copied from room-lobby baseline) */
|
||||
.room-lobby-profile-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10060;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
|
||||
}
|
||||
.room-lobby-profile-overlay.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-profile-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.room-lobby-profile-dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(
|
||||
1701px,
|
||||
calc(100vw - 24px),
|
||||
calc((100dvh - 24px) * (1701 / 951))
|
||||
);
|
||||
height: auto;
|
||||
aspect-ratio: 1701 / 951;
|
||||
max-height: calc(100dvh - 24px);
|
||||
padding: 0;
|
||||
background: url(../img/03-6-Profile/profile-pop-bg.png) center/100% 100% no-repeat;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
color: #eaf8ff;
|
||||
box-sizing: border-box;
|
||||
--rp-scale: 1;
|
||||
--rp-close-hit-size: calc(62px * var(--rp-scale));
|
||||
--rp-close-hit-top: 9.5%;
|
||||
--rp-close-hit-right: 5.5%;
|
||||
}
|
||||
.room-lobby-profile-title {
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-profile-close {
|
||||
position: absolute;
|
||||
top: var(--rp-close-hit-top);
|
||||
right: var(--rp-close-hit-right);
|
||||
width: var(--rp-close-hit-size);
|
||||
height: var(--rp-close-hit-size);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: rgba(0, 255, 255, 0);
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
text-shadow: none;
|
||||
z-index: 3;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.room-lobby-profile-content {
|
||||
position: absolute;
|
||||
top: 17%;
|
||||
right: 7%;
|
||||
bottom: 11%;
|
||||
left: 7%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.3fr);
|
||||
gap: 0;
|
||||
align-items: stretch;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-left-pane,
|
||||
.room-lobby-profile-right-pane {
|
||||
border: 2px solid rgba(92, 233, 255, 0.65);
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
padding: clamp(10px, 1.4vw, 16px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-left-pane {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: calc(3px * var(--rp-scale)) calc(4px * var(--rp-scale)) calc(5px * var(--rp-scale));
|
||||
width: 100%;
|
||||
height: 97%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) calc(58px * var(--rp-scale));
|
||||
justify-items: center;
|
||||
align-items: stretch;
|
||||
row-gap: calc(10px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-inner-frame {
|
||||
width: min(100%, 100%);
|
||||
max-width: 100%;
|
||||
aspect-ratio: 621 / 626;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-width: 0;
|
||||
background: url(../img/03-6-Profile/profile-char-frame.png) center/100% 100% no-repeat;
|
||||
--pf-scale: var(--rp-scale, 1);
|
||||
font-size: calc(16px * var(--pf-scale));
|
||||
padding: calc(12px * var(--pf-scale)) calc(11px * var(--pf-scale)) calc(9px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
align-self: stretch;
|
||||
overflow: hidden;
|
||||
margin-top: -1.5%;
|
||||
}
|
||||
.room-lobby-profile-coins {
|
||||
margin: calc(13px * var(--pf-scale)) 0 calc(10px * var(--pf-scale));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(8px * var(--pf-scale));
|
||||
font-size: calc(30px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
.room-lobby-profile-coins strong {
|
||||
color: #fff;
|
||||
font-size: calc(48px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.room-lobby-profile-coins-icon {
|
||||
width: calc(30px * var(--pf-scale));
|
||||
height: calc(30px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-coins-label {
|
||||
height: calc(62px * var(--pf-scale));
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
margin-top: calc(6px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-char-card {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: calc(3px * var(--pf-scale));
|
||||
background: transparent;
|
||||
padding: calc(4px * var(--pf-scale)) 0 calc(6px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.room-lobby-profile-char-card #room-lobby-profile-overlay-avatar {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
max-height: 83%;
|
||||
margin-top: calc(-28px * var(--pf-scale));
|
||||
margin-bottom: calc(-9px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-overlay-name {
|
||||
margin-top: 0;
|
||||
font-size: calc(38px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.room-lobby-profile-overlay-name-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: calc(6px * var(--pf-scale));
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-left: calc(92px * var(--pf-scale));
|
||||
padding-right: calc(92px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
margin-top: calc(-6px * var(--pf-scale));
|
||||
flex-shrink: 0;
|
||||
min-height: calc(70px * var(--pf-scale));
|
||||
overflow: visible;
|
||||
}
|
||||
.room-lobby-profile-edit-btn {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 4%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: calc(88px * var(--pf-scale));
|
||||
height: calc(88px * var(--pf-scale));
|
||||
aspect-ratio: 1 / 1;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-lobby-profile-edit-btn img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-name-sep {
|
||||
margin-top: calc(-10px * var(--pf-scale));
|
||||
width: min(100%, 505px);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.room-lobby-profile-overlay-agent {
|
||||
margin-top: calc(4px * var(--pf-scale));
|
||||
font-size: calc(18px * var(--pf-scale));
|
||||
color: #F856E2;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: calc(4px * var(--pf-scale));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.room-lobby-profile-logout {
|
||||
margin-top: 0;
|
||||
margin-bottom: 7%;
|
||||
width: min(50%, calc(256px * var(--rp-scale)));
|
||||
max-width: calc(256px * var(--rp-scale));
|
||||
aspect-ratio: 256 / 94;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 0;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.room-lobby-profile-logout img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-right-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
padding-top: calc(6px * var(--rp-scale));
|
||||
margin-left: calc(-32px * var(--rp-scale));
|
||||
min-width: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.room-lobby-profile-archiv-groups {
|
||||
--room-lobby-profile-archiv-overlap: calc(-38px * var(--rp-scale));
|
||||
--room-lobby-profile-archiv-group-scale: 1.031;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
padding-left: calc(45px * var(--rp-scale));
|
||||
height: calc(60px * var(--rp-scale));
|
||||
overflow: visible;
|
||||
transform: scale(var(--room-lobby-profile-archiv-group-scale));
|
||||
transform-origin: left bottom;
|
||||
margin-left: 1%;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn {
|
||||
width: calc(190px * var(--rp-scale));
|
||||
height: calc(60px * var(--rp-scale));
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn + .room-lobby-profile-archiv-group-btn {
|
||||
margin-left: var(--room-lobby-profile-archiv-overlap);
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-frame {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
width: min(100%, calc(872px * var(--rp-scale)));
|
||||
aspect-ratio: 872 / 515;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
background: url(../img/03-6-Profile/archiv-frame.png) center/contain no-repeat;
|
||||
padding: 4.8% 9.5% 3.8% 11.2%;
|
||||
box-sizing: border-box;
|
||||
margin-top: -4.5%;
|
||||
margin-left: 1%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-lobby-profile-achievements-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: calc(16px * var(--rp-scale));
|
||||
padding-right: calc(6px * var(--rp-scale));
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none;
|
||||
scrollbar-gutter: stable;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.room-lobby-profile-achievements-list::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.room-lobby-profile-archiv-item {
|
||||
--room-lobby-profile-archiv-icon-col-width: calc((131px + 14px) * var(--rp-scale));
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
min-height: calc(123px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-icon-bg {
|
||||
width: calc(131px * var(--rp-scale));
|
||||
height: calc(123px * var(--rp-scale));
|
||||
aspect-ratio: 131 / 123;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-main {
|
||||
--room-lobby-profile-archiv-title-progress-offset: calc(-8px * var(--rp-scale));
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
.room-lobby-profile-archiv-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: clamp(8px, 0.8vw, 12px);
|
||||
}
|
||||
.room-lobby-profile-archiv-name {
|
||||
width: calc(421px * var(--rp-scale));
|
||||
height: calc(72px * var(--rp-scale));
|
||||
max-width: 100%;
|
||||
aspect-ratio: 421 / 72;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-count {
|
||||
color: #ffe95a;
|
||||
font-size: calc(38px * var(--rp-scale));
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
}
|
||||
.room-lobby-profile-archiv-progress-wrap {
|
||||
position: relative;
|
||||
margin-top: var(--room-lobby-profile-archiv-title-progress-offset);
|
||||
width: calc(571px * var(--rp-scale));
|
||||
max-width: 100%;
|
||||
aspect-ratio: 571 / 56;
|
||||
height: calc(56px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-progress-bg,
|
||||
.room-lobby-profile-archiv-progress {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-progress {
|
||||
clip-path: inset(0 calc(100% - var(--archiv-progress, 0%)) 0 0);
|
||||
}
|
||||
.room-lobby-profile-archiv-line {
|
||||
width: calc(100% + var(--room-lobby-profile-archiv-icon-col-width));
|
||||
margin-left: calc(-1 * var(--room-lobby-profile-archiv-icon-col-width));
|
||||
height: calc(4px * var(--rp-scale));
|
||||
aspect-ratio: auto;
|
||||
object-fit: fill;
|
||||
display: block;
|
||||
margin-bottom: -3%;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
right: 4.6%;
|
||||
bottom: 8%;
|
||||
width: calc(13px * var(--rp-scale));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bg,
|
||||
.room-lobby-profile-archiv-scroll-bar {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
display: block;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bg {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background: #1b2740;
|
||||
border: 1px solid #22324f;
|
||||
box-shadow: inset 0 0 6px rgba(8, 13, 24, 0.45);
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bar {
|
||||
top: 0;
|
||||
height: 42%;
|
||||
min-height: 18%;
|
||||
background: #f04cdc;
|
||||
border: 1px solid #ffb8f5;
|
||||
box-shadow: 0 0 6px rgba(240, 76, 220, 0.55);
|
||||
}
|
||||
.room-lobby-profile-settings {
|
||||
position: relative;
|
||||
width: min(100%, calc(824px * var(--rp-scale)));
|
||||
aspect-ratio: 824 / 169;
|
||||
margin-top: auto;
|
||||
}
|
||||
.room-lobby-profile-settings-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
margin-left: 7%;
|
||||
margin-top: -4%;
|
||||
}
|
||||
.room-lobby-profile-settings-row {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0px calc(11px * var(--rp-scale)) 13px calc(163px * var(--rp-scale));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-setting-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-setting-label {
|
||||
width: calc(99px * var(--rp-scale));
|
||||
height: calc(39px * var(--rp-scale));
|
||||
aspect-ratio: 99 / 39;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-toggle {
|
||||
margin: 0;
|
||||
width: calc(152px * var(--rp-scale));
|
||||
height: calc(94px * var(--rp-scale));
|
||||
aspect-ratio: 152 / 94;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
margin-left: -11%;
|
||||
}
|
||||
.room-lobby-profile-toggle img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Profile popup: compact by width */
|
||||
@media (max-width: 1100px) {
|
||||
.room-lobby-profile-content {
|
||||
top: 13%;
|
||||
right: 4%;
|
||||
bottom: 8%;
|
||||
left: 4%;
|
||||
gap: clamp(8px, 1vw, 14px);
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.12fr);
|
||||
}
|
||||
.room-lobby-profile-left-pane {
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
row-gap: clamp(3px, 0.5vw, 6px);
|
||||
}
|
||||
.room-lobby-profile-logout {
|
||||
width: min(58%, 100%);
|
||||
}
|
||||
.room-lobby-profile-char-card #room-lobby-profile-overlay-avatar {
|
||||
width: 74%;
|
||||
max-width: 74%;
|
||||
max-height: 60%;
|
||||
}
|
||||
}
|
||||
/* Profile popup: compact by width (small) */
|
||||
@media (max-width: 900px) {
|
||||
.room-lobby-profile-content {
|
||||
top: 11%;
|
||||
right: 3%;
|
||||
bottom: 7%;
|
||||
left: 3%;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.08fr);
|
||||
}
|
||||
.room-lobby-profile-logout {
|
||||
width: min(62%, 100%);
|
||||
}
|
||||
.room-lobby-profile-char-card #room-lobby-profile-overlay-avatar {
|
||||
width: 64%;
|
||||
max-width: 64%;
|
||||
max-height: 56%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Profile popup: compact by height without breaking object ratios */
|
||||
@media (max-height: 700px) {
|
||||
.room-lobby-profile-content {
|
||||
top: 15%;
|
||||
right: 5.2%;
|
||||
bottom: 9%;
|
||||
left: 5.2%;
|
||||
gap: calc(10px * var(--rp-scale));
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.22fr);
|
||||
}
|
||||
.room-lobby-profile-right-pane {
|
||||
gap: calc(10px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-groups {
|
||||
--room-lobby-profile-archiv-group-scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 560px) {
|
||||
.room-lobby-profile-content {
|
||||
top: 13.5%;
|
||||
right: 4%;
|
||||
bottom: 7.5%;
|
||||
left: 4%;
|
||||
gap: calc(8px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-left-pane {
|
||||
row-gap: calc(6px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-logout {
|
||||
margin-bottom: 3.5%;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
* { box-sizing: border-box; }
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src:
|
||||
url('/Create%20Room/FONTS/NotoSansThai-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
body { font-family: sans-serif; margin: 0; background: #1a1b26; color: #c0caf5; }
|
||||
body.room-lobby-page,
|
||||
body.room-lobby-page * {
|
||||
font-family: 'Kanit', system-ui, sans-serif !important;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important;
|
||||
}
|
||||
.container { max-width: 720px; margin: 1rem auto; padding: 1rem; }
|
||||
|
||||
@@ -301,18 +309,16 @@ body.room-lobby--lobby-b .room-lobby-ready-fixed {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.room-lobby-top-right-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 0.45rem;
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-top-right-action-btn {
|
||||
width: 161px;
|
||||
height: 176px;
|
||||
min-width: 161px;
|
||||
min-height: 176px;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: clamp(100px, 11vw, 161px);
|
||||
height: auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
max-width: 161px;
|
||||
max-height: 176px;
|
||||
aspect-ratio: 161 / 176;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -321,14 +327,13 @@ body.room-lobby--lobby-b .room-lobby-ready-fixed {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
overflow: visible;
|
||||
isolation: isolate;
|
||||
}
|
||||
.room-lobby-top-right-action-btn img {
|
||||
width: 161px;
|
||||
height: 176px;
|
||||
min-width: 161px;
|
||||
min-height: 176px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 161px;
|
||||
max-height: 176px;
|
||||
aspect-ratio: 161 / 176;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
@@ -412,473 +417,8 @@ body.room-lobby--lobby-b .room-lobby-ready-fixed {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Profile overlay (open from btn-profile-set) */
|
||||
.room-lobby-profile-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10060;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
|
||||
}
|
||||
.room-lobby-profile-overlay.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-profile-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.room-lobby-profile-dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(
|
||||
1701px,
|
||||
calc(100vw - 24px),
|
||||
calc((100dvh - 24px) * (1701 / 951))
|
||||
);
|
||||
height: auto;
|
||||
aspect-ratio: 1701 / 951;
|
||||
max-height: calc(100dvh - 24px);
|
||||
padding: 0;
|
||||
background: url(../img/03-6-Profile/profile-pop-bg.png) center/100% 100% no-repeat;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
color: #eaf8ff;
|
||||
box-sizing: border-box;
|
||||
--rp-scale: 1;
|
||||
--rp-close-hit-size: calc(62px * var(--rp-scale));
|
||||
--rp-close-hit-top: 5.6%;
|
||||
--rp-close-hit-right: 2.9%;
|
||||
}
|
||||
.room-lobby-profile-title {
|
||||
display: none !important;
|
||||
}
|
||||
.room-lobby-profile-close {
|
||||
position: absolute;
|
||||
top: var(--rp-close-hit-top);
|
||||
right: var(--rp-close-hit-right);
|
||||
width: var(--rp-close-hit-size);
|
||||
height: var(--rp-close-hit-size);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: rgba(0, 255, 255, 0.5);
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
text-shadow: none;
|
||||
z-index: 3;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.room-lobby-profile-content {
|
||||
position: absolute;
|
||||
top: 17%;
|
||||
right: 7%;
|
||||
bottom: 11%;
|
||||
left: 7%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.3fr);
|
||||
gap: 0;
|
||||
align-items: stretch;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-left-pane,
|
||||
.room-lobby-profile-right-pane {
|
||||
border: 2px solid rgba(92, 233, 255, 0.65);
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
padding: clamp(10px, 1.4vw, 16px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-left-pane {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: calc(3px * var(--rp-scale)) calc(4px * var(--rp-scale)) calc(5px * var(--rp-scale));
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) calc(58px * var(--rp-scale));
|
||||
justify-items: center;
|
||||
align-items: stretch;
|
||||
row-gap: calc(10px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-inner-frame {
|
||||
width: min(100%, 98%);
|
||||
max-width: 100%;
|
||||
aspect-ratio: 621 / 626;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-width: 0;
|
||||
background: url(../img/03-6-Profile/profile-char-frame.png) center/100% 100% no-repeat;
|
||||
--pf-scale: var(--rp-scale, 1);
|
||||
font-size: calc(16px * var(--pf-scale));
|
||||
padding: calc(12px * var(--pf-scale)) calc(11px * var(--pf-scale)) calc(9px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
align-self: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
.room-lobby-profile-coins {
|
||||
margin: calc(10px * var(--pf-scale)) 0 calc(10px * var(--pf-scale));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(8px * var(--pf-scale));
|
||||
font-size: calc(30px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
.room-lobby-profile-coins strong {
|
||||
color: #fff;
|
||||
font-size: calc(48px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.room-lobby-profile-coins-icon {
|
||||
width: calc(30px * var(--pf-scale));
|
||||
height: calc(30px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-coins-label {
|
||||
height: calc(62px * var(--pf-scale));
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
margin-top: calc(6px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-char-card {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: calc(3px * var(--pf-scale));
|
||||
background: transparent;
|
||||
padding: calc(4px * var(--pf-scale)) 0 calc(6px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.room-lobby-profile-char-card #room-lobby-profile-overlay-avatar {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
max-height: 83%;
|
||||
margin-top: calc(-28px * var(--pf-scale));
|
||||
margin-bottom: calc(-9px * var(--pf-scale));
|
||||
}
|
||||
.room-lobby-profile-overlay-name {
|
||||
margin-top: 0;
|
||||
font-size: calc(38px * var(--pf-scale));
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.room-lobby-profile-overlay-name-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: calc(6px * var(--pf-scale));
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-left: calc(92px * var(--pf-scale));
|
||||
padding-right: calc(92px * var(--pf-scale));
|
||||
box-sizing: border-box;
|
||||
margin-top: calc(-6px * var(--pf-scale));
|
||||
flex-shrink: 0;
|
||||
min-height: calc(70px * var(--pf-scale));
|
||||
overflow: visible;
|
||||
}
|
||||
.room-lobby-profile-edit-btn {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 4%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: calc(88px * var(--pf-scale));
|
||||
height: calc(88px * var(--pf-scale));
|
||||
aspect-ratio: 1 / 1;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-lobby-profile-edit-btn img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-name-sep {
|
||||
margin-top: calc(-10px * var(--pf-scale));
|
||||
width: min(100%, 505px);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.room-lobby-profile-overlay-agent {
|
||||
margin-top: calc(4px * var(--pf-scale));
|
||||
font-size: calc(18px * var(--pf-scale));
|
||||
color: #F856E2;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-bottom: calc(4px * var(--pf-scale));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.room-lobby-profile-logout {
|
||||
margin-top: 0;
|
||||
width: min(50%, calc(256px * var(--rp-scale)));
|
||||
max-width: calc(256px * var(--rp-scale));
|
||||
aspect-ratio: 256 / 94;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 0;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.room-lobby-profile-logout img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-right-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
padding-top: calc(6px * var(--rp-scale));
|
||||
margin-left: calc(-32px * var(--rp-scale));
|
||||
min-width: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.room-lobby-profile-archiv-groups {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
padding-left: calc(30px * var(--rp-scale));
|
||||
height: calc(60px * var(--rp-scale));
|
||||
overflow: visible;
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn {
|
||||
width: calc(190px * var(--rp-scale));
|
||||
height: calc(60px * var(--rp-scale));
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn + .room-lobby-profile-archiv-group-btn {
|
||||
margin-left: calc(-14px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-group-btn img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-frame {
|
||||
position: relative;
|
||||
width: min(100%, calc(872px * var(--rp-scale)));
|
||||
aspect-ratio: 872 / 515;
|
||||
max-height: 100%;
|
||||
background: url(../img/03-6-Profile/archiv-frame.png) center/100% 100% no-repeat;
|
||||
padding: 4.8% 6.5% 5.8% 4.2%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-achievements-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(12px * var(--rp-scale));
|
||||
overflow: hidden;
|
||||
}
|
||||
.room-lobby-profile-archiv-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
min-height: 0;
|
||||
}
|
||||
.room-lobby-profile-archiv-icon-bg {
|
||||
width: calc(131px * var(--rp-scale));
|
||||
height: calc(123px * var(--rp-scale));
|
||||
aspect-ratio: 131 / 123;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-main {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(6px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: clamp(8px, 0.8vw, 12px);
|
||||
}
|
||||
.room-lobby-profile-archiv-name {
|
||||
width: calc(421px * var(--rp-scale));
|
||||
height: calc(72px * var(--rp-scale));
|
||||
max-width: 100%;
|
||||
aspect-ratio: 421 / 72;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-count {
|
||||
color: #ffe95a;
|
||||
font-size: calc(38px * var(--rp-scale));
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
}
|
||||
.room-lobby-profile-archiv-progress-wrap {
|
||||
position: relative;
|
||||
width: calc(571px * var(--rp-scale));
|
||||
max-width: 100%;
|
||||
aspect-ratio: 571 / 56;
|
||||
height: calc(56px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-archiv-progress-bg,
|
||||
.room-lobby-profile-archiv-progress {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-progress {
|
||||
clip-path: inset(0 calc(100% - var(--archiv-progress, 0%)) 0 0);
|
||||
}
|
||||
.room-lobby-profile-archiv-line {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll {
|
||||
position: absolute;
|
||||
top: 13%;
|
||||
right: 1.6%;
|
||||
bottom: 7%;
|
||||
width: calc(15px * var(--rp-scale));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bg,
|
||||
.room-lobby-profile-archiv-scroll-bar {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bg {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.room-lobby-profile-archiv-scroll-bar {
|
||||
top: 8%;
|
||||
height: 42%;
|
||||
}
|
||||
.room-lobby-profile-settings {
|
||||
position: relative;
|
||||
width: min(100%, calc(824px * var(--rp-scale)));
|
||||
aspect-ratio: 824 / 169;
|
||||
margin-top: auto;
|
||||
}
|
||||
.room-lobby-profile-settings-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-settings-row {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 calc(70px * var(--rp-scale)) 0 calc(90px * var(--rp-scale));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.room-lobby-profile-setting-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: calc(14px * var(--rp-scale));
|
||||
}
|
||||
.room-lobby-profile-setting-label {
|
||||
width: calc(99px * var(--rp-scale));
|
||||
height: calc(39px * var(--rp-scale));
|
||||
aspect-ratio: 99 / 39;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.room-lobby-profile-toggle {
|
||||
margin: 0;
|
||||
width: calc(152px * var(--rp-scale));
|
||||
height: calc(94px * var(--rp-scale));
|
||||
aspect-ratio: 152 / 94;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.room-lobby-profile-toggle img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
/* Profile overlay — see profile-popup.css (linked from room-lobby.html) */
|
||||
|
||||
|
||||
/* Host Console popup */
|
||||
.room-lobby-host-console-overlay {
|
||||
@@ -2166,21 +1706,29 @@ body.room-lobby--quiz-active .lobby-b-extra-row {
|
||||
bottom: max(0.65rem, env(safe-area-inset-bottom));
|
||||
right: max(0.85rem, env(safe-area-inset-right));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
/* แถวเดียว: อันดับ | ไมค์ | หลักฐาน (ตรง production) */
|
||||
.room-lobby-br-fixed .lobby-b-extra-row:not(.is-hidden) {
|
||||
display: contents;
|
||||
}
|
||||
.room-lobby-br-fixed .lobby-b-extra-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
gap: 0.6rem;
|
||||
order: 1;
|
||||
position: static !important;
|
||||
margin: 0 !important;
|
||||
transform: none !important;
|
||||
z-index: auto;
|
||||
}
|
||||
.room-lobby-br-fixed #lobby-b-btn-rank {
|
||||
order: 1;
|
||||
}
|
||||
.room-lobby-br-fixed #btn-voice {
|
||||
order: 2;
|
||||
}
|
||||
.room-lobby-br-fixed #lobby-b-btn-evidence {
|
||||
order: 3;
|
||||
}
|
||||
.room-lobby-br-fixed .btn-voice-icon {
|
||||
order: 2;
|
||||
@@ -2188,13 +1736,20 @@ body.room-lobby--quiz-active .lobby-b-extra-row {
|
||||
margin: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
.room-lobby-br-fixed .lobby-b-extra-row {
|
||||
z-index: auto;
|
||||
.room-lobby-br-fixed .lobby-b-sq-btn,
|
||||
.room-lobby-br-fixed .btn-voice-icon {
|
||||
width: var(--lobby-br-btn);
|
||||
height: var(--lobby-br-btn);
|
||||
min-width: var(--lobby-br-btn);
|
||||
min-height: var(--lobby-br-btn);
|
||||
max-width: var(--lobby-br-btn);
|
||||
max-height: var(--lobby-br-btn);
|
||||
}
|
||||
.room-lobby-chat-peers.chat-panel-collapsed.chat-panel {
|
||||
width: 122px;
|
||||
height: 120px;
|
||||
bottom: 12rem;
|
||||
right: max(0.85rem, env(safe-area-inset-right));
|
||||
}
|
||||
.room-lobby-chat-peers.chat-panel-collapsed .chat-close-btn {
|
||||
width: 122px;
|
||||
@@ -2884,7 +2439,7 @@ body.room-lobby--quiz-active .lobby-b-extra-row {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* ซ่อนชั่วคราว: ปุ่มตั้งค่าโฮสต์ + ปรับแต่ง (มุมขวาบน) */
|
||||
/* design 2026-05: แสดงปุ่มตั้งค่าโฮสต์ + ปรับแต่ง มุมขวาบน (ตาม TryChangeCSS/CSS ใหม่ — ไม่ใช้ JS เก่า) */
|
||||
body.room-lobby-page .room-lobby-top-right-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
LOCALHOST ONLY — does not change production pages.
|
||||
|
||||
1) Edit YOUR LOCAL file (not production server):
|
||||
C:\AppServ\www\srv1361159.hstgr.cloud\Game\room-lobby.html
|
||||
|
||||
2) Find the first <script ...> near end of file (before app-base.js).
|
||||
|
||||
3) Insert this line ABOVE app-base.js:
|
||||
|
||||
<script src="https://srv1361159.hstgr.cloud/Game/dev-local/bootstrap.js"></script>
|
||||
|
||||
4) Save. Ctrl+Shift+R on room-lobby.
|
||||
|
||||
Production https://srv1361159.hstgr.cloud/ must NOT include this script tag.
|
||||
|
||||
Create Room (local): same bootstrap in Create Room\index.html if needed.
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* LOCALHOST ONLY — do NOT add this script to production room-lobby.html
|
||||
* AppServ: add before other scripts in your LOCAL copy of room-lobby.html:
|
||||
* <script src="https://srv1361159.hstgr.cloud/Game/dev-local/bootstrap.js"></script>
|
||||
*/
|
||||
(function (g) {
|
||||
'use strict';
|
||||
var h = (g.location && g.location.hostname || '').toLowerCase();
|
||||
if (h !== 'localhost' && h !== '127.0.0.1') return;
|
||||
|
||||
var PROD = 'https://srv1361159.hstgr.cloud';
|
||||
var PROD_GAME = PROD + '/Game';
|
||||
|
||||
g.GAME_NODE_ORIGIN = PROD;
|
||||
g.GAME_SERVER = PROD;
|
||||
g.GAME_API_FETCH_BASE = PROD_GAME;
|
||||
g.__JD_GAME_API_BASE__ = PROD_GAME;
|
||||
|
||||
try {
|
||||
if (g.localStorage.getItem('isLoggedIn') !== 'true') {
|
||||
g.localStorage.setItem('isLoggedIn', 'true');
|
||||
g.localStorage.setItem('loginType', 'guest');
|
||||
}
|
||||
if (!(g.localStorage.getItem('playerName') || '').trim()) {
|
||||
g.localStorage.setItem('playerName', 'DevTester');
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
if (typeof g.fetch === 'function' && !g.__JD_DEV_LOCAL_FETCH__) {
|
||||
g.__JD_DEV_LOCAL_FETCH__ = 1;
|
||||
var nativeFetch = g.fetch.bind(g);
|
||||
var bad = /^https?:\/\/(?:localhost|127\.0\.0\.1):(3004|3001|3010|13010)\/Game/i;
|
||||
g.fetch = function (input, init) {
|
||||
if (typeof input === 'string' && bad.test(input)) {
|
||||
input = PROD_GAME + input.slice(input.indexOf('/Game') + 5);
|
||||
}
|
||||
return nativeFetch(input, init);
|
||||
};
|
||||
}
|
||||
|
||||
if (!g.io) {
|
||||
document.write('<script src="' + PROD + '/Game/socket.io/socket.io.js"><\/script>');
|
||||
}
|
||||
|
||||
g.jdDevLocalBootstrapLoaded = true;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
@@ -103,6 +103,7 @@
|
||||
<option value="cellImage">รูปบนกริด (อัปโหลด / เลือกจากคลัง)</option>
|
||||
<option value="interactive">โซน interactive (จุดกด/ใช้ได้)</option>
|
||||
<option value="customizeSpot">จุดห้องแต่งตัว (เดินไปกด F — คลิกซ้ายวาง / ขวาลบ)</option>
|
||||
<option value="hostConsoleSpot" id="draw-mode-option-host-console">จุดตั้งค่าโฮสต์ (Host กด F — คลิกซ้ายวาง / ขวาลบ)</option>
|
||||
<option value="spawnArea" id="draw-mode-option-spawn-area">พื้นที่สุ่มจุดเกิด (ฟ้า — ผู้เล่นใหม่สุ่มในช่องนี้)</option>
|
||||
<option value="lobbyPlayerSpawn" id="draw-mode-option-lobby-spawn" hidden>จุดเกิดผู้เล่น 1–6 (ลำดับเข้า P1…P6)</option>
|
||||
<option value="startGame" id="draw-mode-option-start-game">พื้นที่เริ่มเกม (host ยืนแล้วกดเริ่มได้)</option>
|
||||
@@ -399,7 +400,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/version.js?v=0.0258"></script>
|
||||
<script src="js/editor.js?v=0.0334"></script>
|
||||
<script src="js/editor.js?v=0.0335"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -3,14 +3,21 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Game</title>
|
||||
<title>Game — JD JUSTICE DIVERS</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Game</h1>
|
||||
<p><a href="character.html">เลือกตัวละคร</a> · <a href="lobby.html">ล็อบบี้ (สร้างห้อง/เข้าร่วม)</a> · <a href="game-timing-admin.html">ตั้งค่าเวลาเกม (Admin)</a></p>
|
||||
<p>
|
||||
<a href="/Login/">เข้าสู่ระบบ</a> ·
|
||||
<a href="/Main-Lobby/">Main Lobby</a> ·
|
||||
<a href="/Main-Menu/">เมนูหลัก (สร้าง/เข้าห้อง)</a> ·
|
||||
<a href="character.html">เลือกตัวละคร</a> ·
|
||||
<a href="game-timing-admin.html">ตั้งค่าเวลาเกม (Admin)</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="/app-base.js?v=2"></script>
|
||||
<script src="js/version.js?v=0.0104"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
var DISPLAY_NAME_KEY = 'roomLobbyDisplayName';
|
||||
var PLAYER_NAME_KEY = 'playerName';
|
||||
var MAX_LEN = 24;
|
||||
|
||||
function sanitizeDisplayName(raw) {
|
||||
var s = String(raw == null ? '' : raw).trim();
|
||||
if (!s) return '';
|
||||
try {
|
||||
s = s.normalize('NFKC');
|
||||
} catch (e) { /* ignore */ }
|
||||
s = s.replace(/[\x00-\x1f\x7f]/g, '');
|
||||
if (s.length > MAX_LEN) s = s.slice(0, MAX_LEN);
|
||||
return s;
|
||||
}
|
||||
|
||||
function readDisplayName() {
|
||||
try {
|
||||
return (
|
||||
localStorage.getItem(DISPLAY_NAME_KEY) ||
|
||||
localStorage.getItem(PLAYER_NAME_KEY) ||
|
||||
'MONE'
|
||||
).trim() || 'MONE';
|
||||
} catch (e) {
|
||||
return 'MONE';
|
||||
}
|
||||
}
|
||||
|
||||
function syncDisplayNameDom(displayName) {
|
||||
var text = String(displayName || '').trim();
|
||||
if (!text) return;
|
||||
var upper = text.toUpperCase();
|
||||
var ids = [
|
||||
'lobby-profile-name',
|
||||
'room-lobby-profile-overlay-name',
|
||||
'room-lobby-profile-name'
|
||||
];
|
||||
ids.forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.textContent = upper;
|
||||
});
|
||||
try {
|
||||
document.querySelectorAll('[data-jd-display-name]').forEach(function (el) {
|
||||
el.textContent = upper;
|
||||
});
|
||||
} catch (e2) { /* ignore */ }
|
||||
}
|
||||
|
||||
function replaceNickQueryParam(displayName) {
|
||||
try {
|
||||
var u = new URL(global.location.href);
|
||||
if (!u.searchParams.has('nick')) return;
|
||||
u.searchParams.set('nick', displayName);
|
||||
global.history.replaceState({}, '', u.pathname + u.search + u.hash);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} nextName
|
||||
* @param {{ socket?: object, silent?: boolean }} [options]
|
||||
* @returns {{ ok: boolean, displayName?: string, error?: string }}
|
||||
*/
|
||||
function applyDisplayNameChange(nextName, options) {
|
||||
var safe = sanitizeDisplayName(nextName);
|
||||
if (!safe) return { ok: false, error: 'empty' };
|
||||
var opts = options || {};
|
||||
try {
|
||||
localStorage.setItem(DISPLAY_NAME_KEY, safe);
|
||||
localStorage.setItem(PLAYER_NAME_KEY, safe);
|
||||
} catch (e) { /* ignore */ }
|
||||
syncDisplayNameDom(safe);
|
||||
replaceNickQueryParam(safe);
|
||||
try {
|
||||
global.dispatchEvent(new CustomEvent('jd-display-name-changed', {
|
||||
detail: { displayName: safe }
|
||||
}));
|
||||
} catch (e2) { /* ignore */ }
|
||||
var sock = opts.socket || global.__jdGameSocket || null;
|
||||
if (sock && typeof sock.emit === 'function' && sock.connected) {
|
||||
sock.emit('update-display-name', { nickname: safe }, function (res) {
|
||||
if (res && res.ok && res.nickname) {
|
||||
try {
|
||||
localStorage.setItem(DISPLAY_NAME_KEY, res.nickname);
|
||||
localStorage.setItem(PLAYER_NAME_KEY, res.nickname);
|
||||
} catch (e3) { /* ignore */ }
|
||||
syncDisplayNameDom(res.nickname);
|
||||
}
|
||||
});
|
||||
}
|
||||
return { ok: true, displayName: safe };
|
||||
}
|
||||
|
||||
var editorReady = false;
|
||||
|
||||
function ensureNameEditorDom() {
|
||||
if (editorReady && document.getElementById('jd-display-name-editor-overlay')) return;
|
||||
if (!document.getElementById('jd-display-name-editor-style')) {
|
||||
var st = document.createElement('style');
|
||||
st.id = 'jd-display-name-editor-style';
|
||||
st.textContent = '' +
|
||||
'.jd-dne-overlay{position:fixed;inset:0;z-index:10080;display:flex;align-items:center;justify-content:center;padding:16px;}' +
|
||||
'.jd-dne-overlay.is-hidden{display:none!important;}' +
|
||||
'.jd-dne-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.72);}' +
|
||||
'.jd-dne-dialog{position:relative;z-index:1;width:min(420px,calc(100vw - 32px));padding:22px 20px 18px;border-radius:14px;' +
|
||||
'background:linear-gradient(180deg,#0d2848 0%,#061528 100%);border:2px solid rgba(56,232,255,.85);box-shadow:0 0 28px rgba(56,232,255,.35);color:#eaf8ff;font-family:Kanit,Segoe UI,sans-serif;}' +
|
||||
'.jd-dne-title{margin:0 0 14px;font-size:1.25rem;font-weight:700;text-align:center;letter-spacing:.02em;}' +
|
||||
'.jd-dne-input{display:block;width:100%;box-sizing:border-box;padding:12px 14px;border-radius:10px;border:2px solid rgba(56,232,255,.55);' +
|
||||
'background:rgba(0,20,40,.85);color:#fff;font-size:1.05rem;font-family:inherit;outline:none;}' +
|
||||
'.jd-dne-input:focus{border-color:#5ce9ff;box-shadow:0 0 0 3px rgba(92,233,255,.25);}' +
|
||||
'.jd-dne-hint{margin:8px 0 0;font-size:.82rem;opacity:.75;text-align:center;}' +
|
||||
'.jd-dne-error{margin:8px 0 0;font-size:.85rem;color:#ff8a8a;text-align:center;min-height:1.1em;}' +
|
||||
'.jd-dne-actions{display:flex;gap:10px;margin-top:18px;justify-content:center;}' +
|
||||
'.jd-dne-btn{min-width:110px;padding:10px 16px;border-radius:10px;border:2px solid transparent;font-size:1rem;font-weight:600;font-family:inherit;cursor:pointer;}' +
|
||||
'.jd-dne-btn--cancel{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.25);color:#e2e8f0;}' +
|
||||
'.jd-dne-btn--confirm{background:linear-gradient(180deg,#2ee6ff,#0ea5c9);border-color:#7df4ff;color:#042033;}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
if (!document.getElementById('jd-display-name-editor-overlay')) {
|
||||
var wrap = document.createElement('div');
|
||||
wrap.id = 'jd-display-name-editor-overlay';
|
||||
wrap.className = 'jd-dne-overlay is-hidden';
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.innerHTML = '' +
|
||||
'<div class="jd-dne-backdrop" id="jd-dne-backdrop" aria-hidden="true"></div>' +
|
||||
'<div class="jd-dne-dialog" role="dialog" aria-modal="true" aria-labelledby="jd-dne-title">' +
|
||||
' <h2 class="jd-dne-title" id="jd-dne-title">แก้ไขชื่อผู้เล่น</h2>' +
|
||||
' <input type="text" id="jd-dne-input" class="jd-dne-input" maxlength="24" autocomplete="nickname" aria-label="ชื่อผู้เล่น">' +
|
||||
' <p class="jd-dne-hint">สูงสุด 24 ตัวอักษร — ชื่อนี้จะแสดงในเกม</p>' +
|
||||
' <p class="jd-dne-error" id="jd-dne-error" role="alert"></p>' +
|
||||
' <div class="jd-dne-actions">' +
|
||||
' <button type="button" class="jd-dne-btn jd-dne-btn--cancel" id="jd-dne-cancel">ยกเลิก</button>' +
|
||||
' <button type="button" class="jd-dne-btn jd-dne-btn--confirm" id="jd-dne-confirm">ยืนยัน</button>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(wrap);
|
||||
}
|
||||
editorReady = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* เปิดกล่องแก้ชื่อ (ใช้แทน window.prompt — ทำงานใน Cursor browser / mobile)
|
||||
* @param {{ initialValue?: string, title?: string }} [options]
|
||||
* @returns {Promise<string|null>} ชื่อใหม่ หรือ null ถ้ายกเลิก
|
||||
*/
|
||||
function openDisplayNameEditor(options) {
|
||||
ensureNameEditorDom();
|
||||
var opts = options || {};
|
||||
var overlay = document.getElementById('jd-display-name-editor-overlay');
|
||||
var input = document.getElementById('jd-dne-input');
|
||||
var errEl = document.getElementById('jd-dne-error');
|
||||
var btnOk = document.getElementById('jd-dne-confirm');
|
||||
var btnCancel = document.getElementById('jd-dne-cancel');
|
||||
var backdrop = document.getElementById('jd-dne-backdrop');
|
||||
var titleEl = document.getElementById('jd-dne-title');
|
||||
if (!overlay || !input || !btnOk || !btnCancel) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
var settled = false;
|
||||
function finish(value) {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
overlay.classList.add('is-hidden');
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
document.removeEventListener('keydown', onDocKey, true);
|
||||
resolve(value);
|
||||
}
|
||||
|
||||
function onDocKey(e) {
|
||||
if (overlay.classList.contains('is-hidden')) return;
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
finish(null);
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (errEl) errEl.textContent = '';
|
||||
var safe = sanitizeDisplayName(input.value);
|
||||
if (!safe) {
|
||||
if (errEl) errEl.textContent = 'กรุณากรอกชื่อผู้เล่น';
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
finish(safe);
|
||||
}
|
||||
|
||||
if (titleEl && opts.title) titleEl.textContent = opts.title;
|
||||
input.value = opts.initialValue != null ? String(opts.initialValue) : readDisplayName();
|
||||
if (errEl) errEl.textContent = '';
|
||||
overlay.classList.remove('is-hidden');
|
||||
overlay.setAttribute('aria-hidden', 'false');
|
||||
document.addEventListener('keydown', onDocKey, true);
|
||||
|
||||
btnOk.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
};
|
||||
btnCancel.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
finish(null);
|
||||
};
|
||||
backdrop.onclick = function () {
|
||||
finish(null);
|
||||
};
|
||||
input.onkeydown = function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
input.focus();
|
||||
input.select();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* เปิด editor แล้ว apply ชื่อ (socket sync ถ้ามี)
|
||||
* @param {{ socket?: object }} [options]
|
||||
* @returns {Promise<{ ok: boolean, displayName?: string, cancelled?: boolean }>}
|
||||
*/
|
||||
function promptAndApplyDisplayName(options) {
|
||||
var opts = options || {};
|
||||
return openDisplayNameEditor({ initialValue: readDisplayName() }).then(function (draft) {
|
||||
if (draft == null) return { ok: false, cancelled: true };
|
||||
return applyDisplayNameChange(draft, opts);
|
||||
});
|
||||
}
|
||||
|
||||
global.jdDisplayName = {
|
||||
key: DISPLAY_NAME_KEY,
|
||||
read: readDisplayName,
|
||||
sanitize: sanitizeDisplayName,
|
||||
syncDom: syncDisplayNameDom,
|
||||
apply: applyDisplayNameChange,
|
||||
openEditor: openDisplayNameEditor,
|
||||
promptAndApply: promptAndApplyDisplayName
|
||||
};
|
||||
global.jdApplyDisplayNameChange = applyDisplayNameChange;
|
||||
global.jdReadDisplayName = readDisplayName;
|
||||
global.jdOpenDisplayNameEditor = openDisplayNameEditor;
|
||||
global.jdPromptAndApplyDisplayName = promptAndApplyDisplayName;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
@@ -131,6 +131,7 @@
|
||||
let balloonBossPlayerSlots = [];
|
||||
let balloonBossBossSpawn = null;
|
||||
let customizeSpot = null; // จุด "ห้องแต่งตัว" ในฉาก (เดินไปกด F) — วางเองในeditor
|
||||
let hostConsoleSpot = null; // จุด "ตั้งค่าโฮสต์" (Host Console) — วางใน editor
|
||||
let showMapInGame = true;
|
||||
let backgroundImage = null;
|
||||
let backgroundImageImg = null;
|
||||
@@ -2527,6 +2528,19 @@
|
||||
ctx.fillText('แต่งตัว', tx + tileSize / 2, ty + tileSize / 2 + 4);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
if (hostConsoleSpot && hostConsoleSpot.x === x && hostConsoleSpot.y === y) {
|
||||
ctx.fillStyle = 'rgba(236, 72, 153, 0.42)';
|
||||
ctx.fillRect(tx + 2, ty + 2, tileSize - 4, tileSize - 4);
|
||||
ctx.strokeStyle = 'rgba(255, 140, 200, 0.95)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(tx + 2, ty + 2, tileSize - 4, tileSize - 4);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = '#fff0f8';
|
||||
ctx.font = 'bold 9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('โฮสต์', tx + tileSize / 2, ty + tileSize / 2 + 4);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
if (gtDraw === 'quiz_carry') {
|
||||
if (quizQuestionArea[y] && quizQuestionArea[y][x] === 1) {
|
||||
ctx.fillStyle = 'rgba(255, 214, 102, 0.44)';
|
||||
@@ -3097,6 +3111,19 @@
|
||||
if (statusEl) statusEl.textContent = 'ลบจุดห้องแต่งตัว';
|
||||
}
|
||||
return;
|
||||
} else if (drawModeEl.value === 'hostConsoleSpot') {
|
||||
if (objects[y][x] === 1) {
|
||||
if (statusEl) statusEl.textContent = 'ช่องกำแพง — ใช้เป็นจุดตั้งค่าโฮสต์ไม่ได้';
|
||||
return;
|
||||
}
|
||||
if (left) {
|
||||
hostConsoleSpot = { x: x, y: y };
|
||||
if (statusEl) statusEl.textContent = 'จุดตั้งค่าโฮสต์ → ช่อง (' + x + ',' + y + ')';
|
||||
} else {
|
||||
hostConsoleSpot = null;
|
||||
if (statusEl) statusEl.textContent = 'ลบจุดตั้งค่าโฮสต์';
|
||||
}
|
||||
return;
|
||||
} else if (drawModeEl.value === 'quizBattlePath') {
|
||||
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
|
||||
ensureQuizBattlePathArea();
|
||||
@@ -3730,6 +3757,9 @@
|
||||
customizeSpot: customizeSpot && Number.isFinite(customizeSpot.x)
|
||||
? { x: Math.floor(customizeSpot.x), y: Math.floor(customizeSpot.y) }
|
||||
: null,
|
||||
hostConsoleSpot: hostConsoleSpot && Number.isFinite(hostConsoleSpot.x)
|
||||
? { x: Math.floor(hostConsoleSpot.x), y: Math.floor(hostConsoleSpot.y) }
|
||||
: null,
|
||||
gridImageLibrary: gridImageLibrary.map(serializeGridLibEntry).filter((x) => x != null),
|
||||
gridImageSprites: gridImageSprites.map((s) => {
|
||||
const o = { i: s.i, x: s.x, y: s.y, w: s.w, h: s.h };
|
||||
@@ -4035,6 +4065,11 @@
|
||||
} else {
|
||||
customizeSpot = null;
|
||||
}
|
||||
if (m.hostConsoleSpot && Number.isFinite(Number(m.hostConsoleSpot.x)) && Number.isFinite(Number(m.hostConsoleSpot.y))) {
|
||||
hostConsoleSpot = { x: Math.floor(Number(m.hostConsoleSpot.x)), y: Math.floor(Number(m.hostConsoleSpot.y)) };
|
||||
} else {
|
||||
hostConsoleSpot = null;
|
||||
}
|
||||
stackReleaseArea = m.stackReleaseArea && m.stackReleaseArea.length ? m.stackReleaseArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
stackLandArea = m.stackLandArea && m.stackLandArea.length ? m.stackLandArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
cellColors = (m.cellColors && m.cellColors.length === height) ? m.cellColors.map(r => r && r.slice ? r.slice() : Array(width).fill(null)) : Array(height).fill(0).map(() => Array(width).fill(null));
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Game API / Socket routing
|
||||
* - Production: same-origin /Game/api (Nginx → Node)
|
||||
* - localhost AppServ: production https://srv1361159.hstgr.cloud/Game (ไม่ต้องรัน node บน Windows)
|
||||
*/
|
||||
(function (g) {
|
||||
'use strict';
|
||||
|
||||
var loc = g.location || {};
|
||||
var pageBase = typeof g.appPath === 'function' ? g.appPath('/Game') : '/Game';
|
||||
var PROD = 'https://srv1361159.hstgr.cloud';
|
||||
var PROD_GAME = PROD + '/Game';
|
||||
|
||||
function isLocalDevHost() {
|
||||
try {
|
||||
var h = (loc.hostname || '').toLowerCase();
|
||||
return h === 'localhost' || h === '127.0.0.1';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function useLocalNode() {
|
||||
try { return localStorage.getItem('jdUseLocalGameApi') === '1'; } catch (e) { return false; }
|
||||
}
|
||||
|
||||
function localApiBase() {
|
||||
var port = '13010';
|
||||
try {
|
||||
var p = localStorage.getItem('jdGameNodePort');
|
||||
if (p) port = String(p).replace(/\D/g, '') || port;
|
||||
} catch (e) { /* ignore */ }
|
||||
return 'http://127.0.0.1:' + port + '/Game';
|
||||
}
|
||||
|
||||
var localDev = isLocalDevHost();
|
||||
var apiBase = pageBase.replace(/\/$/, '');
|
||||
var nodeOrigin = '';
|
||||
|
||||
if (localDev) {
|
||||
if (useLocalNode()) {
|
||||
apiBase = localApiBase().replace(/\/$/, '');
|
||||
nodeOrigin = apiBase.replace(/\/Game\/?$/i, '');
|
||||
} else {
|
||||
apiBase = PROD_GAME;
|
||||
nodeOrigin = PROD;
|
||||
}
|
||||
}
|
||||
|
||||
g.GAME_HTTP_BASE = pageBase;
|
||||
g.GAME_API_FETCH_BASE = apiBase;
|
||||
g.GAME_NODE_ORIGIN = localDev ? nodeOrigin : (String(g.GAME_SERVER || '').replace(/\/$/, '') || loc.origin || '');
|
||||
if (localDev) g.GAME_SERVER = nodeOrigin;
|
||||
|
||||
g.ensureLocalDevLogin = function ensureLocalDevLogin() {
|
||||
if (!localDev) return;
|
||||
try {
|
||||
if (localStorage.getItem('isLoggedIn') !== 'true') {
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
localStorage.setItem('loginType', 'guest');
|
||||
}
|
||||
if (!(localStorage.getItem('playerName') || '').trim()) {
|
||||
localStorage.setItem('playerName', 'DevTester');
|
||||
}
|
||||
if (!(localStorage.getItem('roomLobbyDisplayName') || '').trim()) {
|
||||
localStorage.setItem('roomLobbyDisplayName', 'DevTester');
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
};
|
||||
|
||||
g.resolveGameApiBase = function resolveGameApiBase() {
|
||||
if (!localDev) return pageBase.replace(/\/$/, '');
|
||||
return String(g.GAME_API_FETCH_BASE || PROD_GAME).replace(/\/$/, '');
|
||||
};
|
||||
|
||||
if (!localDev || g.__GAME_HTTP_FETCH_PATCHED__ || typeof g.fetch !== 'function') return;
|
||||
|
||||
var nativeFetch = g.fetch.bind(g);
|
||||
var pageApiPrefix = pageBase.replace(/\/$/, '') + '/api';
|
||||
var directApiBase = apiBase.replace(/\/$/, '');
|
||||
var badPort = /https?:\/\/(?:localhost|127\.0\.0\.1):(3004|3001|3010|13010)\/Game/i;
|
||||
|
||||
g.fetch = function gameHttpFetch(input, init) {
|
||||
if (typeof input === 'string') {
|
||||
if (badPort.test(input)) {
|
||||
input = PROD_GAME + input.slice(input.indexOf('/Game') + 5);
|
||||
} else if (input.indexOf(pageApiPrefix) === 0) {
|
||||
input = directApiBase + input.slice(pageBase.length);
|
||||
} else if (input.indexOf('/Game/api') === 0) {
|
||||
input = directApiBase + input.slice('/Game'.length);
|
||||
}
|
||||
}
|
||||
return nativeFetch(input, init);
|
||||
};
|
||||
g.__GAME_HTTP_FETCH_PATCHED__ = true;
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
@@ -1,5 +1,21 @@
|
||||
(function () {
|
||||
const BASE = '/Game';
|
||||
function playDesignMainMenuHref() {
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined' && typeof globalThis.designMainMenuHref === 'function') {
|
||||
return globalThis.designMainMenuHref();
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return '/Main-Menu/';
|
||||
}
|
||||
function playDesignMainLobbyHref() {
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined' && typeof globalThis.designMainLobbyHref === 'function') {
|
||||
return globalThis.designMainLobbyHref();
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return '/Main-Lobby/';
|
||||
}
|
||||
/** รูปเลข 3–2–1 — เสิร์ฟที่ /Game/img/QUESTION/ (nginx alias /Game/ → public; อย่าใช้ /Game/public/img/) */
|
||||
const COUNTDOWN_321_IMG_BASE = BASE + '/img/QUESTION/';
|
||||
/** แผงคำถามบนแมป quiz_carry — ไฟล์อยู่ public/img/quiz-carry */
|
||||
@@ -11,7 +27,12 @@
|
||||
const QUIZ_MAP_CARRY_FEEDBACK_MS = 1400;
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const spaceId = params.get('space');
|
||||
const nick = params.get('nick') || 'ผู้เล่น';
|
||||
function readStoredDisplayName() {
|
||||
try {
|
||||
return (localStorage.getItem('roomLobbyDisplayName') || localStorage.getItem('playerName') || '').trim();
|
||||
} catch (e) { return ''; }
|
||||
}
|
||||
let nick = (params.get('nick') || '').trim() || readStoredDisplayName() || 'ผู้เล่น';
|
||||
const previewMode = params.get('preview') === '1';
|
||||
const forceDefaultCharacter = params.get('defaultChar') === '1';
|
||||
const editorEmbedReturn = params.get('editorEmbed') === '1';
|
||||
@@ -493,9 +514,10 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!spaceId) { window.location.replace(BASE + '/lobby.html'); return; }
|
||||
if (!spaceId) { window.location.replace(playDesignMainMenuHref()); return; }
|
||||
|
||||
const socket = io({ path: BASE + '/socket.io' });
|
||||
window.__jdGameSocket = socket;
|
||||
const canvas = document.getElementById('game-canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let mapData = null, tileSize = 32, myId = null;
|
||||
@@ -1613,8 +1635,8 @@
|
||||
const PLAY_LAYER_ORDER = ['shadow', 'bodyColor', 'bodyStroke', 'headColor', 'headStroke', 'hairColor', 'hairStroke', 'face'];
|
||||
const PLAY_LAYER_TINT_KEY = { bodyColor: 'body', headColor: 'head', hairColor: 'hair' };
|
||||
const PLAY_TINT_HEAD = ['#eaa78a', '#fbd5c4', '#fae9e1'];
|
||||
const PLAY_TINT_HAIR = ['#d72520', '#ef8508', '#efe237', '#5bb443', '#2585cb', '#3f4ead', '#b53fd6', '#ef62b9'];
|
||||
const PLAY_TINT_BODY = ['#fb4941', '#feaa11', '#fefe6d', '#adfd85', '#45fbfd', '#799afe', '#f87dff', '#fec1fe'];
|
||||
const PLAY_TINT_HAIR = ['#d72520', '#ef8508', '#efe237', '#5bb443', '#2585cb', '#3f4ead', '#b53fd6', '#fec1fe'];
|
||||
const PLAY_TINT_BODY = ['#d72520', '#ef8508', '#efe237', '#5bb443', '#2585cb', '#3f4ead', '#b53fd6', '#fec1fe'];
|
||||
const playLayerMode = {};
|
||||
/** คิว probe ครบ up/down/left/right แล้ว — กันทิศที่ไม่มีไฟล์ layer ไปตก fallback แถบทั้งทั้งที่ทิศอื่นมี */
|
||||
const playLayerAllDirsQueued = {};
|
||||
@@ -14425,6 +14447,10 @@
|
||||
me.y = pathSnap.y;
|
||||
}
|
||||
me.direction = myPeer?.direction ?? 'down';
|
||||
if (myPeer && myPeer.nickname) {
|
||||
me.nickname = String(myPeer.nickname).trim() || me.nickname;
|
||||
nick = me.nickname;
|
||||
}
|
||||
me.characterId = myPeer?.characterId || null;
|
||||
meGauntletJumpTicks = typeof myPeer?.gauntletJumpTicks === 'number' ? myPeer.gauntletJumpTicks : 0;
|
||||
meGauntletJumpVis = meGauntletJumpTicks;
|
||||
@@ -14746,9 +14772,12 @@
|
||||
socket.on('connect', () => {
|
||||
firstCharacterDefaultPromise.then(() => {
|
||||
const joinPlayMapId = (params.get('map') || '').trim();
|
||||
const joinNick = (typeof window.jdReadDisplayName === 'function' ? window.jdReadDisplayName() : null)
|
||||
|| readStoredDisplayName()
|
||||
|| nick;
|
||||
socket.emit('join-space', {
|
||||
spaceId,
|
||||
nickname: nick,
|
||||
nickname: joinNick,
|
||||
characterId: getPlayCharacterId(),
|
||||
playMapId: joinPlayMapId || undefined,
|
||||
}, (res) => {
|
||||
@@ -14759,7 +14788,7 @@
|
||||
if (caseLocked) {
|
||||
window.location.replace(buildRoomLobbyReturnHref());
|
||||
} else {
|
||||
window.location.replace(BASE + '/lobby.html');
|
||||
window.location.replace(playDesignMainMenuHref());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -14827,6 +14856,38 @@
|
||||
});
|
||||
|
||||
const LERP = 0.2;
|
||||
|
||||
function applyPlayDisplayNameUpdate(data) {
|
||||
if (!data || !data.id) return;
|
||||
const next = String(data.nickname || '').trim();
|
||||
if (!next) return;
|
||||
if (data.id === myId) {
|
||||
me.nickname = next;
|
||||
nick = next;
|
||||
try {
|
||||
localStorage.setItem('roomLobbyDisplayName', next);
|
||||
localStorage.setItem('playerName', next);
|
||||
} catch (e) { /* ignore */ }
|
||||
if (window.jdDisplayName && typeof window.jdDisplayName.syncDom === 'function') {
|
||||
window.jdDisplayName.syncDom(next);
|
||||
}
|
||||
} else {
|
||||
const o = others.get(data.id);
|
||||
if (o) o.nickname = next;
|
||||
}
|
||||
if (quizCarryPregameActive && isQuizCarry()) updateQuizCarryPregameHud();
|
||||
if (isQuizQuestionMissionUiMapPlay() && quizQuestionMissionPhase === 'howto') updateQuizQuestionMissionHowtoHud();
|
||||
if (typeof syncPlayCyberHud === 'function') syncPlayCyberHud();
|
||||
}
|
||||
|
||||
socket.on('peer-display-name', applyPlayDisplayNameUpdate);
|
||||
|
||||
window.addEventListener('jd-display-name-changed', function (ev) {
|
||||
const next = ev && ev.detail && ev.detail.displayName ? String(ev.detail.displayName).trim() : '';
|
||||
if (!next || !myId) return;
|
||||
applyPlayDisplayNameUpdate({ id: myId, nickname: next });
|
||||
});
|
||||
|
||||
socket.on('user-joined', (data) => {
|
||||
if (!data || isPreviewBotId(data.id)) return;
|
||||
const x = Number(data.x);
|
||||
@@ -18719,7 +18780,7 @@
|
||||
var edQ = '?id=' + encodeURIComponent(mid) + (editorEmbedReturn ? '&embed=1' : '');
|
||||
window.location.replace(BASE + '/editor.html' + edQ);
|
||||
} else {
|
||||
window.location.replace(BASE + '/lobby.html');
|
||||
window.location.replace(playDesignMainMenuHref());
|
||||
}
|
||||
});
|
||||
(function wireQuizBattleMcqModal() {
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var script = document.currentScript;
|
||||
var triggerSelector = (script && script.getAttribute('data-profile-trigger')) || '#btn-myprofile,#btn-profile-set';
|
||||
var assetBase = (script && script.getAttribute('data-profile-asset-base')) || 'img/03-6-Profile';
|
||||
var DISPLAY_NAME_KEY = 'roomLobbyDisplayName';
|
||||
var overlayId = 'room-lobby-profile-overlay';
|
||||
|
||||
if (typeof window !== 'undefined') window.__USE_SHARED_PROFILE_POPUP = true;
|
||||
|
||||
function resolveAssetBase(raw) {
|
||||
var base = String(raw || 'img/03-6-Profile').replace(/\/$/, '');
|
||||
if (/^\/(Game|img)\//i.test(base)) {
|
||||
return typeof window.appPath === 'function' ? window.appPath(base) : base;
|
||||
}
|
||||
try {
|
||||
return new URL(base + '/', window.location.href).pathname.replace(/\/$/, '');
|
||||
} catch (e) {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
var resolvedAssetBase = resolveAssetBase(assetBase);
|
||||
|
||||
function asset(name) {
|
||||
return resolvedAssetBase + '/' + name;
|
||||
}
|
||||
|
||||
function appRel(path) {
|
||||
if (typeof window.appPath === 'function') return window.appPath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
function readName() {
|
||||
try { return (localStorage.getItem(DISPLAY_NAME_KEY) || localStorage.getItem('playerName') || 'MONE').trim() || 'MONE'; } catch (e) { return 'MONE'; }
|
||||
}
|
||||
function readAgentId() {
|
||||
try { return (localStorage.getItem('agentDisplayId') || '001998').trim() || '001998'; } catch (e) { return '001998'; }
|
||||
}
|
||||
function readCoins() {
|
||||
try { return String(Math.max(0, parseInt(localStorage.getItem('jdCoins') || '0', 10) || 0)); } catch (e) { return '0'; }
|
||||
}
|
||||
|
||||
var refs = {
|
||||
overlay: null,
|
||||
backdrop: null,
|
||||
dialog: null,
|
||||
closeBtn: null,
|
||||
avatarEl: null,
|
||||
nameEl: null,
|
||||
agentEl: null,
|
||||
coinsEl: null,
|
||||
musicBtn: null,
|
||||
sfxBtn: null,
|
||||
groupBtns: [],
|
||||
archivListEl: null,
|
||||
archivScrollEl: null,
|
||||
archivScrollBarEl: null,
|
||||
editBtn: null,
|
||||
logoutBtn: null
|
||||
};
|
||||
|
||||
function createMarkupIfNeeded() {
|
||||
if (document.getElementById(overlayId)) return;
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = overlayId;
|
||||
overlay.className = 'room-lobby-profile-overlay is-hidden';
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
overlay.innerHTML = '' +
|
||||
'<div class="room-lobby-profile-backdrop" id="room-lobby-profile-backdrop" aria-hidden="true"></div>' +
|
||||
'<div class="room-lobby-profile-dialog" role="dialog" aria-modal="true" aria-label="โปรไฟล์ผู้เล่น">' +
|
||||
' <button type="button" class="room-lobby-profile-close" id="room-lobby-profile-close" aria-label="ปิดโปรไฟล์">×</button>' +
|
||||
' <div class="room-lobby-profile-content">' +
|
||||
' <section class="room-lobby-profile-left-pane">' +
|
||||
' <div class="room-lobby-profile-inner-frame">' +
|
||||
' <p class="room-lobby-profile-coins">' +
|
||||
' <img src="' + asset('profile-pop-coin.png') + '" alt="My Coins" class="room-lobby-profile-coins-label" decoding="async">' +
|
||||
' <strong id="room-lobby-profile-coins-val">150</strong>' +
|
||||
' </p>' +
|
||||
' <div class="room-lobby-profile-char-card">' +
|
||||
' <img id="room-lobby-profile-overlay-avatar" src="" alt="ตัวละครผู้เล่น" decoding="async">' +
|
||||
' <div class="room-lobby-profile-overlay-name-row">' +
|
||||
' <div class="room-lobby-profile-overlay-name" id="room-lobby-profile-overlay-name">PLAYER</div>' +
|
||||
' <button type="button" class="room-lobby-profile-edit-btn" id="room-lobby-profile-edit-btn" title="แก้ไขชื่อผู้เล่น" aria-label="แก้ไขชื่อผู้เล่น">' +
|
||||
' <img src="' + asset('btn-edit.png') + '" alt="" decoding="async">' +
|
||||
' </button>' +
|
||||
' </div>' +
|
||||
' <img src="' + asset('profile-char-frame-2.png') + '" alt="" class="room-lobby-profile-name-sep" decoding="async">' +
|
||||
' <div class="room-lobby-profile-overlay-agent" id="room-lobby-profile-overlay-agent">AGENT ID : 001998</div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' <button type="button" class="room-lobby-profile-logout" id="room-lobby-profile-logout" aria-label="ออกจากระบบ">' +
|
||||
' <img src="' + asset('btn-ex.png') + '" alt="ออกจากระบบ" decoding="async">' +
|
||||
' </button>' +
|
||||
' </section>' +
|
||||
' <section class="room-lobby-profile-right-pane">' +
|
||||
' <div class="room-lobby-profile-archiv-groups" id="room-lobby-profile-archiv-groups">' +
|
||||
' <button type="button" class="room-lobby-profile-archiv-group-btn is-active" data-group="1"><img src="' + asset('archiv-group-01-a.png') + '" alt=""></button>' +
|
||||
' <button type="button" class="room-lobby-profile-archiv-group-btn" data-group="2"><img src="' + asset('archiv-group-02.png') + '" alt=""></button>' +
|
||||
' <button type="button" class="room-lobby-profile-archiv-group-btn" data-group="3"><img src="' + asset('archiv-group-03.png') + '" alt=""></button>' +
|
||||
' <button type="button" class="room-lobby-profile-archiv-group-btn" data-group="4"><img src="' + asset('archiv-group-04.png') + '" alt=""></button>' +
|
||||
' <button type="button" class="room-lobby-profile-archiv-group-btn" data-group="5"><img src="' + asset('archiv-group-05.png') + '" alt=""></button>' +
|
||||
' </div>' +
|
||||
' <div class="room-lobby-profile-archiv-frame">' +
|
||||
' <ul class="room-lobby-profile-achievements-list" id="room-lobby-profile-achievements-list">' +
|
||||
' <li class="room-lobby-profile-archiv-item"><img class="room-lobby-profile-archiv-icon-bg" src="' + asset('archiv-icon-bg.png') + '" alt=""><div class="room-lobby-profile-archiv-main"><div class="room-lobby-profile-archiv-title-row"><img class="room-lobby-profile-archiv-name" src="' + asset('archiv-name-1.png') + '" alt=""><strong class="room-lobby-profile-archiv-count">1/1</strong></div><div class="room-lobby-profile-archiv-progress-wrap"><img class="room-lobby-profile-archiv-progress-bg" src="' + asset('archiv-progress-bg.png') + '" alt=""><img class="room-lobby-profile-archiv-progress" src="' + asset('archiv-progress.png') + '" alt="" style="--archiv-progress:100%"></div><img class="room-lobby-profile-archiv-line" src="' + asset('archiv-line.png') + '" alt=""></div></li>' +
|
||||
' <li class="room-lobby-profile-archiv-item"><img class="room-lobby-profile-archiv-icon-bg" src="' + asset('archiv-icon-bg.png') + '" alt=""><div class="room-lobby-profile-archiv-main"><div class="room-lobby-profile-archiv-title-row"><img class="room-lobby-profile-archiv-name" src="' + asset('archiv-name-2.png') + '" alt=""><strong class="room-lobby-profile-archiv-count">2/5</strong></div><div class="room-lobby-profile-archiv-progress-wrap"><img class="room-lobby-profile-archiv-progress-bg" src="' + asset('archiv-progress-bg.png') + '" alt=""><img class="room-lobby-profile-archiv-progress" src="' + asset('archiv-progress.png') + '" alt="" style="--archiv-progress:40%"></div><img class="room-lobby-profile-archiv-line" src="' + asset('archiv-line.png') + '" alt=""></div></li>' +
|
||||
' <li class="room-lobby-profile-archiv-item"><img class="room-lobby-profile-archiv-icon-bg" src="' + asset('archiv-icon-bg.png') + '" alt=""><div class="room-lobby-profile-archiv-main"><div class="room-lobby-profile-archiv-title-row"><img class="room-lobby-profile-archiv-name" src="' + asset('archiv-name-3.png') + '" alt=""><strong class="room-lobby-profile-archiv-count">3/10</strong></div><div class="room-lobby-profile-archiv-progress-wrap"><img class="room-lobby-profile-archiv-progress-bg" src="' + asset('archiv-progress-bg.png') + '" alt=""><img class="room-lobby-profile-archiv-progress" src="' + asset('archiv-progress.png') + '" alt="" style="--archiv-progress:30%"></div><img class="room-lobby-profile-archiv-line" src="' + asset('archiv-line.png') + '" alt=""></div></li>' +
|
||||
' <li class="room-lobby-profile-archiv-item"><img class="room-lobby-profile-archiv-icon-bg" src="' + asset('archiv-icon-bg.png') + '" alt=""><div class="room-lobby-profile-archiv-main"><div class="room-lobby-profile-archiv-title-row"><img class="room-lobby-profile-archiv-name" src="' + asset('archiv-name-1.png') + '" alt=""><strong class="room-lobby-profile-archiv-count">4/10</strong></div><div class="room-lobby-profile-archiv-progress-wrap"><img class="room-lobby-profile-archiv-progress-bg" src="' + asset('archiv-progress-bg.png') + '" alt=""><img class="room-lobby-profile-archiv-progress" src="' + asset('archiv-progress.png') + '" alt="" style="--archiv-progress:40%"></div><img class="room-lobby-profile-archiv-line" src="' + asset('archiv-line.png') + '" alt=""></div></li>' +
|
||||
' <li class="room-lobby-profile-archiv-item"><img class="room-lobby-profile-archiv-icon-bg" src="' + asset('archiv-icon-bg.png') + '" alt=""><div class="room-lobby-profile-archiv-main"><div class="room-lobby-profile-archiv-title-row"><img class="room-lobby-profile-archiv-name" src="' + asset('archiv-name-2.png') + '" alt=""><strong class="room-lobby-profile-archiv-count">5/10</strong></div><div class="room-lobby-profile-archiv-progress-wrap"><img class="room-lobby-profile-archiv-progress-bg" src="' + asset('archiv-progress-bg.png') + '" alt=""><img class="room-lobby-profile-archiv-progress" src="' + asset('archiv-progress.png') + '" alt="" style="--archiv-progress:50%"></div><img class="room-lobby-profile-archiv-line" src="' + asset('archiv-line.png') + '" alt=""></div></li>' +
|
||||
' </ul>' +
|
||||
' <div class="room-lobby-profile-archiv-scroll is-hidden"><div class="room-lobby-profile-archiv-scroll-bg"></div><div class="room-lobby-profile-archiv-scroll-bar"></div></div>' +
|
||||
' </div>' +
|
||||
' <div class="room-lobby-profile-settings"><img class="room-lobby-profile-settings-frame" src="' + asset('setting-frame.png') + '" alt=""><div class="room-lobby-profile-settings-row">' +
|
||||
' <div class="room-lobby-profile-setting-item"><img class="room-lobby-profile-setting-label" src="' + asset('setting-txt-1.png') + '" alt="Music"><button type="button" class="room-lobby-profile-toggle" id="room-lobby-profile-music" data-on="true"><img src="' + asset('btn-on.png') + '" alt=""></button></div>' +
|
||||
' <div class="room-lobby-profile-setting-item"><img class="room-lobby-profile-setting-label" src="' + asset('setting-txt-2.png') + '" alt="SFX"><button type="button" class="room-lobby-profile-toggle" id="room-lobby-profile-sfx" data-on="false"><img src="' + asset('btn-off.png') + '" alt=""></button></div>' +
|
||||
' </div></div>' +
|
||||
' </section>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function bindRefs() {
|
||||
refs.overlay = document.getElementById(overlayId);
|
||||
refs.backdrop = document.getElementById('room-lobby-profile-backdrop');
|
||||
refs.dialog = refs.overlay ? refs.overlay.querySelector('.room-lobby-profile-dialog') : null;
|
||||
refs.closeBtn = document.getElementById('room-lobby-profile-close');
|
||||
refs.avatarEl = document.getElementById('room-lobby-profile-overlay-avatar');
|
||||
refs.nameEl = document.getElementById('room-lobby-profile-overlay-name');
|
||||
refs.agentEl = document.getElementById('room-lobby-profile-overlay-agent');
|
||||
refs.coinsEl = document.getElementById('room-lobby-profile-coins-val');
|
||||
refs.musicBtn = document.getElementById('room-lobby-profile-music');
|
||||
refs.sfxBtn = document.getElementById('room-lobby-profile-sfx');
|
||||
refs.groupBtns = Array.prototype.slice.call(document.querySelectorAll('.room-lobby-profile-archiv-group-btn'));
|
||||
refs.archivListEl = document.getElementById('room-lobby-profile-achievements-list');
|
||||
refs.archivScrollEl = document.querySelector('.room-lobby-profile-archiv-scroll');
|
||||
refs.archivScrollBarEl = document.querySelector('.room-lobby-profile-archiv-scroll-bar');
|
||||
refs.editBtn = document.getElementById('room-lobby-profile-edit-btn');
|
||||
refs.logoutBtn = document.getElementById('room-lobby-profile-logout');
|
||||
}
|
||||
|
||||
function syncArchivScroll() {
|
||||
if (!refs.archivListEl || !refs.archivScrollEl || !refs.archivScrollBarEl) return;
|
||||
var total = refs.archivListEl.scrollHeight || 0;
|
||||
var visible = refs.archivListEl.clientHeight || 0;
|
||||
var overflow = total > visible + 1;
|
||||
refs.archivScrollEl.classList.toggle('is-hidden', !overflow);
|
||||
if (!overflow) return;
|
||||
var max = Math.max(1, total - visible);
|
||||
var ratio = Math.max(0, Math.min(1, refs.archivListEl.scrollTop / max));
|
||||
var barPct = Math.max(18, Math.min(42, (visible / Math.max(1, total)) * 100));
|
||||
refs.archivScrollBarEl.style.height = barPct.toFixed(2) + '%';
|
||||
refs.archivScrollBarEl.style.top = ((100 - barPct) * ratio).toFixed(2) + '%';
|
||||
}
|
||||
|
||||
function syncProfileScale() {
|
||||
if (!refs.dialog) return;
|
||||
var rect = refs.dialog.getBoundingClientRect();
|
||||
if (!rect.width || !rect.height) return;
|
||||
var scaleW = rect.width / 1701;
|
||||
var scaleH = rect.height / 951;
|
||||
var scale = Math.max(0.35, Math.min(1, Math.min(scaleW, scaleH)));
|
||||
refs.dialog.style.setProperty('--rp-scale', String(scale.toFixed(4)));
|
||||
}
|
||||
|
||||
function setGroup(index) {
|
||||
refs.groupBtns.forEach(function (btn) {
|
||||
var id = parseInt(btn.getAttribute('data-group'), 10) || 1;
|
||||
var active = id === index;
|
||||
btn.classList.toggle('is-active', active);
|
||||
var img = btn.querySelector('img');
|
||||
if (img) img.src = active ? asset('archiv-group-0' + id + '-a.png') : asset('archiv-group-0' + id + '.png');
|
||||
});
|
||||
syncArchivScroll();
|
||||
}
|
||||
|
||||
function toggleChip(btn) {
|
||||
if (!btn) return;
|
||||
var on = String(btn.getAttribute('data-on') || '').toLowerCase() === 'true';
|
||||
on = !on;
|
||||
btn.setAttribute('data-on', on ? 'true' : 'false');
|
||||
var img = btn.querySelector('img');
|
||||
if (img) img.src = on ? asset('btn-on.png') : asset('btn-off.png');
|
||||
}
|
||||
|
||||
function setProfileData(data) {
|
||||
var payload = data || {};
|
||||
var lobbyAvatar = document.getElementById('lobby-profile-avatar');
|
||||
var roomAvatar = document.getElementById('room-lobby-profile-avatar');
|
||||
var src = payload.avatarSrc || (lobbyAvatar && lobbyAvatar.getAttribute('src')) || (roomAvatar && roomAvatar.getAttribute('src')) || '';
|
||||
if (refs.avatarEl && src) refs.avatarEl.src = src;
|
||||
if (refs.nameEl) refs.nameEl.textContent = (payload.displayName || readName()).toUpperCase();
|
||||
if (refs.agentEl) refs.agentEl.textContent = payload.agentIdLabel || ('AGENT ID : ' + readAgentId());
|
||||
if (refs.coinsEl) refs.coinsEl.textContent = String(payload.coins != null ? payload.coins : readCoins());
|
||||
}
|
||||
|
||||
function openPopup(data) {
|
||||
if (!refs.overlay) return;
|
||||
setProfileData(data);
|
||||
refs.overlay.classList.remove('is-hidden');
|
||||
refs.overlay.setAttribute('aria-hidden', 'false');
|
||||
syncProfileScale();
|
||||
syncArchivScroll();
|
||||
if (refs.closeBtn) refs.closeBtn.focus();
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
if (!refs.overlay) return;
|
||||
refs.overlay.classList.add('is-hidden');
|
||||
refs.overlay.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
if (refs.backdrop) refs.backdrop.addEventListener('click', closePopup);
|
||||
if (refs.closeBtn) refs.closeBtn.addEventListener('click', closePopup);
|
||||
if (refs.musicBtn) refs.musicBtn.addEventListener('click', function () { toggleChip(refs.musicBtn); });
|
||||
if (refs.sfxBtn) refs.sfxBtn.addEventListener('click', function () { toggleChip(refs.sfxBtn); });
|
||||
refs.groupBtns.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () { setGroup(parseInt(btn.getAttribute('data-group'), 10) || 1); });
|
||||
});
|
||||
if (refs.archivListEl) refs.archivListEl.addEventListener('scroll', syncArchivScroll, { passive: true });
|
||||
if (refs.editBtn) refs.editBtn.addEventListener('click', function () {
|
||||
var runApply = function (draft) {
|
||||
if (draft == null) return;
|
||||
var applyFn = typeof window.jdApplyDisplayNameChange === 'function'
|
||||
? window.jdApplyDisplayNameChange
|
||||
: null;
|
||||
if (applyFn) {
|
||||
var result = applyFn(draft, { socket: window.__jdGameSocket || null });
|
||||
if (!result.ok) return;
|
||||
if (refs.nameEl) refs.nameEl.textContent = result.displayName.toUpperCase();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(DISPLAY_NAME_KEY, draft);
|
||||
localStorage.setItem('playerName', draft);
|
||||
} catch (e) { /* ignore */ }
|
||||
if (refs.nameEl) refs.nameEl.textContent = draft.toUpperCase();
|
||||
var headerName = document.getElementById('lobby-profile-name');
|
||||
if (headerName) headerName.textContent = draft.toUpperCase();
|
||||
};
|
||||
|
||||
if (window.jdDisplayName && typeof window.jdDisplayName.openEditor === 'function') {
|
||||
window.jdDisplayName.openEditor({
|
||||
initialValue: typeof window.jdReadDisplayName === 'function' ? window.jdReadDisplayName() : readName()
|
||||
}).then(runApply);
|
||||
return;
|
||||
}
|
||||
if (typeof window.jdPromptAndApplyDisplayName === 'function') {
|
||||
window.jdPromptAndApplyDisplayName({ socket: window.__jdGameSocket || null }).then(function (result) {
|
||||
if (result && result.ok && refs.nameEl) refs.nameEl.textContent = result.displayName.toUpperCase();
|
||||
});
|
||||
return;
|
||||
}
|
||||
var draft = window.prompt('แก้ไขชื่อผู้เล่น', readName() || '');
|
||||
runApply(draft == null ? null : String(draft).trim() || null);
|
||||
});
|
||||
if (refs.logoutBtn) refs.logoutBtn.addEventListener('click', function () {
|
||||
if (document.getElementById('btn-back')) {
|
||||
window.location.href = appRel('/Login/');
|
||||
return;
|
||||
}
|
||||
window.location.href = appRel('/Create%20Room/');
|
||||
});
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') return;
|
||||
var nameEditor = document.getElementById('jd-display-name-editor-overlay');
|
||||
if (nameEditor && !nameEditor.classList.contains('is-hidden')) return;
|
||||
if (refs.overlay && !refs.overlay.classList.contains('is-hidden')) closePopup();
|
||||
});
|
||||
window.addEventListener('resize', function () {
|
||||
syncProfileScale();
|
||||
syncArchivScroll();
|
||||
});
|
||||
document.querySelectorAll(triggerSelector).forEach(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
openPopup();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isRoomLobbyPage() {
|
||||
return /room-lobby\.html$/i.test(String(window.location.pathname || ''));
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (isRoomLobbyPage()) {
|
||||
return;
|
||||
}
|
||||
createMarkupIfNeeded();
|
||||
bindRefs();
|
||||
bindEvents();
|
||||
syncProfileScale();
|
||||
setGroup(1);
|
||||
window.openProfilePopup = openPopup;
|
||||
window.closeProfilePopup = closePopup;
|
||||
window.roomLobbyProfileOverlay = { open: openPopup, close: closePopup };
|
||||
window.mainLobbyProfileOverlay = { open: openPopup, close: closePopup };
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init, { once: true });
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
// Error "message channel closed" มาจาก extension ของเบราว์เซอร์ ไม่ใช่โค้ดเกม — ลองโหมดไม่ระบุตัวตนหรือปิด extension
|
||||
const BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
const SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game';
|
||||
const SERVER = BASE;
|
||||
const MAIN_LOBBY_AI_BTN_ICON = typeof appPath === 'function'
|
||||
? appPath('/Main-Lobby/IMAGE/BTN-AI-ChatBOT.png')
|
||||
: '/Main-Lobby/IMAGE/BTN-AI-ChatBOT.png';
|
||||
@@ -10,7 +10,8 @@
|
||||
const nick = (params.get('nick') || '').trim() || 'ผู้เล่น';
|
||||
const DISPLAY_NAME_STORAGE_KEY = 'roomLobbyDisplayName';
|
||||
let profileDisplayName = nick;
|
||||
if (!spaceId) { location.href = BASE + '/lobby.html'; return; }
|
||||
const DESIGN_MENU = typeof designMainMenuHref === 'function' ? designMainMenuHref() : '/Main-Menu/';
|
||||
if (!spaceId) { location.href = DESIGN_MENU; return; }
|
||||
const roomIdValueEl = document.getElementById('room-id-value');
|
||||
const displayRoomParam = (params.get('displayRoom') || '').trim();
|
||||
if (displayRoomParam) {
|
||||
@@ -102,7 +103,19 @@
|
||||
}
|
||||
};
|
||||
|
||||
const socket = io(typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : undefined, { path: '/Game/socket.io' });
|
||||
const socketOrigin = (typeof GAME_NODE_ORIGIN !== 'undefined' && GAME_NODE_ORIGIN)
|
||||
? GAME_NODE_ORIGIN
|
||||
: (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : undefined);
|
||||
if (typeof io !== 'function') {
|
||||
var rlErr = document.createElement('div');
|
||||
rlErr.setAttribute('role', 'alert');
|
||||
rlErr.style.cssText = 'position:fixed;inset:0;z-index:999999;display:flex;align-items:center;justify-content:center;padding:24px;background:#0a0e1a;color:#e8f4ff;font-family:Kanit,system-ui,sans-serif;text-align:center;line-height:1.6';
|
||||
rlErr.innerHTML = '<div><p style="font-size:1.15rem;margin:0 0 12px">โหลด Socket ไม่สำเร็จ (localhost)</p><p style="opacity:.85;margin:0 0 16px">เพิ่มในไฟล์ <code>room-lobby.html</code> บนเครื่องคุณเท่านั้น:<br><code><script src="https://srv1361159.hstgr.cloud/Game/dev-local/bootstrap.js"></script></code><br>ดู Game/dev-local/INSTALL-WINDOWS.txt</p><a href="' + (typeof designMainMenuHref === 'function' ? designMainMenuHref() : '/Main-Menu/') + '" style="color:#67e8f9">กลับเมนูหลัก</a></div>';
|
||||
(document.body || document.documentElement).appendChild(rlErr);
|
||||
throw new Error('socket.io not loaded');
|
||||
}
|
||||
const socket = io(socketOrigin, { path: '/Game/socket.io' });
|
||||
window.__jdGameSocket = socket;
|
||||
const roomPlayersHud = document.getElementById('room-players-hud');
|
||||
const peersList = document.getElementById('peers-list');
|
||||
const readyCheck = document.getElementById('ready-check');
|
||||
@@ -132,6 +145,8 @@
|
||||
let spaceName = '';
|
||||
let maxPlayers = 10;
|
||||
let lobbyBotSlotCount = 0;
|
||||
/** จำนวนช่องรวมในโถง (คน + บอท) — ตรง server LOBBY_SLOT_TOTAL */
|
||||
const LOBBY_SLOT_TOTAL = 6;
|
||||
const LOBBY_BOT_PREFIX = '__lobby_bot_';
|
||||
const lobbyBots = new Map();
|
||||
let suspectPickOverlayOpen = false;
|
||||
@@ -305,9 +320,23 @@
|
||||
img.src = src;
|
||||
}
|
||||
|
||||
function rlHexToRgbCss(hex) {
|
||||
var h = String(hex || '').replace('#', '').trim();
|
||||
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
||||
var n = parseInt(h, 16);
|
||||
if (!Number.isFinite(n)) return null;
|
||||
return 'rgb(' + ((n >> 16) & 255) + ',' + ((n >> 8) & 255) + ',' + (n & 255) + ')';
|
||||
}
|
||||
|
||||
function rlSampleSwatch(group, idx, cb) {
|
||||
var key = group + '-' + idx;
|
||||
if (rlSwatchCache[key]) { cb(rlSwatchCache[key]); return; }
|
||||
var hexArr = group === 'color' ? ROOM_CZ_THEME_HEX : ROOM_CZ_SKIN_HEX;
|
||||
var hi = parseInt(idx, 10) - 1;
|
||||
if (hexArr && hexArr[hi]) {
|
||||
var fromHex = rlHexToRgbCss(hexArr[hi]);
|
||||
if (fromHex) { rlSwatchCache[key] = fromHex; cb(fromHex); return; }
|
||||
}
|
||||
rlLoadImg(RL_CUSTOMIZE_ASSET + (group === 'color' ? 'color-' : 'skin-tone-') + idx + '.png', function (img) {
|
||||
if (!img || !img.naturalWidth) { cb(null); return; }
|
||||
try {
|
||||
@@ -501,17 +530,25 @@
|
||||
}
|
||||
|
||||
/* ===== ห้องแต่งตัว (Customize) ในห้อง room-lobby — ปุ่มล่างซ้าย + popup + tint สด ===== */
|
||||
var ROOM_CZ_ASSET = SERVER + '/img/03-5-Customize/';
|
||||
var ROOM_CZ_FACE = [
|
||||
{ idx: 1, price: 0 }, { idx: 2, price: 0 }, { idx: 3, price: 50 }, { idx: 4, price: 50 },
|
||||
{ idx: 5, price: 50 }, { idx: 6, price: 100 }, { idx: 7, price: 100 }, { idx: 8, price: 200 }
|
||||
/** ธีมสี 8 ช่อง — ตรงกับ customize-popup / play.js */
|
||||
var ROOM_CZ_THEME_HEX = [
|
||||
'#d72520', '#ef8508', '#efe237', '#5bb443',
|
||||
'#2585cb', '#3f4ead', '#b53fd6', '#fec1fe'
|
||||
];
|
||||
/** สีผิว 3 ช่อง */
|
||||
var ROOM_CZ_SKIN_HEX = ['#eaa78a', '#fbd5c4', '#fae9e1'];
|
||||
/** จุด "ห้องแต่งตัว" ในฉาก — ตั้งจาก mapData.customizeSpot (วางใน editor) ถ้าไม่มี = null = ไม่แสดง */
|
||||
var ROOM_CZ_SPOT = null;
|
||||
var roomCzIcon = new Image();
|
||||
roomCzIcon.src = '/Main-Lobby/IMAGE/btn-cloth.png';
|
||||
roomCzIcon.onload = function () { if (typeof mapData !== 'undefined' && mapData && canvas) drawLobbyMap(); };
|
||||
|
||||
/** จุด "ตั้งค่าโฮสต์" — mapData.hostConsoleSpot (วางใน editor) */
|
||||
var ROOM_HC_SPOT = null;
|
||||
var roomHcIcon = new Image();
|
||||
roomHcIcon.src = SERVER + '/img/btn-setting-host.png';
|
||||
roomHcIcon.onload = function () { if (typeof mapData !== 'undefined' && mapData && canvas) drawLobbyMap(); };
|
||||
|
||||
function markRoomCzInteractiveCell() {
|
||||
if (!mapData) return;
|
||||
if (mapData.customizeSpot && Number.isFinite(Number(mapData.customizeSpot.x)) && Number.isFinite(Number(mapData.customizeSpot.y))) {
|
||||
@@ -527,6 +564,21 @@
|
||||
mapData.interactive[ROOM_CZ_SPOT.y][ROOM_CZ_SPOT.x] = 1;
|
||||
}
|
||||
|
||||
function markHostHcInteractiveCell() {
|
||||
if (!mapData) return;
|
||||
if (mapData.hostConsoleSpot && Number.isFinite(Number(mapData.hostConsoleSpot.x)) && Number.isFinite(Number(mapData.hostConsoleSpot.y))) {
|
||||
ROOM_HC_SPOT = { x: Math.floor(Number(mapData.hostConsoleSpot.x)), y: Math.floor(Number(mapData.hostConsoleSpot.y)) };
|
||||
} else {
|
||||
ROOM_HC_SPOT = null;
|
||||
return;
|
||||
}
|
||||
var w = mapData.width || 20, h = mapData.height || 15;
|
||||
if (ROOM_HC_SPOT.x < 0 || ROOM_HC_SPOT.x >= w || ROOM_HC_SPOT.y < 0 || ROOM_HC_SPOT.y >= h) { ROOM_HC_SPOT = null; return; }
|
||||
if (!Array.isArray(mapData.interactive)) mapData.interactive = [];
|
||||
for (var y = 0; y < h; y++) { if (!Array.isArray(mapData.interactive[y])) mapData.interactive[y] = []; }
|
||||
mapData.interactive[ROOM_HC_SPOT.y][ROOM_HC_SPOT.x] = 1;
|
||||
}
|
||||
|
||||
function isRoomCzInteractTarget(t) {
|
||||
return !!(t && ROOM_CZ_SPOT && t.x === ROOM_CZ_SPOT.x && t.y === ROOM_CZ_SPOT.y);
|
||||
}
|
||||
@@ -537,140 +589,49 @@
|
||||
return (dx * dx + dy * dy) <= 2.25; // ภายในรัศมี ~1.5 ช่อง (ยืนใกล้ตู้ก็กด F ได้)
|
||||
}
|
||||
|
||||
function roomCzInjectStyle() {
|
||||
if (document.getElementById('room-cz-style')) return;
|
||||
var st = document.createElement('style');
|
||||
st.id = 'room-cz-style';
|
||||
st.textContent = [
|
||||
'.room-cz-open{position:fixed;left:clamp(10px,1.4vw,22px);bottom:clamp(96px,15vh,150px);z-index:60;width:clamp(54px,6vw,76px);padding:0;border:none;background:none;cursor:pointer}',
|
||||
'.room-cz-open img{display:block;width:100%;height:auto;filter:drop-shadow(0 0 8px rgba(34,211,238,.5))}',
|
||||
'.room-cz-overlay{position:fixed;inset:0;z-index:140;display:flex;align-items:center;justify-content:center;padding:16px;font-family:Kanit,Sarabun,system-ui,sans-serif}',
|
||||
'.room-cz-overlay.hidden{display:none!important}',
|
||||
'.room-cz-backdrop{position:absolute;inset:0;background:rgba(4,6,18,.78);backdrop-filter:blur(4px)}',
|
||||
'.room-cz-dialog{position:relative;width:min(92vw,720px);max-height:90vh;overflow:hidden auto;display:flex;flex-direction:column;gap:clamp(.5rem,1.6vh,1rem);padding:clamp(1rem,3vw,1.6rem) clamp(1rem,3vw,1.8rem) clamp(1.2rem,3vw,1.8rem);border-radius:18px;background:linear-gradient(180deg,rgba(14,20,44,.97),rgba(9,13,30,.97));border:2px solid rgba(34,211,238,.7);box-shadow:0 0 22px rgba(34,211,238,.45),0 0 48px rgba(236,72,153,.28),inset 0 0 24px rgba(34,211,238,.1);color:#e8faff}',
|
||||
'.room-cz-titlebar{display:flex;align-items:center;justify-content:center;position:relative}',
|
||||
'.room-cz-titlebar h2{margin:0;font-size:clamp(1.1rem,3vw,1.6rem);font-weight:700;text-shadow:0 0 12px rgba(34,211,238,.6)}',
|
||||
'.room-cz-close{position:absolute;right:0;top:0;width:38px;height:38px;border:2px solid rgba(236,72,153,.6);border-radius:10px;background:rgba(20,16,40,.6);color:#ff8fd0;font-size:1.4rem;line-height:1;cursor:pointer}',
|
||||
'.room-cz-row{display:flex;align-items:center;gap:clamp(.5rem,2vw,1rem);flex-wrap:wrap}',
|
||||
'.room-cz-rowlabel{height:clamp(20px,3.4vh,30px);width:auto;flex:0 0 auto}',
|
||||
'.room-cz-swatches{display:flex;flex-wrap:wrap;gap:clamp(6px,1vw,10px)}',
|
||||
'.room-cz-swatch{padding:0;border:2px solid transparent;border-radius:8px;background:none;cursor:pointer;line-height:0}',
|
||||
'.room-cz-swatch img{display:block;width:clamp(28px,4vw,40px);height:auto;border-radius:6px}',
|
||||
'.room-cz-swatch.sel{border-color:#22d3ee;box-shadow:0 0 10px rgba(34,211,238,.7)}',
|
||||
'.room-cz-tabs{position:relative;width:100%;max-width:560px;margin:0 auto;aspect-ratio:776/118}',
|
||||
'.room-cz-tabbar{display:block;width:100%;height:100%;object-fit:contain;pointer-events:none}',
|
||||
'.room-cz-tabhit{position:absolute;top:0;height:100%;width:33.34%;padding:0;border:none;background:transparent;cursor:pointer}',
|
||||
'.room-cz-tabhit[data-tab=face]{left:0}.room-cz-tabhit[data-tab=hair]{left:33.33%}.room-cz-tabhit[data-tab=cloth]{left:66.66%}',
|
||||
'.room-cz-items{flex:1;min-height:clamp(140px,30vh,280px);max-height:42vh;overflow-y:auto;display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(8px,1.5vw,14px);padding:clamp(8px,1.5vw,14px);border-radius:12px;background:rgba(8,12,28,.6);border:1px solid rgba(34,211,238,.25)}',
|
||||
'.room-cz-item{position:relative;padding:6px;border:2px solid rgba(34,211,238,.3);border-radius:12px;background:rgba(18,26,52,.7);cursor:pointer;aspect-ratio:1;display:flex;align-items:center;justify-content:center}',
|
||||
'.room-cz-item.sel{border-color:#22d3ee;box-shadow:0 0 10px rgba(34,211,238,.6)}',
|
||||
'.room-cz-item img{width:78%;height:auto;object-fit:contain}',
|
||||
'.room-cz-item .pr{position:absolute;bottom:4px;left:50%;transform:translateX(-50%);font-size:.7rem;font-weight:700;color:#ffe066;background:rgba(0,0,0,.45);border-radius:8px;padding:1px 8px}',
|
||||
'.room-cz-empty{grid-column:1/-1;align-self:center;text-align:center;color:rgba(255,255,255,.6);padding:2rem 1rem}',
|
||||
'.room-cz-confirm{align-self:center;padding:0;border:none;background:none;cursor:pointer}',
|
||||
'.room-cz-confirm img{display:block;width:min(60vw,240px);height:auto}',
|
||||
'@media(max-width:560px){.room-cz-items{grid-template-columns:repeat(3,1fr)}}'
|
||||
].join('');
|
||||
document.head.appendChild(st);
|
||||
function isHostHcInteractTarget(t) {
|
||||
return !!(t && ROOM_HC_SPOT && t.x === ROOM_HC_SPOT.x && t.y === ROOM_HC_SPOT.y);
|
||||
}
|
||||
|
||||
function roomCzBuildDom() {
|
||||
if (document.getElementById('room-cz-overlay')) return;
|
||||
var ov = document.createElement('div');
|
||||
ov.id = 'room-cz-overlay'; ov.className = 'room-cz-overlay hidden';
|
||||
ov.setAttribute('role', 'dialog'); ov.setAttribute('aria-modal', 'true'); ov.setAttribute('aria-label', 'ห้องแต่งตัว');
|
||||
ov.innerHTML =
|
||||
'<div class="room-cz-backdrop" id="room-cz-backdrop"></div>' +
|
||||
'<div class="room-cz-dialog">' +
|
||||
'<div class="room-cz-titlebar"><h2>ห้องแต่งตัว</h2><button type="button" class="room-cz-close" id="room-cz-close" aria-label="ปิด">×</button></div>' +
|
||||
'<div class="room-cz-row"><img class="room-cz-rowlabel" src="' + ROOM_CZ_ASSET + 'theme-color-txt.png" alt="ธีมสี" decoding="async"><div class="room-cz-swatches" id="room-cz-colors"></div></div>' +
|
||||
'<div class="room-cz-row"><img class="room-cz-rowlabel" src="' + ROOM_CZ_ASSET + 'theme-skin-txt.png" alt="สีผิว" decoding="async"><div class="room-cz-swatches" id="room-cz-skins"></div></div>' +
|
||||
'<div class="room-cz-tabs"><img id="room-cz-tabbar" class="room-cz-tabbar" src="' + ROOM_CZ_ASSET + 'tab-1-face.png" alt="" decoding="async"><button type="button" class="room-cz-tabhit" data-tab="face" aria-label="ใบหน้า"></button><button type="button" class="room-cz-tabhit" data-tab="hair" aria-label="ทรงผม"></button><button type="button" class="room-cz-tabhit" data-tab="cloth" aria-label="ชุด"></button></div>' +
|
||||
'<div class="room-cz-items" id="room-cz-items"></div>' +
|
||||
'<button type="button" class="room-cz-confirm" id="room-cz-confirm" aria-label="ยืนยัน"><img src="' + ROOM_CZ_ASSET + 'btn-cf.png" alt="ยืนยัน" decoding="async"></button>' +
|
||||
'</div>';
|
||||
document.body.appendChild(ov);
|
||||
function isNearHostHcSpot(me) {
|
||||
if (!me || !ROOM_HC_SPOT || typeof me.x !== 'number' || typeof me.y !== 'number') return false;
|
||||
var dx = me.x - ROOM_HC_SPOT.x, dy = me.y - ROOM_HC_SPOT.y;
|
||||
return (dx * dx + dy * dy) <= 2.25;
|
||||
}
|
||||
|
||||
function roomCzMarkSwatch(group, idx) {
|
||||
var wrap = document.getElementById(group === 'color' ? 'room-cz-colors' : 'room-cz-skins');
|
||||
if (wrap) [].forEach.call(wrap.children, function (c) { c.classList.toggle('sel', c.getAttribute('data-idx') === String(idx)); });
|
||||
}
|
||||
|
||||
function roomCzSelectSwatch(group, idx) {
|
||||
roomCzMarkSwatch(group, idx);
|
||||
try { localStorage.setItem(group === 'color' ? 'lobbyThemeColor' : 'lobbySkinTone', String(idx)); } catch (e) {}
|
||||
rlResolveMyColors(function () {
|
||||
persistLobbyPlayerTintJson();
|
||||
updateRoomProfileAvatarTinted();
|
||||
if (mapData && canvas) drawLobbyMap();
|
||||
});
|
||||
}
|
||||
|
||||
function roomCzMakeSwatch(group, idx) {
|
||||
var b = document.createElement('button');
|
||||
b.type = 'button'; b.className = 'room-cz-swatch'; b.setAttribute('data-idx', String(idx));
|
||||
var im = document.createElement('img');
|
||||
im.src = ROOM_CZ_ASSET + (group === 'color' ? 'color-' : 'skin-tone-') + idx + '.png'; im.alt = '';
|
||||
b.appendChild(im);
|
||||
b.addEventListener('click', function () { roomCzSelectSwatch(group, idx); });
|
||||
return b;
|
||||
}
|
||||
|
||||
function roomCzRenderItems(tab) {
|
||||
var grid = document.getElementById('room-cz-items'); if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
if (tab !== 'face') { var e = document.createElement('div'); e.className = 'room-cz-empty'; e.textContent = 'ยังไม่เปิดให้บริการ'; grid.appendChild(e); return; }
|
||||
var saved = ''; try { saved = localStorage.getItem('lobbyItem_face') || ''; } catch (e2) {}
|
||||
ROOM_CZ_FACE.forEach(function (it) {
|
||||
var cell = document.createElement('button'); cell.type = 'button';
|
||||
cell.className = 'room-cz-item' + (String(it.idx) === saved ? ' sel' : ''); cell.setAttribute('data-idx', String(it.idx));
|
||||
var im = document.createElement('img'); im.src = ROOM_CZ_ASSET + 'face-' + it.idx + '.png'; im.alt = ''; cell.appendChild(im);
|
||||
if (it.price > 0) { var p = document.createElement('span'); p.className = 'pr'; p.textContent = String(it.price); cell.appendChild(p); }
|
||||
cell.addEventListener('click', function () {
|
||||
[].forEach.call(grid.children, function (c) { c.classList.remove('sel'); });
|
||||
cell.classList.add('sel');
|
||||
try { localStorage.setItem('lobbyItem_face', String(it.idx)); } catch (e3) {}
|
||||
});
|
||||
grid.appendChild(cell);
|
||||
});
|
||||
}
|
||||
|
||||
function roomCzSetTab(tab) {
|
||||
var bar = document.getElementById('room-cz-tabbar');
|
||||
if (bar) { var m = { face: 'tab-1-face.png', hair: 'tab-2-hair.png', cloth: 'tab-3-cloth.png' }; bar.src = ROOM_CZ_ASSET + (m[tab] || m.face); }
|
||||
roomCzRenderItems(tab);
|
||||
function openHostConsoleIfHost() {
|
||||
if (hostId !== socket.id) {
|
||||
appendLobbySystemChat('— เฉพาะ Host เท่านั้น');
|
||||
return;
|
||||
}
|
||||
if (hostConsoleOverlay && typeof hostConsoleOverlay.open === 'function') hostConsoleOverlay.open();
|
||||
}
|
||||
|
||||
/** เปิดห้องแต่งตัว — production ใช้ #room-cz-overlay + CSS จาก customize-popup.css */
|
||||
function openRoomCustomize() {
|
||||
var ov = document.getElementById('room-cz-overlay'); if (!ov) return;
|
||||
var cw = document.getElementById('room-cz-colors'), sw = document.getElementById('room-cz-skins');
|
||||
if (cw && !cw.childElementCount) { for (var i = 1; i <= 8; i++) cw.appendChild(roomCzMakeSwatch('color', i)); }
|
||||
if (sw && !sw.childElementCount) { for (var j = 1; j <= 3; j++) sw.appendChild(roomCzMakeSwatch('skin', j)); }
|
||||
try {
|
||||
var c = localStorage.getItem('lobbyThemeColor'); if (c) roomCzMarkSwatch('color', c);
|
||||
var s = localStorage.getItem('lobbySkinTone'); if (s) roomCzMarkSwatch('skin', s);
|
||||
} catch (e) {}
|
||||
roomCzSetTab('face');
|
||||
ov.classList.remove('hidden');
|
||||
if (typeof window.openCustomizePopup === 'function') {
|
||||
window.openCustomizePopup();
|
||||
}
|
||||
}
|
||||
|
||||
function closeRoomCustomize() { var ov = document.getElementById('room-cz-overlay'); if (ov) ov.classList.add('hidden'); }
|
||||
function closeRoomCustomize() {
|
||||
if (typeof window.closeCustomizePopup === 'function') {
|
||||
window.closeCustomizePopup();
|
||||
}
|
||||
}
|
||||
|
||||
function setupRoomCustomize() {
|
||||
roomCzInjectStyle();
|
||||
roomCzBuildDom();
|
||||
var ob = document.getElementById('room-cz-open');
|
||||
if (ob) ob.addEventListener('click', openRoomCustomize);
|
||||
var cb = document.getElementById('room-cz-close');
|
||||
if (cb) cb.addEventListener('click', closeRoomCustomize);
|
||||
var bd = document.getElementById('room-cz-backdrop');
|
||||
if (bd) bd.addEventListener('click', closeRoomCustomize);
|
||||
var cf = document.getElementById('room-cz-confirm');
|
||||
if (cf) cf.addEventListener('click', closeRoomCustomize);
|
||||
[].forEach.call(document.querySelectorAll('.room-cz-tabhit'), function (t) {
|
||||
t.addEventListener('click', function () { roomCzSetTab(t.getAttribute('data-tab')); });
|
||||
window.openRoomCustomize = openRoomCustomize;
|
||||
window.closeRoomCustomize = closeRoomCustomize;
|
||||
window.addEventListener('customize-popup:applied', function () {
|
||||
rlResolveMyColors(function () {
|
||||
persistLobbyPlayerTintJson();
|
||||
updateRoomProfileAvatarTinted();
|
||||
if (mapData && canvas) drawLobbyMap();
|
||||
renderPeers();
|
||||
});
|
||||
});
|
||||
try { document.dispatchEvent(new CustomEvent('room-lobby-ready')); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function rlTintMask(img, color) {
|
||||
@@ -1121,6 +1082,20 @@
|
||||
ctx.drawImage(roomCzIcon, _czx + tileSize / 2 - _czS / 2, _czy + tileSize - _czS, _czS, _czS);
|
||||
}
|
||||
|
||||
if (ROOM_HC_SPOT && roomHcIcon && hostId === socket.id && roomHcIcon.complete && roomHcIcon.naturalWidth) {
|
||||
const _hcx = ROOM_HC_SPOT.x * tileSize;
|
||||
const _hcy = ROOM_HC_SPOT.y * tileSize;
|
||||
const _hcS = tileSize * 1.5;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.fillStyle = 'rgba(236,72,153,0.35)';
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(_hcx + tileSize / 2, _hcy + tileSize - 3, tileSize * 0.55, tileSize * 0.22, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
ctx.drawImage(roomHcIcon, _hcx + tileSize / 2 - _hcS / 2, _hcy + tileSize - _hcS, _hcS, _hcS);
|
||||
}
|
||||
|
||||
const peerList = [...peers.entries(), ...lobbyBots.entries()].sort(function (a, b) {
|
||||
const pa = a[1], pb = b[1];
|
||||
const ya = pa.y != null ? pa.y : 1, yb = pb.y != null ? pb.y : 1;
|
||||
@@ -1625,9 +1600,26 @@
|
||||
if (preplayCaseDetailOverlay) preplayCaseDetailOverlay.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
function syncPreplayCaseStepScale() {
|
||||
if (!getPreplayEls() || !preplayStepCase || !preplayOverlay) return;
|
||||
if (preplayOverlay.classList.contains('is-hidden')) return;
|
||||
if (preplayStepCase.classList.contains('is-hidden')) return;
|
||||
var rect = preplayStepCase.getBoundingClientRect();
|
||||
if (!rect.width || !rect.height) return;
|
||||
var baseWidth = 1700;
|
||||
var baseHeight = 900;
|
||||
var byWidth = rect.width / baseWidth;
|
||||
var byHeight = rect.height / baseHeight;
|
||||
var targetScale = 1.08;
|
||||
var scale = Math.max(0.56, Math.min(targetScale, Math.min(byWidth, byHeight) * targetScale));
|
||||
preplayStepCase.style.setProperty('--lobby-preplay-case-scale', scale.toFixed(4));
|
||||
}
|
||||
|
||||
function showPreplayCaseStep() {
|
||||
if (preplayStepLevel) preplayStepLevel.classList.add('is-hidden');
|
||||
if (preplayStepCase) preplayStepCase.classList.remove('is-hidden');
|
||||
syncPreplayCaseStepScale();
|
||||
requestAnimationFrame(syncPreplayCaseStepScale);
|
||||
}
|
||||
|
||||
function showPreplayLevelStep() {
|
||||
@@ -1716,6 +1708,11 @@
|
||||
var o = document.getElementById('lobby-preplay-case-detail-overlay');
|
||||
if (o) o.classList.add('is-hidden');
|
||||
});
|
||||
|
||||
window.addEventListener('resize', syncPreplayCaseStepScale);
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(syncPreplayCaseStepScale, 180);
|
||||
});
|
||||
}
|
||||
|
||||
const PATH_ARRIVE_THRESH = 0.15;
|
||||
@@ -2104,6 +2101,7 @@
|
||||
if (serverName) return serverName;
|
||||
return profileDisplayName || nick || 'PLAYER';
|
||||
}
|
||||
window.getProfileDisplayName = getProfileDisplayName;
|
||||
|
||||
loadProfileDisplayName();
|
||||
|
||||
@@ -2172,6 +2170,9 @@
|
||||
this.musicBtn = document.getElementById('room-lobby-profile-music');
|
||||
this.sfxBtn = document.getElementById('room-lobby-profile-sfx');
|
||||
this.archivGroupBtns = Array.from(document.querySelectorAll('.room-lobby-profile-archiv-group-btn'));
|
||||
this.archivListEl = document.getElementById('room-lobby-profile-achievements-list');
|
||||
this.archivScrollEl = document.querySelector('.room-lobby-profile-archiv-scroll');
|
||||
this.archivScrollBarEl = document.querySelector('.room-lobby-profile-archiv-scroll-bar');
|
||||
this.editBtn = document.getElementById('room-lobby-profile-edit-btn');
|
||||
this.activeArchivGroup = 1;
|
||||
this._boundSyncProfileFrameScale = () => this._syncProfileFrameScale();
|
||||
@@ -2197,9 +2198,25 @@
|
||||
});
|
||||
});
|
||||
this.editBtn?.addEventListener('click', () => this._editDisplayName());
|
||||
this.archivListEl?.addEventListener('scroll', () => this._syncArchivScroll(), { passive: true });
|
||||
window.addEventListener('resize', this._boundSyncProfileFrameScale);
|
||||
}
|
||||
|
||||
_syncArchivScroll() {
|
||||
if (!this.archivListEl || !this.archivScrollEl || !this.archivScrollBarEl) return;
|
||||
const total = this.archivListEl.scrollHeight || 0;
|
||||
const visible = this.archivListEl.clientHeight || 0;
|
||||
const overflow = total > visible + 1;
|
||||
this.archivScrollEl.classList.toggle('is-hidden', !overflow);
|
||||
this.archivScrollEl.setAttribute('aria-hidden', overflow ? 'false' : 'true');
|
||||
if (!overflow) return;
|
||||
const max = Math.max(1, total - visible);
|
||||
const ratio = Math.max(0, Math.min(1, this.archivListEl.scrollTop / max));
|
||||
const barPct = Math.max(18, Math.min(42, (visible / Math.max(1, total)) * 100));
|
||||
this.archivScrollBarEl.style.height = barPct.toFixed(2) + '%';
|
||||
this.archivScrollBarEl.style.top = ((100 - barPct) * ratio).toFixed(2) + '%';
|
||||
}
|
||||
|
||||
_initProfileFrameScaleObserver() {
|
||||
if (!this.innerFrameEl) return;
|
||||
if (typeof ResizeObserver === 'undefined') return;
|
||||
@@ -2210,15 +2227,17 @@
|
||||
_syncProfileFrameScale() {
|
||||
let dialogScale = 1;
|
||||
if (this.dialogEl) {
|
||||
const dialogWidth = this.dialogEl.clientWidth || 0;
|
||||
if (dialogWidth > 0) {
|
||||
const rawDialogScale = dialogWidth / 1701;
|
||||
dialogScale = Math.max(0.35, Math.min(1, rawDialogScale));
|
||||
const rect = this.dialogEl.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
const scaleW = rect.width / 1701;
|
||||
const scaleH = rect.height / 951;
|
||||
dialogScale = Math.max(0.35, Math.min(1, Math.min(scaleW, scaleH)));
|
||||
this.dialogEl.style.setProperty('--rp-scale', String(dialogScale.toFixed(4)));
|
||||
}
|
||||
}
|
||||
if (!this.innerFrameEl) return;
|
||||
this.innerFrameEl.style.setProperty('--pf-scale', String(dialogScale.toFixed(4)));
|
||||
this._syncArchivScroll();
|
||||
}
|
||||
|
||||
_toggleChip(btn) {
|
||||
@@ -2253,6 +2272,7 @@
|
||||
? 'img/03-6-Profile/archiv-group-0' + id + '-a.png'
|
||||
: 'img/03-6-Profile/archiv-group-0' + id + '.png';
|
||||
});
|
||||
this._syncArchivScroll();
|
||||
}
|
||||
|
||||
setProfileData(data) {
|
||||
@@ -2282,7 +2302,32 @@
|
||||
}
|
||||
|
||||
_editDisplayName() {
|
||||
const currentName = this.nameEl ? String(this.nameEl.textContent || '').trim() : getProfileDisplayName();
|
||||
const self = this;
|
||||
const currentName = getProfileDisplayName();
|
||||
|
||||
function afterRename(nextName) {
|
||||
if (!nextName) return;
|
||||
saveProfileDisplayName(nextName);
|
||||
self.setProfileData({ displayName: nextName });
|
||||
updateLobbyProfileAvatar();
|
||||
renderPeers();
|
||||
updatePlayersHud();
|
||||
refreshHostConsoleOverlayIfOpen();
|
||||
}
|
||||
|
||||
if (window.jdDisplayName && typeof window.jdDisplayName.openEditor === 'function') {
|
||||
window.jdDisplayName.openEditor({ initialValue: currentName }).then(function (draft) {
|
||||
if (draft == null) return;
|
||||
if (typeof window.jdApplyDisplayNameChange === 'function') {
|
||||
const result = window.jdApplyDisplayNameChange(draft, { socket });
|
||||
if (result.ok) afterRename(result.displayName);
|
||||
return;
|
||||
}
|
||||
afterRename(draft);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const draft = window.prompt('แก้ไขชื่อผู้เล่น', currentName || '');
|
||||
if (draft == null) return;
|
||||
const nextName = String(draft).trim();
|
||||
@@ -2290,9 +2335,13 @@
|
||||
alert('กรุณากรอกชื่อผู้เล่น');
|
||||
return;
|
||||
}
|
||||
saveProfileDisplayName(nextName);
|
||||
this.setProfileData({ displayName: nextName });
|
||||
updateLobbyProfileAvatar();
|
||||
if (typeof window.jdApplyDisplayNameChange === 'function') {
|
||||
const result = window.jdApplyDisplayNameChange(nextName, { socket });
|
||||
if (!result.ok) return;
|
||||
afterRename(result.displayName);
|
||||
return;
|
||||
}
|
||||
afterRename(nextName);
|
||||
}
|
||||
|
||||
open(data) {
|
||||
@@ -2304,6 +2353,7 @@
|
||||
this._syncProfileFrameScale();
|
||||
requestAnimationFrame(() => this._syncProfileFrameScale());
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => this._syncProfileFrameScale()));
|
||||
this._syncArchivScroll();
|
||||
this.closeBtn?.focus();
|
||||
}
|
||||
|
||||
@@ -2382,11 +2432,10 @@
|
||||
|
||||
_changeMax(delta) {
|
||||
if (!this._isHost()) return;
|
||||
const currentPlayers = this._getPlayersCount();
|
||||
const minAllowed = Math.max(2, currentPlayers);
|
||||
const maxAllowed = 10;
|
||||
const humans = this._getPlayersCount();
|
||||
const minAllowed = Math.max(humans, 2);
|
||||
const next = this.pendingMaxPlayers + delta;
|
||||
this.pendingMaxPlayers = Math.max(minAllowed, Math.min(maxAllowed, next));
|
||||
this.pendingMaxPlayers = Math.max(minAllowed, Math.min(LOBBY_SLOT_TOTAL, next));
|
||||
this._render();
|
||||
}
|
||||
|
||||
@@ -2460,14 +2509,42 @@
|
||||
|
||||
_confirm() {
|
||||
if (!this._isHost()) return;
|
||||
maxPlayers = this.pendingMaxPlayers;
|
||||
const humans = this._getPlayersCount();
|
||||
const total = Math.min(LOBBY_SLOT_TOTAL, Math.max(humans, this.pendingMaxPlayers));
|
||||
const bots = total - humans;
|
||||
lobbyBotSlotCount = bots;
|
||||
maxPlayers = Math.max(humans, total - bots);
|
||||
this.pendingMaxPlayers = total;
|
||||
syncLobbyCaseBots();
|
||||
updatePlayersHud();
|
||||
this.close();
|
||||
if (mapData && canvas) drawLobbyMap();
|
||||
const self = this;
|
||||
socket.emit('host-console-apply', {
|
||||
totalSlots: total,
|
||||
botSlotCount: bots,
|
||||
maxPlayers: maxPlayers
|
||||
}, function (res) {
|
||||
if (!res || !res.ok) {
|
||||
var msg = (res && res.error) ? res.error : 'บันทึกการตั้งค่าไม่สำเร็จ';
|
||||
try { alert(msg); } catch (e) { /* ignore */ }
|
||||
return;
|
||||
}
|
||||
if (res.maxPlayers != null) maxPlayers = res.maxPlayers;
|
||||
if (res.botSlotCount != null) {
|
||||
lobbyBotSlotCount = Math.max(0, parseInt(res.botSlotCount, 10) || 0);
|
||||
syncLobbyCaseBots();
|
||||
updatePlayersHud();
|
||||
if (mapData && canvas) drawLobbyMap();
|
||||
}
|
||||
self.close();
|
||||
});
|
||||
}
|
||||
|
||||
open() {
|
||||
if (!this.overlay) return;
|
||||
this.pendingMaxPlayers = Math.max(2, maxPlayers || 10);
|
||||
if (!this._isHost()) return;
|
||||
const totalNow = Math.min(LOBBY_SLOT_TOTAL, Math.max(peers.size, lobbyOccupantCount()));
|
||||
this.pendingMaxPlayers = totalNow > 0 ? totalNow : Math.min(LOBBY_SLOT_TOTAL, Math.max(2, (maxPlayers || 2) + (lobbyBotSlotCount || 0)));
|
||||
this._render();
|
||||
this.overlay.classList.remove('is-hidden');
|
||||
this.overlay.setAttribute('aria-hidden', 'false');
|
||||
@@ -2491,8 +2568,26 @@
|
||||
hostConsoleOverlay._render();
|
||||
}
|
||||
|
||||
function applyPeerDisplayNameUpdate(data) {
|
||||
if (!data || !data.id) return;
|
||||
const p = peers.get(data.id);
|
||||
if (p) p.nickname = data.nickname;
|
||||
if (data.id === socket.id) {
|
||||
saveProfileDisplayName(data.nickname);
|
||||
try { localStorage.setItem('playerName', data.nickname); } catch (e) { /* ignore */ }
|
||||
if (window.jdDisplayName && typeof window.jdDisplayName.syncDom === 'function') {
|
||||
window.jdDisplayName.syncDom(data.nickname);
|
||||
}
|
||||
}
|
||||
renderPeers();
|
||||
updatePlayersHud();
|
||||
refreshHostConsoleOverlayIfOpen();
|
||||
}
|
||||
|
||||
socket.on('peer-display-name', applyPeerDisplayNameUpdate);
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.emit('join-space', { spaceId, nickname: nick, characterId: getStoredCharacterId() }, (res) => {
|
||||
socket.emit('join-space', { spaceId, nickname: getProfileDisplayName(), characterId: getStoredCharacterId() }, (res) => {
|
||||
if (!res || !res.ok) {
|
||||
var joinErr = (res && res.error) || 'เข้าไม่ได้';
|
||||
if (/เริ่มคดี|ไม่รับผู้เล่น/.test(joinErr)) {
|
||||
@@ -2500,14 +2595,16 @@
|
||||
} else {
|
||||
alert(joinErr);
|
||||
}
|
||||
location.href = BASE + '/lobby.html';
|
||||
location.href = DESIGN_MENU;
|
||||
return;
|
||||
}
|
||||
mapData = res.mapData;
|
||||
roomJoinReady = true;
|
||||
maybeHideRoomLoading();
|
||||
markRoomCzInteractiveCell();
|
||||
markHostHcInteractiveCell();
|
||||
if (ROOM_CZ_SPOT) appendLobbySystemChat('— เดินไปช่องเขียว แล้วกด F เพื่อเปิดห้องแต่งตัว');
|
||||
if (ROOM_HC_SPOT && hostId === socket.id) appendLobbySystemChat('— เดินไปจุดโฮสต์ (ไอคอนชมพู) แล้วกด F เพื่อเปิด Host Console');
|
||||
clientLobbyMapId = res.mapId != null ? res.mapId : null;
|
||||
hostId = res.hostId || null;
|
||||
spaceName = (res.spaceName || '').trim() || spaceId;
|
||||
@@ -2625,6 +2722,18 @@
|
||||
window.location.href = CREATE_ROOM_URL;
|
||||
});
|
||||
|
||||
socket.on('host-console-settings', (data) => {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (data.maxPlayers != null) maxPlayers = Math.max(peers.size, parseInt(data.maxPlayers, 10) || peers.size);
|
||||
if (data.botSlotCount != null) {
|
||||
lobbyBotSlotCount = Math.max(0, parseInt(data.botSlotCount, 10) || 0);
|
||||
syncLobbyCaseBots();
|
||||
}
|
||||
updatePlayersHud();
|
||||
refreshHostConsoleOverlayIfOpen();
|
||||
if (mapData && canvas) drawLobbyMap();
|
||||
});
|
||||
|
||||
socket.on('peer-ready', (data) => {
|
||||
const p = peers.get(data.id);
|
||||
if (p) { p.ready = data.ready; renderPeers(); redrawLobbyMap(); }
|
||||
@@ -3745,14 +3854,10 @@
|
||||
roomLobbyProfileOverlay.open();
|
||||
});
|
||||
document.getElementById('btn-setting-host')?.addEventListener('click', () => {
|
||||
if (hostId !== socket.id) {
|
||||
alert('เฉพาะ Host เท่านั้น');
|
||||
return;
|
||||
}
|
||||
hostConsoleOverlay.open();
|
||||
openHostConsoleIfHost();
|
||||
});
|
||||
document.getElementById('btn-customize')?.addEventListener('click', () => {
|
||||
roomLobbyProfileOverlay.open();
|
||||
openRoomCustomize();
|
||||
});
|
||||
document.getElementById('room-lobby-profile-logout')?.addEventListener('click', () => {
|
||||
window.location.href = CREATE_ROOM_URL;
|
||||
@@ -4741,8 +4846,10 @@
|
||||
if (!mapData.quizTrueArea) mapData.quizTrueArea = [];
|
||||
if (!mapData.quizFalseArea) mapData.quizFalseArea = [];
|
||||
if (!mapData.quizQuestionArea) mapData.quizQuestionArea = [];
|
||||
ROOM_CZ_SPOT = null; // รีเซ็ตจุดแต่งตัวก่อน แล้วคำนวณใหม่ตาม map ใหม่ (LobbyB ไม่มี customizeSpot → ไม่แสดง icon)
|
||||
ROOM_CZ_SPOT = null;
|
||||
ROOM_HC_SPOT = null;
|
||||
markRoomCzInteractiveCell();
|
||||
markHostHcInteractiveCell();
|
||||
(data.peersSnap || []).forEach(function (row) {
|
||||
if (!row || !row.id) return;
|
||||
const p = peers.get(row.id);
|
||||
@@ -4927,7 +5034,7 @@
|
||||
sessionStorage.setItem('justicePlayLobbyAvatar:' + cidPlay, avEl.src);
|
||||
}
|
||||
} catch (eAv) { /* ignore */ }
|
||||
let q = 'play.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(nick);
|
||||
let q = 'play.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(getProfileDisplayName());
|
||||
if (mid) q += '&map=' + encodeURIComponent(mid);
|
||||
if (lobbyLevelStr) q += '&lobbyLevel=' + encodeURIComponent(lobbyLevelStr);
|
||||
if (caseIdStr) q += '&case=' + encodeURIComponent(caseIdStr);
|
||||
@@ -4998,6 +5105,11 @@
|
||||
openRoomCustomize();
|
||||
return;
|
||||
}
|
||||
if (isHostHcInteractTarget(target) || isNearHostHcSpot(me)) {
|
||||
e.preventDefault();
|
||||
openHostConsoleIfHost();
|
||||
return;
|
||||
}
|
||||
/* F อื่นในโถง (Host เลือกระดับ/คดี และช่องเขียว) — ต้องกดพร้อมก่อน */
|
||||
if (!me.ready) {
|
||||
appendLobbySystemChat('— กดพร้อมก่อน แล้วค่อยกด F');
|
||||
|
||||
@@ -4,57 +4,16 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ล็อบบี้ — Game</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<meta http-equiv="refresh" content="0; url=/Main-Menu/">
|
||||
<script src="/app-base.js?v=2"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var dest = typeof designMainMenuHref === 'function' ? designMainMenuHref() : '/Main-Menu/';
|
||||
window.location.replace(dest);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>ล็อบบี้</h1>
|
||||
<p><a href="index.html">กลับ</a> · <a href="character.html">เลือกตัวละคร</a></p>
|
||||
<p><label>ชื่อคุณ <input type="text" id="nick" placeholder="ชื่อผู้เล่น" value="ผู้เล่น"></label></p>
|
||||
|
||||
<section class="lobby-section">
|
||||
<h2>ห้องสาธารณะ (Public) — เลือกเข้าร่วมได้เลย</h2>
|
||||
<div id="room-list-wrap">
|
||||
<p id="room-list-status">โหลดรายการห้อง...</p>
|
||||
<ul id="room-list"></ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lobby-section">
|
||||
<h2>สร้างห้องใหม่</h2>
|
||||
<p>เลือกประเภทห้อง แล้วกดสร้าง</p>
|
||||
<p>
|
||||
<label><input type="radio" name="room-type" value="public" checked> สาธารณะ (Public)</label> — โชว์ในรายการด้านบน คนอื่น join ได้เลย
|
||||
<br>
|
||||
<label><input type="radio" name="room-type" value="private"> ส่วนตัว (Private)</label> — สร้างรหัสอัตโนมัติ แชร์รหัสเพื่อให้คนอื่นเข้า
|
||||
</p>
|
||||
<p>
|
||||
<label>จำนวนผู้เล่นสูงสุด <select id="max-players">
|
||||
<option value="2">2 คน</option>
|
||||
<option value="3">3 คน</option>
|
||||
<option value="4" selected>4 คน</option>
|
||||
<option value="5">5 คน</option>
|
||||
<option value="6">6 คน</option>
|
||||
<option value="7">7 คน</option>
|
||||
<option value="8">8 คน</option>
|
||||
<option value="9">9 คน</option>
|
||||
<option value="10">10 คน</option>
|
||||
</select></label>
|
||||
</p>
|
||||
<button type="button" id="btn-create-lobby">สร้างห้อง</button>
|
||||
</section>
|
||||
|
||||
<section class="lobby-section">
|
||||
<h2>เข้าร่วมห้องส่วนตัว (Private)</h2>
|
||||
<p>กรอกรหัสห้องที่ได้จาก host เพื่อเข้า</p>
|
||||
<form id="form-join">
|
||||
<label>รหัสห้อง <input type="text" id="space-id" placeholder="กรอกรหัส 6 หลัก" required></label>
|
||||
<button type="submit">เข้าร่วม</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<script src="js/version.js?v=0.0104"></script>
|
||||
<script src="js/lobby.js?v=0.0105"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
<p>กำลังไปเมนูหลัก… <a href="/Main-Menu/">คลิกที่นี่</a> ถ้าไม่ redirect อัตโนมัติ</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3211,9 +3211,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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.0457"></script>
|
||||
<script src="js/play.js?v=0.0459"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<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="css/room-lobby.css?v=0.0168">
|
||||
<link rel="stylesheet" href="css/room-lobby.css?v=0.0172">
|
||||
<link rel="stylesheet" href="css/profile-popup.css?v=6">
|
||||
<link rel="stylesheet" href="css/customize-popup.css?v=40">
|
||||
<style>
|
||||
html, body.room-lobby-page { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; min-height: 100%; }
|
||||
#mic-permission-overlay {
|
||||
@@ -630,14 +632,23 @@
|
||||
}
|
||||
.lobby-preplay-back-wrap img { display: block; width: min(367px, 20vw); height: auto; }
|
||||
#lobby-preplay-step-case {
|
||||
--lobby-preplay-case-scale: 1;
|
||||
--lobby-preplay-case-gap: calc(20px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-head-width: calc(1120px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-row-width: calc(1400px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-wrap-width: calc(1260px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-row-gap: calc(24px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-wrap-gap: calc(16px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-arrow-size: calc(130px * var(--lobby-preplay-case-scale));
|
||||
--lobby-preplay-case-card-size: calc(439px * var(--lobby-preplay-case-scale));
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: clamp(10px, 1.6vh, 20px);
|
||||
gap: max(calc(10px * var(--lobby-preplay-case-scale)), min(1.6vh, var(--lobby-preplay-case-gap)));
|
||||
}
|
||||
.lobby-preplay-case-head {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
width: min(96vw, 1120px);
|
||||
width: min(96vw, var(--lobby-preplay-case-head-width));
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
@@ -649,8 +660,8 @@
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: clamp(10px, 1.5vw, 24px);
|
||||
width: min(96vw, 1400px);
|
||||
gap: max(calc(10px * var(--lobby-preplay-case-scale, 1)), min(1.5vw, var(--lobby-preplay-case-row-gap, 24px)));
|
||||
width: min(96vw, var(--lobby-preplay-case-row-width));
|
||||
min-height: auto;
|
||||
padding: 0;
|
||||
overflow-x: auto;
|
||||
@@ -659,17 +670,17 @@
|
||||
}
|
||||
.lobby-preplay-case-row-wrap {
|
||||
flex: 0 0 auto;
|
||||
width: min(98vw, 1260px);
|
||||
width: min(98vw, var(--lobby-preplay-case-wrap-width));
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: clamp(6px, 1vw, 16px);
|
||||
gap: max(calc(6px * var(--lobby-preplay-case-scale, 1)), min(1vw, var(--lobby-preplay-case-wrap-gap, 16px)));
|
||||
}
|
||||
.lobby-preplay-case-arrow {
|
||||
flex: 0 0 auto;
|
||||
width: clamp(44px, 8vw, 130px);
|
||||
height: clamp(44px, 8vw, 130px);
|
||||
width: max(calc(44px * var(--lobby-preplay-case-scale, 1)), min(8vw, var(--lobby-preplay-case-arrow-size, 130px)));
|
||||
height: max(calc(44px * var(--lobby-preplay-case-scale, 1)), min(8vw, var(--lobby-preplay-case-arrow-size, 130px)));
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
@@ -709,15 +720,15 @@
|
||||
}
|
||||
}
|
||||
.lobby-case-card-wrap {
|
||||
flex: 0 0 clamp(220px, 26vw, 439px);
|
||||
width: clamp(220px, 26vw, 439px);
|
||||
min-width: clamp(220px, 26vw, 439px);
|
||||
max-width: clamp(220px, 26vw, 439px);
|
||||
flex: 0 0 max(calc(220px * var(--lobby-preplay-case-scale, 1)), min(26vw, var(--lobby-preplay-case-card-size, 439px)));
|
||||
width: max(calc(220px * var(--lobby-preplay-case-scale, 1)), min(26vw, var(--lobby-preplay-case-card-size, 439px)));
|
||||
min-width: max(calc(220px * var(--lobby-preplay-case-scale, 1)), min(26vw, var(--lobby-preplay-case-card-size, 439px)));
|
||||
max-width: max(calc(220px * var(--lobby-preplay-case-scale, 1)), min(26vw, var(--lobby-preplay-case-card-size, 439px)));
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: clamp(6px, 1vh, 12px);
|
||||
gap: max(calc(6px * var(--lobby-preplay-case-scale, 1)), min(1vh, calc(12px * var(--lobby-preplay-case-scale, 1))));
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
@@ -1241,7 +1252,7 @@
|
||||
<span>เลือกผู้ต้องสงสัย</span>
|
||||
</button>
|
||||
<div class="room-lobby-top-right-cluster" role="group" aria-label="โปรไฟล์ตัวละคร">
|
||||
<div class="room-lobby-top-right-actions" role="group" aria-label="ปุ่มตั้งค่าโฮสต์และปรับแต่ง">
|
||||
<div class="room-lobby-top-right-actions is-hidden" hidden role="group" aria-label="ปุ่มตั้งค่าโฮสต์และปรับแต่ง" aria-hidden="true">
|
||||
<button type="button" id="btn-setting-host" class="room-lobby-top-right-action-btn" title="ตั้งค่าโฮสต์" aria-label="ตั้งค่าโฮสต์">
|
||||
<img src="img/btn-setting-host.png" alt="" width="161" height="176" decoding="async">
|
||||
</button>
|
||||
@@ -1351,9 +1362,9 @@
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="room-lobby-profile-archiv-scroll">
|
||||
<img class="room-lobby-profile-archiv-scroll-bg" src="img/03-6-Profile/archiv-scroll-bg.png" alt="" decoding="async">
|
||||
<img class="room-lobby-profile-archiv-scroll-bar" src="img/03-6-Profile/archiv-scroll-bar.png" alt="" decoding="async">
|
||||
<div class="room-lobby-profile-archiv-scroll is-hidden" aria-hidden="true">
|
||||
<div class="room-lobby-profile-archiv-scroll-bg"></div>
|
||||
<div class="room-lobby-profile-archiv-scroll-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="room-lobby-profile-settings">
|
||||
@@ -1472,12 +1483,12 @@
|
||||
</div>
|
||||
<div class="room-lobby-br-fixed" id="room-lobby-br-fixed">
|
||||
<div id="lobby-b-extra-row" class="lobby-b-extra-row is-hidden" aria-hidden="true">
|
||||
<button type="button" class="lobby-b-sq-btn" id="lobby-b-btn-evidence" title="สืบสวน / หลักฐาน" aria-label="สืบสวน หลักฐาน">
|
||||
<img src="img/lobby-b/icon-evidence.png?v=1" alt="" width="56" height="56" decoding="async">
|
||||
</button>
|
||||
<button type="button" class="lobby-b-sq-btn" id="lobby-b-btn-rank" title="อันดับ / รางวัล" aria-label="อันดับ รางวัล">
|
||||
<img src="img/lobby-b/icon-rank.png?v=1" alt="" width="56" height="56" decoding="async">
|
||||
</button>
|
||||
<button type="button" class="lobby-b-sq-btn" id="lobby-b-btn-evidence" title="สืบสวน / หลักฐาน" aria-label="สืบสวน หลักฐาน">
|
||||
<img src="img/lobby-b/icon-evidence.png?v=1" alt="" width="56" height="56" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" id="btn-voice" class="btn-voice-icon" title="เปิด/ปิดเสียงพูด" aria-label="เปิด/ปิดเสียงพูด">
|
||||
<img src="img/btn-mic-mute.png" alt="เปิดเสียง" id="btn-voice-icon-img">
|
||||
@@ -1530,10 +1541,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/app-base.js?v=1"></script>
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/display-name.js?v=2"></script>
|
||||
<script src="js/version.js?v=0.0122"></script>
|
||||
<script src="js/room-lobby.js?v=0.0205"></script>
|
||||
<script src="js/customize-popup.js?v=29" data-customize-triggers="" data-customize-asset-base="img/03-5-Customize"></script>
|
||||
<script src="js/room-lobby.js?v=0.0218"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,7 +4,9 @@ const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const { Server } = require('socket.io');
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const PORT = parseInt(process.env.PORT, 10) || 3001;
|
||||
/** Windows dev: bind 127.0.0.1 (0.0.0.0 มัก EACCES ถ้าพอร์ตอยู่ใน excluded range ของ Hyper-V) */
|
||||
const HOST = process.env.HOST || process.env.GAME_HOST || '0.0.0.0';
|
||||
const BASE_PATH = '/Game';
|
||||
const AI_SETTINGS_PATH = path.join(__dirname, 'data', 'ai-settings.json');
|
||||
const ADMIN_PASSWORD = process.env.GAME_AI_ADMIN_PASSWORD || 'game-ai-admin';
|
||||
@@ -2440,7 +2442,23 @@ function isMapNameTaken(name, excludeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyDevCors(req, res) {
|
||||
const origin = req.headers.origin;
|
||||
if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
res.setHeader('Vary', 'Origin');
|
||||
}
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
applyDevCors(req, res);
|
||||
if (req.method === 'OPTIONS' && req.url && req.url.indexOf(BASE_PATH + '/api') === 0) {
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
let url = req.url;
|
||||
if (url === BASE_PATH || url === BASE_PATH + '/') url = BASE_PATH + '/index.html';
|
||||
if (url.startsWith(BASE_PATH + '/api/maps')) {
|
||||
@@ -4409,6 +4427,33 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
});
|
||||
|
||||
/** เปลี่ยนชื่อที่แสดงใน lobby / เกม (ซิงก์ทุก client ในห้อง) */
|
||||
socket.on('update-display-name', ({ nickname }, cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space) return reply({ ok: false, error: 'ไม่อยู่ในห้อง' });
|
||||
const p = space.peers.get(socket.id);
|
||||
if (!p) return reply({ ok: false, error: 'ไม่พบผู้เล่น' });
|
||||
let safe = String(nickname || '').trim();
|
||||
if (!safe) return reply({ ok: false, error: 'ชื่อว่าง' });
|
||||
try {
|
||||
safe = safe.normalize('NFKC');
|
||||
} catch (e) { /* ignore */ }
|
||||
if (safe.length > 32) safe = safe.slice(0, 32);
|
||||
const oldNorm = normalizeLobbyNickname(p.nickname);
|
||||
const newNorm = normalizeLobbyNickname(safe);
|
||||
p.nickname = safe;
|
||||
if (space.caseParticipantNicknames && space.caseParticipantNicknames.size > 0) {
|
||||
if (space.caseParticipantNicknames.has(oldNorm)) {
|
||||
space.caseParticipantNicknames.delete(oldNorm);
|
||||
space.caseParticipantNicknames.add(newNorm);
|
||||
}
|
||||
}
|
||||
io.to(sid).emit('peer-display-name', { id: socket.id, nickname: safe });
|
||||
reply({ ok: true, nickname: safe });
|
||||
});
|
||||
|
||||
/** ห้องทดสอบจากเอดิเตอร์ (ชื่อ preview) — สลับโฮสต์ให้เครื่องนี้กดเริ่มเกมได้ */
|
||||
socket.on('preview-claim-host', (cb) => {
|
||||
const reply = typeof cb === 'function' ? cb : () => {};
|
||||
@@ -4605,6 +4650,55 @@ io.on('connection', (socket) => {
|
||||
reply({ ok: true });
|
||||
});
|
||||
|
||||
/** Host Console — ตั้งจำนวนรวม (คน+บอท) ไม่เกิน LOBBY_SLOT_TOTAL */
|
||||
socket.on('host-console-apply', (data, cb) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || space.hostId !== socket.id) {
|
||||
return cb && cb({ ok: false, error: 'เฉพาะ Host เท่านั้น' });
|
||||
}
|
||||
const humans = space.peers.size;
|
||||
let total = parseInt(data && data.totalSlots, 10);
|
||||
if (!Number.isFinite(total)) total = humans + effectiveBotSlotCount(space);
|
||||
total = Math.min(LOBBY_SLOT_TOTAL, Math.max(humans, total));
|
||||
let bots = parseInt(data && data.botSlotCount, 10);
|
||||
if (!Number.isFinite(bots)) bots = total - humans;
|
||||
bots = Math.max(0, Math.min(LOBBY_SLOT_TOTAL - humans, bots));
|
||||
total = humans + bots;
|
||||
space.botSlotCount = bots;
|
||||
space.maxPlayers = Math.max(humans, total - bots);
|
||||
io.to(sid).emit('host-console-settings', {
|
||||
maxPlayers: space.maxPlayers,
|
||||
botSlotCount: space.botSlotCount,
|
||||
totalSlots: total
|
||||
});
|
||||
cb && cb({ ok: true, maxPlayers: space.maxPlayers, botSlotCount: space.botSlotCount, totalSlots: total });
|
||||
});
|
||||
|
||||
socket.on('host-console-kick-peer', ({ targetId }, cb) => {
|
||||
const sid = socket.data.spaceId;
|
||||
const space = sid ? spaces.get(sid) : null;
|
||||
if (!space || space.hostId !== socket.id) {
|
||||
return cb && cb({ ok: false, error: 'เฉพาะ Host เท่านั้น' });
|
||||
}
|
||||
const tid = targetId != null ? String(targetId) : '';
|
||||
if (!tid || tid === socket.id || tid === space.hostId) {
|
||||
return cb && cb({ ok: false, error: 'ไม่สามารถลบผู้เล่นนี้ได้' });
|
||||
}
|
||||
if (!space.peers.has(tid)) {
|
||||
return cb && cb({ ok: false, error: 'ไม่พบผู้เล่นในห้อง' });
|
||||
}
|
||||
const targetSock = io.sockets.sockets.get(tid);
|
||||
if (targetSock) {
|
||||
targetSock.emit('host-console-kicked', { message: 'คุณถูก Host นำออกจากห้อง' });
|
||||
targetSock.leave(sid);
|
||||
delete targetSock.data.spaceId;
|
||||
}
|
||||
space.peers.delete(tid);
|
||||
io.to(sid).emit('user-left', { id: tid });
|
||||
cb && cb({ ok: true });
|
||||
});
|
||||
|
||||
/* พร้อมแล้ว — ไม่บังคับจำนวนคนในห้อง */
|
||||
socket.on('set-ready', ({ ready }) => {
|
||||
for (const [sid, space] of spaces) {
|
||||
@@ -5183,4 +5277,18 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
loadMaps();
|
||||
server.listen(PORT, () => console.log('Game', PORT));
|
||||
server.on('error', (err) => {
|
||||
if (err && (err.code === 'EACCES' || err.code === 'EADDRINUSE')) {
|
||||
console.error('');
|
||||
console.error('[Game] Cannot listen on', HOST + ':' + PORT, '(' + err.code + ').');
|
||||
console.error(' Windows AppServ:');
|
||||
console.error(' set HOST=127.0.0.1');
|
||||
console.error(' set PORT=13010');
|
||||
console.error(' node server.js');
|
||||
console.error(' Or double-click start-dev.cmd');
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
server.listen(PORT, HOST, () => console.log('Game server listening on http://' + HOST + ':' + PORT + BASE_PATH));
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
REM Windows AppServ — Hyper-V มักจองพอร์ตต่ำ (3001, 3010 = EACCES)
|
||||
REM ใช้ 127.0.0.1:13010 แทน
|
||||
cd /d "%~dp0"
|
||||
set HOST=127.0.0.1
|
||||
set PORT=13010
|
||||
echo.
|
||||
echo Game dev server: http://localhost:%PORT%/Game
|
||||
echo (Create Room / Join จะเรียก API ที่พอร์ตนี้อัตโนมัติ)
|
||||
echo.
|
||||
node server.js
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ถ้ายัง error ลองรัน CMD แบบ Run as administrator
|
||||
echo หรือเปลี่ยนพอร์ต: set PORT=18080
|
||||
pause
|
||||
)
|
||||
@@ -4,42 +4,44 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>เข้าร่วมเกม — JD JUSTICE DIVERS</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="style.css?v=0.004">
|
||||
</head>
|
||||
<body>
|
||||
<div class="join-bg" role="img" aria-label="พื้นหลัง">
|
||||
<img src="/Main-Menu/bg.png" alt="" class="join-bg-img" role="presentation">
|
||||
<img src="../Main-Menu/bg.png" alt="" class="join-bg-img" role="presentation">
|
||||
</div>
|
||||
<div class="join-char-layer" aria-hidden="true" role="img">
|
||||
<img src="/Main-Menu/char-main.png" alt="" class="join-char-img" role="presentation">
|
||||
<img src="../Main-Menu/char-main.png" alt="" class="join-char-img" role="presentation">
|
||||
</div>
|
||||
<div class="join-vignette" aria-hidden="true"></div>
|
||||
|
||||
<div class="join-ui">
|
||||
<div class="join-back-section">
|
||||
<button type="button" class="join-btn-back" id="btn-back" aria-label="ย้อนกลับ">
|
||||
<img src="/Main-Menu/IMAGE/btn-back.png" alt="" class="join-btn-back-img" role="presentation">
|
||||
<img src="../Main-Menu/IMAGE/btn-back.png" alt="" class="join-btn-back-img" role="presentation">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="join-content">
|
||||
<header class="join-header">
|
||||
<img src="IMAGE/txt-joinroom.png" alt="เข้าร่วมเกม" class="join-title-img" role="presentation">
|
||||
<img src="IMAGE/line.png" alt="" class="join-title-line" aria-hidden="true" role="presentation">
|
||||
</header>
|
||||
|
||||
<div class="join-panel">
|
||||
<div class="join-tabs" role="tablist" aria-label="เลือกประเภทห้อง">
|
||||
<button type="button" class="join-tab" id="tab-public" role="tab" aria-selected="true" aria-controls="join-public-view">
|
||||
<img src="IMAGE/btn-publish-room-h.png" alt="ห้องสาธารณะ" class="join-tab-img" id="tab-img-public" role="presentation">
|
||||
</button>
|
||||
<button type="button" class="join-tab" id="tab-private" role="tab" aria-selected="false" aria-controls="join-private-view">
|
||||
<img src="IMAGE/btn-private-room.png" alt="ห้องส่วนตัว" class="join-tab-img" id="tab-img-private" role="presentation">
|
||||
</button>
|
||||
</div>
|
||||
<div class="join-tabs" role="tablist" aria-label="เลือกประเภทห้อง">
|
||||
<button type="button" class="join-tab" id="tab-public" role="tab" aria-selected="true" aria-controls="join-public-view">
|
||||
<img src="IMAGE/btn-publish-room-h.png" alt="ห้องสาธารณะ" class="join-tab-img" id="tab-img-public" role="presentation">
|
||||
</button>
|
||||
<button type="button" class="join-tab" id="tab-private" role="tab" aria-selected="false" aria-controls="join-private-view">
|
||||
<img src="IMAGE/btn-private-room.png" alt="ห้องส่วนตัว" class="join-tab-img" id="tab-img-private" role="presentation">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="join-panel">
|
||||
<div id="join-public-view" class="join-view" role="tabpanel">
|
||||
<div class="join-list-scroll">
|
||||
<div class="join-list" id="room-list-public" aria-label="รายการห้องสาธารณะ"></div>
|
||||
<div class="join-scrollbar-thumb" id="join-scrollbar-thumb" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +60,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="join-private-submit" id="btn-private-join" aria-label="เข้าร่วมห้องส่วนตัว">
|
||||
<img src="IMAGE/btn-join-room.png" alt="เข้าร่วม" class="join-private-submit-img" role="presentation">
|
||||
<img src="IMAGE/btn-join-room-2.png" alt="เข้าร่วม" class="join-private-submit-img" role="presentation">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +80,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="join.js?v=0.003"></script>
|
||||
<script src="../app-base.js?v=2"></script>
|
||||
<script src="../Game/js/display-name.js?v=2"></script>
|
||||
<script src="join.js?v=0.004"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,7 +4,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;
|
||||
@@ -14,7 +14,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const BASE = '/Game';
|
||||
const BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
const SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + (typeof appPath === 'function' ? appPath('/Game') : '/Game');
|
||||
const IMG = {
|
||||
publishOn: 'IMAGE/btn-publish-room-h.png',
|
||||
publishOff: 'IMAGE/btn-publish-room.png',
|
||||
@@ -23,9 +24,26 @@
|
||||
joinOn: 'IMAGE/btn-join-room.png',
|
||||
joinOff: 'IMAGE/btn-join-room-2.png',
|
||||
};
|
||||
const USE_PUBLIC_ROOM_MOCK = false;
|
||||
const MOCK_PUBLIC_ROOMS = [
|
||||
{ spaceId: 'JD-001', spaceName: '[JUSTICE DIVERS #01]', mapName: 'Neo City', peerCount: 2, maxPlayers: 4 },
|
||||
{ spaceId: 'JD-002', spaceName: '[JUSTICE DIVERS #02]', mapName: 'Cyber Lab', peerCount: 5, maxPlayers: 6 },
|
||||
{ spaceId: 'JD-003', spaceName: '[JUSTICE DIVERS #03]', mapName: 'Sky Deck', peerCount: 5, maxPlayers: 5 },
|
||||
{ spaceId: 'JD-004', spaceName: '[JUSTICE DIVERS #04]', mapName: 'Dockyard', peerCount: 2, maxPlayers: 4 },
|
||||
{ spaceId: 'JD-005', spaceName: '[JUSTICE DIVERS #05]', mapName: 'Temple', peerCount: 3, maxPlayers: 4 },
|
||||
{ spaceId: 'JD-006', spaceName: '[JUSTICE DIVERS #06]', mapName: 'Arena', peerCount: 6, maxPlayers: 6 },
|
||||
{ spaceId: 'JD-007', spaceName: '[JUSTICE DIVERS #07]', mapName: 'Outpost', peerCount: 1, maxPlayers: 4 }
|
||||
];
|
||||
|
||||
function getNick() {
|
||||
return (localStorage.getItem('playerName') || '').trim() || 'ผู้เล่น';
|
||||
if (typeof window.jdReadDisplayName === 'function') {
|
||||
return window.jdReadDisplayName();
|
||||
}
|
||||
try {
|
||||
return (localStorage.getItem('roomLobbyDisplayName') || localStorage.getItem('playerName') || '').trim() || 'ผู้เล่น';
|
||||
} catch (e) {
|
||||
return 'ผู้เล่น';
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
@@ -42,13 +60,14 @@
|
||||
var viewPublic = document.getElementById('join-public-view');
|
||||
var viewPrivate = document.getElementById('join-private-view');
|
||||
var listPublic = document.getElementById('room-list-public');
|
||||
var publicScrollThumb = document.getElementById('join-scrollbar-thumb');
|
||||
var tplRow = document.getElementById('tpl-room-row');
|
||||
var passPrivate = document.getElementById('join-private-password');
|
||||
var idPrivate = document.getElementById('join-private-room-id');
|
||||
var btnPrivateJoin = document.getElementById('btn-private-join');
|
||||
|
||||
btnBack?.addEventListener('click', function () {
|
||||
window.location.href = '/Main-Menu/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
|
||||
});
|
||||
|
||||
passPrivate?.addEventListener('input', function (e) {
|
||||
@@ -59,49 +78,85 @@
|
||||
window.location.href = BASE + '/room-lobby.html?space=' + encodeURIComponent(spaceId) + '&nick=' + encodeURIComponent(getNick());
|
||||
}
|
||||
|
||||
function renderPublicRooms(list) {
|
||||
if (!listPublic || !tplRow) return;
|
||||
listPublic.innerHTML = '';
|
||||
if (!Array.isArray(list) || list.length === 0) {
|
||||
listPublic.innerHTML = '<div class="join-list-status">ยังไม่มีห้อง</div>';
|
||||
return;
|
||||
}
|
||||
list.forEach(function (room) {
|
||||
var code = room.spaceId || ('room-' + Math.random().toString(36).slice(2, 8));
|
||||
var name = room.spaceName || code;
|
||||
var mapName = room.mapName || '—';
|
||||
var count = room.peerCount != null ? room.peerCount : 0;
|
||||
var max = room.maxPlayers != null ? room.maxPlayers : 10;
|
||||
var full = count >= max;
|
||||
var node = tplRow.content.cloneNode(true);
|
||||
var nameEl = node.querySelector('.join-room-name');
|
||||
var playersEl = node.querySelector('.join-room-players');
|
||||
var btn = node.querySelector('.join-room-btn');
|
||||
var img = node.querySelector('.join-room-btn-img');
|
||||
|
||||
nameEl.textContent = 'ห้อง : ' + name + ' [' + mapName + ']';
|
||||
playersEl.textContent = 'ผู้เล่น : ' + count + '/' + max;
|
||||
img.src = IMG.joinOn;
|
||||
img.alt = full ? 'เต็ม' : 'เข้าร่วม';
|
||||
|
||||
if (full) {
|
||||
btn.disabled = true;
|
||||
} else {
|
||||
btn.addEventListener('click', function () {
|
||||
goToRoom(room.spaceId);
|
||||
});
|
||||
}
|
||||
listPublic.appendChild(node);
|
||||
});
|
||||
syncPublicScrollbarThumb();
|
||||
}
|
||||
|
||||
function syncPublicScrollbarThumb() {
|
||||
if (!listPublic || !publicScrollThumb) return;
|
||||
var total = listPublic.scrollHeight;
|
||||
var visible = listPublic.clientHeight;
|
||||
var track = listPublic.parentElement ? listPublic.parentElement.clientHeight : visible;
|
||||
var styles = window.getComputedStyle(publicScrollThumb);
|
||||
var topInset = parseFloat(styles.top) || 0;
|
||||
var bottomInset = topInset;
|
||||
var minThumb = parseFloat(styles.minHeight) || 40;
|
||||
var innerTrack = Math.max(0, track - topInset - bottomInset);
|
||||
if (!total || total <= visible || track <= 0) {
|
||||
publicScrollThumb.style.opacity = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
var thumbH = Math.max(minThumb, (visible / total) * innerTrack);
|
||||
var maxY = Math.max(0, innerTrack - thumbH);
|
||||
var ratio = listPublic.scrollTop / Math.max(1, total - visible);
|
||||
var y = topInset + (maxY * ratio);
|
||||
var clampedY = Math.min(topInset + maxY, Math.max(topInset, y));
|
||||
|
||||
publicScrollThumb.style.height = thumbH + 'px';
|
||||
publicScrollThumb.style.transform = 'translateY(' + clampedY + 'px)';
|
||||
publicScrollThumb.style.opacity = '1';
|
||||
}
|
||||
|
||||
function refreshPublicRooms() {
|
||||
if (!listPublic || !tplRow) return;
|
||||
if (USE_PUBLIC_ROOM_MOCK) {
|
||||
renderPublicRooms(MOCK_PUBLIC_ROOMS);
|
||||
return;
|
||||
}
|
||||
listPublic.innerHTML = '<div class="join-list-status" id="room-list-status">โหลดรายการห้อง...</div>';
|
||||
var statusEl = document.getElementById('room-list-status');
|
||||
fetch(BASE + '/api/spaces', { cache: 'no-store' })
|
||||
fetch(SERVER + '/api/spaces?_=' + Date.now())
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (list) {
|
||||
if (!Array.isArray(list)) {
|
||||
if (statusEl) statusEl.textContent = 'โหลดรายการห้องไม่ได้';
|
||||
return;
|
||||
}
|
||||
listPublic.innerHTML = '';
|
||||
if (list.length === 0) {
|
||||
listPublic.innerHTML = '<div class="join-list-status">ยังไม่มีห้อง</div>';
|
||||
return;
|
||||
}
|
||||
list.forEach(function (room) {
|
||||
var code = room.spaceId || ('room-' + Math.random().toString(36).slice(2, 8));
|
||||
var name = room.spaceName || code;
|
||||
var mapName = room.mapName || '—';
|
||||
var count = room.peerCount != null ? room.peerCount : 0;
|
||||
var max = room.maxPlayers != null ? room.maxPlayers : 10;
|
||||
var full = count >= max;
|
||||
var node = tplRow.content.cloneNode(true);
|
||||
var nameEl = node.querySelector('.join-room-name');
|
||||
var playersEl = node.querySelector('.join-room-players');
|
||||
var btn = node.querySelector('.join-room-btn');
|
||||
var img = node.querySelector('.join-room-btn-img');
|
||||
|
||||
nameEl.textContent = escapeHtml(name) + (mapName !== '—' ? ' (ฉาก: ' + escapeHtml(mapName) + ')' : '');
|
||||
playersEl.textContent = count + '/' + max + ' คน' + (full ? ' (เต็ม)' : '');
|
||||
img.src = full ? IMG.joinOff : IMG.joinOn;
|
||||
img.alt = full ? 'เต็ม' : 'เข้าร่วม';
|
||||
|
||||
if (full) {
|
||||
btn.disabled = true;
|
||||
} else {
|
||||
btn.addEventListener('click', function () {
|
||||
goToRoom(room.spaceId);
|
||||
});
|
||||
}
|
||||
listPublic.appendChild(node);
|
||||
});
|
||||
renderPublicRooms(list);
|
||||
})
|
||||
.catch(function () {
|
||||
if (statusEl) statusEl.textContent = 'โหลดรายการห้องไม่ได้';
|
||||
@@ -142,7 +197,13 @@
|
||||
goToRoom(code);
|
||||
});
|
||||
|
||||
refreshPublicRooms();
|
||||
setInterval(refreshPublicRooms, 8000);
|
||||
if (USE_PUBLIC_ROOM_MOCK) {
|
||||
renderPublicRooms(MOCK_PUBLIC_ROOMS);
|
||||
} else {
|
||||
refreshPublicRooms();
|
||||
setInterval(refreshPublicRooms, 8000);
|
||||
}
|
||||
listPublic?.addEventListener('scroll', syncPublicScrollbarThumb);
|
||||
window.addEventListener('resize', syncPublicScrollbarThumb);
|
||||
setTab('public');
|
||||
})();
|
||||
|
||||
@@ -15,6 +15,14 @@ body {
|
||||
background: #0a0b1e;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src: url('../FONTS/NotoSansThai-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.join-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -34,7 +42,7 @@ body {
|
||||
.join-char-layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -47,6 +55,7 @@ body {
|
||||
max-height: 72vh;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
filter: brightness(0.62);
|
||||
}
|
||||
|
||||
.join-vignette {
|
||||
@@ -68,17 +77,18 @@ body {
|
||||
}
|
||||
|
||||
.join-back-section {
|
||||
position: fixed;
|
||||
top: max(12px, env(safe-area-inset-top));
|
||||
left: max(12px, env(safe-area-inset-left));
|
||||
z-index: 10;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.join-btn-back {
|
||||
position: fixed;
|
||||
top: max(clamp(0.5rem, 2vw, 2rem), env(safe-area-inset-top));
|
||||
left: max(clamp(0.5rem, 2vw, 2rem), env(safe-area-inset-left));
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@@ -86,8 +96,10 @@ body {
|
||||
display: block;
|
||||
width: clamp(40px, 10vw, 60px);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
|
||||
.join-content {
|
||||
width: 100%;
|
||||
max-width: min(96vw, 720px);
|
||||
@@ -104,25 +116,40 @@ body {
|
||||
}
|
||||
|
||||
.join-title-img {
|
||||
--join-title-height: 14cqh; /* ปรับความสูง txt-joinroom ที่นี่ */
|
||||
display: block;
|
||||
width: min(88vw, 420px);
|
||||
height: auto;
|
||||
height: var(--join-title-height);
|
||||
width: calc(var(--join-title-height) * 535 / 137);
|
||||
max-width: 88vw;
|
||||
margin-inline: auto;
|
||||
aspect-ratio: 535 / 137;
|
||||
object-fit: contain;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
filter: drop-shadow(0 0 16px rgba(191, 0, 255, 0.55));
|
||||
filter: drop-shadow(0 0 16px rgba(191, 0, 255, 0.55));
|
||||
margin-top: 0cqh;
|
||||
}
|
||||
|
||||
.join-title-line {
|
||||
--line-height: 5cqh; /* ปรับความสูง line.png ที่นี่ */
|
||||
display: block;
|
||||
height: var(--line-height);
|
||||
width: min(calc(var(--line-height) * 1018 / 6), 100%);
|
||||
max-width: none;
|
||||
aspect-ratio: 1018 / 6;
|
||||
object-fit: contain;
|
||||
margin: -2cqh auto -3.5cqh;
|
||||
}
|
||||
|
||||
/* กล่องหลักตาม mock — bg-pop */
|
||||
.join-panel {
|
||||
width: 100%;
|
||||
padding: clamp(14px, 3vw, 22px) clamp(12px, 2.5vw, 20px) clamp(16px, 3vw, 24px);
|
||||
padding: 0px;
|
||||
border-radius: 10px;
|
||||
background-image: url('IMAGE/bg-pop.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
box-shadow:
|
||||
0 0 20px rgba(0, 255, 255, 0.25),
|
||||
0 0 40px rgba(191, 0, 255, 0.15);
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.join-tabs {
|
||||
@@ -130,8 +157,14 @@ body {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
--join-tab-overlap: 0px;
|
||||
gap: clamp(0.5rem, 2vw, 1rem);
|
||||
margin-bottom: clamp(12px, 2.5vw, 18px);
|
||||
margin-bottom: clamp(8px, 1.2cqh, 14px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.join-tab + .join-tab {
|
||||
margin-left: var(--join-tab-overlap);
|
||||
}
|
||||
|
||||
.join-tab {
|
||||
@@ -159,64 +192,105 @@ body {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#join-public-view {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#join-private-view {
|
||||
height: 100%;
|
||||
min-height: min(48vh, 420px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
padding: clamp(8px, 2vh, 16px) 0;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
/* พื้นที่สกรอล + แถบเลื่อนตาม asset */
|
||||
.join-list-scroll {
|
||||
--join-scrollbar-width: clamp(12px, 3.8cqw, 25px); /* ปรับความอ้วน scrollbar ที่นี่ */
|
||||
--join-scrollbar-thumb-inset: clamp(2px, 0.8cqw, 6px);
|
||||
--join-scrollbar-right-offset: clamp(4px, 2.2cqw, 17px); /* ขยับราง+แท่ง scrollbar ไปซ้าย/ขวา */
|
||||
position: relative;
|
||||
padding-right: clamp(10px, 2.5vw, 16px);
|
||||
margin-right: -4px;
|
||||
width: 100%;
|
||||
height: 95%;
|
||||
min-height: 0;
|
||||
padding-right: calc(var(--join-scrollbar-width) + var(--join-scrollbar-thumb-inset) + 2px);
|
||||
margin-top: 1cqh;
|
||||
margin-right: var(--join-scrollbar-right-offset);
|
||||
}
|
||||
|
||||
.join-list-scroll::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 14px;
|
||||
right: var(--join-scrollbar-right-offset);
|
||||
width: var(--join-scrollbar-width);
|
||||
height: 100%;
|
||||
background: url('IMAGE/join-room-scrollbar-bg.png') center top / 100% 100% no-repeat;
|
||||
pointer-events: none;
|
||||
opacity: 0.95;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.join-scrollbar-thumb {
|
||||
position: absolute;
|
||||
right: calc(var(--join-scrollbar-right-offset) + var(--join-scrollbar-thumb-inset));
|
||||
top: var(--join-scrollbar-thumb-inset);
|
||||
width: calc(var(--join-scrollbar-width) - (var(--join-scrollbar-thumb-inset) * 2));
|
||||
min-height: clamp(18px, 4.2cqh, 40px);
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(80, 255, 255, 0.95) 0%, rgba(0, 210, 255, 0.95) 100%);
|
||||
border: clamp(1px, 0.25cqw, 2px) solid rgba(120, 255, 255, 0.9);
|
||||
box-shadow:
|
||||
0 0 10px rgba(0, 255, 255, 0.65),
|
||||
inset 0 0 6px rgba(255, 255, 255, 0.35);
|
||||
transform: translateY(0);
|
||||
opacity: 0;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.join-list {
|
||||
min-height: min(48vh, 420px);
|
||||
max-height: min(48vh, 420px);
|
||||
--join-list-shift-x: 2cqw; /* ขยับรายการห้องไปทางขวา */
|
||||
--join-room-row-stack-offset: -3cqh; /* ปรับระยะห่างแนวตั้งระหว่างห้อง (ติดลบได้) */
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 6px;
|
||||
padding-right: 4px;
|
||||
margin-left: var(--join-list-shift-x);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: clamp(0.25rem, 1vw, 0.45rem); */
|
||||
}
|
||||
|
||||
.join-room-row + .join-room-row {
|
||||
margin-top: var(--join-room-row-stack-offset);
|
||||
}
|
||||
|
||||
.join-list::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.join-list::-webkit-scrollbar-track {
|
||||
background: rgba(0, 40, 60, 0.4);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.join-list::-webkit-scrollbar-thumb {
|
||||
background: url('IMAGE/join-room-scrollbar.png') center / contain no-repeat;
|
||||
background-color: rgba(0, 255, 255, 0.35);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.join-list {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 255, 255, 0.5) rgba(0, 40, 60, 0.4);
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: rgba(0, 255, 255, 1) rgba(0, 40, 60, 0.4);
|
||||
}
|
||||
|
||||
.join-list-status {
|
||||
margin: auto;
|
||||
--join-list-status-font-size: 3.5cqw; /* ปรับขนาดฟอนต์สถานะห้องสาธารณะ */
|
||||
padding: 1rem;
|
||||
font-size: clamp(0.8rem, 2vw, 0.92rem);
|
||||
font-size: var(--join-list-status-font-size);
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', sans-serif;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-align: center;
|
||||
margin-top: 2cqh;
|
||||
}
|
||||
|
||||
/* แถวห้อง — bg-room-name */
|
||||
@@ -225,13 +299,19 @@ body {
|
||||
}
|
||||
|
||||
.join-room-row-inner {
|
||||
--join-room-row-height: 16.5cqh; /* ปรับความสูง bg-room-name ได้ที่นี่ */
|
||||
--join-room-card-padding-y: 1.6cqh; /* ปรับ padding บน/ล่างของกล่องห้อง */
|
||||
--join-room-card-padding-x: 4cqw; /* ปรับ padding ซ้าย/ขวาของกล่องห้อง */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: clamp(0.35rem, 1.2vw, 0.75rem);
|
||||
height: clamp(64px, 14vw, 90px);
|
||||
padding: clamp(8px, 2vw, 12px) clamp(10px, 2.5vw, 16px);
|
||||
height: var(--join-room-row-height);
|
||||
width: calc(var(--join-room-row-height) * (910 / 160));
|
||||
max-width: 100%;
|
||||
aspect-ratio: 910 / 160;
|
||||
padding: var(--join-room-card-padding-y) var(--join-room-card-padding-x);
|
||||
background-image: url('IMAGE/bg-room-name.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
@@ -239,10 +319,12 @@ body {
|
||||
}
|
||||
|
||||
.join-room-name {
|
||||
--join-room-name-font-size: 2.5cqw; /* ปรับขนาดฟอนต์ชื่อห้อง */
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: clamp(0.72rem, 1.9vw, 0.88rem);
|
||||
font-size: var(--join-room-name-font-size);
|
||||
font-weight: 700;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', sans-serif;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 8px rgba(0, 255, 255, 0.25);
|
||||
overflow: hidden;
|
||||
@@ -253,9 +335,11 @@ body {
|
||||
}
|
||||
|
||||
.join-room-players {
|
||||
--join-room-players-font-size: 2.5cqw; /* ปรับขนาดฟอนต์จำนวนผู้เล่น */
|
||||
flex: 0 0 auto;
|
||||
font-size: clamp(0.72rem, 1.9vw, 0.88rem);
|
||||
font-size: var(--join-room-players-font-size);
|
||||
font-weight: 600;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', sans-serif;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
line-height: 1.3;
|
||||
@@ -272,58 +356,90 @@ body {
|
||||
|
||||
.join-room-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.85;
|
||||
filter: grayscale(0.35) brightness(0.65);
|
||||
opacity: 0.5;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.join-room-btn-img {
|
||||
--join-room-btn-height: 12cqh; /* ปรับความสูงปุ่มเข้าร่วมในรายการห้อง */
|
||||
display: block;
|
||||
width: clamp(72px, 20vw, 110px);
|
||||
height: auto;
|
||||
height: var(--join-room-btn-height);
|
||||
width: calc(var(--join-room-btn-height) * (198 / 119));
|
||||
aspect-ratio: 198 / 119;
|
||||
max-width: 100%;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
transform: translateX(1.5cqw);
|
||||
}
|
||||
|
||||
/* แท็บห้องส่วนตัว — ฟอร์ม */
|
||||
.join-private-form {
|
||||
--join-private-row-width: 100%;
|
||||
--join-private-label-width: 25%; /* ปรับความกว้างคอลัมน์ label */
|
||||
--join-private-pass-scale: 1.15; /* ทำให้ PASSWORD ใหญ่กว่า ROOM ID */
|
||||
--join-private-input-width: 70%; /* ปรับความกว้าง textfield */
|
||||
--join-private-row-gap: 0;
|
||||
--join-private-shift-x: -2cqw; /* ขยับฟอร์มไปทางขวา */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(0.75rem, 2vw, 1.1rem);
|
||||
padding-top: clamp(4px, 1vw, 8px);
|
||||
width: min(96%, 94cqw);
|
||||
max-width: 100%;
|
||||
gap: clamp(0.6rem, 2.2cqw, 2.2cqw);
|
||||
padding-top: 0;
|
||||
margin: auto;
|
||||
transform: translateX(var(--join-private-shift-x));
|
||||
}
|
||||
|
||||
.join-private-row + .join-private-row {
|
||||
margin-top: -4cqh; /* ค่าติดลบได้ */
|
||||
}
|
||||
|
||||
.join-private-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(0.6rem, 2.2vw, 1rem);
|
||||
justify-content: flex-end;
|
||||
width: var(--join-private-row-width);
|
||||
margin-inline: auto;
|
||||
gap: var(--join-private-row-gap);
|
||||
}
|
||||
|
||||
.join-private-label-img {
|
||||
--join-private-label-shift-x: 2.5cqw; /* ขยับ label ไปทางขวา */
|
||||
display: block;
|
||||
height: clamp(28px, 6.5vw, 40px);
|
||||
width: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 211 / 83;
|
||||
object-fit: contain;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
flex: 0 0 auto;
|
||||
flex: 0 0 var(--join-private-label-width);
|
||||
max-width: var(--join-private-label-width);
|
||||
margin-left: auto;
|
||||
transform: translateX(var(--join-private-label-shift-x));
|
||||
}
|
||||
|
||||
.join-private-label-img--pass {
|
||||
height: clamp(26px, 6vw, 36px);
|
||||
--join-private-label-shift-x: 2.5cqw;
|
||||
width: calc(100% * var(--join-private-pass-scale));
|
||||
height: auto;
|
||||
aspect-ratio: 228 / 84;
|
||||
flex: 0 0 calc(var(--join-private-label-width) * var(--join-private-pass-scale));
|
||||
max-width: calc(var(--join-private-label-width) * var(--join-private-pass-scale));
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.join-textfield-wrap {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-width: 200px;
|
||||
max-width: min(100%, 75%);
|
||||
height: clamp(48px, 11vw, 60px);
|
||||
flex: 0 0 var(--join-private-input-width);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
aspect-ratio: 613 / 141;
|
||||
height: auto;
|
||||
max-width: var(--join-private-input-width);
|
||||
}
|
||||
|
||||
.join-textfield-wrap--narrow {
|
||||
flex: 0 1 auto;
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
flex: 0 0 var(--join-private-input-width);
|
||||
width: 100%;
|
||||
max-width: var(--join-private-input-width);
|
||||
}
|
||||
|
||||
.join-textfield-wrap::before {
|
||||
@@ -331,7 +447,7 @@ body {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url('IMAGE/textfield.png');
|
||||
background-size: cover;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
pointer-events: none;
|
||||
@@ -345,16 +461,16 @@ input.join-textfield-input,
|
||||
z-index: 2;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding-top: clamp(0.35rem, 1vw, 0.55rem) !important;
|
||||
padding-bottom: clamp(0.35rem, 1vw, 0.55rem) !important;
|
||||
padding-left: clamp(0.85rem, 2.2vw, 1.15rem) !important;
|
||||
padding-right: clamp(0.85rem, 2.2vw, 1.15rem) !important;
|
||||
padding-top: clamp(0.28rem, 0.9cqw, 0.55rem) !important;
|
||||
padding-bottom: clamp(0.28rem, 0.9cqw, 0.55rem) !important;
|
||||
padding-left: clamp(0.65rem, 1.8cqw, 1.15rem) !important;
|
||||
padding-right: clamp(0.65rem, 1.8cqw, 1.15rem) !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
color: #fff;
|
||||
font-size: clamp(0.88rem, 2.2vw, 1.02rem);
|
||||
font-size: clamp(0.5rem, 2.4cqw, 3cqw);
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', sans-serif;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
@@ -372,9 +488,9 @@ input.join-textfield-input,
|
||||
|
||||
.join-textfield-input--code {
|
||||
text-align: center;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', sans-serif;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: clamp(0.95rem, 2.5vw, 1.15rem);
|
||||
font-size: clamp(0.5rem, 2.7cqw, 3cqw);
|
||||
}
|
||||
|
||||
.join-textfield-input::placeholder {
|
||||
@@ -384,7 +500,7 @@ input.join-textfield-input,
|
||||
.join-private-submit {
|
||||
align-self: center;
|
||||
padding: 0;
|
||||
margin-top: 0.25rem;
|
||||
margin-top: clamp(0.4rem, 1.5cqh, 0.9rem);
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
@@ -397,13 +513,40 @@ input.join-textfield-input,
|
||||
}
|
||||
|
||||
.join-private-submit-img {
|
||||
--join-private-submit-height: 16cqh; /* ปรับความสูงปุ่มเข้าร่วมได้ที่นี่ */
|
||||
display: block;
|
||||
width: min(72vw, 280px);
|
||||
height: auto;
|
||||
height: var(--join-private-submit-height);
|
||||
width: calc(var(--join-private-submit-height) * (404 / 141));
|
||||
aspect-ratio: 404 / 141;
|
||||
max-width: 100%;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
transform: translateX(4cqw);
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.join-private-form {
|
||||
--join-private-label-width: 100%;
|
||||
--join-private-input-width: 100%;
|
||||
}
|
||||
|
||||
.join-private-row {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 0.35rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.join-private-label-img,
|
||||
.join-private-label-img--pass {
|
||||
margin-left: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.join-textfield-wrap {
|
||||
width: min(100%, 86vw);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.join-room-row-inner {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
@@ -420,3 +563,83 @@ input.join-textfield-input,
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
.join-btn-back-img {
|
||||
width: 112px;
|
||||
height: 139px;
|
||||
}
|
||||
|
||||
.join-ui {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100dvh;
|
||||
min-height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.join-content {
|
||||
position: relative;
|
||||
z-index: 40;
|
||||
isolation: isolate;
|
||||
width: min(calc(95dvh * 1), 90vw);
|
||||
max-width: min(calc(95dvh * 1), 90vw);
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
aspect-ratio: 1 / 1;
|
||||
margin: 0 auto;
|
||||
padding: 2.1dvh 0 1.4dvh;
|
||||
justify-content: flex-start;
|
||||
container-type: size;
|
||||
overflow: hidden;
|
||||
gap: 1.2cqh;
|
||||
}
|
||||
|
||||
.join-panel {
|
||||
--join-panel-height: 70cqh; /* ปรับความสูง bg-pop ที่นี่ */
|
||||
height: var(--join-panel-height);
|
||||
width: calc(var(--join-panel-height) * 1008 / 681);
|
||||
max-width: 100%;
|
||||
aspect-ratio: 1008 / 681;
|
||||
padding: 2.4cqh 2.2cqw 2.2cqh;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
#join-private-view {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.join-tabs {
|
||||
--join-tab-height: 14.5cqh; /* ปรับความสูงปุ่มห้องสาธารณะ/ส่วนตัวที่นี่ */
|
||||
--join-tab-overlap: -3cqh; /* ติดลบได้: ปุ่มจะซ้อนกันมากขึ้น */
|
||||
margin-bottom: -4cqh;
|
||||
}
|
||||
|
||||
.join-tab {
|
||||
flex: 0 0 auto;
|
||||
height: var(--join-tab-height);
|
||||
width: calc(var(--join-tab-height) * 499 / 143);
|
||||
aspect-ratio: 499 / 143;
|
||||
}
|
||||
|
||||
.join-tab-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 499 / 143;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>เข้าสู่ระบบ — JD JUSTICE DIVERS</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="style.css?v=20260526">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-bg" role="img" aria-label="พื้นหลัง">
|
||||
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,235 @@
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src: url('../FONTS/NotoSansThai-ExtraBold.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.daily-popup-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10120;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.daily-popup-overlay.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.daily-popup-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.72);
|
||||
}
|
||||
|
||||
.daily-popup-dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(1181px, calc(100vw - 24px), calc((100dvh - 24px) * (1181 / 960)));
|
||||
aspect-ratio: 1181 / 960;
|
||||
max-height: calc(100dvh - 24px);
|
||||
--dr-scale: 1;
|
||||
font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.daily-popup-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.daily-popup-close {
|
||||
position: absolute;
|
||||
top: calc(85px * var(--dr-scale));
|
||||
right: calc(75px * var(--dr-scale));
|
||||
width: calc(78px * var(--dr-scale));
|
||||
height: calc(78px * var(--dr-scale));
|
||||
border: none;
|
||||
background: #fff;
|
||||
opacity: 0;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
cursor: pointer;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.daily-popup-content {
|
||||
position: absolute;
|
||||
inset: calc(122px * var(--dr-scale)) calc(62px * var(--dr-scale)) calc(80px * var(--dr-scale));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.daily-popup-reset {
|
||||
margin: calc(50px * var(--dr-scale)) 0 calc(25px * var(--dr-scale));
|
||||
text-align: center;
|
||||
color: #d9e5ff;
|
||||
font-size: calc(30px * var(--dr-scale));
|
||||
}
|
||||
|
||||
.daily-popup-grid {
|
||||
--daily-popup-grid-gap-x: calc(-10px * var(--dr-scale));
|
||||
--daily-popup-grid-gap-y: calc(-10px * var(--dr-scale));
|
||||
margin: calc(var(--daily-popup-grid-gap-y) / -2) calc(var(--daily-popup-grid-gap-x) / -2) 0;
|
||||
width: calc(1052px * var(--dr-scale));
|
||||
max-width: 100%;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.daily-popup-card {
|
||||
position: relative;
|
||||
width: calc(254px * var(--dr-scale));
|
||||
height: calc(311px * var(--dr-scale));
|
||||
isolation: isolate;
|
||||
flex: 0 0 auto;
|
||||
margin: calc(var(--daily-popup-grid-gap-y) / 2) calc(var(--daily-popup-grid-gap-x) / 2);
|
||||
}
|
||||
|
||||
.daily-popup-card.day-7 {
|
||||
width: calc(505px * var(--dr-scale));
|
||||
height: calc(319px * var(--dr-scale));
|
||||
}
|
||||
|
||||
.daily-popup-card-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.daily-popup-received-top-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.daily-popup-card-inner {
|
||||
--daily-day-offset-y: calc(20px * var(--dr-scale));
|
||||
--daily-claim-offset-y: calc(-11px * var(--dr-scale));
|
||||
position: absolute;
|
||||
inset: calc(12px * var(--dr-scale)) calc(10px * var(--dr-scale)) calc(14px * var(--dr-scale));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.daily-popup-day-img {
|
||||
width: calc(101px * var(--dr-scale));
|
||||
aspect-ratio: 101 / 41;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
margin-top: var(--daily-day-offset-y);
|
||||
}
|
||||
|
||||
.daily-popup-icon-wrap {
|
||||
position: relative;
|
||||
margin-top: calc(-10px * var(--dr-scale));
|
||||
width: calc(161px * var(--dr-scale));
|
||||
aspect-ratio: 1 / 1;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.daily-popup-icon-bg,
|
||||
.daily-popup-reward,
|
||||
.daily-popup-icon-frame,
|
||||
.daily-popup-icon-lock-overlay,
|
||||
.daily-popup-redeemed {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.daily-popup-reward {
|
||||
width: calc(151px * 1 * var(--dr-scale));
|
||||
aspect-ratio: 151 / 101;
|
||||
height: auto;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(4%, 27%);
|
||||
inset: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.daily-popup-redeemed {
|
||||
width: calc(89px * var(--dr-scale));
|
||||
aspect-ratio: 89 / 77;
|
||||
height: auto;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(50%, 50%);
|
||||
inset: auto;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.daily-popup-redeemed.is-claim-animate {
|
||||
animation: dailyRedeemedPopIn 360ms cubic-bezier(0.2, 0.9, 1, 1) both;
|
||||
}
|
||||
|
||||
@keyframes dailyRedeemedPopIn {
|
||||
0% {
|
||||
transform: translate(50%, 50%) scale(1.55);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translate(50%, 50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.daily-popup-status-img {
|
||||
margin-top: calc(5px * var(--dr-scale));
|
||||
width: calc(151px * var(--dr-scale));
|
||||
aspect-ratio: 151 / 41;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.daily-popup-claim-btn {
|
||||
margin-top: var(--daily-claim-offset-y);
|
||||
width: calc(208px * 1 * var(--dr-scale));
|
||||
aspect-ratio: 208 / 76;
|
||||
margin-left: calc(6px * var(--dr-scale));
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.daily-popup-claim-btn-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.daily-popup-preload {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function appPath(path) {
|
||||
if (typeof window.appPath === 'function') return window.appPath(path);
|
||||
if (typeof window.projectPath === 'function') return window.projectPath(path);
|
||||
var base = '';
|
||||
try {
|
||||
base = window.APP_BASE || sessionStorage.getItem('APP_BASE') || localStorage.getItem('APP_BASE') || '';
|
||||
} catch (e) {
|
||||
base = window.APP_BASE || '';
|
||||
}
|
||||
var normalized = String(path || '/').replace(/\\/g, '/');
|
||||
normalized = normalized.startsWith('/') ? normalized : '/' + normalized;
|
||||
return (String(base || '').replace(/\/$/, '')) + normalized;
|
||||
}
|
||||
|
||||
var DEFAULT_ASSET_BASE = 'IMAGE/Daily';
|
||||
|
||||
function makeAssetResolver(base) {
|
||||
var root = String(base || DEFAULT_ASSET_BASE).replace(/\\/g, '/').replace(/\/+$/, '');
|
||||
return function asset(fileName) {
|
||||
var cleanFile = String(fileName || '').replace(/^\/+/, '');
|
||||
if (/^(?:https?:)?\/\//i.test(root)) {
|
||||
return root + '/' + cleanFile;
|
||||
}
|
||||
/* ใช้ path เทียบกับหน้า Main-Lobby เสมอ — ไม่ผูก APP_BASE (กัน localhost ดึงรูปจาก production) */
|
||||
var relRoot = root.replace(/^\/+/, '');
|
||||
if (/^Main-Lobby\//i.test(relRoot)) {
|
||||
relRoot = relRoot.slice('Main-Lobby/'.length);
|
||||
}
|
||||
try {
|
||||
return new URL(relRoot + '/' + cleanFile, window.location.href).href;
|
||||
} catch (e) {
|
||||
return relRoot + '/' + cleanFile;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function findDatasetScript() {
|
||||
var scripts = Array.prototype.slice.call(document.querySelectorAll('script[src]'));
|
||||
for (var i = 0; i < scripts.length; i += 1) {
|
||||
var src = scripts[i].getAttribute('src') || '';
|
||||
if (src.indexOf('daily-popup.js') !== -1) return scripts[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var scriptEl = findDatasetScript();
|
||||
var config = (scriptEl && scriptEl.dataset) || {};
|
||||
var asset = makeAssetResolver(config.dailyAssetBase || DEFAULT_ASSET_BASE);
|
||||
var triggerSelector = config.dailyTrigger || '#btn-daily';
|
||||
var TOTAL_DAYS = 7;
|
||||
var testResetSeconds = Math.max(0, Number(config.dailyTestResetSeconds || 0));
|
||||
var isFastTestMode = testResetSeconds > 0;
|
||||
var testResetMs = testResetSeconds * 1000;
|
||||
var normalResetMs = 24 * 60 * 60 * 1000;
|
||||
var activeResetMs = isFastTestMode ? testResetMs : normalResetMs;
|
||||
var DAILY_STORAGE_KEY = 'jdDailyProgressV3:shared';
|
||||
|
||||
var state = {
|
||||
isOpen: false,
|
||||
claimedAnimDay: null,
|
||||
currentDay: 3,
|
||||
msUntilReset: 0,
|
||||
progress: null
|
||||
};
|
||||
|
||||
var refs = {
|
||||
overlay: null,
|
||||
dialog: null,
|
||||
cardsEl: null,
|
||||
resetEl: null
|
||||
};
|
||||
var resetTimerId = null;
|
||||
var dailyDotTimerId = null;
|
||||
|
||||
function pad2(value) {
|
||||
return String(value).padStart(2, '0');
|
||||
}
|
||||
|
||||
function createDefaultProgress(nowMs) {
|
||||
var anchorMs = nowMs;
|
||||
if (!isFastTestMode) {
|
||||
var midnight = new Date(nowMs);
|
||||
midnight.setHours(0, 0, 0, 0);
|
||||
anchorMs = midnight.getTime();
|
||||
}
|
||||
return {
|
||||
anchorMs: anchorMs,
|
||||
claimedDays: [false, false, false, false, false, false, false],
|
||||
lockUntilMs: 0,
|
||||
timingKey: isFastTestMode ? ('test-' + String(testResetSeconds)) : 'normal'
|
||||
};
|
||||
}
|
||||
|
||||
function getFirstUnclaimedDay(claimedDays) {
|
||||
for (var i = 0; i < TOTAL_DAYS; i += 1) {
|
||||
if (!claimedDays[i]) return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function normalizeProgressTiming(progress, nowMs) {
|
||||
var currentTimingKey = isFastTestMode ? ('test-' + String(testResetSeconds)) : 'normal';
|
||||
if (progress.timingKey === currentTimingKey) return;
|
||||
|
||||
var firstUnclaimed = getFirstUnclaimedDay(progress.claimedDays);
|
||||
if (firstUnclaimed > 0) {
|
||||
// ย้ายโหมดเวลาแล้วให้วันที่รอเคลม "ขึ้นกดได้" ทันทีในโหมดใหม่
|
||||
if (isFastTestMode) {
|
||||
progress.anchorMs = nowMs - Math.max(0, firstUnclaimed - 1) * activeResetMs;
|
||||
} else {
|
||||
var nowMidnight = new Date(nowMs);
|
||||
nowMidnight.setHours(0, 0, 0, 0);
|
||||
progress.anchorMs = nowMidnight.getTime() - Math.max(0, firstUnclaimed - 1) * normalResetMs;
|
||||
}
|
||||
progress.lockUntilMs = 0;
|
||||
} else {
|
||||
// เคลมครบ 7 แล้ว ให้รออีก 1 รอบของโหมดใหม่ก่อนรีเซ็ต
|
||||
if (isFastTestMode) {
|
||||
progress.anchorMs = nowMs;
|
||||
progress.lockUntilMs = nowMs + activeResetMs;
|
||||
} else {
|
||||
var anchorMidnight = new Date(nowMs);
|
||||
anchorMidnight.setHours(0, 0, 0, 0);
|
||||
progress.anchorMs = anchorMidnight.getTime();
|
||||
progress.lockUntilMs = nowMs + getMsUntilNextMidnight(nowMs);
|
||||
}
|
||||
}
|
||||
progress.timingKey = currentTimingKey;
|
||||
}
|
||||
|
||||
function loadProgress() {
|
||||
var nowMs = Date.now();
|
||||
try {
|
||||
var raw = localStorage.getItem(DAILY_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
var legacyKeys = [
|
||||
'jdDailyProgressV3:normal',
|
||||
'jdDailyProgressV3:test-' + String(testResetSeconds),
|
||||
'jdDailyTestStateV2:' + String(testResetSeconds || 'default')
|
||||
];
|
||||
for (var i = 0; i < legacyKeys.length; i += 1) {
|
||||
raw = localStorage.getItem(legacyKeys[i]);
|
||||
if (raw) {
|
||||
localStorage.setItem(DAILY_STORAGE_KEY, raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!raw) {
|
||||
var fresh = createDefaultProgress(nowMs);
|
||||
localStorage.setItem(DAILY_STORAGE_KEY, JSON.stringify(fresh));
|
||||
return fresh;
|
||||
}
|
||||
var parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== 'object') throw new Error('invalid');
|
||||
if (!Array.isArray(parsed.claimedDays) || parsed.claimedDays.length !== TOTAL_DAYS) throw new Error('invalid');
|
||||
if (typeof parsed.anchorMs !== 'number' || !isFinite(parsed.anchorMs)) throw new Error('invalid');
|
||||
if (typeof parsed.lockUntilMs !== 'number' || !isFinite(parsed.lockUntilMs)) parsed.lockUntilMs = 0;
|
||||
normalizeProgressTiming(parsed, nowMs);
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
var fallback = createDefaultProgress(nowMs);
|
||||
try { localStorage.setItem(DAILY_STORAGE_KEY, JSON.stringify(fallback)); } catch (e2) { /* ignore */ }
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function saveProgress() {
|
||||
if (!state.progress) return;
|
||||
try {
|
||||
localStorage.setItem(DAILY_STORAGE_KEY, JSON.stringify(state.progress));
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function syncProgress() {
|
||||
if (!state.progress) return false;
|
||||
var progress = state.progress;
|
||||
var nowMs = Date.now();
|
||||
var elapsedMs = Math.max(0, nowMs - progress.anchorMs);
|
||||
var slotMs = elapsedMs % activeResetMs;
|
||||
var dayIndex = Math.floor(elapsedMs / activeResetMs);
|
||||
var unlockedByTimeDay = Math.min(TOTAL_DAYS, dayIndex + 1);
|
||||
var remainMs = activeResetMs - slotMs;
|
||||
if (remainMs <= 0 || remainMs > activeResetMs) remainMs = activeResetMs;
|
||||
if (!isFastTestMode) {
|
||||
var anchorMidnight = new Date(progress.anchorMs);
|
||||
anchorMidnight.setHours(0, 0, 0, 0);
|
||||
var nowMidnight = new Date(nowMs);
|
||||
nowMidnight.setHours(0, 0, 0, 0);
|
||||
dayIndex = Math.max(0, Math.floor((nowMidnight.getTime() - anchorMidnight.getTime()) / normalResetMs));
|
||||
unlockedByTimeDay = Math.min(TOTAL_DAYS, dayIndex + 1);
|
||||
remainMs = getMsUntilNextMidnight(nowMs);
|
||||
}
|
||||
var firstUnclaimedDay = 0;
|
||||
for (var i = 0; i < TOTAL_DAYS; i += 1) {
|
||||
if (!progress.claimedDays[i]) {
|
||||
firstUnclaimedDay = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
|
||||
// Self-heal: ถ้าข้อมูลเก่าค้างในสถานะเคลมครบแต่ไม่มี lockUntil ให้ตั้ง lock ถึง boundary ถัดไป
|
||||
if (!firstUnclaimedDay && Number(progress.lockUntilMs || 0) <= 0) {
|
||||
progress.lockUntilMs = nowMs + Math.max(1, remainMs);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// รีเซ็ตรอบใหม่เฉพาะตอนเคลมครบ 7 วันแล้ว และถึง boundary ถัดไปเท่านั้น
|
||||
if (!firstUnclaimedDay && Number(progress.lockUntilMs || 0) > 0 && nowMs >= progress.lockUntilMs) {
|
||||
if (isFastTestMode) {
|
||||
progress.anchorMs = progress.lockUntilMs;
|
||||
} else {
|
||||
var resetMidnight = new Date(nowMs);
|
||||
resetMidnight.setHours(0, 0, 0, 0);
|
||||
progress.anchorMs = resetMidnight.getTime();
|
||||
}
|
||||
progress.claimedDays = [false, false, false, false, false, false, false];
|
||||
progress.lockUntilMs = 0;
|
||||
elapsedMs = Math.max(0, nowMs - progress.anchorMs);
|
||||
slotMs = elapsedMs % activeResetMs;
|
||||
dayIndex = Math.floor(elapsedMs / activeResetMs);
|
||||
unlockedByTimeDay = Math.min(TOTAL_DAYS, dayIndex + 1);
|
||||
remainMs = activeResetMs - slotMs;
|
||||
if (remainMs <= 0 || remainMs > activeResetMs) remainMs = activeResetMs;
|
||||
if (!isFastTestMode) {
|
||||
remainMs = getMsUntilNextMidnight(nowMs);
|
||||
}
|
||||
firstUnclaimedDay = 1;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var currentDay = 0;
|
||||
if (firstUnclaimedDay > 0 && firstUnclaimedDay <= unlockedByTimeDay) {
|
||||
currentDay = firstUnclaimedDay;
|
||||
}
|
||||
var canUnlockByTime = firstUnclaimedDay > 0 && firstUnclaimedDay <= unlockedByTimeDay;
|
||||
var lockActive = Number(progress.lockUntilMs || 0) > nowMs;
|
||||
var gatedDay = canUnlockByTime && !lockActive ? firstUnclaimedDay : 0;
|
||||
if (!lockActive && progress.lockUntilMs) {
|
||||
progress.lockUntilMs = 0;
|
||||
changed = true;
|
||||
}
|
||||
if (state.currentDay !== gatedDay) changed = true;
|
||||
|
||||
state.currentDay = gatedDay;
|
||||
state.msUntilReset = remainMs;
|
||||
|
||||
if (changed) saveProgress();
|
||||
return changed;
|
||||
}
|
||||
|
||||
function hasPendingClaim() {
|
||||
for (var day = 1; day <= TOTAL_DAYS; day += 1) {
|
||||
if (getDayStatus(day) === 'claim') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function syncDailyDotIndicator() {
|
||||
var dailyBtn = document.getElementById('btn-daily');
|
||||
if (!dailyBtn) return;
|
||||
var dot = dailyBtn.querySelector('.lobby-daily-dot');
|
||||
if (!dot) return;
|
||||
dot.style.display = hasPendingClaim() ? '' : 'none';
|
||||
}
|
||||
|
||||
function startDailyDotWatcher() {
|
||||
syncDailyDotIndicator();
|
||||
if (dailyDotTimerId != null) return;
|
||||
dailyDotTimerId = window.setInterval(function () {
|
||||
var changed = syncProgress();
|
||||
if (changed && refs.cardsEl && state.isOpen) renderCards();
|
||||
syncDailyDotIndicator();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var ALL_DAILY_ASSETS = [
|
||||
'btn-claim-reward.png',
|
||||
'daily-bg.png',
|
||||
'Day-1-h.png',
|
||||
'Day-1.png',
|
||||
'Day-2-h.png',
|
||||
'Day-2.png',
|
||||
'Day-3-h.png',
|
||||
'Day-3.png',
|
||||
'Day-4-h.png',
|
||||
'Day-4.png',
|
||||
'Day-5-h.png',
|
||||
'Day-5.png',
|
||||
'Day-6-h.png',
|
||||
'Day-6.png',
|
||||
'Day-7-h.png',
|
||||
'Day-7.png',
|
||||
'icon-bg.png',
|
||||
'icon-frame-h.png',
|
||||
'icon-frame-lock.png',
|
||||
'icon-frame.png',
|
||||
'reward-01.png',
|
||||
'reward-02.png',
|
||||
'reward-03.png',
|
||||
'reward-04.png',
|
||||
'reward-05.png',
|
||||
'reward-06.png',
|
||||
'reward-07.png',
|
||||
'reward-redeemed.png',
|
||||
'rewards-frame-d7.png',
|
||||
'rewards-frame-hilight-d7.png',
|
||||
'rewards-frame-hilight.png',
|
||||
'rewards-frame-received-d7.png',
|
||||
'rewards-frame-d7-received-top.png',
|
||||
'rewards-frame-received-top.png',
|
||||
'rewards-frame-received.png',
|
||||
'rewards-frame.png',
|
||||
'txt-locked.png',
|
||||
'txt-recieved.png'
|
||||
];
|
||||
|
||||
function getDayStatus(day) {
|
||||
if (state.progress) {
|
||||
if (state.progress.claimedDays[day - 1]) return 'received';
|
||||
if (day === state.currentDay) return 'claim';
|
||||
return 'locked';
|
||||
}
|
||||
return day === 1 ? 'claim' : 'locked';
|
||||
}
|
||||
|
||||
function getCardFrame(day, status) {
|
||||
var isDay7 = day === 7;
|
||||
if (status === 'received') {
|
||||
if (isDay7) return 'rewards-frame-received-d7.png';
|
||||
return 'rewards-frame-received.png';
|
||||
}
|
||||
if (status === 'claim') return isDay7 ? 'rewards-frame-hilight-d7.png' : 'rewards-frame-hilight.png';
|
||||
return isDay7 ? 'rewards-frame-d7.png' : 'rewards-frame.png';
|
||||
}
|
||||
|
||||
function getIconFrame(status) {
|
||||
if (status === 'claim') return 'icon-frame-h.png';
|
||||
return 'icon-frame.png';
|
||||
}
|
||||
|
||||
function getDayTitle(day, status) {
|
||||
if (status === 'claim') return 'Day-' + day + '-h.png';
|
||||
return 'Day-' + day + '.png';
|
||||
}
|
||||
|
||||
function makeImg(className, src, alt) {
|
||||
var img = document.createElement('img');
|
||||
img.className = className;
|
||||
img.src = src;
|
||||
img.alt = alt || '';
|
||||
img.decoding = 'async';
|
||||
return img;
|
||||
}
|
||||
|
||||
function appendPreloadContainer(parent) {
|
||||
var preload = document.createElement('div');
|
||||
preload.className = 'daily-popup-preload';
|
||||
preload.setAttribute('aria-hidden', 'true');
|
||||
ALL_DAILY_ASSETS.forEach(function (fileName) {
|
||||
preload.appendChild(makeImg('', asset(fileName), ''));
|
||||
});
|
||||
parent.appendChild(preload);
|
||||
}
|
||||
|
||||
function createCard(day) {
|
||||
var status = getDayStatus(day);
|
||||
var li = document.createElement('li');
|
||||
li.className = 'daily-popup-card ' + status + (day === 7 ? ' day-7' : '');
|
||||
|
||||
li.appendChild(makeImg('daily-popup-card-frame', asset(getCardFrame(day, status)), ''));
|
||||
if (status === 'received') {
|
||||
var topOverlayFile = day === 7 ? 'rewards-frame-d7-received-top.png' : 'rewards-frame-received-top.png';
|
||||
li.appendChild(makeImg('daily-popup-received-top-overlay', asset(topOverlayFile), ''));
|
||||
}
|
||||
|
||||
var inner = document.createElement('div');
|
||||
inner.className = 'daily-popup-card-inner';
|
||||
li.appendChild(inner);
|
||||
|
||||
inner.appendChild(makeImg('daily-popup-day-img', asset(getDayTitle(day, status)), 'Day ' + day));
|
||||
|
||||
var iconWrap = document.createElement('div');
|
||||
iconWrap.className = 'daily-popup-icon-wrap';
|
||||
iconWrap.appendChild(makeImg('daily-popup-icon-bg', asset('icon-bg.png'), ''));
|
||||
iconWrap.appendChild(makeImg('daily-popup-reward', asset('reward-0' + day + '.png'), 'Reward Day ' + day));
|
||||
iconWrap.appendChild(makeImg('daily-popup-icon-frame', asset(getIconFrame(status)), ''));
|
||||
if (status === 'locked') {
|
||||
iconWrap.appendChild(makeImg('daily-popup-icon-lock-overlay', asset('icon-frame-lock.png'), ''));
|
||||
}
|
||||
if (status === 'received') {
|
||||
var redeemedClassName = 'daily-popup-redeemed';
|
||||
if (state.claimedAnimDay === day) redeemedClassName += ' is-claim-animate';
|
||||
var redeemedImg = makeImg(redeemedClassName, asset('reward-redeemed.png'), 'Received');
|
||||
if (state.claimedAnimDay === day) {
|
||||
redeemedImg.addEventListener('animationend', function () {
|
||||
redeemedImg.classList.remove('is-claim-animate');
|
||||
}, { once: true });
|
||||
}
|
||||
iconWrap.appendChild(redeemedImg);
|
||||
}
|
||||
inner.appendChild(iconWrap);
|
||||
|
||||
if (status === 'claim') {
|
||||
var claimBtn = document.createElement('button');
|
||||
claimBtn.type = 'button';
|
||||
claimBtn.className = 'daily-popup-claim-btn';
|
||||
claimBtn.appendChild(makeImg('daily-popup-claim-btn-img', asset('btn-claim-reward.png'), 'Claim'));
|
||||
claimBtn.addEventListener('click', function () {
|
||||
if (!state.progress) return;
|
||||
if (day !== state.currentDay) return;
|
||||
if (state.progress.claimedDays[day - 1]) return;
|
||||
state.progress.claimedDays[day - 1] = true;
|
||||
state.progress.lockUntilMs = Date.now() + Math.max(1, state.msUntilReset || activeResetMs);
|
||||
saveProgress();
|
||||
syncProgress();
|
||||
state.claimedAnimDay = day;
|
||||
renderCards();
|
||||
state.claimedAnimDay = null;
|
||||
syncDailyDotIndicator();
|
||||
});
|
||||
inner.appendChild(claimBtn);
|
||||
} else {
|
||||
var statusFile = status === 'received' ? 'txt-recieved.png' : 'txt-locked.png';
|
||||
inner.appendChild(makeImg('daily-popup-status-img', asset(statusFile), status === 'received' ? 'Received' : 'Locked'));
|
||||
}
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
function renderCards() {
|
||||
if (!refs.cardsEl) return;
|
||||
refs.cardsEl.innerHTML = '';
|
||||
for (var day = 1; day <= TOTAL_DAYS; day += 1) refs.cardsEl.appendChild(createCard(day));
|
||||
syncDailyDotIndicator();
|
||||
}
|
||||
|
||||
function getMsUntilNextMidnight(nowMs) {
|
||||
var now = nowMs ? new Date(nowMs) : new Date();
|
||||
var nextMidnight = new Date(now);
|
||||
nextMidnight.setHours(24, 0, 0, 0);
|
||||
return Math.max(0, nextMidnight.getTime() - now.getTime());
|
||||
}
|
||||
|
||||
function renderResetCountdown() {
|
||||
if (!refs.resetEl) return;
|
||||
var changed = syncProgress();
|
||||
if (changed) renderCards();
|
||||
var remainMs = state.msUntilReset || (isFastTestMode ? testResetMs : getMsUntilNextMidnight(Date.now()));
|
||||
var totalSeconds = Math.floor(remainMs / 1000);
|
||||
var hours = Math.floor(totalSeconds / 3600);
|
||||
var minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
var seconds = totalSeconds % 60;
|
||||
refs.resetEl.textContent = 'รีเซ็ตในอีก ' + pad2(hours) + ':' + pad2(minutes) + ':' + pad2(seconds);
|
||||
syncDailyDotIndicator();
|
||||
}
|
||||
|
||||
function startResetCountdown() {
|
||||
renderResetCountdown();
|
||||
if (resetTimerId != null) return;
|
||||
resetTimerId = window.setInterval(renderResetCountdown, 1000);
|
||||
}
|
||||
|
||||
function stopResetCountdown() {
|
||||
if (resetTimerId == null) return;
|
||||
window.clearInterval(resetTimerId);
|
||||
resetTimerId = null;
|
||||
}
|
||||
|
||||
function buildPopup() {
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'daily-popup-overlay';
|
||||
overlay.className = 'daily-popup-overlay is-hidden';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.setAttribute('aria-label', 'รางวัลประจำวัน');
|
||||
|
||||
var backdrop = document.createElement('button');
|
||||
backdrop.type = 'button';
|
||||
backdrop.className = 'daily-popup-backdrop';
|
||||
backdrop.setAttribute('aria-label', 'ปิดหน้าต่างรางวัลประจำวัน');
|
||||
overlay.appendChild(backdrop);
|
||||
|
||||
var dialog = document.createElement('div');
|
||||
dialog.className = 'daily-popup-dialog';
|
||||
dialog.appendChild(makeImg('daily-popup-bg', asset('daily-bg.png'), ''));
|
||||
appendPreloadContainer(dialog);
|
||||
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'daily-popup-close';
|
||||
closeBtn.setAttribute('aria-label', 'ปิด');
|
||||
dialog.appendChild(closeBtn);
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.className = 'daily-popup-content';
|
||||
|
||||
var reset = document.createElement('p');
|
||||
reset.className = 'daily-popup-reset';
|
||||
reset.textContent = 'รีเซ็ตในอีก 00:00:00';
|
||||
content.appendChild(reset);
|
||||
|
||||
var list = document.createElement('ol');
|
||||
list.className = 'daily-popup-grid';
|
||||
content.appendChild(list);
|
||||
dialog.appendChild(content);
|
||||
overlay.appendChild(dialog);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
refs.overlay = overlay;
|
||||
refs.dialog = dialog;
|
||||
refs.cardsEl = list;
|
||||
refs.resetEl = reset;
|
||||
renderCards();
|
||||
|
||||
backdrop.addEventListener('click', closePopup);
|
||||
closeBtn.addEventListener('click', closePopup);
|
||||
overlay.addEventListener('click', function (event) {
|
||||
if (event.target === overlay) closePopup();
|
||||
});
|
||||
}
|
||||
|
||||
function syncScale() {
|
||||
if (!refs.dialog) return;
|
||||
var width = refs.dialog.getBoundingClientRect().width;
|
||||
if (!width) return;
|
||||
refs.dialog.style.setProperty('--dr-scale', String(width / 1181));
|
||||
}
|
||||
|
||||
function openPopup() {
|
||||
if (!refs.overlay) buildPopup();
|
||||
syncProgress();
|
||||
renderCards();
|
||||
refs.overlay.classList.remove('is-hidden');
|
||||
state.isOpen = true;
|
||||
syncScale();
|
||||
startResetCountdown();
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
if (!refs.overlay) return;
|
||||
refs.overlay.classList.add('is-hidden');
|
||||
state.isOpen = false;
|
||||
stopResetCountdown();
|
||||
}
|
||||
|
||||
function bindTriggers() {
|
||||
var triggers = Array.prototype.slice.call(document.querySelectorAll(triggerSelector));
|
||||
triggers.forEach(function (btn) {
|
||||
btn.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
openPopup();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindGlobalEvents() {
|
||||
window.addEventListener('resize', syncScale);
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && state.isOpen) closePopup();
|
||||
});
|
||||
}
|
||||
|
||||
window.openDailyPopup = openPopup;
|
||||
window.closeDailyPopup = closePopup;
|
||||
|
||||
state.progress = loadProgress();
|
||||
syncProgress();
|
||||
|
||||
startDailyDotWatcher();
|
||||
bindTriggers();
|
||||
bindGlobalEvents();
|
||||
})();
|
||||
@@ -7,7 +7,10 @@
|
||||
<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.0175">
|
||||
<link rel="stylesheet" href="style.css?v=0.0189">
|
||||
<link rel="stylesheet" href="daily-popup.css?v=8">
|
||||
<link rel="stylesheet" href="../Game/css/customize-popup.css?v=39">
|
||||
<link rel="stylesheet" href="../Game/css/profile-popup.css?v=5">
|
||||
</head>
|
||||
<body class="lobby-page">
|
||||
<div class="lobby-bg" role="img" aria-label="พื้นหลัง">
|
||||
@@ -146,8 +149,9 @@
|
||||
<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 class="lobby-leaderboard-scroll-track" id="lobby-leaderboard-scroll-track" aria-hidden="true"></div>
|
||||
<div class="lobby-leaderboard-scroll-thumb" id="lobby-leaderboard-scroll-thumb" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -219,38 +223,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ห้องแต่งตัว (Customize) — Phase 1: UI + เลือกธีมสี/สีผิว/ใบหน้า -->
|
||||
<div id="lobby-customize-overlay" class="lobby-customize-overlay hidden" role="dialog" aria-modal="true" aria-label="ห้องแต่งตัว" aria-hidden="true">
|
||||
<div class="lobby-customize-backdrop" id="lobby-customize-backdrop" aria-hidden="true"></div>
|
||||
<div class="lobby-customize-dialog">
|
||||
<div class="lobby-customize-titlebar">
|
||||
<h2 class="lobby-customize-title">ห้องแต่งตัว</h2>
|
||||
<button type="button" class="lobby-customize-close" id="lobby-customize-close" aria-label="ปิด">×</button>
|
||||
</div>
|
||||
<div class="lobby-customize-row">
|
||||
<img src="../Game/img/03-5-Customize/theme-color-txt.png" alt="ธีมสี" class="lobby-customize-row-label" decoding="async">
|
||||
<div class="lobby-customize-swatches" id="lobby-theme-colors"></div>
|
||||
</div>
|
||||
<div class="lobby-customize-row">
|
||||
<img src="../Game/img/03-5-Customize/theme-skin-txt.png" alt="สีผิว" class="lobby-customize-row-label" decoding="async">
|
||||
<div class="lobby-customize-swatches" id="lobby-skin-tones"></div>
|
||||
</div>
|
||||
<div class="lobby-customize-tabs" role="tablist" aria-label="ประเภทการแต่งตัว">
|
||||
<img id="lobby-customize-tabbar" class="lobby-customize-tabbar-img" src="../Game/img/03-5-Customize/tab-1-face.png" alt="" decoding="async">
|
||||
<button type="button" class="lobby-customize-tabhit" data-tab="face" role="tab" aria-selected="true" aria-label="ใบหน้า"></button>
|
||||
<button type="button" class="lobby-customize-tabhit" data-tab="hair" role="tab" aria-selected="false" aria-label="ทรงผม"></button>
|
||||
<button type="button" class="lobby-customize-tabhit" data-tab="cloth" role="tab" aria-selected="false" aria-label="ชุด"></button>
|
||||
</div>
|
||||
<div class="lobby-customize-items" id="lobby-customize-items"></div>
|
||||
<button type="button" class="lobby-customize-confirm" id="lobby-customize-confirm" aria-label="ยืนยัน">
|
||||
<img src="../Game/img/03-5-Customize/btn-cf.png" alt="ยืนยัน" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lobby-toast" class="lobby-toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<script src="../app-base.js?v=1"></script>
|
||||
<script src="lobby.js?v=0.0170"></script>
|
||||
<script src="../app-base.js?v=2"></script>
|
||||
<script src="../Game/js/display-name.js?v=2"></script>
|
||||
<script src="daily-popup.js?v=25" data-daily-trigger="#btn-daily" data-daily-asset-base="IMAGE/Daily" data-daily-test-reset-seconds="0"></script>
|
||||
<script src="../Game/js/customize-popup.js?v=26" data-customize-triggers="#btn-cloth" data-customize-asset-base="/Game/img/03-5-Customize"></script>
|
||||
<script src="../Game/js/profile-popup.js?v=6" data-profile-trigger="#btn-myprofile" data-profile-asset-base="/Game/img/03-6-Profile"></script>
|
||||
<script src="lobby.js?v=0.0190"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
function normalizeCharacterAssetId(id) {
|
||||
var s = String(id == null ? '' : id).trim();
|
||||
if (!s) return '';
|
||||
if (s === 'Chatest') return ''; // legacy placeholder — ไม่มี sprite จริง ให้ถือเป็นว่าง
|
||||
if (s.length > 96) s = s.slice(0, 96);
|
||||
if (!/^[a-zA-Z0-9._-]+$/.test(s)) return '';
|
||||
return s;
|
||||
@@ -210,7 +209,6 @@
|
||||
}
|
||||
var last = list[list.length - 1];
|
||||
var id = last && last.id ? normalizeCharacterAssetId(String(last.id)) : '';
|
||||
if (id) { try { localStorage.setItem(CHAR_KEY, id); } catch (e) {} } // persist ตัวละคร default ให้ห้อง/เกมอ่านไปใช้ join
|
||||
callback(id || '');
|
||||
})
|
||||
.catch(function () {
|
||||
@@ -219,6 +217,7 @@
|
||||
}
|
||||
|
||||
var PLAYER_KEY = 'jdPlayerKey';
|
||||
var DISPLAY_NAME_STORAGE_KEY = 'roomLobbyDisplayName';
|
||||
|
||||
function ensurePlayerKey() {
|
||||
var k;
|
||||
@@ -258,11 +257,10 @@
|
||||
applyProfileTexts();
|
||||
syncCoinsFromServer();
|
||||
function applyCharAssets(urlOrNull) {
|
||||
lobbyBakedCharUrl = urlOrNull;
|
||||
applyLobbyCharacterAppearance();
|
||||
applyProfileAvatar(urlOrNull);
|
||||
applyCenterCharacterResolved(urlOrNull);
|
||||
}
|
||||
resolveLobbyCharacterId(function (id) {
|
||||
lobbyResolvedCharId = id || '';
|
||||
if (!id) {
|
||||
applyCharAssets(null);
|
||||
return;
|
||||
@@ -276,8 +274,21 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getProfileDisplayName() {
|
||||
if (typeof window.jdReadDisplayName === 'function') {
|
||||
return window.jdReadDisplayName();
|
||||
}
|
||||
var fromRoomLobby = '';
|
||||
var fromPlayerName = '';
|
||||
try {
|
||||
fromRoomLobby = (localStorage.getItem(DISPLAY_NAME_STORAGE_KEY) || '').trim();
|
||||
fromPlayerName = (localStorage.getItem('playerName') || '').trim();
|
||||
} catch (e) { /* ignore */ }
|
||||
return fromRoomLobby || fromPlayerName || 'MONE';
|
||||
}
|
||||
|
||||
function applyProfileTexts() {
|
||||
var name = (localStorage.getItem('playerName') || '').trim() || 'MONE';
|
||||
var name = getProfileDisplayName();
|
||||
var coins = localStorage.getItem('jdCoins') || '0';
|
||||
var nameEl = document.getElementById('lobby-profile-name');
|
||||
var agentEl = document.getElementById('lobby-profile-agent-id');
|
||||
@@ -368,6 +379,7 @@
|
||||
function syncLeaderboardScrollbarThumb() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
var thumbEl = document.getElementById('lobby-leaderboard-scroll-thumb');
|
||||
var trackEl = document.getElementById('lobby-leaderboard-scroll-track');
|
||||
if (!scrollEl || !thumbEl) return;
|
||||
|
||||
var styles = window.getComputedStyle(thumbEl);
|
||||
@@ -376,11 +388,13 @@
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) {
|
||||
thumbEl.style.display = 'none';
|
||||
if (trackEl) trackEl.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (trackEl) trackEl.style.display = 'block';
|
||||
thumbEl.style.display = 'block';
|
||||
|
||||
var trackHeight = scrollEl.clientHeight;
|
||||
var trackHeight = (trackEl && trackEl.clientHeight) ? trackEl.clientHeight : 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);
|
||||
@@ -443,6 +457,140 @@
|
||||
scrollEl.dataset.scrollSyncBound = '1';
|
||||
}
|
||||
|
||||
function bindLeaderboardWheelSupport() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
if (!scrollEl || scrollEl.dataset.wheelSupportBound === '1') return;
|
||||
|
||||
scrollEl.addEventListener('wheel', function (e) {
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) return;
|
||||
scrollEl.scrollTop += e.deltaY;
|
||||
syncLeaderboardScrollbarThumb();
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
scrollEl.dataset.wheelSupportBound = '1';
|
||||
}
|
||||
|
||||
function bindLeaderboardThumbDrag() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
var thumbEl = document.getElementById('lobby-leaderboard-scroll-thumb');
|
||||
var trackEl = document.getElementById('lobby-leaderboard-scroll-track');
|
||||
if (!scrollEl || !thumbEl || !trackEl || thumbEl.dataset.dragBound === '1') return;
|
||||
|
||||
var dragging = false;
|
||||
var dragOffsetY = 0;
|
||||
|
||||
function getThumbMetrics() {
|
||||
var styles = window.getComputedStyle(thumbEl);
|
||||
var topInset = parseFloat(styles.top) || 0;
|
||||
var thumbHeight = thumbEl.clientHeight || parseFloat(thumbEl.style.height) || 18;
|
||||
var trackHeight = trackEl.clientHeight || scrollEl.clientHeight || 1;
|
||||
var maxTranslate = Math.max(topInset, trackHeight - thumbHeight);
|
||||
var travel = Math.max(1, maxTranslate - topInset);
|
||||
return { topInset: topInset, thumbHeight: thumbHeight, trackHeight: trackHeight, maxTranslate: maxTranslate, travel: travel };
|
||||
}
|
||||
|
||||
function stopDrag() {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
thumbEl.classList.remove('is-dragging');
|
||||
document.body.classList.remove('lobby-leaderboard-dragging');
|
||||
}
|
||||
|
||||
thumbEl.addEventListener('mousedown', function (e) {
|
||||
if (e.button !== 0) return;
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) return;
|
||||
var metrics = getThumbMetrics();
|
||||
var thumbRect = thumbEl.getBoundingClientRect();
|
||||
dragOffsetY = Math.max(0, Math.min(metrics.thumbHeight, e.clientY - thumbRect.top));
|
||||
dragging = true;
|
||||
thumbEl.classList.add('is-dragging');
|
||||
document.body.classList.add('lobby-leaderboard-dragging');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
if (!dragging) return;
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) {
|
||||
stopDrag();
|
||||
return;
|
||||
}
|
||||
var metrics = getThumbMetrics();
|
||||
var trackRect = trackEl.getBoundingClientRect();
|
||||
var pointerY = e.clientY - trackRect.top;
|
||||
var nextTranslate = pointerY - dragOffsetY;
|
||||
nextTranslate = Math.max(metrics.topInset, Math.min(metrics.maxTranslate, nextTranslate));
|
||||
var ratio = (nextTranslate - metrics.topInset) / metrics.travel;
|
||||
scrollEl.scrollTop = ratio * maxScroll;
|
||||
syncLeaderboardScrollbarThumb();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', stopDrag);
|
||||
window.addEventListener('blur', stopDrag);
|
||||
|
||||
thumbEl.dataset.dragBound = '1';
|
||||
}
|
||||
|
||||
function bindLeaderboardDragScroll() {
|
||||
var scrollEl = document.querySelector('.lobby-leaderboard-scroll');
|
||||
if (!scrollEl || scrollEl.dataset.dragScrollBound === '1') return;
|
||||
|
||||
var dragging = false;
|
||||
var moved = false;
|
||||
var startY = 0;
|
||||
var startScrollTop = 0;
|
||||
|
||||
function stopDrag() {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
window.setTimeout(function () { moved = false; }, 0);
|
||||
document.body.classList.remove('lobby-leaderboard-dragging');
|
||||
scrollEl.classList.remove('is-dragging');
|
||||
}
|
||||
|
||||
scrollEl.addEventListener('mousedown', function (e) {
|
||||
if (e.button !== 0) return;
|
||||
var maxScroll = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight);
|
||||
if (maxScroll <= 0) return;
|
||||
dragging = true;
|
||||
moved = false;
|
||||
startY = e.clientY;
|
||||
startScrollTop = scrollEl.scrollTop;
|
||||
document.body.classList.add('lobby-leaderboard-dragging');
|
||||
scrollEl.classList.add('is-dragging');
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', function (e) {
|
||||
if (!dragging) return;
|
||||
var deltaY = e.clientY - startY;
|
||||
if (Math.abs(deltaY) > 2) moved = true;
|
||||
scrollEl.scrollTop = startScrollTop - deltaY;
|
||||
syncLeaderboardScrollbarThumb();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', stopDrag);
|
||||
window.addEventListener('blur', stopDrag);
|
||||
scrollEl.addEventListener('mouseleave', function (e) {
|
||||
if (!dragging) return;
|
||||
if (e.buttons === 0) stopDrag();
|
||||
});
|
||||
|
||||
scrollEl.addEventListener('click', function (e) {
|
||||
if (!moved) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, true);
|
||||
|
||||
scrollEl.dataset.dragScrollBound = '1';
|
||||
}
|
||||
|
||||
var guideStep = 1;
|
||||
var guideImg = document.getElementById('guide-img');
|
||||
var guideBar = document.getElementById('lobby-guide-bar');
|
||||
@@ -616,14 +764,26 @@
|
||||
}
|
||||
|
||||
document.getElementById('btn-cloth')?.addEventListener('click', function () {
|
||||
openLobbyCustomize();
|
||||
if (typeof window.openCustomizePopup === 'function') {
|
||||
window.openCustomizePopup();
|
||||
return;
|
||||
}
|
||||
window.location.href = BASE + '/character.html';
|
||||
});
|
||||
|
||||
document.getElementById('btn-daily')?.addEventListener('click', function () {
|
||||
if (typeof window.openDailyPopup === 'function') {
|
||||
window.openDailyPopup();
|
||||
return;
|
||||
}
|
||||
toast('รางวัลประจำวัน — เข้าเกมทุกวันเพื่อรับรางวัลต่อเนื่อง!');
|
||||
});
|
||||
|
||||
document.getElementById('btn-myprofile')?.addEventListener('click', function () {
|
||||
if (typeof window.openProfilePopup === 'function') {
|
||||
window.openProfilePopup();
|
||||
return;
|
||||
}
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Menu/') : '/Main-Menu/';
|
||||
});
|
||||
|
||||
@@ -778,6 +938,9 @@
|
||||
applyCharacterFootOffsetByViewport();
|
||||
syncMainLobbyCharacterUi();
|
||||
bindLeaderboardScrollSync();
|
||||
bindLeaderboardWheelSupport();
|
||||
bindLeaderboardDragScroll();
|
||||
bindLeaderboardThumbDrag();
|
||||
scheduleLeaderboardPlacement();
|
||||
initGuide();
|
||||
|
||||
@@ -789,6 +952,9 @@
|
||||
window.addEventListener('resize', function () {
|
||||
applyCharacterFootOffsetByViewport();
|
||||
bindLeaderboardScrollSync();
|
||||
bindLeaderboardWheelSupport();
|
||||
bindLeaderboardDragScroll();
|
||||
bindLeaderboardThumbDrag();
|
||||
scheduleLeaderboardPlacement();
|
||||
});
|
||||
window.addEventListener('orientationchange', function () {
|
||||
@@ -799,6 +965,9 @@
|
||||
});
|
||||
window.addEventListener('load', function () {
|
||||
bindLeaderboardScrollSync();
|
||||
bindLeaderboardWheelSupport();
|
||||
bindLeaderboardDragScroll();
|
||||
bindLeaderboardThumbDrag();
|
||||
scheduleLeaderboardPlacement();
|
||||
});
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
@@ -809,7 +978,7 @@
|
||||
}
|
||||
});
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key == null || e.key === CHAR_KEY || e.key === 'playerName') {
|
||||
if (e.key == null || e.key === CHAR_KEY || e.key === 'playerName' || e.key === DISPLAY_NAME_STORAGE_KEY) {
|
||||
syncMainLobbyCharacterUi();
|
||||
return;
|
||||
}
|
||||
@@ -817,250 +986,4 @@
|
||||
syncMainLobbyCharacterUi();
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== ห้องแต่งตัว (Customize popup) — Phase 1: UI + เลือกธีมสี/สีผิว/ใบหน้า (เก็บ localStorage) ===== */
|
||||
var CUSTOMIZE_ASSET = (typeof appPath === 'function' ? appPath('/Game') : '/Game') + '/img/03-5-Customize/';
|
||||
var customizeOverlay = document.getElementById('lobby-customize-overlay');
|
||||
|
||||
/* ---- Phase 2: ทาสีตัวละครจริง (tint layer mask ด้วยสีที่เลือก) ---- */
|
||||
var lobbyResolvedCharId = '';
|
||||
var lobbyBakedCharUrl = null;
|
||||
var LOBBY_LAYER_NAMES = ['shadow', 'bodyColor', 'bodyStroke', 'headColor', 'headStroke', 'hairColor', 'hairStroke', 'face'];
|
||||
var lobbySwatchColorCache = {};
|
||||
|
||||
function lobbyHasSavedTint() {
|
||||
try { return !!(localStorage.getItem('lobbyThemeColor') || localStorage.getItem('lobbySkinTone')); } catch (e) { return false; }
|
||||
}
|
||||
|
||||
function lobbyCharLayerUrl(charId, name) {
|
||||
return BASE + '/img/characters/' + encodeURIComponent(charId) + '_down_idle_layer_' + name + '.png';
|
||||
}
|
||||
|
||||
function lobbyLoadImg(src, cb) {
|
||||
var img = new Image();
|
||||
img.onload = function () { cb(img); };
|
||||
img.onerror = function () { cb(null); };
|
||||
img.src = src;
|
||||
}
|
||||
|
||||
function lobbySampleSwatchColor(group, idx, cb) {
|
||||
var key = group + '-' + idx;
|
||||
if (lobbySwatchColorCache[key]) { cb(lobbySwatchColorCache[key]); return; }
|
||||
var src = CUSTOMIZE_ASSET + (group === 'color' ? 'color-' : 'skin-tone-') + idx + '.png';
|
||||
lobbyLoadImg(src, function (img) {
|
||||
if (!img || !img.naturalWidth) { cb(null); return; }
|
||||
try {
|
||||
var c = document.createElement('canvas');
|
||||
c.width = 1; c.height = 1;
|
||||
var x = c.getContext('2d');
|
||||
x.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, 1, 1);
|
||||
var d = x.getImageData(0, 0, 1, 1).data;
|
||||
var rgb = 'rgb(' + d[0] + ',' + d[1] + ',' + d[2] + ')';
|
||||
lobbySwatchColorCache[key] = rgb;
|
||||
cb(rgb);
|
||||
} catch (e) { cb(null); }
|
||||
});
|
||||
}
|
||||
|
||||
function lobbyTintMask(img, color) {
|
||||
var c = document.createElement('canvas');
|
||||
c.width = img.naturalWidth; c.height = img.naturalHeight;
|
||||
var x = c.getContext('2d');
|
||||
x.drawImage(img, 0, 0);
|
||||
x.globalCompositeOperation = 'source-in';
|
||||
x.fillStyle = color;
|
||||
x.fillRect(0, 0, c.width, c.height);
|
||||
return c;
|
||||
}
|
||||
|
||||
function lobbyCompositeTinted(charId, cb) {
|
||||
if (!charId) { cb(null); return; }
|
||||
// กัน init รันก่อนตัวแปรถูกประกาศ (resolve แบบ sync ตอน localStorage มี char)
|
||||
if (!lobbySwatchColorCache) lobbySwatchColorCache = {};
|
||||
if (!LOBBY_LAYER_NAMES) LOBBY_LAYER_NAMES = ['shadow', 'bodyColor', 'bodyStroke', 'headColor', 'headStroke', 'hairColor', 'hairStroke', 'face'];
|
||||
var colorIdx = '', skinIdx = '';
|
||||
try { colorIdx = localStorage.getItem('lobbyThemeColor') || ''; skinIdx = localStorage.getItem('lobbySkinTone') || ''; } catch (e) {}
|
||||
|
||||
function withColors(themeColor, skinColor) {
|
||||
var loaded = {};
|
||||
var pending = LOBBY_LAYER_NAMES.length;
|
||||
LOBBY_LAYER_NAMES.forEach(function (name) {
|
||||
lobbyLoadImg(lobbyCharLayerUrl(charId, name), function (img) {
|
||||
loaded[name] = img;
|
||||
if (--pending === 0) finish(themeColor, skinColor, loaded);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finish(themeColor, skinColor, loaded) {
|
||||
var ref = null, i;
|
||||
for (i = 0; i < LOBBY_LAYER_NAMES.length; i++) {
|
||||
if (loaded[LOBBY_LAYER_NAMES[i]]) { ref = loaded[LOBBY_LAYER_NAMES[i]]; break; }
|
||||
}
|
||||
if (!ref) { cb(null); return; }
|
||||
var c = document.createElement('canvas');
|
||||
c.width = ref.naturalWidth; c.height = ref.naturalHeight;
|
||||
var x = c.getContext('2d');
|
||||
LOBBY_LAYER_NAMES.forEach(function (name) {
|
||||
var img = loaded[name];
|
||||
if (!img || !img.naturalWidth) return;
|
||||
if (themeColor && (name === 'bodyColor' || name === 'hairColor')) x.drawImage(lobbyTintMask(img, themeColor), 0, 0);
|
||||
else if (skinColor && name === 'headColor') x.drawImage(lobbyTintMask(img, skinColor), 0, 0);
|
||||
else x.drawImage(img, 0, 0);
|
||||
});
|
||||
try { cb(c.toDataURL('image/png')); } catch (e) { cb(null); }
|
||||
}
|
||||
|
||||
var need = 0, themeColor = null, skinColor = null;
|
||||
function maybeGo() { if (need === 0) withColors(themeColor, skinColor); }
|
||||
if (colorIdx) { need++; lobbySampleSwatchColor('color', colorIdx, function (rgb) { themeColor = rgb; need--; maybeGo(); }); }
|
||||
if (skinIdx) { need++; lobbySampleSwatchColor('skin', skinIdx, function (rgb) { skinColor = rgb; need--; maybeGo(); }); }
|
||||
if (need === 0) withColors(null, null);
|
||||
}
|
||||
|
||||
function applyLobbyCharacterAppearance() {
|
||||
if (lobbyResolvedCharId && lobbyHasSavedTint()) {
|
||||
lobbyCompositeTinted(lobbyResolvedCharId, function (dataUrl) {
|
||||
applyProfileAvatar(dataUrl || lobbyBakedCharUrl);
|
||||
applyCenterCharacterResolved(dataUrl || lobbyBakedCharUrl);
|
||||
});
|
||||
return;
|
||||
}
|
||||
applyProfileAvatar(lobbyBakedCharUrl);
|
||||
applyCenterCharacterResolved(lobbyBakedCharUrl);
|
||||
}
|
||||
|
||||
var CUSTOMIZE_ITEMS = {
|
||||
face: [
|
||||
{ idx: 1, price: 0 }, { idx: 2, price: 0 }, { idx: 3, price: 50 }, { idx: 4, price: 50 },
|
||||
{ idx: 5, price: 50 }, { idx: 6, price: 100 }, { idx: 7, price: 100 }, { idx: 8, price: 200 }
|
||||
],
|
||||
hair: [],
|
||||
cloth: []
|
||||
};
|
||||
|
||||
function lobbyMakeSwatch(group, idx, label) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'lobby-customize-swatch';
|
||||
btn.setAttribute('data-idx', String(idx));
|
||||
btn.setAttribute('aria-label', label);
|
||||
var img = document.createElement('img');
|
||||
img.src = CUSTOMIZE_ASSET + (group === 'color' ? 'color-' : 'skin-tone-') + idx + '.png';
|
||||
img.alt = '';
|
||||
img.decoding = 'async';
|
||||
btn.appendChild(img);
|
||||
btn.addEventListener('click', function () { lobbySelectSwatch(group, idx); });
|
||||
return btn;
|
||||
}
|
||||
|
||||
function lobbySelectSwatch(group, idx) {
|
||||
var wrap = document.getElementById(group === 'color' ? 'lobby-theme-colors' : 'lobby-skin-tones');
|
||||
if (!wrap) return;
|
||||
[].forEach.call(wrap.children, function (c) {
|
||||
c.classList.toggle('is-selected', c.getAttribute('data-idx') === String(idx));
|
||||
});
|
||||
try { localStorage.setItem(group === 'color' ? 'lobbyThemeColor' : 'lobbySkinTone', String(idx)); } catch (e) {}
|
||||
applyLobbyCharacterAppearance();
|
||||
}
|
||||
|
||||
function lobbyBuildSwatches() {
|
||||
var colorWrap = document.getElementById('lobby-theme-colors');
|
||||
var skinWrap = document.getElementById('lobby-skin-tones');
|
||||
if (colorWrap && !colorWrap.childElementCount) {
|
||||
for (var i = 1; i <= 8; i++) colorWrap.appendChild(lobbyMakeSwatch('color', i, 'ธีมสี ' + i));
|
||||
}
|
||||
if (skinWrap && !skinWrap.childElementCount) {
|
||||
for (var j = 1; j <= 3; j++) skinWrap.appendChild(lobbyMakeSwatch('skin', j, 'สีผิว ' + j));
|
||||
}
|
||||
}
|
||||
|
||||
function lobbyRenderCustomizeItems(tab) {
|
||||
var grid = document.getElementById('lobby-customize-items');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
var items = CUSTOMIZE_ITEMS[tab] || [];
|
||||
if (!items.length) {
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'lobby-customize-empty';
|
||||
empty.textContent = 'ยังไม่เปิดให้บริการ';
|
||||
grid.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
var savedKey = 'lobbyItem_' + tab;
|
||||
var saved = '';
|
||||
try { saved = localStorage.getItem(savedKey) || ''; } catch (e) {}
|
||||
items.forEach(function (it) {
|
||||
var cell = document.createElement('button');
|
||||
cell.type = 'button';
|
||||
cell.className = 'lobby-customize-item' + (String(it.idx) === saved ? ' is-selected' : '');
|
||||
cell.setAttribute('data-idx', String(it.idx));
|
||||
var img = document.createElement('img');
|
||||
img.className = 'lobby-customize-item-img';
|
||||
img.src = CUSTOMIZE_ASSET + tab + '-' + it.idx + '.png';
|
||||
img.alt = '';
|
||||
img.decoding = 'async';
|
||||
cell.appendChild(img);
|
||||
if (it.price > 0) {
|
||||
var price = document.createElement('span');
|
||||
price.className = 'lobby-customize-item-price';
|
||||
price.textContent = String(it.price);
|
||||
cell.appendChild(price);
|
||||
}
|
||||
cell.addEventListener('click', function () {
|
||||
[].forEach.call(grid.children, function (c) { c.classList.remove('is-selected'); });
|
||||
cell.classList.add('is-selected');
|
||||
try { localStorage.setItem(savedKey, String(it.idx)); } catch (e) {}
|
||||
});
|
||||
grid.appendChild(cell);
|
||||
});
|
||||
}
|
||||
|
||||
function lobbySetCustomizeTab(tab) {
|
||||
var bar = document.getElementById('lobby-customize-tabbar');
|
||||
if (bar) {
|
||||
var map = { face: 'tab-1-face.png', hair: 'tab-2-hair.png', cloth: 'tab-3-cloth.png' };
|
||||
bar.src = CUSTOMIZE_ASSET + (map[tab] || map.face);
|
||||
}
|
||||
[].forEach.call(document.querySelectorAll('.lobby-customize-tabhit'), function (t) {
|
||||
t.setAttribute('aria-selected', t.getAttribute('data-tab') === tab ? 'true' : 'false');
|
||||
});
|
||||
lobbyRenderCustomizeItems(tab);
|
||||
}
|
||||
|
||||
function openLobbyCustomize() {
|
||||
if (!customizeOverlay) {
|
||||
window.location.href = BASE + '/character.html';
|
||||
return;
|
||||
}
|
||||
lobbyBuildSwatches();
|
||||
try {
|
||||
var c = localStorage.getItem('lobbyThemeColor'); if (c) lobbySelectSwatch('color', c);
|
||||
var s = localStorage.getItem('lobbySkinTone'); if (s) lobbySelectSwatch('skin', s);
|
||||
} catch (e) {}
|
||||
lobbySetCustomizeTab('face');
|
||||
customizeOverlay.classList.remove('hidden');
|
||||
customizeOverlay.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
function closeLobbyCustomize() {
|
||||
if (!customizeOverlay) return;
|
||||
customizeOverlay.classList.add('hidden');
|
||||
customizeOverlay.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
|
||||
function setupLobbyCustomize() {
|
||||
if (!customizeOverlay) return;
|
||||
document.getElementById('lobby-customize-close')?.addEventListener('click', closeLobbyCustomize);
|
||||
document.getElementById('lobby-customize-backdrop')?.addEventListener('click', closeLobbyCustomize);
|
||||
document.getElementById('lobby-customize-confirm')?.addEventListener('click', function () {
|
||||
closeLobbyCustomize();
|
||||
toast('บันทึกการแต่งตัวแล้ว');
|
||||
});
|
||||
[].forEach.call(document.querySelectorAll('.lobby-customize-tabhit'), function (t) {
|
||||
t.addEventListener('click', function () { lobbySetCustomizeTab(t.getAttribute('data-tab')); });
|
||||
});
|
||||
}
|
||||
|
||||
setupLobbyCustomize();
|
||||
})();
|
||||
|
||||
@@ -7,7 +7,13 @@ html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
/* Noto local file removed — use Google Kanit in index.html (avoids 404 on /Create%20Room/FONTS/...) */
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src: url('../FONTS/NotoSansThai-ExtraBold.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--lobby-character-height: 62cqh; /* ความสูงตัวละครแบบพิกเซลคงที่ */
|
||||
@@ -36,7 +42,7 @@ body.lobby-page {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
color: #fff;
|
||||
background: #060818;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
@@ -51,7 +57,7 @@ body.lobby-page select {
|
||||
|
||||
body.lobby-page,
|
||||
body.lobby-page * {
|
||||
font-family: 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
font-family: 'NotoSansThai', 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* ปุ่มที่กดง่ายบนมือถือ */
|
||||
@@ -406,13 +412,14 @@ body.lobby-page * {
|
||||
--lobby-daily-leaderboard-gap: 0cqh; /* ระยะห่างจากปุ่มรางวัลประจำวัน (ติดลบได้) */
|
||||
--lobby-leaderboard-row-height: 7cqh; /* ปรับความสูงแต่ละรายชื่อ */
|
||||
--lobby-leaderboard-rank-icon-size: 2.3em; /* ปรับขนาดไอคอนอันดับ 1-3 */
|
||||
--lobby-leaderboard-scrollbar-width: 1cqh; /* ความอ้วน scrollbar */
|
||||
--lobby-leaderboard-scrollbar-thumb-inset: 1cqh;
|
||||
--lobby-leaderboard-scrollbar-offset-x: 1cqh; /* + = ขยับ scrollbar ไปขวา */
|
||||
--lobby-leaderboard-scrollbar-width: max(8px, 1.1cqh); /* ความอ้วน scrollbar */
|
||||
--lobby-leaderboard-scrollbar-safe-gap: 10px; /* เว้นช่องกันทับตัวหนังสือ */
|
||||
--lobby-leaderboard-scrollbar-thumb-inset: 1px;
|
||||
--lobby-leaderboard-scrollbar-offset-x: 0px; /* ตำแหน่งแถบ scrollbar */
|
||||
--lobby-leaderboard-font-size: 2.1cqh;
|
||||
--lobby-leaderboard-content-inset-top: 37%;
|
||||
--lobby-leaderboard-content-inset-right: 4%;
|
||||
--lobby-leaderboard-content-inset-bottom: 4%;
|
||||
--lobby-leaderboard-content-inset-right: 8.5%;
|
||||
--lobby-leaderboard-content-inset-bottom: 8%;
|
||||
--lobby-leaderboard-content-inset-left: 6%;
|
||||
position: absolute;
|
||||
top: 19em;
|
||||
@@ -445,12 +452,6 @@ body.lobby-page * {
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
-webkit-mask-image: url('IMAGE/leaderboard-bg.png');
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-image: url('IMAGE/leaderboard-bg.png');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-inner {
|
||||
@@ -476,41 +477,66 @@ body.lobby-page * {
|
||||
margin-right: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: calc(var(--lobby-leaderboard-scrollbar-width) + var(--lobby-leaderboard-scrollbar-thumb-inset) + 2px);
|
||||
padding-right: calc(var(--lobby-leaderboard-scrollbar-width) + var(--lobby-leaderboard-scrollbar-safe-gap));
|
||||
scrollbar-width: none;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-scroll::after {
|
||||
content: '';
|
||||
.lobby-leaderboard-scroll.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
body.lobby-page.lobby-leaderboard-dragging,
|
||||
body.lobby-page.lobby-leaderboard-dragging * {
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-scroll-track {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: calc(var(--lobby-leaderboard-scrollbar-offset-x) * -1);
|
||||
right: 0;
|
||||
width: var(--lobby-leaderboard-scrollbar-width);
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0.95;
|
||||
opacity: 1;
|
||||
border-radius: 999px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(127, 225, 255, 0.25) 0%, rgba(54, 126, 188, 0.2) 100%),
|
||||
linear-gradient(180deg, rgba(20, 37, 72, 0.85) 0%, rgba(19, 34, 63, 0.78) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(115, 220, 255, 0.26),
|
||||
0 0 0 1px rgba(43, 88, 149, 0.35);
|
||||
background: #1b2740;
|
||||
border: 1px solid #22324f;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-scroll-thumb {
|
||||
position: absolute;
|
||||
right: calc((var(--lobby-leaderboard-scrollbar-offset-x) * -1) + var(--lobby-leaderboard-scrollbar-thumb-inset));
|
||||
right: var(--lobby-leaderboard-scrollbar-thumb-inset);
|
||||
top: var(--lobby-leaderboard-scrollbar-thumb-inset);
|
||||
width: calc(var(--lobby-leaderboard-scrollbar-width) - (var(--lobby-leaderboard-scrollbar-thumb-inset) * 2));
|
||||
min-height: 18px;
|
||||
border-radius: 999px;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, #ff9adf 0%, #ff4fb5 42%, #e02b92 100%);
|
||||
pointer-events: auto;
|
||||
z-index: 2;
|
||||
background: #f04cdc;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 210, 240, 0.52),
|
||||
0 0 8px rgba(255, 83, 181, 0.45);
|
||||
display: block;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-scroll-thumb.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-scroll::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.lobby-leaderboard-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
.lobby-leaderboard-scroll::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.lobby-leaderboard-list {
|
||||
@@ -520,6 +546,7 @@ body.lobby-page * {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-right: 2px;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
@@ -1852,6 +1879,7 @@ body.lobby-guide-active:not(.lobby-guide-top-step) .lobby-footer {
|
||||
max-width: 100% !important;
|
||||
aspect-ratio: 517 / 213;
|
||||
transform: translateY(var(--lobby-news-wrap-shift-y));
|
||||
margin-right: -1%;
|
||||
}
|
||||
|
||||
.lobby-news-bg {
|
||||
@@ -1944,236 +1972,3 @@ body.lobby-guide-active:not(.lobby-guide-top-step) .lobby-footer {
|
||||
width: var(--lobby-profile-coin-size) !important;
|
||||
height: var(--lobby-profile-coin-size) !important;
|
||||
}
|
||||
|
||||
/* ---- News panel: ซ่อนกล่องขาว (news-banner-bg.png) ใช้พื้นมืดเข้าธีมแทน ---- */
|
||||
.lobby-news-bg {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lobby-news-wrap {
|
||||
background: linear-gradient(180deg, rgba(12, 18, 40, 0.92), rgba(10, 16, 34, 0.92));
|
||||
border: 2px solid rgba(34, 211, 238, 0.65);
|
||||
border-radius: 12px;
|
||||
box-shadow:
|
||||
0 0 14px rgba(34, 211, 238, 0.35),
|
||||
inset 0 0 18px rgba(34, 211, 238, 0.12);
|
||||
}
|
||||
|
||||
/* ===== ห้องแต่งตัว (Customize popup) — Phase 1 ===== */
|
||||
.lobby-customize-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 150;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.lobby-customize-overlay.hidden { display: none !important; }
|
||||
|
||||
.lobby-customize-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(4, 6, 18, 0.78);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.lobby-customize-dialog {
|
||||
position: relative;
|
||||
width: min(92vw, 720px);
|
||||
max-height: 90vh;
|
||||
overflow: hidden auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(0.5rem, 1.6vh, 1rem);
|
||||
padding: clamp(1rem, 3vw, 1.6rem) clamp(1rem, 3vw, 1.8rem) clamp(1.2rem, 3vw, 1.8rem);
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(14, 20, 44, 0.97), rgba(9, 13, 30, 0.97));
|
||||
border: 2px solid rgba(34, 211, 238, 0.7);
|
||||
box-shadow:
|
||||
0 0 22px rgba(34, 211, 238, 0.45),
|
||||
0 0 48px rgba(236, 72, 153, 0.28),
|
||||
inset 0 0 24px rgba(34, 211, 238, 0.1);
|
||||
}
|
||||
|
||||
.lobby-customize-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.lobby-customize-title {
|
||||
margin: 0;
|
||||
font-size: clamp(1.1rem, 3vw, 1.6rem);
|
||||
font-weight: 700;
|
||||
color: #e8faff;
|
||||
text-shadow: 0 0 12px rgba(34, 211, 238, 0.6);
|
||||
}
|
||||
|
||||
.lobby-customize-close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border: 2px solid rgba(236, 72, 153, 0.6);
|
||||
border-radius: 10px;
|
||||
background: rgba(20, 16, 40, 0.6);
|
||||
color: #ff8fd0;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lobby-customize-close:hover { background: rgba(236, 72, 153, 0.25); }
|
||||
|
||||
.lobby-customize-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: clamp(0.5rem, 2vw, 1rem);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.lobby-customize-row-label {
|
||||
height: clamp(20px, 3.4vh, 30px);
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.lobby-customize-swatches {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: clamp(6px, 1vw, 10px);
|
||||
}
|
||||
|
||||
.lobby-customize-swatch {
|
||||
padding: 0;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.lobby-customize-swatch img {
|
||||
display: block;
|
||||
width: clamp(28px, 4vw, 40px);
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.lobby-customize-swatch.is-selected {
|
||||
border-color: #22d3ee;
|
||||
box-shadow: 0 0 10px rgba(34, 211, 238, 0.7);
|
||||
}
|
||||
|
||||
.lobby-customize-tabs {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 776 / 118;
|
||||
}
|
||||
|
||||
.lobby-customize-tabbar-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lobby-customize-tabhit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 33.34%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lobby-customize-tabhit[data-tab="face"] { left: 0; }
|
||||
.lobby-customize-tabhit[data-tab="hair"] { left: 33.33%; }
|
||||
.lobby-customize-tabhit[data-tab="cloth"] { left: 66.66%; }
|
||||
|
||||
.lobby-customize-items {
|
||||
flex: 1;
|
||||
min-height: clamp(140px, 30vh, 280px);
|
||||
max-height: 42vh;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: clamp(8px, 1.5vw, 14px);
|
||||
padding: clamp(8px, 1.5vw, 14px);
|
||||
border-radius: 12px;
|
||||
background: rgba(8, 12, 28, 0.6);
|
||||
border: 1px solid rgba(34, 211, 238, 0.25);
|
||||
}
|
||||
|
||||
.lobby-customize-item {
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
border: 2px solid rgba(34, 211, 238, 0.3);
|
||||
border-radius: 12px;
|
||||
background: rgba(18, 26, 52, 0.7);
|
||||
cursor: pointer;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lobby-customize-item.is-selected {
|
||||
border-color: #22d3ee;
|
||||
box-shadow: 0 0 10px rgba(34, 211, 238, 0.6);
|
||||
}
|
||||
|
||||
.lobby-customize-item-img {
|
||||
width: 78%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.lobby-customize-item-price {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: #ffe066;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
border-radius: 8px;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
|
||||
.lobby-customize-empty {
|
||||
grid-column: 1 / -1;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
padding: 2rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.lobby-customize-confirm {
|
||||
align-self: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lobby-customize-confirm img {
|
||||
display: block;
|
||||
width: min(60vw, 240px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.lobby-customize-items { grid-template-columns: repeat(3, 1fr); }
|
||||
}
|
||||
|
||||
@@ -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?v=1.2">
|
||||
<link rel="stylesheet" href="style.css?v=1.3">
|
||||
</head>
|
||||
<body>
|
||||
<!-- พื้นหลังจาก Login -->
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../app-base.js"></script>
|
||||
<script src="menu.js"></script>
|
||||
<script src="../app-base.js?v=2"></script>
|
||||
<script src="menu.js?v=1.1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
}
|
||||
|
||||
// ปุ่มต่างๆ
|
||||
function syncMainMenuScale() {
|
||||
var ui = document.querySelector('.menu-ui');
|
||||
if (!ui) return;
|
||||
var vw = Math.max(1, window.innerWidth || 0);
|
||||
var vh = Math.max(1, window.innerHeight || 0);
|
||||
var scale = Math.min(1, vw / 1920, vh / 1080);
|
||||
ui.style.setProperty('--mm-scale', String(scale.toFixed(4)));
|
||||
}
|
||||
|
||||
document.getElementById('btn-back')?.addEventListener('click', function () {
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Lobby/') : '/Main-Lobby/';
|
||||
});
|
||||
@@ -36,4 +45,10 @@
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
}
|
||||
});
|
||||
|
||||
syncMainMenuScale();
|
||||
window.addEventListener('resize', syncMainMenuScale);
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(syncMainMenuScale, 150);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -7,24 +7,24 @@
|
||||
<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&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css?v=0.0204">
|
||||
<link rel="stylesheet" href="style.css?v=0.0221">
|
||||
</head>
|
||||
<body>
|
||||
<div class="qb-bg" aria-hidden="true">
|
||||
<img src="/Quiz-Battle/IMAGE/Quiz-Battle-bg.png" alt="" class="qb-bg-img" decoding="async">
|
||||
<img src="IMAGE/Quiz-Battle-bg.png" alt="" class="qb-bg-img" decoding="async">
|
||||
</div>
|
||||
<div class="qb-vignette" aria-hidden="true"></div>
|
||||
|
||||
<div class="qb-ui">
|
||||
<header class="qb-top">
|
||||
<button type="button" class="qb-icon-btn qb-icon-btn--back" id="btn-back" aria-label="กลับล็อบบี้">
|
||||
<img src="/Main-Menu/IMAGE/btn-back.png" alt="" decoding="async">
|
||||
<button type="button" class="qb-join-back" id="btn-back" aria-label="กลับล็อบบี้">
|
||||
<img src="IMAGE/btn-exit-room.png" alt="" class="qb-join-back-img" decoding="async">
|
||||
</button>
|
||||
<div class="qb-top-fill" aria-hidden="true"></div>
|
||||
<div class="qb-profile-panel">
|
||||
<div class="qb-avatar-frame">
|
||||
<div class="qb-avatar-wrap">
|
||||
<img src="/Main-Menu/char-main.png" alt="" class="lobby-avatar-mask qb-avatar-img" id="lobby-profile-avatar" width="64" height="64" decoding="async">
|
||||
<div class="qb-room-profile-wrap" aria-hidden="true">
|
||||
<div class="qb-room-profile-frame">
|
||||
<div class="qb-room-profile-avatar-viewport">
|
||||
<img src="../Main-Menu/char-main.png" alt="" class="qb-room-profile-avatar" id="lobby-profile-avatar" width="64" height="64" decoding="async">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,19 +32,19 @@
|
||||
|
||||
<main class="qb-main">
|
||||
<div class="qb-room-banner" aria-hidden="true">
|
||||
<img src="/Quiz-Battle/IMAGE/quiz-room.png" alt="" decoding="async">
|
||||
<img src="IMAGE/quiz-room.png" alt="" decoding="async">
|
||||
</div>
|
||||
<div class="qb-grid" id="qb-grid" role="list" aria-label="เลือกห้องควิซ"></div>
|
||||
<p class="qb-toast" id="qb-toast" role="status"></p>
|
||||
</main>
|
||||
|
||||
<footer class="qb-bottom">
|
||||
<button type="button" class="qb-start-btn" id="btn-start-quiz" aria-label="เริ่มควิซ">
|
||||
<img src="/Quiz-Battle/IMAGE/btn-start-quiz.png" alt="" decoding="async">
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="room-lobby-br-fixed qb-room-lobby-br-fixed" id="qb-room-lobby-br-fixed">
|
||||
<button type="button" id="btn-voice" class="btn-voice-icon" title="เปิด/ปิดเสียงพูด" aria-label="เปิด/ปิดเสียงพูด">
|
||||
<img src="../Game/img/btn-mic-mute.png" alt="เปิดเสียง" id="btn-voice-icon-img" decoding="async">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script src="quiz-battle.js?v=0.0204"></script>
|
||||
<script src="../app-base.js?v=2"></script>
|
||||
<script src="quiz-battle.js?v=0.0220"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var QB_IMAGE = '/Quiz-Battle/IMAGE';
|
||||
var BASE = '/Game';
|
||||
var QB_IMAGE = typeof appPath === 'function' ? appPath('/Quiz-Battle/IMAGE') : '/Quiz-Battle/IMAGE';
|
||||
var BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game';
|
||||
var CHAR_KEY = 'gameCharacterId';
|
||||
var LOBBY_IDLE_DOWN_PREFIX = 'jdCharLobbyIdleDown:';
|
||||
var VOICE_STATE_KEY = 'qbVoiceMicOn';
|
||||
|
||||
if (localStorage.getItem('isLoggedIn') !== 'true') {
|
||||
window.location.href = '/Login/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Login/') : '/Login/';
|
||||
return;
|
||||
}
|
||||
|
||||
var grid = document.getElementById('qb-grid');
|
||||
var toast = document.getElementById('qb-toast');
|
||||
var selectedRoom = 1;
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : String(n);
|
||||
@@ -41,13 +42,28 @@
|
||||
function applyProfileAvatar() {
|
||||
var av = document.getElementById('lobby-profile-avatar');
|
||||
if (!av) return;
|
||||
var fallbackAvatar = typeof appPath === 'function' ? appPath('/Main-Menu/char-main.png') : '/Main-Menu/char-main.png';
|
||||
|
||||
var cid = getSelectedCharacterId();
|
||||
if (!cid) {
|
||||
av.onerror = null;
|
||||
av.src = '/Main-Menu/char-main.png';
|
||||
av.src = fallbackAvatar;
|
||||
return;
|
||||
}
|
||||
|
||||
// ใช้รูปเดียวกับหน้า Main-Lobby ก่อน (composited idle ที่บันทึกไว้)
|
||||
try {
|
||||
var savedLobbyAvatar = localStorage.getItem(LOBBY_IDLE_DOWN_PREFIX + cid) || '';
|
||||
if (savedLobbyAvatar && savedLobbyAvatar.indexOf('data:image/') === 0) {
|
||||
av.onerror = function () {
|
||||
av.onerror = null;
|
||||
av.src = fallbackAvatar;
|
||||
};
|
||||
av.src = savedLobbyAvatar;
|
||||
return;
|
||||
}
|
||||
} catch (e0) { /* ignore */ }
|
||||
|
||||
var urls = characterDownUrls(cid);
|
||||
var avStep = 0;
|
||||
av.onerror = function () {
|
||||
@@ -55,7 +71,7 @@
|
||||
if (avStep === 1) av.src = urls[1];
|
||||
else {
|
||||
av.onerror = null;
|
||||
av.src = '/Main-Menu/char-main.png';
|
||||
av.src = fallbackAvatar;
|
||||
}
|
||||
};
|
||||
av.src = urls[0];
|
||||
@@ -65,56 +81,126 @@
|
||||
try {
|
||||
localStorage.setItem('quizBattleRoom', String(n));
|
||||
} catch (e) { /* ignore */ }
|
||||
setToast('เริ่มควิซห้อง ' + n + ' (ต่อระบบจริงในเวอร์ชันถัดไป)');
|
||||
setToast('เริ่มเกมห้อง ' + n + ' (ต่อระบบจริงในเวอร์ชันถัดไป)');
|
||||
}
|
||||
|
||||
function selectRoom(n) {
|
||||
selectedRoom = n;
|
||||
grid?.querySelectorAll('.qb-card').forEach(function (btn) {
|
||||
var r = parseInt(btn.getAttribute('data-room'), 10);
|
||||
btn.classList.toggle('qb-card--selected', r === n);
|
||||
function getVoiceIconPath(micOn) {
|
||||
var imgPath = micOn ? '/Game/img/btn-mic-on.png' : '/Game/img/btn-mic-mute.png';
|
||||
return typeof appPath === 'function' ? appPath(imgPath) : imgPath;
|
||||
}
|
||||
|
||||
function setVoiceButtonIcon(btn, micOn) {
|
||||
if (!btn) return;
|
||||
var img = btn.querySelector('#btn-voice-icon-img') || btn.querySelector('img');
|
||||
if (img) {
|
||||
img.src = getVoiceIconPath(micOn);
|
||||
img.alt = micOn ? 'ปิดเสียง' : 'เปิดเสียง';
|
||||
}
|
||||
btn.title = micOn ? 'ปิดเสียงพูด' : 'เปิดเสียงพูด';
|
||||
btn.setAttribute('aria-label', micOn ? 'ปิดเสียงพูด' : 'เปิดเสียงพูด');
|
||||
}
|
||||
|
||||
function bindVoiceButton() {
|
||||
var btnVoice = document.getElementById('btn-voice');
|
||||
if (!btnVoice) return;
|
||||
var micOn = false;
|
||||
try { micOn = localStorage.getItem(VOICE_STATE_KEY) === '1'; } catch (e) { /* ignore */ }
|
||||
setVoiceButtonIcon(btnVoice, micOn);
|
||||
btnVoice.addEventListener('click', function () {
|
||||
micOn = !micOn;
|
||||
try { localStorage.setItem(VOICE_STATE_KEY, micOn ? '1' : '0'); } catch (e2) { /* ignore */ }
|
||||
setVoiceButtonIcon(btnVoice, micOn);
|
||||
});
|
||||
setToast('ห้อง ' + n + ' — กด Start ด้านล่างเพื่อเริ่ม');
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
var b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'qb-card' + (i === 1 ? ' qb-card--selected' : '');
|
||||
b.setAttribute('data-room', String(i));
|
||||
b.setAttribute('role', 'listitem');
|
||||
b.setAttribute('aria-label', 'ห้อง ' + i + ' — แตะเพื่อเลือก');
|
||||
b.addEventListener('click', function () {
|
||||
selectRoom(parseInt(this.getAttribute('data-room'), 10));
|
||||
});
|
||||
var card = document.createElement('div');
|
||||
card.className = 'qb-card';
|
||||
card.setAttribute('data-room', String(i));
|
||||
card.setAttribute('role', 'listitem');
|
||||
card.setAttribute('aria-label', 'ห้อง ' + i);
|
||||
|
||||
var roomImg = document.createElement('img');
|
||||
roomImg.className = 'qb-card-room';
|
||||
roomImg.src = QB_IMAGE + '/Room-' + pad2(i) + '.png';
|
||||
roomImg.alt = '';
|
||||
roomImg.decoding = 'async';
|
||||
var tagImg = document.createElement('img');
|
||||
tagImg.className = 'qb-card-tag';
|
||||
tagImg.src = QB_IMAGE + '/tag-' + pad2(i) + '.png';
|
||||
tagImg.alt = '';
|
||||
tagImg.decoding = 'async';
|
||||
b.appendChild(roomImg);
|
||||
b.appendChild(tagImg);
|
||||
grid.appendChild(b);
|
||||
|
||||
var startBtn = document.createElement('button');
|
||||
startBtn.type = 'button';
|
||||
startBtn.className = 'qb-card-start';
|
||||
startBtn.setAttribute('aria-label', 'เริ่มเกมห้อง ' + i);
|
||||
startBtn.addEventListener('click', (function (roomNo) {
|
||||
return function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
startQuizForRoom(roomNo);
|
||||
};
|
||||
})(i));
|
||||
|
||||
var startImg = document.createElement('img');
|
||||
startImg.src = QB_IMAGE + '/btn-start-quiz.png';
|
||||
startImg.alt = 'เริ่มเกม';
|
||||
startImg.decoding = 'async';
|
||||
startBtn.appendChild(startImg);
|
||||
|
||||
var countEl = document.createElement('span');
|
||||
countEl.className = 'qb-card-count';
|
||||
countEl.textContent = '0/50';
|
||||
|
||||
card.appendChild(roomImg);
|
||||
card.appendChild(startBtn);
|
||||
card.appendChild(countEl);
|
||||
grid.appendChild(card);
|
||||
}
|
||||
setToast('เลือกห้อง แล้วกด Start ด้านล่าง');
|
||||
setToast('กดปุ่มเริ่มเกมในแต่ละห้องเพื่อเล่น');
|
||||
}
|
||||
|
||||
document.getElementById('btn-start-quiz')?.addEventListener('click', function () {
|
||||
startQuizForRoom(selectedRoom);
|
||||
});
|
||||
function syncMainScale() {
|
||||
var mainEl = document.querySelector('.qb-main');
|
||||
if (!mainEl) return;
|
||||
var availableW = Math.max(1, mainEl.clientWidth);
|
||||
var availableH = Math.max(1, mainEl.clientHeight);
|
||||
// Base layout from image dimensions:
|
||||
// banner 419x151, banner top offset, card 271x351, 5 columns, 2 rows, gaps + toast spacing.
|
||||
var baseWidth = (271 * 5) + (18 * 4);
|
||||
var baseHeight = 60 + 151 + 12 + (351 * 2) + 14 + 8 + 24;
|
||||
var scaleW = availableW / baseWidth;
|
||||
var scaleH = availableH / baseHeight;
|
||||
var scale = Math.max(0.22, Math.min(1, Math.min(scaleW, scaleH)));
|
||||
mainEl.style.setProperty('--qb-main-scale', scale.toFixed(4));
|
||||
}
|
||||
|
||||
function syncUiScale() {
|
||||
var uiEl = document.querySelector('.qb-ui');
|
||||
if (!uiEl) return;
|
||||
var vw = Math.max(1, window.innerWidth || 0);
|
||||
var vh = Math.max(1, window.innerHeight || 0);
|
||||
// Base stage from mock-normal viewport.
|
||||
var scale = Math.max(0.34, Math.min(1, Math.min(vw / 1920, vh / 1080)));
|
||||
uiEl.style.setProperty('--qb-ui-scale', scale.toFixed(4));
|
||||
}
|
||||
|
||||
document.getElementById('btn-back')?.addEventListener('click', function () {
|
||||
window.location.href = '/Main-Lobby/';
|
||||
window.location.href = typeof appPath === 'function' ? appPath('/Main-Lobby/') : '/Main-Lobby/';
|
||||
});
|
||||
|
||||
applyProfileAvatar();
|
||||
buildGrid();
|
||||
bindVoiceButton();
|
||||
syncUiScale();
|
||||
syncMainScale();
|
||||
window.addEventListener('resize', function () {
|
||||
syncUiScale();
|
||||
syncMainScale();
|
||||
});
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(function () {
|
||||
syncUiScale();
|
||||
syncMainScale();
|
||||
}, 120);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,315 +1,386 @@
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
color: #fff;
|
||||
background: #030712;
|
||||
}
|
||||
|
||||
.qb-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.qb-bg-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.qb-vignette {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(ellipse at 50% 25%, transparent 15%, rgba(0, 0, 0, 0.4) 100%);
|
||||
}
|
||||
|
||||
.qb-ui {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
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));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.qb-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.qb-top-fill {
|
||||
flex: 1;
|
||||
min-width: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-icon-btn {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.qb-icon-btn img {
|
||||
display: block;
|
||||
height: clamp(38px, 10vw, 52px);
|
||||
width: auto;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
/* ย้อนกลับ — สเกลตามจอใหญ่ (ใกล้เคียง Main-Menu .menu-btn-back-img) */
|
||||
.qb-icon-btn--back img {
|
||||
width: clamp(48px, 6.5vw, 76px);
|
||||
height: auto;
|
||||
max-height: clamp(56px, 8.5vw, 92px);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.qb-profile-panel {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qb-avatar-frame {
|
||||
width: clamp(60px, 14vw, 92px);
|
||||
height: clamp(60px, 14vw, 92px);
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid rgba(34, 211, 238, 0.95);
|
||||
border-radius: 4px;
|
||||
background: rgba(3, 7, 18, 0.65);
|
||||
box-shadow:
|
||||
0 0 16px rgba(34, 211, 238, 0.55),
|
||||
0 0 28px rgba(168, 85, 247, 0.22),
|
||||
inset 0 0 12px rgba(34, 211, 238, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qb-avatar-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.qb-avatar-img.lobby-avatar-mask {
|
||||
width: 151%;
|
||||
height: 212%;
|
||||
/* object-fit / object-position: match Main-Lobby .lobby-avatar-mask */
|
||||
transform: translate(0, 16px);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qb-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(34, 211, 238, 0.45) rgba(0, 20, 40, 0.5);
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar-track {
|
||||
background: url(/Quiz-Battle/IMAGE/q-scroll-bg.png) center / 100% 100% no-repeat;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar-thumb {
|
||||
background: url(/Quiz-Battle/IMAGE/q-scroll-bar.png) center / 100% 100% no-repeat;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.qb-room-banner {
|
||||
width: 100%;
|
||||
max-width: min(94vw, 640px);
|
||||
margin: 0 auto clamp(10px, 1.5vh, 18px);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.qb-room-banner {
|
||||
max-width: min(92vw, 760px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.qb-room-banner {
|
||||
max-width: min(88vw, 880px);
|
||||
}
|
||||
}
|
||||
|
||||
.qb-room-banner img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
|
||||
.qb-grid {
|
||||
width: 100%;
|
||||
max-width: min(96vw, 1040px);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: clamp(8px, 1.2vw, 18px);
|
||||
padding: clamp(8px, 1.2vw, 14px) clamp(6px, 1vw, 12px) clamp(14px, 2vh, 24px);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.qb-grid {
|
||||
max-width: min(94vw, 1180px);
|
||||
gap: clamp(10px, 1vw, 20px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.qb-grid {
|
||||
max-width: min(92vw, 1320px);
|
||||
gap: clamp(12px, 0.9vw, 22px);
|
||||
}
|
||||
}
|
||||
|
||||
.qb-card {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition: box-shadow 0.2s, transform 0.15s;
|
||||
}
|
||||
|
||||
.qb-card:hover,
|
||||
.qb-card:focus-visible {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.qb-card:focus-visible {
|
||||
outline: 2px solid #22d3ee;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.qb-card--selected {
|
||||
box-shadow: 0 0 0 2px rgba(168, 85, 247, 0.9), 0 0 18px rgba(34, 211, 238, 0.45);
|
||||
}
|
||||
|
||||
.qb-card-room {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.qb-card-tag {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-toast {
|
||||
text-align: center;
|
||||
font-size: clamp(0.88rem, 1.5vw, 1.05rem);
|
||||
color: rgba(248, 250, 252, 0.88);
|
||||
min-height: 1.2em;
|
||||
margin-top: 4px;
|
||||
flex-shrink: 0;
|
||||
padding: 0 8px 4px;
|
||||
}
|
||||
|
||||
.qb-bottom {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px 12px max(12px, env(safe-area-inset-bottom));
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.qb-start-btn {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
filter: drop-shadow(0 4px 20px rgba(34, 211, 238, 0.35));
|
||||
transition: transform 0.15s, filter 0.2s;
|
||||
}
|
||||
|
||||
.qb-start-btn:hover,
|
||||
.qb-start-btn:focus-visible {
|
||||
transform: scale(1.04);
|
||||
filter: drop-shadow(0 6px 28px rgba(168, 85, 247, 0.45));
|
||||
}
|
||||
|
||||
.qb-start-btn:focus-visible {
|
||||
outline: 2px solid #22d3ee;
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
.qb-start-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.qb-start-btn img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: clamp(54px, 9vw, 96px);
|
||||
max-height: min(22vh, 120px);
|
||||
max-width: min(94vw, 440px);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.qb-start-btn img {
|
||||
height: clamp(64px, 7.5vw, 104px);
|
||||
max-height: 128px;
|
||||
max-width: min(90vw, 520px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.qb-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.qb-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'NotoSansThai';
|
||||
src: url('../FONTS/NotoSansThai-ExtraBold.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NotoSansThaiMedium';
|
||||
src: url('../FONTS/NotoSansThai-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Kanit', 'Sarabun', 'Segoe UI', system-ui, sans-serif;
|
||||
color: #fff;
|
||||
background: #030712;
|
||||
}
|
||||
|
||||
.qb-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.qb-bg-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.qb-vignette {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(ellipse at 50% 25%, transparent 15%, rgba(0, 0, 0, 0.4) 100%);
|
||||
}
|
||||
|
||||
.qb-ui {
|
||||
--qb-ui-scale: 1;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
padding:
|
||||
max(calc(10px * var(--qb-ui-scale)), env(safe-area-inset-top))
|
||||
max(calc(12px * var(--qb-ui-scale)), env(safe-area-inset-right))
|
||||
max(calc(14px * var(--qb-ui-scale)), env(safe-area-inset-bottom))
|
||||
max(calc(12px * var(--qb-ui-scale)), env(safe-area-inset-left));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qb-top {
|
||||
position: absolute;
|
||||
top: max(calc(10px * var(--qb-ui-scale)), env(safe-area-inset-top));
|
||||
left: max(calc(12px * var(--qb-ui-scale)), env(safe-area-inset-left));
|
||||
right: max(calc(12px * var(--qb-ui-scale)), env(safe-area-inset-right));
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
gap: calc(8px * var(--qb-ui-scale));
|
||||
min-height: calc(157px * var(--qb-ui-scale));
|
||||
z-index: 6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-top-fill {
|
||||
flex: 1;
|
||||
min-width: calc(8px * var(--qb-ui-scale));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* กรอบแดง 1: ใช้ layout แบบ Join Room back button */
|
||||
.qb-join-back {
|
||||
position: fixed;
|
||||
top: max(2px, env(safe-area-inset-top));
|
||||
left: max(2px, env(safe-area-inset-left));
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
pointer-events: auto;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.qb-join-back-img {
|
||||
--qb-exit-btn-height: 142px;
|
||||
display: block;
|
||||
height: var(--qb-exit-btn-height);
|
||||
width: calc(var(--qb-exit-btn-height) * (122 / 142));
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* กรอบแดง 2: ใช้ layout แบบ room-lobby-profile-avatar-viewport */
|
||||
.qb-room-profile-wrap {
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-room-profile-frame {
|
||||
position: relative;
|
||||
width: 159px;
|
||||
height: 157px;
|
||||
min-width: 159px;
|
||||
min-height: 157px;
|
||||
max-width: 159px;
|
||||
max-height: 157px;
|
||||
aspect-ratio: 159 / 157;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
background: url(../Game/img/btn-profile-bg.png) center/100% 100% no-repeat;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.qb-room-profile-avatar-viewport {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 10px;
|
||||
width: 124px;
|
||||
height: 118px;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.qb-room-profile-avatar {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 5px;
|
||||
display: block;
|
||||
width: 120%;
|
||||
height: auto;
|
||||
max-width: none;
|
||||
object-fit: contain;
|
||||
object-position: center top;
|
||||
transform: translateX(-50%);
|
||||
transform-origin: center center;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-main {
|
||||
--qb-main-scale: 1;
|
||||
--qb-banner-width: calc(419px * var(--qb-main-scale));
|
||||
--qb-banner-height: calc(151px * var(--qb-main-scale));
|
||||
--qb-card-width: calc(271px * var(--qb-main-scale));
|
||||
--qb-card-height: calc(351px * var(--qb-main-scale));
|
||||
--qb-card-start-height: calc(83px * var(--qb-main-scale));
|
||||
--qb-card-start-width: calc(168px * var(--qb-main-scale));
|
||||
--qb-grid-gap-x: calc(18px * var(--qb-main-scale));
|
||||
--qb-grid-gap-y: calc(14px * var(--qb-main-scale));
|
||||
--qb-main-gap-banner-grid: calc(12px * var(--qb-main-scale));
|
||||
--qb-main-gap-grid-toast: calc(8px * var(--qb-main-scale));
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
height: calc(100dvh - max(calc(10px * var(--qb-ui-scale)), env(safe-area-inset-top)) - max(calc(14px * var(--qb-ui-scale)), env(safe-area-inset-bottom)));
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding-top: calc(2px * var(--qb-main-scale));
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.qb-main::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.qb-room-banner {
|
||||
flex: 0 0 auto;
|
||||
width: var(--qb-banner-width);
|
||||
height: var(--qb-banner-height);
|
||||
max-width: 100%;
|
||||
flex-shrink: 0;
|
||||
margin: 0 auto var(--qb-main-gap-banner-grid);
|
||||
margin-top: calc(60px * var(--qb-main-scale));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.qb-room-banner img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
|
||||
.qb-grid {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, var(--qb-card-width));
|
||||
grid-auto-rows: var(--qb-card-height);
|
||||
justify-content: center;
|
||||
align-content: start;
|
||||
column-gap: var(--qb-grid-gap-x);
|
||||
row-gap: var(--qb-grid-gap-y);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.qb-card {
|
||||
position: relative;
|
||||
width: var(--qb-card-width);
|
||||
height: var(--qb-card-height);
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.qb-card-room {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.qb-card-start {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(25px * var(--qb-main-scale));
|
||||
transform: translateX(-50%);
|
||||
width: var(--qb-card-start-width);
|
||||
height: var(--qb-card-start-height);
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
transition: transform 0.14s ease, filter 0.14s ease;
|
||||
}
|
||||
|
||||
.qb-card-start:hover,
|
||||
.qb-card-start:focus-visible {
|
||||
transform: translateX(-50%) translateY(-1px);
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
|
||||
.qb-card-start:focus-visible {
|
||||
outline: 2px solid #22d3ee;
|
||||
outline-offset: 3px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.qb-card-start:active {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.qb-card-start img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-card-count {
|
||||
position: absolute;
|
||||
top: calc(44px * var(--qb-main-scale));
|
||||
right: calc(54px * var(--qb-main-scale));
|
||||
font-family: 'NotoSansThaiMedium', 'NotoSansThai', 'Kanit', 'Sarabun', system-ui, sans-serif;
|
||||
font-size: calc(22px * var(--qb-main-scale));
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #ffffff;
|
||||
letter-spacing: 0;
|
||||
text-shadow:
|
||||
0 0 calc(10px * var(--qb-main-scale)) rgba(87, 239, 255, 0.55),
|
||||
0 0 calc(2px * var(--qb-main-scale)) rgba(0, 0, 0, 0.65);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.qb-toast {
|
||||
text-align: center;
|
||||
font-size: calc(20px * var(--qb-main-scale));
|
||||
color: rgba(248, 250, 252, 0.88);
|
||||
line-height: 1.2;
|
||||
min-height: calc(24px * var(--qb-main-scale));
|
||||
margin-top: var(--qb-main-gap-grid-toast);
|
||||
flex-shrink: 0;
|
||||
padding: 0 calc(8px * var(--qb-main-scale)) calc(4px * var(--qb-main-scale));
|
||||
}
|
||||
|
||||
/* มุมขวาล่าง — btn-voice (ซ่อนไว้ก่อน: ทับการ์ด Room 09–10 ในหน้าเลือกห้อง) */
|
||||
.qb-room-lobby-br-fixed {
|
||||
--lobby-br-btn: calc(121px * var(--qb-ui-scale, 1));
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
position: fixed;
|
||||
bottom: max(calc(0.95rem * var(--qb-ui-scale, 1)), env(safe-area-inset-bottom));
|
||||
right: max(calc(1.25rem * var(--qb-ui-scale, 1)), env(safe-area-inset-right));
|
||||
z-index: 400;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
gap: calc(10px * var(--qb-ui-scale, 1));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qb-room-lobby-br-fixed .btn-voice-icon {
|
||||
pointer-events: auto;
|
||||
box-sizing: border-box;
|
||||
width: var(--lobby-br-btn);
|
||||
height: var(--lobby-br-btn);
|
||||
min-width: var(--lobby-br-btn);
|
||||
min-height: var(--lobby-br-btn);
|
||||
max-width: var(--lobby-br-btn);
|
||||
max-height: var(--lobby-br-btn);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qb-room-lobby-br-fixed .btn-voice-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,14 +6,33 @@
|
||||
'use strict';
|
||||
if (typeof global.appPath === 'function') return;
|
||||
|
||||
/**
|
||||
* @param {string} path - must start with / (e.g. '/Game/play.html')
|
||||
* @returns {string}
|
||||
*/
|
||||
function sitePathPrefix() {
|
||||
try {
|
||||
var loc = global.location && global.location.pathname;
|
||||
if (!loc) return '';
|
||||
var m = loc.match(
|
||||
/^(\/[^/]+)\/(?:Main-Lobby|Main-Menu|Login|Create(?:%20| )Room|Join(?:%20| )Room|Loading|Quiz-Battle|Game)(?:\/|$)/i
|
||||
);
|
||||
return m ? m[1] : '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
global.appPath = function appPath(path) {
|
||||
if (path == null || path === '') return '';
|
||||
var p = String(path);
|
||||
if (p.charAt(0) !== '/') return '/' + p.replace(/^\/+/, '');
|
||||
return p;
|
||||
if (p.charAt(0) !== '/') p = '/' + p.replace(/^\/+/, '');
|
||||
return sitePathPrefix() + p;
|
||||
};
|
||||
|
||||
global.designLoginHref = function designLoginHref() { return appPath('/Login/'); };
|
||||
global.designMainLobbyHref = function designMainLobbyHref() { return appPath('/Main-Lobby/'); };
|
||||
global.designMainMenuHref = function designMainMenuHref() { return appPath('/Main-Menu/'); };
|
||||
global.designCreateRoomHref = function designCreateRoomHref() { return appPath('/Create%20Room/'); };
|
||||
global.designJoinRoomHref = function designJoinRoomHref() { return appPath('/Join%20Room/'); };
|
||||
global.designLoadingHref = function designLoadingHref(query) {
|
||||
var q = query ? String(query) : '';
|
||||
return appPath('/Loading/') + (q ? (q.charAt(0) === '?' ? q : '?' + q) : '');
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
|
||||