@@ -241,33 +241,229 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
241241 <meta name="viewport" content="width=device-width, initial-scale=1">
242242 <title>Config Generator</title>
243243 <style>
244- :root { color-scheme: light dark; font-family: "Segoe UI", Arial, sans-serif; }
245- body { margin: 0; background: #f4f6f8; color: #1f2933; }
246- header { padding: 16px 24px; background: #1d3557; color: #fff; box-shadow: 0 2px 6px rgba(0,0,0,0.2); display: flex; justify-content: space-between; align-items: center; }
247- header h1 { margin: 0; font-size: 1.6rem; }
248- header a { color: #fff; text-decoration: none; font-size: 0.95rem; }
249- main { padding: 20px; max-width: 800px; margin: 0 auto; }
250- .card { background: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(15,23,42,0.08); padding: 20px; }
251- h2 { margin-top: 0; font-size: 1.3rem; }
252- h3 { margin: 20px 0 10px; font-size: 1.1rem; border-bottom: 1px solid #e2e8f0; padding-bottom: 6px; }
253- .field { display: flex; flex-direction: column; margin-bottom: 12px; }
254- .field span { font-size: 0.9rem; color: #475569; margin-bottom: 4px; }
255- .field input, .field select { padding: 8px 10px; border-radius: 6px; border: 1px solid #cbd5f5; font-size: 0.95rem; }
256- .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
257- .sensor-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 16px; margin-bottom: 16px; position: relative; }
258- .sensor-header { display: flex; justify-content: space-between; margin-bottom: 12px; }
259- .sensor-title { font-weight: 600; color: #334155; }
260- .remove-btn { color: #ef4444; cursor: pointer; font-size: 0.9rem; border: none; background: none; padding: 0; }
261- .actions { margin-top: 24px; display: flex; gap: 12px; }
262- button { border: none; border-radius: 6px; padding: 10px 16px; font-size: 0.95rem; cursor: pointer; background: #1d4ed8; color: #fff; }
263- button.secondary { background: #64748b; }
264- button:hover { opacity: 0.9; }
244+ :root {
245+ font-family: "Segoe UI", Arial, sans-serif;
246+ color-scheme: light dark;
247+ }
248+ * {
249+ box-sizing: border-box;
250+ }
251+ body {
252+ margin: 0;
253+ min-height: 100vh;
254+ background: var(--bg);
255+ color: var(--text);
256+ transition: background 0.2s ease, color 0.2s ease;
257+ }
258+ body[data-theme="light"] {
259+ --bg: #f8fafc;
260+ --surface: #ffffff;
261+ --text: #1f2933;
262+ --muted: #475569;
263+ --header-bg: #e2e8f0;
264+ --card-border: rgba(15,23,42,0.08);
265+ --card-shadow: rgba(15,23,42,0.08);
266+ --accent: #2563eb;
267+ --accent-strong: #1d4ed8;
268+ --accent-contrast: #f8fafc;
269+ --chip: #f8fafc;
270+ --input-border: #cbd5e1;
271+ --danger: #ef4444;
272+ --pill-bg: rgba(37,99,235,0.12);
273+ }
274+ body[data-theme="dark"] {
275+ --bg: #0f172a;
276+ --surface: #1e293b;
277+ --text: #e2e8f0;
278+ --muted: #94a3b8;
279+ --header-bg: #16213d;
280+ --card-border: rgba(15,23,42,0.55);
281+ --card-shadow: rgba(0,0,0,0.55);
282+ --accent: #38bdf8;
283+ --accent-strong: #22d3ee;
284+ --accent-contrast: #0f172a;
285+ --chip: rgba(148,163,184,0.15);
286+ --input-border: rgba(148,163,184,0.4);
287+ --danger: #f87171;
288+ --pill-bg: rgba(56,189,248,0.18);
289+ }
290+ header {
291+ background: var(--header-bg);
292+ padding: 28px 24px;
293+ box-shadow: 0 20px 45px var(--card-shadow);
294+ }
295+ header .bar {
296+ display: flex;
297+ justify-content: space-between;
298+ gap: 16px;
299+ flex-wrap: wrap;
300+ align-items: flex-start;
301+ }
302+ header h1 {
303+ margin: 0;
304+ font-size: 1.9rem;
305+ }
306+ header p {
307+ margin: 8px 0 0;
308+ color: var(--muted);
309+ max-width: 640px;
310+ line-height: 1.4;
311+ }
312+ .header-actions {
313+ display: flex;
314+ gap: 12px;
315+ flex-wrap: wrap;
316+ align-items: center;
317+ }
318+ .pill {
319+ border-radius: 999px;
320+ padding: 10px 20px;
321+ text-decoration: none;
322+ font-weight: 600;
323+ background: var(--pill-bg);
324+ color: var(--accent);
325+ border: 1px solid transparent;
326+ transition: transform 0.15s ease;
327+ }
328+ .pill:hover {
329+ transform: translateY(-1px);
330+ }
331+ .icon-button {
332+ width: 42px;
333+ height: 42px;
334+ border-radius: 50%;
335+ border: 1px solid var(--card-border);
336+ background: var(--surface);
337+ color: var(--text);
338+ font-size: 1.2rem;
339+ cursor: pointer;
340+ transition: transform 0.15s ease;
341+ }
342+ .icon-button:hover {
343+ transform: translateY(-1px);
344+ }
345+ main {
346+ padding: 24px;
347+ max-width: 1000px;
348+ margin: 0 auto;
349+ width: 100%;
350+ }
351+ .card {
352+ background: var(--surface);
353+ border-radius: 24px;
354+ border: 1px solid var(--card-border);
355+ padding: 20px;
356+ box-shadow: 0 25px 55px var(--card-shadow);
357+ }
358+ h2 {
359+ margin-top: 0;
360+ font-size: 1.3rem;
361+ }
362+ h3 {
363+ margin: 20px 0 10px;
364+ font-size: 1.1rem;
365+ border-bottom: 1px solid var(--card-border);
366+ padding-bottom: 6px;
367+ color: var(--text);
368+ }
369+ .field {
370+ display: flex;
371+ flex-direction: column;
372+ margin-bottom: 12px;
373+ }
374+ .field span {
375+ font-size: 0.9rem;
376+ color: var(--muted);
377+ margin-bottom: 4px;
378+ }
379+ .field input, .field select {
380+ padding: 10px 12px;
381+ border-radius: 8px;
382+ border: 1px solid var(--input-border);
383+ font-size: 0.95rem;
384+ background: var(--bg);
385+ color: var(--text);
386+ }
387+ .form-grid {
388+ display: grid;
389+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
390+ gap: 12px;
391+ }
392+ .sensor-card {
393+ background: var(--chip);
394+ border: 1px solid var(--card-border);
395+ border-radius: 12px;
396+ padding: 16px;
397+ margin-bottom: 16px;
398+ position: relative;
399+ }
400+ .sensor-header {
401+ display: flex;
402+ justify-content: space-between;
403+ margin-bottom: 12px;
404+ }
405+ .sensor-title {
406+ font-weight: 600;
407+ color: var(--text);
408+ }
409+ .remove-btn {
410+ color: var(--danger);
411+ cursor: pointer;
412+ font-size: 0.9rem;
413+ border: none;
414+ background: none;
415+ padding: 0;
416+ font-weight: 600;
417+ }
418+ .remove-btn:hover {
419+ opacity: 0.8;
420+ }
421+ .actions {
422+ margin-top: 24px;
423+ display: flex;
424+ gap: 12px;
425+ flex-wrap: wrap;
426+ }
427+ button {
428+ border: none;
429+ border-radius: 10px;
430+ padding: 10px 16px;
431+ font-size: 0.95rem;
432+ font-weight: 600;
433+ cursor: pointer;
434+ background: var(--accent);
435+ color: var(--accent-contrast);
436+ transition: transform 0.15s ease;
437+ }
438+ button.secondary {
439+ background: transparent;
440+ border: 1px solid var(--card-border);
441+ color: var(--text);
442+ }
443+ button:hover {
444+ transform: translateY(-1px);
445+ }
446+ button:disabled {
447+ opacity: 0.5;
448+ cursor: not-allowed;
449+ transform: none;
450+ }
265451 </style>
266452</head>
267- <body>
453+ <body data-theme="light" >
268454 <header>
269- <h1>Config Generator</h1>
270- <a href="/">← Back to Dashboard</a>
455+ <div class="bar">
456+ <div>
457+ <h1>Config Generator</h1>
458+ <p>
459+ Create new client configurations with sensor definitions and upload settings for Tank Alarm field units.
460+ </p>
461+ </div>
462+ <div class="header-actions">
463+ <button class="icon-button" id="themeToggle" aria-label="Switch to dark mode">☽</button>
464+ <a class="pill" href="/">← Back to Dashboard</a>
465+ </div>
466+ </div>
271467 </header>
272468 <main>
273469 <div class="card">
@@ -294,6 +490,24 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
294490 </div>
295491 </main>
296492 <script>
493+ // Theme support
494+ const THEME_KEY = 'tankalarmTheme';
495+ const themeToggle = document.getElementById('themeToggle');
496+
497+ function applyTheme(next) {
498+ const theme = next === 'dark' ? 'dark' : 'light';
499+ document.body.dataset.theme = theme;
500+ themeToggle.textContent = theme === 'dark' ? '☀' : '☾';
501+ themeToggle.setAttribute('aria-label', theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode');
502+ localStorage.setItem(THEME_KEY, theme);
503+ }
504+
505+ applyTheme(localStorage.getItem(THEME_KEY) || 'light');
506+ themeToggle.addEventListener('click', () => {
507+ const next = document.body.dataset.theme === 'dark' ? 'light' : 'dark';
508+ applyTheme(next);
509+ });
510+
297511 const sensorTypes = [
298512 { value: 0, label: 'Digital Input' },
299513 { value: 1, label: 'Analog Input (0-10V)' },
0 commit comments