diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index d766bf5..f04e496 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -122,6 +122,12 @@ body { box-shadow: none; } +.ghost { + background: transparent; + border: 1px solid #2d3a57; + box-shadow: none; +} + .secondary { background: #475569; } @@ -176,6 +182,12 @@ body { border-color: rgba(148, 163, 184, 0.2); } +.badge.danger { + background: rgba(248, 113, 113, 0.12); + color: #fecdd3; + border-color: rgba(248, 113, 113, 0.45); +} + .badge-row { display: flex; gap: 8px; @@ -475,6 +487,29 @@ body { margin: 0; } +.selected-asset-banner { + display: grid; + grid-template-columns: 1fr auto; + gap: 12px; + padding: 14px; + border-radius: 12px; + background: linear-gradient(135deg, rgba(124, 58, 237, 0.08), rgba(59, 130, 246, 0.05)); + border: 1px solid rgba(124, 58, 237, 0.2); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +.selected-asset-main { + display: flex; + flex-direction: column; + gap: 6px; +} + +.selected-asset-actions { + display: flex; + align-items: center; + gap: 8px; +} + .panel ul { list-style: none; padding: 0; @@ -560,6 +595,38 @@ body { gap: 8px; } +.icon-button { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border-radius: 8px; + border: 1px solid rgba(148, 163, 184, 0.25); + background: rgba(255, 255, 255, 0.04); + color: #e2e8f0; + transition: all 0.15s ease; +} + +.icon-button .icon { + font-size: 16px; + line-height: 1; +} + +.icon-button:hover { + border-color: rgba(124, 58, 237, 0.4); + box-shadow: 0 5px 18px rgba(0, 0, 0, 0.25); +} + +.icon-button.danger { + border-color: rgba(248, 113, 113, 0.35); + color: #fecdd3; +} + +.icon-button.danger:hover { + border-color: rgba(248, 113, 113, 0.6); + background: rgba(248, 113, 113, 0.08); +} + .asset-item.hidden { opacity: 0.6; } @@ -602,6 +669,27 @@ body { color: #e2e8f0; } +.number-input { + position: relative; + padding-right: 48px !important; + font-variant-numeric: tabular-nums; +} + +.number-input::-webkit-outer-spin-button, +.number-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.number-input { + -moz-appearance: textfield; +} + +.number-input:focus { + border-color: #7c3aed; + box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25); +} + .control-actions { display: flex; gap: 8px; diff --git a/src/main/resources/static/js/admin.js b/src/main/resources/static/js/admin.js index 9b1ea71..6fbdcdc 100644 --- a/src/main/resources/static/js/admin.js +++ b/src/main/resources/static/js/admin.js @@ -27,12 +27,29 @@ const selectedAssetName = document.getElementById('selected-asset-name'); const selectedAssetMeta = document.getElementById('selected-asset-meta'); const selectedZLabel = document.getElementById('asset-z-level'); const selectedTypeLabel = document.getElementById('asset-type-label'); +const selectedVisibilityBadge = document.getElementById('selected-asset-visibility'); +const selectedToggleBtn = document.getElementById('selected-asset-toggle'); +const selectedDeleteBtn = document.getElementById('selected-asset-delete'); const aspectLockState = new Map(); if (widthInput) widthInput.addEventListener('input', () => handleSizeInputChange('width')); if (heightInput) heightInput.addEventListener('input', () => handleSizeInputChange('height')); if (speedInput) speedInput.addEventListener('change', updatePlaybackFromInputs); if (muteInput) muteInput.addEventListener('change', updateMuteFromInput); +if (selectedToggleBtn) selectedToggleBtn.addEventListener('click', (event) => { + event.stopPropagation(); + const asset = getSelectedAsset(); + if (asset) { + updateVisibility(asset, !asset.hidden); + } +}); +if (selectedDeleteBtn) selectedDeleteBtn.addEventListener('click', (event) => { + event.stopPropagation(); + const asset = getSelectedAsset(); + if (asset) { + deleteAsset(asset); + } +}); function connect() { const socket = new SockJS('/ws'); @@ -599,8 +616,8 @@ function renderAssetList() { const toggleBtn = document.createElement('button'); toggleBtn.type = 'button'; - toggleBtn.className = 'secondary'; - toggleBtn.textContent = asset.hidden ? 'Show' : 'Hide'; + toggleBtn.className = 'ghost icon-button'; + toggleBtn.innerHTML = `${asset.hidden ? 'Show' : 'Hide'}`; toggleBtn.addEventListener('click', (e) => { e.stopPropagation(); selectedAssetId = asset.id; @@ -609,8 +626,8 @@ function renderAssetList() { const deleteBtn = document.createElement('button'); deleteBtn.type = 'button'; - deleteBtn.className = 'secondary'; - deleteBtn.textContent = 'Delete'; + deleteBtn.className = 'ghost danger icon-button'; + deleteBtn.innerHTML = 'Delete'; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); deleteAsset(asset); @@ -678,6 +695,14 @@ function updateSelectedAssetControls() { if (selectedTypeLabel) { selectedTypeLabel.textContent = getDisplayMediaType(asset); } + if (selectedVisibilityBadge) { + selectedVisibilityBadge.textContent = asset.hidden ? 'Hidden' : 'Visible'; + selectedVisibilityBadge.classList.toggle('danger', !!asset.hidden); + } + if (selectedToggleBtn) { + selectedToggleBtn.querySelector('.label').textContent = asset.hidden ? 'Show' : 'Hide'; + selectedToggleBtn.querySelector('.icon').textContent = asset.hidden ? '👁️' : '🙈'; + } if (widthInput) widthInput.value = Math.round(asset.width); if (heightInput) heightInput.value = Math.round(asset.height); @@ -686,7 +711,7 @@ function updateSelectedAssetControls() { aspectLockInput.onchange = () => setAspectLock(asset.id, aspectLockInput.checked); } if (speedInput) { - speedInput.value = Math.round((asset.speed && asset.speed > 0 ? asset.speed : 1) * 100) / 100; + speedInput.value = Math.round((asset.speed && asset.speed > 0 ? asset.speed : 1) * 100); } if (muteInput) { muteInput.checked = !!asset.muted; @@ -723,8 +748,8 @@ function applyTransformFromInputs() { function updatePlaybackFromInputs() { const asset = getSelectedAsset(); if (!asset) return; - const nextSpeed = Math.max(0.1, parseFloat(speedInput?.value) || asset.speed || 1); - asset.speed = nextSpeed; + const percent = Math.max(10, Math.min(400, parseFloat(speedInput?.value) || 100)); + asset.speed = percent / 100; renderStates.set(asset.id, { ...asset }); persistTransform(asset); drawAndList(); diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index c84479e..e23283d 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -27,12 +27,25 @@