diff --git a/build/check-bundle-size.ts b/build/check-bundle-size.ts index 1fdc6d64..e8248fd2 100644 --- a/build/check-bundle-size.ts +++ b/build/check-bundle-size.ts @@ -6,7 +6,9 @@ const BUDGET_BYTES: Record = { // product panel, status strip, section tabs, stacked cards, responsive // layout, and fixed apply bar are all implemented in the existing CSS // bundle without adding a UI framework or runtime dependency. - ".css": 54_000, + // Raised from 54 kB for the in-app lighting colour picker: the SV panel, + // hue bar, drag thumbs, swatch, and hex field in the dedicated Lighting tab. + ".css": 55_500, // Raised from 280 kB for the production Logitech onboard-profile codec, // guarded flash editor, verification exporter, upstream Finalmouse driver, // the dedicated Viper Mini protocol driver, Viper V3 sleep/low-power plus @@ -18,8 +20,12 @@ const BUDGET_BYTES: Record = { // command protocol. No framework or runtime dependency was added. // Raised from 410 kB for capability-gated NinjaForce performance controls, // multi-stage DPI selection, and Sora V3 receiver lighting read/write UI. + // Raised from 425 kB for the in-app lighting colour picker: pointer-event + // binding, canvas-drawn SV/hue surfaces, and hex/swatch sync. + // Raised from 427 kB for the second, dedicated Lighting-tab card: the + // render path now drives both the Advanced-tab card and the new tab. // Protocol tests and preview fixtures remain unbundled. - ".js": 425_000, + ".js": 429_000, }; const ASSETS = join("dist", "assets"); diff --git a/public/devices/README.md b/public/devices/README.md index 33bae9b5..25403a6d 100644 --- a/public/devices/README.md +++ b/public/devices/README.md @@ -6,7 +6,7 @@ this folder from the site root, so a file here is reachable at Adding one: -1. Save a **transparent** PNG named after the model in kebab-case, e.g. +1. Save a **transparent** PNG or WebP named after the model in kebab-case, e.g. `razer-viper-v3-pro.png`. The panel sits on a dark background, so an image with a white backdrop shows as a white block. 2. Keep enough resolution for a product panel up to roughly 340 px wide. @@ -31,3 +31,7 @@ before including it in a public release package. `endgame-gear-op1-8k.png` was supplied from an Overclockers UK product-image URL for the OP1 8K. Confirm redistribution terms before including it in a public release package. + +`razer-viper-mini.webp` was supplied from a Discord attachment URL for the +Razer Viper Mini (wired). Confirm redistribution terms before including it in +a public release package. diff --git a/public/devices/razer-viper-mini.webp b/public/devices/razer-viper-mini.webp new file mode 100644 index 00000000..063628bf Binary files /dev/null and b/public/devices/razer-viper-mini.webp differ diff --git a/src/control-events.ts b/src/control-events.ts index e3f5fe7d..56903197 100644 --- a/src/control-events.ts +++ b/src/control-events.ts @@ -407,28 +407,31 @@ export function bindControlEvents(handlers: ControlEventHandlers): void { }); }); // The effect and speed buttons are rebuilt on every render, so their clicks - // are delegated to the containers that own them. - document.querySelector("#lighting-modes")?.addEventListener("click", (event) => { - const target = (event.target as HTMLElement).closest("[data-lighting-mode]"); - if (target && !target.disabled) handlers.applyLighting({ mode: target.dataset.lightingMode as MouseLighting["mode"] }); - }); - document.querySelector("#lighting-speeds")?.addEventListener("click", (event) => { - const target = (event.target as HTMLElement).closest("[data-lighting-speed]"); - if (target && !target.disabled) handlers.applyLighting({ speed: Number(target.dataset.lightingSpeed) }); - }); - document.querySelector("#lighting-speed-slider")?.addEventListener("change", (event) => { - handlers.applyLighting({ speed: Number((event.target as HTMLInputElement).value) }); - }); - document.querySelector("#lighting-brightness-levels")?.addEventListener("click", (event) => { - const target = (event.target as HTMLElement).closest("[data-lighting-brightness]"); - if (target && !target.disabled) handlers.applyLighting({ brightness: Number(target.dataset.lightingBrightness) }); - }); - document.querySelector("#lighting-color")?.addEventListener("change", (event) => { - handlers.applyLighting({ color: (event.target as HTMLInputElement).value }); - }); - document.querySelector("#lighting-color2")?.addEventListener("change", (event) => { - handlers.applyLighting({ color2: (event.target as HTMLInputElement).value }); - }); + // are delegated to the containers that own them. Both the Advanced-tab card + // and the dedicated Lighting-tab card bind the same way. + for (const prefix of ["lighting", "lighting-tab"] as const) { + document.querySelector(`#${prefix}-modes`)?.addEventListener("click", (event) => { + const target = (event.target as HTMLElement).closest("[data-lighting-mode]"); + if (target && !target.disabled) handlers.applyLighting({ mode: target.dataset.lightingMode as MouseLighting["mode"] }); + }); + document.querySelector(`#${prefix}-speeds`)?.addEventListener("click", (event) => { + const target = (event.target as HTMLElement).closest("[data-lighting-speed]"); + if (target && !target.disabled) handlers.applyLighting({ speed: Number(target.dataset.lightingSpeed) }); + }); + document.querySelector(`#${prefix}-speed-slider`)?.addEventListener("change", (event) => { + handlers.applyLighting({ speed: Number((event.target as HTMLInputElement).value) }); + }); + document.querySelector(`#${prefix}-brightness-levels`)?.addEventListener("click", (event) => { + const target = (event.target as HTMLElement).closest("[data-lighting-brightness]"); + if (target && !target.disabled) handlers.applyLighting({ brightness: Number(target.dataset.lightingBrightness) }); + }); + document.querySelector(`#${prefix}-color`)?.addEventListener("change", (event) => { + handlers.applyLighting({ color: (event.target as HTMLInputElement).value }); + }); + document.querySelector(`#${prefix}-color2`)?.addEventListener("change", (event) => { + handlers.applyLighting({ color2: (event.target as HTMLInputElement).value }); + }); + } document.querySelectorAll("[data-ninjutso-controls]").forEach((container) => { container.addEventListener("click", (event) => { const target = (event.target as HTMLElement).closest("[data-ninjutso-setting]"); diff --git a/src/control-template.ts b/src/control-template.ts index 30eab058..73c1af61 100644 --- a/src/control-template.ts +++ b/src/control-template.ts @@ -59,6 +59,7 @@ export function controlTemplate(buildLabel: string): string {