diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6ca4e4c..a63f9fd 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -341,3 +341,78 @@ http://localhost:8000/?effect=stripes&stripeColors=1,0,0,1,1,0&suppressWarnings= *The Matrix has you. Follow the white rabbit.* 🐰 +## UI Components Architecture + +### Mode Display Panel (`js/mode-display.js`) +The Matrix Mode panel in the top-right corner provides user controls for customizing the experience: + +**Key Features**: +- **Version Dropdown**: Interactive select element populated from `getAvailableModes()` in config.js + - Lists all available Matrix versions (classic, resurrections, trinity, etc.) + - Changing version triggers page reload with new URL parameter + - Current selection is synchronized with URL params + +- **Effect Dropdown**: Interactive select element populated from `getAvailableEffects()` in config.js + - Lists all available effects (palette, rainbow, mirror, etc.) + - Changing effect triggers page reload with new URL parameter + - Current selection is synchronized with URL params + +- **Auto Mode Switching**: Checkbox to enable/disable screensaver-like mode rotation + - When enabled, automatically cycles through different version/effect combinations + - Interval configurable via dropdown (10-60 minutes) + +- **Switch Mode Now**: Button to manually trigger a random mode change + - Calls `modeManager.switchToRandomMode(true)` with manual flag + - For manual switches, page reloads to ensure clean state + - For auto switches, attempts in-place config update + +**Event System**: +```javascript +modeDisplay.on("versionChange", (version) => { /* handle version change */ }); +modeDisplay.on("effectChange", (effect) => { /* handle effect change */ }); +modeDisplay.on("toggleScreensaver", (enabled) => { /* handle screensaver toggle */ }); +modeDisplay.on("changeSwitchInterval", (interval) => { /* handle interval change */ }); +``` + +**Integration Points**: +- Imports `getAvailableModes()` and `getAvailableEffects()` from config.js +- Communicates with `ModeManager` for mode switching logic +- Events handled in `main.js` `setupModeManagementEvents()` function + +### Page Title Updates +The page title dynamically updates to reflect the current version and effect: +- Format: `"Matrix - {Version Name} / {Effect Name}"` +- Examples: + - `"Matrix - Classic / Palette"` + - `"Matrix - Resurrections / Rainbow"` + - `"Matrix - Trinity / Mirror"` +- Updated via `updatePageTitle(config)` function in main.js +- Title updates occur on: + - Initial page load + - Version/effect dropdown changes (via page reload) + - Auto mode switching (via history.replaceState) +- Works correctly in both normal browser and PWA modes +- Name formatting uses camelCase-to-Title-Case conversion for readability + +### Spotify UI Component +The Spotify integration UI (`js/spotify-ui.js`) is hidden by default: +- Located in top-left corner when visible +- Controlled via `spotifyControlsVisible` config parameter +- **Note**: "Show Spotify Controls" checkbox removed from Mode Display as of QOL improvements +- To enable Spotify UI: use URL parameter `?spotifyControls=true` +- Component still functional for users who explicitly enable it via URL + +### Configuration System +All UI options are controlled via URL parameters: +- `version`: Matrix version (classic, resurrections, etc.) +- `effect`: Visual effect (palette, rainbow, mirror, etc.) + - **Note**: The "trans" effect was removed in a prior PR and is no longer available +- `screensaver`: Enable auto mode switching (true/false) +- `switchInterval`: Auto-switch interval in milliseconds +- `suppressWarnings`: Hide hardware acceleration warnings (true/false) + +See `js/config.js` `paramMapping` object for complete list of supported parameters. + +Available effects are defined in `getAvailableEffects()` function in config.js: +- none, plain, palette, customStripes, stripes, rainbow, spectrum, image, mirror, gallery + diff --git a/js/config.js b/js/config.js index 548f892..502fed1 100644 --- a/js/config.js +++ b/js/config.js @@ -58,7 +58,7 @@ export function getAvailableModes() { * Returns a list of all available effect names */ export function getAvailableEffects() { - return ["none", "plain", "palette", "customStripes", "stripes", "rainbow", "spectrum", "trans", "image", "mirror", "gallery"]; + return ["none", "plain", "palette", "customStripes", "stripes", "rainbow", "spectrum", "image", "mirror", "gallery"]; } /* diff --git a/js/main.js b/js/main.js index f02eea7..da64a0f 100644 --- a/js/main.js +++ b/js/main.js @@ -257,7 +257,7 @@ function initializeModeManagement(config) { modeDisplay.setModeManager(modeManager); // Set initial toggle states - modeDisplay.setToggleStates(config.screensaverMode || false, config.spotifyControlsVisible || false, config.modeSwitchInterval || 600000); + modeDisplay.setToggleStates(config.screensaverMode || false, config.modeSwitchInterval || 600000); // Set up event listeners setupModeManagementEvents(config); @@ -266,6 +266,9 @@ function initializeModeManagement(config) { if (config.screensaverMode) { modeManager.start(); } + + // Update page title with initial mode + updatePageTitle(config); } /** @@ -282,15 +285,18 @@ function setupModeManagementEvents(config) { } }); - modeDisplay.on("toggleSpotifyControls", (visible) => { - config.spotifyControlsVisible = visible; - if (spotifyUI) { - if (visible) { - spotifyUI.show(); - } else { - spotifyUI.hide(); - } - } + modeDisplay.on("versionChange", (version) => { + // Update URL and reload with new version + const urlParams = new URLSearchParams(window.location.search); + urlParams.set("version", version); + window.location.search = urlParams.toString(); + }); + + modeDisplay.on("effectChange", (effect) => { + // Update URL and reload with new effect + const urlParams = new URLSearchParams(window.location.search); + urlParams.set("effect", effect); + window.location.search = urlParams.toString(); }); modeDisplay.on("changeSwitchInterval", (interval) => { @@ -318,10 +324,34 @@ function setupModeManagementEvents(config) { // Update the configuration and restart the renderer const newConfig = makeConfig(Object.fromEntries(urlParams.entries())); restartMatrixWithNewConfig(newConfig); + + // Update page title + updatePageTitle(newConfig); } }); } +/** + * Update page title with current version and effect + */ +function updatePageTitle(config) { + const version = config.version || "classic"; + const effect = config.effect || "palette"; + + // Format names for display + const formatName = (name) => { + return name + .split(/(?=[A-Z])|[_-]/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); + }; + + const versionName = formatName(version); + const effectName = formatName(effect); + + document.title = `Matrix - ${versionName} / ${effectName}`; +} + /** * Restart the Matrix renderer with new configuration */ @@ -353,10 +383,10 @@ function initializeSpotifyIntegration(config) { // Create Spotify integration instance spotifyIntegration = new SpotifyIntegration(); - // Create UI controls + // Create UI controls - hidden by default spotifyUI = new SpotifyUI({ clientId: config.spotifyClientId, - visible: config.spotifyControlsVisible, + visible: false, // Always hide Spotify controls }); spotifyUI.setSpotifyIntegration(spotifyIntegration); diff --git a/js/mode-display.js b/js/mode-display.js index dd570c6..910fdd6 100644 --- a/js/mode-display.js +++ b/js/mode-display.js @@ -5,6 +5,8 @@ * along with controls for the screensaver mode functionality. */ +import { getAvailableModes, getAvailableEffects } from "./config.js"; + export default class ModeDisplay { constructor(config = {}) { this.config = { @@ -21,8 +23,9 @@ export default class ModeDisplay { this.modeManager = null; this.callbacks = { toggleScreensaver: [], - toggleSpotifyControls: [], changeSwitchInterval: [], + versionChange: [], + effectChange: [], }; this.init(); @@ -65,6 +68,12 @@ export default class ModeDisplay { this.element.className = "mode-display"; this.element.style.cssText = this.getBaseStyles(); + const availableVersions = getAvailableModes(); + const availableEffects = getAvailableEffects(); + + const versionOptions = availableVersions.map((v) => ``).join(""); + const effectOptions = availableEffects.map((e) => ``).join(""); + this.element.innerHTML = `