13 Commits
Author SHA1 Message Date
brian 120c7ec309 Merge pull request 'Make recent activity cards dynamic' (#45) from make-recent-activity-last-sleep-dynamic into main
Python Code Quality / python-code-quality (push) Successful in 12s
Python Test / python-test (push) Successful in 21s
Build and Push Docker Image / build-and-push (push) Successful in 39s
Reviewed-on: https://gitea.lille-vemmelund.dk/brian/baby-monitor/pulls/45
2026-05-21 15:29:16 +02:00
Brian Bjarke Jensen 6c74358d8b changed formatting of recent activity cards
Build and Push Docker Image / build-and-push (pull_request) Successful in 3m23s
Python Code Quality / python-code-quality (pull_request) Successful in 13s
Python Test / python-test (pull_request) Successful in 20s
2026-05-21 15:20:42 +02:00
brian 993612a90f Merge pull request 'added support for setting start time of sleep entry' (#44) from fix(#43)--enable-editing-sleep-entries into main
Python Code Quality / python-code-quality (push) Successful in 12s
Python Test / python-test (push) Successful in 20s
Build and Push Docker Image / build-and-push (push) Successful in 41s
Refresh Showcase Data / refresh-data (push) Successful in 22s
Reviewed-on: #44
2026-03-29 20:29:28 +02:00
Brian Bjarke Jensen 0b06aefa03 added support for setting start time of sleep entry
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m25s
Python Code Quality / python-code-quality (pull_request) Successful in 12s
Python Test / python-test (pull_request) Successful in 21s
2026-03-29 20:22:26 +02:00
brian ff88d0fcb6 Merge pull request 'implement-ready-endpoint-logic' (#42) from implement-ready-endpoint-logic into main
Build and Push Docker Image / build-and-push (push) Successful in 27s
Python Code Quality / python-code-quality (push) Successful in 10s
Python Test / python-test (push) Successful in 19s
Refresh Showcase Data / refresh-data (push) Successful in 47s
Reviewed-on: #42
2026-01-27 21:26:06 +01:00
Brian Bjarke Jensen ec0e1f6140 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
2026-01-27 21:21:55 +01:00
Brian Bjarke Jensen b2aa71b95b updated ready endpoints logic
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m6s
Python Code Quality / python-code-quality (pull_request) Failing after 10s
Python Test / python-test (pull_request) Successful in 18s
2026-01-27 21:17:47 +01:00
Brian Bjarke Jensen 826cfde2ab recreated lock file 2026-01-27 21:17:34 +01:00
brian 97fe5429ea Merge pull request 'add-dark-mode' (#41) from add-dark-mode into main
Python Code Quality / python-code-quality (push) Successful in 12s
Python Test / python-test (push) Successful in 19s
Build and Push Docker Image / build-and-push (push) Successful in 26s
Refresh Showcase Data / refresh-data (push) Successful in 17s
Reviewed-on: #41
2026-01-24 23:07:23 +01:00
Brian Bjarke Jensen b7d80e7f26 added support for dark mode
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m5s
Python Code Quality / python-code-quality (pull_request) Successful in 11s
Python Test / python-test (pull_request) Successful in 18s
2026-01-24 23:04:44 +01:00
Brian Bjarke Jensen d9948d271f changes startup waiting time 2026-01-24 23:04:40 +01:00
brian adb96b5b5f Merge pull request 'update package registry url' (#40) from update-package-registry-ip-address into main
Build and Push Docker Image / build-and-push (push) Successful in 27s
Python Code Quality / python-code-quality (push) Successful in 11s
Python Test / python-test (push) Successful in 19s
Refresh Showcase Data / refresh-data (push) Successful in 22s
Reviewed-on: #40
2026-01-14 00:13:26 +01:00
Brian Bjarke Jensen 8d3b6fa5a2 updated url
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m3s
Python Code Quality / python-code-quality (pull_request) Successful in 12s
Python Test / python-test (pull_request) Successful in 19s
2026-01-14 00:10:11 +01:00
25 changed files with 1699 additions and 759 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 1s start_period: 3s
postgres: postgres:
image: postgres:14-alpine image: postgres:14-alpine
environment: environment:
+1 -1
View File
@@ -41,7 +41,7 @@ dev = [
[[tool.uv.index]] [[tool.uv.index]]
name = "threadripper-proxpi-cache" name = "threadripper-proxpi-cache"
url = "http://10.0.0.2:5001/index/" url = "http://10.184.20.2:5001/index/"
default = true default = true
[tool.mypy] [tool.mypy]
+18
View File
@@ -143,6 +143,24 @@ def serve_menu_js() -> FileResponse:
return FileResponse(static_path / "menu.js") return FileResponse(static_path / "menu.js")
@app.get("/dark-mode.css", include_in_schema=False)
def serve_dark_mode_css() -> FileResponse:
"""Serve the dark mode CSS."""
return FileResponse(static_path / "dark-mode.css")
@app.get("/theme-init.js", include_in_schema=False)
def serve_theme_init_js() -> FileResponse:
"""Serve the theme initialization JavaScript."""
return FileResponse(static_path / "theme-init.js")
@app.get("/chart-theme.js", include_in_schema=False)
def serve_chart_theme_js() -> FileResponse:
"""Serve the chart theming JavaScript."""
return FileResponse(static_path / "chart-theme.js")
@app.get("/api/") @app.get("/api/")
def read_root(token: Annotated[str, Depends(verify_token)]) -> dict: def read_root(token: Annotated[str, Depends(verify_token)]) -> dict:
"""API root endpoint (requires authentication).""" """API root endpoint (requires authentication)."""
+1
View File
@@ -15,6 +15,7 @@ class CreateSleepRequest(BaseModel):
class UpdateSleepRequest(BaseModel): class UpdateSleepRequest(BaseModel):
"""Request model for updating a sleep log.""" """Request model for updating a sleep log."""
start_time: datetime | None = None
end_time: datetime | None = None end_time: datetime | None = None
@@ -32,6 +32,7 @@ class SleepRepositoryInterface(ABC):
def update( def update(
self, self,
sleep_id: int, sleep_id: int,
start_time: datetime | None = None,
end_time: datetime | None = None, end_time: datetime | None = None,
) -> dict | None: ) -> dict | None:
"""Update a sleep log entry.""" """Update a sleep log entry."""
@@ -73,6 +73,7 @@ class DatabaseSleepRepository(SleepRepositoryInterface):
def update( def update(
self, self,
sleep_id: int, sleep_id: int,
start_time: datetime | None = None,
end_time: datetime | None = None, end_time: datetime | None = None,
) -> dict | None: ) -> dict | None:
"""Update a sleep log entry.""" """Update a sleep log entry."""
@@ -80,6 +81,8 @@ class DatabaseSleepRepository(SleepRepositoryInterface):
if not sleep: if not sleep:
return None return None
if start_time is not None:
sleep.start_time = start_time # type: ignore[assignment]
if end_time is not None: if end_time is not None:
sleep.end_time = end_time # type: ignore[assignment] sleep.end_time = end_time # type: ignore[assignment]
+50 -7
View File
@@ -1,6 +1,7 @@
"""Health check router for monitoring endpoints.""" """Health check router for monitoring endpoints."""
from fastapi import APIRouter import os
from fastapi import APIRouter, HTTPException
router = APIRouter(tags=["health"]) router = APIRouter(tags=["health"])
@@ -20,11 +21,53 @@ def readiness_check() -> dict[str, str]:
""" """
Readiness check endpoint for container orchestration. Readiness check endpoint for container orchestration.
Returns 200 if the service is ready to accept traffic. Returns 200 if the service is ready to accept traffic, else 503.
This can include checks for database connectivity, external services, etc.
""" """
# Add dependency checks here if needed (database, cache, etc.)
# For now, simple readiness check # Determine if configured to use Redis
redis_uri = os.getenv("REDIS_URI")
# If Redis is used, check connectivity
if redis_uri:
try:
import redis
redis_client = redis.from_url(redis_uri)
redis_client.ping()
except Exception as exc:
raise HTTPException(
status_code=503,
detail={
"status": "not ready",
"reason": "No connection to Redis",
"exception": str(exc),
},
) from exc
# Determine if configured to use PostgreSQL
postgres_uri = os.getenv("POSTGRES_URI")
# If PostgreSQL is used, check connectivity
if postgres_uri:
try:
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:
db.execute(text("SELECT 1"))
except Exception as exc:
raise HTTPException(
status_code=503,
detail={
"status": "not ready",
"reason": "No connection to PostgreSQL",
"exception": str(exc),
},
) from exc
# Unable to prove not ready, so assume ready
return {"status": "ready"} return {"status": "ready"}
@@ -60,9 +103,9 @@ def service_info() -> dict:
# Check current configuration # Check current configuration
config = { config = {
"environment": os.getenv("ENVIRONMENT", "production"), "environment": os.getenv("ENVIRONMENT", "production"),
"data_dir": os.getenv("DATA_DIR", "/data"),
"redis_configured": bool(os.getenv("REDIS_URI")),
} }
if not features["postgresql"]:
config["data_dir"] = os.getenv("DATA_DIR", "/data")
return { return {
"service": "baby-monitor", "service": "baby-monitor",
+1
View File
@@ -112,6 +112,7 @@ def update_sleep(
updated = sleep_repo.update( updated = sleep_repo.update(
sleep_id=sleep_id, sleep_id=sleep_id,
start_time=request.start_time,
end_time=request.end_time, end_time=request.end_time,
) )
+13 -8
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Add Child - Baby Monitor</title> <title>Add Child - Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: #667eea;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -33,13 +35,13 @@
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -51,7 +53,7 @@
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
@@ -59,19 +61,22 @@
input { input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 1px solid #ddd; border: 2px solid #ccc;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.2s;
background-color: var(--input-bg);
color: var(--input-text);
} }
input:focus { input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--input-focus-border);
} }
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -135,7 +140,7 @@
.help-text { .help-text {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-top: 4px; margin-top: 4px;
} }
</style> </style>
+19 -16
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor - Admin</title> <title>Baby Monitor - Admin</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: #667eea;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -33,13 +35,13 @@
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -62,7 +64,8 @@
} }
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -95,7 +98,7 @@
display: none; display: none;
margin-top: 20px; margin-top: 20px;
padding: 15px; padding: 15px;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
border: 2px solid #667eea; border: 2px solid #667eea;
} }
@@ -106,7 +109,7 @@
.link-label { .link-label {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-bottom: 8px; margin-bottom: 8px;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
@@ -121,13 +124,13 @@
.link-text { .link-text {
flex: 1; flex: 1;
padding: 10px; padding: 10px;
background: white; background: var(--bg-secondary);
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
font-size: 13px; font-size: 13px;
word-break: break-all; word-break: break-all;
color: #333; color: var(--text-primary);
} }
.copy-button { .copy-button {
@@ -193,7 +196,7 @@
.search-box { .search-box {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
margin-bottom: 15px; margin-bottom: 15px;
@@ -202,7 +205,7 @@
.user-list { .user-list {
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
} }
@@ -211,8 +214,8 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 12px; padding: 12px;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--border-lighter);
background: white; background: var(--bg-secondary);
} }
.user-item:last-child { .user-item:last-child {
@@ -220,7 +223,7 @@
} }
.user-item:hover { .user-item:hover {
background: #f8f9fa; background: var(--bg-tertiary);
} }
.user-info { .user-info {
@@ -229,13 +232,13 @@
.user-name { .user-name {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
margin-bottom: 4px; margin-bottom: 4px;
} }
.user-meta { .user-meta {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
} }
.admin-badge { .admin-badge {
+207
View File
@@ -0,0 +1,207 @@
/* Chart.js Dark Mode Helper
* Provides theme-aware color palettes and chart options for Chart.js
* 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",
};
};
// 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,
},
},
scales: {},
};
// 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",
},
},
};
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);
}
// 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();
});
};
})();
+258
View File
@@ -0,0 +1,258 @@
/* Dark Mode CSS Variables and Theming System
* Supports automatic detection via prefers-color-scheme and manual override via .dark-mode class
*/
: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;
}
/* ===== 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 {
/* 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;
}
+48 -29
View File
@@ -4,6 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Diapers - Baby Monitor</title> <title>Diapers - 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" /> <link rel="stylesheet" href="/menu.css" />
<style> <style>
* { * {
@@ -16,7 +19,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background-color: #f0f0f0; background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
max-width: 800px; max-width: 800px;
margin: 2rem auto; margin: 2rem auto;
@@ -24,26 +27,27 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px; padding: 40px;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
.button { .button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); background: var(--gradient-pink);
background-color: #f093fb;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -79,13 +83,13 @@
.loading { .loading {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
color: #666; color: var(--text-secondary);
} }
.empty-state-icon { .empty-state-icon {
@@ -94,7 +98,7 @@
} }
.empty-state h2 { .empty-state h2 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -105,12 +109,12 @@
.chart-container { .chart-container {
margin-bottom: 40px; margin-bottom: 40px;
padding: 20px; padding: 20px;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
} }
.chart-container h2 { .chart-container h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -150,29 +154,37 @@
.controls label { .controls label {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
} }
.controls select { .controls select,
.controls input {
padding: 8px 16px; padding: 8px 16px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
background: white; background: var(--bg-secondary);
color: var(--text-primary);
} }
.controls select:focus { .controls select:focus,
.controls input:focus {
outline: none; outline: none;
border-color: #f093fb; border-color: #f093fb;
} }
.controls input[readonly] {
opacity: 0.6;
cursor: default;
}
.diapers-list { .diapers-list {
margin-top: 40px; margin-top: 40px;
} }
.diapers-list h2 { .diapers-list h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -180,9 +192,9 @@
.diapers-scroll { .diapers-scroll {
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 8px; border-radius: 8px;
background: #f8f9fa; background: var(--bg-tertiary);
} }
.diaper-item { .diaper-item {
@@ -190,13 +202,13 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 15px 20px; padding: 15px 20px;
background: white; background: var(--bg-secondary);
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--border-lighter);
transition: background 0.2s; transition: background 0.2s;
} }
.diaper-item:hover { .diaper-item:hover {
background: #f8f9fa; background: var(--bg-tertiary);
} }
.diaper-item:last-child { .diaper-item:last-child {
@@ -209,13 +221,13 @@
.diaper-time { .diaper-time {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
margin-bottom: 5px; margin-bottom: 5px;
} }
.diaper-details { .diaper-details {
font-size: 14px; font-size: 14px;
color: #666; color: var(--text-secondary);
} }
.diaper-badge { .diaper-badge {
@@ -356,6 +368,7 @@
<div class="empty-state-icon">🧷</div> <div class="empty-state-icon">🧷</div>
<h2>No Diaper Changes Yet</h2> <h2>No Diaper Changes Yet</h2>
<p>Start tracking diaper changes from the home page</p> <p>Start tracking diaper changes from the home page</p>
<button class="button" onclick="window.location.href='/'">Go Home</button>
</div> </div>
`; `;
} else { } else {
@@ -383,7 +396,6 @@
id="childDisplay" id="childDisplay"
value="${allChildren[0].name}" value="${allChildren[0].name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/> />
` `
: ` : `
@@ -845,7 +857,7 @@
}, },
}, },
scales: { scales: {
y: { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
title: { title: {
display: true, display: true,
@@ -861,10 +873,7 @@
size: 12, size: 12,
}, },
}, },
grid: { }),
color: "rgba(0, 0, 0, 0.05)",
},
},
x: { x: {
ticks: { ticks: {
font: { font: {
@@ -1087,6 +1096,16 @@
// Load data on page load // Load data on page load
loadDiaperChanges(); loadDiaperChanges();
// Listen for theme changes and update charts
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
poopPieChartInstance,
peePieChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script> </script>
</body> </body>
</html> </html>
+86 -63
View File
@@ -4,6 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Feedings - Baby Monitor</title> <title>Feedings - 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" /> <link rel="stylesheet" href="/menu.css" />
<style> <style>
* { * {
@@ -16,7 +19,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background-color: #f0f0f0; background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
max-width: 800px; max-width: 800px;
margin: 2rem auto; margin: 2rem auto;
@@ -24,26 +27,27 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px; padding: 40px;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -79,13 +83,13 @@
.loading { .loading {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
color: #666; color: var(--text-secondary);
} }
.empty-state-icon { .empty-state-icon {
@@ -94,7 +98,7 @@
} }
.empty-state h2 { .empty-state h2 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -105,12 +109,12 @@
.chart-container { .chart-container {
margin-bottom: 40px; margin-bottom: 40px;
padding: 20px; padding: 20px;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
} }
.chart-container h2 { .chart-container h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -148,14 +152,14 @@
} }
.stat-card { .stat-card {
background: #f8f9fa; background: var(--bg-tertiary);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
} }
.stat-card h3 { .stat-card h3 {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
@@ -163,7 +167,7 @@
} }
.stat-card .stat-value { .stat-card .stat-value {
color: #333; color: var(--text-primary);
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
} }
@@ -182,21 +186,34 @@
.controls label { .controls label {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
} }
.controls select { .controls select,
.controls input {
padding: 8px 16px; padding: 8px 16px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
background: white; background: var(--bg-secondary);
color: var(--text-primary);
}
.controls select:focus,
.controls input:focus {
outline: none;
border-color: var(--input-focus-border);
}
.controls input[readonly] {
opacity: 0.6;
cursor: default;
} }
.controls select:focus { .controls select:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--input-focus-border);
} }
.feedings-list { .feedings-list {
@@ -204,7 +221,7 @@
} }
.feedings-list h2 { .feedings-list h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -212,9 +229,9 @@
.feedings-scroll { .feedings-scroll {
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 8px; border-radius: 8px;
background: #f8f9fa; background: var(--bg-tertiary);
} }
.feeding-item { .feeding-item {
@@ -222,13 +239,13 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 15px 20px; padding: 15px 20px;
background: white; background: var(--bg-secondary);
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--border-lighter);
transition: background 0.2s; transition: background 0.2s;
} }
.feeding-item:hover { .feeding-item:hover {
background: #f8f9fa; background: var(--bg-tertiary);
} }
.feeding-item:last-child { .feeding-item:last-child {
@@ -241,13 +258,13 @@
.feeding-time { .feeding-time {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
margin-bottom: 5px; margin-bottom: 5px;
} }
.feeding-details { .feeding-details {
font-size: 14px; font-size: 14px;
color: #666; color: var(--text-secondary);
} }
.feeding-type-badge { .feeding-type-badge {
@@ -387,29 +404,33 @@
function displayFeedings(feedings) { function displayFeedings(feedings) {
const content = document.getElementById("content"); const content = document.getElementById("content");
if (feedings.length === 0) { // Check if we have children data
if (allChildren.length === 0) {
content.innerHTML = ` content.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<div class="empty-state-icon">🍼</div> <div class="empty-state-icon">👶</div>
<h2>No Feedings Yet</h2> <h2>No Children Added</h2>
<p>Start tracking feeding sessions from the home page</p> <p>Please add a child first before tracking feedings</p>
<button class="button" onclick="window.location.href='/add-child.html'">Add Child</button>
</div> </div>
`; `;
} else { return;
// Prepare chart data }
const barChartData = prepareBarChartData(feedings);
const pieChartData = preparePieChartData(feedings);
const durationChartData = prepareDurationChartData(feedings);
// Build child dropdown options // Prepare chart data (works with empty arrays too)
const childOptions = allChildren const barChartData = prepareBarChartData(feedings);
.map( const pieChartData = preparePieChartData(feedings);
(child) => const durationChartData = prepareDurationChartData(feedings);
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = ` // Build child dropdown options
const childOptions = allChildren
.map(
(child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = `
<div class="controls"> <div class="controls">
${ ${
allChildren.length === 1 allChildren.length === 1
@@ -420,7 +441,6 @@
id="childDisplay" id="childDisplay"
value="${allChildren[0].name}" value="${allChildren[0].name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/> />
` `
: ` : `
@@ -466,14 +486,13 @@
</div> </div>
`; `;
// Render charts // Render charts
renderBarChart(barChartData); renderBarChart(barChartData);
renderPieChart(pieChartData); renderPieChart(pieChartData);
renderDurationChart(durationChartData); renderDurationChart(durationChartData);
// Render feeding list // Render feeding list
renderFeedingList(feedings); renderFeedingList(feedings);
}
} }
function renderFeedingList(feedings) { function renderFeedingList(feedings) {
@@ -921,7 +940,7 @@
display: false, display: false,
}, },
}, },
y: { y: getChartScaleOptions("y", {
stacked: true, stacked: true,
beginAtZero: true, beginAtZero: true,
title: { title: {
@@ -940,10 +959,7 @@
return Math.round(value) + "m"; return Math.round(value) + "m";
}, },
}, },
grid: { }),
color: "rgba(0, 0, 0, 0.05)",
},
},
}, },
plugins: { plugins: {
legend: { legend: {
@@ -1111,7 +1127,7 @@
}, },
}, },
scales: { scales: {
y: { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
title: { title: {
display: true, display: true,
@@ -1127,11 +1143,8 @@
size: 12, size: 12,
}, },
}, },
grid: { }),
color: "rgba(0, 0, 0, 0.05)", x: getChartScaleOptions("x", {
},
},
x: {
title: { title: {
display: true, display: true,
text: "Duration (minutes)", text: "Duration (minutes)",
@@ -1148,7 +1161,7 @@
grid: { grid: {
display: false, display: false,
}, },
}, }),
}, },
}, },
}); });
@@ -1187,6 +1200,16 @@
// Load data on page load // Load data on page load
loadFeedings(); loadFeedings();
// Listen for theme changes and update charts
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
pieChartInstance,
durationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script> </script>
</body> </body>
</html> </html>
+47 -29
View File
@@ -4,27 +4,37 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor</title> <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" /> <link rel="stylesheet" href="/menu.css" />
<style> <style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
max-width: 800px; max-width: 800px;
margin: 2rem auto; margin: 2rem auto;
padding: 0 1rem; padding: 0 1rem;
background-color: #f0f0f0; background-color: var(--bg-primary);
overflow-x: hidden;
} }
.container { .container {
background: white; background: var(--bg-secondary);
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md);
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.greeting { .greeting {
color: #666; color: var(--text-secondary);
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
@@ -59,7 +69,9 @@
padding: 2rem; padding: 2rem;
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white;
border-radius: 8px; border-radius: 8px;
transition: transition:
transform 0.2s, transform 0.2s,
@@ -68,13 +80,16 @@
.feeding-button:hover { .feeding-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); background: var(--gradient-purple);
background-color: #667eea;
} }
.feeding-button.active { .feeding-button.active {
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%); background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
background-color: #f44336;
} }
.feeding-button.active:hover { .feeding-button.active:hover {
background: linear-gradient(135deg, #e91e63 0%, #f44336 100%); background: linear-gradient(135deg, #e91e63 0%, #f44336 100%);
background-color: #e91e63;
} }
.feeding-info { .feeding-info {
font-size: 0.9rem; font-size: 0.9rem;
@@ -97,7 +112,7 @@
display: flex; display: flex;
} }
.modal-content { .modal-content {
background: white; background: var(--bg-secondary);
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
max-width: 400px; max-width: 400px;
@@ -107,7 +122,7 @@
.modal h2 { .modal h2 {
margin-top: 0; margin-top: 0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
color: #333; color: var(--text-primary);
} }
.form-group { .form-group {
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -115,13 +130,13 @@
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
} }
.form-group select { .form-group select {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
font-size: 1rem; font-size: 1rem;
} }
@@ -145,7 +160,9 @@
padding: 2rem; padding: 2rem;
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-blue);
background-color: #4facfe;
color: white;
border-radius: 8px; border-radius: 8px;
transition: transition:
transform 0.2s, transform 0.2s,
@@ -153,14 +170,17 @@
} }
.sleep-button:hover { .sleep-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); box-shadow: 0 6px 20px rgba(79, 172, 254, 0.4);
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); background: var(--gradient-blue);
background-color: #4facfe;
} }
.sleep-button.active { .sleep-button.active {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); background: var(--gradient-blue);
background-color: #4facfe;
} }
.sleep-button.active:hover { .sleep-button.active:hover {
background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%); background: var(--gradient-blue);
background-color: #4facfe;
} }
.sleep-info { .sleep-info {
font-size: 0.9rem; font-size: 0.9rem;
@@ -170,13 +190,13 @@
.recent-activity { .recent-activity {
margin-top: 2rem; margin-top: 2rem;
padding: 1.5rem; padding: 1.5rem;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
} }
.recent-activity h3 { .recent-activity h3 {
margin-top: 0; margin-top: 0;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #333; color: var(--text-primary);
font-size: 1.1rem; font-size: 1.1rem;
} }
.activity-grid { .activity-grid {
@@ -185,7 +205,7 @@
gap: 1rem; gap: 1rem;
} }
.activity-card { .activity-card {
background: white; background: var(--bg-secondary);
padding: 1rem; padding: 1rem;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
@@ -196,12 +216,12 @@
} }
.activity-card .type { .activity-card .type {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.activity-card .time { .activity-card .time {
font-size: 0.85rem; font-size: 0.85rem;
color: #666; color: var(--text-secondary);
} }
.activity-card .details { .activity-card .details {
font-size: 0.85rem; font-size: 0.85rem;
@@ -286,6 +306,9 @@
const token = localStorage.getItem("access_token"); const token = localStorage.getItem("access_token");
const username = localStorage.getItem("username"); const username = localStorage.getItem("username");
// Initialize burger menu
initBurgerMenu({ includeHome: true });
// Check if user is logged in // Check if user is logged in
if (!token) { if (!token) {
window.location.href = "/login.html"; window.location.href = "/login.html";
@@ -308,11 +331,6 @@
} }
}) })
.then((user) => { .then((user) => {
// Initialize burger menu
initBurgerMenu({
includeHome: true,
});
// Check if user has any children // Check if user has any children
return fetch("/api/children", { return fetch("/api/children", {
headers: { headers: {
@@ -396,7 +414,7 @@
if (feedings.length > 0) { if (feedings.length > 0) {
// Calculate the most recent feeding session // Calculate the most recent feeding session
let sessionStart = feedings[0].start_time; let sessionStart = feedings[0].start_time;
let sessionEnd = feedings[0].end_time || feedings[0].start_time; let sessionEnd = feedings[0].end_time;
let sessionChildId = feedings[0].child_id; let sessionChildId = feedings[0].child_id;
let sessionEntries = [feedings[0]]; let sessionEntries = [feedings[0]];
@@ -631,7 +649,7 @@
<a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;"> <a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">🍼</div> <div class="icon">🍼</div>
<div class="type">Last Feeding Session</div> <div class="type">Last Feeding Session</div>
<div class="time">${formatTime(lastFeeding.start_time)}</div> <div class="time">${lastFeeding.end_time ? `stopped ${formatTime(lastFeeding.end_time)}` : `started ${formatTime(lastFeeding.start_time)}`}</div>
<div class="details"> <div class="details">
${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""} ${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""}
${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""} ${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""}
@@ -653,7 +671,7 @@
<a href="/sleep.html" class="activity-card" style="text-decoration:none; color:inherit;"> <a href="/sleep.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">😴</div> <div class="icon">😴</div>
<div class="type">Last Sleep</div> <div class="type">Last Sleep</div>
<div class="time">${formatTime(lastSleep.start_time)}</div> <div class="time">${lastSleep.end_time ? `stopped ${formatTime(lastSleep.end_time)}` : `started ${formatTime(lastSleep.start_time)}`}</div>
<div class="details"> <div class="details">
Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"} Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div> </div>
+25 -16
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Log Diaper Change - Baby Monitor</title> <title>Log Diaper Change - Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -24,22 +26,22 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: var(--shadow-md);
padding: 40px; padding: 40px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -51,7 +53,7 @@
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
@@ -60,20 +62,28 @@
select { select {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.2s;
background-color: var(--input-bg);
color: var(--input-text);
} }
input:focus, input:focus,
select:focus { select:focus {
outline: none; outline: none;
border-color: #667eea; border-color: #f093fb;
}
input[readonly] {
opacity: 0.6;
cursor: default;
} }
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-pink);
background-color: #f093fb;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -90,7 +100,7 @@
.button:hover { .button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); box-shadow: 0 4px 12px rgba(240, 147, 251, 0.4);
} }
.button:active { .button:active {
@@ -137,25 +147,25 @@
.help-text { .help-text {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-top: 4px; margin-top: 4px;
} }
.loading { .loading {
text-align: center; text-align: center;
padding: 20px; padding: 20px;
color: #666; color: var(--text-secondary);
} }
.section-title { .section-title {
color: #666; color: var(--text-secondary);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
margin: 25px 0 15px 0; margin: 25px 0 15px 0;
padding-top: 15px; padding-top: 15px;
border-top: 1px solid #eee; border-top: 1px solid var(--border-lighter);
} }
.section-title:first-of-type { .section-title:first-of-type {
@@ -235,7 +245,7 @@
<button <button
type="button" type="button"
class="button secondary" class="button secondary"
onclick="window.location.href=getReturnUrl()" onclick="window.location.href = getReturnUrl()"
> >
Cancel Cancel
</button> </button>
@@ -299,7 +309,6 @@
id="childName" id="childName"
value="${child.name}" value="${child.name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default;"
/> />
<input type="hidden" id="childSelect" name="childSelect" value="${child.id}" /> <input type="hidden" id="childSelect" name="childSelect" value="${child.id}" />
`; `;
+20 -11
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Log Feeding - Baby Monitor</title> <title>Log Feeding - Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -24,22 +26,22 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: var(--shadow-md);
padding: 40px; padding: 40px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -51,7 +53,7 @@
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
@@ -60,10 +62,12 @@
select { select {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.2s;
background-color: var(--input-bg);
color: var(--input-text);
} }
input:focus, input:focus,
@@ -72,8 +76,14 @@
border-color: #667eea; border-color: #667eea;
} }
input[readonly] {
opacity: 0.6;
cursor: default;
}
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -137,14 +147,14 @@
.help-text { .help-text {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-top: 4px; margin-top: 4px;
} }
.loading { .loading {
text-align: center; text-align: center;
padding: 20px; padding: 20px;
color: #666; color: var(--text-secondary);
} }
</style> </style>
</head> </head>
@@ -281,7 +291,6 @@
id="childName" id="childName"
value="${child.name}" value="${child.name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default;"
/> />
<input type="hidden" id="childId" name="childId" value="${child.id}" /> <input type="hidden" id="childId" name="childId" value="${child.id}" />
`; `;
+20 -11
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Log Sleep - Baby Monitor</title> <title>Log Sleep - Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -24,22 +26,22 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: var(--shadow-md);
padding: 40px; padding: 40px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -51,7 +53,7 @@
label { label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
@@ -60,10 +62,12 @@
select { select {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.2s;
background-color: var(--input-bg);
color: var(--input-text);
} }
input:focus, input:focus,
@@ -72,8 +76,14 @@
border-color: #4facfe; border-color: #4facfe;
} }
input[readonly] {
opacity: 0.6;
cursor: default;
}
.button { .button {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); background: var(--gradient-blue);
background-color: #4facfe;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -137,14 +147,14 @@
.help-text { .help-text {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-top: 4px; margin-top: 4px;
} }
.loading { .loading {
text-align: center; text-align: center;
padding: 20px; padding: 20px;
color: #666; color: var(--text-secondary);
} }
</style> </style>
</head> </head>
@@ -271,7 +281,6 @@
id="childName" id="childName"
value="${child.name}" value="${child.name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default;"
/> />
<input type="hidden" id="childId" name="childId" value="${child.id}" /> <input type="hidden" id="childId" name="childId" value="${child.id}" />
`; `;
+13 -5
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor</title> <title>Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
@@ -12,27 +14,33 @@
align-items: center; align-items: center;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
background-color: #f0f0f0; background-color: var(--bg-primary);
} }
.login-container { .login-container {
background: white; background: var(--bg-secondary);
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md);
width: 300px; width: 300px;
} }
h1 { h1 {
text-align: center; text-align: center;
color: #333; color: var(--text-primary);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
input { input {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
margin-bottom: 1rem; margin-bottom: 1rem;
border: 1px solid #ddd; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
box-sizing: border-box; box-sizing: border-box;
background-color: var(--input-bg);
color: var(--input-text);
}
input:focus {
outline: none;
border-color: var(--input-focus-border);
} }
button { button {
width: 100%; width: 100%;
+21 -11
View File
@@ -5,18 +5,21 @@
left: 1rem; left: 1rem;
cursor: pointer; cursor: pointer;
z-index: 1000; z-index: 1000;
background: white; background: var(--bg-secondary, #ffffff);
padding: 0.5rem; padding: 0.5rem;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md, 0 2px 10px rgba(0, 0, 0, 0.1));
transition:
background 0.3s,
box-shadow 0.3s;
} }
.burger-menu div { .burger-menu div {
width: 25px; width: 25px;
height: 3px; height: 3px;
background-color: #333; background-color: var(--text-primary, #333333);
margin: 5px 0; margin: 5px 0;
transition: 0.3s; transition: background-color 0.3s;
} }
.menu-backdrop { .menu-backdrop {
@@ -37,14 +40,17 @@
.menu-overlay { .menu-overlay {
position: fixed; position: fixed;
top: 0; top: 0;
left: -250px; left: -100%;
width: 250px; width: 250px;
height: 100vh; height: 100vh;
background: white; background: var(--bg-secondary, #ffffff);
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md, 2px 0 10px rgba(0, 0, 0, 0.1));
transition: left 0.3s; transition:
left 0.3s,
background 0.3s;
z-index: 999; z-index: 999;
padding: 4rem 1rem 1rem 1rem; padding: 4rem 1rem 1rem 1rem;
overflow: hidden;
} }
.menu-overlay.open { .menu-overlay.open {
@@ -53,13 +59,17 @@
.menu-item { .menu-item {
padding: 1rem; padding: 1rem;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--border-color, #eeeeee);
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition:
background 0.2s,
color 0.3s,
border-color 0.3s;
color: var(--text-primary, #333333);
} }
.menu-item:hover { .menu-item:hover {
background: #f5f5f5; background: var(--bg-tertiary, #f5f5f5);
} }
.menu-item.admin { .menu-item.admin {
+13 -8
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Baby Monitor - Register</title> <title>Baby Monitor - Register</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -15,7 +17,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: #667eea;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -33,14 +35,14 @@
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
text-align: center; text-align: center;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
@@ -61,20 +63,23 @@
input { input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 2px solid #e0e0e0; border: 2px solid #ccc;
border-radius: 6px; border-radius: 6px;
font-size: 16px; font-size: 16px;
transition: border-color 0.3s; transition: border-color 0.3s;
background-color: var(--input-bg);
color: var(--input-text);
} }
input:focus { input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--input-focus-border);
} }
.button { .button {
width: 100%; width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 14px; padding: 14px;
@@ -173,7 +178,7 @@
.password-requirements { .password-requirements {
font-size: 12px; font-size: 12px;
color: #666; color: var(--text-secondary);
margin-top: 5px; margin-top: 5px;
} }
@@ -186,7 +191,7 @@
<div class="container"> <div class="container">
<div id="loadingContainer" class="loading-container"> <div id="loadingContainer" class="loading-container">
<div class="spinner"></div> <div class="spinner"></div>
<p style="color: #666">Validating invitation...</p> <p style="color: var(--text-secondary)">Validating invitation...</p>
</div> </div>
<div id="registerContainer" class="hidden"> <div id="registerContainer" class="hidden">
+231 -34
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Settings - Baby Monitor</title> <title>Settings - Baby Monitor</title>
<link rel="stylesheet" href="/dark-mode.css" />
<script src="/theme-init.js"></script>
<link rel="stylesheet" href="/menu.css" /> <link rel="stylesheet" href="/menu.css" />
<style> <style>
* { * {
@@ -16,7 +18,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background-color: #f0f0f0; background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
max-width: 800px; max-width: 800px;
margin: 2rem auto; margin: 2rem auto;
@@ -24,20 +26,20 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px; padding: 40px;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
@@ -45,12 +47,12 @@
.section { .section {
margin-bottom: 40px; margin-bottom: 40px;
padding: 20px; padding: 20px;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
} }
.section h2 { .section h2 {
color: #333; color: var(--text-primary);
font-size: 20px; font-size: 20px;
margin-bottom: 20px; margin-bottom: 20px;
display: flex; display: flex;
@@ -65,7 +67,7 @@
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
color: #333; color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
@@ -73,24 +75,27 @@
.form-group input { .form-group input {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 2px solid #e0e0e0; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
transition: border-color 0.2s; transition: border-color 0.2s;
background-color: var(--input-bg);
color: var(--input-text);
} }
.form-group input:focus { .form-group input:focus {
outline: none; outline: none;
border-color: #667eea; border-color: var(--input-focus-border);
} }
.form-group input:disabled { .form-group input:disabled {
background-color: #f5f5f5; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.button { .button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: var(--gradient-purple);
background-color: #667eea;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -125,7 +130,7 @@
} }
.button.success { .button.success {
background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); background: var(--gradient-success);
} }
.button.success:hover { .button.success:hover {
@@ -139,20 +144,20 @@
} }
.child-card { .child-card {
background: white; background: var(--bg-secondary);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md);
} }
.child-card h3 { .child-card h3 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 18px; font-size: 18px;
} }
.child-info { .child-info {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
margin-bottom: 15px; margin-bottom: 15px;
} }
@@ -169,7 +174,7 @@
.loading { .loading {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.alert { .alert {
@@ -200,7 +205,7 @@
width: 100%; width: 100%;
padding: 15px; padding: 15px;
margin-bottom: 15px; margin-bottom: 15px;
background: white; background: var(--bg-secondary);
border: 2px solid #667eea; border: 2px solid #667eea;
color: #667eea; color: #667eea;
border-radius: 8px; border-radius: 8px;
@@ -233,6 +238,140 @@
</div> </div>
</div> </div>
<div class="section">
<h2>🌓 Theme Preference</h2>
<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;
"
/>
<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;
"
/>
<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;
"
/>
<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>
</div>
</div>
<div class="section"> <div class="section">
<h2>🔑 Change Password</h2> <h2>🔑 Change Password</h2>
<form id="userForm"> <form id="userForm">
@@ -261,12 +400,18 @@
" "
> >
<h3 style="color: #dc3545; margin-bottom: 15px">⚠️ Danger Zone</h3> <h3 style="color: #dc3545; margin-bottom: 15px">⚠️ Danger Zone</h3>
<p style="color: #666; 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: Deleting your account is permanent and cannot be undone. This will:
</p> </p>
<ul <ul
style=" style="
color: #666; color: var(--text-secondary);
margin-bottom: 20px; margin-bottom: 20px;
margin-left: 20px; margin-left: 20px;
font-size: 14px; font-size: 14px;
@@ -285,9 +430,7 @@
</ul> </ul>
<button <button
class="button" class="button"
style=" style="background: var(--gradient-error)"
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
"
onclick="confirmDeleteAccount()" onclick="confirmDeleteAccount()"
> >
🗑️ Delete Account 🗑️ Delete Account
@@ -300,7 +443,7 @@
<button <button
class="quick-action" class="quick-action"
onclick="window.location.href='/add-child.html'" onclick="window.location.href = '/add-child.html'"
> >
Add New Child Add New Child
</button> </button>
@@ -338,15 +481,17 @@
> >
<div <div
style=" style="
background: white; background: var(--bg-secondary);
padding: 30px; padding: 30px;
border-radius: 12px; border-radius: 12px;
max-width: 400px; max-width: 400px;
width: 90%; width: 90%;
" "
> >
<h2 style="margin-top: 0">🔗 Redeem Share Code</h2> <h2 style="margin-top: 0; color: var(--text-primary)">
<p style="color: #666; margin-bottom: 20px"> 🔗 Redeem Share Code
</h2>
<p style="color: var(--text-secondary); margin-bottom: 20px">
Enter the share code to get access to a child. Enter the share code to get access to a child.
</p> </p>
<form id="redeemCodeForm"> <form id="redeemCodeForm">
@@ -360,9 +505,11 @@
style=" style="
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 2px solid #e0e0e0; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
background-color: var(--input-bg);
color: var(--input-text);
" "
/> />
</div> </div>
@@ -402,18 +549,21 @@
> >
<div <div
style=" style="
background: white; background: var(--bg-secondary);
padding: 30px; padding: 30px;
border-radius: 12px; border-radius: 12px;
max-width: 400px; max-width: 400px;
width: 90%; width: 90%;
" "
> >
<h2 style="margin-top: 0">🔗 Share Code</h2> <h2 style="margin-top: 0; color: var(--text-primary)">🔗 Share Code</h2>
<p style="color: #666; margin-bottom: 20px" id="shareCodeChildName"></p> <p
style="color: var(--text-secondary); margin-bottom: 20px"
id="shareCodeChildName"
></p>
<div <div
style=" style="
background: #f8f9fa; background: var(--bg-tertiary);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
margin-bottom: 20px; margin-bottom: 20px;
@@ -430,7 +580,7 @@
style=" style="
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
color: #333; color: var(--text-primary);
letter-spacing: 2px; letter-spacing: 2px;
" "
id="shareCodeValue" id="shareCodeValue"
@@ -451,7 +601,13 @@
</button> </button>
</div> </div>
</div> </div>
<p style="color: #666; 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 > Share this code with another user. They can use it in Settings >
Redeem Share Code to get access to this child. Redeem Share Code to get access to this child.
<br /><br /> <br /><br />
@@ -478,6 +634,47 @@
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ includeHome: true }); initBurgerMenu({ includeHome: true });
// Initialize theme selector
function initThemeSelector() {
const currentTheme = window.getThemePreference();
const themeRadio = document.querySelector(
`input[name="theme"][value="${currentTheme}"]`,
);
if (themeRadio) {
themeRadio.checked = true;
}
// Add visual feedback for selected theme option
updateThemeOptionStyles();
// Listen for theme changes
document.querySelectorAll('input[name="theme"]').forEach((radio) => {
radio.addEventListener("change", (e) => {
window.setTheme(e.target.value);
updateThemeOptionStyles();
});
});
}
function updateThemeOptionStyles() {
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)";
} else {
option.style.borderColor = "#e0e0e0";
option.style.background = "transparent";
}
});
}
// Initialize theme selector on load
initThemeSelector();
let currentUser = null; let currentUser = null;
let children = []; let children = [];
+50 -41
View File
@@ -4,6 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sleep - Baby Monitor</title> <title>Sleep - 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" /> <link rel="stylesheet" href="/menu.css" />
<style> <style>
* { * {
@@ -16,7 +19,7 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif; Cantarell, sans-serif;
background-color: #f0f0f0; background-color: var(--bg-primary);
min-height: 100vh; min-height: 100vh;
max-width: 800px; max-width: 800px;
margin: 2rem auto; margin: 2rem auto;
@@ -24,26 +27,27 @@
} }
.container { .container {
background: white; background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px; padding: 40px;
} }
h1 { h1 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
font-size: 28px; font-size: 28px;
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 14px; font-size: 14px;
} }
.button { .button {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); background: var(--gradient-blue);
background-color: #4facfe;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -79,13 +83,13 @@
.loading { .loading {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
color: #666; color: var(--text-secondary);
} }
.empty-state-icon { .empty-state-icon {
@@ -94,7 +98,7 @@
} }
.empty-state h2 { .empty-state h2 {
color: #333; color: var(--text-primary);
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -105,12 +109,12 @@
.chart-container { .chart-container {
margin-bottom: 40px; margin-bottom: 40px;
padding: 20px; padding: 20px;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
} }
.chart-container h2 { .chart-container h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -148,14 +152,14 @@
} }
.stat-card { .stat-card {
background: #f8f9fa; background: var(--bg-tertiary);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
} }
.stat-card h3 { .stat-card h3 {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
@@ -169,13 +173,13 @@
} }
.list-container { .list-container {
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
} }
.list-container h2 { .list-container h2 {
color: #333; color: var(--text-primary);
margin-bottom: 20px; margin-bottom: 20px;
font-size: 20px; font-size: 20px;
} }
@@ -186,14 +190,14 @@
} }
.sleep-item { .sleep-item {
background: white; background: var(--bg-secondary);
padding: 16px; padding: 16px;
border-radius: 6px; border-radius: 6px;
margin-bottom: 12px; margin-bottom: 12px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); box-shadow: var(--shadow-sm);
} }
.sleep-item:last-child { .sleep-item:last-child {
@@ -206,12 +210,12 @@
.sleep-child { .sleep-child {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
margin-bottom: 4px; margin-bottom: 4px;
} }
.sleep-time { .sleep-time {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
margin-bottom: 4px; margin-bottom: 4px;
} }
@@ -252,23 +256,31 @@
.controls label { .controls label {
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-primary);
} }
.controls select { .controls select,
.controls input {
padding: 8px 16px; padding: 8px 16px;
border: 1px solid #ddd; border: 2px solid var(--border-color, #ddd);
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
background: white; background: var(--bg-secondary);
color: var(--text-primary);
} }
.controls select:focus { .controls select:focus,
.controls input:focus {
outline: none; outline: none;
border-color: #4facfe; border-color: #4facfe;
} }
.controls input[readonly] {
opacity: 0.6;
cursor: default;
}
.badge { .badge {
display: inline-block; display: inline-block;
padding: 4px 8px; padding: 4px 8px;
@@ -388,7 +400,6 @@
id="childDisplay" id="childDisplay"
value="${child.name}" value="${child.name}"
readonly readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/> />
<label for="timeRangeFilter">Time Range:</label> <label for="timeRangeFilter">Time Range:</label>
@@ -479,19 +490,7 @@
return matchesTimeRange && matchesChild; return matchesTimeRange && matchesChild;
}); });
if (filteredSleeps.length === 0) { // Calculate stats (works with empty arrays too)
document.getElementById("content").innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">😴</div>
<h2>No Sleep Data</h2>
<p>Start tracking sleep sessions from the home page.</p>
<button class="button" onclick="window.location.href='/'">Go Home</button>
</div>
`;
return;
}
// Calculate stats
const completedSleeps = filteredSleeps.filter( const completedSleeps = filteredSleeps.filter(
(sleep) => sleep.end_time, (sleep) => sleep.end_time,
); );
@@ -896,7 +895,7 @@
x: { x: {
stacked: true, stacked: true,
}, },
y: { y: getChartScaleOptions("y", {
stacked: true, stacked: true,
beginAtZero: true, beginAtZero: true,
title: { title: {
@@ -912,7 +911,7 @@
} }
}, },
}, },
}, }),
}, },
plugins: { plugins: {
legend: { legend: {
@@ -1057,14 +1056,14 @@
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
y: { y: getChartScaleOptions("y", {
beginAtZero: true, beginAtZero: true,
ticks: { ticks: {
callback: function (value) { callback: function (value) {
return value + "m"; return value + "m";
}, },
}, },
}, }),
}, },
plugins: { plugins: {
legend: { legend: {
@@ -1134,6 +1133,16 @@
// Load data on page load // Load data on page load
loadData(); loadData();
// Listen for theme changes and update charts
window.addEventListener("themechange", () => {
const charts = [
barChartInstance,
durationChartInstance,
avgDurationChartInstance,
].filter((c) => c);
updateChartsForTheme(charts);
});
</script> </script>
</body> </body>
</html> </html>
+87
View File
@@ -0,0 +1,87 @@
/* Theme Initialization Script
* Handles theme detection and application based on localStorage preference or browser settings
* 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");
}
}
// 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 } }),
);
}
};
// 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" } }),
);
}
});
}
})();
Generated
+465 -468
View File
File diff suppressed because it is too large Load Diff