diff --git a/submissions/Visual Timer/.gitignore b/submissions/Visual Timer/.gitignore new file mode 100644 index 00000000..7e7a7ee7 --- /dev/null +++ b/submissions/Visual Timer/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.pnp +.pnp.* +.yarn/* +!.yarn/patches \ No newline at end of file diff --git a/submissions/Visual Timer/README.md b/submissions/Visual Timer/README.md new file mode 100644 index 00000000..984973b1 --- /dev/null +++ b/submissions/Visual Timer/README.md @@ -0,0 +1,314 @@ +# Visual Timer Chrome/Firefox Extension + +

+ Visual Timer Icon +

+ +

+ Version + Chrome + Firefox + Manifest V3 + License +
+ Pure JavaScript + No Dependencies + Settings Synced +

+ +A Chrome extension that provides a visual time-tracking overlay that changes color over time, helping you stay aware of time passing while working on tasks. + +## Screenshots + + +

+ Timer Pixel UI
+ Default Pixel UI +

+ +

+ Time Settings
+ Settings: Time +

+ +

+ Display Setting
+ Settings: Display +

+ +

+ Colors Setting
+ Settings: Colors +

+ +

+ Timer Classic UI
+ Classic UI +

+ +## Features + +- Visual color-changing overlay that transitions through different colors (Blue → Green → Purple → Red) +- Adjustable overlay opacity (1-100%) +- Customizable time settings (up to 24 hours) +- Show/hide time display +- Pause and reset functionality +- Works on all websites +- Special compatibility with YouTube +- Settings sync across Chrome instances +- Automatic pause during system idle +- Choice between Classic and Minecraft-styled Pixel UI + +## Version History + +### 1.5.0 (Current) +- Major UI improvements with pixel-perfect design +- Added alternative Minecraft-styled Pixel UI +- Fixed live timer update issue - timer now updates in real-time without requiring extension click +- Fixed timer display visibility - timer now properly disappears when extension is disabled +- Enhanced code structure and organization +- Improved extension reliability and performance +- Better handling of disabled state +- Added UI switcher to toggle between Classic and Pixel interfaces +- Improved performance with CSS-based textures +- Enhanced cross-browser compatibility +- Style enhancements to both interfaces + +### v1.4.4 +- Implemented gradual opacity increase as timer progresses +- Added gentler visual start with initially reduced opacity +- Fixed permissions policy violations with safer event listeners +- Enhanced cleanup handling for better browser compatibility +- Added fallback event listeners for cross-browser support + +### v1.4.3 +- Fixed multiple script injection issues +- Prevented tab reloading on message failures +- Added protection against duplicate elements +- Improved script messaging and error handling +- Added script presence detection +- Enhanced compatibility with different websites +- Reduced unnecessary tab updates + +### v1.4.2 +- Fixed overlay persistence after disabling extension +- Fixed glitchy behavior after system idle/sleep +- Improved state management across tabs +- Added periodic state verification +- Enhanced visibility state handling +- Improved tab reload behavior +- Added force disable functionality +- Optimized transitions and animations + +### v1.4.1 +- Fixed overlay flickering issue on page load/refresh +- Improved transition animations +- Enhanced initialization timing +- Optimized performance with will-change CSS property + +### v1.4.0 +- Added color stage customization +- Added visual timeline for color transitions +- Added color reset functionality +- Updated interface with color picker controls +- Improved color transition visualization + +### v1.3.1 +- Fixed message handling for better stability +- Improved error handling across browsers +- Enhanced settings synchronization +- Updated documentation + +### v1.3 +- Added adjustable opacity control +- Added opacity sync across browser instances +- Added tooltip for opacity percentage +- Improved settings persistence + +### v1.0 +- Initial release +- Basic timer functionality +- Color transitions +- YouTube compatibility + +## Installation + +### Watch Installation & Usage Guide +[![Watch the Installation & Usage Guide](\assets\Screenshots\Screenshot 2025-02-24 102937.png)](https://youtu.be/ik6qIkiS-jo "Watch the Installation & Usage Guide") +

👆 Click the image above to watch the installation and usage guide on YouTube

+ +### Chrome +1. Clone this repository or download the source code +2. Open Chrome and navigate to `chrome://extensions/` +3. Enable "Developer mode" in the top right corner +4. Click "Load unpacked" in the top left +5. Select the folder containing the extension files + +### Firefox +1. Clone this repository or download the source code +2. Rename `manifest-ff.json` to `manifest.json` (make sure to backup the original Chrome manifest if needed) +3. ### Firefox +1. Clone this repository or download the source code +2. Rename `manifest-ff.json` to `manifest.json` (make sure to backup the original Chrome manifest if needed) +3. Open Firefox and navigate to `about:debugging` +4. Click on "This Firefox" in the left sidebar +5. Click "Load Temporary Add-on" +6. Navigate to your extension folder and select the `manifest.json` file + +Note: Firefox extensions loaded this way are temporary and will be removed when Firefox is closed. For permanent installation, the extension needs to be signed by Mozilla. + +## Usage + +1. Click the extension icon to open the settings popup +2. Toggle between Classic and Pixel UI styles using the UI switch button +3. Set your desired time duration (default is 2 hours) +4. Adjust overlay opacity as needed +5. Customize colors if desired +6. Enable/Disable the timer as needed +7. Use Pause/Resume to control timing + +### Settings + +#### Timer Controls +- **Enable Visual Timer**: Toggle the overlay on/off for all websites +- **Show Time Display**: Toggle the visibility of the time counter +- **Time Until Red**: Set the total duration before the overlay turns red + - Hours: 0-24 + - Minutes: 0-59 +- **Overlay Opacity**: Adjust the transparency of the color overlay (1-100%) + - Lower values make the overlay more transparent + - Higher values make the overlay more visible + - Default: 70% +- **Color Transitions**: Customize the color stages of the timer + - Choose colors for Start, 33%, 66%, and End stages + - Reset to default colors (Blue → Green → Purple → Red) + - Changes apply immediately to all tabs + +#### Control Buttons +- **Reset Timer**: Restart the timer from 0 and reset the color to blue +- **Pause/Resume**: Temporarily stop/start the timer + +### Color Stages + +The overlay transitions through four distinct colors: +1. Blue (Start) +2. Green (33% of set time) +3. Purple (66% of set time) +4. Red (100% of set time) + +### YouTube Compatibility + +When using YouTube, the extension automatically adjusts its overlay to: +- Keep video controls accessible +- Maintain video player visibility +- Use a screen blend mode for better color visibility + +## Tips + +- Use the time display toggle to reduce distractions while keeping the color indicator +- Reset the timer when starting a new task +- Pause the timer during breaks +- The extension automatically pauses when your system is idle +- The settings are synchronized across your Chrome profile +- The overlay works independently on each tab +- Use the opacity slider to find the perfect balance between visibility and non-intrusiveness +- Your opacity settings will sync across all your Chrome instances +- Find your ideal opacity setting based on your screen brightness and website colors +- On dark websites, you might want to use lower opacity values +- On light websites, higher opacity values might work better +- Save different opacity settings for different times of day + +## Technical Details + +- Built with vanilla JavaScript +- Uses Chrome Extension Manifest V3 +- Utilizes Chrome's storage, alarms, idle, and tabs APIs +- Lightweight with minimal performance impact + +## Permissions + +The extension requires the following permissions: +- `storage`: Save your settings and timer state +- `alarms`: Update the timer every second +- `idle`: Detect system idle state +- `tabs`: Apply the overlay to web pages + +## Troubleshooting + +### Common Issues + +1. **Timer Not Appearing or Extension not working** + - Try toggling the "Enable Visual Timer" switch off and on again + - Refresh the webpage you're trying to use the timer on + - Make sure you've granted all necessary permissions + +2. **Time Display Not Showing** + - Toggle the "Show Time Display" switch off and then on + - Check if "Enable Visual Timer" is turned on, as the time display requires the timer to be enabled + +3. **Settings Not Syncing** + - Make sure you're signed into your browser + - Try closing and reopening your browser + - Check if you have sync enabled in your browser settings + +If these steps don't resolve your issue, please don't hesitate to: +1. [Create a new issue](https://github.com/yourusername/visual-timer/issues/new) with details about: + - Your browser and version + - Steps to reproduce the problem + - What you expected to happen + - What actually happened +2. Include any error messages from the browser's developer console (F12) + +## Contributing + +Feel free to submit issues, fork the repository, and create pull requests for any improvements. + +## Browser Support + +### Chrome +- Supports Manifest V3 +- Full feature support +- Persistent settings across sessions +- Syncs across devices when signed in + +### Firefox +- Uses Manifest V2 +- All core features supported +- Temporary installation for development +- Requires Mozilla signing for permanent installation + +## Development + +### Building from Source +1. Clone the repository +2. No build step required - plain JavaScript +3. Load unpacked in Chrome or temporary add-on in Firefox +4. Make changes and reload extension as needed + +### Contributing +We welcome contributions! Please: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test in both Chrome and Firefox +5. Submit a pull request + +### Testing +- Test on both light and dark websites +- Verify YouTube compatibility +- Check all opacity levels +- Confirm sync functionality +- Validate timer accuracy + +## Support + +If you find this extension helpful, you can: +- Star the repository +- Report bugs +- Suggest features +- Share with others +- Contribute improvements + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182049.png b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182049.png new file mode 100644 index 00000000..04fe7ce1 Binary files /dev/null and b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182049.png differ diff --git a/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182112.png b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182112.png new file mode 100644 index 00000000..adef320a Binary files /dev/null and b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182112.png differ diff --git a/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182132.png b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182132.png new file mode 100644 index 00000000..22536095 Binary files /dev/null and b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182132.png differ diff --git a/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182147.png b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182147.png new file mode 100644 index 00000000..15277e59 Binary files /dev/null and b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182147.png differ diff --git a/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182218.png b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182218.png new file mode 100644 index 00000000..a1af25d3 Binary files /dev/null and b/submissions/Visual Timer/assets/Screenshots/Screenshot 2025-03-17 182218.png differ diff --git a/submissions/Visual Timer/visual-timer/icon128.png b/submissions/Visual Timer/assets/icons/icon128.png similarity index 100% rename from submissions/Visual Timer/visual-timer/icon128.png rename to submissions/Visual Timer/assets/icons/icon128.png diff --git a/submissions/Visual Timer/visual-timer/overlay.css b/submissions/Visual Timer/css/overlay.css similarity index 96% rename from submissions/Visual Timer/visual-timer/overlay.css rename to submissions/Visual Timer/css/overlay.css index 3b278f9a..d11329fb 100644 --- a/submissions/Visual Timer/visual-timer/overlay.css +++ b/submissions/Visual Timer/css/overlay.css @@ -1,31 +1,31 @@ -#visual-timer-overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - pointer-events: none; - z-index: 2147483647; - mix-blend-mode: screen; - background-color: hsl(240, 100%, 50%); - transition: background-color 0.5s linear, opacity 0.3s ease-in-out, visibility 0.3s ease-in-out !important; - opacity: 0; /* Start fully transparent */ - will-change: opacity, background-color; /* Optimize for animations */ -} - -#visual-timer-display { - position: fixed; - top: 20px; - right: 20px; - color: white; - font-family: Arial, sans-serif; - font-size: 16px; - text-shadow: 1px 1px 3px rgba(0,0,0,0.7); - z-index: 2147483647; - background: rgba(0,0,0,0.5); - padding: 5px 10px; - border-radius: 4px; - transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; - opacity: 0; /* Start fully transparent */ - will-change: opacity; /* Optimize for animations */ -} +#visual-timer-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 2147483647; + mix-blend-mode: screen; + background-color: hsl(240, 100%, 50%); + transition: background-color 0.5s linear, opacity 0.3s ease-in-out, visibility 0.3s ease-in-out !important; + opacity: 0; /* Start fully transparent */ + will-change: opacity, background-color; /* Optimize for animations */ +} + +#visual-timer-display { + position: fixed; + top: 20px; + right: 20px; + color: white; + font-family: Arial, sans-serif; + font-size: 16px; + text-shadow: 1px 1px 3px rgba(0,0,0,0.7); + z-index: 2147483647; + background: rgba(0,0,0,0.5); + padding: 5px 10px; + border-radius: 4px; + transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; + opacity: 0; /* Start fully transparent */ + will-change: opacity; /* Optimize for animations */ +} \ No newline at end of file diff --git a/submissions/Visual Timer/css/pixel-popup.css b/submissions/Visual Timer/css/pixel-popup.css new file mode 100644 index 00000000..0081b3be --- /dev/null +++ b/submissions/Visual Timer/css/pixel-popup.css @@ -0,0 +1,572 @@ +/* Base styles */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + width: 340px; + height: 500px; + overflow: hidden; + font-family: 'Minecraft', sans-serif; + background: #333333; + color: white; +} + +/* UI Container */ +.pixel-ui { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; +} + +/* CSS-based texture patterns */ +.main-interface, .settings-panel { + /* Dirt texture - brown with noise pattern */ + background-color: #866043; + background-image: + linear-gradient(45deg, #705236 25%, transparent 25%), + linear-gradient(-45deg, #705236 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #705236 75%), + linear-gradient(-45deg, transparent 75%, #705236 75%); + background-size: 8px 8px; + background-position: 0 0, 0 4px, 4px -4px, -4px 0px; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + transition: transform 0.3s ease-in-out; +} + +.main-interface { + display: flex; + flex-direction: column; + align-items: center; + transform: translateX(0); +} + +.settings-panel { + display: flex; + flex-direction: column; + left: 100%; + transform: translateX(0); +} + +/* When settings are active */ +.main-interface.show-settings { + transform: translateX(-100%); +} + +.settings-panel.show { + transform: translateX(-100%); +} + +/* Header styles - uses plank texture */ +.header { + width: 100%; + height: 64px; + display: flex; + align-items: center; + justify-content: center; + background-color: #9e7c5a; + background-image: + linear-gradient(90deg, #876a4c 2px, transparent 2px), + linear-gradient(0deg, #876a4c 2px, transparent 2px); + background-size: 16px 16px; + border-bottom: 2px solid black; + position: relative; +} + +.title { + font-size: 22px; + text-align: center; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.ui-switcher-btn, .settings-btn, .back-btn { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid black; + border-radius: 4px; + /* Stone texture - gray with noise */ + background-color: #888888; + background-image: + linear-gradient(45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(-45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #7a7a7a 75%), + linear-gradient(-45deg, transparent 75%, #7a7a7a 75%); + background-size: 6px 6px; + cursor: pointer; + position: absolute; +} + +.ui-switcher-btn { + left: 8px; +} + +.settings-btn { + right: 8px; +} + +.back-btn { + left: 8px; +} + +.ui-switcher-btn:hover, .settings-btn:hover, .back-btn:hover { + filter: brightness(1.1); +} + +.ui-switcher-icon { + width: 24px; + height: 24px; + /* Checkerboard pattern for UI switcher icon */ + background-color: #8BC34A; + background-image: + linear-gradient(45deg, #5b9630 25%, transparent 25%), + linear-gradient(-45deg, #5b9630 25%, transparent 25%); + background-size: 8px 8px; + border: 2px solid #000; +} + +.settings-btn svg, .back-btn svg { + stroke: white; + filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 1)); +} + +/* Timer Display - uses dark obsidian texture */ +.timer-display { + width: 280px; + height: 100px; + margin-top: 32px; + display: flex; + align-items: center; + justify-content: center; + background-color: #15151b; + background-image: + radial-gradient(#252531 15%, transparent 16%), + radial-gradient(#252531 15%, transparent 16%); + background-size: 8px 8px; + background-position: 0 0, 4px 4px; + border: 2px solid black; +} + +.timer-text { + font-size: 36px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +/* Progress Bar - uses bedrock texture */ +.progress-container { + width: 280px; + height: 30px; + margin-top: 16px; + background-color: #535353; + background-image: + linear-gradient(45deg, #414141 25%, transparent 25%), + linear-gradient(-45deg, #414141 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #414141 75%), + linear-gradient(-45deg, transparent 75%, #414141 75%); + background-size: 8px 8px; + background-position: 0 0, 0 4px, 4px -4px, -4px 0px; + border: 2px solid black; + position: relative; +} + +.progress-bar { + height: 100%; + width: 100%; + /* Water texture - blue with waves */ + background-color: #3498db; + background-image: + linear-gradient(90deg, #2980b9 2px, transparent 2px), + linear-gradient(0deg, #2980b9 2px, transparent 2px); + background-size: 10px 10px; + position: absolute; + top: 0; + left: 0; + transition: width 0.3s linear; +} + +/* Timer Toggle */ +.timer-toggle { + margin-top: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.toggle-label { + font-size: 18px; + margin-right: 8px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.toggle-btn { + width: 48px; + height: 48px; + border: 2px solid black; + border-radius: 4px; + cursor: pointer; + background-position: center; + background-repeat: no-repeat; +} + +.toggle-on { + /* Lever ON texture - on state with lit redstone */ + background-color: #9e9e9e; + position: relative; +} + +.toggle-on::after { + content: ""; + position: absolute; + top: 10px; + left: 20px; + width: 8px; + height: 25px; + background-color: #8BC34A; + transform: rotate(-30deg); +} + +.toggle-off { + /* Lever OFF texture */ + background-color: #7a7a7a; + position: relative; +} + +.toggle-off::after { + content: ""; + position: absolute; + top: 10px; + left: 20px; + width: 8px; + height: 25px; + background-color: #555555; + transform: rotate(30deg); +} + +/* Action Buttons */ +.action-buttons { + display: flex; + gap: 16px; + margin-top: 24px; +} + +.action-btn { + width: 130px; + height: 40px; + font-family: 'Minecraft', sans-serif; + font-size: 16px; + color: white; + border: 2px solid black; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.pause-btn { + /* Stone button texture */ + background-color: #888888; + background-image: + linear-gradient(45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(-45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #7a7a7a 75%), + linear-gradient(-45deg, transparent 75%, #7a7a7a 75%); + background-size: 6px 6px; +} + +.resume-btn { + /* Grass texture for Resume button */ + background-color: #5d9c3d; + background-image: + radial-gradient(#4a8a2a 15%, transparent 16%), + radial-gradient(#4a8a2a 15%, transparent 16%); + background-size: 6px 6px; + background-position: 0 0, 3px 3px; +} + +.reset-btn { + /* Redstone texture for Reset button */ + background-color: #c01818; + background-image: + radial-gradient(#8e1010 15%, transparent 16%), + radial-gradient(#8e1010 15%, transparent 16%); + background-size: 6px 6px; + background-position: 0 0, 3px 3px; +} + +.action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.action-btn svg { + stroke: white; +} + +/* Settings Panel Styles */ +/* Tabs */ +.tabs { + display: flex; + width: 100%; + border-bottom: 2px solid black; +} + +.tab { + flex: 1; + height: 48px; + font-family: 'Minecraft', sans-serif; + font-size: 16px; + color: white; + border: none; + border-right: 2px solid black; + cursor: pointer; + /* Stone texture */ + background-color: #888888; + background-image: + linear-gradient(45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(-45deg, #7a7a7a 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #7a7a7a 75%), + linear-gradient(-45deg, transparent 75%, #7a7a7a 75%); + background-size: 6px 6px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.tab:last-child { + border-right: none; +} + +.tab.active { + /* Plank texture for active tabs */ + background-color: #9e7c5a; + background-image: + linear-gradient(90deg, #876a4c 2px, transparent 2px), + linear-gradient(0deg, #876a4c 2px, transparent 2px); + background-size: 16px 16px; +} + +/* Tab Content */ +.tab-content { + flex: 1; + padding: 16px; + overflow-y: auto; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; +} + +.section-title { + font-size: 20px; + margin-bottom: 24px; + text-align: center; + color: #FBA945; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +/* Time Settings */ +.time-inputs { + display: flex; + gap: 16px; + width: 100%; +} + +.time-input-group { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +.time-input-group input { + width: 100%; + height: 48px; + font-family: 'Minecraft', sans-serif; + font-size: 20px; + color: white; + text-align: center; + border: 2px solid black; + /* Obsidian texture */ + background-color: #15151b; + background-image: + radial-gradient(#252531 15%, transparent 16%), + radial-gradient(#252531 15%, transparent 16%); + background-size: 8px 8px; + background-position: 0 0, 4px 4px; +} + +.time-label { + margin-top: 8px; + font-size: 14px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +/* Hide number input arrows */ +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + appearance: textfield; + -moz-appearance: textfield; +} + +/* Save Button */ +.save-btn { + width: 200px; + height: 40px; + margin: 32px auto 0; + display: flex; + align-items: center; + justify-content: center; + font-family: 'Minecraft', sans-serif; + font-size: 16px; + color: white; + border: 2px solid black; + border-radius: 4px; + cursor: pointer; + /* Plank texture */ + background-color: #9e7c5a; + background-image: + linear-gradient(90deg, #876a4c 2px, transparent 2px), + linear-gradient(0deg, #876a4c 2px, transparent 2px); + background-size: 16px 16px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.save-btn:hover { + filter: brightness(1.1); +} + +/* Colors Tab */ +.color-pickers { + display: flex; + justify-content: space-between; + width: 100%; + margin-bottom: 8px; +} + +.color-picker { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.color-picker input[type="color"] { + width: 48px; + height: 48px; + border: 2px solid black; + background: transparent; + cursor: pointer; +} + +.color-label { + font-size: 12px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.color-preview { + width: 100%; + height: 32px; + margin-top: 16px; + display: flex; + border: 2px solid black; + overflow: hidden; +} + +.color-segment { + flex: 1; + height: 100%; +} + +/* Display Tab */ +.display-setting { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 32px; + width: 100%; +} + +.setting-label { + font-size: 18px; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 1); +} + +.opacity-setting { + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +.slider-container { + position: relative; + width: 100%; + height: 32px; + /* Bedrock texture */ + background-color: #535353; + background-image: + linear-gradient(45deg, #414141 25%, transparent 25%), + linear-gradient(-45deg, #414141 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #414141 75%), + linear-gradient(-45deg, transparent 75%, #414141 75%); + background-size: 8px 8px; + background-position: 0 0, 0 4px, 4px -4px, -4px 0px; + border: 2px solid black; +} + +.slider { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; + z-index: 10; +} + +.slider-fill { + position: absolute; + top: 0; + left: 0; + height: 100%; + /* Water texture */ + background-color: #3498db; + background-image: + linear-gradient(90deg, #2980b9 2px, transparent 2px), + linear-gradient(0deg, #2980b9 2px, transparent 2px); + background-size: 10px 10px; + pointer-events: none; +} + +.slider-knob { + position: absolute; + top: 0; + width: 16px; + height: 100%; + /* Diamond texture - blue with shine */ + background-color: #38c6f4; + background-image: + linear-gradient(45deg, #29a7d1 25%, transparent 25%), + linear-gradient(-45deg, #29a7d1 25%, transparent 25%); + background-size: 6px 6px; + pointer-events: none; +} \ No newline at end of file diff --git a/submissions/Visual Timer/html/pixel-popup.html b/submissions/Visual Timer/html/pixel-popup.html new file mode 100644 index 00000000..8ef22278 --- /dev/null +++ b/submissions/Visual Timer/html/pixel-popup.html @@ -0,0 +1,140 @@ + + + + + + Visual Timer - Pixel UI + + + + +
+
+ +
+ +

