Introduce a level selection UI and persistence for unlocked levels. Adds CSS styles and HTML markup for a floating level menu, and JS to load/save highest unlocked level to localStorage (key: mirror-game-highest-unlocked-level). Implements functions to render/toggle the menu, unlock levels on finish, and navigate to arbitrary levels via goToLevel. nextLevel now delegates to goToLevel, and setup calls loadUnlockedLevels() and setupLevelMenu() so the menu reflects progress immediately.
Introduce per-level glass inventory and UI for draggable glass pieces, including disabled state and count labels. glassOptions now hold objects with color, maxAmount and currentAmount; palette creation uses these values to enable/disable dragging, show remaining counts, and update on place/remove. Drag-and-drop logic now tracks a draggedGlassColor fallback, prevents placing when inventory is empty, decrements/increments inventory on place/remove, and rebuilds the palette. Reset inventory on level start and when advancing levels. Also adjust laser tracing: yellow lasers are saved as segments and terminate on target, and yellow interacts with demi-wall corners by saving the segment instead of reflecting (blue still reflects). Add CSS for .glass-item:disabled and .glass-item-label. Overall fixes inventory handling and yellow-laser behavior.
Convert global config maps (initialMirrorAngles, buttonGroups, doorGroups, captorGroups, rotatorButtons) into per-level arrays and add getCurrentLevelConfig(levelConfigs) helper. Update getButtonGroup, getDoorGroup, getCaptorGroup, getRotatorButtonConfig, isMirrorControlledByButton, syncRotatorButtons and initializeMirrorOrientations to pull configs for the current level. Also tweak a couple of level tiles and add a rotator button config for the third level ("3,7": { mirrorX: 7, mirrorY: 7, step: -22.5, intervalMs: 1000 }) while keeping empty objects for levels without overrides. These changes enable per-level customization of mirrors, buttons, doors, captors and rotators.
Add captor support and related door-toggling state plus color-specific laser interactions. Changes include: replace one level tile value (15 -> 20), add captorGroups/getCaptorGroup, add toggleDoorsFromCaptor, and new state (toggledDoors, poweredCaptors). traceLaser now initializes openedDoors from toggledDoors, tracks nextPoweredCaptors, handles captor tiles (red lasers power/toggle door groups, yellow passes, others stop), updates poweredCaptors, and applies color-based behavior for rotator buttons and demi-wall reflections (only reflect for blue). Reset toggledDoors and poweredCaptors on next level. These changes implement captor mechanics that toggle door groups and ensure correct color-dependent laser effects.
Introduce a new rotator button tile and mirror auto-rotation feature. CSS adds styles for rotator buttons and locked mirror display. JS: add legend entry and rotatorButtons config, track activeRotatorButtons and rotatorIntervals, implement rotateMirrorStep, isMirrorControlledByButton, syncRotatorButtons and stopAllRotatorButtons; wire rotator behavior into traceLaser and loadGrid so rotator buttons activate intervals that rotate target mirrors automatically and lock out manual rotation. Refactor rotateMirror to reuse rotation step logic and ensure rotator intervals are cleared when advancing levels.
Add per-cell laserSegments tracking and use it to render correct laser orientation: introduce laserSegments global, reset it at trace start, populate entries when tracing, and consult it in loadGrid to choose horizontal vs vertical classes. Update UI styling from a dark to a lighter theme (body and main backgrounds, cell/empty/mirror/wall colors and laser gradients), remove some borders/fit-content sizing, and add a "map" class to the map container in the HTML. These changes fix laser orientation rendering and refresh the game's visual theme.
Add a small utility function in web/assets/js/index.js that increments an element's CSS rotation by 45 degrees. The function reads the element's inline transform (handling an empty value), parses the current rotation angle modulo 360, and sets the new rotate(angle+45) value.