feat(ui): redesign web UI in the "Gallery" visual language#25
Open
bring42 wants to merge 1 commit into
Open
Conversation
Full restyle of data/index.html + app.css + app.js: porcelain-light, monochrome-precise chrome where colour appears only where it represents actual light (a new live strip readout + effect swatches, both approximating the active effect client-side). Motion is eased throughout (power-off fades to standby rather than snapping), no emoji, hairline structure over boxed cards, tabular numerals, system fonts (offline / LittleFS budget). Direction was picked by the owner from two interactive mockups pitched against docs/premium-lighting-criteria.md, replacing the prior cobalt-on-black admin-panel look. This lands the visual system; a follow-up will rework the page structure itself (the current layout is still the original's card stack). The effect rail now builds dynamically from GET /api/v2/effects (was 23 hardcoded emoji tiles); dead legacy palette/speed/intensity/color controls, the light/dark theme toggle, and the 404'ing scenes UI are removed. Net smaller: app.js 63KB -> 32KB, total UI 62KB uncompressed / 16KB gzipped. Also adds scripts/mock_server.py + .claude/launch.json: a dev-only mock of the device HTTP API (serves the real data/ files, fakes v2 endpoints with 202 semantics) so the UI can be iterated and screenshotted in a browser without hardware, since mklittlefs is still x86-broken on this Mac. Firmware and API are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR implements the “Gallery” redesign of the LittleFS-served web UI (HTML/CSS/JS), moving effect selection + parameter controls to a schema-driven v2 API model, and adds a dev-only HTTP mock server to iterate on the UI without hardware.
Changes:
- Restyles
data/index.html+app.cssinto the new “Gallery” visual language and updates markup to match the new interaction model. - Rewrites
app.jsto build the effect rail dynamically fromGET /api/v2/effectsand render controls from each effect’s schema. - Adds a Python mock server (plus a
.claudelauncher config) for local UI development without an ESP32 device.
Reviewed changes
Copilot reviewed 5 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/mock_server.py | Dev-only HTTP mock for serving data/ and faking key v2 + legacy endpoints for UI iteration. |
| data/index.html | Updates page structure to the new Gallery layout and hooks into the rewritten JS behaviors. |
| data/assets/app.js | Schema-driven UI logic (effect rail, param controls, power/brightness, settings modal, nightlight, live strip approximation). |
| data/assets/app.css | Implements the Gallery design system styles and interaction affordances (motion, rails, modal, controls). |
| .claude/launch.json | Convenience launcher configuration for running the mock server in development. |
Comment on lines
+280
to
+304
| async function applySegmentState() { | ||
| const effectId = $('effect').value; | ||
| if (!effectId) return; | ||
| const segment = { effect: effectId }; | ||
| const meta = effectMetadata[effectId]; | ||
| if (meta && meta.hasSchema) { | ||
| const params = {}; | ||
| let palette = null; | ||
| document.querySelectorAll('#schemaControls > .ctl').forEach(group => { | ||
| const pid = group.dataset.paramId; | ||
| let input = group.querySelector('input') || group.querySelector('select'); | ||
| if (!input) return; | ||
| let value; | ||
| if (input.type === 'checkbox') value = input.checked; | ||
| else if (input.type === 'number') value = parseFloat(input.value); | ||
| else if (input.type === 'range') value = parseInt(input.value); | ||
| else if (input.type === 'color') value = input.value; | ||
| else if (input.tagName === 'SELECT') value = parseInt(input.value); | ||
| else value = input.value; | ||
| if (pid === 'palette') palette = PALETTE_PRESETS[input.value] ?? 0; | ||
| else params[pid] = value; | ||
| }); | ||
| if (Object.keys(params).length) segment.params = params; | ||
| if (palette !== null) segment.palette = palette; | ||
| } |
Comment on lines
+251
to
+255
| tile.onclick = () => { | ||
| hidden.value = name; | ||
| row.querySelectorAll('.pal-tile').forEach(t => t.classList.toggle('sel', t === tile)); | ||
| applySegmentState(); | ||
| }; |
Comment on lines
+98
to
+105
| def _static(self, path): | ||
| rel = "index.html" if path == "/" else path.lstrip("/") | ||
| fp = os.path.normpath(os.path.join(ROOT, rel)) | ||
| if not fp.startswith(os.path.normpath(ROOT)) or not os.path.isfile(fp): | ||
| return self._send(404, {"error": "not_found"}) | ||
| ct = {"html": "text/html", "css": "text/css", "js": "application/javascript"}.get(fp.rsplit(".", 1)[-1], "text/plain") | ||
| with open(fp, "rb") as f: | ||
| self._send(200, f.read(), ct) |
Comment on lines
+247
to
+250
| const tile = document.createElement('div'); | ||
| tile.className = 'pal-tile' + (name === hidden.value ? ' sel' : ''); | ||
| tile.dataset.value = name; | ||
| tile.innerHTML = `<div class="pal-swatch" style="background:${paletteGradientCss(name)}"></div><div class="pal-nm">${name[0].toUpperCase()+name.slice(1)}</div>`; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
data/index.html+app.css+app.jsto the Gallery direction: porcelain-light, monochrome-precise chrome where colour appears only where it represents actual light (a live strip readout + effect swatches, both approximated client-side). Direction was picked by the owner from two interactive mockups pitched againstdocs/premium-lighting-criteria.md.GET /api/v2/effects(was 23 hardcoded emoji tiles). Removed dead legacy palette/speed/intensity/color controls, the light/dark theme toggle, and the 404'ing scenes UI.app.js63KB → 32KB; total UI 62KB uncompressed / 16KB gzipped.scripts/mock_server.py+.claude/launch.json: a dev-only mock of the device HTTP API (serves the realdata/files, fakes v2 endpoints with 202 semantics) so the UI can be iterated and screenshotted in a browser without hardware —mklittlefsis still x86-broken locally.Note: this lands the visual system — page structure is still the original's card stack. A follow-up will rework the layout itself (see conversation).
Firmware and API are unchanged.
Test plan
scripts/mock_server.py: effect switch → schema params render (int/float/color/bool/enum/palette) → apply serializes correctly and reconciles (202 contract)onclick/$()references after the rewrite (swept for orphans)🤖 Generated with Claude Code