513 lines
44 KiB
HTML
513 lines
44 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="th">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<meta name="robots" content="noindex, nofollow">
|
||
<meta name="theme-color" content="#0b0d14">
|
||
<title>Admin — JD JUSTICE DIVERS</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="admin.css?v=14">
|
||
</head>
|
||
<body>
|
||
<a class="skip-link" href="#admin-main">ข้ามไปเนื้อหา</a>
|
||
<div class="admin-bg" aria-hidden="true"></div>
|
||
<div class="admin-shell">
|
||
<header class="admin-header-bar">
|
||
<div class="admin-brand">
|
||
<span class="admin-brand-mark" aria-hidden="true"></span>
|
||
<div class="admin-brand-text">
|
||
<h1 class="admin-title">ศูนย์ควบคุม Admin</h1>
|
||
<p class="admin-tagline">JD JUSTICE DIVERS</p>
|
||
</div>
|
||
</div>
|
||
<div class="admin-top-actions" id="top-actions" hidden>
|
||
<a href="/Admin/justice-git.html" class="btn btn-ghost" style="text-decoration:none;margin-right:0.35rem;">อัป Git (justice)</a>
|
||
<span class="admin-user" id="admin-user-label"></span>
|
||
<button type="button" class="btn btn-ghost btn-logout" id="btn-logout">ออกจากระบบ</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main id="admin-main" class="admin-main-flow">
|
||
<section id="panel-setup" class="card card--auth" hidden>
|
||
<h2>ตั้งค่าครั้งแรก</h2>
|
||
<p class="muted">สร้างบัญชีแอดมินหลัก (super) — ใช้ชื่อผู้ใช้ <strong>admin</strong></p>
|
||
<form id="form-setup" class="form-grid">
|
||
<label>รหัสผ่าน (อย่างน้อย 8 ตัว)
|
||
<input type="password" name="password" required minlength="8" autocomplete="new-password">
|
||
</label>
|
||
<label>ยืนยันรหัสผ่าน
|
||
<input type="password" name="password2" required minlength="8" autocomplete="new-password">
|
||
</label>
|
||
<button type="submit" class="btn btn-primary">สร้างแอดมิน</button>
|
||
</form>
|
||
<p id="setup-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="panel-login" class="card card--auth" hidden>
|
||
<h2>เข้าสู่ระบบ Admin</h2>
|
||
<form id="form-login" class="form-grid">
|
||
<label>ชื่อผู้ใช้
|
||
<input type="text" name="username" autocomplete="username" required>
|
||
</label>
|
||
<label>รหัสผ่าน
|
||
<span class="password-input-wrap">
|
||
<input type="password" name="password" id="login-password" autocomplete="current-password" required>
|
||
<button type="button" class="btn-password-toggle" id="login-password-toggle" aria-pressed="false" aria-label="แสดงรหัสผ่าน" title="แสดง/ซ่อนรหัสผ่าน">แสดง</button>
|
||
</span>
|
||
</label>
|
||
<button type="submit" class="btn btn-primary">เข้าสู่ระบบ</button>
|
||
</form>
|
||
<p id="login-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<div id="panel-app" class="admin-app" hidden>
|
||
<nav class="admin-tabs" role="tablist" aria-label="เมนูหลัก Admin">
|
||
<button type="button" class="tab" data-tab="change-password" role="tab" id="tab-change-password" aria-selected="false" aria-controls="tab-panel-change-password"><span class="tab-label">รหัสผ่าน</span><span class="tab-desc tab-desc--wide">เปลี่ยนรหัสของคุณ · กดซ้ำเพื่อปิด</span></button>
|
||
<button type="button" class="tab is-active" data-tab="oauth" role="tab" id="tab-oauth" aria-selected="true" aria-controls="tab-panel-oauth"><span class="tab-label">OAuth</span><span class="tab-desc">Facebook / Google</span></button>
|
||
<button type="button" class="tab" data-tab="map-editor" role="tab" id="tab-map-editor" aria-controls="tab-panel-map-editor"><span class="tab-label">Editor</span><span class="tab-desc">แผนที่ฉาก</span></button>
|
||
<button type="button" class="tab" data-tab="quiz" role="tab" id="tab-quiz" aria-controls="tab-panel-quiz"><span class="tab-label">คำถามเกม</span><span class="tab-desc">ถูก/ผิด · เวลา</span></button>
|
||
<button type="button" class="tab" data-tab="quiz-carry" role="tab" id="tab-quiz-carry" aria-controls="tab-panel-quiz-carry"><span class="tab-label">คำถามหลายข้อ</span><span class="tab-desc">หยิบมาวาง · A–D</span></button>
|
||
<button type="button" class="tab" data-tab="quiz-battle" role="tab" id="tab-quiz-battle" aria-controls="tab-panel-quiz-battle"><span class="tab-label">Quiz Battle</span><span class="tab-desc">โดม · A B C · หมวด</span></button>
|
||
<button type="button" class="tab" data-tab="jump-survive" role="tab" id="tab-jump-survive" aria-controls="tab-panel-jump-survive"><span class="tab-label">กระโดดให้รอด</span><span class="tab-desc">ความสูงกระโดด · timing</span></button>
|
||
<button type="button" class="tab" data-tab="game-timing" role="tab" id="tab-game-timing" aria-controls="tab-panel-game-timing"><span class="tab-label">เวลาเกม</span><span class="tab-desc">พรมแดง · tick & laser</span></button>
|
||
<button type="button" class="tab" data-tab="stack-game" role="tab" id="tab-stack-game" aria-controls="tab-panel-stack-game"><span class="tab-label">Stack</span><span class="tab-desc">สวิง · ความยาวแท่ง</span></button>
|
||
<button type="button" class="tab" data-tab="characters" role="tab" id="tab-characters" aria-controls="tab-panel-characters"><span class="tab-label">ตัวละคร</span><span class="tab-desc">อัปโหลด · เลือกใช้ในเกม</span></button>
|
||
<button type="button" class="tab" data-tab="accounts" role="tab" id="tab-accounts" aria-controls="tab-panel-accounts"><span class="tab-label">ผู้ใช้</span><span class="tab-desc">บัญชี & COINS</span></button>
|
||
<button type="button" class="tab" data-tab="admins" role="tab" id="tab-admins" aria-controls="tab-panel-admins"><span class="tab-label">แอดมิน</span><span class="tab-desc">สิทธิ์ระบบ</span></button>
|
||
</nav>
|
||
|
||
<section id="tab-panel-change-password" class="tab-panel card card--password" hidden role="tabpanel" aria-labelledby="tab-change-password">
|
||
<h2 class="card-title-icon">เปลี่ยนรหัสผ่านของคุณ</h2>
|
||
<p class="muted">ใช้รหัสปัจจุบันยืนยันก่อนตั้งรหัสใหม่ (ทุกแอดมินใช้ได้) · <strong>กดแท็บ "รหัสผ่าน" อีกครั้งเพื่อปิด</strong>และกลับไป OAuth</p>
|
||
<form id="form-change-password" class="form-grid">
|
||
<label>รหัสผ่านปัจจุบัน
|
||
<input type="password" name="currentPassword" required autocomplete="current-password">
|
||
</label>
|
||
<label>รหัสผ่านใหม่ (อย่างน้อย 8 ตัว)
|
||
<input type="password" name="newPassword" required minlength="8" autocomplete="new-password">
|
||
</label>
|
||
<label>ยืนยันรหัสผ่านใหม่
|
||
<input type="password" name="newPassword2" required minlength="8" autocomplete="new-password">
|
||
</label>
|
||
<button type="submit" class="btn btn-primary">บันทึกรหัสใหม่</button>
|
||
</form>
|
||
<p id="password-self-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-oauth" class="tab-panel card" role="tabpanel" aria-labelledby="tab-oauth">
|
||
<h2>Token & OAuth (Facebook / Google)</h2>
|
||
<p class="muted">เก็บ App ID / Client ID / Secret และ Redirect URI สำหรับ Login — หน้า Login จะดึงเฉพาะค่าที่ไม่เป็นความลับผ่าน <code>oauth-public.php</code></p>
|
||
<form id="form-oauth" class="form-grid form-oauth">
|
||
<fieldset>
|
||
<legend>Facebook Login</legend>
|
||
<label>App ID
|
||
<input type="text" name="facebookAppId" autocomplete="off" placeholder="ตัวเลขจาก Meta Developer">
|
||
</label>
|
||
<label>App Secret
|
||
<input type="password" name="facebookAppSecret" autocomplete="off" placeholder="ความลับ — ไม่ส่งให้ลูกค้า">
|
||
</label>
|
||
<label>Redirect URI
|
||
<input type="url" name="facebookRedirectUri" placeholder="https://your.domain/Login/facebook-callback.html">
|
||
</label>
|
||
</fieldset>
|
||
<fieldset>
|
||
<legend>Google Sign-In</legend>
|
||
<label>Client ID
|
||
<input type="text" name="googleClientId" autocomplete="off" placeholder="xxx.apps.googleusercontent.com">
|
||
</label>
|
||
<label>Client Secret
|
||
<input type="password" name="googleClientSecret" autocomplete="off">
|
||
</label>
|
||
<label>Redirect URI
|
||
<input type="url" name="googleRedirectUri" placeholder="https://your.domain/Login/google-callback.html">
|
||
</label>
|
||
</fieldset>
|
||
<button type="submit" class="btn btn-primary">บันทึกการตั้งค่า OAuth</button>
|
||
</form>
|
||
<p id="oauth-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-map-editor" class="tab-panel card admin-editor-panel" hidden role="tabpanel" aria-labelledby="tab-map-editor">
|
||
<div class="admin-editor-panel-head">
|
||
<div class="admin-editor-panel-intro">
|
||
<h2>Editor ฉาก (Lobby / ZEP / กบ / ตอบคำถาม)</h2>
|
||
<p class="muted">สร้างและบันทึกแผนที่ — ใช้รหัสฉากตอนสร้างห้องในล็อบบี้ · คลิกซ้ายวาด / คลิกขวาลบ · ห้องโถง: พื้นที่เริ่มเกม (ส้ม) · <strong>เกมตอบคำถาม</strong>: วาดโซนถูก/ผิด + พื้นที่โชว์คำถาม (ทอง) — ข้อความคำถามจากแท็บ <strong>คำถามเกม</strong> · <strong>เลื่อนแถบเครื่องมือด้านบน</strong>ได้แยกจากพื้นที่วาด</p>
|
||
</div>
|
||
<button type="button" class="btn btn-ghost btn-editor-fs" id="btn-map-editor-fullscreen" aria-pressed="false" title="ขยายเต็มหน้าจอ (Esc ออก)">เต็มจอ</button>
|
||
</div>
|
||
<iframe class="admin-editor-iframe" id="admin-map-editor-frame" title="Editor ฉาก" src="/Game/editor.html?embed=1"></iframe>
|
||
</section>
|
||
|
||
<section id="tab-panel-quiz" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-quiz">
|
||
<h2>คำถามเกม (ถูก / ผิด)</h2>
|
||
<p class="muted">ใช้กับฉากประเภท <strong>เกมตอบคำถาม</strong> ใน Editor — สุ่มสูงสุด <strong>10 ข้อ</strong> ต่อรอบ · เวลาเป็นวินาที (เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/quiz-settings.json</code>)</p>
|
||
<fieldset class="quiz-timing-fieldset">
|
||
<legend>เวลาในเกม</legend>
|
||
<div class="form-grid form-inline quiz-timing-grid">
|
||
<label>อ่านคำถาม (วินาที) <input type="number" id="quiz-read-sec" min="1" max="300" step="1" value="10"></label>
|
||
<label>เดินไปยืนในโซนตอบ (วินาที) <input type="number" id="quiz-answer-sec" min="1" max="300" step="1" value="5"></label>
|
||
<label>พักระหว่างข้อ (วินาที) <input type="number" id="quiz-between-sec" min="0" max="120" step="1" value="3"></label>
|
||
</div>
|
||
</fieldset>
|
||
<h3 class="admin-subheading">รายการคำถาม</h3>
|
||
<p class="muted">แต่ละข้อ: ข้อความ + คำตอบที่<strong>ถูกต้อง</strong> (ถูก=จริง / ผิด=เท็จ)</p>
|
||
<div id="quiz-admin-questions-list" class="quiz-admin-questions-list"></div>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-ghost" id="btn-quiz-admin-add">+ เพิ่มคำถาม</button>
|
||
<button type="button" class="btn btn-primary" id="btn-quiz-admin-save">บันทึกทั้งหมด</button>
|
||
</div>
|
||
<p id="quiz-settings-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-quiz-carry" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-quiz-carry">
|
||
<h2>คำถามหลายตัวเลือก (หยิบมาวาง)</h2>
|
||
<p class="muted">ใช้กับฉากประเภท <strong>ตอบคำถาม — หยิบคำตอบมาวางกลาง</strong> ใน Editor — ผู้เล่นหยิบตัวเลือกไปวางโซนกลาง · ถ้ามีรายการที่นี่ เกมจะใช้<strong>เฉพาะชุดนี้</strong> (ไม่ผสมกับคำถามถูก/ผิดแท็บคำถามเกม) · เก็บที่ <code>/Game/data/quiz-settings.json</code> ฟิลด์ <code>carryQuestions</code></p>
|
||
<p class="muted" style="margin-top:-0.35rem">อย่างน้อย <strong>2 ตัวเลือก</strong>ต่อข้อ (สูงสุด 6) · เวลายังตั้งที่แท็บ <strong>คำถามเกม</strong>ได้ (ใช้ร่วมกับโหมดถามตอบอื่น)</p>
|
||
<h3 class="admin-subheading">รายการคำถาม</h3>
|
||
<div id="quiz-carry-admin-list" class="quiz-carry-admin-list"></div>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-ghost" id="btn-quiz-carry-add">+ เพิ่มคำถาม</button>
|
||
<button type="button" class="btn btn-primary" id="btn-quiz-carry-save">บันทึกชุดหลายตัวเลือก</button>
|
||
</div>
|
||
<p id="quiz-carry-settings-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-quiz-battle" class="tab-panel card tab-panel-quiz-battle" hidden role="tabpanel" aria-labelledby="tab-quiz-battle">
|
||
<h2>Quiz Battle — คำถาม 3 ตัวเลือก (สไตล์โดม)</h2>
|
||
<p class="muted">สร้างคำถามแบบ <strong>A / B / C</strong> แยกตาม<strong>หมวด</strong>ให้สอดคล้องธีม <a href="https://srv1361159.hstgr.cloud/Quiz-Battle/" target="_blank" rel="noopener noreferrer">Quiz Battle</a> · เก็บที่ <code>/Game/data/quiz-settings.json</code> ฟิลด์ <code>battleQuizMcq</code> · ตัวอย่าง UI ด้านขวา (โดม + ป๊อปอัป) จำลองจากรีเฟอเรนซ์ในเกม</p>
|
||
<p class="muted" style="margin-top:-0.35rem">เวลาอ่าน/ตอบใช้ร่วมกับแท็บ <strong>คำถามเกม</strong>ได้เมื่อนำไปผูกโหมดเล่นในอนาคต · <em>English:</em> MCQ with category; dome + modal preview for art direction.</p>
|
||
<div class="qb-admin-layout">
|
||
<div class="qb-admin-form-col">
|
||
<div class="qb-admin-toolbar">
|
||
<label class="qb-filter-label">กรองรายการตามหมวด
|
||
<select id="qb-battle-filter-cat" class="qb-battle-filter-cat" aria-label="กรองหมวด">
|
||
<option value="">ทุกหมวด</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<h3 class="admin-subheading">รายการคำถาม</h3>
|
||
<p class="muted" style="margin:-0.25rem 0 0.5rem;font-size:0.88rem">ก่อนกดบันทึก: ต้องมี<strong>ข้อความคำถาม</strong>และ<strong>ตัวเลือก A B C ครบทั้งสามช่อง</strong>ในแต่ละข้อที่ต้องการเก็บ · ถ้าช่องว่าง ระบบจะไม่บันทึกข้อนั้นและจะแจ้งเตือน</p>
|
||
<div id="qb-battle-admin-list" class="qb-battle-admin-list"></div>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-ghost" id="btn-qb-battle-add">+ เพิ่มคำถาม</button>
|
||
<button type="button" class="btn btn-primary" id="btn-qb-battle-save">บันทึกชุด Quiz Battle</button>
|
||
</div>
|
||
<p id="qb-battle-settings-msg" class="msg" role="status"></p>
|
||
</div>
|
||
<div class="qb-preview-col" aria-label="ตัวอย่าง UI โดมและป๊อปอัป">
|
||
<h3 class="admin-subheading qb-preview-heading">ตัวอย่าง (ตามรีเฟอเรนซ์)</h3>
|
||
<div class="qb-preview-toolbar">
|
||
<span class="qb-preview-toolbar-label">โหมดดูตัวอย่าง</span>
|
||
<div class="qb-preview-mode-btns" role="group" aria-label="โหมดตัวอย่าง">
|
||
<button type="button" class="btn btn-ghost btn-sm qb-preview-mode-btn is-active" data-qb-mode="question">คำถาม</button>
|
||
<button type="button" class="btn btn-ghost btn-sm qb-preview-mode-btn" data-qb-mode="revealed">หลังเลือกตอบ</button>
|
||
<button type="button" class="btn btn-ghost btn-sm qb-preview-mode-btn" data-qb-mode="summary">สรุปผล</button>
|
||
</div>
|
||
</div>
|
||
<div class="qb-preview-stage" id="qb-preview-stage">
|
||
<div class="qb-preview-hud">
|
||
<div class="qb-hud-left">
|
||
<span class="qb-hud-icon" aria-hidden="true">💻</span>
|
||
<span class="qb-hud-cat" id="qb-preview-hud-cat">อาชญากรรมออนไลน์</span>
|
||
</div>
|
||
<div class="qb-hud-center">PLAYERS : —</div>
|
||
<div class="qb-hud-right">SCORE : —</div>
|
||
</div>
|
||
<div class="qb-preview-floor">
|
||
<div class="qb-path"></div>
|
||
<div class="qb-dome-scene">
|
||
<div class="qb-dome-float">
|
||
<div class="qb-dome-qbadge" aria-hidden="true">?</div>
|
||
<div class="qb-dome-chevron" aria-hidden="true"></div>
|
||
</div>
|
||
<div class="qb-dome-glass">
|
||
<div class="qb-dome-scanlines" aria-hidden="true"></div>
|
||
<div class="qb-dome-pedestal"></div>
|
||
<div class="qb-dome-lock" aria-hidden="true">🔒</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="qb-modal-layer" id="qb-modal-layer">
|
||
<div class="qb-cyber-modal qb-cyber-modal--question" id="qb-cyber-modal-question">
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--tl" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--tr" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--bl" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--br" aria-hidden="true"></div>
|
||
<button type="button" class="qb-cyber-close" tabindex="-1" aria-hidden="true">×</button>
|
||
<div class="qb-cyber-title-tab">QUESTION #<span id="qb-preview-qnum">1</span></div>
|
||
<p class="qb-cyber-qtext" id="qb-preview-modal-q">การกระทำใดผิดกฎหมายไซเบอร์?</p>
|
||
<div class="qb-cyber-choices" id="qb-preview-choices">
|
||
<div class="qb-cyber-choice" data-slot="0"><span class="qb-cyber-choice-label">A</span><span class="qb-cyber-choice-txt" id="qb-preview-c0">ตัวอย่างตัวเลือก A</span><span class="qb-cyber-choice-mark" aria-hidden="true"></span></div>
|
||
<div class="qb-cyber-choice" data-slot="1"><span class="qb-cyber-choice-label">B</span><span class="qb-cyber-choice-txt" id="qb-preview-c1">ตัวอย่างตัวเลือก B</span><span class="qb-cyber-choice-mark" aria-hidden="true"></span></div>
|
||
<div class="qb-cyber-choice" data-slot="2"><span class="qb-cyber-choice-label">C</span><span class="qb-cyber-choice-txt" id="qb-preview-c2">ตัวอย่างตัวเลือก C</span><span class="qb-cyber-choice-mark" aria-hidden="true"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="qb-cyber-modal qb-cyber-modal--summary" id="qb-cyber-modal-summary" hidden>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--tl" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--tr" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--bl" aria-hidden="true"></div>
|
||
<div class="qb-cyber-bracket qb-cyber-bracket--br" aria-hidden="true"></div>
|
||
<button type="button" class="qb-cyber-close" tabindex="-1" aria-hidden="true">×</button>
|
||
<div class="qb-summary-header">Completed</div>
|
||
<p class="qb-summary-msg">เย่! คุณตอบคำถามครบทุกข้อแล้ว มาสรุปคะแนนกัน</p>
|
||
<div class="qb-summary-stats">
|
||
<div class="qb-summary-card">
|
||
<span class="qb-summary-card-icon" aria-hidden="true">✓</span>
|
||
<span class="qb-summary-card-label">Correct</span>
|
||
<span class="qb-summary-card-val qb-summary-card-val--green">10</span>
|
||
</div>
|
||
<div class="qb-summary-card">
|
||
<span class="qb-summary-card-icon" aria-hidden="true">🕐</span>
|
||
<span class="qb-summary-card-label">Time Taken</span>
|
||
<span class="qb-summary-card-val qb-summary-card-val--cyan">27 m 33 s</span>
|
||
</div>
|
||
<div class="qb-summary-card">
|
||
<span class="qb-summary-card-icon" aria-hidden="true">🏆</span>
|
||
<span class="qb-summary-card-label">My Rank</span>
|
||
<span class="qb-summary-card-val qb-summary-card-val--gold">11</span>
|
||
</div>
|
||
</div>
|
||
<div class="qb-summary-footer-btn">ดูรายละเอียดเพิ่มเติม</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="tab-panel-jump-survive" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-jump-survive">
|
||
<h2>กระโดดให้รอด — ตั้งค่าเกม</h2>
|
||
<p class="muted">คนละโหมดกับ <strong>พรมแดง (Gauntlet)</strong> · เก็บที่ <code>/Game/data/game-timing.json</code> ฟิลด์ <code>jumpSurviveJumpHeightMult</code> · ผู้เล่นได้ค่าใหม่เมื่อโหลดหน้าเล่น / sync timing จากเซิร์ฟเวอร์ · ถ้า API 404 ให้รีสตาร์ท Node ที่รัน <code>Game/server.js</code></p>
|
||
<fieldset class="quiz-timing-fieldset">
|
||
<legend>ความสูงกระโดด</legend>
|
||
<div class="form-grid form-inline quiz-timing-grid">
|
||
<label title="1 = สูงเท่าความสูงตัวละครจากแมป · 1.5 = สูงกว่าตัวครึ่งหนึ่ง">ทวีคูณความสูงกระโดด × ความสูงตัว <input type="number" id="jump-survive-height-mult" min="0.5" max="4" step="0.05" value="1.5"></label>
|
||
</div>
|
||
</fieldset>
|
||
<p class="muted" style="margin-top:-0.25rem;margin-bottom:0.65rem">ใช้กับฉากประเภท <strong>กระโดดให้รอด</strong> เท่านั้น · ถ้าในแมปตั้ง <code>jumpSurviveJumpImpulse</code> > 0 จะใช้ค่านั้นแทน (override) · <em>English:</em> Jump height multiplier vs character height; per-map impulse overrides.</p>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-primary" id="btn-jump-survive-save">บันทึก</button>
|
||
</div>
|
||
<p id="jump-survive-timing-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-game-timing" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-game-timing">
|
||
<h2>เวลาเกม — พรมแดงสุดท้าย (Gauntlet)</h2>
|
||
<p class="muted">เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/game-timing.json</code> · หลังบันทึกห้องที่กำลังเล่นจะปรับความถี่ tick ทันที · ค่าจะส่งไปยังผู้เล่นผ่าน <code>gauntlet-sync</code> · <strong>จำกัดเวลา</strong>นับจากตอนโฮสต์เริ่มเกมพรมแดง — หมดเวลาแล้วเกมจบและส่งทุกคนกลับฉากล็อบบี้บนเซิร์ฟเวอร์ · ถ้าขึ้น Not Found ให้<strong>รีสตาร์ท Node</strong>ที่รัน <code>Game/server.js</code> (เช่น <code>pm2 restart</code>) หลัง deploy · <strong>กระโดดให้รอด</strong>ตั้งที่แท็บชื่อเดียวกับโหมด</p>
|
||
<fieldset class="quiz-timing-fieldset">
|
||
<legend>พารามิเตอร์</legend>
|
||
<div class="form-grid form-inline quiz-timing-grid">
|
||
<label>ความถี่ tick (มิลลิวินาที) <input type="number" id="game-timing-tick-ms" min="80" max="800" step="10" value="220"></label>
|
||
<label>ระยะกระโดด (tick) <input type="number" id="game-timing-jump-ticks" min="4" max="40" step="1" value="16"></label>
|
||
<label>จำกัดเวลาเกม (วินาที) <input type="number" id="game-timing-limit-sec" min="0" max="7200" step="10" value="0" title="0 = เล่นได้ไม่จำกัดเวลา"></label>
|
||
</div>
|
||
</fieldset>
|
||
<p class="muted" style="margin-top:0.35rem">ค่าเวลาเกม: ตั้ง <strong>0</strong> = ไม่จำกัด · ตั้งอย่างน้อย <strong>10</strong> วินาทีถ้าต้องการจับเวลา (สูงสุด 7200 = 2 ชม.) · Per-round time limit; <strong>0</strong> = no limit, else min <strong>10</strong>s, max <strong>7200</strong>s.</p>
|
||
<fieldset class="quiz-timing-fieldset gauntlet-timing-fieldset">
|
||
<legend>รูปอุปสรรค์ Gauntlet / Obstacle & laser art</legend>
|
||
<div class="gauntlet-editor-shell">
|
||
<header class="gauntlet-editor-toolbar">
|
||
<h3 class="gauntlet-editor-toolbar-title">พรมแดงสุดท้าย — อุปสรรค์ & เลเซอร์</h3>
|
||
<div class="gauntlet-editor-toolbar-actions">
|
||
<a class="gauntlet-editor-link" href="/Game/editor.html?id=mnn93hpi&embed=1" target="_blank" rel="noopener noreferrer">Editor ฉาก (ฝัง)</a>
|
||
<a class="gauntlet-editor-link gauntlet-editor-link-secondary" href="/Game/editor.html?id=mnn93hpi" target="_blank" rel="noopener noreferrer">Editor เต็มหน้า</a>
|
||
</div>
|
||
</header>
|
||
<p class="gauntlet-editor-legend">เลือกรูปจากคลัง → กด <strong>Lane</strong> / <strong>Laser</strong> ตามชิ้นส่วน (หัวบน · เส้น · ท้ายล่าง) แบบในเกม · โครง UI อ้างอิง <a href="/Game/editor.html?id=mnn93hpi&embed=1" target="_blank" rel="noopener noreferrer">Editor ฉาก</a></p>
|
||
<div class="gauntlet-editor-panels">
|
||
<div class="gauntlet-asset-library gauntlet-editor-panel">
|
||
<h4 class="gauntlet-editor-panel-title">คลังรูป · Asset library</h4>
|
||
<p class="muted gauntlet-asset-library-intro">ลากวางหรือคลิกโซนอัปโหลด (png / jpg / gif / webp · สูงสุด 4 MB) · กด <strong>Lane</strong> / <strong>Laser บน·ล่าง·เส้น</strong> เพื่อใส่ในชุดที่ส่งเข้าเกม</p>
|
||
<div id="gauntlet-asset-drop" class="gauntlet-asset-drop" role="button" tabindex="0" aria-label="วางรูปหรือคลิกเพื่อเลือกไฟล์">
|
||
<span class="gauntlet-asset-drop-text">ลากรูปมาวางที่นี่ หรือคลิกเลือกไฟล์<br><small class="muted">Drop images here or click to browse</small></span>
|
||
</div>
|
||
<input type="file" id="gauntlet-asset-file-input" accept="image/png,image/jpeg,image/jpg,image/gif,image/webp" multiple hidden>
|
||
<p id="gauntlet-asset-upload-msg" class="msg gauntlet-asset-upload-msg" role="status"></p>
|
||
<div id="gauntlet-asset-list" class="gauntlet-asset-list" aria-live="polite"></div>
|
||
</div>
|
||
<div class="game-timing-assigned-visuals gauntlet-editor-panel gauntlet-editor-panel--stage">
|
||
<div class="game-timing-visual-block game-timing-visual-block--lanes">
|
||
<h3 class="game-timing-visual-title">Lane — สิ่งกีดขวางบนพรมแดง</h3>
|
||
<p class="muted game-timing-visual-hint">กด <strong>Lane</strong> ที่การ์ดในคลังเพื่อเพิ่ม · เรียงลำดับด้วยลูกศร · ลบออกจากรายการที่นี่ (ไม่ลบไฟล์ในคลัง)</p>
|
||
<div id="game-timing-lane-visual-list" class="game-timing-visual-grid" role="list" aria-label="รูป lane ที่ใช้"></div>
|
||
<textarea id="game-timing-lane-urls" hidden aria-hidden="true" tabindex="-1" spellcheck="false"></textarea>
|
||
</div>
|
||
<div class="game-timing-visual-block game-timing-visual-block--laser">
|
||
<h3 class="game-timing-visual-title">Laser — คอลัมน์แนวตั้ง (หัว · เส้น · ท้าย)</h3>
|
||
<p class="muted game-timing-visual-hint">กด <strong>Laser บน / ล่าง / เส้น</strong> ที่คลัง · ดูหรือล้างช่องด้านล่าง · เส้นกลาง = tile ซ้ำแนวตั้งในเกม</p>
|
||
<div class="game-timing-laser-slots-grid">
|
||
<div class="game-timing-laser-slot">
|
||
<div class="game-timing-laser-slot-label">หัวบน · Top emitter</div>
|
||
<div id="game-timing-laser-top-preview" class="game-timing-laser-preview" aria-label="ตัวอย่าง laser บน"></div>
|
||
<input type="hidden" id="game-timing-laser-top-url" value="">
|
||
<div class="game-timing-laser-slot-actions">
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-top-view">ดู</button>
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-top-clear">ล้าง</button>
|
||
</div>
|
||
</div>
|
||
<div class="game-timing-laser-slot game-timing-laser-slot--beam">
|
||
<div class="game-timing-laser-slot-label">เส้นกลาง (tile) · Beam</div>
|
||
<div id="game-timing-laser-line-preview" class="game-timing-laser-preview" aria-label="ตัวอย่างเส้น laser"></div>
|
||
<input type="hidden" id="game-timing-laser-line-url" value="">
|
||
<div class="game-timing-laser-slot-actions">
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-line-view">ดู</button>
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-line-clear">ล้าง</button>
|
||
</div>
|
||
</div>
|
||
<div class="game-timing-laser-slot">
|
||
<div class="game-timing-laser-slot-label">ท้ายล่าง · Bottom</div>
|
||
<div id="game-timing-laser-bottom-preview" class="game-timing-laser-preview" aria-label="ตัวอย่าง laser ล่าง"></div>
|
||
<input type="hidden" id="game-timing-laser-bottom-url" value="">
|
||
<div class="game-timing-laser-slot-actions">
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-bottom-view">ดู</button>
|
||
<button type="button" class="btn btn-ghost btn-sm" id="btn-game-timing-laser-bottom-clear">ล้าง</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-grid form-inline quiz-timing-grid game-timing-laser-colors-row gauntlet-editor-laser-colors">
|
||
<label>สีเติมคอลัมน์ laser (CSS) <input type="text" id="game-timing-laser-fill" maxlength="100" placeholder="rgba(239,68,68,0.35)" spellcheck="false"></label>
|
||
<label>สีเส้นกรอบ laser <input type="text" id="game-timing-laser-stroke" maxlength="100" placeholder="rgba(252,165,165,0.95)" spellcheck="false"></label>
|
||
<label>ความหนาเส้น (px, 0 = ไม่กรอบ) <input type="number" id="game-timing-laser-line-width" min="0" max="24" step="1" value="2"></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</fieldset>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-primary" id="btn-game-timing-save">บันทึก</button>
|
||
</div>
|
||
<p id="game-timing-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-stack-game" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-stack-game">
|
||
<h2>Stack — สลับจังหวะลงตึก</h2>
|
||
<p class="muted">ปรับความเร็วสวิง (crane) และ<strong>ความยาวแท่งบล็อก</strong>ในเกม <strong>Stack</strong> · เก็บที่ <code>/Game/data/game-timing.json</code> · ผู้เล่นได้ค่าใหม่เมื่อเข้าห้อง / รีเฟรชหน้าเล่น</p>
|
||
<fieldset class="quiz-timing-fieldset">
|
||
<legend>ความเร็วสวิง</legend>
|
||
<div class="form-grid form-inline quiz-timing-grid">
|
||
<label title="cycles per second — น้อย = ช้า, มาก = เร็ว · ใช้ช่องข้อความเพื่อไม่ให้เบราว์เซอร์ตัดค่าที่ไม่ตรง step ของ type=number">รอบสวิงต่อวินาที (Hz)
|
||
<input type="text" id="stack-swing-hz" inputmode="decimal" autocomplete="off" spellcheck="false" value="" placeholder="เช่น 0.55" title="0.08–2.8 · ว่าง = 0.55">
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
<p class="muted" style="margin-top:0.35rem">แนะนำสวิง <strong>0.35–0.9</strong> · สูงสุด ~2.8 · Lower Hz = slower pendulum.</p>
|
||
<fieldset class="quiz-timing-fieldset" style="margin-top:0.75rem">
|
||
<legend>ความยาวแท่งบล็อก</legend>
|
||
<div class="form-grid form-inline quiz-timing-grid">
|
||
<label title="ความกว้างบล็อกเป็นหน่วย tile (1 tile = 1 ช่องบนแผนที่)">กว้างบล็อก (tile)
|
||
<input type="text" id="stack-block-width-tiles" inputmode="decimal" autocomplete="off" spellcheck="false" value="" placeholder="ว่าง = อัตโนมัติ" title="ตัวเลข 0.85–3.2 หรือปล่อยว่าง">
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
<p class="muted" style="margin-top:0.35rem"><strong>ว่างช่อง</strong> = ใช้สูตรจากพื้นที่ลงบนแผนที่ (~48% ของความกว้างโซนลง) · ระหว่าง <strong>0.85–3.2</strong> tile · แท่งยาว = ยากขึ้น</p>
|
||
<div class="quiz-admin-actions">
|
||
<button type="button" class="btn btn-primary" id="btn-stack-game-save">บันทึก</button>
|
||
</div>
|
||
<p id="stack-game-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-characters" class="tab-panel card admin-editor-panel" hidden role="tabpanel" aria-labelledby="tab-characters">
|
||
<div class="admin-editor-panel-head">
|
||
<div class="admin-editor-panel-intro">
|
||
<h2>ตัวละคร (4 ทิศ)</h2>
|
||
<p class="muted">อัปโหลดสกิน 4 ทิศหรือแบบเลเยอร์ และเลือกตัวที่ใช้ในล็อบบี้/เกม — เปิดแยกได้ที่ <a href="/Game/character.html" target="_blank" rel="noopener noreferrer">/Game/character.html</a></p>
|
||
</div>
|
||
<button type="button" class="btn btn-ghost btn-editor-fs" id="btn-character-fullscreen" aria-pressed="false" title="ขยายเต็มหน้าจอ (Esc ออก)">เต็มจอ</button>
|
||
</div>
|
||
<iframe class="admin-editor-iframe" id="admin-character-frame" title="เลือกตัวละคร" src="/Game/character.html?embed=1&from=admin"></iframe>
|
||
</section>
|
||
|
||
<section id="tab-panel-accounts" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-accounts">
|
||
<h2>บัญชีผู้ใช้ (รายการภายในระบบ)</h2>
|
||
<p class="muted">ใช้บันทึก / บล็อกผู้เล่นหรือบัญชีที่เชื่อมกับ Guest / Facebook / Google — ผู้เล่น Guest ใน Main-Lobby จะถูกสร้างอัตโนมัติและมี COINS ตามที่เก็บที่นี่</p>
|
||
<p id="accounts-coins-summary" class="muted" aria-live="polite"></p>
|
||
<form id="form-account-add" class="form-grid form-inline">
|
||
<label>อีเมล <input type="email" name="email" placeholder="optional"></label>
|
||
<label>ชื่อแสดง <input type="text" name="displayName"></label>
|
||
<label>ประเภท
|
||
<select name="loginType">
|
||
<option value="guest">guest</option>
|
||
<option value="facebook">facebook</option>
|
||
<option value="google">google</option>
|
||
<option value="email">email</option>
|
||
</select>
|
||
</label>
|
||
<label>Provider User ID <input type="text" name="providerUserId" placeholder="id จาก Facebook/Google"></label>
|
||
<label>หมายเหตุ <input type="text" name="notes"></label>
|
||
<label>COINS เริ่มต้น <input type="number" name="coins" min="0" value="0" step="1"></label>
|
||
<label class="check"><input type="checkbox" name="blocked"> บล็อก</label>
|
||
<button type="submit" class="btn btn-primary">เพิ่มบัญชี</button>
|
||
</form>
|
||
<div class="table-wrap">
|
||
<table class="data-table" id="table-accounts">
|
||
<thead>
|
||
<tr>
|
||
<th>ชื่อ</th>
|
||
<th>อีเมล</th>
|
||
<th>ประเภท</th>
|
||
<th>Provider ID</th>
|
||
<th>COINS</th>
|
||
<th>สถานะ</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="accounts-msg" class="msg" role="status"></p>
|
||
</section>
|
||
|
||
<section id="tab-panel-admins" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-admins">
|
||
<h2>บัญชีแอดมิน</h2>
|
||
<p class="muted">เฉพาะ <strong>super admin</strong> เท่านั้นที่เพิ่ม/ลบแอดมินได้</p>
|
||
<form id="form-admin-add" class="form-grid form-inline">
|
||
<label>ชื่อผู้ใช้ <input type="text" name="username" required minlength="2" autocomplete="off"></label>
|
||
<label>รหัสผ่าน <input type="password" name="password" required minlength="8" autocomplete="new-password"></label>
|
||
<label>บทบาท
|
||
<select name="role">
|
||
<option value="admin">admin</option>
|
||
<option value="super">super</option>
|
||
</select>
|
||
</label>
|
||
<button type="submit" class="btn btn-primary">สร้างแอดมิน</button>
|
||
</form>
|
||
|
||
<h3 class="admin-subheading">ตั้งรหัสใหม่ให้แอดมินอื่น (super)</h3>
|
||
<p class="muted">ไม่ต้องรู้รหัสเดิม — ใช้เมื่อผู้ใช้ลืมรหัส</p>
|
||
<form id="form-admin-reset-other" class="form-grid form-inline">
|
||
<label>เลือกแอดมิน
|
||
<select name="targetId" id="select-admin-reset-target" required>
|
||
<option value="">— เลือก —</option>
|
||
</select>
|
||
</label>
|
||
<label>รหัสผ่านใหม่ <input type="password" name="newPassword" required minlength="8" autocomplete="new-password"></label>
|
||
<label>ยืนยันรหัส <input type="password" name="newPassword2" required minlength="8" autocomplete="new-password"></label>
|
||
<button type="submit" class="btn btn-primary">บันทึกรหัสใหม่</button>
|
||
</form>
|
||
|
||
<div class="table-wrap">
|
||
<table class="data-table" id="table-admins">
|
||
<thead>
|
||
<tr>
|
||
<th>ชื่อผู้ใช้</th>
|
||
<th>บทบาท</th>
|
||
<th>สร้างเมื่อ</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
<p id="admins-msg" class="msg" role="status"></p>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
<script src="admin.js?v=27"></script>
|
||
</body>
|
||
</html>
|