feat: UI improvements — silver commander border, life panel border, hand drag-and-drop, card placement
- Add bright silver 3D smoky border around commander card zones - Wrap all 4 life panels in a single bordered container - Add drag-and-drop from hand zone to battlefield with ghost preview - Update card placement to center of battlefield, avoiding overlaid zones - Fix card dimensions in placement calc (110x150 -> 63x84) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
920d7d9787
commit
ef739e1f49
3 files changed
+117
-25
No files matched your search
+7
-11
@@ -3247,19 +3247,15 @@ function addCardToBattlefield(name, imageUrl, typeLine, ownerIndex, x, y, oracle
|
||||
} else {
|
||||
// Place cards by type in designated battlefield zones
|
||||
var zone = getCardTypeZone(typeLine || '');
|
||||
var cardW = 110, cardH = 150;
|
||||
var cardW = 63, cardH = 84;
|
||||
// Zone regions (percentage of section area)
|
||||
// Creatures: top center (30%-70% x, 5%-45% y)
|
||||
// Artifacts/Enchantments: top right (70%-95% x, 5%-45% y)
|
||||
// Lands: bottom left (5%-35% x, 55%-90% y)
|
||||
// Instants/Sorceries: bottom center (35%-65% x, 55%-90% y)
|
||||
// Other: center (30%-70% x, 25%-75% y)
|
||||
// Offset from edges to avoid overlapping the overlaid zone-row/commander-row
|
||||
var zones = {
|
||||
creature: { x1: 0.25, x2: 0.65, y1: 0.05, y2: 0.42 },
|
||||
artifact: { x1: 0.68, x2: 0.95, y1: 0.05, y2: 0.42 },
|
||||
land: { x1: 0.03, x2: 0.32, y1: 0.55, y2: 0.90 },
|
||||
spell: { x1: 0.35, x2: 0.65, y1: 0.55, y2: 0.90 },
|
||||
other: { x1: 0.25, x2: 0.70, y1: 0.25, y2: 0.75 }
|
||||
creature: { x1: 0.25, x2: 0.70, y1: 0.20, y2: 0.55 },
|
||||
artifact: { x1: 0.70, x2: 0.95, y1: 0.20, y2: 0.55 },
|
||||
land: { x1: 0.03, x2: 0.35, y1: 0.55, y2: 0.85 },
|
||||
spell: { x1: 0.35, x2: 0.65, y1: 0.55, y2: 0.85 },
|
||||
other: { x1: 0.25, x2: 0.70, y1: 0.30, y2: 0.70 }
|
||||
};
|
||||
var z = zones[zone] || zones.other;
|
||||
var areaW = rect.width;
|
||||
|
||||
+45
-14
@@ -642,25 +642,33 @@ input { font-family: inherit; }
|
||||
/* Life Panels Overlay — sits above everything */
|
||||
#life-panels-overlay {
|
||||
position: absolute;
|
||||
inset: 44px 0 0 0;
|
||||
top: calc(44px + 50%);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
pointer-events: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border: 2px solid var(--felt-line-strong);
|
||||
border-radius: 8px;
|
||||
background: rgba(10,10,20,0.7);
|
||||
}
|
||||
|
||||
.life-panel {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 130px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Position each panel near the center crossing */
|
||||
.lp-p1 { bottom: calc(50% + 4px); right: calc(50% + 4px); }
|
||||
.lp-p2 { bottom: calc(50% + 4px); left: calc(50% + 4px); }
|
||||
.lp-p3 { top: calc(50% + 4px); right: calc(50% + 4px); }
|
||||
.lp-p4 { top: calc(50% + 4px); left: calc(50% + 4px); }
|
||||
/* Grid positions */
|
||||
.lp-p1 { grid-area: 1 / 1; }
|
||||
.lp-p2 { grid-area: 1 / 2; }
|
||||
.lp-p3 { grid-area: 2 / 1; }
|
||||
.lp-p4 { grid-area: 2 / 2; }
|
||||
|
||||
.life-panel-box {
|
||||
border-radius: 4px;
|
||||
@@ -881,7 +889,12 @@ input { font-family: inherit; }
|
||||
.zone.commander {
|
||||
width: calc(var(--card-w) + 12px);
|
||||
height: calc(var(--card-h) + 5px);
|
||||
border-width: 2px;
|
||||
border: 2px solid rgba(200,200,210,0.6);
|
||||
box-shadow:
|
||||
0 0 6px rgba(192,192,200,0.4),
|
||||
0 0 14px rgba(192,192,200,0.25),
|
||||
0 0 24px rgba(192,192,200,0.15),
|
||||
inset 0 0 8px rgba(192,192,200,0.1);
|
||||
}
|
||||
|
||||
/* Commander card image in zone */
|
||||
@@ -895,22 +908,22 @@ input { font-family: inherit; }
|
||||
/* Player-colored zone borders */
|
||||
.p1 .zone { border-color: rgba(192,57,43,0.12); }
|
||||
.p1 .zone:hover { border-color: rgba(192,57,43,0.3); box-shadow: 0 0 10px var(--p1-soft); }
|
||||
.p1 .zone.commander { border-color: rgba(192,57,43,0.3); background: var(--p1-soft); }
|
||||
.p1 .zone.commander { border-color: rgba(200,200,210,0.6); background: var(--p1-soft); }
|
||||
.p1 .zone-icon { color: var(--p1-text); }
|
||||
|
||||
.p2 .zone { border-color: rgba(41,128,185,0.12); }
|
||||
.p2 .zone:hover { border-color: rgba(41,128,185,0.3); box-shadow: 0 0 10px var(--p2-soft); }
|
||||
.p2 .zone.commander { border-color: rgba(41,128,185,0.3); background: var(--p2-soft); }
|
||||
.p2 .zone.commander { border-color: rgba(200,200,210,0.6); background: var(--p2-soft); }
|
||||
.p2 .zone-icon { color: var(--p2-text); }
|
||||
|
||||
.p3 .zone { border-color: rgba(39,174,96,0.12); }
|
||||
.p3 .zone:hover { border-color: rgba(39,174,96,0.3); box-shadow: 0 0 10px var(--p3-soft); }
|
||||
.p3 .zone.commander { border-color: rgba(39,174,96,0.3); background: var(--p3-soft); }
|
||||
.p3 .zone.commander { border-color: rgba(200,200,210,0.6); background: var(--p3-soft); }
|
||||
.p3 .zone-icon { color: var(--p3-text); }
|
||||
|
||||
.p4 .zone { border-color: rgba(212,147,13,0.12); }
|
||||
.p4 .zone:hover { border-color: rgba(212,147,13,0.3); box-shadow: 0 0 10px var(--p4-soft); }
|
||||
.p4 .zone.commander { border-color: rgba(212,147,13,0.3); background: var(--p4-soft); }
|
||||
.p4 .zone.commander { border-color: rgba(200,200,210,0.6); background: var(--p4-soft); }
|
||||
.p4 .zone-icon { color: var(--p4-text); }
|
||||
|
||||
/* Card count in zone */
|
||||
@@ -950,6 +963,24 @@ input { font-family: inherit; }
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hand drag ghost */
|
||||
.hand-drag-ghost {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
width: 80px;
|
||||
height: 112px;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.85;
|
||||
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.6));
|
||||
}
|
||||
.hand-drag-ghost img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Zone tooltip */
|
||||
.zone[data-tooltip]:hover::after {
|
||||
content: attr(data-tooltip);
|
||||
|
||||
@@ -17,6 +17,12 @@ let undoHistoryVisible = false;
|
||||
let aiTurnActive = false;
|
||||
let floatingPreviewTimeout = null;
|
||||
|
||||
// Hand-to-battlefield drag state
|
||||
let isDraggingFromHand = false;
|
||||
let handDragPlayerIdx = null;
|
||||
let handDragCardIdx = null;
|
||||
let handDragGhost = null;
|
||||
|
||||
// ============================================================
|
||||
// LOG SYSTEM (override engine stub)
|
||||
// ============================================================
|
||||
@@ -246,7 +252,9 @@ function renderHandZone(playerIdx) {
|
||||
if (imgSrc) {
|
||||
thumbsHtml +=
|
||||
'<div class="hand-thumb" ' +
|
||||
'data-player="' + playerIdx + '" data-hand-index="' + i + '" ' +
|
||||
'onclick="openZoneModal(' + playerIdx + ')" ' +
|
||||
'onmousedown="startHandDrag(event,' + playerIdx + ',' + i + ',\'' + imgSrc.replace(/'/g, "\\'") + '\')" ' +
|
||||
'onmouseenter="showFloatingCardPreview(\'' + imgSrc.replace(/'/g, "\\'") + '\',event,{name:\'' + escapedName + '\',typeLine:\'' + escapedType + '\'})" ' +
|
||||
'onmousemove="positionFloatingPreview(event)" ' +
|
||||
'onmouseleave="hideFloatingCardPreview()">' +
|
||||
@@ -521,6 +529,63 @@ document.addEventListener('mouseup', e => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HAND-TO-BATTLEFIELD DRAG & DROP
|
||||
// ============================================================
|
||||
function startHandDrag(e, playerIdx, handIndex, imgSrc) {
|
||||
// Only left mouse button
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
isDraggingFromHand = true;
|
||||
handDragPlayerIdx = playerIdx;
|
||||
handDragCardIdx = handIndex;
|
||||
hideFloatingCardPreview();
|
||||
|
||||
// Create ghost element
|
||||
handDragGhost = document.createElement('div');
|
||||
handDragGhost.className = 'hand-drag-ghost';
|
||||
handDragGhost.innerHTML = '<img src="' + imgSrc + '" draggable="false"/>';
|
||||
handDragGhost.style.left = e.clientX + 'px';
|
||||
handDragGhost.style.top = e.clientY + 'px';
|
||||
document.body.appendChild(handDragGhost);
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (!isDraggingFromHand || !handDragGhost) return;
|
||||
handDragGhost.style.left = e.clientX + 'px';
|
||||
handDragGhost.style.top = e.clientY + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function(e) {
|
||||
if (!isDraggingFromHand) return;
|
||||
|
||||
// Clean up ghost
|
||||
if (handDragGhost) {
|
||||
handDragGhost.remove();
|
||||
handDragGhost = null;
|
||||
}
|
||||
|
||||
// Check if dropped over a battle-zone
|
||||
const pNum = handDragPlayerIdx + 1;
|
||||
const battleZone = document.getElementById('battle-zone-' + pNum);
|
||||
if (battleZone) {
|
||||
const rect = battleZone.getBoundingClientRect();
|
||||
if (e.clientX >= rect.left && e.clientX <= rect.right &&
|
||||
e.clientY >= rect.top && e.clientY <= rect.bottom) {
|
||||
// Play the card
|
||||
if (typeof playCardFromHand === 'function') {
|
||||
playCardFromHand(handDragPlayerIdx, handDragCardIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isDraggingFromHand = false;
|
||||
handDragPlayerIdx = null;
|
||||
handDragCardIdx = null;
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// PHASE TRACKER
|
||||
// ============================================================
|
||||
|
||||
Reference in new issue
Block a user