diff --git a/www/html/Admin/admin.css b/www/html/Admin/admin.css index abf79b3..6ed5dc8 100644 --- a/www/html/Admin/admin.css +++ b/www/html/Admin/admin.css @@ -1078,27 +1078,61 @@ code { border-color: rgba(239, 68, 68, 0.25); } -.game-timing-laser-slot--beam .game-timing-laser-preview { +.game-timing-laser-slot--beam .game-timing-laser-preview--beam { position: relative; + flex-direction: column; + justify-content: stretch; + align-items: stretch; + min-height: clamp(200px, 34vh, 380px); + max-height: 420px; + width: 100%; + max-width: 76px; + margin-left: auto; + margin-right: auto; + padding: 0; + overflow: hidden; } -.game-timing-laser-slot--beam .game-timing-laser-preview::after { +.game-timing-laser-slot--beam .game-timing-laser-preview--beam::after { content: ''; position: absolute; left: 50%; - top: 8%; - bottom: 8%; - width: 3px; + top: 3%; + bottom: 3%; + width: 4px; transform: translateX(-50%); border-radius: 2px; - background: linear-gradient(180deg, rgba(252, 165, 165, 0.15), rgba(239, 68, 68, 0.9), rgba(252, 165, 165, 0.15)); - box-shadow: 0 0 14px rgba(239, 68, 68, 0.45); + background: linear-gradient(180deg, rgba(252, 165, 165, 0.15), rgba(239, 68, 68, 0.92), rgba(252, 165, 165, 0.15)); + box-shadow: 0 0 16px rgba(239, 68, 68, 0.5); pointer-events: none; - opacity: 0.9; + opacity: 0.92; + z-index: 1; } -.game-timing-laser-slot--beam .game-timing-laser-preview:has(.game-timing-laser-preview-img)::after { - opacity: 0.4; +.game-timing-laser-slot--beam .game-timing-laser-preview--beam:has(.game-timing-laser-preview-img)::after { + opacity: 0.38; +} + +.game-timing-laser-slot--beam .game-timing-laser-preview--beam .game-timing-laser-preview-img { + position: relative; + z-index: 0; + width: 100%; + flex: 1 1 auto; + min-height: 0; + max-height: none; + max-width: none; + object-fit: fill; + object-position: center top; + border-radius: 2px; +} + +.game-timing-laser-slot--beam .game-timing-laser-preview--beam .game-timing-laser-placeholder { + flex: 1 1 auto; + display: flex; + align-items: center; + justify-content: center; + min-height: 5rem; + padding: 0.35rem; } @media (min-width: 700px) { @@ -1145,6 +1179,70 @@ code { color: #c0caf5; } +.game-timing-laser-color-label { + display: block; +} + +.game-timing-laser-color-wrap { + display: flex; + align-items: center; + gap: 0.55rem; + margin-top: 0.28rem; +} + +.game-timing-laser-swatch-stack { + position: relative; + flex-shrink: 0; + width: 2.35rem; + height: 2.35rem; +} + +.game-timing-laser-swatch { + display: block; + width: 100%; + height: 100%; + border-radius: 8px; + border: 1px solid #565f89; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25); + pointer-events: none; +} + +.game-timing-laser-color-picker { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + border: 0; + border-radius: 8px; + cursor: pointer; + opacity: 0; +} + +.game-timing-laser-color-picker::-webkit-color-swatch-wrapper { + padding: 0; +} + +.game-timing-laser-color-picker::-webkit-color-swatch { + border: 0; + border-radius: 8px; +} + +.game-timing-laser-swatch.is-empty { + background-color: transparent !important; + background-image: + linear-gradient(45deg, #3b4261 25%, transparent 25%, transparent 75%, #3b4261 75%, #3b4261), + linear-gradient(45deg, #3b4261 25%, #24283b 25%, #24283b 75%, #3b4261 75%, #3b4261); + background-size: 8px 8px; + background-position: 0 0, 4px 4px; +} + +.game-timing-laser-color-wrap input[type='text'] { + flex: 1; + min-width: 0; +} + /* —— Gauntlet asset library (game timing) —— */ .gauntlet-asset-library { margin: 0.75rem 0 1rem; diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index 0c0f66a..9588e35 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -1982,6 +1982,26 @@ }); } + /** พรีวิวคอลัมน์ beam — สูงแนวตั้ง + สีเติม/กรอบตามช่อง (คล้ายในเกม) */ + function styleGameTimingLaserBeamPreviewShell() { + var prev = el('game-timing-laser-line-preview'); + if (!prev) return; + var fillIn = el('game-timing-laser-fill'); + var strokeIn = el('game-timing-laser-stroke'); + var lwIn = el('game-timing-laser-line-width'); + var fill = fillIn && fillIn.value ? String(fillIn.value).trim() : ''; + var stroke = strokeIn && strokeIn.value ? String(strokeIn.value).trim() : ''; + var lw = lwIn ? parseInt(lwIn.value, 10) : 2; + if (Number.isNaN(lw)) lw = 2; + lw = Math.max(0, Math.min(24, lw)); + prev.style.backgroundColor = fill || ''; + if (lw > 0 && stroke) { + prev.style.boxShadow = 'inset 0 0 0 ' + Math.min(8, lw) + 'px ' + stroke; + } else { + prev.style.boxShadow = ''; + } + } + function renderGameTimingLaserVisuals() { var slots = [ { inpId: 'game-timing-laser-top-url', prevId: 'game-timing-laser-top-preview' }, @@ -2009,6 +2029,141 @@ img.onerror = function () { img.classList.add('is-broken'); }; prev.appendChild(img); }); + styleGameTimingLaserBeamPreviewShell(); + } + + function clamp255(x) { + return Math.max(0, Math.min(255, Math.round(Number(x)))); + } + + function parseCssColorToRgba(str) { + str = String(str || '').trim(); + if (!str) return null; + var m = str.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/i); + if (m) { + var r = clamp255(m[1]); + var g = clamp255(m[2]); + var b = clamp255(m[3]); + var a = + m[4] !== undefined && m[4] !== '' && !Number.isNaN(Number(m[4])) + ? Math.max(0, Math.min(1, Number(m[4]))) + : 1; + return { r: r, g: g, b: b, a: a }; + } + m = str.match(/^#([\da-f]{3}|[\da-f]{6})$/i); + if (m) { + var h = m[1]; + var r2; + var g2; + var b2; + if (h.length === 3) { + r2 = parseInt(h[0] + h[0], 16); + g2 = parseInt(h[1] + h[1], 16); + b2 = parseInt(h[2] + h[2], 16); + } else { + r2 = parseInt(h.slice(0, 2), 16); + g2 = parseInt(h.slice(2, 4), 16); + b2 = parseInt(h.slice(4, 6), 16); + } + if (Number.isNaN(r2) || Number.isNaN(g2) || Number.isNaN(b2)) return null; + return { r: r2, g: g2, b: b2, a: 1 }; + } + var probe = document.createElement('span'); + probe.style.cssText = 'position:absolute;visibility:hidden;left:0;top:0;background-color:' + str; + document.body.appendChild(probe); + var bg = getComputedStyle(probe).backgroundColor; + document.body.removeChild(probe); + m = bg && bg.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/); + if (!m) return null; + r2 = clamp255(m[1]); + g2 = clamp255(m[2]); + b2 = clamp255(m[3]); + var a2 = m[4] !== undefined && !Number.isNaN(Number(m[4])) ? Math.max(0, Math.min(1, Number(m[4]))) : 1; + return { r: r2, g: g2, b: b2, a: a2 }; + } + + function rgbToHex6(r, g, b) { + function hx(n) { + var h = clamp255(n).toString(16); + return h.length === 1 ? '0' + h : h; + } + return '#' + hx(r) + hx(g) + hx(b); + } + + function laserHexFromTextOrFallback(text, fallbackHex) { + var c = parseCssColorToRgba(text); + if (!c) return fallbackHex; + return rgbToHex6(c.r, c.g, c.b); + } + + function formatLaserRgba(r, g, b, a) { + var aa = Math.round(Math.max(0, Math.min(1, a)) * 100) / 100; + return 'rgba(' + clamp255(r) + ',' + clamp255(g) + ',' + clamp255(b) + ',' + aa + ')'; + } + + function syncGameTimingLaserColorSwatches() { + function apply(swatchId, textVal) { + var sw = el(swatchId); + if (!sw) return; + var v = textVal != null ? String(textVal).trim() : ''; + sw.style.backgroundColor = v || ''; + sw.classList.toggle('is-empty', !v); + sw.title = v || 'ว่าง — พิมพ์ค่า CSS หรือกดสี่เหลี่ยมเพื่อเลือกสี'; + } + var fillIn = el('game-timing-laser-fill'); + var strokeIn = el('game-timing-laser-stroke'); + apply('game-timing-laser-fill-swatch', fillIn && fillIn.value); + apply('game-timing-laser-stroke-swatch', strokeIn && strokeIn.value); + var fillPk = el('game-timing-laser-fill-picker'); + var strokePk = el('game-timing-laser-stroke-picker'); + if (fillPk && fillIn) { + var hf = laserHexFromTextOrFallback(fillIn.value, '#ef4444'); + if (fillPk.value.toLowerCase() !== hf.toLowerCase()) fillPk.value = hf; + } + if (strokePk && strokeIn) { + var hs = laserHexFromTextOrFallback(strokeIn.value, '#fca5a5'); + if (strokePk.value.toLowerCase() !== hs.toLowerCase()) strokePk.value = hs; + } + styleGameTimingLaserBeamPreviewShell(); + } + + function bindGameTimingLaserColorSwatches() { + var fillIn = el('game-timing-laser-fill'); + var strokeIn = el('game-timing-laser-stroke'); + if (fillIn) { + fillIn.addEventListener('input', syncGameTimingLaserColorSwatches); + fillIn.addEventListener('change', syncGameTimingLaserColorSwatches); + } + if (strokeIn) { + strokeIn.addEventListener('input', syncGameTimingLaserColorSwatches); + strokeIn.addEventListener('change', syncGameTimingLaserColorSwatches); + } + function wirePicker(pickerId, textEl, defaultAlpha) { + var pk = el(pickerId); + var tx = textEl; + if (!pk || !tx) return; + function applyHex() { + var hex = String(pk.value || '').trim(); + if (!/^#[\da-f]{6}$/i.test(hex)) return; + var r = parseInt(hex.slice(1, 3), 16); + var g = parseInt(hex.slice(3, 5), 16); + var b = parseInt(hex.slice(5, 7), 16); + var prev = parseCssColorToRgba(tx.value); + var a = prev && typeof prev.a === 'number' ? prev.a : defaultAlpha; + tx.value = formatLaserRgba(r, g, b, a); + syncGameTimingLaserColorSwatches(); + } + pk.addEventListener('input', applyHex); + pk.addEventListener('change', applyHex); + } + wirePicker('game-timing-laser-fill-picker', fillIn, 0.35); + wirePicker('game-timing-laser-stroke-picker', strokeIn, 0.95); + var lwIn = el('game-timing-laser-line-width'); + if (lwIn) { + lwIn.addEventListener('input', styleGameTimingLaserBeamPreviewShell); + lwIn.addEventListener('change', styleGameTimingLaserBeamPreviewShell); + } + syncGameTimingLaserColorSwatches(); } function bindGameTimingAssignedVisuals() { @@ -2445,6 +2600,7 @@ if (el('game-timing-laser-line-width')) el('game-timing-laser-line-width').value = String(Number.isFinite(lw) ? lw : 2); renderGameTimingLaneVisuals(); renderGameTimingLaserVisuals(); + syncGameTimingLaserColorSwatches(); applyGameTimingDataToStackInputs(data); setMsg('game-timing-msg', '', ''); return refreshGauntletAssetLibrary(); @@ -2691,6 +2847,7 @@ } bindGauntletAssetLibrary(); bindGameTimingAssignedVisuals(); + bindGameTimingLaserColorSwatches(); var btnQuizAdd = el('btn-quiz-admin-add'); var btnQuizSave = el('btn-quiz-admin-save'); diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index e723680..5075d87 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -9,7 +9,7 @@ - +
ข้ามไปเนื้อหา @@ -513,7 +513,7 @@