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