Move test toggle zones outside border, improve span handle hit areas
Some checks failed
Validate / validate (push) Failing after 8s

Separate test edge toggles into dedicated elements outside the
calibration preview border so they don't conflict with span bar
interactions. Expand span handle hit areas to 16px with 4px visible
indicators. Increase canvas padding to 36px on all sides and
reposition tick labels outside toggle zones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 02:48:14 +03:00
parent 2b953e2e3e
commit 9fc15e3213
3 changed files with 110 additions and 51 deletions

View File

@@ -1172,18 +1172,16 @@ function updateCalibrationPreview() {
const activeEdges = calibrationTestState[deviceId] || new Set(); const activeEdges = calibrationTestState[deviceId] || new Set();
['top', 'right', 'bottom', 'left'].forEach(edge => { ['top', 'right', 'bottom', 'left'].forEach(edge => {
const edgeEl = document.querySelector(`.preview-edge.edge-${edge}`); const toggleEl = document.querySelector(`.edge-toggle.toggle-${edge}`);
if (!edgeEl) return; if (!toggleEl) return;
if (activeEdges.has(edge)) { if (activeEdges.has(edge)) {
const [r, g, b] = EDGE_TEST_COLORS[edge]; const [r, g, b] = EDGE_TEST_COLORS[edge];
edgeEl.classList.add('active'); toggleEl.style.background = `rgba(${r}, ${g}, ${b}, 0.35)`;
edgeEl.style.background = `rgba(${r}, ${g}, ${b}, 0.7)`; toggleEl.style.boxShadow = `inset 0 0 6px rgba(${r}, ${g}, ${b}, 0.5)`;
edgeEl.style.boxShadow = `0 0 8px rgba(${r}, ${g}, ${b}, 0.5)`;
} else { } else {
edgeEl.classList.remove('active'); toggleEl.style.background = '';
edgeEl.style.background = ''; toggleEl.style.boxShadow = '';
edgeEl.style.boxShadow = '';
} }
}); });
@@ -1200,9 +1198,9 @@ function renderCalibrationCanvas() {
const containerRect = container.getBoundingClientRect(); const containerRect = container.getBoundingClientRect();
if (containerRect.width === 0 || containerRect.height === 0) return; if (containerRect.width === 0 || containerRect.height === 0) return;
// Canvas extends beyond the container (matches CSS: left:-32px, top:-18px, +64px/+36px) // Canvas extends beyond the container (matches CSS: left:-36px, top:-36px, +72px/+72px)
const padX = 32; const padX = 36;
const padY = 18; const padY = 36;
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
const canvasW = containerRect.width + padX * 2; const canvasW = containerRect.width + padX * 2;
@@ -1261,12 +1259,13 @@ function renderCalibrationCanvas() {
right: { y1: oy + ch + (spans.right?.start || 0) * edgeLenV, y2: oy + ch + (spans.right?.end || 1) * edgeLenV, midX: ox + cW - cw / 2, horizontal: false }, right: { y1: oy + ch + (spans.right?.start || 0) * edgeLenV, y2: oy + ch + (spans.right?.end || 1) * edgeLenV, midX: ox + cW - cw / 2, horizontal: false },
}; };
// Axis positions for labels (outside the container bounds, in the padding area) // Axis positions for labels (outside the 12px toggle zones)
const toggleSize = 12;
const axisPos = { const axisPos = {
top: oy - 3, // labels above top edge top: oy - toggleSize - 3,
bottom: oy + cH + 3, // labels below bottom edge bottom: oy + cH + toggleSize + 3,
left: ox - 3, // labels left of left edge left: ox - toggleSize - 3,
right: ox + cW + 3, // labels right of right edge right: ox + cW + toggleSize + 3,
}; };
// Arrow positions (inside the screen area, near each edge bar) // Arrow positions (inside the screen area, near each edge bar)

View File

@@ -100,40 +100,46 @@
</div> </div>
</div> </div>
<!-- Clickable edge bars with LED count inputs --> <!-- Edge bars with span controls and LED count inputs -->
<div class="preview-edge edge-top" onclick="toggleTestEdge('top')"> <div class="preview-edge edge-top">
<div class="edge-span-bar" data-edge="top"> <div class="edge-span-bar" data-edge="top">
<div class="edge-span-handle edge-span-handle-start" data-edge="top" data-handle="start"></div> <div class="edge-span-handle edge-span-handle-start" data-edge="top" data-handle="start"></div>
<div class="edge-span-handle edge-span-handle-end" data-edge="top" data-handle="end"></div> <div class="edge-span-handle edge-span-handle-end" data-edge="top" data-handle="end"></div>
</div> </div>
<input type="number" id="cal-top-leds" class="edge-led-input" min="0" value="0" <input type="number" id="cal-top-leds" class="edge-led-input" min="0" value="0"
oninput="updateCalibrationPreview()" onclick="event.stopPropagation()"> oninput="updateCalibrationPreview()">
</div> </div>
<div class="preview-edge edge-right" onclick="toggleTestEdge('right')"> <div class="preview-edge edge-right">
<div class="edge-span-bar" data-edge="right"> <div class="edge-span-bar" data-edge="right">
<div class="edge-span-handle edge-span-handle-start" data-edge="right" data-handle="start"></div> <div class="edge-span-handle edge-span-handle-start" data-edge="right" data-handle="start"></div>
<div class="edge-span-handle edge-span-handle-end" data-edge="right" data-handle="end"></div> <div class="edge-span-handle edge-span-handle-end" data-edge="right" data-handle="end"></div>
</div> </div>
<input type="number" id="cal-right-leds" class="edge-led-input" min="0" value="0" <input type="number" id="cal-right-leds" class="edge-led-input" min="0" value="0"
oninput="updateCalibrationPreview()" onclick="event.stopPropagation()"> oninput="updateCalibrationPreview()">
</div> </div>
<div class="preview-edge edge-bottom" onclick="toggleTestEdge('bottom')"> <div class="preview-edge edge-bottom">
<div class="edge-span-bar" data-edge="bottom"> <div class="edge-span-bar" data-edge="bottom">
<div class="edge-span-handle edge-span-handle-start" data-edge="bottom" data-handle="start"></div> <div class="edge-span-handle edge-span-handle-start" data-edge="bottom" data-handle="start"></div>
<div class="edge-span-handle edge-span-handle-end" data-edge="bottom" data-handle="end"></div> <div class="edge-span-handle edge-span-handle-end" data-edge="bottom" data-handle="end"></div>
</div> </div>
<input type="number" id="cal-bottom-leds" class="edge-led-input" min="0" value="0" <input type="number" id="cal-bottom-leds" class="edge-led-input" min="0" value="0"
oninput="updateCalibrationPreview()" onclick="event.stopPropagation()"> oninput="updateCalibrationPreview()">
</div> </div>
<div class="preview-edge edge-left" onclick="toggleTestEdge('left')"> <div class="preview-edge edge-left">
<div class="edge-span-bar" data-edge="left"> <div class="edge-span-bar" data-edge="left">
<div class="edge-span-handle edge-span-handle-start" data-edge="left" data-handle="start"></div> <div class="edge-span-handle edge-span-handle-start" data-edge="left" data-handle="start"></div>
<div class="edge-span-handle edge-span-handle-end" data-edge="left" data-handle="end"></div> <div class="edge-span-handle edge-span-handle-end" data-edge="left" data-handle="end"></div>
</div> </div>
<input type="number" id="cal-left-leds" class="edge-led-input" min="0" value="0" <input type="number" id="cal-left-leds" class="edge-led-input" min="0" value="0"
oninput="updateCalibrationPreview()" onclick="event.stopPropagation()"> oninput="updateCalibrationPreview()">
</div> </div>
<!-- Edge test toggle zones (outside container border, in tick area) -->
<div class="edge-toggle toggle-top" onclick="toggleTestEdge('top')"></div>
<div class="edge-toggle toggle-right" onclick="toggleTestEdge('right')"></div>
<div class="edge-toggle toggle-bottom" onclick="toggleTestEdge('bottom')"></div>
<div class="edge-toggle toggle-left" onclick="toggleTestEdge('left')"></div>
<!-- Corner start position buttons --> <!-- Corner start position buttons -->
<div class="preview-corner corner-top-left" onclick="setStartPosition('top_left')"></div> <div class="preview-corner corner-top-left" onclick="setStartPosition('top_left')"></div>
<div class="preview-corner corner-top-right" onclick="setStartPosition('top_right')"></div> <div class="preview-corner corner-top-right" onclick="setStartPosition('top_right')"></div>

View File

@@ -920,10 +920,10 @@ input:-webkit-autofill:focus {
#calibration-preview-canvas { #calibration-preview-canvas {
position: absolute; position: absolute;
top: -18px; top: -36px;
left: -32px; left: -36px;
width: calc(100% + 64px); width: calc(100% + 72px);
height: calc(100% + 36px); height: calc(100% + 72px);
pointer-events: none; pointer-events: none;
z-index: 3; z-index: 3;
} }
@@ -1001,20 +1001,52 @@ input:-webkit-autofill:focus {
font-size: 11px; font-size: 11px;
color: var(--text-secondary); color: var(--text-secondary);
background: rgba(128, 128, 128, 0.15); background: rgba(128, 128, 128, 0.15);
cursor: pointer; transition: background 0.2s;
transition: background 0.2s, box-shadow 0.2s;
z-index: 2; z-index: 2;
user-select: none; user-select: none;
} }
.preview-edge:hover { /* Edge test toggle zones — positioned outside the container border */
background: rgba(128, 128, 128, 0.3); .edge-toggle {
position: absolute;
cursor: pointer;
z-index: 1;
background: rgba(128, 128, 128, 0.08);
border: 1px solid rgba(128, 128, 128, 0.15);
border-radius: 3px;
transition: background 0.2s, box-shadow 0.2s;
} }
.preview-edge.active { .edge-toggle:hover {
color: white; background: rgba(128, 128, 128, 0.25);
font-weight: 600; }
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
.toggle-top {
top: -12px;
left: 56px;
right: 56px;
height: 12px;
}
.toggle-bottom {
bottom: -12px;
left: 56px;
right: 56px;
height: 12px;
}
.toggle-left {
left: -12px;
top: 36px;
bottom: 36px;
width: 12px;
}
.toggle-right {
right: -12px;
top: 36px;
bottom: 36px;
width: 12px;
} }
.edge-top { .edge-top {
@@ -1123,17 +1155,23 @@ input:-webkit-autofill:focus {
right: 2px; right: 2px;
} }
/* Resize handles */ /* Resize handles — large transparent hit area with narrow visible strip */
.edge-span-handle { .edge-span-handle {
position: absolute; position: absolute;
background: rgba(255, 255, 255, 0.7); background: transparent;
border: 1px solid rgba(76, 175, 80, 0.7);
border-radius: 2px;
z-index: 2; z-index: 2;
opacity: 0; opacity: 0;
transition: opacity 0.15s; transition: opacity 0.15s;
} }
.edge-span-handle::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(76, 175, 80, 0.7);
border-radius: 2px;
}
.edge-span-bar:hover .edge-span-handle { .edge-span-bar:hover .edge-span-handle {
opacity: 1; opacity: 1;
} }
@@ -1141,39 +1179,55 @@ input:-webkit-autofill:focus {
/* Horizontal handles */ /* Horizontal handles */
.edge-top .edge-span-handle, .edge-top .edge-span-handle,
.edge-bottom .edge-span-handle { .edge-bottom .edge-span-handle {
top: 2px; top: 0;
bottom: 2px; bottom: 0;
width: 6px; width: 16px;
cursor: ew-resize; cursor: ew-resize;
} }
.edge-top .edge-span-handle::after,
.edge-bottom .edge-span-handle::after {
top: 3px;
bottom: 3px;
left: 6px;
width: 4px;
}
.edge-top .edge-span-handle-start, .edge-top .edge-span-handle-start,
.edge-bottom .edge-span-handle-start { .edge-bottom .edge-span-handle-start {
left: 0; left: -8px;
} }
.edge-top .edge-span-handle-end, .edge-top .edge-span-handle-end,
.edge-bottom .edge-span-handle-end { .edge-bottom .edge-span-handle-end {
right: 0; right: -8px;
} }
/* Vertical handles */ /* Vertical handles */
.edge-left .edge-span-handle, .edge-left .edge-span-handle,
.edge-right .edge-span-handle { .edge-right .edge-span-handle {
left: 2px; left: 0;
right: 2px; right: 0;
height: 6px; height: 16px;
cursor: ns-resize; cursor: ns-resize;
} }
.edge-left .edge-span-handle::after,
.edge-right .edge-span-handle::after {
left: 3px;
right: 3px;
top: 6px;
height: 4px;
}
.edge-left .edge-span-handle-start, .edge-left .edge-span-handle-start,
.edge-right .edge-span-handle-start { .edge-right .edge-span-handle-start {
top: 0; top: -8px;
} }
.edge-left .edge-span-handle-end, .edge-left .edge-span-handle-end,
.edge-right .edge-span-handle-end { .edge-right .edge-span-handle-end {
bottom: 0; bottom: -8px;
} }
/* Ensure LED input is above span bar */ /* Ensure LED input is above span bar */