diff --git a/scripts/pull-www-from-server.sh b/scripts/pull-www-from-server.sh new file mode 100755 index 0000000..8fb30b4 --- /dev/null +++ b/scripts/pull-www-from-server.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# ดึงไฟล์จาก /var/www/html เข้า repo www/html หลังอัปโหลด/แก้บนเซิร์ฟ — ให้ git ใน repo ตรงกับของจริง +# ย้อนกับ deploy-www.sh (exclude ชุดเดียวกัน; ไม่ใช้ --delete เพื่อไม่ลบไฟล์ที่มีแค่ใน repo) +set -euo pipefail +REPO="$(cd "$(dirname "$0")/.." && pwd)" +DST="/var/www/html/" +SRC="$REPO/www/html/" +if [[ "$(id -u)" -ne 0 ]]; then + echo "Run as root: sudo $0" >&2 + exit 1 +fi + +rsync -a --checksum \ + --exclude 'node_modules' \ + --exclude '**/node_modules/**' \ + --exclude 'Game/data/maps/' \ + --exclude 'Game/data/game-timing.json' \ + "$DST" "$SRC" + +if [[ -n "${SUDO_UID:-}" ]]; then + chown -R "$SUDO_UID:$SUDO_GID" "$SRC" +fi + +echo "OK: pulled $DST -> $SRC (no --delete; excludes match deploy-www.sh)" diff --git a/www/html/Game/public/css/room-lobby.css b/www/html/Game/public/css/room-lobby.css new file mode 100644 index 0000000..a1661df --- /dev/null +++ b/www/html/Game/public/css/room-lobby.css @@ -0,0 +1,2890 @@ +* { box-sizing: border-box; } +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; +} +.container { max-width: 720px; margin: 1rem auto; padding: 1rem; } + +/* โครง Editor — แยกแถบเครื่องมือ / พื้นที่วาด */ +.editor-page { + display: block; +} + +/* Editor ฝังใน Admin (?embed=1) — เต็มความสูง iframe, พื้นที่แคนวาสใหญ่ + เลื่อนแยก */ +html.editor-embed-admin { + height: 100%; +} +html.editor-embed-admin body { + height: 100%; + margin: 0; + overflow: hidden; +} +body.editor-embed-admin .version-tag { + display: none !important; +} +body.editor-embed-admin .container.editor-page { + max-width: none !important; + width: 100%; + height: 100%; + margin: 0 !important; + padding: 0.35rem 0.5rem 0.45rem; + display: flex; + flex-direction: column; + min-height: 0; + box-sizing: border-box; +} +body.editor-embed-admin .container.editor-page > h1 { + display: none; +} +body.editor-embed-admin .editor-toolbar-wrap { + flex: 0 1 auto; + max-height: min(36vh, 360px); + overflow-x: hidden; + overflow-y: auto; + padding: 0.15rem 0.25rem 0.45rem 0; + margin: 0 0 0.3rem; + border-bottom: 1px solid rgba(65, 72, 104, 0.9); + scrollbar-width: thin; +} +body.editor-embed-admin .editor-workspace { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} +body.editor-embed-admin .canvas-wrap { + flex: 1 1 auto; + min-height: 140px; + overflow: auto; + margin: 0 !important; + border-radius: 10px; + border: 1px solid #414868; + background: #0f1018; + -webkit-overflow-scrolling: touch; +} +body.editor-embed-admin #editor-status { + flex: 0 0 auto; + margin: 0.3rem 0 0; + font-size: 0.8rem; + line-height: 1.35; +} +body.editor-embed-admin .editor-legend-short { + margin: 0.25rem 0 0; + font-size: 0.76rem; + line-height: 1.4; + color: #a8b0d0; +} +body.editor-embed-admin .editor-toolbar { + margin-top: 0.25rem; + margin-bottom: 0.35rem; +} +body.editor-embed-admin .frogger-lanes { + margin: 0.35rem 0; +} +body.editor-embed-admin .editor-bg-section { + margin: 0.35rem 0 0.15rem; +} +h1 { color: #7aa2f7; } +a { color: #9ece6a; } +label { display: inline-block; margin: 0.25rem 0.5rem 0.25rem 0; } +input, button { padding: 0.4rem 0.6rem; margin: 0 0.25rem 0 0; border-radius: 6px; border: 1px solid #414868; } +input { background: #24283b; color: #c0caf5; } +button { background: #7aa2f7; color: #1a1b26; cursor: pointer; border: none; } +.legend { font-size: 0.9rem; color: #a9b1d6; margin: 0.5rem 0; } +.frogger-lanes { margin: 1rem 0; padding: 0.75rem; background: #24283b; border-radius: 8px; position: relative; z-index: 2; min-height: 80px; } +.frogger-lanes h3 { margin: 0 0 0.5rem 0; color: #7aa2f7; font-size: 1rem; } +.frogger-lanes table { width: 100%; border-collapse: collapse; font-size: 0.85rem; } +.frogger-lanes th, .frogger-lanes td { padding: 0.25rem 0.5rem; text-align: left; border: 1px solid #414868; } +.frogger-lanes select, .frogger-lanes input[type="number"] { padding: 0.35rem 0.5rem; min-width: 4rem; background: #1a1b26; color: #c0caf5; border: 1px solid #414868; border-radius: 4px; cursor: pointer; font-size: 0.9rem; } +.frogger-lanes select { min-width: 6rem; } +.frogger-lanes .lane-speed { width: 4rem; } +.frogger-lanes .lane-spacing { width: 4rem; } +.frogger-lanes td { vertical-align: middle; } +.frogger-lanes select:disabled, .frogger-lanes input:disabled { opacity: 0.6; cursor: not-allowed; } +.game-type-hint { font-size: 0.85rem; color: #a9b1d6; margin-left: 0.5rem; } +.editor-toolbar { margin: 1rem 0; flex-wrap: wrap; display: flex; align-items: center; gap: 0.5rem 1rem; } +.editor-toolbar label, .editor-toolbar .game-type-hint { margin: 0; } +.editor-bg-section { margin: 0.75rem 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; } +.editor-bg-section label { margin: 0; } +.canvas-wrap { margin: 1rem 0; } +.container { padding-left: 1rem; padding-right: 1rem; } +#editor-canvas { background: #1a1b26; display: block; } +.game-wrap { display: flex; flex-direction: column; height: 100vh; height: 100dvh; min-height: 0; } +.game-header { flex-shrink: 0; background: #24283b; padding: 0.5rem 1rem; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; } +.game-header span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.game-header button { flex-shrink: 0; } +#game-canvas { + flex: 1 1 auto; + display: block; + width: 100%; + min-height: 0; + min-width: 0; + touch-action: none; +} +.game-wrap .chat-panel { flex-shrink: 0; } +.chat-panel { height: 160px; border-top: 1px solid #414868; display: flex; flex-direction: column; } +#chat-messages { flex: 1; overflow-y: auto; padding: 0.5rem; font-size: 0.9rem; } +#chat-form { display: flex; padding: 0.5rem; gap: 0.5rem; } +#chat-input { flex: 1; } + +.lobby-section { margin: 1.25rem 0; padding: 1rem; background: #24283b; border-radius: 8px; } +.lobby-section h2 { margin: 0 0 0.75rem 0; font-size: 1rem; color: #7aa2f7; } +#room-list { list-style: none; margin: 0; padding: 0; } +#room-list .room-item { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; padding: 0.5rem 0; border-bottom: 1px solid #414868; } +#room-list .room-item:last-child { border-bottom: none; } +#room-list .room-name { font-weight: 600; color: #c0caf5; min-width: 0; } +#room-list .room-meta { font-size: 0.85rem; color: #a9b1d6; } +#room-list .room-meta-inline { font-size: 0.8rem; color: #565f89; font-weight: normal; } +#room-list-status { margin: 0; color: #a9b1d6; font-size: 0.9rem; } + +.room-lobby-peers { margin: 1rem 0; padding: 0.75rem; background: #24283b; border-radius: 8px; } +.room-lobby-peer { padding: 0.35rem 0; color: #c0caf5; } +.room-lobby-peer:not(:last-child) { border-bottom: 1px solid #414868; } +#lobby-map-wrap { position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; min-width: 100vw; min-height: 100vh; z-index: 0; } +body.room-lobby-page { + background: + radial-gradient(120% 120% at 50% -12%, rgba(86, 122, 255, 0.26) 0%, rgba(30, 37, 83, 0.16) 40%, rgba(7, 10, 25, 0) 75%), + linear-gradient(180deg, #1a1d46 0%, #111530 42%, #090d22 100%); + color: #ebf6ff; +} +#lobby-map-wrap::before { + content: ''; + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; + background: + radial-gradient(80% 65% at 50% 18%, rgba(106, 194, 255, 0.2), rgba(106, 194, 255, 0) 72%), + linear-gradient(180deg, rgba(10, 16, 46, 0.25) 0%, rgba(6, 12, 31, 0.05) 30%, rgba(5, 9, 24, 0.5) 100%); +} +#lobby-map-wrap::after { + content: ''; + position: absolute; + inset: 0; + z-index: 2; + pointer-events: none; + box-shadow: inset 0 0 160px rgba(8, 12, 30, 0.85); +} +#lobby-map-canvas { + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + min-width: 100%; + min-height: 100%; + margin: 0; + padding: 0; + background: + radial-gradient(circle at 50% 20%, rgba(124, 197, 255, 0.25) 0%, rgba(124, 197, 255, 0) 45%), + linear-gradient(180deg, #252c66 0%, #1a2150 45%, #121a3b 100%); + box-sizing: border-box; +} +#lobby-map-canvas:focus { + outline: none; +} +#lobby-map-canvas:focus-visible { + outline: 2px solid rgba(122, 162, 247, 0.45); + outline-offset: -2px; +} +/* LobbyB (mn8nx46h) — ไม่แสดงปุ่มพร้อม */ +body.room-lobby--lobby-b .room-lobby-ready-fixed { + display: none !important; +} +.lobby-interact-hint { + position: absolute; + bottom: max(10px, env(safe-area-inset-bottom)); + left: 50%; + transform: translateX(-50%); + z-index: 2; + margin: 0; + padding: 0.2rem 0.55rem; + font-size: clamp(0.72rem, 1.8vw, 0.85rem); + color: rgba(203, 213, 245, 0.9); + text-shadow: 0 1px 10px rgba(0, 0, 0, 0.55); + pointer-events: none; + font-family: system-ui, 'Kanit', sans-serif; + white-space: nowrap; +} +.lobby-interact-hint kbd { + display: inline-block; + padding: 0.06em 0.32em; + border-radius: 4px; + background: rgba(26, 27, 38, 0.88); + border: 1px solid rgba(122, 162, 247, 0.45); + font-size: 0.88em; + font-family: inherit; +} +.lobby-zoom-pct { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 2; + padding: 6px 12px; + border-radius: 8px; + background: rgba(26, 27, 38, 0.85); + color: #c0caf5; + font-size: 1rem; + font-weight: 600; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease; +} +.lobby-zoom-pct.lobby-zoom-pct-visible { opacity: 1; } +/* ซ่อนแผง overlay รายชื่อ / host / แชท — โถงโฟกัสแผนที่ + ปุ่ม READY/ไมค์ ด้านนอก */ +.room-lobby-overlay { display: none !important; } +.room-lobby-top-left-cluster { + position: fixed; + top: max(12px, env(safe-area-inset-top)); + left: max(0px, env(safe-area-inset-left)); + z-index: 55; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.4rem; + pointer-events: none; +} +.room-lobby-top-left-cluster .room-lobby-exit-fixed { + pointer-events: auto; +} +.room-lobby-room-id-pill { + pointer-events: auto; + display: inline-flex; + align-items: center; + gap: 0.4rem; + min-height: 44px; + padding: 0.5rem 0.9rem; + border-radius: 12px; + border: 1px solid rgba(106, 232, 255, 0.58); + background: linear-gradient(180deg, rgba(43, 67, 128, 0.86), rgba(22, 34, 74, 0.9)); + box-shadow: + inset 0 0 0 1px rgba(188, 247, 255, 0.2), + 0 0 18px rgba(80, 213, 255, 0.32); + color: #dff6ff; + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif; + letter-spacing: 0.03em; +} +.room-lobby-room-id-label { + font-weight: 600; + font-size: clamp(0.78rem, 1.7vw, 0.9rem); + opacity: 0.92; +} +#room-id-value { + font-weight: 800; + font-size: clamp(0.85rem, 1.9vw, 1rem); + color: #f4fdff; +} +.room-lobby-room-id-lock { + font-size: 0.85rem; + filter: drop-shadow(0 0 6px rgba(105, 227, 255, 0.6)); +} + +/* มุมขวาบน — โปรไฟล์ตัวละคร */ +.room-lobby-top-right-cluster { + position: fixed; + top: max(12px, env(safe-area-inset-top)); + right: max(12px, env(safe-area-inset-right)); + z-index: 56; + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.4rem; + pointer-events: none; +} +.room-lobby-top-right-cluster > * { + pointer-events: auto; +} +.room-lobby-top-right-actions { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.45rem; +} +.room-lobby-top-right-action-btn { + width: 161px; + height: 176px; + min-width: 161px; + min-height: 176px; + max-width: 161px; + max-height: 176px; + aspect-ratio: 161 / 176; + padding: 0; + margin: 0; + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; + cursor: pointer; +} +.room-lobby-top-right-action-btn img { + width: 161px; + height: 176px; + min-width: 161px; + min-height: 176px; + max-width: 161px; + max-height: 176px; + aspect-ratio: 161 / 176; + object-fit: contain; + display: block; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} +.room-lobby-profile-wrap { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.2rem; +} +.room-lobby-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(../img/btn-profile-bg.png) center/100% 100% no-repeat; + box-shadow: none; +} +.room-lobby-profile-avatar-viewport { + position: absolute; + left: 12px; + top: 10px; + width: 124px; + height: 118px; + overflow: hidden; + border-radius: 12px; +} +.room-lobby-profile-frame #room-lobby-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; +} +.room-lobby-profile-set-btn { + position: absolute; + right: 0; + bottom: 0; + width: 71px; + height: 71px; + min-width: 71px; + min-height: 71px; + max-width: 71px; + max-height: 71px; + aspect-ratio: 71 / 71; + padding: 0; + margin: 0; + border: none; + border-radius: 0; + background: transparent; + cursor: pointer; + z-index: 2; +} +.room-lobby-profile-set-btn img { + display: block; + width: 71px; + height: 71px; + min-width: 71px; + min-height: 71px; + max-width: 71px; + max-height: 71px; + aspect-ratio: 71 / 71; + 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; +} + +/* Host Console popup */ +.room-lobby-host-console-overlay { + position: fixed; + inset: 0; + z-index: 10070; + 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-host-console-overlay.is-hidden { + display: none !important; +} +.room-lobby-host-console-backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.8); +} +.room-lobby-host-console-dialog { + position: relative; + z-index: 1; + width: min(990px, calc(100vw - 24px), calc((100dvh - 24px) * (990 / 881))); + aspect-ratio: 990 / 881; + max-height: calc(100dvh - 24px); + --hc-scale: 1; + --hc-close-hit-size: calc(82px * var(--hc-scale)); + --hc-close-hit-top: 9%; + --hc-close-hit-right: 6.5%; + background: url(../img/03-4-Host-Console/host-console-bg.png) center/100% 100% no-repeat; + box-sizing: border-box; +} +.room-lobby-host-console-body { + position: absolute; + top: 13.6%; + right: 7.9%; + bottom: 6%; + left: 8.2%; + display: flex; + flex-direction: column; + align-items: center; + min-height: 0; + padding-top: calc(50px * var(--hc-scale)); + padding-bottom: calc(62px * var(--hc-scale)); + box-sizing: border-box; +} +.room-lobby-host-console-close { + position: absolute; + top: var(--hc-close-hit-top); + right: var(--hc-close-hit-right); + width: var(--hc-close-hit-size); + height: var(--hc-close-hit-size); + border: none; + border-radius: 0; + background: transparent; + color: transparent; + font-size: 0; + line-height: 1; + cursor: pointer; + text-shadow: none; + z-index: 3; + -webkit-tap-highlight-color: transparent; +} +.room-lobby-host-console-txt-img { + width: auto; + object-fit: contain; + display: block; +} +.room-lobby-host-console-txt-max { + height: calc(66px * var(--hc-scale)); + margin-bottom: calc(10px * var(--hc-scale)); +} +.room-lobby-host-console-max-wrap { + position: relative; + width: min(82%, calc(657px * var(--hc-scale))); + aspect-ratio: 657 / 141; + margin-bottom: calc(-10px * var(--hc-scale)); + margin-top: calc(-30px * var(--hc-scale)); +} +.room-lobby-host-console-textfield-bg { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +.room-lobby-host-console-arrow-btn { + position: absolute; + top: 50%; + width: calc(51px * var(--hc-scale)); + height: calc(54px * var(--hc-scale)); + padding: 0; + margin: 0; + border: none; + border-radius: 0; + background: transparent; + cursor: pointer; + transform: translateY(-50%); +} +.room-lobby-host-console-arrow-left { left: 4.9%; } +.room-lobby-host-console-arrow-right { right: 4.9%; } +.room-lobby-host-console-arrow-btn img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +.room-lobby-host-console-max-value { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + min-width: 2ch; + text-align: center; + font-size: calc(40px * var(--hc-scale)); + font-weight: 700; + color: #eaf9ff; + text-shadow: 0 0 10px rgba(0, 255, 255, 0.35); +} +.room-lobby-host-console-summary { + margin: 0 0 calc(12px * var(--hc-scale)); + font-size: calc(24px * var(--hc-scale)); + color: #d9f3ff; + margin-bottom: 4%; +} +.room-lobby-host-console-line { + width: min(94%, calc(744px * var(--hc-scale))); + height: auto; + margin-bottom: calc(10px * var(--hc-scale)); + object-fit: contain; +} +.room-lobby-host-console-txt-members { + height: calc(61px * var(--hc-scale)); + margin-bottom: calc(8px * var(--hc-scale)); +} +.room-lobby-host-console-members { + width: min(84%, calc(694px * var(--hc-scale))); + flex: 1 1 auto; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + padding-right: calc(40px * var(--hc-scale)); + padding-left: calc(35px * var(--hc-scale)); + scrollbar-width: auto; + scrollbar-color: rgba(255, 104, 228, 0.98) rgba(20, 40, 70, 0.55); + scrollbar-gutter: stable both-edges; +} +.room-lobby-host-console-members::-webkit-scrollbar { + width: clamp(24px, calc(36px * var(--hc-scale)), 42px); +} +.room-lobby-host-console-members::-webkit-scrollbar-track { + background: rgba(20, 40, 70, 0.55); + border-radius: 999px; + border: 1px solid rgba(90, 220, 255, 0.35); +} +.room-lobby-host-console-members::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(255, 125, 236, 1) 0%, rgba(241, 74, 215, 1) 100%); + border-radius: 999px; + border: 3px solid rgba(16, 32, 56, 0.65); + background-clip: padding-box; + box-shadow: 0 0 10px rgba(255, 84, 226, 0.45); +} +.room-lobby-host-console-members-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: calc(18px * var(--hc-scale)); +} +.room-lobby-host-console-member-item { + position: relative; + min-height: calc(44px * var(--hc-scale)); + padding: calc(2px * var(--hc-scale)) calc(104px * var(--hc-scale)) calc(2px * var(--hc-scale)) calc(12px * var(--hc-scale)); + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.6rem; + color: #e9f8ff; + font-size: calc(28px * var(--hc-scale)); + background: url(../img/03-4-Host-Console/host-console-line-2.png) left bottom/100% 3px no-repeat; +} +.room-lobby-host-console-member-item.is-host { + min-height: calc(80px * var(--hc-scale)); + padding-right: calc(12px * var(--hc-scale)); + background: url(../img/03-4-Host-Console/host-console-hostname.png) center/100% 100% no-repeat; + font-weight: 700; +} +.room-lobby-host-console-member-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: inherit; + padding-left: 4%; + margin-top: -4%; +} +.room-lobby-host-console-member-item.is-host .room-lobby-host-console-member-name { + margin-top: 0%; /* เพิ่มค่านี้ = ขยับลง */ +} +.room-lobby-host-console-delete-btn { + position: absolute; + right: calc(10px * var(--hc-scale)); + top: 50%; + transform: translateY(-50%); + width: calc(84px * var(--hc-scale)); + height: calc(84px * var(--hc-scale)); + min-width: calc(84px * var(--hc-scale)); + padding: 0; + border: none; + border-radius: 0; + background: transparent; + cursor: pointer; + margin-top: -2%; + margin-right: -1%; +} +.room-lobby-host-console-delete-btn img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +.room-lobby-host-console-delete-btn:disabled { + opacity: 0.35; + cursor: default; +} +.room-lobby-host-console-confirm { + position: absolute; + left: 50%; + bottom: calc(-60px * var(--hc-scale)); + transform: translateX(-50%); + width: min(43%, calc(350px * var(--hc-scale))); + aspect-ratio: 350 / 129; + border: none; + border-radius: 0; + padding: 0; + background: transparent; + cursor: pointer; +} +.room-lobby-host-console-confirm img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} +/* Profile popup: tightened scaling for reduced viewports */ +@media (max-width: 1100px), (max-height: 700px) { + .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%; + } +} +@media (max-width: 900px), (max-height: 560px) { + .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%; + } +} +.room-lobby-actions { margin-top: 1rem; display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; } +.room-lobby-actions .room-lobby-btn-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; opacity: 0; pointer-events: none; } +.room-lobby-actions label { margin: 0; } +.room-lobby-actions #btn-start { background: #9ece6a; color: #1a1b26; } +#host-only { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; } +#host-only select { padding: 0.4rem 0.6rem; border-radius: 6px; border: 1px solid #414868; background: #24283b; color: #c0caf5; } +.host-start-area-hint { + flex-basis: 100%; + margin: 0; + font-size: 0.88rem; + color: #e0af68; + line-height: 1.35; + max-width: 100%; +} +#host-only #btn-start:disabled { + opacity: 0.5; + cursor: not-allowed; + filter: grayscale(0.25); +} +.room-lobby-chat { margin-top: 1rem; } +.room-lobby-chat.chat-panel { + border-radius: 12px; + overflow: hidden; + box-shadow: none; +} +.room-lobby-chat-peers.chat-panel { + position: fixed; + right: 0.25rem; + bottom: 10rem; + width: 576px; + height: 525px; + max-width: calc(100vw - 2.5rem); + z-index: 52; + margin-top: 0; + display: flex; + flex-flow: column; + padding: 2rem; + box-sizing: border-box; + border-top: 0px solid #414868; + background: url(../img/chat-msg-bg-alpha.png) center/100% 100% no-repeat; + background-color: transparent; +} +.room-lobby-chat-peers.chat-panel .chat-header-title.chat-header-title-img img { width: 90px; height: 77px; } +.room-lobby-chat-peers.chat-panel .chat-close-btn { width: 48px; height: 48px; } +.room-lobby-chat-peers.chat-panel .chat-close-btn img { width: 50px; height: 50px; } +.room-lobby-chat-peers.chat-panel #chat-messages { padding: 0.75rem; font-size: 1.05rem; gap: 0.5rem; } +.room-lobby-chat-peers.chat-panel .chat-form-bar { padding: 0.6rem 0; gap: 0.5rem; } +.room-lobby-chat-peers.chat-panel #chat-input { padding: 0.6rem 0.85rem; font-size: 1.05rem; } +.room-lobby-chat-peers.chat-panel .chat-send-btn { width: 52px; height: 52px; } +.room-lobby-chat-peers.chat-panel .chat-send-btn img { width: 58px; height: 58px; } +.room-lobby-chat-peers.chat-panel .chat-msg { padding: 0.6rem 1rem; font-size: 1.05rem; } +.room-lobby-chat-peers.chat-panel .chat-header-bar { + margin: 0 -2rem 0 -2rem; + padding-left: 2rem; + padding-right: 2rem; + justify-content: space-between; + align-items: center; + min-height: 72px; + height: 72px; +} +.room-lobby-chat-peers.chat-panel #chat-messages { + flex: 1; + min-height: 0; + height: auto; +} +.room-lobby-chat .chat-header-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0; + min-height: 48px; + height: 48px; + background: url(../img/chat-line.png) bottom repeat-x; + background-size: 100% 4px; + background-color: transparent; +} +.room-lobby-chat-peers .chat-header-title.chat-header-title-img { + display: inline-flex; + align-items: center; + line-height: 0; +} +.room-lobby-chat-peers .chat-header-title.chat-header-title-img img { + width: 60px; + height: 51px; + object-fit: contain; + display: block; +} +.room-lobby-chat-ai .chat-header-title.chat-header-title-img { + display: flex; + align-items: flex-end; + gap: 0.5rem; + line-height: 0; + min-width: 0; + overflow: visible; +} +.room-lobby-chat-ai .chat-header-title.chat-header-title-img .ai-chat-title-label { + height: 64px; + width: auto; + max-width: none; + object-fit: contain; + display: block; + flex-shrink: 0; + margin-bottom: -2px; +} +/* รูปหัวแชท AI — ยกขึ้นเหนือเส้น */ +.room-lobby-chat-ai .chat-header-title.chat-header-title-img img { + width: 86px; + height: 92px; + object-fit: cover; + display: block; + flex-shrink: 0; + margin-top: -27px; + margin-left: -6px; + position: relative; + z-index: 1; +} +.room-lobby-chat .chat-header-title { + font-size: 1rem; + font-weight: 600; + color: #a0aec0; +} +.room-lobby-chat .chat-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: none; + border-radius: 8px; + background: transparent; + cursor: pointer; + margin-left: auto; +} +.room-lobby-chat-peers .chat-header-bar .chat-close-btn { margin-left: 0; } +.room-lobby-chat .chat-close-btn img { width: 34px; height: 34px; object-fit: contain; } +.room-lobby-chat .chat-close-btn:hover { opacity: 0.9; } +.room-lobby-chat.chat-panel-collapsed #chat-messages, +.room-lobby-chat.chat-panel-collapsed .chat-form-bar { display: none !important; } +.room-lobby-chat-peers.chat-panel-collapsed .chat-header-title { display: none !important; } +.room-lobby-chat-peers.chat-panel-collapsed .chat-header-bar { + justify-content: center; + padding: 0; + background: none !important; + border: none; +} +.room-lobby-chat-peers.chat-panel-collapsed.chat-panel { + width: 80px; + height: 80px; + min-height: 0; + padding: 0; + right: 1.25rem; + bottom: 6.25rem; + display: flex; + align-items: center; + justify-content: center; + background: none !important; + background-color: transparent !important; + border: none; + box-shadow: none; +} +.room-lobby-chat-peers.chat-panel-collapsed .chat-close-btn { + width: 80px; + height: 80px; + overflow: visible; +} +.room-lobby-chat-peers.chat-panel-collapsed .chat-close-btn img { + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; + display: block; +} +.room-lobby-chat #chat-messages, +.room-lobby-chat #ai-chat-messages { + height: 100px; + overflow-y: auto; + padding: 0.5rem; + font-size: 0.85rem; + border: none; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.35rem; + background: none; + background-color: transparent; + box-shadow: none; +} +.room-lobby-chat .chat-messages-bg { border-radius: 0; } +.room-lobby-chat #chat-form.chat-form-bar, +.room-lobby-chat #ai-chat-form.chat-form-bar { + display: flex; + gap: 0.4rem; + align-items: center; + padding: 0.4rem 0; + background: none; + background-color: transparent; + border-top: none; + flex-shrink: 0; +} +.room-lobby-chat-peers .chat-form-bar { + padding-left: 0; + padding-right: 0; +} +.room-lobby-chat #chat-input, +.room-lobby-chat #ai-chat-input { + flex: 1; + min-width: 0; + padding: 0.45rem 0.6rem; + border-radius: 10px; + border: 1px solid rgba(0, 212, 255, 0.25); + background: rgba(30, 35, 50, 0.8); + color: #c0caf5; + font-size: 0.9rem; + box-sizing: border-box; + margin-left: 9px; +} +.room-lobby-chat .chat-send-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + padding: 0; + border: none; + border-radius: 10px; + background: transparent; + cursor: pointer; +} +.room-lobby-chat .chat-send-btn img { width: 45px; height: 45px; object-fit: contain; } +.room-lobby-chat .chat-send-btn:hover { opacity: 0.9; } +.room-lobby-chat .chat-msg { + padding: 0.4rem 0.75rem; + border-radius: 12px; + max-width: 85%; + background-size: 100% 100%; + background-repeat: no-repeat; + background-position: center; +} +.room-lobby-chat .chat-msg-mine { + align-self: flex-end; + text-align: right; + background-image: url(../img/chat-bubble-me.png); + background-color: rgba(122, 162, 247, 0.9); + color: #1a1b26; +} +.room-lobby-chat .chat-msg-other { + align-self: flex-start; + text-align: left; + background-image: url(../img/chat-bubble-friend.png); + background-color: rgba(65, 72, 104, 0.9); + color: #c0caf5; +} +.room-lobby-chat .chat-msg-ai { + align-self: flex-start; + text-align: left; + background-image: url(../img/chat-bubble-friend.png); + background-color: rgba(158, 206, 106, 0.9); + color: #1a1b26; +} +.room-lobby-chat .chat-msg-system { + align-self: center; + text-align: center; + max-width: 95%; + background-image: none; + background-color: rgba(26, 27, 38, 0.72); + color: #9aa5ce; + font-size: 0.88rem; + font-style: italic; + padding: 0.35rem 0.6rem; +} + +/* AI chat — loading แบบ Messenger (จุดกระโดด 3 จุด), อยู่ติดข้อความล่าสุดในลิสต์ */ +.room-lobby-chat .ai-chat-typing { + display: flex; + align-items: center; + gap: 4px; + padding: 12px 14px; + min-height: 24px; + flex-shrink: 0; + background-image: url(../img/chat-bubble-friend.png); + background-color: rgba(158, 206, 106, 0.9); + background-size: 100% 100%; + background-repeat: no-repeat; + background-position: center; +} +.room-lobby-chat .ai-typing-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #1a1b26; + animation: ai-typing-bounce 1.4s ease-in-out infinite both; +} +.room-lobby-chat .ai-typing-dot:nth-child(1) { animation-delay: 0s; } +.room-lobby-chat .ai-typing-dot:nth-child(2) { animation-delay: 0.2s; } +.room-lobby-chat .ai-typing-dot:nth-child(3) { animation-delay: 0.4s; } +@keyframes ai-typing-bounce { + 0%, 60%, 100% { transform: translateY(0); } + 30% { transform: translateY(-6px); } +} + +/* AI chat panel — match Main-Lobby lobby-ai-chat-panel */ +.room-lobby-chat-ai.chat-panel { + position: fixed; + left: 8px; + right: auto; + bottom: clamp(0.5rem, 2vw, 1rem); + width: 454px; + height: 526px; + max-width: calc(100vw - 2.5rem); + z-index: 51; + margin: 0; + display: flex; + flex-flow: column; + box-sizing: border-box; + padding: 2.6rem; + overflow: visible; + background: url(../img/chat-msg-bg-alpha.png) center/100% 100% no-repeat; + background-color: transparent; + border: none; + box-shadow: none; + border-radius: 12px; + pointer-events: none; +} +.room-lobby-chat-ai.chat-panel .chat-header-bar, +.room-lobby-chat-ai.chat-panel #ai-chat-messages, +.room-lobby-chat-ai.chat-panel #ai-chat-form, +.room-lobby-chat-ai.chat-panel .ai-chat-admin-link { + pointer-events: auto; +} +.room-lobby-chat-ai.chat-panel .chat-header-bar { + margin: 0 -2rem 0 -2rem; + padding: 0 1rem; + justify-content: space-between; + align-items: center; + min-height: 50px; + height: 50px; + overflow: visible; + background: url(../img/AI-chat-line.png) bottom repeat-x; + background-size: 100% 3px; + background-color: transparent; +} +.room-lobby-chat-ai.chat-panel .chat-header-title.chat-header-title-img img { + width: 100px; + height: 116px; + margin-top: -39px; + margin-left: 16px; + object-fit: cover; + position: relative; + z-index: 1; +} +.room-lobby-chat-ai.chat-panel .chat-header-title.chat-header-title-img .ai-chat-title-label { + height: 80px; + width: auto; + max-width: none; + margin-bottom: -4px; + margin-left: -5px; + transform: translateX(-6px); +} +.room-lobby-chat-ai.chat-panel .chat-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 60px; + padding: 0; + border: none; + border-radius: 8px; + background: transparent; + cursor: pointer; + margin-left: auto; +} +.room-lobby-chat-ai.chat-panel .chat-close-btn img { + width: 60px; + height: 60px; + object-fit: contain; +} +.room-lobby-chat-ai.chat-panel .chat-close-btn:hover { + opacity: 0.9; +} +.room-lobby-chat-ai.chat-panel #ai-chat-messages { + flex: 1; + min-height: 0; + height: auto; + overflow-y: auto; + padding: 0.2rem 0.35rem 0.25rem 0; + font-size: 1.05rem; + gap: 0.5rem; + background: none; + border: none; + scrollbar-width: thin; + scrollbar-color: rgba(244, 252, 255, 0.98) rgba(53, 110, 167, 0.28); +} +.room-lobby-chat-ai.chat-panel #ai-chat-messages::-webkit-scrollbar { + width: 1rem; + scrollbar-width: auto; +} +.room-lobby-chat-ai.chat-panel #ai-chat-messages::-webkit-scrollbar-track { + border-radius: 999px; + background: linear-gradient(180deg, rgba(38, 77, 125, 0.45) 0%, rgba(32, 66, 106, 0.38) 100%); + box-shadow: inset 0 0 0 1px rgba(107, 193, 255, 0.25); +} +.room-lobby-chat-ai.chat-panel #ai-chat-messages::-webkit-scrollbar-thumb { + border-radius: 999px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(221, 244, 255, 0.96) 100%); + box-shadow: + 0 0 10px rgba(169, 239, 255, 0.55), + inset 0 0 0 1px rgba(180, 235, 255, 0.9); +} +.room-lobby-chat-ai.chat-panel .chat-form-bar { + display: flex; + gap: 0; + align-items: center; + padding: 0.6rem 0; + flex-shrink: 0; + background: none; + border: none; + width: calc(100% + 3rem); + margin-left: 0; + margin-right: 0; +} +.room-lobby-chat-ai.chat-panel #ai-chat-input { + flex: 0.85; + min-width: 0; + min-height: 50px; + padding: 0.6rem 0.85rem; + border-radius: 8px; + border: 1px solid rgba(0, 212, 255, 0.32); + background: rgba(30, 35, 50, 0.55); + color: #c0caf5; + font-size: 1.05rem; + margin: 0; +} +.room-lobby-chat-ai.chat-panel .chat-send-btn { + width: 73px; + height: 73px; + margin-left: 0; + margin-right: -0.7rem; +} +.room-lobby-chat-ai.chat-panel .chat-send-btn img { + width: 102px; + height: 98px; + object-fit: contain; +} +.room-lobby-chat-ai.chat-panel .chat-send-btn:focus, +.room-lobby-chat-ai.chat-panel .chat-send-btn:focus-visible { + outline: none !important; + box-shadow: none !important; + border-color: transparent !important; +} +.room-lobby-chat-ai.chat-panel .chat-msg { + padding: 0.58rem 0.92rem; + font-size: 1.05rem; + line-height: 1.33; + border: none; + box-shadow: none; +} +.room-lobby-chat-ai.chat-panel .chat-msg-mine { + background-color: rgba(122, 162, 247, 0.2); + color: #f787e4; +} +.room-lobby-chat-ai.chat-panel .chat-msg-ai { + background-color: rgba(158, 206, 106, 0.2); + color: #f2f8ff; +} +.room-lobby-chat-ai.chat-panel .ai-chat-typing { + padding: 14px 18px; + min-height: 28px; + background-color: rgba(158, 206, 106, 0.32); +} +.room-lobby-chat-ai.chat-panel .ai-typing-dot { + width: 8px; + height: 8px; + background: #f2f8ff; +} +.room-lobby-chat-ai.chat-panel .ai-chat-admin-link { + margin: 0.35rem 0 0 0; + font-size: 0.75rem; + flex-shrink: 0; +} +.room-lobby-chat-ai.chat-panel, +.room-lobby-chat-ai.chat-panel #ai-chat-messages, +.room-lobby-chat-ai.chat-panel #ai-chat-input, +.room-lobby-chat-ai.chat-panel .chat-msg, +.room-lobby-chat-ai.chat-panel .ai-chat-typing, +.room-lobby-chat-ai.chat-panel .chat-send-btn, +.room-lobby-chat-ai.chat-panel .ai-chat-admin-link, +.room-lobby-chat-ai.chat-panel .ai-chat-admin-link a { + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important; +} +.room-lobby-chat-ai.chat-panel #ai-chat-messages, +.room-lobby-chat-ai.chat-panel #ai-chat-messages * { + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important; +} +.room-lobby-chat-ai.chat-panel #ai-chat-input::placeholder { + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important; +} + +/* Host console: force NotoSansThai for highlighted text areas */ +.room-lobby-host-console-summary, +.room-lobby-host-console-member-item, +.room-lobby-host-console-member-name, +.room-lobby-host-console-max-value { + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important; +} + +/* AI chat collapsed — match Main-Lobby lobby-btn-ai-chat style */ +.room-lobby-chat-ai.chat-panel-collapsed #ai-chat-messages, +.room-lobby-chat-ai.chat-panel-collapsed .chat-form-bar, +.room-lobby-chat-ai.chat-panel-collapsed .ai-chat-admin-link { display: none !important; } +.room-lobby-chat-ai.chat-panel-collapsed .chat-header-title { display: none !important; } +.room-lobby-chat-ai.chat-panel-collapsed .chat-header-bar { + justify-content: center; + padding: 0; + background: none !important; + border: none; + min-height: 0; + height: auto; +} +.room-lobby-chat-ai.chat-panel-collapsed.chat-panel { + width: 213px; + max-width: 213px; + height: 201px; + min-height: 201px; + padding: 0; + left: max(1.25rem, env(safe-area-inset-left)); + bottom: clamp(0.5rem, 2vw, 1rem); + display: flex; + align-items: center; + justify-content: center; + background: none !important; + background-color: transparent !important; + border: none; + box-shadow: none; +} +.room-lobby-chat-ai.chat-panel-collapsed .chat-close-btn { + width: 213px; + height: 201px; + min-width: 213px; + min-height: 201px; + max-width: 213px; + max-height: 201px; + aspect-ratio: 213 / 201; + overflow: visible; + border-radius: 0; + margin-left: 0; +} +.room-lobby-chat-ai.chat-panel-collapsed .chat-close-btn img { + width: 213px; + height: 201px; + min-width: 213px; + min-height: 201px; + max-width: 213px; + max-height: 201px; + aspect-ratio: 213 / 201; + object-fit: contain; + object-position: center; + display: block; +} + +.room-lobby-exit-fixed { + position: static; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-start; + gap: 10px; + width: max-content; + max-width: calc(100vw - 24px); + box-sizing: border-box; +} +.room-lobby-exit-fixed .btn-exit-icon { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + margin: 0; + width: clamp(58px, 9vw, 72px); + height: clamp(58px, 9vw, 72px); + min-width: clamp(58px, 9vw, 72px); + padding: 0; + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; + cursor: pointer; +} +.room-lobby-exit-fixed .btn-exit-icon:hover { + transform: translateY(-1px); + filter: brightness(1.08); +} +.room-lobby-exit-fixed .btn-exit-icon img { + width: 100%; + height: auto; + object-fit: contain; + display: block; +} + +.room-lobby-ready-fixed { + position: fixed; + bottom: max(2rem, env(safe-area-inset-bottom)); + left: 50%; + transform: translateX(-50%); + z-index: 120; + pointer-events: none; +} +.room-lobby-ready-fixed .ready-label, +.room-lobby-ready-fixed .ready-check-input { + pointer-events: auto; +} +.room-lobby-ready-fixed .ready-label { + position: relative; + display: inline-block; + padding: 0; + margin: 0; + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; + cursor: pointer; + transition: transform 0.15s ease, filter 0.15s ease; + line-height: 0; + filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.42)); +} +.room-lobby-ready-fixed .ready-label:hover { + transform: scale(1.04); + filter: brightness(1.08); +} +.room-lobby-ready-fixed .ready-label:active { + transform: scale(0.98); +} +.room-lobby-ready-fixed .ready-check-input { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + opacity: 0; + cursor: pointer; + z-index: 2; +} +.room-lobby-ready-fixed .ready-label-img { + display: block; + height: clamp(44px, 8vh, 72px); + width: auto; + max-width: min(240px, 78vw); + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} +.room-lobby-ready-fixed .ready-label--active .ready-label-img { + filter: drop-shadow(0 0 10px rgba(158, 206, 106, 0.45)); +} +body.room-lobby-page #room-players-hud { + top: max(10px, env(safe-area-inset-top)) !important; + min-width: 0; + border-radius: 0; + border: none; + background: transparent; + box-shadow: none; + backdrop-filter: none; + color: #e3f6ff !important; + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif !important; + text-transform: none; + letter-spacing: 0.08em; + text-shadow: + 0 2px 0 rgba(0, 0, 0, 0.8), + 0 0 12px rgba(0, 0, 0, 0.55); + text-align: center; +} + +/* มุมขวาล่าง — ไมค์ทุกห้อง + ปุ่ม LobbyB (แว่น/ถ้วย) */ +.room-lobby-br-fixed { + --lobby-br-btn: 80px; + position: fixed; + bottom: max(0.95rem, env(safe-area-inset-bottom)); + right: max(1.25rem, env(safe-area-inset-right)); + z-index: 400; + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 10px; + pointer-events: none; +} +.room-lobby-br-fixed .lobby-b-extra-row, +.room-lobby-br-fixed .btn-voice-icon { + pointer-events: auto; +} +.lobby-b-extra-row { + display: flex; + flex-direction: row; + align-items: flex-end; + gap: 10px; +} +.lobby-b-extra-row.is-hidden { + display: none !important; +} +/* โหมดเกม (ควิซ) ใน room-lobby — ไม่โชว์แว่น/ถ้วย LobbyB */ +body.room-lobby--quiz-active .lobby-b-extra-row { + display: none !important; + visibility: hidden !important; + pointer-events: none !important; +} +/* ปุ่มมุมขวาล่าง — ขนาดเดียวกัน (กัน UA border / box model ต่างกัน) */ +.room-lobby-br-fixed .lobby-b-sq-btn, +.room-lobby-br-fixed .btn-voice-icon { + 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; +} +/* สไตล์เดียวกับปุ่มไมค์ — ไม่มีกรอบ cyan / bg-item */ +.lobby-b-sq-btn { + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.15s ease, filter 0.15s ease; +} +.lobby-b-sq-btn:hover { + transform: scale(1.04); + filter: brightness(1.08); +} +.lobby-b-sq-btn:active { transform: scale(0.98); } +.room-lobby-br-fixed .lobby-b-sq-btn img, +.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; +} +.room-lobby-br-fixed .btn-voice-icon { + display: flex; + align-items: center; + justify-content: center; + background: transparent; +} + +/* Desktop tuning: keep controls visibly large and readable */ +@media (min-width: 1024px) { + .room-lobby-chat-peers.chat-panel { + right: 0.9rem; + bottom: 11rem; + width: 454px; + height: 414px; + padding: 1.7rem 1.45rem 1.15rem; + border-radius: 0; + background: url(../img/chat-bg.png) center/100% 100% no-repeat; + border: none; + box-shadow: none; + backdrop-filter: none; + } + .room-lobby-chat-peers.chat-panel .chat-header-bar { + margin: 0; + padding: 0 0.15rem; + min-height: 58px; + height: 58px; + background: url(../img/chat-line.png) left bottom / 375px 4px no-repeat; + box-shadow: none; + } + .room-lobby-chat-peers.chat-panel .chat-header-title.chat-header-title-img img { + display: block; + width: 90px; + height: 77px; + object-fit: contain; + } + .room-lobby-chat-peers.chat-panel .chat-header-title.chat-header-title-img::before { + content: none; + display: none; + } + .room-lobby-chat-peers.chat-panel .chat-close-btn { + width: 58px; + height: 58px; + min-width: 58px; + min-height: 58px; + max-width: 58px; + max-height: 58px; + margin-left: auto; + } + .room-lobby-chat-peers.chat-panel .chat-close-btn img { + width: 58px; + height: 58px; + object-fit: contain; + } + .room-lobby-chat-peers.chat-panel #chat-messages { + width: calc(100% - 16px); + margin-right: 16px; + padding: 0.35rem 0.7rem 0.25rem 0.15rem; + font-size: 1rem; + line-height: 1.42; + gap: 0.42rem; + margin-top: 0.15rem; + margin-bottom: 0.2rem; + scrollbar-width: auto; + scrollbar-color: rgba(250, 253, 255, 0.98) rgba(12, 26, 47, 0.45); + box-sizing: border-box; + } + .room-lobby-chat-peers.chat-panel #chat-messages::-webkit-scrollbar { + width: 15px; + } + .room-lobby-chat-peers.chat-panel #chat-messages::-webkit-scrollbar-track { + border-radius: 999px; + background: rgba(20, 44, 72, 0.45); + border: 1px solid rgba(124, 221, 255, 0.3); + } + .room-lobby-chat-peers.chat-panel #chat-messages::-webkit-scrollbar-thumb { + border-radius: 999px; + background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(241, 249, 255, 0.98) 100%); + box-shadow: 0 0 8px rgba(182, 239, 255, 0.45); + } + .room-lobby-chat-peers.chat-panel .chat-msg { + max-width: 93%; + padding: 0.22rem 0.35rem; + font-size: 0.98rem; + border-radius: 10px; + background-image: none; + background-color: transparent; + box-shadow: none; + } + .room-lobby-chat-peers.chat-panel .chat-msg-other { + color: #d5bb63; + font-weight: 500; + } + .room-lobby-chat-peers.chat-panel .chat-msg-mine { + color: #cf74d1; + background-color: rgba(39, 67, 112, 0.82); + border: 1px solid rgba(116, 206, 255, 0.2); + align-self: flex-end; + text-align: left; + padding: 0.32rem 0.62rem; + } + .room-lobby-chat-peers.chat-panel .chat-form-bar { + padding: 0.24rem 0 0; + gap: 0; + align-items: center; + justify-content: space-between; + } + .room-lobby-chat-peers.chat-panel #chat-input { + margin-left: 17px; + width: 308px; + min-width: 308px; + max-width: 308px; + height: 61px; + min-height: 61px; + max-height: 61px; + padding: 0 0.95rem; + font-size: 0.95rem; + border-radius: 0; + border: none; + background: url(../img/chatbox.png) center/100% 100% no-repeat; + color: #e8f7ff; + } + .room-lobby-chat-peers.chat-panel #chat-input::placeholder { + color: rgba(195, 211, 236, 0.66); + } + .room-lobby-chat-peers.chat-panel .chat-send-btn { + width: 102px; + height: 98px; + min-width: 102px; + min-height: 98px; + max-width: 102px; + max-height: 98px; + margin-left: -26px; + margin-right: 2px; + } + .room-lobby-chat-peers.chat-panel .chat-send-btn img { + width: 102px; + height: 98px; + } + .room-lobby-top-left-cluster { + top: max(18px, env(safe-area-inset-top)); + left: max(0px, env(safe-area-inset-left)); + gap: 0; + } + .room-lobby-exit-fixed { + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 0.45rem; + margin-top: -1.4rem; + width: auto; + max-width: none; + } + .room-lobby-top-right-cluster { + top: max(16px, env(safe-area-inset-top)); + right: max(16px, env(safe-area-inset-right)); + } + .room-lobby-room-id-pill { + width: fit-content; + min-width: 0; + height: 180px; + min-height: 180px; + max-height: 180px; + max-width: none; + padding: 0 15px; + border-style: solid; + border-width: 0 60px; + border-radius: 0; + background: transparent; + border-image-source: url("../img/Room-id-BG.png"); + border-image-slice: 0 60 0 60 fill; + border-image-repeat: stretch; + box-shadow: none; + gap: 0.7rem; + align-items: center; + white-space: nowrap; + } + .room-lobby-room-id-label { + font-size: 2.05rem; + font-weight: 600; + line-height: 1; + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif; + text-transform: none; + color: #e8f7ff; + text-shadow: + 0 0 6px rgba(110, 226, 255, 0.8), + 0 0 14px rgba(44, 198, 255, 0.5); + } + #room-id-value { + font-size: 2.35rem; + line-height: 1; + font-family: 'NotoSansThai', 'Kanit', system-ui, sans-serif; + text-transform: none; + font-weight: 600; + color: #ecfbff; + text-shadow: + 0 0 7px rgba(118, 230, 255, 0.9), + 0 0 16px rgba(52, 203, 255, 0.55); + } + .room-lobby-room-id-lock { + width: 60px; + height: 65px; + min-width: 60px; + min-height: 65px; + max-width: 60px; + max-height: 65px; + aspect-ratio: 60 / 65; + font-size: 0; + color: transparent; + background: url("../img/Room-id-private.png") center / 100% 100% no-repeat; + filter: none; + } + .room-lobby-profile-frame { + width: 159px; + height: 157px; + min-width: 159px; + min-height: 157px; + max-width: 159px; + max-height: 157px; + aspect-ratio: 159 / 157; + } + .room-lobby-exit-fixed .btn-exit-icon { + width: 121px; + height: 119px; + min-width: 121px; + min-height: 119px; + max-width: 121px; + max-height: 119px; + aspect-ratio: 121 / 119; + margin-left: 13px; + margin-top: -15px; + margin-bottom: -15px; + } + body.room-lobby-page #room-players-hud { + min-width: 0; + font-size: clamp(1.35rem, 1.95vw, 1.9rem); + padding: 0.1rem 0.4rem; + } + .room-lobby-ready-fixed .ready-label-img { + width: 320px; + height: 126px; + min-width: 320px; + min-height: 126px; + max-width: 320px; + max-height: 126px; + aspect-ratio: 320 / 126; + } + .room-lobby-br-fixed { + --lobby-br-btn: 137px; + 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; + justify-content: flex-end; + gap: 1rem; + } + .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; + } + .room-lobby-br-fixed .btn-voice-icon { + order: 2; + position: static !important; + margin: 0 !important; + transform: none !important; + } + .room-lobby-br-fixed .lobby-b-extra-row { + z-index: auto; + } + .room-lobby-chat-peers.chat-panel-collapsed.chat-panel { + width: 122px; + height: 120px; + bottom: 12rem; + } + .room-lobby-chat-peers.chat-panel-collapsed .chat-close-btn { + width: 122px; + height: 120px; + min-width: 122px; + min-height: 120px; + max-width: 122px; + max-height: 120px; + aspect-ratio: 122 / 120; + margin-right: -20px; + } + .room-lobby-chat-peers.chat-panel-collapsed .chat-close-btn img { + width: 122px; + height: 120px; + aspect-ratio: 122 / 120; + } +} + +/* —— แฟ้มหลักฐาน LobbyB (รูปจาก /Main-Lobby/IMAGE/See evidence) —— */ +.lobby-evidence-overlay { + position: fixed; + inset: 0; + z-index: 10047; + 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)); + box-sizing: border-box; +} +.lobby-evidence-overlay.is-hidden { + display: none !important; +} +.lobby-evidence-backdrop { + position: absolute; + inset: 0; + background: rgba(4, 8, 22, 0.78); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + cursor: pointer; +} +.lobby-evidence-dialog { + position: relative; + z-index: 1; + width: min(96vw, 900px); + max-height: min(92vh, 920px); + pointer-events: none; +} +.lobby-evidence-dialog * { + pointer-events: auto; +} +.lobby-evidence-frame { + position: relative; + width: 100%; + aspect-ratio: 1684 / 963; + filter: drop-shadow(0 0 24px rgba(0, 255, 255, 0.12)) drop-shadow(0 0 40px rgba(255, 0, 255, 0.08)); +} +.lobby-evidence-bg { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: center top; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} +.lobby-evidence-close { + position: absolute; + top: 5.5%; + right: 4%; + width: 8%; + max-width: 56px; + aspect-ratio: 1; + padding: 0; + margin: 0; + border: none; + border-radius: 10px; + background: transparent; + color: rgba(0, 255, 255, 0.95); + font-size: clamp(1.25rem, 4vw, 2rem); + line-height: 1; + cursor: pointer; + text-shadow: 0 0 12px rgba(0, 255, 255, 0.9); + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.15s ease, filter 0.15s ease; +} +.lobby-evidence-close:hover { + transform: scale(1.08); + filter: brightness(1.15); +} +.lobby-evidence-tabs-layer { + position: absolute; + left: 7%; + right: 7%; + top: 15.5%; + height: 13.5%; + min-height: 36px; +} +.lobby-evidence-tabs-img { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: left center; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} +.lobby-evidence-tab-hits { + position: absolute; + inset: 0; + display: flex; + flex-direction: row; + align-items: stretch; + padding-left: 1.5%; + padding-right: 0; + box-sizing: border-box; +} +.lobby-evidence-tab-hit { + flex: 0 0 13.2%; + min-width: 0; + height: 100%; + padding: 0; + margin: 0; + border: none; + background: transparent; + cursor: pointer; + border-radius: 8px; +} +.lobby-evidence-tab-hit:focus-visible { + outline: 2px solid rgba(0, 255, 255, 0.9); + outline-offset: 2px; +} +.lobby-evidence-tab-hit-spacer { + flex: 1 1 auto; + pointer-events: none; +} +.lobby-evidence-cards-root { + position: absolute; + left: 5%; + right: 5%; + top: 31%; + bottom: 7%; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: clamp(6px, 1.2vw, 14px); + align-items: stretch; + justify-content: center; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + padding-bottom: 4px; + box-sizing: border-box; +} +.lobby-evidence-card { + flex: 1 1 0; + min-width: min(200px, 28vw); + max-width: 320px; + display: flex; + flex-direction: column; + border-radius: 10px; + padding: clamp(6px, 1vw, 10px); + background: rgba(15, 20, 36, 0.92); + box-sizing: border-box; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 8px 24px rgba(0, 0, 0, 0.35); +} +.lobby-evidence-card--common { + box-shadow: 0 0 0 2px rgba(180, 190, 200, 0.65), 0 0 16px rgba(200, 210, 220, 0.2), 0 8px 24px rgba(0, 0, 0, 0.35); +} +.lobby-evidence-card--rare { + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.85), 0 0 20px rgba(59, 130, 246, 0.35), 0 8px 24px rgba(0, 0, 0, 0.35); +} +.lobby-evidence-card--legendary { + box-shadow: 0 0 0 2px rgba(234, 179, 8, 0.9), 0 0 22px rgba(250, 204, 21, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35); +} +.lobby-evidence-card-head { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 8px; + margin-bottom: 6px; +} +.lobby-evidence-card-icon { + flex-shrink: 0; + width: 28px; + height: 28px; + border-radius: 6px; + background: linear-gradient(135deg, rgba(0, 255, 255, 0.25), rgba(255, 0, 255, 0.15)); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 1; +} +.lobby-evidence-card-titles { + flex: 1; + min-width: 0; +} +.lobby-evidence-card-title-th { + font-family: 'Sarabun', 'Kanit', system-ui, sans-serif; + font-size: clamp(0.68rem, 1.35vw, 0.82rem); + font-weight: 700; + color: #e8ecff; + line-height: 1.25; + margin: 0; +} +.lobby-evidence-card-title-en { + font-family: system-ui, sans-serif; + font-size: clamp(0.58rem, 1.05vw, 0.68rem); + color: rgba(180, 190, 220, 0.85); + margin: 2px 0 0; + line-height: 1.2; +} +.lobby-evidence-card-art { + flex: 1 1 auto; + min-height: clamp(72px, 14vh, 140px); + max-height: min(22vh, 200px); + border-radius: 8px; + background: linear-gradient(160deg, rgba(30, 40, 70, 0.95), rgba(12, 16, 32, 0.98)); + border: 1px solid rgba(0, 255, 255, 0.12); + margin-bottom: 6px; + display: flex; + align-items: center; + justify-content: center; + color: rgba(0, 255, 255, 0.35); + font-size: clamp(1.5rem, 4vw, 2.25rem); + user-select: none; +} +.lobby-evidence-card-body { + font-family: 'Sarabun', 'Kanit', system-ui, sans-serif; + font-size: clamp(0.6rem, 1.15vw, 0.72rem); + color: rgba(210, 218, 240, 0.92); + line-height: 1.4; + margin: 0 0 8px; + flex-shrink: 0; + max-height: 4.5em; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; +} +.lobby-evidence-card-foot { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: auto; + flex-shrink: 0; +} +.lobby-evidence-link { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + min-width: 0; +} +.lobby-evidence-avatar { + width: 26px; + height: 26px; + border-radius: 50%; + background: linear-gradient(145deg, #7aa2f7, #565f89); + color: #1a1b26; + font-size: 11px; + font-weight: 800; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 0 0 2px rgba(0, 255, 255, 0.25); +} +.lobby-evidence-link-name { + font-size: clamp(0.62rem, 1.1vw, 0.74rem); + font-weight: 600; + color: #c0caf5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.lobby-evidence-foot-meta { + text-align: right; + flex-shrink: 0; +} +.lobby-evidence-rarity { + display: block; + font-size: clamp(0.55rem, 0.95vw, 0.65rem); + color: rgba(170, 180, 210, 0.9); + margin-bottom: 2px; +} +.lobby-evidence-stars { + font-size: clamp(0.65rem, 1.2vw, 0.78rem); + letter-spacing: 1px; + line-height: 1; +} +.lobby-evidence-card--common .lobby-evidence-stars { color: #d4d4d8; text-shadow: 0 0 8px rgba(200, 200, 210, 0.4); } +.lobby-evidence-card--rare .lobby-evidence-stars { color: #60a5fa; text-shadow: 0 0 10px rgba(59, 130, 246, 0.5); } +.lobby-evidence-card--legendary .lobby-evidence-stars { color: #fcd34d; text-shadow: 0 0 10px rgba(234, 179, 8, 0.55); } +@media (max-width: 720px) { + .lobby-evidence-cards-root { + flex-wrap: wrap; + overflow-y: auto; + align-content: flex-start; + } + .lobby-evidence-card { + flex: 1 1 100%; + max-width: none; + min-width: 0; + } +} + +/* —— กระดานอันดับ LobbyB (เหรียญ leaderboard-1/2/3 จาก Main-Lobby) —— */ +.lobby-rank-overlay { + position: fixed; + inset: 0; + z-index: 10048; + 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)); + box-sizing: border-box; +} +.lobby-rank-overlay.is-hidden { + display: none !important; +} +.lobby-rank-backdrop { + position: absolute; + inset: 0; + background: rgba(2, 4, 14, 0.82); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + cursor: pointer; +} +.lobby-rank-dialog { + position: relative; + z-index: 1; + width: min(96vw, 520px); + max-height: min(90vh, 720px); + pointer-events: none; +} +.lobby-rank-dialog * { + pointer-events: auto; +} +.lobby-rank-panel { + background: linear-gradient(165deg, rgba(28, 36, 58, 0.96), rgba(12, 16, 32, 0.98)); + border-radius: 16px; + border: 2px solid rgba(0, 255, 255, 0.45); + box-shadow: + 0 0 0 1px rgba(255, 0, 200, 0.25), + 0 0 32px rgba(0, 255, 255, 0.12), + 0 0 48px rgba(255, 0, 180, 0.08), + inset 0 1px 0 rgba(255, 255, 255, 0.06); + padding: clamp(10px, 2.5vw, 16px); + display: flex; + flex-direction: column; + min-height: 0; + font-family: 'Kanit', 'Sarabun', system-ui, sans-serif; +} +.lobby-rank-head { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + position: relative; + padding: 6px 44px 12px 12px; + border-bottom: 1px solid rgba(0, 255, 255, 0.28); + margin-bottom: 8px; +} +.lobby-rank-case-title { + margin: 0; + font-size: clamp(0.95rem, 2.8vw, 1.15rem); + font-weight: 700; + color: #e8f4ff; + text-align: center; + text-shadow: 0 0 12px rgba(0, 255, 255, 0.35); + line-height: 1.25; +} +.lobby-rank-close { + position: absolute; + top: 2px; + right: 4px; + width: 44px; + height: 44px; + padding: 0; + border: none; + border-radius: 10px; + background: transparent; + color: rgba(0, 255, 255, 0.95); + font-size: 1.75rem; + line-height: 1; + cursor: pointer; + text-shadow: 0 0 14px rgba(0, 255, 255, 0.85); + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.15s ease, filter 0.15s ease; +} +.lobby-rank-close:hover { + transform: scale(1.06); + filter: brightness(1.12); +} +.lobby-rank-podium { + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: center; + gap: clamp(6px, 2vw, 14px); + padding: 8px 4px 14px; + flex-shrink: 0; +} +.lobby-rank-ped { + flex: 1 1 0; + min-width: 0; + max-width: 150px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 10px 6px 12px; + border-radius: 14px; + background: rgba(0, 0, 0, 0.28); + border: 1px solid rgba(0, 255, 255, 0.22); + box-sizing: border-box; +} +.lobby-rank-ped--2 { + order: 1; + min-height: 148px; +} +.lobby-rank-ped--1 { + order: 2; + min-height: 188px; + border-color: rgba(255, 215, 0, 0.45); + box-shadow: 0 0 22px rgba(255, 200, 0, 0.12); +} +.lobby-rank-ped--3 { + order: 3; + min-height: 132px; +} +.lobby-rank-crown { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 4px; + margin-bottom: 4px; + font-size: 1.1rem; + line-height: 1; + filter: drop-shadow(0 0 6px rgba(255, 215, 0, 0.6)); +} +.lobby-rank-crown-n { + font-size: 0.85rem; + font-weight: 800; + color: #ffe066; +} +.lobby-rank-medal-wrap { + height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 6px; +} +.lobby-rank-medal-img { + width: auto; + height: 34px; + object-fit: contain; + display: block; +} +.lobby-rank-ped-avatar { + width: 48px; + height: 48px; + border-radius: 50%; + background: linear-gradient(145deg, #7aa2f7, #565f89); + color: #1a1b26; + font-size: 1.1rem; + font-weight: 800; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 6px; + box-shadow: 0 0 0 2px rgba(0, 255, 255, 0.3); + flex-shrink: 0; +} +.lobby-rank-ped--1 .lobby-rank-ped-avatar { + width: 56px; + height: 56px; + font-size: 1.25rem; +} +.lobby-rank-ped-name { + font-size: clamp(0.68rem, 2vw, 0.8rem); + font-weight: 600; + color: #e8ecff; + line-height: 1.2; + word-break: break-word; + max-width: 100%; +} +.lobby-rank-ped-score { + margin-top: 4px; + font-size: clamp(0.75rem, 2.2vw, 0.9rem); + font-weight: 700; + color: #fff; + text-shadow: 0 0 8px rgba(255, 255, 255, 0.2); +} +.lobby-rank-table-wrap { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + margin: 0 -4px; + padding: 0 4px; + scrollbar-width: thin; +} +.lobby-rank-table { + width: 100%; + border-collapse: collapse; + font-size: clamp(0.65rem, 1.9vw, 0.78rem); +} +.lobby-rank-table thead th { + text-align: left; + text-transform: uppercase; + letter-spacing: 0.06em; + color: rgba(0, 255, 255, 0.85); + font-weight: 700; + padding: 8px 6px; + border-bottom: 1px solid rgba(0, 255, 255, 0.25); + position: sticky; + top: 0; + background: linear-gradient(180deg, rgba(20, 26, 44, 0.98), rgba(20, 26, 44, 0.92)); + z-index: 1; +} +.lobby-rank-table thead th:nth-child(1) { width: 3.2rem; } +.lobby-rank-table thead th:nth-child(3) { text-align: right; width: 4rem; } +.lobby-rank-table tbody td { + padding: 7px 6px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + color: #e2e8f0; + vertical-align: middle; +} +.lobby-rank-table tbody td:nth-child(3) { + text-align: right; + font-weight: 600; + color: #fff; +} +.lobby-rank-table .lobby-rank-row-av { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; +} +.lobby-rank-table .lobby-rank-mini-av { + width: 26px; + height: 26px; + border-radius: 50%; + background: linear-gradient(145deg, #9ece6a, #565f89); + color: #1a1b26; + font-size: 0.65rem; + font-weight: 800; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 0 0 1px rgba(0, 255, 255, 0.2); +} +.lobby-rank-table .lobby-rank-row-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 180px; +} +.lobby-rank-self { + flex-shrink: 0; + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + padding: 12px 10px 6px; + margin-top: 6px; + border-top: 1px solid rgba(255, 0, 180, 0.42); + background: linear-gradient(90deg, rgba(255, 0, 180, 0.07), transparent); +} +.lobby-rank-self-rank { + font-weight: 800; + color: #c0caf5; + min-width: 2.5rem; + font-size: clamp(0.75rem, 2vw, 0.88rem); +} +.lobby-rank-self-av { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(145deg, #f472b6, #a855f7); + color: #fff; + font-size: 1rem; + font-weight: 800; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 0 0 2px rgba(255, 100, 200, 0.45); +} +.lobby-rank-self-meta { + flex: 1; + min-width: 0; +} +.lobby-rank-self-name { + font-size: clamp(0.85rem, 2.4vw, 1rem); + font-weight: 800; + color: #fff; + line-height: 1.2; +} +.lobby-rank-self-score { + font-size: clamp(0.85rem, 2.4vw, 1rem); + font-weight: 800; + color: #ff4ecd; + text-shadow: 0 0 12px rgba(255, 78, 205, 0.45); + flex-shrink: 0; +} + +.version-tag { + position: fixed; + right: 8px; + bottom: 4px; + left: auto; + font-size: 10px; + color: #a9b1d6; + opacity: 0.7; + pointer-events: none; +} + +/* Editor — เกมตอบคำถาม */ +.quiz-editor-wrap { + margin: 1rem 0; + padding: 1rem; + background: #24283b; + border-radius: 8px; + border: 1px solid #414868; +} +.quiz-editor-wrap h3 { + margin: 0 0 0.5rem; + font-size: 1rem; + color: #7aa2f7; +} +.quiz-questions-list { + display: flex; + flex-direction: column; + gap: 0.6rem; + margin: 0.75rem 0; +} +.quiz-q-row { + display: grid; + grid-template-columns: 1fr auto auto; + gap: 0.5rem; + align-items: center; +} +@media (max-width: 600px) { + .quiz-q-row { + grid-template-columns: 1fr; + } +} +.quiz-q-row input[type="text"] { + min-width: 0; + padding: 0.4rem 0.5rem; + border-radius: 6px; + border: 1px solid #565f89; + background: #1a1b26; + color: #c0caf5; +} +.quiz-q-row select { + padding: 0.35rem; + border-radius: 6px; + border: 1px solid #565f89; + background: #1a1b26; + color: #c0caf5; +} +.btn-quiz-add, +.quiz-q-remove { + padding: 0.35rem 0.65rem; + border-radius: 6px; + cursor: pointer; + border: 1px solid #565f89; + background: #414868; + color: #c0caf5; + font: inherit; +} +.quiz-q-remove { + font-size: 0.8rem; +} + +/* ซ่อนชั่วคราว: ปุ่มตั้งค่าโฮสต์ + ปรับแต่ง (มุมขวาบน) */ +body.room-lobby-page .room-lobby-top-right-actions { + display: none !important; +} diff --git a/www/html/Game/public/img/03-4-Host-Console/arrow-b.png b/www/html/Game/public/img/03-4-Host-Console/arrow-b.png new file mode 100644 index 0000000..fd12532 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/arrow-b.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/arrow-n.png b/www/html/Game/public/img/03-4-Host-Console/arrow-n.png new file mode 100644 index 0000000..d3d8003 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/arrow-n.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-bg.png b/www/html/Game/public/img/03-4-Host-Console/host-console-bg.png new file mode 100644 index 0000000..095ac60 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-bg.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-cf.png b/www/html/Game/public/img/03-4-Host-Console/host-console-cf.png new file mode 100644 index 0000000..233ed61 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-cf.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-delete.png b/www/html/Game/public/img/03-4-Host-Console/host-console-delete.png new file mode 100644 index 0000000..42862fd Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-delete.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-hostname.png b/www/html/Game/public/img/03-4-Host-Console/host-console-hostname.png new file mode 100644 index 0000000..cf46f6b Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-hostname.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-line-2.png b/www/html/Game/public/img/03-4-Host-Console/host-console-line-2.png new file mode 100644 index 0000000..614735b Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-line-2.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-line.png b/www/html/Game/public/img/03-4-Host-Console/host-console-line.png new file mode 100644 index 0000000..7e3a47a Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-line.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-scroll-bg.png b/www/html/Game/public/img/03-4-Host-Console/host-console-scroll-bg.png new file mode 100644 index 0000000..7853dbf Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-scroll-bg.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-scrollbar.png b/www/html/Game/public/img/03-4-Host-Console/host-console-scrollbar.png new file mode 100644 index 0000000..5208964 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-scrollbar.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-textfield.png b/www/html/Game/public/img/03-4-Host-Console/host-console-textfield.png new file mode 100644 index 0000000..a1c2bd8 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-textfield.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-txt-1.png b/www/html/Game/public/img/03-4-Host-Console/host-console-txt-1.png new file mode 100644 index 0000000..c16c113 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-txt-1.png differ diff --git a/www/html/Game/public/img/03-4-Host-Console/host-console-txt-2.png b/www/html/Game/public/img/03-4-Host-Console/host-console-txt-2.png new file mode 100644 index 0000000..1add1c8 Binary files /dev/null and b/www/html/Game/public/img/03-4-Host-Console/host-console-txt-2.png differ diff --git a/www/html/Game/public/img/03-5-Customize/btn-cf.png b/www/html/Game/public/img/03-5-Customize/btn-cf.png new file mode 100644 index 0000000..a8496fb Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/btn-cf.png differ diff --git a/www/html/Game/public/img/03-5-Customize/char-bg.png b/www/html/Game/public/img/03-5-Customize/char-bg.png new file mode 100644 index 0000000..48cfae0 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/char-bg.png differ diff --git a/www/html/Game/public/img/03-5-Customize/cloth-bg.png b/www/html/Game/public/img/03-5-Customize/cloth-bg.png new file mode 100644 index 0000000..21660e6 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/cloth-bg.png differ diff --git a/www/html/Game/public/img/03-5-Customize/coin-bg.png b/www/html/Game/public/img/03-5-Customize/coin-bg.png new file mode 100644 index 0000000..b98b3c3 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/coin-bg.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-1.png b/www/html/Game/public/img/03-5-Customize/color-1.png new file mode 100644 index 0000000..23a046d Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-1.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-2.png b/www/html/Game/public/img/03-5-Customize/color-2.png new file mode 100644 index 0000000..7b1807e Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-2.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-3.png b/www/html/Game/public/img/03-5-Customize/color-3.png new file mode 100644 index 0000000..a925b05 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-3.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-4.png b/www/html/Game/public/img/03-5-Customize/color-4.png new file mode 100644 index 0000000..d621f2c Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-4.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-5.png b/www/html/Game/public/img/03-5-Customize/color-5.png new file mode 100644 index 0000000..fbab831 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-5.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-6.png b/www/html/Game/public/img/03-5-Customize/color-6.png new file mode 100644 index 0000000..82cfe7a Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-6.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-7.png b/www/html/Game/public/img/03-5-Customize/color-7.png new file mode 100644 index 0000000..b4391c5 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-7.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-8.png b/www/html/Game/public/img/03-5-Customize/color-8.png new file mode 100644 index 0000000..27810ed Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-8.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-frame-session.png b/www/html/Game/public/img/03-5-Customize/color-frame-session.png new file mode 100644 index 0000000..e593733 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-frame-session.png differ diff --git a/www/html/Game/public/img/03-5-Customize/color-select.png b/www/html/Game/public/img/03-5-Customize/color-select.png new file mode 100644 index 0000000..83c12df Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/color-select.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-1.png b/www/html/Game/public/img/03-5-Customize/face-1.png new file mode 100644 index 0000000..323d0e8 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-1.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-2.png b/www/html/Game/public/img/03-5-Customize/face-2.png new file mode 100644 index 0000000..72e9130 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-2.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-3.png b/www/html/Game/public/img/03-5-Customize/face-3.png new file mode 100644 index 0000000..92885ce Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-3.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-4.png b/www/html/Game/public/img/03-5-Customize/face-4.png new file mode 100644 index 0000000..cfcdcaa Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-4.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-5.png b/www/html/Game/public/img/03-5-Customize/face-5.png new file mode 100644 index 0000000..379ddb5 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-5.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-6.png b/www/html/Game/public/img/03-5-Customize/face-6.png new file mode 100644 index 0000000..fa773aa Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-6.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-7.png b/www/html/Game/public/img/03-5-Customize/face-7.png new file mode 100644 index 0000000..c233f07 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-7.png differ diff --git a/www/html/Game/public/img/03-5-Customize/face-8.png b/www/html/Game/public/img/03-5-Customize/face-8.png new file mode 100644 index 0000000..cf5c4d0 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/face-8.png differ diff --git a/www/html/Game/public/img/03-5-Customize/item-bg.png b/www/html/Game/public/img/03-5-Customize/item-bg.png new file mode 100644 index 0000000..b467ef6 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/item-bg.png differ diff --git a/www/html/Game/public/img/03-5-Customize/item-select.png b/www/html/Game/public/img/03-5-Customize/item-select.png new file mode 100644 index 0000000..2f4e785 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/item-select.png differ diff --git a/www/html/Game/public/img/03-5-Customize/price-frame.png b/www/html/Game/public/img/03-5-Customize/price-frame.png new file mode 100644 index 0000000..e6cec34 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/price-frame.png differ diff --git a/www/html/Game/public/img/03-5-Customize/scroll-bar.png b/www/html/Game/public/img/03-5-Customize/scroll-bar.png new file mode 100644 index 0000000..373ee6c Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/scroll-bar.png differ diff --git a/www/html/Game/public/img/03-5-Customize/scroll-bg.png b/www/html/Game/public/img/03-5-Customize/scroll-bg.png new file mode 100644 index 0000000..123ab60 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/scroll-bg.png differ diff --git a/www/html/Game/public/img/03-5-Customize/skin-tone-1.png b/www/html/Game/public/img/03-5-Customize/skin-tone-1.png new file mode 100644 index 0000000..ee7e5fe Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/skin-tone-1.png differ diff --git a/www/html/Game/public/img/03-5-Customize/skin-tone-2.png b/www/html/Game/public/img/03-5-Customize/skin-tone-2.png new file mode 100644 index 0000000..9fcd44e Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/skin-tone-2.png differ diff --git a/www/html/Game/public/img/03-5-Customize/skin-tone-3.png b/www/html/Game/public/img/03-5-Customize/skin-tone-3.png new file mode 100644 index 0000000..f950b78 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/skin-tone-3.png differ diff --git a/www/html/Game/public/img/03-5-Customize/tab-1-face.png b/www/html/Game/public/img/03-5-Customize/tab-1-face.png new file mode 100644 index 0000000..5842a2f Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/tab-1-face.png differ diff --git a/www/html/Game/public/img/03-5-Customize/tab-2-hair.png b/www/html/Game/public/img/03-5-Customize/tab-2-hair.png new file mode 100644 index 0000000..d606984 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/tab-2-hair.png differ diff --git a/www/html/Game/public/img/03-5-Customize/tab-3-cloth.png b/www/html/Game/public/img/03-5-Customize/tab-3-cloth.png new file mode 100644 index 0000000..b026756 Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/tab-3-cloth.png differ diff --git a/www/html/Game/public/img/03-5-Customize/theme-color-txt.png b/www/html/Game/public/img/03-5-Customize/theme-color-txt.png new file mode 100644 index 0000000..a8d2aff Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/theme-color-txt.png differ diff --git a/www/html/Game/public/img/03-5-Customize/theme-skin-txt.png b/www/html/Game/public/img/03-5-Customize/theme-skin-txt.png new file mode 100644 index 0000000..4f7eacd Binary files /dev/null and b/www/html/Game/public/img/03-5-Customize/theme-skin-txt.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-frame.png b/www/html/Game/public/img/03-6-Profile/archiv-frame.png new file mode 100644 index 0000000..b3eb50a Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-frame.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-01-a.png b/www/html/Game/public/img/03-6-Profile/archiv-group-01-a.png new file mode 100644 index 0000000..848d876 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-01-a.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-01.png b/www/html/Game/public/img/03-6-Profile/archiv-group-01.png new file mode 100644 index 0000000..b5b8a66 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-01.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-02-a.png b/www/html/Game/public/img/03-6-Profile/archiv-group-02-a.png new file mode 100644 index 0000000..7c39fe2 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-02-a.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-02.png b/www/html/Game/public/img/03-6-Profile/archiv-group-02.png new file mode 100644 index 0000000..14ff0b6 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-02.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-03-a.png b/www/html/Game/public/img/03-6-Profile/archiv-group-03-a.png new file mode 100644 index 0000000..60b86ae Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-03-a.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-03.png b/www/html/Game/public/img/03-6-Profile/archiv-group-03.png new file mode 100644 index 0000000..f138ae2 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-03.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-04-a.png b/www/html/Game/public/img/03-6-Profile/archiv-group-04-a.png new file mode 100644 index 0000000..11060a9 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-04-a.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-04.png b/www/html/Game/public/img/03-6-Profile/archiv-group-04.png new file mode 100644 index 0000000..8f51c6d Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-04.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-05-a.png b/www/html/Game/public/img/03-6-Profile/archiv-group-05-a.png new file mode 100644 index 0000000..751662a Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-05-a.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-group-05.png b/www/html/Game/public/img/03-6-Profile/archiv-group-05.png new file mode 100644 index 0000000..4e463ef Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-group-05.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-icon-bg.png b/www/html/Game/public/img/03-6-Profile/archiv-icon-bg.png new file mode 100644 index 0000000..31e1fcc Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-icon-bg.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-line.png b/www/html/Game/public/img/03-6-Profile/archiv-line.png new file mode 100644 index 0000000..440360b Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-line.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-name-1.png b/www/html/Game/public/img/03-6-Profile/archiv-name-1.png new file mode 100644 index 0000000..0b9153b Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-name-1.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-name-2.png b/www/html/Game/public/img/03-6-Profile/archiv-name-2.png new file mode 100644 index 0000000..5b99605 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-name-2.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-name-3.png b/www/html/Game/public/img/03-6-Profile/archiv-name-3.png new file mode 100644 index 0000000..e887830 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-name-3.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-progress-bg.png b/www/html/Game/public/img/03-6-Profile/archiv-progress-bg.png new file mode 100644 index 0000000..d754389 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-progress-bg.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-progress.png b/www/html/Game/public/img/03-6-Profile/archiv-progress.png new file mode 100644 index 0000000..63877ba Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-progress.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-scroll-bar.png b/www/html/Game/public/img/03-6-Profile/archiv-scroll-bar.png new file mode 100644 index 0000000..985d010 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-scroll-bar.png differ diff --git a/www/html/Game/public/img/03-6-Profile/archiv-scroll-bg.png b/www/html/Game/public/img/03-6-Profile/archiv-scroll-bg.png new file mode 100644 index 0000000..0aa942d Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/archiv-scroll-bg.png differ diff --git a/www/html/Game/public/img/03-6-Profile/btn-edit.png b/www/html/Game/public/img/03-6-Profile/btn-edit.png new file mode 100644 index 0000000..e5ee041 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/btn-edit.png differ diff --git a/www/html/Game/public/img/03-6-Profile/btn-ex.png b/www/html/Game/public/img/03-6-Profile/btn-ex.png new file mode 100644 index 0000000..69bfc10 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/btn-ex.png differ diff --git a/www/html/Game/public/img/03-6-Profile/btn-off.png b/www/html/Game/public/img/03-6-Profile/btn-off.png new file mode 100644 index 0000000..781ec7a Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/btn-off.png differ diff --git a/www/html/Game/public/img/03-6-Profile/btn-on.png b/www/html/Game/public/img/03-6-Profile/btn-on.png new file mode 100644 index 0000000..10f3f95 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/btn-on.png differ diff --git a/www/html/Game/public/img/03-6-Profile/profile-char-frame-2.png b/www/html/Game/public/img/03-6-Profile/profile-char-frame-2.png new file mode 100644 index 0000000..603aeda Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/profile-char-frame-2.png differ diff --git a/www/html/Game/public/img/03-6-Profile/profile-char-frame.png b/www/html/Game/public/img/03-6-Profile/profile-char-frame.png new file mode 100644 index 0000000..4bb3be5 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/profile-char-frame.png differ diff --git a/www/html/Game/public/img/03-6-Profile/profile-pop-bg.png b/www/html/Game/public/img/03-6-Profile/profile-pop-bg.png new file mode 100644 index 0000000..103bfa2 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/profile-pop-bg.png differ diff --git a/www/html/Game/public/img/03-6-Profile/profile-pop-coin.png b/www/html/Game/public/img/03-6-Profile/profile-pop-coin.png new file mode 100644 index 0000000..95ef04f Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/profile-pop-coin.png differ diff --git a/www/html/Game/public/img/03-6-Profile/setting-frame.png b/www/html/Game/public/img/03-6-Profile/setting-frame.png new file mode 100644 index 0000000..c90cbbf Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/setting-frame.png differ diff --git a/www/html/Game/public/img/03-6-Profile/setting-txt-1.png b/www/html/Game/public/img/03-6-Profile/setting-txt-1.png new file mode 100644 index 0000000..595e796 Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/setting-txt-1.png differ diff --git a/www/html/Game/public/img/03-6-Profile/setting-txt-2.png b/www/html/Game/public/img/03-6-Profile/setting-txt-2.png new file mode 100644 index 0000000..926f2aa Binary files /dev/null and b/www/html/Game/public/img/03-6-Profile/setting-txt-2.png differ diff --git a/www/html/Game/public/img/AI-chat--header.png b/www/html/Game/public/img/AI-chat--header.png new file mode 100644 index 0000000..f86dce1 Binary files /dev/null and b/www/html/Game/public/img/AI-chat--header.png differ diff --git a/www/html/Game/public/img/AI-chat-BG.png b/www/html/Game/public/img/AI-chat-BG.png new file mode 100644 index 0000000..ceec8f7 Binary files /dev/null and b/www/html/Game/public/img/AI-chat-BG.png differ diff --git a/www/html/Game/public/img/AI-chat-bubble-thep.png b/www/html/Game/public/img/AI-chat-bubble-thep.png new file mode 100644 index 0000000..e1998dc Binary files /dev/null and b/www/html/Game/public/img/AI-chat-bubble-thep.png differ diff --git a/www/html/Game/public/img/AI-chat-line.png b/www/html/Game/public/img/AI-chat-line.png new file mode 100644 index 0000000..f47ecc8 Binary files /dev/null and b/www/html/Game/public/img/AI-chat-line.png differ diff --git a/www/html/Game/public/img/AI-chat-scrollbar-bg.png b/www/html/Game/public/img/AI-chat-scrollbar-bg.png new file mode 100644 index 0000000..02a7b22 Binary files /dev/null and b/www/html/Game/public/img/AI-chat-scrollbar-bg.png differ diff --git a/www/html/Game/public/img/AI-chat-scrollbar.png b/www/html/Game/public/img/AI-chat-scrollbar.png new file mode 100644 index 0000000..afe43a0 Binary files /dev/null and b/www/html/Game/public/img/AI-chat-scrollbar.png differ diff --git a/www/html/Game/public/img/BG-fade.png b/www/html/Game/public/img/BG-fade.png new file mode 100644 index 0000000..d60b55d Binary files /dev/null and b/www/html/Game/public/img/BG-fade.png differ diff --git a/www/html/Game/public/img/BTN Exit Room.png b/www/html/Game/public/img/BTN Exit Room.png new file mode 100644 index 0000000..752a6c5 Binary files /dev/null and b/www/html/Game/public/img/BTN Exit Room.png differ diff --git a/www/html/Game/public/img/BTN-SettingProfile.png b/www/html/Game/public/img/BTN-SettingProfile.png new file mode 100644 index 0000000..8d1fa86 Binary files /dev/null and b/www/html/Game/public/img/BTN-SettingProfile.png differ diff --git a/www/html/Game/public/img/BTN-howto.png b/www/html/Game/public/img/BTN-howto.png new file mode 100644 index 0000000..ef344b3 Binary files /dev/null and b/www/html/Game/public/img/BTN-howto.png differ diff --git a/www/html/Game/public/img/Room-id-BG.png b/www/html/Game/public/img/Room-id-BG.png new file mode 100644 index 0000000..1258239 Binary files /dev/null and b/www/html/Game/public/img/Room-id-BG.png differ diff --git a/www/html/Game/public/img/Room-id-private.png b/www/html/Game/public/img/Room-id-private.png new file mode 100644 index 0000000..b67280a Binary files /dev/null and b/www/html/Game/public/img/Room-id-private.png differ diff --git a/www/html/Game/public/img/Scene-lobby-main.png b/www/html/Game/public/img/Scene-lobby-main.png new file mode 100644 index 0000000..fadbc89 Binary files /dev/null and b/www/html/Game/public/img/Scene-lobby-main.png differ diff --git a/www/html/Game/public/img/btn-customize.png b/www/html/Game/public/img/btn-customize.png new file mode 100644 index 0000000..5ae8826 Binary files /dev/null and b/www/html/Game/public/img/btn-customize.png differ diff --git a/www/html/Game/public/img/btn-mic-mute.png b/www/html/Game/public/img/btn-mic-mute.png index 47c602c..e45b302 100644 Binary files a/www/html/Game/public/img/btn-mic-mute.png and b/www/html/Game/public/img/btn-mic-mute.png differ diff --git a/www/html/Game/public/img/btn-mic-on.png b/www/html/Game/public/img/btn-mic-on.png index 999bab3..2bb5a11 100644 Binary files a/www/html/Game/public/img/btn-mic-on.png and b/www/html/Game/public/img/btn-mic-on.png differ diff --git a/www/html/Game/public/img/btn-profile-bg.png b/www/html/Game/public/img/btn-profile-bg.png new file mode 100644 index 0000000..6c8810c Binary files /dev/null and b/www/html/Game/public/img/btn-profile-bg.png differ diff --git a/www/html/Game/public/img/btn-profile-set.png b/www/html/Game/public/img/btn-profile-set.png new file mode 100644 index 0000000..2d6970e Binary files /dev/null and b/www/html/Game/public/img/btn-profile-set.png differ diff --git a/www/html/Game/public/img/btn-ready.png b/www/html/Game/public/img/btn-ready.png new file mode 100644 index 0000000..200f47b Binary files /dev/null and b/www/html/Game/public/img/btn-ready.png differ diff --git a/www/html/Game/public/img/btn-setting-host.png b/www/html/Game/public/img/btn-setting-host.png new file mode 100644 index 0000000..4c0e417 Binary files /dev/null and b/www/html/Game/public/img/btn-setting-host.png differ diff --git a/www/html/Game/public/img/bubble-00.png b/www/html/Game/public/img/bubble-00.png new file mode 100644 index 0000000..ac2ec6a Binary files /dev/null and b/www/html/Game/public/img/bubble-00.png differ diff --git a/www/html/Game/public/img/bubble-01.png b/www/html/Game/public/img/bubble-01.png new file mode 100644 index 0000000..4d58ecc Binary files /dev/null and b/www/html/Game/public/img/bubble-01.png differ diff --git a/www/html/Game/public/img/bubble-02.png b/www/html/Game/public/img/bubble-02.png new file mode 100644 index 0000000..8d76fd2 Binary files /dev/null and b/www/html/Game/public/img/bubble-02.png differ diff --git a/www/html/Game/public/img/bubble-03.png b/www/html/Game/public/img/bubble-03.png new file mode 100644 index 0000000..96fa1dc Binary files /dev/null and b/www/html/Game/public/img/bubble-03.png differ diff --git a/www/html/Game/public/img/chat-scrollbar-bg.png b/www/html/Game/public/img/chat-scrollbar-bg.png new file mode 100644 index 0000000..58b9a30 Binary files /dev/null and b/www/html/Game/public/img/chat-scrollbar-bg.png differ diff --git a/www/html/Game/public/img/chat-scrollbar.png b/www/html/Game/public/img/chat-scrollbar.png new file mode 100644 index 0000000..c59c10b Binary files /dev/null and b/www/html/Game/public/img/chat-scrollbar.png differ diff --git a/www/html/Game/public/img/icon-ready.png b/www/html/Game/public/img/icon-ready.png new file mode 100644 index 0000000..d135569 Binary files /dev/null and b/www/html/Game/public/img/icon-ready.png differ diff --git a/www/html/Game/public/img/shadow-char.png b/www/html/Game/public/img/shadow-char.png new file mode 100644 index 0000000..fa579c4 Binary files /dev/null and b/www/html/Game/public/img/shadow-char.png differ diff --git a/www/html/Game/public/js/room-lobby.js b/www/html/Game/public/js/room-lobby.js index 896b57e..75e495d 100644 --- a/www/html/Game/public/js/room-lobby.js +++ b/www/html/Game/public/js/room-lobby.js @@ -1,30 +1,20 @@ (function () { // Error "message channel closed" มาจาก extension ของเบราว์เซอร์ ไม่ใช่โค้ดเกม — ลองโหมดไม่ระบุตัวตนหรือปิด extension - const BASE = '/Game'; + const BASE = typeof appPath === 'function' ? appPath('/Game') : '/Game'; + const SERVER = (typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : '') + '/Game'; + const MAIN_LOBBY_AI_BTN_ICON = typeof appPath === 'function' + ? appPath('/Main-Lobby/IMAGE/BTN-AI-ChatBOT.png') + : '/Main-Lobby/IMAGE/BTN-AI-ChatBOT.png'; const params = new URLSearchParams(location.search); const spaceId = params.get('space'); const nick = (params.get('nick') || '').trim() || 'ผู้เล่น'; - const previewMode = params.get('preview') === '1'; - const editorEmbedReturn = params.get('editorEmbed') === '1'; - const forceDefaultCharacter = params.get('defaultChar') === '1'; - /** จนกว่าโหลด /api/characters — ใช้ชั่วคราวก่อน join (หลังโหลดใช้ตัวแรกตามเวลาไฟล์บนเซิร์ฟเวอร์) */ - const LEGACY_PLACEHOLDER_CHARACTER_ID = 'Chatest'; - let firstCharacterDefaultResolved = null; - const firstCharacterDefaultPromise = fetch(BASE + '/api/characters') - .then((r) => r.json()) - .then((list) => { - if (Array.isArray(list) && list.length > 0 && list[0] && list[0].id) { - firstCharacterDefaultResolved = String(list[0].id); - } else { - firstCharacterDefaultResolved = ''; - } - }) - .catch(() => { - firstCharacterDefaultResolved = ''; - }); + const DISPLAY_NAME_STORAGE_KEY = 'roomLobbyDisplayName'; + let profileDisplayName = nick; if (!spaceId) { location.href = BASE + '/lobby.html'; return; } + const roomIdValueEl = document.getElementById('room-id-value'); + if (roomIdValueEl) roomIdValueEl.textContent = String(spaceId); - const CREATE_ROOM_URL = '/Create%20Room/'; + const CREATE_ROOM_URL = typeof appPath === 'function' ? appPath('/Create%20Room/') : '/Create%20Room/'; function wasPageReload() { try { @@ -39,8 +29,9 @@ /** Lobby หลังคดี — ต้องตรงกับ server POST_CASE_LOBBY_SPACE_ID */ const POST_CASE_LOBBY_SPACE_ID = 'mn8nx46h'; + const PLAYER_KEY = 'jdPlayerKey'; - const LOBBY_EVIDENCE_ASSET_BASE = '/Main-Lobby/IMAGE/See%20evidence'; + const LOBBY_EVIDENCE_ASSET_BASE = typeof appPath === 'function' ? appPath('/Main-Lobby/IMAGE/See%20evidence') : '/Main-Lobby/IMAGE/See%20evidence'; const LOBBY_EVIDENCE_RARITY = { common: 'Common', rare: 'Rare', legendary: 'Legendary' }; /** ข้อมูลตัวอย่างตาม caseId — แก้/โหลดจาก API ได้ภายหลัง */ const LOBBY_EVIDENCE_CASES = { @@ -103,7 +94,7 @@ } }; - const socket = io({ path: BASE + '/socket.io' }); + const socket = io(typeof GAME_SERVER !== 'undefined' ? GAME_SERVER : undefined, { path: '/Game/socket.io' }); const roomPlayersHud = document.getElementById('room-players-hud'); const peersList = document.getElementById('peers-list'); const readyCheck = document.getElementById('ready-check'); @@ -112,8 +103,8 @@ const nonHostMsg = document.getElementById('non-host-msg'); const playMapSelect = document.getElementById('play-map-select'); const canvas = document.getElementById('lobby-map-canvas'); - const READY_IMG_IDLE = BASE + '/img/btn-ready-idle.png?v=1'; - const READY_IMG_ACTIVE = BASE + '/img/btn-ready-active.png?v=1'; + const READY_IMG_IDLE = SERVER + '/img/btn-ready-idle.png?v=1'; + const READY_IMG_ACTIVE = SERVER + '/img/btn-ready-active.png?v=1'; let mapData = null; let quizModeActive = false; @@ -137,78 +128,7 @@ /** ตรงกับเซิร์ฟ — เฟสเลือกผู้ต้องสงสัยยังไม่จบ (ปิด overlay แล้วยังเปิดได้จากปุ่มโถง) */ let serverSuspectPhaseActive = false; let mapBackgroundImg = null; - /** รูปต่อเซลล์ (gridImageLibrary / gridImageCells) — โหลดคู่กับ mapData */ - let mapLobbyGridImgs = []; let hostIconImg = null; - - function normalizeLobbyGridImageCells(md) { - if (!md) return; - const w = md.width || 20, h = md.height || 15; - if (!Array.isArray(md.gridImageLibrary)) md.gridImageLibrary = []; - md.gridImageLibrary = md.gridImageLibrary.filter((s) => typeof s === 'string' && s.length > 0 && s.length < 30000000); - const libLen = md.gridImageLibrary.length; - let placements = []; - if (Array.isArray(md.gridImageSprites) && md.gridImageSprites.length) { - placements = md.gridImageSprites.map((raw) => { - const i = Math.max(0, Math.floor(Number(raw.i))); - const x = Math.floor(Number(raw.x)); - const y = Math.floor(Number(raw.y)); - const ww = Math.max(1, Math.floor(Number(raw.w)) || 1); - const hh = Math.max(1, Math.floor(Number(raw.h)) || 1); - return { i, x, y, w: ww, h: hh }; - }).filter((s) => Number.isFinite(s.i) && s.i >= 0 && s.i < libLen && - Number.isFinite(s.x) && Number.isFinite(s.y) && s.w >= 1 && s.h >= 1 && - s.x < w && s.y < h && s.x + s.w > 0 && s.y + s.h > 0); - placements = placements.map((s) => { - let x = s.x, y = s.y, ww = s.w, hh = s.h; - if (x < 0) { ww += x; x = 0; } - if (y < 0) { hh += y; y = 0; } - if (x + ww > w) ww = w - x; - if (y + hh > h) hh = h - y; - return { i: s.i, x, y, w: Math.max(1, ww), h: Math.max(1, hh) }; - }).filter((s) => s.x < w && s.y < h && s.x + s.w > 0 && s.y + s.h > 0); - } else { - const cells = md.gridImageCells; - if (Array.isArray(cells) && cells.length === h && libLen > 0) { - for (let yy = 0; yy < h; yy++) { - const row = cells[yy]; - if (!row) continue; - for (let xx = 0; xx < w; xx++) { - if (row[xx] == null) continue; - const iv = parseInt(row[xx], 10); - if (!Number.isFinite(iv) || iv < 0 || iv >= libLen) continue; - placements.push({ i: iv, x: xx, y: yy, w: 1, h: 1 }); - } - } - } - } - md.gridImagePlacements = placements; - const outCells = Array(h).fill(0).map(() => Array(w).fill(null)); - placements.forEach((sp) => { - for (let yy = sp.y; yy < sp.y + sp.h; yy++) { - for (let xx = sp.x; xx < sp.x + sp.w; xx++) { - if (yy >= 0 && yy < h && xx >= 0 && xx < w) outCells[yy][xx] = sp.i; - } - } - }); - md.gridImageCells = outCells; - } - - function refreshLobbyGridImages() { - mapLobbyGridImgs = []; - if (!mapData) return; - normalizeLobbyGridImageCells(mapData); - if (!mapData.gridImageLibrary || !mapData.gridImageLibrary.length) return; - mapData.gridImageLibrary.forEach((src, i) => { - mapLobbyGridImgs[i] = null; - if (!src || typeof src !== 'string') return; - const im = new Image(); - im.onload = function () { try { resizeAndDraw(); } catch (e) {} }; - im.onerror = function () {}; - im.src = src; - mapLobbyGridImgs[i] = im; - }); - } const peers = new Map(); /** รองรับ x/y string จาก Socket — ไม่งั้นตำแหน่งจากสุ่มจุดเกิดจะถูกทิ้ง */ function normalizeLobbyPeerFromServer(p, mapRef) { @@ -247,7 +167,6 @@ } const defaultAvatarImg = createDefaultAvatarImg(); const characterAnimations = {}; - const characterIdleAnimations = {}; const CHARACTER_ANIM_FRAMES = 4; const CHARACTER_ANIM_FRAME_MS = 200; @@ -266,65 +185,12 @@ return -1; } - function isUploadedCharAssetId(id) { - if (!id) return false; - const s = String(id).trim(); - return /^char-\d/i.test(s); - } - - function useMultiFrameIdleSpriteSheets(id) { - if (!id) return true; - return !isUploadedCharAssetId(id); - } - - function ensureCharacterIdleAnim(id, dir) { - const d = dir || 'down'; - if (!useMultiFrameIdleSpriteSheets(id)) { - const key0 = String(id) + '_' + d + '_idle'; - var anim0 = characterIdleAnimations[key0]; - if (!anim0) { - anim0 = { frames: [], fallback: null }; - characterIdleAnimations[key0] = anim0; - var enc0 = encodeURIComponent(id); - for (var ii = 0; ii < CHARACTER_ANIM_FRAMES; ii++) { - var im0 = new Image(); - im0.src = BASE + '/img/characters/' + enc0 + '_' + d + '_' + ii + '.png'; - anim0.frames.push(im0); - } - anim0.fallback = new Image(); - anim0.fallback.src = BASE + '/img/characters/' + enc0 + '_' + d + '_idle.png'; - } - return anim0; - } - const key = id + '_' + d + '_idle'; - var anim = characterIdleAnimations[key]; - if (!anim) { - anim = { frames: [], fallback: null }; - characterIdleAnimations[key] = anim; - var enc = encodeURIComponent(id); - var base = BASE + '/img/characters/' + enc + '_' + d; - for (var ii = 0; ii < CHARACTER_ANIM_FRAMES; ii++) { - var im = new Image(); - im.src = base + '_idle_' + ii + '.png'; - anim.frames.push(im); - } - anim.fallback = new Image(); - anim.fallback.src = base + '_idle.png'; - } - return anim; - } - - function idleAnimPhaseIndex(now) { - var t = typeof now === 'number' ? now : Date.now(); - return Math.floor(t / CHARACTER_ANIM_FRAME_MS) % CHARACTER_ANIM_FRAMES; - } - function getCharacterImg(id, direction) { if (!id) return null; const key = id + '_' + (direction || 'down'); if (characterImages[key]) return characterImages[key]; const img = new Image(); - img.src = BASE + '/img/characters/' + encodeURIComponent(id) + '_' + (direction || 'down') + '.png'; + img.src = SERVER + '/img/characters/' + encodeURIComponent(id) + '_' + (direction || 'down') + '.png'; characterImages[key] = img; return img; } @@ -332,15 +198,6 @@ function getCharacterFrame(id, direction, now, isWalking) { if (!id) return null; const dir = direction || 'down'; - const t = typeof now === 'number' ? now : Date.now(); - if (!isWalking) { - const idleAnim = ensureCharacterIdleAnim(id, dir); - const idlePhase = idleAnimPhaseIndex(t); - const idleFi = pickLoadedWalkFrameIndex(idleAnim, idlePhase); - if (idleFi >= 0) return idleAnim.frames[idleFi]; - const idfb = idleAnim.fallback; - if (idfb && idfb.complete && idfb.naturalWidth) return idfb; - } const key = id + '_' + dir; let anim = characterAnimations[key]; if (!anim) { @@ -351,13 +208,13 @@ frame0.onload = function () { for (var i = 1; i < CHARACTER_ANIM_FRAMES; i++) { var img = new Image(); - img.src = BASE + '/img/characters/' + encodeURIComponent(id) + '_' + dir + '_' + i + '.png'; + img.src = SERVER + '/img/characters/' + encodeURIComponent(id) + '_' + dir + '_' + i + '.png'; anim.frames.push(img); } if (mapData && canvas) drawLobbyMap(); }; frame0.onerror = function () { if (mapData && canvas) drawLobbyMap(); }; - frame0.src = BASE + '/img/characters/' + encodeURIComponent(id) + '_' + dir + '_0.png'; + frame0.src = SERVER + '/img/characters/' + encodeURIComponent(id) + '_' + dir + '_0.png'; anim.frames.push(frame0); } var phase = walkAnimPhaseIndex(now, isWalking); @@ -410,11 +267,14 @@ } hostIconImg = new Image(); - hostIconImg.src = BASE + '/img/host-icon.png'; + hostIconImg.src = SERVER + '/img/host-icon.png'; hostIconImg.onload = function () { if (mapData && canvas) drawLobbyMap(); }; const lobbyReadyIconImg = new Image(); - lobbyReadyIconImg.src = BASE + '/img/lobby-icon-ready.png?v=1'; + lobbyReadyIconImg.src = SERVER + '/img/icon-ready.png?v=1'; + lobbyReadyIconImg.onerror = function () { + lobbyReadyIconImg.src = SERVER + '/img/lobby-icon-ready.png?v=1'; + }; lobbyReadyIconImg.onload = function () { if (mapData && canvas) drawLobbyMap(); }; const readyLabelImg = document.getElementById('ready-label-img'); @@ -434,7 +294,7 @@ const defaultShadowImgs = { up: new Image(), down: new Image(), left: new Image(), right: new Image() }; ['up', 'down', 'left', 'right'].forEach(function (d) { - defaultShadowImgs[d].src = BASE + '/img/default-shadow-' + d + '.png'; + defaultShadowImgs[d].src = SERVER + '/img/default-shadow-' + d + '.png'; defaultShadowImgs[d].onload = function () { if (mapData && canvas) drawLobbyMap(); }; }); function getDefaultShadowImg(dir) { @@ -446,7 +306,7 @@ const SPEAKING_BUBBLE_FRAME_MS = 120; for (var sb = 0; sb < 4; sb++) { var img = new Image(); - img.src = BASE + '/img/speaking-bubble-0' + sb + '.png'; + img.src = SERVER + '/img/speaking-bubble-0' + sb + '.png'; img.onload = function () { if (mapData && canvas) drawLobbyMap(); }; speakingBubbleFrames.push(img); } @@ -489,7 +349,6 @@ : null; if (!bounds || !mapTransform || mapTransform.cw == null) { panel.classList.add('is-hidden'); - panel.classList.remove('quiz-map-question-panel--true-false'); panel.setAttribute('aria-hidden', 'true'); return; } @@ -510,7 +369,6 @@ panel.style.height = Math.round(Math.max(40, hPx)) + 'px'; panel.classList.remove('is-hidden'); panel.setAttribute('aria-hidden', 'false'); - panel.classList.add('quiz-map-question-panel--true-false'); } function drawLobbyMap() { @@ -523,7 +381,7 @@ const mapHpx = h * tileSize; const cw = canvas.width; const ch = canvas.height; - const fpDraw = getCharacterFootprintWH(mapData); + const characterCells = mapData.characterCells || 1; const me = peers.get(socket.id); const meX = (me && typeof me.x === 'number') ? me.x : 1; const meY = (me && typeof me.y === 'number') ? me.y : 1; @@ -546,26 +404,6 @@ const showGrid = mapData.showMapInGame !== false && mapData.showMapInGame !== 'false'; const timeMs = Date.now(); - function drawLobbyGridImagePlacementsLayer() { - if (!mapData.gridImagePlacements || !mapData.gridImagePlacements.length || !mapData.gridImageLibrary || !mapData.gridImageLibrary.length) return; - for (let gi = 0; gi < mapData.gridImagePlacements.length; gi++) { - const sp = mapData.gridImagePlacements[gi]; - const sx0 = sp.x * tileSize; - const sy0 = sp.y * tileSize; - const sw0 = sp.w * tileSize; - const sh0 = sp.h * tileSize; - const gim = mapLobbyGridImgs[sp.i]; - if (!gim || !gim.complete || !gim.naturalWidth) continue; - ctx.save(); - ctx.beginPath(); - ctx.rect(sx0, sy0, sw0, sh0); - ctx.clip(); - ctx.drawImage(gim, sx0, sy0, sw0, sh0); - ctx.restore(); - ctx.strokeStyle = 'rgba(255,255,255,0.16)'; - ctx.strokeRect(sx0 + 0.5, sy0 + 0.5, sw0 - 1, sh0 - 1); - } - } if (showGrid) { for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { @@ -585,15 +423,6 @@ ctx.fillStyle = (x + y) % 2 === 0 ? '#24283b' : '#1f2335'; ctx.fillRect(sx, sy, tileSize, tileSize); } - } - } - } - drawLobbyGridImagePlacementsLayer(); - if (showGrid) { - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - const sx = x * tileSize; - const sy = y * tileSize; const isInter = mapData.interactive && mapData.interactive[y] && mapData.interactive[y][x] === 1; if (isInter) { ctx.fillStyle = 'rgba(158,206,106,0.35)'; @@ -675,15 +504,15 @@ ctx.strokeRect(sx + 1, sy + 1, sw - 2, sh - 2); ctx.shadowBlur = 0; const iconSz = Math.min(sw, sh) * 0.4; - ctx.font = 'bold ' + Math.round(iconSz) + 'px sans-serif'; + ctx.font = 'bold ' + Math.round(iconSz) + 'px NotoSansThai, Kanit, system-ui, sans-serif'; ctx.fillStyle = fill; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(icon, mcx, mcy - sh * 0.1); - ctx.font = 'bold ' + Math.max(11, Math.round(tileSize * 0.44)) + 'px Kanit,system-ui,sans-serif'; + ctx.font = 'bold ' + Math.max(11, Math.round(tileSize * 0.44)) + 'px NotoSansThai, Kanit, system-ui, sans-serif'; ctx.fillStyle = '#e2e8f0'; ctx.fillText(en, mcx, mcy + sh * 0.08); - ctx.font = Math.max(10, Math.round(tileSize * 0.32)) + 'px Kanit,system-ui,sans-serif'; + ctx.font = Math.max(10, Math.round(tileSize * 0.32)) + 'px NotoSansThai, Kanit, system-ui, sans-serif'; ctx.fillStyle = 'rgba(226,232,240,0.92)'; ctx.fillText(th, mcx, mcy + sh * 0.2); ctx.restore(); @@ -711,9 +540,9 @@ const py = ((p.y != null ? p.y : 1) + off.ay) * tileSize; const screenX = px; const screenY = py; - const cx = screenX + (fpDraw.cw * tileSize) / 2; - const cellBottom = screenY + fpDraw.ch * tileSize; - const boxSize = Math.min(tileSize, tileSize * 1.2) * Math.max(fpDraw.cw, fpDraw.ch); + const cx = screenX + tileSize / 2; + const cellBottom = screenY + tileSize; + const boxSize = Math.min(tileSize, tileSize * 1.2) * characterCells; const dir = p.direction || 'down'; const isWalking = id === socket.id ? !!(me && me.isWalking) @@ -786,7 +615,7 @@ const voiceIconY = labelY - tileSize * 0.12; if (p.voiceMicOn === false) { const iconSize = Math.max(10, Math.round(tileSize * 0.35)); - ctx.font = iconSize + 'px sans-serif'; + ctx.font = iconSize + 'px NotoSansThai, Kanit, system-ui, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#f7768e'; @@ -797,9 +626,8 @@ var nwI = lobbyReadyIconImg.naturalWidth; var nhI = lobbyReadyIconImg.naturalHeight; if (nwI > 0 && nhI > 0) { - var targetH = Math.min(tileSize * 0.88, Math.max(nameFontSize * 1.18, tileSize * 0.62)); - var rih = Math.max(18, Math.round(targetH)); - var riw = Math.round((nwI / nhI) * rih); + var rih = 82; + var riw = 89; var iconBottom = Math.round(nameY - nameFontSize * 1.0); var iconTop = iconBottom - rih; var iconLeft = Math.round(cx - riw / 2); @@ -810,21 +638,26 @@ ctx.restore(); } } - ctx.fillStyle = '#c0caf5'; - ctx.font = nameFontSize + 'px sans-serif'; + ctx.fillStyle = '#ffffff'; + ctx.strokeStyle = '#111827'; + ctx.lineWidth = Math.max(2, Math.round(nameFontSize * 0.22)); + ctx.lineJoin = 'round'; + ctx.font = '400 ' + nameFontSize + 'px NotoSansThai, Kanit, sans-serif'; ctx.textAlign = 'center'; if (isHost && hostIconImg && hostIconImg.complete && hostIconImg.naturalWidth) { - const iconW = Math.round(tileSize * 0.44); - const iconH = Math.round(tileSize * 0.44); + const iconW = 53; + const iconH = 40; const gap = Math.max(2, Math.round(tileSize * 0.06)); const textW = ctx.measureText(nameText).width; const totalW = iconW + gap + textW; const startX = cx - totalW / 2; ctx.drawImage(hostIconImg, 0, 0, hostIconImg.naturalWidth, hostIconImg.naturalHeight, startX, labelY - iconH / 2, iconW, iconH); ctx.textAlign = 'left'; + ctx.strokeText(nameText, startX + iconW + gap, nameY); ctx.fillText(nameText, startX + iconW + gap, nameY); ctx.textAlign = 'center'; } else { + ctx.strokeText(nameText, cx, nameY); ctx.fillText(nameText, cx, nameY); } if (peerLockedOut) ctx.restore(); @@ -891,28 +724,15 @@ return !!(grid && grid[ty] && grid[ty][tx] === 1); } - function getCharacterFootprintWH(md) { - if (!md) return { cw: 1, ch: 1 }; - let cw = Math.floor(Number(md.characterCellsW)); - let ch = Math.floor(Number(md.characterCellsH)); - const hasW = Number.isFinite(cw) && cw >= 1; - const hasH = Number.isFinite(ch) && ch >= 1; - if (hasW && hasH) { - return { cw: Math.max(1, Math.min(4, cw)), ch: Math.max(1, Math.min(4, ch)) }; - } - const leg = Math.max(1, Math.min(4, Math.floor(Number(md.characterCells)) || 1)); - return { cw: leg, ch: leg }; - } - function quizTilesFootprintLobby(px, py) { const s = new Set(); if (!mapData) return s; - const { cw, ch } = getCharacterFootprintWH(mapData); + const cells = Math.max(1, Math.min(4, mapData.characterCells || 1)); const w = mapData.width || 20, h = mapData.height || 15; const minTx = Math.floor(px); const minTy = Math.floor(py); - const maxTx = Math.min(w - 1, minTx + cw - 1); - const maxTy = Math.min(h - 1, minTy + ch - 1); + const maxTx = Math.min(w - 1, minTx + cells - 1); + const maxTy = Math.min(h - 1, minTy + cells - 1); for (let ty = minTy; ty <= maxTy; ty++) { for (let tx = minTx; tx <= maxTx; tx++) { if (tx >= 0 && ty >= 0) s.add(tx + ',' + ty); @@ -921,49 +741,6 @@ return s; } - /** กล่องตรวจโซนห้ามซ้อน — เหมือน play.js (ชิดเท้า + กลางแนวนอน) */ - function resolveBlockPlayerNoOverlapBoxWHLobby(md) { - if (!md) return { cw: 1, ch: 1, boxW: 1, boxH: 1 }; - const { cw, ch } = getCharacterFootprintWH(md); - let w = md.blockPlayerSeparationW != null && md.blockPlayerSeparationW !== '' ? Math.floor(Number(md.blockPlayerSeparationW)) : NaN; - let h = md.blockPlayerSeparationH != null && md.blockPlayerSeparationH !== '' ? Math.floor(Number(md.blockPlayerSeparationH)) : NaN; - const leg = md.blockPlayerSeparation != null && md.blockPlayerSeparation !== '' ? Math.floor(Number(md.blockPlayerSeparation)) : NaN; - const hasW = Number.isFinite(w) && w >= 0; - const hasH = Number.isFinite(h) && h >= 0; - if (!hasW && !hasH && Number.isFinite(leg) && leg >= 1) { - w = Math.min(leg, cw); - h = Math.min(leg, ch); - } else { - if (!hasW) w = 0; - if (!hasH) h = 0; - } - const boxW = !w || w <= 0 ? cw : Math.max(1, Math.min(cw, w)); - const boxH = !h || h <= 0 ? ch : Math.max(1, Math.min(ch, h)); - return { cw, ch, boxW, boxH }; - } - - function quizTilesBlockPlayerPeerFootprintLobby(px, py) { - const s = new Set(); - if (!mapData) return s; - if (typeof px !== 'number' || typeof py !== 'number' || !Number.isFinite(px) || !Number.isFinite(py)) return s; - const { cw, ch, boxW, boxH } = resolveBlockPlayerNoOverlapBoxWHLobby(mapData); - const w = mapData.width || 20, h = mapData.height || 15; - const minTx = Math.floor(px); - const minTy = Math.floor(py); - const offX = minTx + Math.floor((cw - boxW) / 2); - const offY = minTy + (ch - boxH); - for (let ty = offY; ty < offY + boxH; ty++) { - for (let tx = offX; tx < offX + boxW; tx++) { - if (tx >= 0 && ty >= 0 && tx < w && ty < h) s.add(tx + ',' + ty); - } - } - return s; - } - - function lobbyPeerBlockPlayerOccupiesTile(px, py, txi, tyi) { - return quizTilesBlockPlayerPeerFootprintLobby(px, py).has(txi + ',' + tyi); - } - function quizAnswerTileForbiddenLobby(tx, ty) { if (!quizPlayerLocal || quizPlayerLocal.eliminated) return false; if (quizPlayerLocal.cannotTrue && quizCellOnLobby(mapData.quizTrueArea, tx, ty)) return true; @@ -1005,7 +782,7 @@ if (bp && bp[ty] && bp[ty][tx] === 1) { for (const [id, p] of peers) { if (id === socket.id) continue; - if (lobbyPeerBlockPlayerOccupiesTile(p.x, p.y, tx, ty)) return false; + if (Math.floor(p.x) === tx && Math.floor(p.y) === ty) return false; } } if (mapData.gameType === 'quiz' && quizModeActive && quizPlayerLocal && !quizPlayerLocal.eliminated) { @@ -1163,6 +940,7 @@ if (!preplayOverlay) return; preplayOverlay.classList.add('is-hidden'); preplayOverlay.setAttribute('aria-hidden', 'true'); + try { document.body.classList.remove('room-lobby--preplay-open'); } catch (e) { /* ignore */ } preplaySelectedLevel = null; delete preplayOverlay.dataset.preplayLevel; if (preplayCaseDetailOverlay) preplayCaseDetailOverlay.classList.add('is-hidden'); @@ -1177,6 +955,7 @@ preplayOverlay.querySelectorAll('.lobby-level-card.is-selected').forEach(function (el) { el.classList.remove('is-selected'); }); preplayOverlay.classList.remove('is-hidden'); preplayOverlay.setAttribute('aria-hidden', 'false'); + try { document.body.classList.add('room-lobby--preplay-open'); } catch (e) { /* ignore */ } if (preplayStepLevel) preplayStepLevel.classList.remove('is-hidden'); if (preplayStepCase) preplayStepCase.classList.add('is-hidden'); if (preplayCaseDetailOverlay) preplayCaseDetailOverlay.classList.add('is-hidden'); @@ -1202,7 +981,10 @@ preplayOverlay.querySelectorAll('.lobby-level-card').forEach(function (b) { b.classList.remove('is-selected'); }); btn.classList.add('is-selected'); preplaySelectedLevel = btn.getAttribute('data-level'); - if (preplaySelectedLevel) preplayOverlay.dataset.preplayLevel = preplaySelectedLevel; + if (preplaySelectedLevel) { + preplayOverlay.dataset.preplayLevel = preplaySelectedLevel; + showPreplayCaseStep(); + } }); }); @@ -1212,19 +994,21 @@ var btnCloseCase = document.getElementById('lobby-preplay-close-case'); if (btnCloseCase) btnCloseCase.addEventListener('click', function () { closeLobbyPreplayWizard(); }); - var btnLevelReady = document.getElementById('lobby-preplay-level-ready'); - if (btnLevelReady) btnLevelReady.addEventListener('click', function () { - if (!preplaySelectedLevel) { - try { alert('กรุณาเลือกระดับความท้าทายก่อน'); } catch (e) { /* ignore */ } - return; - } - preplayOverlay.dataset.preplayLevel = preplaySelectedLevel; - showPreplayCaseStep(); - }); - var btnBackCase = document.getElementById('lobby-preplay-back-case'); if (btnBackCase) btnBackCase.addEventListener('click', function () { showPreplayLevelStep(); }); + var caseRow = preplayOverlay.querySelector('.lobby-preplay-case-row'); + var casePrev = document.getElementById('lobby-preplay-case-prev'); + var caseNext = document.getElementById('lobby-preplay-case-next'); + function scrollCaseRow(dir) { + if (!caseRow) return; + var firstCard = caseRow.querySelector('.lobby-case-card-wrap'); + var step = firstCard ? Math.max(120, Math.round(firstCard.getBoundingClientRect().width * 0.92)) : Math.max(140, Math.round(caseRow.clientWidth * 0.65)); + caseRow.scrollBy({ left: dir * step, behavior: 'smooth' }); + } + if (casePrev) casePrev.addEventListener('click', function () { scrollCaseRow(-1); }); + if (caseNext) caseNext.addEventListener('click', function () { scrollCaseRow(1); }); + preplayOverlay.addEventListener('click', function (ev) { var startBtn = ev.target.closest('.lobby-case-start'); if (!startBtn || !preplayOverlay.contains(startBtn)) return; @@ -1233,7 +1017,7 @@ || (preplayOverlay.dataset.preplayLevel || '').trim(); if (!cid) return; if (!level) { - try { alert('กรุณาเลือกระดับความท้าทายก่อน (กลับไปขั้นตอนก่อนหน้าแล้วกด READY)'); } catch (e0) { /* ignore */ } + try { alert('กรุณาเลือกระดับความท้าทายก่อน (กลับไปขั้นตอนก่อนหน้าแล้วเลือกระดับ)'); } catch (e0) { /* ignore */ } return; } var acked = false; @@ -1350,7 +1134,7 @@ } function updatePlayersHud() { - if (roomPlayersHud) roomPlayersHud.textContent = 'player: ' + peers.size + '/' + maxPlayers; + if (roomPlayersHud) roomPlayersHud.textContent = 'PLAYERS : ' + peers.size + '/' + maxPlayers; const sph = document.getElementById('suspect-players-hud'); if (sph) sph.textContent = 'PLAYERS : ' + peers.size + '/' + maxPlayers; } @@ -1370,40 +1154,83 @@ if (quizModeActive) renderQuizScoreboard(lastQuizScores); } + const DEFAULT_CHARACTER_ID = 'Chatest'; function getStoredCharacterId() { - if (forceDefaultCharacter) { - if (firstCharacterDefaultResolved === null) return LEGACY_PLACEHOLDER_CHARACTER_ID; - return firstCharacterDefaultResolved || ''; - } - try { - const v = localStorage.getItem('gameCharacterId'); - if (v) return v; - } catch (e) { /* ignore */ } - if (firstCharacterDefaultResolved === null) return LEGACY_PLACEHOLDER_CHARACTER_ID; - return firstCharacterDefaultResolved || ''; - } - - function updatePreviewClaimHostUi() { - var btn = document.getElementById('btn-preview-claim-host'); - if (!btn) return; - if (!previewMode) { - btn.style.display = 'none'; - return; - } - var isHost = hostId === socket.id; - btn.style.display = !isHost && socket.id ? 'inline-block' : 'none'; + try { return localStorage.getItem('gameCharacterId') || DEFAULT_CHARACTER_ID; } catch (e) { return DEFAULT_CHARACTER_ID; } } function updateLobbyProfileAvatar() { const img = document.getElementById('room-lobby-profile-avatar'); if (!img) return; const id = getStoredCharacterId(); - if (!id) { - img.src = defaultAvatarImg.src; - } else { - img.src = BASE + '/img/characters/' + encodeURIComponent(id) + '_down.png?ch=' + encodeURIComponent(id); + img.src = SERVER + '/img/characters/' + encodeURIComponent(id) + '_down.png?ch=' + encodeURIComponent(id); + img.alt = (profileDisplayName || nick || 'ผู้เล่น') + ' — ตัวละคร'; + } + + function loadProfileDisplayName() { + try { + const saved = (localStorage.getItem(DISPLAY_NAME_STORAGE_KEY) || '').trim(); + if (saved) profileDisplayName = saved; + } catch (e) { /* ignore */ } + } + + function saveProfileDisplayName(nextName) { + const safeName = String(nextName || '').trim(); + if (!safeName) return; + profileDisplayName = safeName; + try { localStorage.setItem(DISPLAY_NAME_STORAGE_KEY, safeName); } catch (e) { /* ignore */ } + } + + function getProfileDisplayName() { + const me = peers.get(socket.id); + const serverName = me && typeof me.nickname === 'string' ? me.nickname.trim() : ''; + if (serverName) return serverName; + return profileDisplayName || nick || 'PLAYER'; + } + + loadProfileDisplayName(); + + function padAgentId(n) { + let s = String(n || ''); + while (s.length < 6) s = '0' + s; + return s; + } + + function ensureAgentDisplayId() { + const key = 'agentDisplayId'; + try { + let v = (localStorage.getItem(key) || '').trim(); + if (!/^\d{6}$/.test(v)) { + v = padAgentId(100000 + Math.floor(Math.random() * 899999)); + localStorage.setItem(key, v); + } + return v; + } catch (e) { + return padAgentId(100000 + Math.floor(Math.random() * 899999)); + } + } + + function ensurePlayerKey() { + try { + let k = (localStorage.getItem(PLAYER_KEY) || '').trim(); + if (!k || k.length < 8) { + k = 'p_' + Date.now() + '_' + Math.random().toString(36).slice(2, 14); + localStorage.setItem(PLAYER_KEY, k); + } + return k; + } catch (e) { + return 'p_' + Date.now() + '_' + Math.random().toString(36).slice(2, 14); + } + } + + function getStoredCoins() { + try { + const raw = localStorage.getItem('jdCoins'); + const n = Math.max(0, parseInt(raw, 10) || 0); + return n; + } catch (e) { + return 0; } - img.alt = (nick || 'ผู้เล่น') + ' — ตัวละคร'; } function syncLobbyAvatarFromStorage() { @@ -1411,9 +1238,344 @@ if (typeof redrawLobbyMap === 'function') redrawLobbyMap(); } + class RoomLobbyProfileOverlay { + constructor() { + this.overlay = document.getElementById('room-lobby-profile-overlay'); + this.backdrop = document.getElementById('room-lobby-profile-backdrop'); + this.closeBtn = document.getElementById('room-lobby-profile-close'); + this.dialogEl = document.querySelector('.room-lobby-profile-dialog'); + this.innerFrameEl = document.querySelector('.room-lobby-profile-inner-frame'); + this.coinsRowEl = document.querySelector('.room-lobby-profile-coins'); + this.coinsLabelEl = document.querySelector('.room-lobby-profile-coins-label'); + this.coinsIconEl = document.querySelector('.room-lobby-profile-coins-icon'); + this.avatarEl = document.getElementById('room-lobby-profile-overlay-avatar'); + this.nameEl = document.getElementById('room-lobby-profile-overlay-name'); + this.agentEl = document.getElementById('room-lobby-profile-overlay-agent'); + this.coinsEl = document.getElementById('room-lobby-profile-coins-val'); + 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.editBtn = document.getElementById('room-lobby-profile-edit-btn'); + this.activeArchivGroup = 1; + this._boundSyncProfileFrameScale = () => this._syncProfileFrameScale(); + this._profileFrameScaleObserver = null; + this._bindEvents(); + this._initProfileFrameScaleObserver(); + this._syncProfileFrameScale(); + this._applySwitchVisual(this.musicBtn, true); + this._applySwitchVisual(this.sfxBtn, false); + this._setArchivGroup(1); + this._syncCoinsFromServer(); + } + + _bindEvents() { + this.backdrop?.addEventListener('click', () => this.close()); + this.closeBtn?.addEventListener('click', () => this.close()); + this.musicBtn?.addEventListener('click', () => this._toggleChip(this.musicBtn)); + this.sfxBtn?.addEventListener('click', () => this._toggleChip(this.sfxBtn)); + this.archivGroupBtns.forEach((btn) => { + btn.addEventListener('click', () => { + const raw = parseInt(btn.getAttribute('data-group'), 10); + this._setArchivGroup(Number.isFinite(raw) ? raw : 1); + }); + }); + this.editBtn?.addEventListener('click', () => this._editDisplayName()); + window.addEventListener('resize', this._boundSyncProfileFrameScale); + } + + _initProfileFrameScaleObserver() { + if (!this.innerFrameEl) return; + if (typeof ResizeObserver === 'undefined') return; + this._profileFrameScaleObserver = new ResizeObserver(() => this._syncProfileFrameScale()); + this._profileFrameScaleObserver.observe(this.innerFrameEl); + } + + _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)); + this.dialogEl.style.setProperty('--rp-scale', String(dialogScale.toFixed(4))); + } + } + if (!this.innerFrameEl) return; + this.innerFrameEl.style.setProperty('--pf-scale', String(dialogScale.toFixed(4))); + } + + _toggleChip(btn) { + if (!btn) return; + const current = String(btn.getAttribute('data-on') || '').toLowerCase() === 'true'; + this._applySwitchVisual(btn, !current); + } + + _applySwitchVisual(btn, isOn) { + if (!btn) return; + const on = !!isOn; + btn.setAttribute('data-on', on ? 'true' : 'false'); + const img = btn.querySelector('img'); + if (!img) return; + const nextSrc = on ? 'img/03-6-Profile/btn-on.png' : 'img/03-6-Profile/btn-off.png'; + img.src = nextSrc; + img.alt = on ? 'เปิด' : 'ปิด'; + } + + _setArchivGroup(groupIndex) { + let idx = parseInt(groupIndex, 10); + if (!Number.isFinite(idx) || idx < 1 || idx > 5) idx = 1; + this.activeArchivGroup = idx; + this.archivGroupBtns.forEach((btn) => { + const raw = parseInt(btn.getAttribute('data-group'), 10); + const id = Number.isFinite(raw) ? raw : 1; + const active = id === idx; + btn.classList.toggle('is-active', active); + const img = btn.querySelector('img'); + if (!img) return; + img.src = active + ? 'img/03-6-Profile/archiv-group-0' + id + '-a.png' + : 'img/03-6-Profile/archiv-group-0' + id + '.png'; + }); + } + + setProfileData(data) { + const payload = data || {}; + const avatarSrc = payload.avatarSrc || document.getElementById('room-lobby-profile-avatar')?.src || ''; + if (this.avatarEl && avatarSrc) this.avatarEl.src = avatarSrc; + if (this.nameEl) this.nameEl.textContent = payload.displayName || getProfileDisplayName(); + const agentId = payload.agentIdLabel || ('AGENT ID : ' + ensureAgentDisplayId()); + if (this.agentEl) this.agentEl.textContent = agentId; + const coinsVal = payload.coins != null ? payload.coins : getStoredCoins(); + if (this.coinsEl) this.coinsEl.textContent = String(Math.max(0, parseInt(coinsVal, 10) || 0)); + } + + _syncCoinsFromServer() { + const key = ensurePlayerKey(); + const url = (typeof appPath === 'function' ? appPath('/Admin/api/player-coins.php') : '/Admin/api/player-coins.php') + + '?playerKey=' + encodeURIComponent(key); + fetch(url, { credentials: 'omit' }) + .then((r) => r.json()) + .then((d) => { + if (!d || !d.ok) return; + const coins = Math.max(0, parseInt(d.coins, 10) || 0); + try { localStorage.setItem('jdCoins', String(coins)); } catch (e) { /* ignore */ } + if (this.coinsEl) this.coinsEl.textContent = String(coins); + }) + .catch(() => { /* ignore */ }); + } + + _editDisplayName() { + const currentName = this.nameEl ? String(this.nameEl.textContent || '').trim() : getProfileDisplayName(); + const draft = window.prompt('แก้ไขชื่อผู้เล่น', currentName || ''); + if (draft == null) return; + const nextName = String(draft).trim(); + if (!nextName) { + alert('กรุณากรอกชื่อผู้เล่น'); + return; + } + saveProfileDisplayName(nextName); + this.setProfileData({ displayName: nextName }); + updateLobbyProfileAvatar(); + } + + open(data) { + if (!this.overlay) return; + this.setProfileData(data); + this._syncCoinsFromServer(); + this.overlay.classList.remove('is-hidden'); + this.overlay.setAttribute('aria-hidden', 'false'); + this._syncProfileFrameScale(); + requestAnimationFrame(() => this._syncProfileFrameScale()); + requestAnimationFrame(() => requestAnimationFrame(() => this._syncProfileFrameScale())); + this.closeBtn?.focus(); + } + + close() { + if (!this.overlay) return; + this.overlay.classList.add('is-hidden'); + this.overlay.setAttribute('aria-hidden', 'true'); + } + + toggle(force, data) { + if (!this.overlay) return; + const shouldOpen = typeof force === 'boolean' ? force : this.overlay.classList.contains('is-hidden'); + if (shouldOpen) this.open(data); + else this.close(); + } + } + + const roomLobbyProfileOverlay = new RoomLobbyProfileOverlay(); + window.RoomLobbyProfileOverlay = RoomLobbyProfileOverlay; + window.roomLobbyProfileOverlay = roomLobbyProfileOverlay; + + class HostConsoleOverlay { + constructor() { + this.overlay = document.getElementById('host-console-overlay'); + this.backdrop = document.getElementById('host-console-backdrop'); + this.dialogEl = document.querySelector('.room-lobby-host-console-dialog'); + this.closeBtn = document.getElementById('host-console-close'); + this.confirmBtn = document.getElementById('host-console-confirm'); + this.decBtn = document.getElementById('host-console-max-dec'); + this.incBtn = document.getElementById('host-console-max-inc'); + this.maxValueEl = document.getElementById('host-console-max-value'); + this.summaryEl = document.getElementById('host-console-summary'); + this.membersListEl = document.getElementById('host-console-members-list'); + this.pendingMaxPlayers = maxPlayers; + this._boundSyncScale = () => this._syncScale(); + this._scaleObserver = null; + this._bindEvents(); + this._initScaleObserver(); + this._syncScale(); + } + + _bindEvents() { + // Close only via the X hit area button (and Esc key handler). + this.closeBtn?.addEventListener('click', () => this.close()); + this.decBtn?.addEventListener('click', () => this._changeMax(-1)); + this.incBtn?.addEventListener('click', () => this._changeMax(1)); + this.confirmBtn?.addEventListener('click', () => this._confirm()); + window.addEventListener('resize', this._boundSyncScale); + } + + _initScaleObserver() { + if (!this.dialogEl) return; + if (typeof ResizeObserver === 'undefined') return; + this._scaleObserver = new ResizeObserver(() => this._syncScale()); + this._scaleObserver.observe(this.dialogEl); + } + + _syncScale() { + if (!this.dialogEl) return; + const w = this.dialogEl.clientWidth || 0; + const h = this.dialogEl.clientHeight || 0; + if (!w || !h) return; + const scaleW = w / 990; + const scaleH = h / 881; + const scale = Math.max(0.5, Math.min(1.08, Math.min(scaleW, scaleH))); + this.dialogEl.style.setProperty('--hc-scale', String(scale.toFixed(4))); + } + + _isHost() { + return hostId === socket.id; + } + + _getPlayersCount() { + return Math.max(0, peers.size); + } + + _changeMax(delta) { + if (!this._isHost()) return; + const currentPlayers = this._getPlayersCount(); + const minAllowed = Math.max(2, currentPlayers); + const maxAllowed = 10; + const next = this.pendingMaxPlayers + delta; + this.pendingMaxPlayers = Math.max(minAllowed, Math.min(maxAllowed, next)); + this._render(); + } + + _kickMember(row) { + if (!this._isHost()) return; + if (!row || !row.id || row.isHost) return; + socket.emit('host-console-kick-peer', { targetId: row.id }, (res) => { + if (!res || !res.ok) { + var msg = (res && res.error) ? res.error : 'ลบสมาชิกไม่สำเร็จ'; + try { alert(msg); } catch (e) { /* ignore */ } + return; + } + }); + } + + _renderMembers() { + if (!this.membersListEl) return; + this.membersListEl.textContent = ''; + const rows = [...peers.entries()].map(([id, p]) => ({ + id, + isHost: id === hostId, + name: (p && p.nickname) ? String(p.nickname).trim() : id.slice(0, 8) + })); + rows.sort((a, b) => { + if (a.isHost && !b.isHost) return -1; + if (!a.isHost && b.isHost) return 1; + return a.name.localeCompare(b.name, 'th'); + }); + + const frag = document.createDocumentFragment(); + rows.forEach((row) => { + const li = document.createElement('li'); + li.className = 'room-lobby-host-console-member-item' + (row.isHost ? ' is-host' : ''); + + const name = document.createElement('span'); + name.className = 'room-lobby-host-console-member-name'; + name.textContent = row.isHost ? (row.name + ' (Host)') : row.name; + li.appendChild(name); + + if (!row.isHost) { + const delBtn = document.createElement('button'); + delBtn.type = 'button'; + delBtn.className = 'room-lobby-host-console-delete-btn'; + delBtn.disabled = !this._isHost(); + delBtn.setAttribute('aria-label', 'ลบสมาชิก ' + row.name); + const delImg = document.createElement('img'); + delImg.src = 'img/03-4-Host-Console/host-console-delete.png'; + delImg.alt = ''; + delImg.decoding = 'async'; + delBtn.appendChild(delImg); + delBtn.addEventListener('click', () => this._kickMember(row)); + li.appendChild(delBtn); + } + frag.appendChild(li); + }); + this.membersListEl.appendChild(frag); + } + + _render() { + const players = this._getPlayersCount(); + const bots = Math.max(0, this.pendingMaxPlayers - players); + if (this.maxValueEl) this.maxValueEl.textContent = String(this.pendingMaxPlayers); + if (this.summaryEl) this.summaryEl.textContent = 'สรุปจำนวน : ' + players + ' ผู้เล่น + ' + bots + ' Bot'; + this._renderMembers(); + + const canEdit = this._isHost(); + if (this.decBtn) this.decBtn.disabled = !canEdit; + if (this.incBtn) this.incBtn.disabled = !canEdit; + if (this.confirmBtn) this.confirmBtn.disabled = !canEdit; + } + + _confirm() { + if (!this._isHost()) return; + maxPlayers = this.pendingMaxPlayers; + updatePlayersHud(); + this.close(); + } + + open() { + if (!this.overlay) return; + this.pendingMaxPlayers = Math.max(2, maxPlayers || 10); + this._render(); + this.overlay.classList.remove('is-hidden'); + this.overlay.setAttribute('aria-hidden', 'false'); + this._syncScale(); + requestAnimationFrame(() => this._syncScale()); + this.closeBtn?.focus(); + } + + close() { + if (!this.overlay) return; + this.overlay.classList.add('is-hidden'); + this.overlay.setAttribute('aria-hidden', 'true'); + } + } + + const hostConsoleOverlay = new HostConsoleOverlay(); + window.hostConsoleOverlay = hostConsoleOverlay; + function refreshHostConsoleOverlayIfOpen() { + if (!hostConsoleOverlay || !hostConsoleOverlay.overlay) return; + if (hostConsoleOverlay.overlay.classList.contains('is-hidden')) return; + hostConsoleOverlay._render(); + } + socket.on('connect', () => { - firstCharacterDefaultPromise.then(() => { - socket.emit('join-space', { spaceId, nickname: nick, characterId: getStoredCharacterId() }, (res) => { + socket.emit('join-space', { spaceId, nickname: nick, characterId: getStoredCharacterId() }, (res) => { if (!res || !res.ok) { var joinErr = (res && res.error) || 'เข้าไม่ได้'; if (/เริ่มคดี|ไม่รับผู้เล่น/.test(joinErr)) { @@ -1435,7 +1597,6 @@ mapBackgroundImg.src = mapData.backgroundImage; mapBackgroundImg.onload = () => { resizeAndDraw(); }; } - refreshLobbyGridImages(); if (!mapData.interactive) mapData.interactive = []; if (!mapData.startGameArea) mapData.startGameArea = []; if (!mapData.quizTrueArea) mapData.quizTrueArea = []; @@ -1445,7 +1606,7 @@ maxPlayers = res.maxPlayers != null ? res.maxPlayers : 10; updatePlayersHud(); - if (wasPageReload() && (hostId === socket.id || peers.size <= 1) && !previewMode) { + if (wasPageReload() && (hostId === socket.id || peers.size <= 1)) { window.location.replace(CREATE_ROOM_URL); return; } @@ -1469,10 +1630,9 @@ const isHost = hostId === socket.id; if (hostOnly) hostOnly.style.display = isHost ? 'flex' : 'none'; if (nonHostMsg) nonHostMsg.style.display = isHost ? 'none' : 'inline'; - updatePreviewClaimHostUi(); if (isHost && playMapSelect) { - fetch(BASE + '/api/maps') + fetch(SERVER + '/api/maps') .then(r => r.json()) .then(list => { playMapSelect.innerHTML = ''; @@ -1505,7 +1665,6 @@ }).catch(() => {}); }, 600); }); - }); }); const LERP = 0.2; @@ -1524,6 +1683,12 @@ redrawLobbyMap(); }); + socket.on('host-console-kicked', (data) => { + var msg = (data && data.message) ? String(data.message) : 'คุณถูก Host นำออกจากห้อง'; + try { alert(msg); } catch (e) { /* ignore */ } + window.location.href = CREATE_ROOM_URL; + }); + socket.on('peer-ready', (data) => { const p = peers.get(data.id); if (p) { p.ready = data.ready; renderPeers(); redrawLobbyMap(); } @@ -1595,7 +1760,7 @@ av.className = 'quiz-sb-avatar'; if (row.characterId) { av.alt = ''; - av.src = BASE + '/img/characters/' + encodeURIComponent(row.characterId) + '_down.png'; + av.src = SERVER + '/img/characters/' + encodeURIComponent(row.characterId) + '_down.png'; } var meta = document.createElement('div'); meta.className = 'quiz-sb-meta'; @@ -1781,7 +1946,7 @@ function updateChatToggleIcon() { const panel = document.querySelector('.room-lobby-chat-peers'); const collapsed = panel && panel.classList.contains('chat-panel-collapsed'); - chatToggleImg.src = collapsed ? BASE + '/img/btn-chat.png' : BASE + '/img/chat-close-btn.png'; + chatToggleImg.src = collapsed ? SERVER + '/img/btn-chat.png' : SERVER + '/img/chat-close-btn.png'; chatCloseBtn.setAttribute('title', collapsed ? 'เปิดแชท' : 'ปิดแชท'); chatCloseBtn.setAttribute('aria-label', collapsed ? 'เปิดแชท' : 'ปิดแชท'); } @@ -1807,7 +1972,7 @@ if (aiChatCloseBtn && aiChatPanel) { function updateAiChatToggleIcon() { const collapsed = aiChatPanel.classList.contains('chat-panel-collapsed'); - if (aiChatToggleImg) aiChatToggleImg.src = collapsed ? BASE + '/img/btn-ai-chatbot.png' : BASE + '/img/chat-close-btn.png'; + if (aiChatToggleImg) aiChatToggleImg.src = collapsed ? MAIN_LOBBY_AI_BTN_ICON : SERVER + '/img/chat-close-btn.png'; if (aiChatToggleImg) aiChatToggleImg.alt = collapsed ? 'เทพความรู้' : 'ปิด'; aiChatCloseBtn.setAttribute('title', collapsed ? 'เปิดแชท AI' : 'ปิดแชท AI'); aiChatCloseBtn.setAttribute('aria-label', aiChatCloseBtn.getAttribute('title')); @@ -1870,7 +2035,7 @@ appendAiMessage(text, false); setAiChatWaiting(true); aiChatInput.value = ''; - fetch(BASE + '/api/ai-chat', { + fetch(SERVER + '/api/ai-chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text, sessionId: socket.id }), @@ -2001,7 +2166,7 @@ async function getIceServers() { if (cachedIceServers) return cachedIceServers; try { - const r = await fetch(BASE + '/api/ice-servers'); + const r = await fetch(SERVER + '/api/ice-servers'); const d = await r.json(); if (d && d.iceServers && d.iceServers.length) cachedIceServers = d.iceServers; else cachedIceServers = defaultIceServers; @@ -2093,10 +2258,9 @@ const isHost = hostId === socket.id; if (hostOnly) hostOnly.style.display = isHost ? 'flex' : 'none'; if (nonHostMsg) nonHostMsg.style.display = isHost ? 'none' : 'inline'; - updatePreviewClaimHostUi(); renderPeers(); if (isHost && playMapSelect) { - fetch(BASE + '/api/maps') + fetch(SERVER + '/api/maps') .then(r => r.json()) .then(list => { playMapSelect.innerHTML = ''; @@ -2114,6 +2278,7 @@ ensureReadyControlEnabled(); updateSuspectHostUi(); updateSuspectFloatingOpenBtn(); + refreshHostConsoleOverlayIfOpen(); }); socket.off('user-left'); @@ -2124,6 +2289,7 @@ renderPeers(); ensureReadyControlEnabled(); redrawLobbyMap(); + refreshHostConsoleOverlayIfOpen(); }); const btnVoice = document.getElementById('btn-voice'); @@ -2133,7 +2299,7 @@ if (!btn) return; const img = btn.querySelector('#btn-voice-icon-img') || btn.querySelector('img'); if (img) { - img.src = micOn ? BASE + '/img/btn-mic-on.png' : BASE + '/img/btn-mic-mute.png'; + img.src = micOn ? SERVER + '/img/btn-mic-on.png' : SERVER + '/img/btn-mic-mute.png'; img.alt = micOn ? 'ปิดเสียง' : 'เปิดเสียง'; } else { btn.textContent = micOn ? '🔇 ปิดเสียง' : '🔊 เปิดเสียง'; @@ -2471,7 +2637,7 @@ const i = parseInt(hit.getAttribute('data-evidence-tab'), 10); if (!Number.isNaN(i)) syncLobbyEvidenceTabUi(i); }); - const LOBBY_RANK_MEDAL_BASE = '/Main-Lobby/IMAGE'; + const LOBBY_RANK_MEDAL_BASE = typeof appPath === 'function' ? appPath('/Main-Lobby/IMAGE') : '/Main-Lobby/IMAGE'; const LOBBY_RANK_MOCK_LEADERS = [ { name: 'Golden L.', score: 9951 }, { name: 'Mixie Kim', score: 9878 }, @@ -2612,6 +2778,22 @@ if (!isPostCaseLobbyRoom()) return; openLobbyRankModal(); }); + document.getElementById('btn-profile-set')?.addEventListener('click', () => { + roomLobbyProfileOverlay.open(); + }); + document.getElementById('btn-setting-host')?.addEventListener('click', () => { + if (hostId !== socket.id) { + alert('เฉพาะ Host เท่านั้น'); + return; + } + hostConsoleOverlay.open(); + }); + document.getElementById('btn-customize')?.addEventListener('click', () => { + roomLobbyProfileOverlay.open(); + }); + document.getElementById('room-lobby-profile-logout')?.addEventListener('click', () => { + window.location.href = CREATE_ROOM_URL; + }); document.getElementById('lobby-rank-backdrop')?.addEventListener('click', () => { closeLobbyRankModal(); }); @@ -2626,10 +2808,12 @@ renderPeers(); ensureReadyControlEnabled(); redrawLobbyMap(); + refreshHostConsoleOverlayIfOpen(); if (localStream && data.id !== socket.id) await createPeerConnection(data.id); }); let troublesomeTickTimer = null; + const troublesomeTimerPausedForTuning = false; function closeTroublesomeOverlay() { const ov = document.getElementById('troublesome-overlay'); if (ov) ov.classList.add('is-hidden'); @@ -2680,7 +2864,7 @@ if (suspectPickOverlayOpen) scheduleSuspectPickScale(); }); } - document.querySelectorAll('#suspect-cards-row img, .suspect-pick-title-img, #suspect-btn-start img').forEach(function (img) { + document.querySelectorAll('#suspect-cards-row img, .suspect-pick-title-img, #suspect-btn-start img, #suspect-btn-accuse img').forEach(function (img) { img.addEventListener('load', function () { if (suspectPickOverlayOpen) scheduleSuspectPickScale(); }); @@ -2751,10 +2935,15 @@ if (!suspectPickOverlayOpen) return; const isHost = hostId === socket.id; const actions = document.getElementById('suspect-pick-actions'); + const accuseBtn = document.getElementById('suspect-btn-accuse'); if (actions) { actions.classList.toggle('suspect-pick-actions--visible', isHost); actions.setAttribute('aria-hidden', isHost ? 'false' : 'true'); } + if (accuseBtn) { + accuseBtn.classList.toggle('is-hidden', !isHost); + accuseBtn.setAttribute('aria-hidden', isHost ? 'false' : 'true'); + } document.querySelectorAll('.suspect-card').forEach((c) => { c.classList.toggle('suspect-card--host', isHost); }); @@ -2805,6 +2994,10 @@ if (troublesomeTickTimer) clearInterval(troublesomeTickTimer); let left = seconds; if (secEl) secEl.textContent = String(left); + if (troublesomeTimerPausedForTuning) { + troublesomeTickTimer = null; + return; + } troublesomeTickTimer = setInterval(() => { left -= 1; if (secEl) secEl.textContent = String(Math.max(0, left)); @@ -2868,6 +3061,18 @@ }); }); + document.getElementById('suspect-btn-accuse')?.addEventListener('click', () => { + if (!suspectPickOverlayOpen || hostId !== socket.id) return; + const pickNumber = Number.isFinite(suspectSelectedIndex) ? (suspectSelectedIndex + 1) : 1; + appendLobbySystemChat('— Host ชี้ตัวคนร้ายหมายเลข ' + pickNumber); + socket.emit('suspect-pick-start', {}, (res) => { + if (res && res.ok) return; + const err = (res && res.error) ? String(res.error) : 'ชี้ตัวคนร้ายไม่สำเร็จ'; + appendLobbySystemChat('— ' + err); + try { console.warn('suspect-btn-accuse', res); } catch (e2) { /* ignore */ } + }); + }); + document.getElementById('suspect-pick-close')?.addEventListener('click', () => { if (!suspectPickOverlayOpen) return; closeSuspectOverlay(); @@ -2881,7 +3086,7 @@ function applyRoomLobbyBTransition(data) { const mid = (data && data.mapId) ? String(data.mapId).trim() : ''; if (!mid) return; - fetch(BASE + '/api/maps/' + encodeURIComponent(mid)) + fetch(SERVER + '/api/maps/' + encodeURIComponent(mid)) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (json) { if (!json) { @@ -2920,7 +3125,6 @@ } else { mapBackgroundImg = null; } - refreshLobbyGridImages(); lobbyPath = []; try { window.__detectiveLobbyMeta = { @@ -3004,7 +3208,7 @@ lobbyPath = []; applyQuizGameStartInLobby(data); if (qMid) { - fetch(BASE + '/api/maps/' + encodeURIComponent(qMid)) + fetch(SERVER + '/api/maps/' + encodeURIComponent(qMid)) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (json) { if (json) { @@ -3022,7 +3226,6 @@ } else { mapBackgroundImg = null; } - refreshLobbyGridImages(); syncLobbyBUiChrome(); try { var u = new URL(window.location.href); @@ -3075,6 +3278,18 @@ e.preventDefault(); } if (e.code === 'Escape' && !e.repeat && !isChatFocused()) { + const hostConsoleOv = document.getElementById('host-console-overlay'); + if (hostConsoleOv && !hostConsoleOv.classList.contains('is-hidden')) { + hostConsoleOverlay.close(); + e.preventDefault(); + return; + } + const profileOv = document.getElementById('room-lobby-profile-overlay'); + if (profileOv && !profileOv.classList.contains('is-hidden')) { + roomLobbyProfileOverlay.close(); + e.preventDefault(); + return; + } const evOv = document.getElementById('lobby-evidence-overlay'); if (evOv && !evOv.classList.contains('is-hidden')) { closeLobbyEvidenceModal(); @@ -3211,28 +3426,10 @@ const btnLeaveLobby = document.getElementById('btn-leave-lobby'); if (btnLeaveLobby) { btnLeaveLobby.addEventListener('click', () => { - if (previewMode && editorEmbedReturn) { - var mid = (params.get('map') || '').trim(); - window.location.href = mid - ? BASE + '/editor.html?id=' + encodeURIComponent(mid) + '&embed=1' - : BASE + '/editor.html?embed=1'; - return; - } window.location.href = CREATE_ROOM_URL; }); } - var btnPreviewClaimHost = document.getElementById('btn-preview-claim-host'); - if (btnPreviewClaimHost) { - btnPreviewClaimHost.addEventListener('click', function () { - socket.emit('preview-claim-host', function (res) { - if (res && res.ok === false && res.error) { - try { alert(res.error); } catch (e1) { /* ignore */ } - } - }); - }); - } - window.addEventListener('resize', resizeAndDraw); const wrapEl = document.getElementById('lobby-map-wrap'); if (wrapEl && typeof ResizeObserver !== 'undefined') { diff --git a/www/html/Game/public/room-lobby.html b/www/html/Game/public/room-lobby.html index b832455..446bbfa 100644 --- a/www/html/Game/public/room-lobby.html +++ b/www/html/Game/public/room-lobby.html @@ -4,7 +4,10 @@