code quality changes
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m3s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 17s

This commit is contained in:
Brian Bjarke Jensen
2026-01-27 21:21:55 +01:00
parent b2aa71b95b
commit ec0e1f6140
11 changed files with 664 additions and 506 deletions
+2 -1
View File
@@ -31,6 +31,7 @@ def readiness_check() -> dict[str, str]:
if redis_uri:
try:
import redis
redis_client = redis.from_url(redis_uri)
redis_client.ping()
except Exception as exc:
@@ -51,6 +52,7 @@ def readiness_check() -> dict[str, str]:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import text
engine = create_engine(postgres_uri)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
with SessionLocal() as db:
@@ -105,7 +107,6 @@ def service_info() -> dict:
if not features["postgresql"]:
config["data_dir"] = os.getenv("DATA_DIR", "/data")
return {
"service": "baby-monitor",
"version": "0.1.0",
+193 -189
View File
@@ -3,201 +3,205 @@
* Usage: Include this before your chart initialization code
*/
(function() {
'use strict';
// Helper to get effective theme
function getEffectiveTheme() {
return window.getEffectiveTheme ? window.getEffectiveTheme() : 'light';
}
// Get theme-appropriate colors for Chart.js
window.getChartColors = function() {
const isDark = getEffectiveTheme() === 'dark';
return {
// Grid lines
grid: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
gridMajor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)',
// Text colors
text: isDark ? '#b0b0b0' : '#666666',
textPrimary: isDark ? '#e0e0e0' : '#333333',
// Data series colors (optimized for background)
series: isDark ? [
'rgba(139, 158, 255, 0.8)', // Brighter purple
'rgba(110, 197, 255, 0.8)', // Brighter blue
'rgba(255, 179, 255, 0.8)', // Brighter pink
'rgba(129, 199, 132, 0.8)', // Brighter green
'rgba(255, 183, 77, 0.8)', // Brighter orange
'rgba(156, 107, 184, 0.8)', // Brighter deep purple
'rgba(77, 255, 255, 0.8)', // Brighter cyan
'rgba(255, 138, 155, 0.8)', // Brighter red
] : [
'rgba(102, 126, 234, 0.8)', // Purple
'rgba(118, 75, 162, 0.8)', // Deep purple
'rgba(255, 159, 64, 0.8)', // Orange
'rgba(76, 175, 80, 0.8)', // Green
'rgba(244, 67, 54, 0.8)', // Red
'rgba(156, 39, 176, 0.8)', // Violet
'rgba(102, 126, 234, 0.6)', // Light purple
'rgba(118, 75, 162, 0.6)', // Light deep purple
],
// Solid data colors (for pie charts, etc.)
solidSeries: isDark ? [
'#8b9eff',
'#6ec5ff',
'#ffb3ff',
'#81c784',
'#ffb74d',
'#9c6bb8',
'#4dffff',
'#ff8a9b',
] : [
'#667eea',
'#764ba2',
'#ff9f40',
'#4caf50',
'#f44336',
'#9c27b0',
'#667eea',
'#764ba2',
],
// Background colors
background: isDark ? '#2d2d2d' : '#f8f9fa',
};
(function () {
"use strict";
// Helper to get effective theme
function getEffectiveTheme() {
return window.getEffectiveTheme ? window.getEffectiveTheme() : "light";
}
// Get theme-appropriate colors for Chart.js
window.getChartColors = function () {
const isDark = getEffectiveTheme() === "dark";
return {
// Grid lines
grid: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)",
gridMajor: isDark ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.1)",
// Text colors
text: isDark ? "#b0b0b0" : "#666666",
textPrimary: isDark ? "#e0e0e0" : "#333333",
// Data series colors (optimized for background)
series: isDark
? [
"rgba(139, 158, 255, 0.8)", // Brighter purple
"rgba(110, 197, 255, 0.8)", // Brighter blue
"rgba(255, 179, 255, 0.8)", // Brighter pink
"rgba(129, 199, 132, 0.8)", // Brighter green
"rgba(255, 183, 77, 0.8)", // Brighter orange
"rgba(156, 107, 184, 0.8)", // Brighter deep purple
"rgba(77, 255, 255, 0.8)", // Brighter cyan
"rgba(255, 138, 155, 0.8)", // Brighter red
]
: [
"rgba(102, 126, 234, 0.8)", // Purple
"rgba(118, 75, 162, 0.8)", // Deep purple
"rgba(255, 159, 64, 0.8)", // Orange
"rgba(76, 175, 80, 0.8)", // Green
"rgba(244, 67, 54, 0.8)", // Red
"rgba(156, 39, 176, 0.8)", // Violet
"rgba(102, 126, 234, 0.6)", // Light purple
"rgba(118, 75, 162, 0.6)", // Light deep purple
],
// Solid data colors (for pie charts, etc.)
solidSeries: isDark
? [
"#8b9eff",
"#6ec5ff",
"#ffb3ff",
"#81c784",
"#ffb74d",
"#9c6bb8",
"#4dffff",
"#ff8a9b",
]
: [
"#667eea",
"#764ba2",
"#ff9f40",
"#4caf50",
"#f44336",
"#9c27b0",
"#667eea",
"#764ba2",
],
// Background colors
background: isDark ? "#2d2d2d" : "#f8f9fa",
};
// Get default Chart.js options with theme support
window.getChartOptions = function(customOptions = {}) {
const colors = window.getChartColors();
const defaults = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: colors.text,
font: {
size: 12
}
}
},
tooltip: {
backgroundColor: getEffectiveTheme() === 'dark' ? '#3a3a3a' : '#ffffff',
titleColor: colors.textPrimary,
bodyColor: colors.text,
borderColor: colors.grid,
borderWidth: 1,
}
};
// Get default Chart.js options with theme support
window.getChartOptions = function (customOptions = {}) {
const colors = window.getChartColors();
const defaults = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: colors.text,
font: {
size: 12,
},
scales: {}
};
// Deep merge customOptions with defaults
return mergeDeep(defaults, customOptions);
},
},
tooltip: {
backgroundColor:
getEffectiveTheme() === "dark" ? "#3a3a3a" : "#ffffff",
titleColor: colors.textPrimary,
bodyColor: colors.text,
borderColor: colors.grid,
borderWidth: 1,
},
},
scales: {},
};
// Get theme-appropriate scale options
window.getChartScaleOptions = function(axis = 'x', customOptions = {}) {
const colors = window.getChartColors();
const defaults = {
grid: {
color: colors.grid,
borderColor: colors.gridMajor,
},
ticks: {
color: colors.text,
font: {
size: 12
}
},
title: {
color: colors.textPrimary,
font: {
size: 13,
weight: '600'
}
}
};
return mergeDeep(defaults, customOptions);
// Deep merge customOptions with defaults
return mergeDeep(defaults, customOptions);
};
// Get theme-appropriate scale options
window.getChartScaleOptions = function (axis = "x", customOptions = {}) {
const colors = window.getChartColors();
const defaults = {
grid: {
color: colors.grid,
borderColor: colors.gridMajor,
},
ticks: {
color: colors.text,
font: {
size: 12,
},
},
title: {
color: colors.textPrimary,
font: {
size: 13,
weight: "600",
},
},
};
// Helper function to deep merge objects
function mergeDeep(target, source) {
const output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = mergeDeep(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
});
return mergeDeep(defaults, customOptions);
};
// Helper function to deep merge objects
function mergeDeep(target, source) {
const output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach((key) => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = mergeDeep(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
return output;
});
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
return output;
}
function isObject(item) {
return item && typeof item === "object" && !Array.isArray(item);
}
// Function to update all charts on theme change
window.updateChartsForTheme = function (chartInstances) {
if (!Array.isArray(chartInstances)) {
chartInstances = [chartInstances];
}
// Function to update all charts on theme change
window.updateChartsForTheme = function(chartInstances) {
if (!Array.isArray(chartInstances)) {
chartInstances = [chartInstances];
}
const colors = window.getChartColors();
chartInstances.forEach(chart => {
if (!chart) return;
// Update grid colors
if (chart.options.scales) {
Object.keys(chart.options.scales).forEach(axis => {
if (chart.options.scales[axis].grid) {
chart.options.scales[axis].grid.color = colors.grid;
chart.options.scales[axis].grid.borderColor = colors.gridMajor;
}
if (chart.options.scales[axis].ticks) {
chart.options.scales[axis].ticks.color = colors.text;
}
if (chart.options.scales[axis].title) {
chart.options.scales[axis].title.color = colors.textPrimary;
}
});
}
// Update legend colors
if (chart.options.plugins && chart.options.plugins.legend) {
if (chart.options.plugins.legend.labels) {
chart.options.plugins.legend.labels.color = colors.text;
}
}
// Update tooltip colors
if (chart.options.plugins && chart.options.plugins.tooltip) {
chart.options.plugins.tooltip.backgroundColor =
getEffectiveTheme() === 'dark' ? '#3a3a3a' : '#ffffff';
chart.options.plugins.tooltip.titleColor = colors.textPrimary;
chart.options.plugins.tooltip.bodyColor = colors.text;
chart.options.plugins.tooltip.borderColor = colors.grid;
}
// Update the chart
chart.update();
const colors = window.getChartColors();
chartInstances.forEach((chart) => {
if (!chart) return;
// Update grid colors
if (chart.options.scales) {
Object.keys(chart.options.scales).forEach((axis) => {
if (chart.options.scales[axis].grid) {
chart.options.scales[axis].grid.color = colors.grid;
chart.options.scales[axis].grid.borderColor = colors.gridMajor;
}
if (chart.options.scales[axis].ticks) {
chart.options.scales[axis].ticks.color = colors.text;
}
if (chart.options.scales[axis].title) {
chart.options.scales[axis].title.color = colors.textPrimary;
}
});
};
}
// Update legend colors
if (chart.options.plugins && chart.options.plugins.legend) {
if (chart.options.plugins.legend.labels) {
chart.options.plugins.legend.labels.color = colors.text;
}
}
// Update tooltip colors
if (chart.options.plugins && chart.options.plugins.tooltip) {
chart.options.plugins.tooltip.backgroundColor =
getEffectiveTheme() === "dark" ? "#3a3a3a" : "#ffffff";
chart.options.plugins.tooltip.titleColor = colors.textPrimary;
chart.options.plugins.tooltip.bodyColor = colors.text;
chart.options.plugins.tooltip.borderColor = colors.grid;
}
// Update the chart
chart.update();
});
};
})();
+188 -188
View File
@@ -3,198 +3,130 @@
*/
:root {
/* ===== LIGHT MODE (Default) ===== */
/* Background colors */
--bg-primary: #f0f0f0;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa;
--bg-card: #ffffff;
/* Text colors */
--text-primary: #333333;
--text-secondary: #666666;
--text-muted: #999999;
--text-inverse: #ffffff;
/* Border colors */
--border-color: #dddddd;
--border-light: #e0e0e0;
--border-lighter: #eeeeee;
/* Gradients - Brand */
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
/* Action colors */
--color-success: #4caf50;
--color-error: #dc3545;
--color-warning: #ff9800;
--color-info: #2196f3;
--color-danger: #f44336;
/* Action gradients */
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
/* Interactive elements */
--input-bg: #ffffff;
--input-border: #dddddd;
--input-focus-border: #667eea;
--input-text: #333333;
--input-placeholder: #999999;
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
/* Modal/Overlay */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors (vibrant for light backgrounds) */
--chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666;
--chart-color-1: #667eea;
--chart-color-2: #4facfe;
--chart-color-3: #f093fb;
--chart-color-4: #56ab2f;
--chart-color-5: #f2994a;
--chart-color-6: #764ba2;
--chart-color-7: #00f2fe;
--chart-color-8: #f5576c;
/* ===== LIGHT MODE (Default) ===== */
/* Background colors */
--bg-primary: #f0f0f0;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa;
--bg-card: #ffffff;
/* Text colors */
--text-primary: #333333;
--text-secondary: #666666;
--text-muted: #999999;
--text-inverse: #ffffff;
/* Border colors */
--border-color: #dddddd;
--border-light: #e0e0e0;
--border-lighter: #eeeeee;
/* Gradients - Brand */
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
/* Action colors */
--color-success: #4caf50;
--color-error: #dc3545;
--color-warning: #ff9800;
--color-info: #2196f3;
--color-danger: #f44336;
/* Action gradients */
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
/* Interactive elements */
--input-bg: #ffffff;
--input-border: #dddddd;
--input-focus-border: #667eea;
--input-text: #333333;
--input-placeholder: #999999;
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
/* Modal/Overlay */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors (vibrant for light backgrounds) */
--chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666;
--chart-color-1: #667eea;
--chart-color-2: #4facfe;
--chart-color-3: #f093fb;
--chart-color-4: #56ab2f;
--chart-color-5: #f2994a;
--chart-color-6: #764ba2;
--chart-color-7: #00f2fe;
--chart-color-8: #f5576c;
}
/* ===== DARK MODE ===== */
/* Applied via @media query (auto-detect) OR .dark-mode class (manual override) */
@media (prefers-color-scheme: dark) {
:root:not(.light-mode) {
/* Background colors - Dark theme */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d;
/* Text colors - Light on dark */
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */
--border-color: #444444;
--border-light: #4a4a4a;
--border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a;
--color-error: #ef5350;
--color-warning: #ffa726;
--color-info: #42a5f5;
--color-danger: #ff5252;
/* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */
--input-bg: #2d2d2d;
--input-border: #444444;
--input-focus-border: #667eea;
--input-text: #e0e0e0;
--input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0;
--chart-color-1: #8b9eff;
--chart-color-2: #6ec5ff;
--chart-color-3: #ffb3ff;
--chart-color-4: #81c784;
--chart-color-5: #ffb74d;
--chart-color-6: #9c6bb8;
--chart-color-7: #4dffff;
--chart-color-8: #ff8a9b;
}
}
/* Manual override with .dark-mode class */
body.dark-mode {
:root:not(.light-mode) {
/* Background colors - Dark theme */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d;
/* Text colors - Light on dark */
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */
--border-color: #444444;
--border-light: #4a4a4a;
--border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a;
--color-error: #ef5350;
--color-warning: #ffa726;
--color-info: #42a5f5;
--color-danger: #ff5252;
/* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */
--input-bg: #2d2d2d;
--input-border: #444444;
--input-focus-border: #667eea;
--input-text: #e0e0e0;
--input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0;
@@ -206,53 +138,121 @@ body.dark-mode {
--chart-color-6: #9c6bb8;
--chart-color-7: #4dffff;
--chart-color-8: #ff8a9b;
}
}
/* Manual override with .dark-mode class */
body.dark-mode {
/* Background colors - Dark theme */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--bg-card: #2d2d2d;
/* Text colors - Light on dark */
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--text-inverse: #1a1a1a;
/* Border colors - Lighter for visibility */
--border-color: #444444;
--border-light: #4a4a4a;
--border-lighter: #505050;
/* Gradients - Dimmed (~50% brightness reduction) */
--gradient-purple: linear-gradient(135deg, #3d4d8c 0%, #452d61 100%);
--gradient-blue: linear-gradient(135deg, #2f667f 0%, #00919a 100%);
--gradient-pink: linear-gradient(135deg, #905997 0%, #924344 100%);
--gradient-green: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-orange: linear-gradient(135deg, #915b2d 0%, #917a2e 100%);
/* Action colors - Slightly brighter for visibility */
--color-success: #66bb6a;
--color-error: #ef5350;
--color-warning: #ffa726;
--color-info: #42a5f5;
--color-danger: #ff5252;
/* Action gradients - Dimmed */
--gradient-success: linear-gradient(135deg, #33661c 0%, #64873c 100%);
--gradient-error: linear-gradient(135deg, #a32a2a 0%, #8b2020 100%);
--gradient-danger: linear-gradient(135deg, #a32929 0%, #8b1e1e 100%);
/* Interactive elements */
--input-bg: #2d2d2d;
--input-border: #444444;
--input-focus-border: #667eea;
--input-text: #e0e0e0;
--input-placeholder: #808080;
/* Shadows - Lighter/softer for dark mode */
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.6);
/* Modal/Overlay - Same opacity */
--overlay-bg: rgba(0, 0, 0, 0.5);
/* Chart colors - Brighter and more saturated for dark backgrounds */
--chart-grid: rgba(255, 255, 255, 0.1);
--chart-text: #b0b0b0;
--chart-color-1: #8b9eff;
--chart-color-2: #6ec5ff;
--chart-color-3: #ffb3ff;
--chart-color-4: #81c784;
--chart-color-5: #ffb74d;
--chart-color-6: #9c6bb8;
--chart-color-7: #4dffff;
--chart-color-8: #ff8a9b;
}
/* Manual override with .light-mode class (forces light even when browser prefers dark) */
body.light-mode {
/* Force light mode values - same as root defaults */
--bg-primary: #f0f0f0;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa;
--bg-card: #ffffff;
--text-primary: #333333;
--text-secondary: #666666;
--text-muted: #999999;
--text-inverse: #ffffff;
--border-color: #dddddd;
--border-light: #e0e0e0;
--border-lighter: #eeeeee;
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
--color-success: #4caf50;
--color-error: #dc3545;
--color-warning: #ff9800;
--color-info: #2196f3;
--color-danger: #f44336;
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
--input-bg: #ffffff;
--input-border: #dddddd;
--input-focus-border: #667eea;
--input-text: #333333;
--input-placeholder: #999999;
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
--overlay-bg: rgba(0, 0, 0, 0.5);
--chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666;
--chart-color-1: #667eea;
--chart-color-2: #4facfe;
--chart-color-3: #f093fb;
--chart-color-4: #56ab2f;
--chart-color-5: #f2994a;
--chart-color-6: #764ba2;
--chart-color-7: #00f2fe;
--chart-color-8: #f5576c;
/* Force light mode values - same as root defaults */
--bg-primary: #f0f0f0;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fa;
--bg-card: #ffffff;
--text-primary: #333333;
--text-secondary: #666666;
--text-muted: #999999;
--text-inverse: #ffffff;
--border-color: #dddddd;
--border-light: #e0e0e0;
--border-lighter: #eeeeee;
--gradient-purple: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-blue: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-pink: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--gradient-green: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-orange: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
--color-success: #4caf50;
--color-error: #dc3545;
--color-warning: #ff9800;
--color-info: #2196f3;
--color-danger: #f44336;
--gradient-success: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
--gradient-error: linear-gradient(135deg, #dc3545 0%, #c62828 100%);
--gradient-danger: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
--input-bg: #ffffff;
--input-border: #dddddd;
--input-focus-border: #667eea;
--input-text: #333333;
--input-placeholder: #999999;
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 2px 10px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.15);
--shadow-hover: 0 5px 15px rgba(0, 0, 0, 0.2);
--overlay-bg: rgba(0, 0, 0, 0.5);
--chart-grid: rgba(0, 0, 0, 0.1);
--chart-text: #666666;
--chart-color-1: #667eea;
--chart-color-2: #4facfe;
--chart-color-3: #f093fb;
--chart-color-4: #56ab2f;
--chart-color-5: #f2994a;
--chart-color-6: #764ba2;
--chart-color-7: #00f2fe;
--chart-color-8: #f5576c;
}
+7 -3
View File
@@ -857,7 +857,7 @@
},
},
scales: {
y: getChartScaleOptions('y', {
y: getChartScaleOptions("y", {
beginAtZero: true,
title: {
display: true,
@@ -1098,8 +1098,12 @@
loadDiaperChanges();
// Listen for theme changes and update charts
window.addEventListener('themechange', () => {
const charts = [barChartInstance, poopPieChartInstance, peePieChartInstance].filter(c => c);
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
poopPieChartInstance,
peePieChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script>
+21 -17
View File
@@ -424,13 +424,13 @@
// Build child dropdown options
const childOptions = allChildren
.map(
(child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
.map(
(child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = `
content.innerHTML = `
<div class="controls">
${
allChildren.length === 1
@@ -486,13 +486,13 @@
</div>
`;
// Render charts
renderBarChart(barChartData);
renderPieChart(pieChartData);
renderDurationChart(durationChartData);
// Render charts
renderBarChart(barChartData);
renderPieChart(pieChartData);
renderDurationChart(durationChartData);
// Render feeding list
renderFeedingList(feedings);
// Render feeding list
renderFeedingList(feedings);
}
function renderFeedingList(feedings) {
@@ -940,7 +940,7 @@
display: false,
},
},
y: getChartScaleOptions('y', {
y: getChartScaleOptions("y", {
stacked: true,
beginAtZero: true,
title: {
@@ -1127,7 +1127,7 @@
},
},
scales: {
y: getChartScaleOptions('y', {
y: getChartScaleOptions("y", {
beginAtZero: true,
title: {
display: true,
@@ -1144,7 +1144,7 @@
},
},
}),
x: getChartScaleOptions('x', {
x: getChartScaleOptions("x", {
title: {
display: true,
text: "Duration (minutes)",
@@ -1202,8 +1202,12 @@
loadFeedings();
// Listen for theme changes and update charts
window.addEventListener('themechange', () => {
const charts = [barChartInstance, pieChartInstance, durationChartInstance].filter(c => c);
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
pieChartInstance,
durationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script>
+4 -2
View File
@@ -3,9 +3,11 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor</title> <link rel="stylesheet" href="/dark-mode.css" />
<title>Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<script src="/chart-theme.js"></script> <link rel="stylesheet" href="/menu.css" />
<script src="/chart-theme.js"></script>
<link rel="stylesheet" href="/menu.css" />
<style>
* {
margin: 0;
+1 -1
View File
@@ -245,7 +245,7 @@
<button
type="button"
class="button secondary"
onclick="window.location.href=getReturnUrl()"
onclick="window.location.href = getReturnUrl()"
>
Cancel
</button>
+10 -3
View File
@@ -9,7 +9,9 @@
padding: 0.5rem;
border-radius: 4px;
box-shadow: var(--shadow-md, 0 2px 10px rgba(0, 0, 0, 0.1));
transition: background 0.3s, box-shadow 0.3s;
transition:
background 0.3s,
box-shadow 0.3s;
}
.burger-menu div {
@@ -43,7 +45,9 @@
height: 100vh;
background: var(--bg-secondary, #ffffff);
box-shadow: var(--shadow-md, 2px 0 10px rgba(0, 0, 0, 0.1));
transition: left 0.3s, background 0.3s;
transition:
left 0.3s,
background 0.3s;
z-index: 999;
padding: 4rem 1rem 1rem 1rem;
overflow: hidden;
@@ -57,7 +61,10 @@
padding: 1rem;
border-bottom: 1px solid var(--border-color, #eeeeee);
cursor: pointer;
transition: background 0.2s, color 0.3s, border-color 0.3s;
transition:
background 0.2s,
color 0.3s,
border-color 0.3s;
color: var(--text-primary, #333333);
}
+152 -31
View File
@@ -240,30 +240,132 @@
<div class="section">
<h2>🌓 Theme Preference</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 14px">
<p
style="
color: var(--text-secondary);
margin-bottom: 20px;
font-size: 14px;
"
>
Choose how Baby Monitor looks to you.
</p>
<div class="form-group">
<div style="display: flex; flex-direction: column; gap: 15px">
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="light">
<input type="radio" name="theme" value="light" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" />
<label
style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="light"
>
<input
type="radio"
name="theme"
value="light"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">☀️ Light Mode</div>
<div style="font-size: 12px; color: var(--text-secondary);">Always use light theme</div>
<div
style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
☀️ Light Mode
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Always use light theme
</div>
</div>
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="dark">
<input type="radio" name="theme" value="dark" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" />
<label
style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="dark"
>
<input
type="radio"
name="theme"
value="dark"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">🌙 Dark Mode</div>
<div style="font-size: 12px; color: var(--text-secondary);">Always use dark theme</div>
<div
style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
🌙 Dark Mode
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Always use dark theme
</div>
</div>
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 12px; border: 2px solid var(--border-light); border-radius: 8px; transition: all 0.2s;" class="theme-option" data-theme="auto">
<input type="radio" name="theme" value="auto" style="margin-right: 12px; width: 20px; height: 20px; cursor: pointer;" />
<label
style="
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
transition: all 0.2s;
"
class="theme-option"
data-theme="auto"
>
<input
type="radio"
name="theme"
value="auto"
style="
margin-right: 12px;
width: 20px;
height: 20px;
cursor: pointer;
"
/>
<div>
<div style="font-weight: 600; color: var(--text-primary); margin-bottom: 4px;">🔄 Auto</div>
<div style="font-size: 12px; color: var(--text-secondary);">Match your browser settings</div>
<div
style="
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
"
>
🔄 Auto
</div>
<div style="font-size: 12px; color: var(--text-secondary)">
Match your browser settings
</div>
</div>
</label>
</div>
@@ -298,7 +400,13 @@
"
>
<h3 style="color: #dc3545; margin-bottom: 15px">⚠️ Danger Zone</h3>
<p style="color: var(--text-secondary); margin-bottom: 15px; font-size: 14px">
<p
style="
color: var(--text-secondary);
margin-bottom: 15px;
font-size: 14px;
"
>
Deleting your account is permanent and cannot be undone. This will:
</p>
<ul
@@ -322,9 +430,7 @@
</ul>
<button
class="button"
style="
background: var(--gradient-error);
"
style="background: var(--gradient-error)"
onclick="confirmDeleteAccount()"
>
🗑️ Delete Account
@@ -337,7 +443,7 @@
<button
class="quick-action"
onclick="window.location.href='/add-child.html'"
onclick="window.location.href = '/add-child.html'"
>
Add New Child
</button>
@@ -382,7 +488,9 @@
width: 90%;
"
>
<h2 style="margin-top: 0; color: var(--text-primary);">🔗 Redeem Share Code</h2>
<h2 style="margin-top: 0; color: var(--text-primary)">
🔗 Redeem Share Code
</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px">
Enter the share code to get access to a child.
</p>
@@ -448,8 +556,11 @@
width: 90%;
"
>
<h2 style="margin-top: 0; color: var(--text-primary);">🔗 Share Code</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px" id="shareCodeChildName"></p>
<h2 style="margin-top: 0; color: var(--text-primary)">🔗 Share Code</h2>
<p
style="color: var(--text-secondary); margin-bottom: 20px"
id="shareCodeChildName"
></p>
<div
style="
background: var(--bg-tertiary);
@@ -490,7 +601,13 @@
</button>
</div>
</div>
<p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 20px">
<p
style="
color: var(--text-secondary);
font-size: 14px;
margin-bottom: 20px;
"
>
Share this code with another user. They can use it in Settings >
Redeem Share Code to get access to this child.
<br /><br />
@@ -520,7 +637,9 @@
// Initialize theme selector
function initThemeSelector() {
const currentTheme = window.getThemePreference();
const themeRadio = document.querySelector(`input[name="theme"][value="${currentTheme}"]`);
const themeRadio = document.querySelector(
`input[name="theme"][value="${currentTheme}"]`,
);
if (themeRadio) {
themeRadio.checked = true;
}
@@ -529,8 +648,8 @@
updateThemeOptionStyles();
// Listen for theme changes
document.querySelectorAll('input[name="theme"]').forEach(radio => {
radio.addEventListener('change', (e) => {
document.querySelectorAll('input[name="theme"]').forEach((radio) => {
radio.addEventListener("change", (e) => {
window.setTheme(e.target.value);
updateThemeOptionStyles();
});
@@ -538,15 +657,17 @@
}
function updateThemeOptionStyles() {
const selectedValue = document.querySelector('input[name="theme"]:checked')?.value;
document.querySelectorAll('.theme-option').forEach(option => {
const selectedValue = document.querySelector(
'input[name="theme"]:checked',
)?.value;
document.querySelectorAll(".theme-option").forEach((option) => {
const isSelected = option.dataset.theme === selectedValue;
if (isSelected) {
option.style.borderColor = '#667eea';
option.style.background = 'rgba(102, 126, 234, 0.05)';
option.style.borderColor = "#667eea";
option.style.background = "rgba(102, 126, 234, 0.05)";
} else {
option.style.borderColor = '#e0e0e0';
option.style.background = 'transparent';
option.style.borderColor = "#e0e0e0";
option.style.background = "transparent";
}
});
}
+8 -4
View File
@@ -895,7 +895,7 @@
x: {
stacked: true,
},
y: getChartScaleOptions('y', {
y: getChartScaleOptions("y", {
stacked: true,
beginAtZero: true,
title: {
@@ -1056,7 +1056,7 @@
responsive: true,
maintainAspectRatio: false,
scales: {
y: getChartScaleOptions('y', {
y: getChartScaleOptions("y", {
beginAtZero: true,
ticks: {
callback: function (value) {
@@ -1135,8 +1135,12 @@
loadData();
// Listen for theme changes and update charts
window.addEventListener('themechange', () => {
const charts = [barChartInstance, durationChartInstance, avgDurationChartInstance].filter(c => c);
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
durationChartInstance,
avgDurationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script>
+78 -67
View File
@@ -3,74 +3,85 @@
* Must be loaded in <head> before page content to prevent flash of unstyled content
*/
(function() {
'use strict';
// Get saved theme preference from localStorage
const savedTheme = localStorage.getItem('theme-preference');
// Apply theme immediately to prevent FOUC (Flash of Unstyled Content)
function applyTheme(theme) {
const body = document.body || document.documentElement;
if (theme === 'dark') {
body.classList.remove('light-mode');
body.classList.add('dark-mode');
} else if (theme === 'light') {
body.classList.remove('dark-mode');
body.classList.add('light-mode');
} else {
// 'auto' mode - remove manual override classes
body.classList.remove('dark-mode', 'light-mode');
}
(function () {
"use strict";
// Get saved theme preference from localStorage
const savedTheme = localStorage.getItem("theme-preference");
// Apply theme immediately to prevent FOUC (Flash of Unstyled Content)
function applyTheme(theme) {
const body = document.body || document.documentElement;
if (theme === "dark") {
body.classList.remove("light-mode");
body.classList.add("dark-mode");
} else if (theme === "light") {
body.classList.remove("dark-mode");
body.classList.add("light-mode");
} else {
// 'auto' mode - remove manual override classes
body.classList.remove("dark-mode", "light-mode");
}
// Apply theme based on preference
if (savedTheme === 'dark' || savedTheme === 'light' || savedTheme === 'auto') {
applyTheme(savedTheme);
}
// Apply theme based on preference
if (
savedTheme === "dark" ||
savedTheme === "light" ||
savedTheme === "auto"
) {
applyTheme(savedTheme);
}
// If no preference saved, default to 'auto' (browser preference)
// Make theme switching function available globally
window.setTheme = function (theme) {
if (theme === "light" || theme === "dark" || theme === "auto") {
localStorage.setItem("theme-preference", theme);
applyTheme(theme);
// Trigger custom event for components that need to update (e.g., charts)
window.dispatchEvent(
new CustomEvent("themechange", { detail: { theme } }),
);
}
// If no preference saved, default to 'auto' (browser preference)
// Make theme switching function available globally
window.setTheme = function(theme) {
if (theme === 'light' || theme === 'dark' || theme === 'auto') {
localStorage.setItem('theme-preference', theme);
applyTheme(theme);
// Trigger custom event for components that need to update (e.g., charts)
window.dispatchEvent(new CustomEvent('themechange', { detail: { theme } }));
}
};
// Get current effective theme (resolves 'auto' to actual theme)
window.getEffectiveTheme = function() {
const savedTheme = localStorage.getItem('theme-preference');
if (savedTheme === 'dark') return 'dark';
if (savedTheme === 'light') return 'light';
// 'auto' or no preference - check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
};
// Get saved theme preference (returns 'light', 'dark', 'auto', or null)
window.getThemePreference = function() {
return localStorage.getItem('theme-preference') || 'auto';
};
// Listen for system theme changes when in auto mode
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
const savedTheme = localStorage.getItem('theme-preference');
// Only react to system changes if in auto mode
if (!savedTheme || savedTheme === 'auto') {
// Trigger theme change event for components that need to update
window.dispatchEvent(new CustomEvent('themechange', { detail: { theme: 'auto' } }));
}
});
};
// Get current effective theme (resolves 'auto' to actual theme)
window.getEffectiveTheme = function () {
const savedTheme = localStorage.getItem("theme-preference");
if (savedTheme === "dark") return "dark";
if (savedTheme === "light") return "light";
// 'auto' or no preference - check system preference
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
return "dark";
}
return "light";
};
// Get saved theme preference (returns 'light', 'dark', 'auto', or null)
window.getThemePreference = function () {
return localStorage.getItem("theme-preference") || "auto";
};
// Listen for system theme changes when in auto mode
if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", function (e) {
const savedTheme = localStorage.getItem("theme-preference");
// Only react to system changes if in auto mode
if (!savedTheme || savedTheme === "auto") {
// Trigger theme change event for components that need to update
window.dispatchEvent(
new CustomEvent("themechange", { detail: { theme: "auto" } }),
);
}
});
}
})();