VISUAL TIMER

+ +
+ + +
+ 00:00 +
+ + +
+
+
+ + +
+ TIMER: + +
+ + +
+ + +
+
+ + +
+ +
+ +

SETTINGS

+
+ + +
+ + + +
+ + +
+ +
+

Time Settings

+
+
+ + HOURS +
+
+ + MINUTES +
+
+ +
+ + +
+

Color Settings

+
+
+ + START +
+
+ + 33% +
+
+ + 66% +
+
+ + END +
+
+
+
+
+
+
+
+ +
+ + +
+

Display Settings

+
+ SHOW TIME: + +
+
+ OPACITY: 70% +
+ +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/popup.html b/submissions/Visual Timer/html/popup.html similarity index 89% rename from submissions/Visual Timer/visual-timer/popup.html rename to submissions/Visual Timer/html/popup.html index f50eaa13..6f443b59 100644 --- a/submissions/Visual Timer/visual-timer/popup.html +++ b/submissions/Visual Timer/html/popup.html @@ -1,289 +1,321 @@ - - - - - - -

Visual Timer Settings

- - -
- Enable Visual Timer - -
- - -
- Show Time Display - -
- - -
- Time Until Red -
- - hrs - - min -
-
- - -
- Overlay Opacity -
- - 70% -
-
- - -
- Color Transitions -
-
- - Start -
-
- - 33% -
-
- - 66% -
-
- - End -
-
- -
- - -
- - -
- - - - + + + + + + +

