diff --git a/www/html/Admin/admin.css b/www/html/Admin/admin.css index 4b5daa5..e97eef1 100644 --- a/www/html/Admin/admin.css +++ b/www/html/Admin/admin.css @@ -2756,3 +2756,138 @@ code { aspect-ratio: 382 / 72; max-width: 200px; } + +/* ===== Evidence Cards admin panel ===== */ +.ev-suspect-block { + border: 1px solid rgba(124, 154, 255, 0.25); + border-radius: 12px; + padding: 14px 16px; + margin: 0 0 16px; + background: rgba(20, 26, 48, 0.4); +} +.ev-suspect-head { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + margin: 0 0 12px; +} +.ev-suspect-head .ev-suspect-no { + font-weight: 900; + font-size: 15px; + color: #ffd666; + background: rgba(255, 214, 102, 0.14); + border: 1px solid rgba(255, 214, 102, 0.4); + border-radius: 999px; + padding: 4px 12px; +} +.ev-suspect-head label { + flex: 1 1 240px; +} +.ev-cards-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} +@media (max-width: 900px) { + .ev-cards-grid { grid-template-columns: 1fr; } +} +.ev-card-edit { + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 10px; + padding: 10px; + background: rgba(10, 14, 28, 0.5); + display: flex; + flex-direction: column; + gap: 8px; +} +.ev-card-edit .ev-card-edit-rar { + font-weight: 800; + font-size: 12px; + letter-spacing: 0.04em; +} +.ev-card-edit.rar-common .ev-card-edit-rar { color: #9aa3b2; } +.ev-card-edit.rar-rare .ev-card-edit-rar { color: #6cc7ff; } +.ev-card-edit.rar-legendary .ev-card-edit-rar { color: #f1b94a; } +.ev-card-edit input, +.ev-card-edit textarea, +.ev-card-edit select { + width: 100%; + box-sizing: border-box; +} +.ev-card-edit textarea { min-height: 60px; resize: vertical; } +.ev-card-edit-imgwrap { + display: flex; + gap: 8px; + align-items: flex-start; +} +.ev-card-edit-thumb { + width: 72px; + height: 102px; + object-fit: contain; + border-radius: 6px; + background: #1b2240; + border: 1px solid rgba(255, 255, 255, 0.15); + flex-shrink: 0; +} +.ev-card-edit-imgcol { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + gap: 6px; +} +.ev-img-picker { + position: fixed; + inset: 0; + z-index: 9000; + background: rgba(6, 9, 20, 0.85); + display: flex; + align-items: center; + justify-content: center; +} +.ev-img-picker[hidden] { display: none; } +.ev-img-picker-shell { + width: min(900px, 94vw); + max-height: 86vh; + background: linear-gradient(180deg, #161d38, #0e1326); + border: 1px solid rgba(124, 154, 255, 0.4); + border-radius: 14px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} +.ev-img-picker-head { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} +.ev-img-picker-head strong { font-size: 16px; color: #fff; } +.ev-img-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 10px; + overflow: auto; + padding: 4px; +} +.ev-img-grid button { + border: 2px solid rgba(124, 154, 255, 0.25); + border-radius: 8px; + background: #1b2240; + padding: 4px; + cursor: pointer; + transition: border-color 0.12s, transform 0.12s; +} +.ev-img-grid button:hover { border-color: #7c9aff; transform: translateY(-2px); } +.ev-img-grid button.is-current { border-color: #9ece6a; } +.ev-img-grid img { width: 100%; height: auto; display: block; border-radius: 4px; } +.ev-img-grid .ev-img-name { font-size: 11px; color: #9fb0d8; text-align: center; margin-top: 2px; } + +/* Evidence pool count badges (รูปอย่างเดียว) */ +.ev-pool-counts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; } +.ev-pool-pill { font-size: 12px; font-weight: 700; padding: 4px 10px; border-radius: 999px; color: #0a0e1a; } +.ev-pool-pill.rar-common { background: #c8d2e0; } +.ev-pool-pill.rar-rare { background: #7fb6ff; } +.ev-pool-pill.rar-legendary { background: #ffcf6b; } diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index 024ea11..e89faab 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -4026,6 +4026,164 @@ el('top-actions').hidden = name !== 'panel-app'; } + // ===== Evidence Cards admin (รูปอย่างเดียว · 15 คดี · rarity จากเลขไฟล์) ===== + var EVIDENCE_CARDS_API = '/Game/api/evidence-cards'; + var EVIDENCE_IMAGES_API = '/Game/api/evidence-card-images'; + var EVIDENCE_CASE_COUNT = 15; + var EVIDENCE_DIRS = ['suspect-1', 'suspect-2', 'suspect-3']; + var evidenceData = null; + var evidencePools = null; // { 'suspect-1': {common,rare,legendary}, ... } + var evidenceCurrentCase = '1'; + + function evidencePopulateCaseSelect() { + var sel = el('evidence-case'); + if (!sel || sel.options.length) return; + for (var i = 1; i <= EVIDENCE_CASE_COUNT; i++) { + var o = document.createElement('option'); + o.value = String(i); o.textContent = 'คดี ' + i; + sel.appendChild(o); + } + } + + function loadEvidenceCardsPanel() { + evidencePopulateCaseSelect(); + if (evidencePools == null) { + fetch(EVIDENCE_IMAGES_API + '?_=' + Date.now(), { credentials: 'include', cache: 'no-store' }) + .then(function (r) { return r.json(); }) + .then(function (j) { evidencePools = (j && j.pools) || {}; renderEvidenceCase(evidenceCurrentCase); }) + .catch(function () { evidencePools = {}; }); + } + fetch(EVIDENCE_CARDS_API + '?_=' + Date.now(), { credentials: 'include', cache: 'no-store' }) + .then(function (r) { return r.json(); }) + .then(function (j) { + evidenceData = (j && j.cases) ? j : { cases: {} }; + var sel = el('evidence-case'); + if (sel) evidenceCurrentCase = sel.value || '1'; + renderEvidenceCase(evidenceCurrentCase); + }) + .catch(function (e) { + setMsg('evidence-cards-msg', (e.message || 'โหลดไม่ได้') + ' — เช็คว่า Node รัน Game/server.js และ /Game/api/evidence-cards ไม่ 404', 'error'); + }); + } + + function evidenceGetCase(cid) { + if (!evidenceData) evidenceData = { cases: {} }; + if (!evidenceData.cases) evidenceData.cases = {}; + if (!evidenceData.cases[cid]) evidenceData.cases[cid] = { suspects: [] }; + var cse = evidenceData.cases[cid]; + if (!Array.isArray(cse.suspects)) cse.suspects = []; + for (var si = 0; si < 3; si++) { + if (!cse.suspects[si]) cse.suspects[si] = {}; + var s = cse.suspects[si]; + if (!s.linkName) s.linkName = 'ผู้ต้องสงสัย ' + (si + 1); + if (EVIDENCE_DIRS.indexOf(s.imageDir) < 0) s.imageDir = EVIDENCE_DIRS[si] || EVIDENCE_DIRS[0]; + } + return cse; + } + + function evidencePoolBadge(dir) { + var p = (evidencePools && evidencePools[dir]) || null; + var box = document.createElement('div'); + box.className = 'ev-pool-counts'; + if (!p) { box.textContent = 'กำลังโหลดจำนวนรูป…'; return box; } + box.innerHTML = + 'ธรรมดา ' + (p.common || 0) + '' + + 'Rare ' + (p.rare || 0) + '' + + 'ชี้คนร้าย ' + (p.legendary || 0) + ''; + return box; + } + + function renderEvidenceCase(cid) { + evidenceCurrentCase = cid; + var tag = el('evidence-set-tag'); + if (tag) tag.textContent = 'กำลังแก้: คดี ' + cid; + var root = el('evidence-suspects-root'); + if (!root) return; + var cse = evidenceGetCase(cid); + root.innerHTML = ''; + cse.suspects.forEach(function (s, si) { + var block = document.createElement('div'); + block.className = 'ev-suspect-block'; + + var head = document.createElement('div'); + head.className = 'ev-suspect-head'; + head.innerHTML = 'ผู้ต้องสงสัย ' + (si + 1) + ''; + block.appendChild(head); + + var nameLabel = document.createElement('label'); + nameLabel.className = 'admin-field'; + nameLabel.textContent = 'ชื่อผู้ต้องสงสัย (Suspect Link)'; + var nameInp = document.createElement('input'); + nameInp.type = 'text'; + nameInp.maxLength = 60; + nameInp.value = s.linkName || ''; + nameInp.addEventListener('input', function () { s.linkName = nameInp.value; }); + nameLabel.appendChild(nameInp); + block.appendChild(nameLabel); + + var poolBox = evidencePoolBadge(s.imageDir); + + var dirLabel = document.createElement('label'); + dirLabel.className = 'admin-field'; + dirLabel.textContent = 'โฟลเดอร์รูปการ์ด (พูลที่ใช้สุ่ม)'; + var dirSel = document.createElement('select'); + dirSel.className = 'admin-inp-num'; + EVIDENCE_DIRS.forEach(function (d) { + var o = document.createElement('option'); + o.value = d; o.textContent = d.replace('suspect-', 'ผู้ต้องสงสัย-'); + if (d === s.imageDir) o.selected = true; + dirSel.appendChild(o); + }); + dirSel.addEventListener('change', function () { + s.imageDir = dirSel.value; + var fresh = evidencePoolBadge(s.imageDir); + poolBox.replaceWith(fresh); + poolBox = fresh; + }); + dirLabel.appendChild(dirSel); + block.appendChild(dirLabel); + block.appendChild(poolBox); + + root.appendChild(block); + }); + } + + function saveEvidenceCardsPanel() { + if (!evidenceData) return; + var btn = el('btn-evidence-save'); + if (btn) btn.disabled = true; + setMsg('evidence-cards-msg', 'กำลังบันทึก…', ''); + fetch(EVIDENCE_CARDS_API, { + method: 'PUT', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(evidenceData), + }).then(function (r) { + return r.text().then(function (t) { + var j = {}; + try { j = t ? JSON.parse(t) : {}; } catch (e) { /* ignore */ } + if (!r.ok || !j.ok) throw new Error((j && j.error) || ('HTTP ' + r.status)); + return j; + }); + }).then(function (j) { + if (j && j.cases) evidenceData = { cases: j.cases }; + renderEvidenceCase(evidenceCurrentCase); + setMsg('evidence-cards-msg', 'บันทึกแล้ว — มีผลในเกมเมื่อรีเฟรช LobbyB', 'ok'); + }).catch(function (e) { + setMsg('evidence-cards-msg', e.message || 'บันทึกไม่ได้', 'error'); + }).then(function () { + if (btn) btn.disabled = false; + }); + } + + (function bindEvidenceCardsPanel() { + evidencePopulateCaseSelect(); + var caseSel = el('evidence-case'); + if (caseSel) caseSel.addEventListener('change', function () { renderEvidenceCase(caseSel.value || '1'); }); + var saveBtn = el('btn-evidence-save'); + if (saveBtn) saveBtn.addEventListener('click', saveEvidenceCardsPanel); + })(); + function setTab(name) { document.querySelectorAll('.tab').forEach(function (t) { var on = t.getAttribute('data-tab') === name; @@ -4053,6 +4211,7 @@ }); if (name === 'quiz') loadQuizSettingsPanel(); if (name === 'special-quiz') loadSpecialQuizPanel(); + if (name === 'evidence-cards') loadEvidenceCardsPanel(); if (name === 'quiz-carry') loadQuizCarryPanel(); if (name === 'quiz-battle') loadQbBattlePanel(); if (name === 'jump-survive') loadJumpSurviveTimingPanel(); diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index a08d6d8..68ed63e 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -10,7 +10,7 @@ - +
ข้ามไปเนื้อหา @@ -83,6 +83,7 @@ + @@ -309,6 +310,28 @@ +มี 15 คดี · แต่ละคดีมี ผู้ต้องสงสัย 3 คน · ตั้ง ชื่อผู้ต้องสงสัย + เลือก โฟลเดอร์รูป (suspect-1/2/3) ได้ · การ์ดเป็นรูปอย่างเดียว โดยระดับ (rarity) คิดอัตโนมัติจากเลขไฟล์: Card-01–22 = ธรรมดา, Card-23–44 = Rare, Card-45–54 = ชี้คนร้าย · ตอนจบมินิเกมระบบจะสุ่มการ์ดตามเกรดของทีม (A/B/C) · เก็บที่ /Game/data/evidence-cards.json
ใช้กับฉากประเภท ตอบคำถาม — หยิบคำตอบมาวางกลาง ใน Editor — ผู้เล่นหยิบตัวเลือกไปวางโซนกลาง · ถ้ามีรายการที่นี่ เกมจะใช้เฉพาะชุดนี้ (ไม่ผสมกับคำถามถูก/ผิดแท็บคำถามเกม) · เก็บที่ /Game/data/quiz-settings.json ฟิลด์ carryQuestions