Add OpenRGB per-zone LED control with separate/combined modes and zone preview

- Zone picker UI in device add/settings modals with per-zone checkbox selection
- Combined mode: pixels distributed sequentially across zones
- Separate mode: full effect resampled independently to each zone via linear interpolation
- Per-zone LED preview in target cards: one canvas strip per zone with hover overlay labels
- Zone badges on device cards enriched with actual LED counts from OpenRGB API
- Fix stale led_count by using device_led_count discovered at connect time

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 20:35:51 +03:00
parent aafcf83896
commit 52ee4bdeb6
19 changed files with 769 additions and 55 deletions

View File

@@ -443,6 +443,62 @@ body.cs-drag-active .card-drag-handle {
animation: spin 0.8s linear infinite;
}
/* OpenRGB zone checkboxes */
.zone-checkbox-list {
display: flex;
flex-direction: column;
gap: 4px;
max-height: 180px;
overflow-y: auto;
padding: 4px 0;
}
.zone-checkbox-list .zone-loading,
.zone-checkbox-list .zone-error {
font-size: 12px;
color: var(--text-secondary);
padding: 4px 0;
}
.zone-checkbox-list .zone-error { color: var(--danger-color, #e53935); }
.zone-checkbox-item {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.15s;
}
.zone-checkbox-item:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); }
.zone-checkbox-item input[type="checkbox"] { margin: 0; flex-shrink: 0; }
.zone-checkbox-item .zone-led-count {
margin-left: auto;
font-size: 11px;
color: var(--text-secondary);
white-space: nowrap;
}
.zone-mode-radios {
display: flex;
gap: 16px;
}
.zone-mode-option {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 13px;
}
.zone-mode-option input[type="radio"] { margin: 0; }
.zone-badge {
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
background: var(--border-color);
font-weight: 600;
}
.channel-indicator {
display: inline-flex;
gap: 2px;
@@ -792,3 +848,42 @@ ul.section-tip li {
pointer-events: none;
opacity: 0.8;
}
/* Per-zone LED preview (OpenRGB separate mode) */
.led-preview-zones {
display: flex;
flex-direction: column;
gap: 2px;
}
.led-preview-zone {
position: relative;
}
.led-preview-zone-canvas {
display: block;
width: 100%;
height: 18px;
border-radius: 2px;
image-rendering: pixelated;
background: #111;
}
.led-preview-zone-label {
position: absolute;
left: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 0.65rem;
font-family: var(--font-mono, monospace);
color: #fff;
text-shadow: 0 0 3px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.6);
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
white-space: nowrap;
}
.led-preview-zones:hover .led-preview-zone-label {
opacity: 1;
}