Add customizable color preferences with separate settings window

## New Features
- **Customizable Progress Bar Colors**: Users can now customize the colors for normal, warning, and danger states through a dedicated settings window
- **Separate Settings Window**: Settings now open in a frameless, resizable window (500x680px) with live preview of color changes
- **CSS Variables**: Converted hardcoded colors to CSS custom properties for dynamic theming

## Files Modified
- **main.js**: Added settings window creation, color preference storage (electron-store), and IPC handlers
- **preload.js**: Exposed color preference and settings IPC methods to renderer
- **app.js**: Implemented color preference loading and live updates from settings window
- **styles.css**: Added CSS variables for customizable colors, updated scrollbar styling
- **index.html**: Removed unused settings overlay
- **.gitignore**: Added CLAUDE_NOTES.md to prevent credential leaks

## Files Added
- **src/renderer/settings.html**: Settings window UI with 2-column color picker layout
- **src/renderer/settings.js**: Settings window logic and color management
- **src/renderer/settings-styles.css**: Settings window styling
- **CHANGES.md**: Tracking document for modifications

## Technical Details
- Color preferences stored in electron-store with encryption
- Live color updates via IPC communication between settings and main windows
- Default color scheme: Purple (normal), Orange (warning), Red (danger)
- Settings accessible via tray menu or settings button

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude 2025-12-24 10:21:36 -05:00
parent d616b885b5
commit 5f9ffff8f6
10 changed files with 615 additions and 62 deletions

1
.gitignore vendored
View file

@ -36,3 +36,4 @@ yarn-error.log*
# Cache
.cache/
.temp/
CLAUDE_NOTES.md

39
CHANGES.md Normal file
View file

@ -0,0 +1,39 @@
# Changes for Upstream PR
## Feature: Customizable Progress Bar Colors
### Modified Files
#### main.js
- Added `DEFAULT_COLOR_PREFERENCES` constant with default color scheme (normal, warning, danger states)
- Added IPC handler `get-color-preferences` to retrieve stored color preferences from electron-store
- Added IPC handler `set-color-preferences` to save color preferences to electron-store
- Storage schema: `colorPreferences: { normal: {start, end}, warning: {start, end}, danger: {start, end} }`
#### src/renderer/styles.css
- TODO: Add CSS custom properties (--color-normal-start, --color-normal-end, etc.)
- TODO: Convert hardcoded gradient colors to use CSS variables
- TODO: Apply to both .progress-fill and .timer-progress elements
#### src/renderer/app.js
- TODO: Add `applyColorPreferences()` function to set CSS custom properties
- TODO: Load and apply color preferences on init
- TODO: Add event listeners for color picker changes
#### src/renderer/index.html
- TODO: Add color picker UI in settings overlay (6 inputs for simplified scheme)
- TODO: Add "Reset to Defaults" button
#### preload.js
- TODO: Expose `getColorPreferences` and `setColorPreferences` IPC methods
---
## Feature: Dynamic Tray Icon with Usage Display (Planned)
- Not yet started
---
## Storage Changes
- Using existing electron-store, no additional files
- New keys: `colorPreferences`, `trayDisplayMode` (planned)

68
main.js
View file

