Split monolithic index.html and style.css for maintainability

- Extract 15 modals and 3 partials from index.html into Jinja2 templates
  (templates/modals/*.html, templates/partials/*.html)
- Split style.css (3,712 lines) into 11 feature-scoped CSS files under
  static/css/ (base, layout, components, cards, modal, calibration,
  dashboard, streams, patterns, profiles, tutorials)
- Switch root route from FileResponse to Jinja2Templates
- Add jinja2 dependency
- Consolidate duplicate @keyframes spin definition
- Browser receives identical assembled HTML — zero JS changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 00:42:50 +03:00
parent 755077607a
commit 2b90fafb9c
35 changed files with 4986 additions and 4990 deletions

View File

@@ -0,0 +1,144 @@
<!-- Calibration Modal -->
<div id="calibration-modal" class="modal">
<div class="modal-content" style="max-width: 700px;">
<div class="modal-header">
<h2 data-i18n="calibration.title">📐 LED Calibration</h2>
<button 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">&#x2715;</button>
</div>
<div class="modal-body">
<input type="hidden" id="calibration-device-id">
<!-- 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"></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>
</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) -->
<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 -->
<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-bottom-left" onclick="setStartPosition('bottom_left')"></div>
<div class="preview-corner corner-bottom-right" onclick="setStartPosition('bottom_right')"></div>
<!-- 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="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()">&times;</button>
</div>
<p class="tutorial-tooltip-text"></p>
<div class="tutorial-tooltip-nav">
<button class="tutorial-prev-btn" onclick="tutorialPrev()">&#8592;</button>
<button class="tutorial-next-btn" onclick="tutorialNext()">&#8594;</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">&#x2715;</button>
<button class="btn btn-icon btn-primary" onclick="saveCalibration()" title="Save">&#x2713;</button>
</div>
</div>
</div>