Visual Timer Settings

+ + +
+ Enable Visual Timer + +
+ + +
+ Show Time Display + +
+ + +
+ Time Until Red +
+ + hrs + + min +
+
+ + +
+ Overlay Opacity +
+ + 70% +
+
+ + +
+ Color Transitions +
+
+ + Start +
+
+ + 33% +
+
+ + 66% +
+
+ + End +
+
+ +
+ + +
+ + +
+ + + + \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/background.js b/submissions/Visual Timer/js/background.js similarity index 74% rename from submissions/Visual Timer/visual-timer/background.js rename to submissions/Visual Timer/js/background.js index 52323671..fac8b533 100644 --- a/submissions/Visual Timer/visual-timer/background.js +++ b/submissions/Visual Timer/js/background.js @@ -1,273 +1,329 @@ -let totalSeconds = 0; -let isActive = true; -let isEnabled = true; -let isPaused = false; -let targetSeconds = 7200; // Default 2 hours -let opacity = 70; // Default opacity -let colorStages = [ - { hue: 240, hex: '#0000FF', percent: 0 }, // Blue - { hue: 120, hex: '#00FF00', percent: 33 }, // Green - { hue: 270, hex: '#8A2BE2', percent: 66 }, // Purple - { hue: 0, hex: '#FF0000', percent: 100 } // Red -]; - -// Load settings AND timer state -chrome.runtime.onInstalled.addListener(() => { - resetState(); -}); - -chrome.runtime.onStartup.addListener(() => { - resetState(); - loadState(); -}); - -function resetState() { - chrome.storage.local.set({ totalSeconds: 0 }); - chrome.storage.sync.set({ - enabled: true, - isPaused: false, - targetTime: { hours: 2, minutes: 0 }, - opacity: 70, - colorStages: colorStages.map(({ hue, hex }) => ({ hue, hex })) - }); - - // Reset all internal states - totalSeconds = 0; - isActive = true; - isEnabled = true; - isPaused = false; -} - -function loadState() { - chrome.storage.local.get(['totalSeconds'], (result) => { - totalSeconds = result.totalSeconds || 0; - - // Double-check enabled state when loading - chrome.storage.sync.get(['enabled', 'isPaused', 'targetTime', 'opacity'], (syncResult) => { - isEnabled = syncResult.enabled !== false; - isPaused = syncResult.isPaused || false; - if (syncResult.targetTime) { - targetSeconds = (syncResult.targetTime.hours * 3600) + (syncResult.targetTime.minutes * 60); - } - opacity = syncResult.opacity || 70; - - // Force update all tabs with current state - updateAllTabs(); - }); - }); -} - -// Listen for setting changes -chrome.storage.onChanged.addListener((changes) => { - if (changes.enabled !== undefined) { - isEnabled = changes.enabled.newValue; - if (!isEnabled) { - isPaused = false; - totalSeconds = 0; // Reset timer when disabled - chrome.storage.sync.set({ isPaused: false }); - chrome.storage.local.set({ totalSeconds: 0 }); - } - updateAllTabs(); - } - if (changes.isPaused !== undefined) { - isPaused = changes.isPaused.newValue; - updateAllTabs(); - } - if (changes.targetTime) { - const newTime = changes.targetTime.newValue; - targetSeconds = (newTime.hours * 3600) + (newTime.minutes * 60); - updateAllTabs(); - } - if (changes.opacity !== undefined) { - opacity = changes.opacity.newValue; - updateAllTabs(); - } - if (changes.colorStages) { - const newColors = changes.colorStages.newValue; - colorStages = [ - { ...newColors[0], percent: 0 }, - { ...newColors[1], percent: 33 }, - { ...newColors[2], percent: 66 }, - { ...newColors[3], percent: 100 } - ]; - updateAllTabs(); - } -}); - -// Listen for messages from popup -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - switch (message.type) { - case 'resetTimer': - totalSeconds = 0; - chrome.storage.local.set({ totalSeconds: 0 }); - updateAllTabs(); - break; - case 'togglePause': - isPaused = message.isPaused; - updateAllTabs(); - break; - case 'settingsUpdated': - if (message.data?.enabled !== undefined) { - isEnabled = message.data.enabled; - if (!isEnabled) { - isPaused = false; - chrome.storage.sync.set({ isPaused: false }); - } - updateAllTabs(); - } - break; - case 'timeSettingsUpdated': - const newTime = message.targetTime; - targetSeconds = (newTime.hours * 3600) + (newTime.minutes * 60); - updateAllTabs(); - break; - case 'opacityUpdated': - opacity = message.opacity; - updateAllTabs(); - break; - case 'colorStagesUpdated': - const newColors = message.colorStages; - colorStages = [ - { ...newColors[0], percent: 0 }, - { ...newColors[1], percent: 33 }, - { ...newColors[2], percent: 66 }, - { ...newColors[3], percent: 100 } - ]; - updateAllTabs(); - break; - } - sendResponse({ success: true }); - return false; -}); - -// Color logic -function getColorFromTime(seconds) { - if (seconds >= targetSeconds) { - return colorStages[3].hex; // End color - } - - const progress = (seconds / targetSeconds) * 100; - - // Find the current color stage - let startStage = colorStages[0]; - let endStage = colorStages[1]; - - for (let i = 0; i < colorStages.length - 1; i++) { - if (progress >= colorStages[i].percent && progress <= colorStages[i + 1].percent) { - startStage = colorStages[i]; - endStage = colorStages[i + 1]; - break; - } - } - - // Use hex colors directly instead of hue interpolation - return startStage.hex; -} - -// Force updates on tab load -chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { - if (changeInfo.status === 'complete' && tab.url?.startsWith("http")) { - // Double check state before updating tab - chrome.storage.sync.get(['enabled'], (result) => { - if (result.enabled !== isEnabled) { - isEnabled = result.enabled !== false; - } - updateTab(tabId); - }); - } -}); - -// Timer logic with improved state checks -chrome.alarms.create('timer', { periodInMinutes: 1/60 }); -chrome.alarms.onAlarm.addListener((alarm) => { - if (alarm.name === 'timer') { - // Double check enabled state before updating - chrome.storage.sync.get(['enabled', 'isPaused'], (result) => { - isEnabled = result.enabled !== false; - isPaused = result.isPaused || false; - - if (isEnabled && isActive && !isPaused) { - totalSeconds++; - chrome.storage.local.set({ totalSeconds }); - updateAllTabs(); - } - }); - } -}); - -// Activity detection with improved state management -chrome.idle.onStateChanged.addListener((state) => { - const wasActive = isActive; - isActive = state === "active"; - - if (!wasActive && isActive) { - // Coming back from idle, verify state - loadState(); - } else if (wasActive && !isActive) { - // Going idle, save current state - chrome.storage.local.set({ totalSeconds }); - } -}); - -// Helper function to update all tabs with error handling -function updateAllTabs() { - if (!isEnabled) { - chrome.tabs.query({}, (tabs) => { - tabs.forEach((tab) => { - if (tab.id && tab.url?.startsWith("http")) { - try { - chrome.tabs.sendMessage(tab.id, { - type: 'forceDisable' - }).catch(() => {}); - } catch (error) { - console.debug("Tab update error:", tab.id); - } - } - }); - }); - return; - } - - chrome.tabs.query({}, (tabs) => { - tabs.forEach((tab) => { - if (tab.id && tab.url?.startsWith("http")) { - updateTab(tab.id); - } - }); - }); -} - -// Helper function to update a single tab with improved messaging -function updateTab(tabId) { - const message = { - type: 'update', - color: getColorFromTime(totalSeconds), - seconds: totalSeconds, - enabled: isEnabled, - opacity: opacity, - isPaused: isPaused, - targetSeconds: targetSeconds // Add target seconds for opacity calculation - }; - - try { - chrome.tabs.sendMessage(tabId, message) - .catch(() => { - // If message fails, only try to inject content script if scripting API is available - if (chrome.scripting) { - // Check if content script is already injected - chrome.tabs.sendMessage(tabId, { type: 'ping' }) - .catch(() => { - // Only inject if the content script isn't already there - chrome.scripting.executeScript({ - target: { tabId }, - files: ['content-script.js'] - }).catch(err => { - console.debug("Script injection failed:", err); - }); - }); - } - }); - } catch (error) { - console.debug("Tab update error:", tabId); - } -} +let totalSeconds = 0; +let isActive = true; +let isEnabled = true; +let isPaused = false; +let targetSeconds = 7200; // Default 2 hours +let opacity = 70; // Default opacity +let uiMode = 'pixel'; // Changed default UI mode to 'pixel' +let colorStages = [ + { hue: 240, hex: '#0000FF', percent: 0 }, // Blue + { hue: 120, hex: '#00FF00', percent: 33 }, // Green + { hue: 270, hex: '#8A2BE2', percent: 66 }, // Purple + { hue: 0, hex: '#FF0000', percent: 100 } // Red +]; + +// Load settings AND timer state +chrome.runtime.onInstalled.addListener(() => { + resetState(); + + // Set correct popup based on UI mode + updatePopupBasedOnMode(); +}); + +chrome.runtime.onStartup.addListener(() => { + resetState(); + loadState(); + + // Set correct popup based on UI mode + updatePopupBasedOnMode(); +}); + +function resetState() { + chrome.storage.local.set({ totalSeconds: 0 }); + chrome.storage.sync.set({ + enabled: true, + isPaused: false, + targetTime: { hours: 2, minutes: 0 }, + opacity: 70, + uiMode: 'pixel', // Changed default UI mode to 'pixel' + colorStages: colorStages.map(({ hue, hex }) => ({ hue, hex })) + }); + + // Reset all internal states + totalSeconds = 0; + isActive = true; + isEnabled = true; + isPaused = false; +} + +function loadState() { + chrome.storage.local.get(['totalSeconds'], (result) => { + totalSeconds = result.totalSeconds || 0; + + // Double-check enabled state when loading + chrome.storage.sync.get(['enabled', 'isPaused', 'targetTime', 'opacity', 'uiMode'], (syncResult) => { + isEnabled = syncResult.enabled !== false; + isPaused = syncResult.isPaused || false; + if (syncResult.targetTime) { + targetSeconds = (syncResult.targetTime.hours * 3600) + (syncResult.targetTime.minutes * 60); + } + opacity = syncResult.opacity || 70; + uiMode = syncResult.uiMode || 'pixel'; // Changed default fallback to 'pixel' + + // Force update all tabs with current state + updateAllTabs(); + }); + }); +} + +// Listen for setting changes +chrome.storage.onChanged.addListener((changes) => { + if (changes.enabled !== undefined) { + isEnabled = changes.enabled.newValue; + if (!isEnabled) { + isPaused = false; + totalSeconds = 0; // Reset timer when disabled + chrome.storage.sync.set({ isPaused: false }); + chrome.storage.local.set({ totalSeconds: 0 }); + } + updateAllTabs(); + } + if (changes.isPaused !== undefined) { + isPaused = changes.isPaused.newValue; + updateAllTabs(); + } + if (changes.targetTime) { + const newTime = changes.targetTime.newValue; + targetSeconds = (newTime.hours * 3600) + (newTime.minutes * 60); + updateAllTabs(); + } + if (changes.opacity !== undefined) { + opacity = changes.opacity.newValue; + updateAllTabs(); + } + if (changes.uiMode !== undefined) { + uiMode = changes.uiMode.newValue; + updatePopupBasedOnMode(); + } + if (changes.colorStages) { + const newColors = changes.colorStages.newValue; + colorStages = [ + { ...newColors[0], percent: 0 }, + { ...newColors[1], percent: 33 }, + { ...newColors[2], percent: 66 }, + { ...newColors[3], percent: 100 } + ]; + updateAllTabs(); + } +}); + +// Listen for messages from popup +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + switch (message.type) { + case 'resetTimer': + totalSeconds = 0; + chrome.storage.local.set({ totalSeconds: 0 }); + updateAllTabs(); + break; + case 'togglePause': + isPaused = message.isPaused; + updateAllTabs(); + break; + case 'settingsUpdated': + if (message.data?.enabled !== undefined) { + isEnabled = message.data.enabled; + if (!isEnabled) { + isPaused = false; + totalSeconds = 0; // Reset timer when disabled + chrome.storage.sync.set({ isPaused: false }); + chrome.storage.local.set({ totalSeconds: 0 }); + forceDisableAllTabs(); // Force disable all tabs immediately + } + updateAllTabs(); + } + break; + case 'timeSettingsUpdated': + const newTime = message.targetTime; + targetSeconds = (newTime.hours * 3600) + (newTime.minutes * 60); + updateAllTabs(); + break; + case 'opacityUpdated': + opacity = message.opacity; + updateAllTabs(); + break; + case 'colorStagesUpdated': + const newColors = message.colorStages; + colorStages = [ + { ...newColors[0], percent: 0 }, + { ...newColors[1], percent: 33 }, + { ...newColors[2], percent: 66 }, + { ...newColors[3], percent: 100 } + ]; + updateAllTabs(); + break; + case 'switchUI': + uiMode = message.uiMode; + chrome.storage.sync.set({ uiMode }); + updatePopupBasedOnMode(); + break; + case 'getState': + sendResponse({ + totalSeconds, + isEnabled, + isPaused, + targetSeconds, + opacity, + uiMode, + color: getColorFromTime(totalSeconds), + colorStages + }); + break; + } + if (message.type !== 'getState') { + sendResponse({ success: true }); + } + return true; // Keep channel open for async responses +}); + +// Function to update the action popup based on UI mode +function updatePopupBasedOnMode() { + const popupFile = uiMode === 'pixel' ? '/html/pixel-popup.html' : '/html/popup.html'; + if (chrome.action) { // For Manifest V3 + chrome.action.setPopup({ popup: popupFile }); + } else if (chrome.browserAction) { // For Manifest V2 + chrome.browserAction.setPopup({ popup: popupFile }); + } +} + +// Color logic +function getColorFromTime(seconds) { + if (seconds >= targetSeconds) { + return colorStages[3].hex; // End color + } + + const progress = (seconds / targetSeconds) * 100; + + // Find the current color stage + let startStage = colorStages[0]; + let endStage = colorStages[1]; + + for (let i = 0; i < colorStages.length - 1; i++) { + if (progress >= colorStages[i].percent && progress <= colorStages[i + 1].percent) { + startStage = colorStages[i]; + endStage = colorStages[i + 1]; + break; + } + } + + // Use hex colors directly instead of hue interpolation + return startStage.hex; +} + +// Force updates on tab load +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (changeInfo.status === 'complete' && tab.url?.startsWith("http")) { + // Double check state before updating tab + chrome.storage.sync.get(['enabled'], (result) => { + if (result.enabled !== isEnabled) { + isEnabled = result.enabled !== false; + } + updateTab(tabId); + }); + } +}); + +// Timer logic with improved state checks +chrome.alarms.create('timer', { periodInMinutes: 1/60 }); +chrome.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === 'timer') { + // Double check enabled state before updating + chrome.storage.sync.get(['enabled', 'isPaused'], (result) => { + isEnabled = result.enabled !== false; + isPaused = result.isPaused || false; + + if (isEnabled && isActive && !isPaused) { + totalSeconds++; + chrome.storage.local.set({ totalSeconds }); + updateAllTabs(); + } + }); + } +}); + +// Activity detection with improved state management +chrome.idle.onStateChanged.addListener((state) => { + const wasActive = isActive; + isActive = state === "active"; + + if (!wasActive && isActive) { + // Coming back from idle, verify state + loadState(); + } else if (wasActive && !isActive) { + // Going idle, save current state + chrome.storage.local.set({ totalSeconds }); + } +}); + +// Helper function to update all tabs with error handling +function updateAllTabs() { + if (!isEnabled) { + forceDisableAllTabs(); + return; + } + + chrome.tabs.query({}, (tabs) => { + tabs.forEach((tab) => { + if (tab.id && tab.url?.startsWith("http")) { + updateTab(tab.id); + } + }); + }); +} + +// New function to explicitly disable timer in all tabs +function forceDisableAllTabs() { + chrome.tabs.query({}, (tabs) => { + tabs.forEach((tab) => { + if (tab.id && tab.url?.startsWith("http")) { + try { + chrome.tabs.sendMessage(tab.id, { + type: 'forceDisable', + seconds: 0 // Explicitly send 0 seconds to prevent 00:00 display + }).catch(() => {}); + } catch (error) { + console.debug("Tab update error:", tab.id); + } + } + }); + }); +} + +// Helper function to update a single tab with improved messaging +function updateTab(tabId) { + const message = { + type: 'update', + color: getColorFromTime(totalSeconds), + seconds: totalSeconds, + enabled: isEnabled, + opacity: opacity, + isPaused: isPaused, + targetSeconds: targetSeconds // Add target seconds for opacity calculation + }; + + try { + chrome.tabs.sendMessage(tabId, message) + .catch((error) => { + // If message fails, only try to inject content script if scripting API is available + if (chrome.scripting) { + // Check if content script is already injected + chrome.tabs.sendMessage(tabId, { type: 'ping' }) + .catch(() => { + // Only inject if the content script isn't already there + chrome.scripting.executeScript({ + target: { tabId }, + files: ['/js/content-script.js'] + }).then(() => { + // After injection succeeds, send the message again with a delay + setTimeout(() => { + chrome.tabs.sendMessage(tabId, message).catch(() => {}); + }, 200); + }).catch(err => { + console.debug("Script injection failed:", err); + }); + }); + } + }); + } catch (error) { + console.debug("Tab update error:", tabId); + } +} \ No newline at end of file diff --git a/submissions/Visual Timer/js/content-script.js b/submissions/Visual Timer/js/content-script.js new file mode 100644 index 00000000..3f657973 --- /dev/null +++ b/submissions/Visual Timer/js/content-script.js @@ -0,0 +1,451 @@ +// Prevent multiple injections +if (window.hasOwnProperty('__visualTimerInjected')) { + console.debug("[CONTENT] Script already injected, skipping initialization"); +} else { + window.__visualTimerInjected = true; + + console.log("[CONTENT] Script injected into:", window.location.href); + + let overlay = null; + let timeDisplay = null; + let totalSeconds = 0; + let isInitialized = false; + let isEnabled = false; + let targetOpacity = 0.7; // Default target opacity + let targetSeconds = 7200; // Default to 2 hours + let showTime = true; // Default to show time + + // Function to safely create and append elements + function createElements() { + try { + // Check if elements already exist + let existingOverlay = document.getElementById('visual-timer-overlay'); + let existingDisplay = document.getElementById('visual-timer-display'); + + if (existingOverlay) { + overlay = existingOverlay; + } else { + overlay = document.createElement('div'); + overlay.id = 'visual-timer-overlay'; + document.body.appendChild(overlay); + } + + if (existingDisplay) { + timeDisplay = existingDisplay; + } else { + timeDisplay = document.createElement('div'); + timeDisplay.id = 'visual-timer-display'; + document.body.appendChild(timeDisplay); + } + + isInitialized = true; + return true; + } catch (error) { + console.debug("[CONTENT] Error creating elements:", error); + return false; + } + } + + // Attempt to create elements at different stages + function initializeElements() { + if (document.body) { + return createElements(); + } else { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', createElements); + } else { + // If DOM is already loaded but body isn't available, try again after a delay + setTimeout(createElements, 500); + } + return false; + } + } + + // Initialize elements as soon as possible + initializeElements(); + + // Check enabled state and set up initial state once elements are created + function initializeState() { + if (!overlay || !timeDisplay) return; + + try { + chrome.storage.sync.get(['enabled', 'showTime'], (result) => { + isEnabled = result.enabled !== false; + showTime = result.showTime !== false; + + if (!isEnabled) { + // If timer is disabled, hide both overlay and time display + hideOverlay(); + hideTimeDisplay(); + } else { + updateOverlayVisibility(true); + // Only show time display if both timer is enabled AND showTime is true + updateTimeDisplayVisibility(showTime); + + // Get current timer value + try { + chrome.runtime.sendMessage({ type: 'getState' }, (response) => { + if (response && response.totalSeconds !== undefined) { + updateTimeDisplay(response.totalSeconds); + } + }); + } catch (e) { + // Silently handle messaging errors + console.debug("[CONTENT] Error getting timer state:", e); + } + } + }); + } catch (e) { + // Handle storage access errors + console.debug("[CONTENT] Error accessing storage:", e); + } + } + + // Handle settings changes and updates + try { + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'ping') { + sendResponse({ success: true }); + return false; + } + + // Ensure elements exist before handling messages + if (!overlay || !timeDisplay) { + if (createElements()) { + initializeState(); + } + } + + try { + switch (message.type) { + case 'forceDisable': + hideOverlay(); + hideTimeDisplay(); // Always hide time display when force disabled + isEnabled = false; + break; + case 'updateVisibility': + if (!isEnabled) { + hideOverlay(); + hideTimeDisplay(); // Always hide time display when timer is disabled + } else { + // Only update time display visibility if timer is enabled + updateTimeDisplayVisibility(message.showTime); + + // Request current time + try { + chrome.runtime.sendMessage({ type: 'getState' }, (response) => { + if (response && response.totalSeconds !== undefined) { + updateTimeDisplay(response.totalSeconds); + } + }); + } catch (e) { + // Silently handle messaging errors + console.debug("[CONTENT] Error getting timer state:", e); + } + } + break; + case 'update': + isEnabled = message.enabled; + if (message.targetSeconds) { + targetSeconds = message.targetSeconds; + } + + if (!isEnabled) { + hideOverlay(); + hideTimeDisplay(); // Hide time display when timer is disabled + } else { + updateDisplay(message.color, message.seconds, message.enabled, message.opacity); + + // Update time display if timer is enabled + if (message.seconds !== undefined && timeDisplay) { + updateTimeDisplay(message.seconds); + } + } + break; + } + sendResponse({ success: true }); + } catch (error) { + console.debug("[CONTENT] Error handling message:", error); + sendResponse({ success: false, error: error.message }); + } + return false; + }); + } catch (e) { + // Handle runtime connection errors + console.debug("[CONTENT] Error setting up message listener:", e); + } + + // Function to only hide the overlay, not the time display + function hideOverlay() { + if (overlay) { + overlay.style.visibility = 'hidden'; + overlay.style.opacity = '0'; + requestAnimationFrame(() => { + if (overlay && overlay.style.visibility === 'hidden') { + overlay.style.display = 'none'; + } + }); + } + } + + // Function to explicitly hide the time display + function hideTimeDisplay() { + if (timeDisplay) { + timeDisplay.style.visibility = 'hidden'; + timeDisplay.style.opacity = '0'; + requestAnimationFrame(() => { + if (timeDisplay && timeDisplay.style.visibility === 'hidden') { + timeDisplay.style.display = 'none'; + } + }); + } + } + + function updateTimeDisplayVisibility(show) { + if (!timeDisplay) return; + + // Only show time display if extension is enabled AND showTime is true + if (!show || !isEnabled) { + hideTimeDisplay(); + } else { + timeDisplay.style.display = 'block'; + requestAnimationFrame(() => { + if (timeDisplay) { + timeDisplay.style.visibility = 'visible'; + timeDisplay.style.opacity = '1'; + } + }); + } + } + + function updateTimeDisplay(seconds) { + if (!timeDisplay) return; + + totalSeconds = seconds; + const minutes = Math.floor(totalSeconds / 60); + const remainingSeconds = totalSeconds % 60; + timeDisplay.textContent = `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + } + + function updateOverlayVisibility(show) { + if (!overlay) return; + + if (!show) { + hideOverlay(); + } else { + overlay.style.display = 'block'; + requestAnimationFrame(() => { + if (overlay) { + overlay.style.visibility = 'visible'; + } + }); + } + } + + function updateDisplay(color, seconds, enabled, overlayOpacity) { + if (!overlay || !timeDisplay) return; + + // Update time display only if timer is enabled + if (seconds !== undefined && enabled) { + updateTimeDisplay(seconds); + } + + if (!enabled) { + hideOverlay(); + hideTimeDisplay(); // Hide time display when disabled + return; + } + + updateOverlayVisibility(enabled); + + try { + chrome.storage.sync.get(['showTime'], (result) => { + // Only show time display if both timer is enabled AND showTime is true + updateTimeDisplayVisibility(result.showTime !== false); + + if (overlay) { + overlay.style.backgroundColor = color; + + // Calculate a reduced opacity for early stages + const progress = seconds / targetSeconds; + const startOpacity = 0.05; // Start with just 5% of the target opacity + const currentOpacity = startOpacity + (progress * (overlayOpacity / 100 - startOpacity)); + + // Clamp the opacity between the start value and the target + const finalOpacity = Math.min(overlayOpacity / 100, Math.max(startOpacity, currentOpacity)); + + // Store target opacity for future calculations + targetOpacity = overlayOpacity / 100; + + overlay.style.opacity = finalOpacity; + } + }); + } catch (e) { + // Handle storage errors + console.debug("[CONTENT] Error accessing storage:", e); + + // Just use the values we have + if (overlay) { + overlay.style.backgroundColor = color; + + // Calculate opacity without the stored showTime value + const progress = seconds / targetSeconds; + const startOpacity = 0.05; + const currentOpacity = startOpacity + (progress * (overlayOpacity / 100 - startOpacity)); + const finalOpacity = Math.min(overlayOpacity / 100, Math.max(startOpacity, currentOpacity)); + + overlay.style.opacity = finalOpacity; + } + } + } + + // Re-verify elements and state periodically + const stateInterval = setInterval(() => { + if (document.visibilityState === 'visible') { + if (!overlay || !timeDisplay) { + if (createElements()) { + initializeState(); + } + } else { + // Try to check timer state, but don't throw errors if extension context is invalid + try { + chrome.storage.sync.get(['enabled', 'showTime'], (result) => { + try { + const shouldBeEnabled = result.enabled !== false; + const shouldShowTime = result.showTime !== false; + + // If timer is disabled, always hide both overlay and time display + if (!shouldBeEnabled) { + if (isEnabled !== shouldBeEnabled) { + isEnabled = shouldBeEnabled; + hideOverlay(); + hideTimeDisplay(); // Hide time display when disabled + } + } else { + isEnabled = shouldBeEnabled; + // Only update time display visibility if timer is enabled + updateTimeDisplayVisibility(shouldShowTime); + + // Request current time from background if timer is enabled + try { + chrome.runtime.sendMessage({ type: 'getState' }, (response) => { + if (response && response.totalSeconds !== undefined && isEnabled) { + updateTimeDisplay(response.totalSeconds); + } + }); + } catch (e) { + // Silently handle messaging errors + console.debug("[CONTENT] Error getting timer state in interval:", e); + } + } + } catch (error) { + // Silently handle errors + console.debug("[CONTENT] Error in state interval:", error); + } + }); + } catch (e) { + // Handle errors gracefully + console.debug("[CONTENT] Error accessing storage in interval:", e); + } + } + } + }, 5000); + + // Handle visibility changes + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + if (!overlay || !timeDisplay) { + if (createElements()) { + initializeState(); + } + } + + try { + chrome.storage.sync.get(['enabled', 'showTime'], (result) => { + isEnabled = result.enabled !== false; + const shouldShowTime = result.showTime !== false; + + if (!isEnabled) { + hideOverlay(); + hideTimeDisplay(); // Hide time display when disabled + } else { + // Only update time display visibility if timer is enabled + updateTimeDisplayVisibility(shouldShowTime); + + // Request current timer value from background + try { + chrome.runtime.sendMessage({ type: 'getState' }, (response) => { + if (response && response.totalSeconds !== undefined) { + updateTimeDisplay(response.totalSeconds); + } + }); + } catch (e) { + // Silently handle messaging errors + console.debug("[CONTENT] Error getting timer state on visibility change:", e); + } + } + }); + } catch (e) { + // Silently handle storage errors + console.debug("[CONTENT] Error accessing storage on visibility change:", e); + } + } + }); + + // YouTube compatibility fix - only if we're on YouTube + if (window.location.hostname.includes('youtube.com')) { + const addYouTubeStyle = () => { + try { + if (document.head) { + const style = document.createElement('style'); + style.textContent = ` + #player-container { + z-index: 2147483646 !important; + } + #visual-timer-overlay { + mix-blend-mode: screen !important; + } + `; + document.head.appendChild(style); + } else { + setTimeout(addYouTubeStyle, 100); + } + } catch (error) { + console.debug("[CONTENT] Error adding YouTube style:", error); + } + }; + addYouTubeStyle(); + } + + // Use safer cleanup method (pagehide instead of unload) + try { + window.addEventListener('pagehide', cleanupElements); + // Fallback cleanup with beforeunload (more widely supported) + window.addEventListener('beforeunload', cleanupElements); + } catch (error) { + console.debug("[CONTENT] Error adding cleanup listeners:", error); + } + + function cleanupElements() { + try { + if (overlay && overlay.parentNode) { + overlay.parentNode.removeChild(overlay); + } + if (timeDisplay && timeDisplay.parentNode) { + timeDisplay.parentNode.removeChild(timeDisplay); + } + clearInterval(stateInterval); + } catch (error) { + console.debug("[CONTENT] Error during cleanup:", error); + } + } + + // Catch global errors to avoid crashing the content script + window.addEventListener('error', (event) => { + // Only log extension-context errors, don't interfere with the timer functionality + if (event.error && event.error.message && event.error.message.includes('Extension context invalidated')) { + console.debug("[CONTENT] Extension context invalidated via error event - ignoring"); + } + }); +} + diff --git a/submissions/Visual Timer/js/pixel-popup.js b/submissions/Visual Timer/js/pixel-popup.js new file mode 100644 index 00000000..7fd24eb1 --- /dev/null +++ b/submissions/Visual Timer/js/pixel-popup.js @@ -0,0 +1,380 @@ +document.addEventListener('DOMContentLoaded', () => { + // DOM Elements + const mainInterface = document.getElementById('main-interface'); + const settingsPanel = document.getElementById('settings-panel'); + const timerText = document.getElementById('timer-text'); + const progressBar = document.getElementById('progress-bar'); + const toggleBtn = document.getElementById('toggle-btn'); + const pauseBtn = document.getElementById('pause-btn'); + const resetBtn = document.getElementById('reset-btn'); + const settingsBtn = document.getElementById('settings-btn'); + const backBtn = document.getElementById('back-btn'); + const uiSwitcher = document.getElementById('ui-switcher'); + const tabs = document.querySelectorAll('.tab'); + const tabPanes = document.querySelectorAll('.tab-pane'); + const hoursInput = document.getElementById('hours-input'); + const minutesInput = document.getElementById('minutes-input'); + const saveTimeBtn = document.getElementById('save-time-btn'); + const showTimeToggle = document.getElementById('show-time-toggle'); + const opacitySlider = document.getElementById('opacity-slider'); + const opacityValue = document.getElementById('opacity-value'); + const opacityFill = document.getElementById('opacity-fill'); + const sliderKnob = document.getElementById('slider-knob'); + const colorStart = document.getElementById('color-start'); + const color33 = document.getElementById('color-33'); + const color66 = document.getElementById('color-66'); + const colorEnd = document.getElementById('color-end'); + const colorSegments = document.querySelectorAll('.color-segment'); + const resetColorsBtn = document.getElementById('reset-colors-btn'); + + // State variables + let isEnabled = true; + let isPaused = false; + let totalSeconds = 0; + let targetSeconds = 7200; // Default: 2 hours + let currentOpacity = 70; + let currentColorStages = [ + { hue: 240, hex: '#3B82F6' }, // Blue + { hue: 120, hex: '#10B981' }, // Green + { hue: 270, hex: '#8B5CF6' }, // Purple + { hue: 0, hex: '#EF4444' } // Red + ]; + let showTime = true; + let defaultColors = [ + { hue: 240, hex: '#3B82F6' }, // Blue + { hue: 120, hex: '#10B981' }, // Green + { hue: 270, hex: '#8B5CF6' }, // Purple + { hue: 0, hex: '#EF4444' } // Red + ]; + + // Load state from Chrome storage + function loadState() { + chrome.runtime.sendMessage({ type: 'getState' }, (response) => { + if (response) { + totalSeconds = response.totalSeconds || 0; + isEnabled = response.isEnabled !== false; + isPaused = response.isPaused || false; + targetSeconds = response.targetSeconds || 7200; + currentOpacity = response.opacity || 70; + + if (response.colorStages && response.colorStages.length === 4) { + currentColorStages = response.colorStages; + } + + updateUI(); + } + }); + + chrome.storage.sync.get(['showTime'], (result) => { + showTime = result.showTime !== false; + updateShowTimeToggle(); + updateTimerVisibility(); + }); + } + + // Format seconds to MM:SS + function formatTime(seconds) { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + // Update UI based on state + function updateUI() { + // Update timer text + timerText.textContent = formatTime(totalSeconds); + + // Update progress bar + const progressPercent = Math.min(100, (totalSeconds / targetSeconds) * 100); + progressBar.style.width = `${progressPercent}%`; + + const currentProgress = totalSeconds / targetSeconds; + if (currentProgress < 0.33) { + progressBar.style.backgroundColor = currentColorStages[0].hex; + } else if (currentProgress < 0.66) { + progressBar.style.backgroundColor = currentColorStages[1].hex; + } else { + progressBar.style.backgroundColor = currentColorStages[2].hex; + } + + // Update toggle button + toggleBtn.className = isEnabled ? 'toggle-btn toggle-on' : 'toggle-btn toggle-off'; + + // Update pause button + pauseBtn.disabled = !isEnabled; + if (isPaused) { + pauseBtn.classList.remove('pause-btn'); + pauseBtn.classList.add('resume-btn'); + pauseBtn.innerHTML = ` + + RESUME + `; + } else { + pauseBtn.classList.add('pause-btn'); + pauseBtn.classList.remove('resume-btn'); + pauseBtn.innerHTML = ` + + PAUSE + `; + } + + // Update reset button + resetBtn.disabled = !isEnabled; + + // Update inputs + const hours = Math.floor(targetSeconds / 3600); + const minutes = Math.floor((targetSeconds % 3600) / 60); + + hoursInput.value = hours; + minutesInput.value = minutes; + + // Update opacity slider + opacitySlider.value = currentOpacity; + opacityValue.textContent = currentOpacity; + opacityFill.style.width = `${currentOpacity}%`; + sliderKnob.style.left = `calc(${currentOpacity}% - 8px)`; + + // Update color inputs and preview + colorStart.value = currentColorStages[0].hex; + color33.value = currentColorStages[1].hex; + color66.value = currentColorStages[2].hex; + colorEnd.value = currentColorStages[3].hex; + + colorSegments[0].style.backgroundColor = currentColorStages[0].hex; + colorSegments[1].style.backgroundColor = currentColorStages[1].hex; + colorSegments[2].style.backgroundColor = currentColorStages[2].hex; + colorSegments[3].style.backgroundColor = currentColorStages[3].hex; + } + + function updateShowTimeToggle() { + showTimeToggle.className = showTime ? 'toggle-btn toggle-on' : 'toggle-btn toggle-off'; + } + + function updateTimerVisibility() { + // Only show timer if extension is enabled AND showTime is true + timerText.style.opacity = (showTime && isEnabled) ? '1' : '0'; + } + + // Helper function to convert hex to HSL hue + function hexToHue(hex) { + // Convert hex to RGB + const r = parseInt(hex.slice(1, 3), 16) / 255; + const g = parseInt(hex.slice(3, 5), 16) / 255; + const b = parseInt(hex.slice(5, 7), 16) / 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + + let h; + if (max === min) { + h = 0; + } else { + const d = max - min; + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h = Math.round(h * 60); + } + + return h < 0 ? h + 360 : h; + } + + // Event listeners + settingsBtn.addEventListener('click', () => { + mainInterface.classList.add('show-settings'); + settingsPanel.classList.add('show'); + }); + + backBtn.addEventListener('click', () => { + mainInterface.classList.remove('show-settings'); + settingsPanel.classList.remove('show'); + }); + + // Toggle button for enabling/disabling the timer + toggleBtn.addEventListener('click', () => { + isEnabled = !isEnabled; + chrome.storage.sync.set({ enabled: isEnabled }, () => { + chrome.runtime.sendMessage({ + type: 'settingsUpdated', + data: { enabled: isEnabled } + }); + updateUI(); + updateVisibility(); + }); + }); + + pauseBtn.addEventListener('click', () => { + isPaused = !isPaused; + chrome.storage.sync.set({ isPaused }, () => { + chrome.runtime.sendMessage({ + type: 'togglePause', + isPaused + }); + updateUI(); + }); + }); + + resetBtn.addEventListener('click', () => { + chrome.storage.local.set({ totalSeconds: 0 }, () => { + chrome.runtime.sendMessage({ type: 'resetTimer' }); + totalSeconds = 0; + updateUI(); + }); + }); + + // Tab navigation + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const tabName = tab.getAttribute('data-tab'); + + // Remove active class from all tabs and panes + tabs.forEach(t => t.classList.remove('active')); + tabPanes.forEach(p => p.classList.remove('active')); + + // Add active class to clicked tab and corresponding pane + tab.classList.add('active'); + document.getElementById(`${tabName}-tab`).classList.add('active'); + }); + }); + + // Save time button + saveTimeBtn.addEventListener('click', () => { + const hours = parseInt(hoursInput.value) || 0; + const minutes = parseInt(minutesInput.value) || 0; + const newTargetTime = { hours, minutes }; + + chrome.storage.sync.set({ targetTime: newTargetTime }, () => { + chrome.runtime.sendMessage({ + type: 'timeSettingsUpdated', + targetTime: newTargetTime + }); + + targetSeconds = (hours * 3600) + (minutes * 60); + updateUI(); + }); + }); + + // Show time toggle + showTimeToggle.addEventListener('click', () => { + showTime = !showTime; + chrome.storage.sync.set({ showTime }, () => { + updateShowTimeToggle(); + updateTimerVisibility(); + updateVisibility(); + }); + }); + + // Opacity slider + opacitySlider.addEventListener('input', (e) => { + const opacity = parseInt(e.target.value); + opacityFill.style.width = `${opacity}%`; + sliderKnob.style.left = `calc(${opacity}% - 8px)`; + opacityValue.textContent = opacity; + + currentOpacity = opacity; + + // Debounce the storage update + clearTimeout(opacitySlider.timeout); + opacitySlider.timeout = setTimeout(() => { + chrome.storage.sync.set({ opacity }, () => { + chrome.runtime.sendMessage({ + type: 'opacityUpdated', + opacity + }); + }); + }, 100); + }); + + // Color pickers + [colorStart, color33, color66, colorEnd].forEach((picker, index) => { + picker.addEventListener('input', (e) => { + const hex = e.target.value; + currentColorStages[index].hex = hex; + currentColorStages[index].hue = hexToHue(hex); + colorSegments[index].style.backgroundColor = hex; + + // Update the colors in Chrome storage + updateColors(); + }); + }); + + // Reset colors button + resetColorsBtn.addEventListener('click', () => { + currentColorStages = JSON.parse(JSON.stringify(defaultColors)); + updateColors(); + updateUI(); + }); + + // Update colors in storage + function updateColors() { + chrome.storage.sync.set({ colorStages: currentColorStages }, () => { + chrome.runtime.sendMessage({ + type: 'colorStagesUpdated', + colorStages: currentColorStages + }); + }); + } + + // Switch UI mode + uiSwitcher.addEventListener('click', () => { + chrome.runtime.sendMessage({ + type: 'switchUI', + uiMode: 'classic' + }, () => { + window.location.href = '/html/popup.html'; + }); + }); + + // Helper function to update visibility across all tabs + function updateVisibility() { + // Query for all tabs and update visibility state + chrome.tabs.query({}, (tabs) => { + tabs.forEach((tab) => { + if (tab.url?.startsWith("http")) { + try { + // If timer is disabled, we'll explicitly hide everything regardless of showTime setting + if (!isEnabled) { + chrome.tabs.sendMessage(tab.id, { + type: 'forceDisable', + seconds: 0 // Send 0 to prevent 00:00 display + }).catch(() => {}); // Silently handle rejected promises + } else { + // Otherwise, update visibility based on showTime setting + chrome.tabs.sendMessage(tab.id, { + type: 'updateVisibility', + showTime: showTime + }).catch(() => {}); // Silently handle rejected promises + } + } catch (error) { + console.debug('Tab not ready:', tab.id); + } + } + }); + }); + } + + // Listen for timer updates from background + chrome.runtime.onMessage.addListener((message) => { + if (message.type === 'update') { + totalSeconds = message.seconds; + isEnabled = message.enabled; + isPaused = message.isPaused; + targetSeconds = message.targetSeconds; + currentOpacity = message.opacity; + + updateUI(); + } + return false; + }); + + // Initial load + loadState(); +}); \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/popup.js b/submissions/Visual Timer/js/popup.js similarity index 78% rename from submissions/Visual Timer/visual-timer/popup.js rename to submissions/Visual Timer/js/popup.js index c878f6ae..76ea78de 100644 --- a/submissions/Visual Timer/visual-timer/popup.js +++ b/submissions/Visual Timer/js/popup.js @@ -1,223 +1,269 @@ -// Wait for DOM to be fully loaded -document.addEventListener('DOMContentLoaded', () => { - // Load saved settings after DOM is fully loaded - chrome.storage.sync.get(['enabled', 'showTime', 'isPaused', 'targetTime', 'opacity'], (result) => { - document.getElementById('toggleEnabled').checked = result.enabled !== false; - document.getElementById('showTime').checked = result.showTime !== false; - - // Set time inputs - const targetTime = result.targetTime || { hours: 2, minutes: 0 }; - document.getElementById('hoursInput').value = targetTime.hours; - document.getElementById('minutesInput').value = targetTime.minutes; - - // Set opacity - const opacity = result.opacity || 70; - document.getElementById('opacityControl').value = opacity; - document.getElementById('opacityValue').textContent = `${opacity}%`; - - updatePauseButtonText(result.isPaused || false); - updatePauseButtonState(result.enabled !== false); - }); - - // Load color settings - chrome.storage.sync.get(['colorStages'], (result) => { - const defaultColors = [ - { hue: 240, hex: '#0000FF' }, // Blue - { hue: 120, hex: '#00FF00' }, // Green - { hue: 270, hex: '#8A2BE2' }, // Purple - { hue: 0, hex: '#FF0000' } // Red - ]; - - const colors = result.colorStages || defaultColors; - colors.forEach((color, index) => { - document.getElementById(`color${index + 1}`).value = color.hex; - }); - }); - - // Save settings - document.getElementById('toggleEnabled').addEventListener('change', (e) => { - const enabled = e.target.checked; - chrome.storage.sync.set({ enabled }, () => { - chrome.runtime.sendMessage({ - type: 'settingsUpdated', - data: { enabled } - }); - updatePauseButtonState(enabled); - updateTabsVisibility(); - }); - }); - - document.getElementById('showTime').addEventListener('change', (e) => { - const showTime = e.target.checked; - const enabled = document.getElementById('toggleEnabled').checked; - chrome.storage.sync.set({ showTime }, () => { - updateTabsVisibility(); - }); - }); - - // Opacity control - document.getElementById('opacityControl').addEventListener('input', (e) => { - const opacity = parseInt(e.target.value); - document.getElementById('opacityValue').textContent = `${opacity}%`; - chrome.storage.sync.set({ opacity }, () => { - chrome.runtime.sendMessage({ - type: 'opacityUpdated', - opacity: opacity - }); - }); - }); - - // Time input controls - document.getElementById('hoursInput').addEventListener('change', updateTargetTime); - document.getElementById('minutesInput').addEventListener('change', updateTargetTime); - - // Timer controls - document.getElementById('resetTimer').addEventListener('click', () => { - chrome.storage.local.set({ totalSeconds: 0 }, () => { - chrome.runtime.sendMessage({ type: 'resetTimer' }); - }); - }); - - document.getElementById('pauseTimer').addEventListener('click', () => { - chrome.storage.sync.get(['isPaused'], (result) => { - const newPausedState = !result.isPaused; - chrome.storage.sync.set({ isPaused: newPausedState }, () => { - chrome.runtime.sendMessage({ - type: 'togglePause', - isPaused: newPausedState - }); - updatePauseButtonText(newPausedState); - }); - }); - }); - - // Color picker event listeners - ['color1', 'color2', 'color3', 'color4'].forEach((id, index) => { - document.getElementById(id).addEventListener('change', (e) => { - updateColors(); - }); - }); - - // Reset colors button - document.getElementById('resetColors').addEventListener('click', () => { - const defaultColors = [ - { hue: 240, hex: '#0000FF' }, // Blue - { hue: 120, hex: '#00FF00' }, // Green - { hue: 270, hex: '#8A2BE2' }, // Purple - { hue: 0, hex: '#FF0000' } // Red - ]; - - defaultColors.forEach((color, index) => { - document.getElementById(`color${index + 1}`).value = color.hex; - }); - - updateColors(); - }); -}); - -// Helper function to safely update tab visibility -function updateTabsVisibility() { - const enabled = document.getElementById('toggleEnabled').checked; - const showTime = document.getElementById('showTime').checked; - - // First, update storage to ensure consistent state - chrome.storage.sync.set({ enabled, showTime }, () => { - // Then update only active tabs to reduce overhead - chrome.tabs.query({ active: true }, (tabs) => { - tabs.forEach((tab) => { - if (tab.url?.startsWith("http")) { - try { - chrome.tabs.sendMessage(tab.id, { - type: 'updateVisibility', - showTime: showTime && enabled - }).catch(() => {}); // Silently handle rejected promises - } catch (error) { - console.debug('Tab not ready:', tab.id); - } - } - }); - }); - }); -} - -// Time input controls -function updateTargetTime() { - const hours = parseInt(document.getElementById('hoursInput').value) || 0; - const minutes = parseInt(document.getElementById('minutesInput').value) || 0; - const targetTime = { hours, minutes }; - - chrome.storage.sync.set({ targetTime }, () => { - chrome.runtime.sendMessage({ - type: 'timeSettingsUpdated', - targetTime - }); - }); -} - -// Helper functions -function updatePauseButtonText(isPaused) { - const pauseButton = document.getElementById('pauseTimer'); - if (pauseButton) { - pauseButton.textContent = isPaused ? 'Resume' : 'Pause'; - pauseButton.classList.toggle('btn-primary', isPaused); - pauseButton.classList.toggle('btn-secondary', !isPaused); - } -} - -function updatePauseButtonState(enabled) { - const pauseButton = document.getElementById('pauseTimer'); - if (pauseButton) { - pauseButton.disabled = !enabled; - pauseButton.style.opacity = enabled ? '1' : '0.5'; - } -} - -// Helper function to convert hex to HSL hue -function hexToHue(hex) { - // Convert hex to RGB - const r = parseInt(hex.slice(1, 3), 16) / 255; - const g = parseInt(hex.slice(3, 5), 16) / 255; - const b = parseInt(hex.slice(5, 7), 16) / 255; - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - - let h; - if (max === min) { - h = 0; - } else { - const d = max - min; - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h = Math.round(h * 60); - } - - return h < 0 ? h + 360 : h; -} - -// Function to update color settings -function updateColors() { - const colorStages = [1, 2, 3, 4].map(num => { - const hex = document.getElementById(`color${num}`).value; - return { - hex: hex, - hue: hexToHue(hex) - }; - }); - - chrome.storage.sync.set({ colorStages }, () => { - chrome.runtime.sendMessage({ - type: 'colorStagesUpdated', - colorStages - }); - }); -} +// Wait for DOM to be fully loaded +document.addEventListener('DOMContentLoaded', () => { + // First check if we need to redirect to pixel UI + chrome.storage.sync.get(['uiMode'], (result) => { + if (result.uiMode === 'pixel') { + window.location.href = '/html/pixel-popup.html'; + return; + } + + // Continue with classic UI initialization + initClassicUI(); + }); +}); + +function initClassicUI() { + // Load saved settings after DOM is fully loaded + chrome.storage.sync.get(['enabled', 'showTime', 'isPaused', 'targetTime', 'opacity'], (result) => { + document.getElementById('toggleEnabled').checked = result.enabled !== false; + document.getElementById('showTime').checked = result.showTime !== false; + + // Set time inputs + const targetTime = result.targetTime || { hours: 2, minutes: 0 }; + document.getElementById('hoursInput').value = targetTime.hours; + document.getElementById('minutesInput').value = targetTime.minutes; + + // Set opacity + const opacity = result.opacity || 70; + document.getElementById('opacityControl').value = opacity; + document.getElementById('opacityValue').textContent = `${opacity}%`; + + updatePauseButtonText(result.isPaused || false); + updatePauseButtonState(result.enabled !== false); + }); + + // Load color settings + chrome.storage.sync.get(['colorStages'], (result) => { + const defaultColors = [ + { hue: 240, hex: '#0000FF' }, // Blue + { hue: 120, hex: '#00FF00' }, // Green + { hue: 270, hex: '#8A2BE2' }, // Purple + { hue: 0, hex: '#FF0000' } // Red + ]; + + const colors = result.colorStages || defaultColors; + colors.forEach((color, index) => { + document.getElementById(`color${index + 1}`).value = color.hex; + }); + }); + + // Add UI mode switcher + const header = document.querySelector('h3'); + const uiSwitcher = document.createElement('div'); + uiSwitcher.className = 'ui-switcher'; + uiSwitcher.innerHTML = ` + + `; + + // Insert after the header + header.parentNode.insertBefore(uiSwitcher, header.nextSibling); + + // Add event listener to the UI toggle button + document.getElementById('pixelUiToggle').addEventListener('click', () => { + // Switch to pixel UI + chrome.runtime.sendMessage({ + type: 'switchUI', + uiMode: 'pixel' + }, () => { + window.location.href = '/html/pixel-popup.html'; + }); + }); + + // Save settings + document.getElementById('toggleEnabled').addEventListener('change', (e) => { + const enabled = e.target.checked; + chrome.storage.sync.set({ enabled }, () => { + chrome.runtime.sendMessage({ + type: 'settingsUpdated', + data: { enabled } + }); + updatePauseButtonState(enabled); + updateTabsVisibility(); + }); + }); + + document.getElementById('showTime').addEventListener('change', (e) => { + const showTime = e.target.checked; + const enabled = document.getElementById('toggleEnabled').checked; + chrome.storage.sync.set({ showTime }, () => { + updateTabsVisibility(); + }); + }); + + // Opacity control + document.getElementById('opacityControl').addEventListener('input', (e) => { + const opacity = parseInt(e.target.value); + document.getElementById('opacityValue').textContent = `${opacity}%`; + chrome.storage.sync.set({ opacity }, () => { + chrome.runtime.sendMessage({ + type: 'opacityUpdated', + opacity: opacity + }); + }); + }); + + // Time input controls + document.getElementById('hoursInput').addEventListener('change', updateTargetTime); + document.getElementById('minutesInput').addEventListener('change', updateTargetTime); + + // Timer controls + document.getElementById('resetTimer').addEventListener('click', () => { + chrome.storage.local.set({ totalSeconds: 0 }, () => { + chrome.runtime.sendMessage({ type: 'resetTimer' }); + }); + }); + + document.getElementById('pauseTimer').addEventListener('click', () => { + chrome.storage.sync.get(['isPaused'], (result) => { + const newPausedState = !result.isPaused; + chrome.storage.sync.set({ isPaused: newPausedState }, () => { + chrome.runtime.sendMessage({ + type: 'togglePause', + isPaused: newPausedState + }); + updatePauseButtonText(newPausedState); + }); + }); + }); + + // Color picker event listeners + ['color1', 'color2', 'color3', 'color4'].forEach((id, index) => { + document.getElementById(id).addEventListener('change', (e) => { + updateColors(); + }); + }); + + // Reset colors button + document.getElementById('resetColors').addEventListener('click', () => { + const defaultColors = [ + { hue: 240, hex: '#0000FF' }, // Blue + { hue: 120, hex: '#00FF00' }, // Green + { hue: 270, hex: '#8A2BE2' }, // Purple + { hue: 0, hex: '#FF0000' } // Red + ]; + + defaultColors.forEach((color, index) => { + document.getElementById(`color${index + 1}`).value = color.hex; + }); + + updateColors(); + }); +} + +// Helper function to safely update tab visibility +function updateTabsVisibility() { + const enabled = document.getElementById('toggleEnabled').checked; + const showTime = document.getElementById('showTime').checked; + + // First, update storage to ensure consistent state + chrome.storage.sync.set({ enabled, showTime }, () => { + // Then update all tabs with the new visibility state + chrome.tabs.query({}, (tabs) => { + tabs.forEach((tab) => { + if (tab.url?.startsWith("http")) { + try { + // If timer is disabled, we'll explicitly hide everything + if (!enabled) { + chrome.tabs.sendMessage(tab.id, { + type: 'forceDisable', + seconds: 0 // Send 0 to prevent 00:00 display + }).catch(() => {}); // Silently handle rejected promises + } else { + // Otherwise, update visibility based on showTime setting + chrome.tabs.sendMessage(tab.id, { + type: 'updateVisibility', + showTime: showTime + }).catch(() => {}); // Silently handle rejected promises + } + } catch (error) { + console.debug('Tab not ready:', tab.id); + } + } + }); + }); + }); +} + +// Time input controls +function updateTargetTime() { + const hours = parseInt(document.getElementById('hoursInput').value) || 0; + const minutes = parseInt(document.getElementById('minutesInput').value) || 0; + const targetTime = { hours, minutes }; + + chrome.storage.sync.set({ targetTime }, () => { + chrome.runtime.sendMessage({ + type: 'timeSettingsUpdated', + targetTime + }); + }); +} + +// Helper functions +function updatePauseButtonText(isPaused) { + const pauseButton = document.getElementById('pauseTimer'); + if (pauseButton) { + pauseButton.textContent = isPaused ? 'Resume' : 'Pause'; + pauseButton.classList.toggle('btn-primary', isPaused); + pauseButton.classList.toggle('btn-secondary', !isPaused); + } +} + +function updatePauseButtonState(enabled) { + const pauseButton = document.getElementById('pauseTimer'); + if (pauseButton) { + pauseButton.disabled = !enabled; + pauseButton.style.opacity = enabled ? '1' : '0.5'; + } +} + +// Helper function to convert hex to HSL hue +function hexToHue(hex) { + // Convert hex to RGB + const r = parseInt(hex.slice(1, 3), 16) / 255; + const g = parseInt(hex.slice(3, 5), 16) / 255; + const b = parseInt(hex.slice(5, 7), 16) / 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + + let h; + if (max === min) { + h = 0; + } else { + const d = max - min; + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h = Math.round(h * 60); + } + + return h < 0 ? h + 360 : h; +} + +// Function to update color settings +function updateColors() { + const colorStages = [1, 2, 3, 4].map(num => { + const hex = document.getElementById(`color${num}`).value; + return { + hex: hex, + hue: hexToHue(hex) + }; + }); + + chrome.storage.sync.set({ colorStages }, () => { + chrome.runtime.sendMessage({ + type: 'colorStagesUpdated', + colorStages + }); + }); +} \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/manifest-ff.json b/submissions/Visual Timer/manifest-ff.json similarity index 57% rename from submissions/Visual Timer/visual-timer/manifest-ff.json rename to submissions/Visual Timer/manifest-ff.json index e1f46a28..a79fa696 100644 --- a/submissions/Visual Timer/visual-timer/manifest-ff.json +++ b/submissions/Visual Timer/manifest-ff.json @@ -1,39 +1,42 @@ -{ - "manifest_version": 2, - "name": "Visual Timer", - "version": "1.4.4", - "description": "A visual timer that changes color over time to help track time visually, with adjustable opacity", - "browser_specific_settings": { - "gecko": { - "id": "visual-timer@yourdomain.com", - "strict_min_version": "57.0" - } - }, - "permissions": [ - "storage", - "alarms", - "idle", - "tabs", - "", - "activeTab", - "tabs", - "webNavigation" - ], - "background": { - "scripts": ["background.js"], - "persistent": true - }, - "content_scripts": [ - { - "matches": [""], - "css": ["overlay.css"], - "js": ["content-script.js"], - "all_frames": true, - "run_at": "document_end" - } - ], - "browser_action": { - "default_popup": "popup.html", - "default_icon": "icon128.png" - } -} +{ + "manifest_version": 2, + "name": "Visual Timer", + "version": "1.5.0", + "description": "A visual timer that changes color over time to help track time visually, with adjustable opacity and choice of UI styles", + "browser_specific_settings": { + "gecko": { + "id": "marv3rickism@gmail.com", + "strict_min_version": "57.0" + } + }, + "permissions": [ + "storage", + "alarms", + "idle", + "tabs", + "", + "activeTab", + "tabs", + "webNavigation" + ], + "background": { + "scripts": ["/js/background.js"], + "persistent": true + }, + "content_scripts": [ + { + "matches": [""], + "css": ["/css/overlay.css"], + "js": ["/js/content-script.js"], + "all_frames": true, + "run_at": "document_end" + } + ], + "browser_action": { + "default_popup": "/html/pixel-popup.html", + "default_icon": "/assets/icons/icon128.png" + }, + "web_accessible_resources": [ + "images/*" + ] +} \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/manifest.json b/submissions/Visual Timer/manifest.json similarity index 50% rename from submissions/Visual Timer/visual-timer/manifest.json rename to submissions/Visual Timer/manifest.json index 4885ae99..d9c01ee7 100644 --- a/submissions/Visual Timer/visual-timer/manifest.json +++ b/submissions/Visual Timer/manifest.json @@ -1,33 +1,39 @@ -{ - "manifest_version": 3, - "name": "Visual Timer", - "version": "1.4.4", - "description": "A visual timer that changes color over time to help track time visually, with adjustable opacity", - "permissions": [ - "storage", - "alarms", - "idle", - "tabs", - "scripting" - ], - "host_permissions": [ - "" - ], - "background": { - "service_worker": "background.js", - "type": "module" - }, - "content_scripts": [ - { - "matches": [""], - "css": ["overlay.css"], - "js": ["content-script.js"], - "all_frames": true, - "run_at": "document_end" - } - ], - "action": { - "default_popup": "popup.html", - "default_icon": "icon128.png" - } -} +{ + "manifest_version": 3, + "name": "Visual Timer", + "version": "1.5.0", + "description": "A visual timer that changes color over time to help track time visually, with adjustable opacity and choice of UI styles", + "permissions": [ + "storage", + "alarms", + "idle", + "tabs", + "scripting" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "/js/background.js", + "type": "module" + }, + "content_scripts": [ + { + "matches": [""], + "css": ["/css/overlay.css"], + "js": ["/js/content-script.js"], + "all_frames": true, + "run_at": "document_end" + } + ], + "action": { + "default_popup": "/html/pixel-popup.html", + "default_icon": "/assets/icons/icon128.png" + }, + "web_accessible_resources": [ + { + "resources": ["images/*"], + "matches": [""] + } + ] +} \ No newline at end of file diff --git a/submissions/Visual Timer/visual-timer/README.md b/submissions/Visual Timer/visual-timer/README.md deleted file mode 100644 index aa14da1f..00000000 --- a/submissions/Visual Timer/visual-timer/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# Visual Timer Chrome/Firefox Extension - -