@ -10,11 +10,14 @@ const store = new Store({
let mainWindow = null;
let loginWindow = null;
let silentLoginWindow = null;
let settingsWindow = null;
let tray = null;
// Window configuration
const WIDGET_WIDTH = 480;
const WIDGET_HEIGHT = 140;
const SETTINGS_WIDTH = 500;
const SETTINGS_HEIGHT = 680;
function createMainWindow() {
// Load saved position or use defaults
@ -347,7 +350,7 @@ function createTray() {
{
label: 'Settings',
click: () => {
// TODO: Open settings window
createSettingsWindow();
}
},
{
@ -380,6 +383,40 @@ function createTray() {
}
}
function createSettingsWindow() {
if (settingsWindow) {
settingsWindow.focus();
return;
}
settingsWindow = new BrowserWindow({
width: SETTINGS_WIDTH,
height: SETTINGS_HEIGHT,
title: 'Settings - Claude Usage Widget',
frame: false,
resizable: true,
minimizable: true,
maximizable: false,
icon: path.join(__dirname, 'assets/icon.ico'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
settingsWindow.loadFile('src/renderer/settings.html');
settingsWindow.on('closed', () => {
settingsWindow = null;
});
// Development tools
if (process.env.NODE_ENV === 'development') {
settingsWindow.webContents.openDevTools({ mode: 'detach' });
}
}
// IPC Handlers
ipcMain.handle('get-credentials', () => {
return {
@ -416,6 +453,10 @@ ipcMain.on('open-login', () => {
createLoginWindow();
});
ipcMain.on('open-settings', () => {
createSettingsWindow();
});
ipcMain.on('minimize-window', () => {
if (mainWindow) mainWindow.hide();
});
@ -443,6 +484,31 @@ ipcMain.on('open-external', (event, url) => {
shell.openExternal(url);
});
// Color preferences handlers
const DEFAULT_COLOR_PREFERENCES = {
normal: { start: '#8b5cf6', end: '#a78bfa' },
warning: { start: '#f59e0b', end: '#fbbf24' },
danger: { start: '#ef4444', end: '#f87171' }
};
ipcMain.handle('get-color-preferences', () => {
const saved = store.get('colorPreferences');
return saved || DEFAULT_COLOR_PREFERENCES;
});
ipcMain.handle('set-color-preferences', (event, preferences) => {
store.set('colorPreferences', preferences);
return true;
});
ipcMain.handle('notify-color-change', (event, preferences) => {
// Notify main window to update colors
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('colors-changed', preferences);
}
return true;
});
ipcMain.handle('fetch-usage-data', async () => {
console.log('[Main] fetch-usage-data handler called');
const sessionKey = store.get('sessionKey');

View file

@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
minimizeWindow: () => ipcRenderer.send('minimize-window'),
closeWindow: () => ipcRenderer.send('close-window'),
openLogin: () => ipcRenderer.send('open-login'),
openSettings: () => ipcRenderer.send('open-settings'),
// Window position
getWindowPosition: () => ipcRenderer.invoke('get-window-position'),
@ -36,5 +37,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
// API
fetchUsageData: () => ipcRenderer.invoke('fetch-usage-data'),
openExternal: (url) => ipcRenderer.send('open-external', url)
openExternal: (url) => ipcRenderer.send('open-external', url),
// Color preferences
getColorPreferences: () => ipcRenderer.invoke('get-color-preferences'),
setColorPreferences: (preferences) => ipcRenderer.invoke('set-color-preferences', preferences),
notifyColorChange: (preferences) => ipcRenderer.invoke('notify-color-change', preferences),
onColorsChanged: (callback) => {
ipcRenderer.on('colors-changed', (event, preferences) => callback(preferences));
}
});

View file

@ -27,11 +27,7 @@ const elements = {
weeklyTimer: document.getElementById('weeklyTimer'),
weeklyTimeText: document.getElementById('weeklyTimeText'),
settingsBtn: document.getElementById('settingsBtn'),
settingsOverlay: document.getElementById('settingsOverlay'),
closeSettingsBtn: document.getElementById('closeSettingsBtn'),
logoutBtn: document.getElementById('logoutBtn'),
coffeeBtn: document.getElementById('coffeeBtn')
settingsBtn: document.getElementById('settingsBtn')
};
// Initialize
@ -39,6 +35,10 @@ async function init() {
setupEventListeners();
credentials = await window.electronAPI.getCredentials();
// Load and apply color preferences
const colorPrefs = await window.electronAPI.getColorPreferences();
applyColorPreferences(colorPrefs);
if (credentials.sessionKey && credentials.organizationId) {
showMainContent();
await fetchUsageData();
@ -69,24 +69,9 @@ function setupEventListeners() {
window.electronAPI.closeWindow(); // Exit application completely
});
// Settings calls
// Settings button
elements.settingsBtn.addEventListener('click', () => {
elements.settingsOverlay.style.display = 'flex';
});
elements.closeSettingsBtn.addEventListener('click', () => {
elements.settingsOverlay.style.display = 'none';
});
elements.logoutBtn.addEventListener('click', async () => {
await window.electronAPI.deleteCredentials();
elements.settingsOverlay.style.display = 'none';
showLoginRequired();
window.electronAPI.openLogin();
});
elements.coffeeBtn.addEventListener('click', () => {
window.electronAPI.openExternal('https://paypal.me/SlavomirDurej?country.x=GB&locale.x=en_GB');
window.electronAPI.openSettings();
});
// Listen for login success
@ -123,6 +108,12 @@ function setupEventListeners() {
console.log('Silent login failed, manual login required');
showLoginRequired();
});
// Listen for color preference changes from settings window
window.electronAPI.onColorsChanged((preferences) => {
console.log('Colors changed, applying new preferences');
applyColorPreferences(preferences);
});
}
// Fetch usage data from Claude API
@ -381,6 +372,23 @@ function showError(message) {
console.error(message);
}
// Color preference management
function applyColorPreferences(prefs) {
const root = document.documentElement;
// Apply normal colors
root.style.setProperty('--color-normal-start', prefs.normal.start);
root.style.setProperty('--color-normal-end', prefs.normal.end);
// Apply warning colors
root.style.setProperty('--color-warning-start', prefs.warning.start);
root.style.setProperty('--color-warning-end', prefs.warning.end);
// Apply danger colors
root.style.setProperty('--color-danger-start', prefs.danger.start);
root.style.setProperty('--color-danger-end', prefs.danger.end);
}
// Auto-update management
function startAutoUpdate() {
stopAutoUpdate();

View file

@ -120,32 +120,6 @@
<span id="lastUpdate" style="display: none;"></span>
</div>
<!-- Settings Overlay -->
<div class="settings-overlay" id="settingsOverlay" style="display: none;">
<div class="settings-content">
<button class="icon-close-settings-btn" id="closeSettingsBtn" title="Close">×</button>
<p class="disclaimer">
<strong>Disclaimer:</strong> Unofficial tool not affiliated with Anthropic. Use at your own
discretion.
</p>
<button class="coffee-btn" id="coffeeBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
<line x1="6" y1="2" x2="6" y2="4" />
<line x1="10" y1="2" x2="10" y2="4" />
<line x1="14" y1="2" x2="14" y2="4" />
</svg>
If you find this useful, buy me a coffee!
</button>
<div class="settings-actions">
<button class="logout-btn" id="logoutBtn">Log Out</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,230 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%);
color: #e0e0e0;
overflow: hidden;
}
/* Title Bar */
.title-bar {
-webkit-app-region: drag;
background: rgba(0, 0, 0, 0.3);
padding: 8px 12px;
padding-left: 6px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
height: 36px;
}
.title {
color: #e0e0e0;
font-family: 'Libre Baskerville', serif;
font-size: 14px;
font-weight: 400;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 8px;
}
.app-logo {
width: 19px;
height: 19px;
border-radius: 6px;
opacity: 0.8;
}
.controls {
-webkit-app-region: no-drag;
display: flex;
gap: 8px;
margin-right: -10px;
}
.control-btn {
width: 28px;
height: 28px;
border: none;
background: rgba(255, 255, 255, 0.05);
color: #a0a0a0;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s ease;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.close-btn:hover {
background: #e74c3c;
color: white;
}
#settings-app {
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.settings-container {
max-width: 600px;
margin: 0 auto;
padding: 24px;
flex: 1;
}
.settings-section {
margin-bottom: 24px;
}
.settings-section h2 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #e0e0e0;
}
/* 2-Column Grid for Color Pickers */
.color-grid-2col {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.color-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.color-item label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #a0a0a0;
text-align: center;
}
.color-item input[type="color"] {
width: 80px;
height: 50px;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
background: transparent;
cursor: pointer;
padding: 4px;
transition: all 0.2s ease;
}
.color-item input[type="color"]:hover {
border-color: rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.color-item input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.color-item input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 6px;
}
/* Buttons */
.btn-secondary,
.btn-danger,
.btn-coffee {
width: 100%;
padding: 10px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
border: none;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: #e0e0e0;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.btn-danger {
background: transparent;
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.5);
}
.btn-danger:hover {
background: rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.7);
}
.btn-coffee {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
color: #1a1a1a;
}
.btn-coffee:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(255, 165, 0, 0.4);
}
.btn-coffee svg {
flex-shrink: 0;
}
/* Divider */
.divider {
width: 100%;
height: 1px;
background: rgba(255, 255, 255, 0.15);
margin: 24px 0;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Disclaimer */
.disclaimer {
font-size: 11px;
color: #666;
text-align: center;
line-height: 1.5;
}
.disclaimer strong {
color: #888;
}
/* Disclaimer spacing */
.disclaimer {
margin-top: 16px;
}

105
src/renderer/settings.html Normal file
View file

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings - Claude Usage Widget</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="settings-styles.css">
</head>
<body>
<div id="settings-app">
<!-- Title Bar -->
<div class="title-bar" id="titleBar">
<div class="title">
<img src="../../assets/logo.png" alt="Logo" class="app-logo">
<span>Settings</span>
</div>
<div class="controls">
<button class="control-btn minimize-btn" id="minimizeBtn" title="Minimize"></button>
<button class="control-btn close-btn" id="closeBtn" title="Close">×</button>
</div>
</div>
<div class="settings-container">
<!-- Color Customization -->
<section class="settings-section">
<h2>Customize Colors</h2>
<div class="color-grid-2col">
<div class="color-item">
<label>Normal Start</label>
<input type="color" id="colorNormalStart" value="#8b5cf6">
</div>
<div class="color-item">
<label>Normal End</label>
<input type="color" id="colorNormalEnd" value="#a78bfa">
</div>
<div class="color-item">
<label>Warning Start</label>
<input type="color" id="colorWarningStart" value="#f59e0b">
</div>
<div class="color-item">
<label>Warning End</label>
<input type="color" id="colorWarningEnd" value="#fbbf24">
</div>
<div class="color-item">
<label>Danger Start</label>
<input type="color" id="colorDangerStart" value="#ef4444">
</div>
<div class="color-item">
<label>Danger End</label>
<input type="color" id="colorDangerEnd" value="#f87171">
</div>
</div>
<button class="btn-secondary" id="resetColorsBtn">Reset to Defaults</button>
</section>
<div class="divider"></div>
<!-- Account Actions -->
<section class="settings-section">
<h2>Account</h2>
<button class="btn-danger" id="logoutBtn">Log Out</button>
</section>
<div class="divider"></div>
<!-- Support -->
<section class="settings-section">
<button class="btn-coffee" id="coffeeBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
<line x1="6" y1="2" x2="6" y2="4" />
<line x1="10" y1="2" x2="10" y2="4" />
<line x1="14" y1="2" x2="14" y2="4" />
</svg>
If you find this useful, buy me a coffee!
</button>
<!-- Disclaimer -->
<p class="disclaimer">
<strong>Disclaimer:</strong> Unofficial tool not affiliated with Anthropic. Use at your own
discretion.
</p>
</section>
</div>
</div>
<script src="settings.js"></script>
</body>
</html>

107
src/renderer/settings.js Normal file
View file

@ -0,0 +1,107 @@
// DOM elements
const elements = {
colorNormalStart: document.getElementById('colorNormalStart'),
colorNormalEnd: document.getElementById('colorNormalEnd'),
colorWarningStart: document.getElementById('colorWarningStart'),
colorWarningEnd: document.getElementById('colorWarningEnd'),
colorDangerStart: document.getElementById('colorDangerStart'),
colorDangerEnd: document.getElementById('colorDangerEnd'),
resetColorsBtn: document.getElementById('resetColorsBtn'),
logoutBtn: document.getElementById('logoutBtn'),
coffeeBtn: document.getElementById('coffeeBtn'),
minimizeBtn: document.getElementById('minimizeBtn'),
closeBtn: document.getElementById('closeBtn')
};
// Initialize
async function init() {
setupEventListeners();
// Load and apply color preferences
const colorPrefs = await window.electronAPI.getColorPreferences();
loadColorPickerValues(colorPrefs);
}
// Event Listeners
function setupEventListeners() {
// Color picker event listeners
const colorInputs = [
elements.colorNormalStart,
elements.colorNormalEnd,
elements.colorWarningStart,
elements.colorWarningEnd,
elements.colorDangerStart,
elements.colorDangerEnd
];
colorInputs.forEach(input => {
input.addEventListener('change', async () => {
await saveCurrentColors();
});
});
elements.resetColorsBtn.addEventListener('click', async () => {
const defaults = {
normal: { start: '#8b5cf6', end: '#a78bfa' },
warning: { start: '#f59e0b', end: '#fbbf24' },
danger: { start: '#ef4444', end: '#f87171' }
};
await window.electronAPI.setColorPreferences(defaults);
loadColorPickerValues(defaults);
// Notify main window to update colors immediately
await window.electronAPI.notifyColorChange(defaults);
});
elements.logoutBtn.addEventListener('click', async () => {
await window.electronAPI.deleteCredentials();
window.close();
window.electronAPI.openLogin();
});
elements.coffeeBtn.addEventListener('click', () => {
window.electronAPI.openExternal('https://paypal.me/SlavomirDurej?country.x=GB&locale.x=en_GB');
});
// Window controls
elements.minimizeBtn.addEventListener('click', () => {
window.electronAPI.minimizeWindow();
});
elements.closeBtn.addEventListener('click', () => {
window.close();
});
}
// Helper functions
function loadColorPickerValues(prefs) {
elements.colorNormalStart.value = prefs.normal.start;
elements.colorNormalEnd.value = prefs.normal.end;
elements.colorWarningStart.value = prefs.warning.start;
elements.colorWarningEnd.value = prefs.warning.end;
elements.colorDangerStart.value = prefs.danger.start;
elements.colorDangerEnd.value = prefs.danger.end;
}
async function saveCurrentColors() {
const prefs = {
normal: {
start: elements.colorNormalStart.value,
end: elements.colorNormalEnd.value
},
warning: {
start: elements.colorWarningStart.value,
end: elements.colorWarningEnd.value
},
danger: {
start: elements.colorDangerStart.value,
end: elements.colorDangerEnd.value
}
};
await window.electronAPI.setColorPreferences(prefs);
// Notify main window to update colors
await window.electronAPI.notifyColorChange(prefs);
}
// Start the application
init();

View file

@ -1,3 +1,17 @@
:root {
/* Customizable color scheme - normal state */
--color-normal-start: #8b5cf6;
--color-normal-end: #a78bfa;
/* Warning state (>=75% usage) */
--color-warning-start: #f59e0b;
--color-warning-end: #fbbf24;
/* Danger state (>=90% usage) */
--color-danger-start: #ef4444;
--color-danger-end: #f87171;
}
* {
margin: 0;
padding: 0;
@ -339,7 +353,7 @@ body {
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #8b5cf6 0%, #a78bfa 100%);
background: linear-gradient(90deg, var(--color-normal-start) 0%, var(--color-normal-end) 100%);
border-radius: 3px;
transition: width 0.6s ease;
position: relative;
@ -349,8 +363,8 @@ body {
/* Shimmer removed */
.progress-fill.weekly {
background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%);
box-shadow: 0 0 10px rgba(59, 130, 246, 0.3);
background: linear-gradient(90deg, var(--color-normal-start) 0%, var(--color-normal-end) 100%);
box-shadow: 0 0 10px rgba(139, 92, 246, 0.3);
}
@ -378,13 +392,13 @@ body {
.timer-progress {
fill: none;
stroke: #8b5cf6;
stroke: var(--color-normal-start);
stroke-width: 4;
transition: stroke-dashoffset 0.6s ease;
}
.timer-progress.weekly {
stroke: #3b82f6;
stroke: var(--color-normal-start);
}
.timer-text {
@ -545,31 +559,31 @@ body {
}
::-webkit-scrollbar-track {
background: transparent;
background: rgba(0, 0, 0, 0.2);
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.25);
}
/* Warning states */
.progress-fill.warning {
background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 100%);
background: linear-gradient(90deg, var(--color-warning-start) 0%, var(--color-warning-end) 100%);
}
.progress-fill.danger {
background: linear-gradient(90deg, #ef4444 0%, #f87171 100%);
background: linear-gradient(90deg, var(--color-danger-start) 0%, var(--color-danger-end) 100%);
}
.timer-progress.warning {
stroke: #f59e0b;
stroke: var(--color-warning-start);
}
.timer-progress.danger {
stroke: #ef4444;
stroke: var(--color-danger-start);
}