Safety & Correctness: - Add confirmation dialogs to Stop All, turnOffDevice - i18n confirm dialog (title, yes, no buttons) - Fix duplicate tutorial-overlay ID - Define missing CSS variables (--radius, --text-primary, --hover-bg, --input-bg) - Fix toast z-index conflict with confirm dialog (2500 → 3000) UX Consistency: - Add backdrop-close to test modals - Add device clone feature (only entity without it) - Add sync clocks to command palette - Replace 20+ hardcoded accent colors with CSS vars/color-mix() - Remove dead .badge duplicate from components.css - Make calibration elements keyboard-accessible (div → button) - Add aria-labels to color picker swatches - Fix pattern canvas mobile horizontal scroll - Fix graph editor mobile bottom clipping Polish: - Add empty-state messages to all CardSection instances - Convert 21 px font-sizes to rem - Add scroll-behavior: smooth with reduced-motion override - Add @media print styles - Add :focus-visible to 4 missing interactive elements - Fix settings modal close label (Cancel → Close) - Fix api-key submit button i18n New Features: - Command palette actions: start/stop targets, activate scenes, enable/disable - Bulk start/stop API endpoints (POST /output-targets/bulk/start|stop) - OS notification history viewer modal - Scene "used by" automation reference count on cards - Clock elapsed time display on Streams tab cards - Device "last seen" relative timestamp on cards - Audio device refresh button in edit modal - Composite layer drag-to-reorder - MQTT settings panel (broker config with JSON persistence) - WebSocket log viewer with level filtering and ring buffer - Runtime log-level adjustment (GET/PUT endpoints + settings UI) - Animated value source waveform canvas preview - Gradient custom preset save/delete (localStorage) - API key read-only display in settings - Backup metadata (file size, auto/manual badges) - Server restart button with confirm + overlay - Partial config export/import per entity type - Progressive disclosure in target editor (Advanced section) CSS Architecture: - Define radius scale tokens (--radius-sm/md/lg/pill) - Scope .cs-filter selectors to remove 7 !important overrides - Consolidate duplicate toggle switch (filter-list → settings-toggle) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
12 KiB
HTML
168 lines
12 KiB
HTML
<!-- Calibration Modal -->
|
|
<div id="calibration-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="calibration-modal-title">
|
|
<div class="modal-content" style="max-width: 700px;">
|
|
<div class="modal-header">
|
|
<h2 id="calibration-modal-title"><svg class="icon" viewBox="0 0 24 24"><path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/><path d="m14.5 12.5 2-2"/><path d="m11.5 9.5 2-2"/><path d="m8.5 6.5 2-2"/><path d="m17.5 15.5 2-2"/></svg> <span data-i18n="calibration.title">LED Calibration</span></h2>
|
|
<button id="calibration-tutorial-btn" class="tutorial-trigger-btn" onclick="startCalibrationTutorial()" data-i18n-title="calibration.tutorial.start" title="Start tutorial">?</button>
|
|
<button class="modal-close-btn" onclick="closeCalibrationModal()" title="Close" data-i18n-aria-label="aria.close">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="calibration-device-id">
|
|
<input type="hidden" id="calibration-css-id">
|
|
<!-- Device picker shown in CSS calibration mode for edge testing -->
|
|
<div id="calibration-css-test-group" class="form-group" style="display:none; margin-bottom: 12px; padding: 0 4px;">
|
|
<div class="label-row">
|
|
<label for="calibration-test-device" data-i18n="color_strip.test_device">Test on Device:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.test_device.hint">Select a device to send test pixels to when clicking edge toggles</small>
|
|
<select id="calibration-test-device"></select>
|
|
</div>
|
|
<!-- LED count input (CSS calibration mode only) -->
|
|
<div id="cal-css-led-count-group" class="form-group" style="display:none; margin-bottom: 12px; padding: 0 4px;">
|
|
<div class="label-row">
|
|
<label for="cal-css-led-count" data-i18n="color_strip.led_count">LED Count:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.led_count.hint">Total number of LEDs on the physical strip. Set to 0 to use the sum from calibration. If your strip has LEDs behind the TV that are not mapped to screen edges, set the exact count here and they will be filled with black.</small>
|
|
<input type="number" id="cal-css-led-count" min="0" max="1500" step="1" value="0" oninput="updateCalibrationPreview()">
|
|
</div>
|
|
|
|
<!-- Interactive Preview with integrated LED inputs and test toggles -->
|
|
<div style="margin-bottom: 12px; padding: 0 24px;">
|
|
<div class="calibration-preview">
|
|
<!-- Screen with direction toggle, total LEDs, and offset -->
|
|
<div class="preview-screen">
|
|
<button type="button" class="direction-toggle" onclick="toggleDirection()" title="Toggle direction">
|
|
<span id="direction-icon"><svg class="icon" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg></span> <span id="direction-label">CW</span>
|
|
</button>
|
|
<div class="preview-screen-total" onclick="toggleEdgeInputs()" title="Toggle edge LED inputs"><span id="cal-total-leds-inline">0</span> / <span id="cal-device-led-count-inline">0</span></div>
|
|
<div class="preview-screen-border-width">
|
|
<label for="cal-border-width" data-i18n="calibration.border_width">Border (px):</label>
|
|
<input type="number" id="cal-border-width" min="1" max="100" value="10">
|
|
</div>
|
|
<button id="calibration-overlay-btn" class="calibration-overlay-toggle" onclick="toggleCalibrationOverlay()" data-i18n-title="overlay.button.show" title="Show overlay visualization" style="display:none">
|
|
<svg class="icon" viewBox="0 0 24 24"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg> <span data-i18n="calibration.overlay_toggle">Overlay</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Edge bars with span controls and LED count inputs -->
|
|
<div class="preview-edge 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-end" data-edge="top" data-handle="end"></div>
|
|
</div>
|
|
<input type="number" id="cal-top-leds" class="edge-led-input" min="0" value="0"
|
|
oninput="updateCalibrationPreview()">
|
|
</div>
|
|
<div class="preview-edge 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-end" data-edge="right" data-handle="end"></div>
|
|
</div>
|
|
<input type="number" id="cal-right-leds" class="edge-led-input" min="0" value="0"
|
|
oninput="updateCalibrationPreview()">
|
|
</div>
|
|
<div class="preview-edge 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-end" data-edge="bottom" data-handle="end"></div>
|
|
</div>
|
|
<input type="number" id="cal-bottom-leds" class="edge-led-input" min="0" value="0"
|
|
oninput="updateCalibrationPreview()">
|
|
</div>
|
|
<div class="preview-edge 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-end" data-edge="left" data-handle="end"></div>
|
|
</div>
|
|
<input type="number" id="cal-left-leds" class="edge-led-input" min="0" value="0"
|
|
oninput="updateCalibrationPreview()">
|
|
</div>
|
|
|
|
<!-- Edge test toggle zones (outside container border, in tick area) -->
|
|
<button type="button" class="edge-toggle toggle-top" onclick="toggleTestEdge('top')"></button>
|
|
<button type="button" class="edge-toggle toggle-right" onclick="toggleTestEdge('right')"></button>
|
|
<button type="button" class="edge-toggle toggle-bottom" onclick="toggleTestEdge('bottom')"></button>
|
|
<button type="button" class="edge-toggle toggle-left" onclick="toggleTestEdge('left')"></button>
|
|
|
|
<!-- Corner start position buttons -->
|
|
<button type="button" class="preview-corner corner-top-left" onclick="setStartPosition('top_left')">●</button>
|
|
<button type="button" class="preview-corner corner-top-right" onclick="setStartPosition('top_right')">●</button>
|
|
<button type="button" class="preview-corner corner-bottom-left" onclick="setStartPosition('bottom_left')">●</button>
|
|
<button type="button" class="preview-corner corner-bottom-right" onclick="setStartPosition('bottom_right')">●</button>
|
|
|
|
<!-- Canvas overlay for ticks, arrows, start label -->
|
|
<canvas id="calibration-preview-canvas"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hidden selects (used by saveCalibration) -->
|
|
<div style="display: none;">
|
|
<select id="cal-start-position">
|
|
<option value="bottom_left">Bottom Left</option>
|
|
<option value="bottom_right">Bottom Right</option>
|
|
<option value="top_left">Top Left</option>
|
|
<option value="top_right">Top Right</option>
|
|
</select>
|
|
<select id="cal-layout">
|
|
<option value="clockwise">Clockwise</option>
|
|
<option value="counterclockwise">Counterclockwise</option>
|
|
</select>
|
|
</div>
|
|
|
|
|
|
<!-- Offset & Skip LEDs -->
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; padding: 0 24px;">
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="cal-offset" data-i18n="calibration.offset">LED Offset:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="calibration.offset.hint">Distance from physical LED 0 to the start corner (along strip direction)</small>
|
|
<input type="number" id="cal-offset" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="cal-skip-start" data-i18n="calibration.skip_start">Skip LEDs (Start):</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="calibration.skip_start.hint">Number of LEDs to turn off at the beginning of the strip (0 = none)</small>
|
|
<input type="number" id="cal-skip-start" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="cal-skip-end" data-i18n="calibration.skip_end">Skip LEDs (End):</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="calibration.skip_end.hint">Number of LEDs to turn off at the end of the strip (0 = none)</small>
|
|
<input type="number" id="cal-skip-end" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tutorial Overlay -->
|
|
<div id="calibration-tutorial-overlay" class="tutorial-overlay">
|
|
<div class="tutorial-backdrop"></div>
|
|
<div class="tutorial-ring"></div>
|
|
<div class="tutorial-tooltip">
|
|
<div class="tutorial-tooltip-header">
|
|
<span class="tutorial-step-counter"></span>
|
|
<button class="tutorial-close-btn" onclick="closeTutorial()">×</button>
|
|
</div>
|
|
<p class="tutorial-tooltip-text"></p>
|
|
<div class="tutorial-tooltip-nav">
|
|
<button class="tutorial-prev-btn" onclick="tutorialPrev()">←</button>
|
|
<button class="tutorial-next-btn" onclick="tutorialNext()">→</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="calibration-error" class="error-message" style="display: none;"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-icon btn-secondary" onclick="closeCalibrationModal()" title="Cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
|
<button class="btn btn-icon btn-primary" onclick="saveCalibration()" title="Save" data-i18n-aria-label="aria.save">✓</button>
|
|
</div>
|
|
</div>
|
|
</div>
|