- Visual Timer Icon -

- -

- Version - Chrome - Firefox - Manifest V3 - License -
- Pure JavaScript - No Dependencies - Settings Synced -

- -A Chrome extension that provides a visual time-tracking overlay that changes color over time, helping you stay aware of time passing while working on tasks. - -## Screenshots - -

- Timer Initial State
- Interface of extension -

- -

- Timer Mid State
- Transitioning through green -

- -

- Timer Late State
- more view of it -

- -

- Timer Final State
- overlay on youtube video -

- -## Features - -- Visual color-changing overlay that transitions through different colors (Blue → Green → Purple → Red) -- Adjustable overlay opacity (1-100%) -- Customizable time settings (up to 24 hours) -- Show/hide time display -- Pause and reset functionality -- Works on all websites -- Special compatibility with YouTube -- Settings sync across Chrome instances -- Automatic pause during system idle - -## Version History - -### v1.4.4 (Current) -- Implemented gradual opacity increase as timer progresses -- Added gentler visual start with initially reduced opacity -- Fixed permissions policy violations with safer event listeners -- Enhanced cleanup handling for better browser compatibility -- Added fallback event listeners for cross-browser support - -### v1.4.3 -- Fixed multiple script injection issues -- Prevented tab reloading on message failures -- Added protection against duplicate elements -- Improved script messaging and error handling -- Added script presence detection -- Enhanced compatibility with different websites -- Reduced unnecessary tab updates - -### v1.4.2 -- Fixed overlay persistence after disabling extension -- Fixed glitchy behavior after system idle/sleep -- Improved state management across tabs -- Added periodic state verification -- Enhanced visibility state handling -- Improved tab reload behavior -- Added force disable functionality -- Optimized transitions and animations - -### v1.4.1 -- Fixed overlay flickering issue on page load/refresh -- Improved transition animations -- Enhanced initialization timing -- Optimized performance with will-change CSS property - -### v1.4.0 -- Added color stage customization -- Added visual timeline for color transitions -- Added color reset functionality -- Updated interface with color picker controls -- Improved color transition visualization - -### v1.3.1 -- Fixed message handling for better stability -- Improved error handling across browsers -- Enhanced settings synchronization -- Updated documentation - -### v1.3 -- Added adjustable opacity control -- Added opacity sync across browser instances -- Added tooltip for opacity percentage -- Improved settings persistence - -### v1.0 -- Initial release -- Basic timer functionality -- Color transitions -- YouTube compatibility - -## Installation - -### Watch Installation & Usage Guide -[![Watch the Installation & Usage Guide](recording/Screenshot 2025-02-24 102937.png)](https://youtu.be/ik6qIkiS-jo "Watch the Installation & Usage Guide") -

👆 Click the image above to watch the installation and usage guide on YouTube

- -### Chrome -1. Clone this repository or download the source code -2. Open Chrome and navigate to `chrome://extensions/` -3. Enable "Developer mode" in the top right corner -4. Click "Load unpacked" in the top left -5. Select the folder containing the extension files - -### Firefox -1. Clone this repository or download the source code -2. Rename `manifest-ff.json` to `manifest.json` (make sure to backup the original Chrome manifest if needed) -3. Open Firefox and diff --git a/submissions/Visual Timer/visual-timer/content-script.js b/submissions/Visual Timer/visual-timer/content-script.js deleted file mode 100644 index c802dab4..00000000 --- a/submissions/Visual Timer/visual-timer/content-script.js +++ /dev/null @@ -1,315 +0,0 @@ -// Prevent multiple injections -if (window.hasOwnProperty('__visualTimerInjected')) { - console.debug("[CONTENT] Script already injected, skipping initialization"); -} else { - window.__visualTimerInjected = true; - - console.log("[CONTENT] Script injected into:", window.location.href); - - let overlay = null; - let timeDisplay = null; - let totalSeconds = 0; - let isInitialized = false; - let isEnabled = false; - let targetOpacity = 0.7; // Default target opacity - let targetSeconds = 7200; // Default to 2 hours - - // Function to safely create and append elements - function createElements() { - if (!document.body) { - console.debug("[CONTENT] Document body not ready, will retry"); - return false; - } - - try { - // Only create elements if they don't exist and previous ones aren't in the DOM - const existingOverlay = document.getElementById('visual-timer-overlay'); - const existingDisplay = document.getElementById('visual-timer-display'); - - if (existingOverlay) { - overlay = existingOverlay; - } else if (!overlay) { - overlay = document.createElement('div'); - overlay.id = 'visual-timer-overlay'; - overlay.style.display = 'none'; - overlay.style.opacity = '0'; - document.body.appendChild(overlay); - } - - if (existingDisplay) { - timeDisplay = existingDisplay; - } else if (!timeDisplay) { - timeDisplay = document.createElement('div'); - timeDisplay.id = 'visual-timer-display'; - timeDisplay.style.display = 'none'; - timeDisplay.style.opacity = '0'; - document.body.appendChild(timeDisplay); - } - - return true; - } catch (error) { - console.debug("[CONTENT] Error creating elements:", error); - return false; - } - } - - // Attempt to create elements at different stages - function initializeElements() { - if (!createElements()) { - // If creation fails, try again when DOM is ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - if (!createElements()) { - // If it still fails, try one last time after a short delay - setTimeout(createElements, 500); - } - }); - } else { - // If DOM is already ready, try again after a short delay - setTimeout(createElements, 500); - } - } - } - - // Initialize elements as soon as possible - initializeElements(); - - // Check enabled state and set up initial state once elements are created - function initializeState() { - if (!overlay || !timeDisplay) return; - - chrome.storage.sync.get(['enabled', 'showTime'], (result) => { - isEnabled = result.enabled !== false; - const showTime = result.showTime !== false; - - if (!isEnabled) { - ensureDisabled(); - } else { - updateOverlayVisibility(true); - updateTimeDisplayVisibility(showTime); - } - }); - } - - // Handle settings changes and updates - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === 'ping') { - sendResponse({ success: true }); - return false; - } - - // Ensure elements exist before handling messages - if (!overlay || !timeDisplay) { - if (createElements()) { - initializeState(); - } - } - - try { - switch (message.type) { - case 'forceDisable': - ensureDisabled(); - break; - case 'updateVisibility': - if (!isEnabled) { - ensureDisabled(); - } else { - updateTimeDisplayVisibility(message.showTime); - } - break; - case 'update': - isEnabled = message.enabled; - if (message.targetSeconds) { - targetSeconds = message.targetSeconds; - } - if (!isEnabled) { - ensureDisabled(); - } else { - updateDisplay(message.color, message.seconds, message.enabled, message.opacity); - } - break; - } - sendResponse({ success: true }); - } catch (error) { - console.error("[CONTENT] Error handling message:", error); - sendResponse({ success: false, error: error.message }); - } - return false; - }); - - function ensureDisabled() { - if (overlay) { - overlay.style.visibility = 'hidden'; - overlay.style.opacity = '0'; - requestAnimationFrame(() => { - overlay.style.display = 'none'; - }); - } - if (timeDisplay) { - timeDisplay.style.visibility = 'hidden'; - timeDisplay.style.opacity = '0'; - requestAnimationFrame(() => { - timeDisplay.style.display = 'none'; - }); - } - } - - function updateTimeDisplayVisibility(show) { - if (!timeDisplay || !isEnabled) return; - - if (!show) { - timeDisplay.style.visibility = 'hidden'; - timeDisplay.style.opacity = '0'; - requestAnimationFrame(() => { - timeDisplay.style.display = 'none'; - }); - } else { - timeDisplay.style.display = 'block'; - requestAnimationFrame(() => { - timeDisplay.style.visibility = 'visible'; - timeDisplay.style.opacity = '1'; - }); - } - } - - function updateOverlayVisibility(show) { - if (!overlay) return; - - if (!show) { - overlay.style.visibility = 'hidden'; - overlay.style.opacity = '0'; - requestAnimationFrame(() => { - overlay.style.display = 'none'; - }); - } else { - overlay.style.display = 'block'; - requestAnimationFrame(() => { - overlay.style.visibility = 'visible'; - }); - } - } - - function updateDisplay(color, seconds, enabled, overlayOpacity) { - if (!overlay || !timeDisplay) return; - - if (!enabled) { - ensureDisabled(); - return; - } - - updateOverlayVisibility(enabled); - - chrome.storage.sync.get(['showTime'], (result) => { - updateTimeDisplayVisibility(result.showTime && enabled); - if (overlay) { - overlay.style.backgroundColor = color; - - // Calculate a reduced opacity for early stages - const progress = seconds / targetSeconds; - const startOpacity = 0.05; // Start with just 5% of the target opacity - const currentOpacity = startOpacity + (progress * (overlayOpacity / 100 - startOpacity)); - - // Clamp the opacity between the start value and the target - const finalOpacity = Math.min(overlayOpacity / 100, Math.max(startOpacity, currentOpacity)); - - // Store target opacity for future calculations - targetOpacity = overlayOpacity / 100; - - overlay.style.opacity = finalOpacity; - } - }); - - if (seconds !== undefined && timeDisplay) { - totalSeconds = seconds; - const minutes = Math.floor(totalSeconds / 60); - const remainingSeconds = totalSeconds % 60; - timeDisplay.textContent = `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; - } - } - - // Re-verify elements and state periodically - setInterval(() => { - if (document.visibilityState === 'visible') { - if (!overlay || !timeDisplay) { - if (createElements()) { - initializeState(); - } - } else { - chrome.storage.sync.get(['enabled'], (result) => { - const shouldBeEnabled = result.enabled !== false; - if (shouldBeEnabled !== isEnabled) { - isEnabled = shouldBeEnabled; - if (!isEnabled) { - ensureDisabled(); - } - } - }); - } - } - }, 5000); - - // Handle visibility changes - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - if (!overlay || !timeDisplay) { - if (createElements()) { - initializeState(); - } - } - chrome.storage.sync.get(['enabled'], (result) => { - isEnabled = result.enabled !== false; - if (!isEnabled) { - ensureDisabled(); - } - }); - } - }); - - // YouTube compatibility fix - only if we're on YouTube - if (window.location.hostname.includes('youtube.com')) { - const addYouTubeStyle = () => { - try { - if (document.head) { - const style = document.createElement('style'); - style.textContent = ` - #player-container { - z-index: 2147483646 !important; - } - #visual-timer-overlay { - mix-blend-mode: screen !important; - } - `; - document.head.appendChild(style); - } else { - setTimeout(addYouTubeStyle, 100); - } - } catch (error) { - console.debug("[CONTENT] Error adding YouTube style:", error); - } - }; - addYouTubeStyle(); - } - - // Use safer cleanup method (pagehide instead of unload) - try { - window.addEventListener('pagehide', cleanupElements); - // Fallback cleanup with beforeunload (more widely supported) - window.addEventListener('beforeunload', cleanupElements); - } catch (error) { - console.debug("[CONTENT] Error adding cleanup listeners:", error); - } - - function cleanupElements() { - try { - if (overlay && overlay.parentNode) { - overlay.parentNode.removeChild(overlay); - } - if (timeDisplay && timeDisplay.parentNode) { - timeDisplay.parentNode.removeChild(timeDisplay); - } - } catch (error) { - console.debug("[CONTENT] Error during cleanup:", error); - } - } -} - diff --git a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 102937.png b/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 102937.png deleted file mode 100644 index 3c4f4290..00000000 Binary files a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 102937.png and /dev/null differ diff --git a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103013.png b/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103013.png deleted file mode 100644 index 8b7b0818..00000000 Binary files a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103013.png and /dev/null differ diff --git a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103033.png b/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103033.png deleted file mode 100644 index 24e94a3f..00000000 Binary files a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103033.png and /dev/null differ diff --git a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103209.png b/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103209.png deleted file mode 100644 index 2623c2af..00000000 Binary files a/submissions/Visual Timer/visual-timer/recording/Screenshot 2025-02-24 103209.png and /dev/null differ