diff --git a/raspi/static/css/mirror.css b/raspi/static/css/mirror.css index 9f4dbf3..98d5834 100755 --- a/raspi/static/css/mirror.css +++ b/raspi/static/css/mirror.css @@ -6,7 +6,6 @@ :root { --bg: #111318; --bg-card: #1c1f2b; - --bg-card2: #21253a; --border: rgba(255,255,255,.07); --border-h: rgba(255,255,255,.13); @@ -16,7 +15,6 @@ --blue: #3b82f6; --blue-soft: rgba(59,130,246,.12); - --blue-glow: rgba(59,130,246,.35); --green: #22c55e; --green-soft: rgba(34,197,94,.12); @@ -37,11 +35,11 @@ --font: 'Inter', system-ui, sans-serif; --mono: 'JetBrains Mono', monospace; - /* Consumer view palette */ + /* Consumer palette */ --c-bg: #090b12; --c-txt: #dde8ff; - --c-dim: rgba(221,232,255,.45); - --c-ghost: rgba(221,232,255,.2); + --c-dim: rgba(221,232,255,.6); + --c-ghost: rgba(221,232,255,.35); --c-accent: #60a5fa; --c-accent2: #34d399; } @@ -52,17 +50,15 @@ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html, body { - width: 100%; min-height: 100vh; + width: 100%; height: 100vh; + overflow: hidden; background: var(--bg); color: var(--txt); font-family: var(--font); font-size: 14px; - font-weight: 400; -webkit-font-smoothing: antialiased; - overflow: hidden; /* both views are full-viewport */ } -/* grid overlay */ body::before { content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none; @@ -75,75 +71,69 @@ body::before { body.consumer-mode::before { opacity: 0; } /* ══════════════════════════════════════════ - VIEW SWITCHER (hidden toggle button) + VIEW TOGGLE ══════════════════════════════════════════ */ #view-toggle { position: fixed; bottom: 18px; right: 18px; z-index: 9999; - width: 28px; height: 28px; - border-radius: 50%; - background: rgba(255,255,255,.04); - border: 1px solid rgba(255,255,255,.08); + width: 32px; height: 32px; + border-radius: 8px; + background: rgba(255,255,255,.06); + border: 1px solid rgba(255,255,255,.12); cursor: pointer; display: flex; align-items: center; justify-content: center; - opacity: 0; - transition: opacity .3s, background .2s, transform .2s; + opacity: .3; + transition: opacity .25s, background .2s, transform .2s, box-shadow .2s; user-select: none; } #view-toggle:hover { - opacity: 1 !important; - background: rgba(255,255,255,.10); - transform: scale(1.15); + opacity: 1; + background: rgba(59,130,246,.18); + border-color: rgba(59,130,246,.4); + box-shadow: 0 0 12px rgba(59,130,246,.25); + transform: scale(1.08); } -#view-toggle:active { transform: scale(.95); } -#view-toggle svg { width: 12px; height: 12px; fill: rgba(255,255,255,.4); } - -/* Show faintly on long hover of bottom-right corner */ -body:hover #view-toggle { opacity: .15; } +#view-toggle:active { transform: scale(.94); } +#view-toggle svg { width: 13px; height: 13px; fill: rgba(255,255,255,.7); } /* ══════════════════════════════════════════ - VIEWS — TRANSITION CONTAINER + VIEW CONTAINERS ══════════════════════════════════════════ */ #view-dev, #view-consumer { position: fixed; inset: 0; - overflow-y: auto; - transition: opacity .55s cubic-bezier(.4,0,.2,1), - transform .55s cubic-bezier(.4,0,.2,1); + transition: opacity .5s cubic-bezier(.4,0,.2,1), + transform .5s cubic-bezier(.4,0,.2,1); } -/* dev default: visible */ #view-dev { - opacity: 1; transform: none; - z-index: 1; + opacity: 1; transform: none; z-index: 1; pointer-events: all; + overflow-y: auto; } -/* consumer: hidden right */ #view-consumer { - opacity: 0; transform: translateX(60px); - z-index: 2; + opacity: 0; transform: translateX(60px); z-index: 2; pointer-events: none; background: var(--c-bg); + overflow-y: auto; } - -/* active states */ body.consumer-mode #view-dev { - opacity: 0; transform: translateX(-60px); - pointer-events: none; + opacity: 0; transform: translateX(-60px); pointer-events: none; } body.consumer-mode #view-consumer { - opacity: 1; transform: none; - pointer-events: all; + opacity: 1; transform: none; pointer-events: all; } /* ══════════════════════════════════════════ ── DEV VIEW ── ══════════════════════════════════════════ */ + +/* Fixed full-height layout — no page growth */ .mirror-layout { position: relative; z-index: 1; display: grid; grid-template-rows: auto 1fr auto; - min-height: 100vh; + height: 100vh; padding: var(--gap); gap: var(--gap); max-width: 1400px; @@ -154,7 +144,7 @@ body.consumer-mode #view-consumer { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--r); - transition: border-color .25s, background .25s; + transition: border-color .25s; } .card:hover { border-color: var(--border-h); } @@ -164,16 +154,14 @@ body.consumer-mode #view-consumer { grid-template-columns: auto 1fr auto; align-items: center; gap: 20px; - padding: 16px 20px; + padding: 12px 20px; + flex-shrink: 0; } .header-brand { display: flex; align-items: center; gap: 10px; } .brand-icon { - width: 32px; height: 32px; - border-radius: 8px; - background: var(--blue-soft); - border: 1px solid rgba(59,130,246,.3); - display: flex; align-items: center; justify-content: center; - font-size: 16px; + width: 32px; height: 32px; border-radius: 8px; + background: var(--blue-soft); border: 1px solid rgba(59,130,246,.3); + display: flex; align-items: center; justify-content: center; font-size: 16px; } .brand-name { font-size: 13px; font-weight: 600; letter-spacing: .04em; } .brand-sub { font-size: 11px; color: var(--txt-ghost); margin-top: 1px; } @@ -181,23 +169,18 @@ body.consumer-mode #view-consumer { .mirror-clock { text-align: center; } .clock-time { font-family: var(--mono); - font-size: clamp(2rem, 5vw, 3.4rem); - font-weight: 500; - letter-spacing: .08em; - line-height: 1; + font-size: clamp(1.8rem, 4vw, 3rem); + font-weight: 500; letter-spacing: .08em; line-height: 1; } .clock-date { - font-size: 11px; font-weight: 400; - letter-spacing: .18em; text-transform: uppercase; - color: var(--txt-ghost); margin-top: 6px; + font-size: 11px; letter-spacing: .18em; text-transform: uppercase; + color: var(--txt-ghost); margin-top: 5px; } .conn-badge { display: flex; align-items: center; gap: 8px; - padding: 8px 14px; - border-radius: var(--r-sm); - font-family: var(--mono); font-size: 11px; - color: var(--txt-ghost); + padding: 7px 13px; border-radius: var(--r-sm); + font-family: var(--mono); font-size: 11px; color: var(--txt-ghost); transition: all .3s; } .conn-badge.ok { background: var(--green-soft); border-color: rgba(34,197,94,.25); color: var(--green); } @@ -209,21 +192,22 @@ body.consumer-mode #view-consumer { .conn-badge.ok .conn-dot { animation: pulse-ok 2s infinite; box-shadow: 0 0 6px var(--green); } @keyframes pulse-ok { 0%,100%{opacity:1} 50%{opacity:.4} } -/* Body grid */ +/* Body — fills the 1fr row, no overflow */ .mirror-body { display: grid; grid-template-columns: repeat(4, 1fr); - grid-template-rows: auto auto auto; + grid-template-rows: auto auto 1fr; gap: var(--gap); + min-height: 0; /* allow grid children to shrink */ } /* Sensor tiles */ .sensor-tile { - padding: 18px 18px 14px; + padding: 14px 16px 12px; position: relative; overflow: hidden; - transition: transform .2s, box-shadow .2s, border-color .25s; + transition: transform .2s, border-color .25s; } -.sensor-tile:hover { transform: translateY(-1px); box-shadow: 0 8px 24px rgba(0,0,0,.35); } +.sensor-tile:hover { transform: translateY(-1px); } .sensor-tile::after { content: ''; position: absolute; top: 0; left: 0; bottom: 0; width: 3px; @@ -233,11 +217,11 @@ body.consumer-mode #view-consumer { .sensor-tile.breach-high::after { background: var(--red); } .sensor-tile.breach-low::after { background: var(--yellow); } -.sensor-tile-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; } +.sensor-tile-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } .sensor-icon { - width: 30px; height: 30px; border-radius: 7px; + width: 28px; height: 28px; border-radius: 7px; background: var(--blue-soft); border: 1px solid rgba(59,130,246,.2); - display: flex; align-items: center; justify-content: center; font-size: 14px; + display: flex; align-items: center; justify-content: center; font-size: 13px; transition: background .3s, border-color .3s; } .sensor-tile.breach-high .sensor-icon { background: var(--red-soft); border-color: rgba(239,68,68,.3); } @@ -247,95 +231,118 @@ body.consumer-mode #view-consumer { background: var(--blue); opacity: .4; transition: all .3s; } .sensor-tile.breach-high .sensor-state-dot, -.sensor-tile.breach-low .sensor-state-dot { - opacity: 1; animation: blink .8s infinite; -} -.sensor-tile.breach-high .sensor-state-dot { background: var(--red); box-shadow: 0 0 6px var(--red); } +.sensor-tile.breach-low .sensor-state-dot { opacity: 1; animation: blink .8s infinite; } +.sensor-tile.breach-high .sensor-state-dot { background: var(--red); box-shadow: 0 0 6px var(--red); } .sensor-tile.breach-low .sensor-state-dot { background: var(--yellow); box-shadow: 0 0 6px var(--yellow); } @keyframes blink { 0%,100%{opacity:1} 50%{opacity:.2} } -.sensor-label { font-size: 11px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--txt-ghost); } -.sensor-value { font-family: var(--mono); font-size: clamp(1.5rem,2.5vw,2.1rem); font-weight: 500; line-height: 1; transition: color .3s; } +.sensor-label { font-size: 10px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--txt-ghost); } +.sensor-value { font-family: var(--mono); font-size: clamp(1.3rem,2vw,1.9rem); font-weight: 500; line-height: 1; margin-top: 4px; transition: color .3s; } .sensor-tile.breach-high .sensor-value { color: var(--red); } .sensor-tile.breach-low .sensor-value { color: var(--yellow); } -.sensor-meta { margin-top: 10px; display: flex; align-items: center; justify-content: space-between; } +.sensor-meta { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; } .sensor-threshold { font-family: var(--mono); font-size: 10px; color: var(--txt-ghost); } .sensor-alert-badge { - font-size: 10px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase; - padding: 2px 7px; border-radius: 4px; + font-size: 10px; font-weight: 500; letter-spacing: .04em; text-transform: uppercase; + padding: 2px 6px; border-radius: 4px; background: var(--red-soft); color: var(--red); border: 1px solid rgba(239,68,68,.25); } .sensor-alert-badge.low { background: var(--yellow-soft); color: var(--yellow); border-color: rgba(245,158,11,.25); } /* Stat row */ .stat-row { grid-column: 1/-1; display: grid; grid-template-columns: repeat(4,1fr); gap: var(--gap); } -.stat-card { padding: 14px 16px; display: flex; align-items: center; gap: 12px; } +.stat-card { padding: 12px 14px; display: flex; align-items: center; gap: 10px; } .stat-card-icon { - width: 36px; height: 36px; border-radius: 9px; - display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; + width: 34px; height: 34px; border-radius: 9px; + display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; } .stat-card-icon.blue { background: var(--blue-soft); } .stat-card-icon.green { background: var(--green-soft); } .stat-card-icon.red { background: var(--red-soft); } .stat-card-icon.teal { background: var(--teal-soft); } .stat-info { min-width: 0; } -.stat-label { font-size: 11px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--txt-ghost); margin-bottom: 3px; } -.stat-value { font-family: var(--mono); font-size: 1.15rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.stat-label { font-size: 10px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--txt-ghost); margin-bottom: 2px; } +.stat-value { font-family: var(--mono); font-size: 1.1rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .stat-value.red { color: var(--red); } -.stat-sub { font-size: 10px; color: var(--txt-ghost); margin-top: 2px; font-family: var(--mono); } +.stat-sub { font-size: 10px; color: var(--txt-ghost); margin-top: 1px; font-family: var(--mono); } /* Chart */ -.chart-panel { grid-column: 1/3; padding: 16px 18px 14px; } -.panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; } -.panel-title { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--txt-dim); } +.chart-panel { grid-column: 1/3; padding: 14px 16px 12px; display: flex; flex-direction: column; min-height: 0; } +.panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-shrink: 0; } +.panel-title { font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--txt-dim); } .panel-badge { font-family: var(--mono); font-size: 10px; color: var(--txt-ghost); - background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: 4px; padding: 2px 7px; + background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: 4px; padding: 2px 6px; } -#chart { display: block; width: 100%; } +#chart { display: block; width: 100%; flex: 1; min-height: 0; } -/* Log panel */ -.log-panel { grid-column: 3/-1; padding: 0; display: flex; flex-direction: column; overflow: hidden; min-height: 220px; } -.tab-bar { display: flex; border-bottom: 1px solid var(--border); flex-shrink: 0; padding: 0 6px; gap: 2px; } +/* Log panel — fixed height, internal scroll */ +.log-panel { + grid-column: 3/-1; + padding: 0; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; /* critical: don't grow past grid row */ +} +.tab-bar { + display: flex; border-bottom: 1px solid var(--border); + flex-shrink: 0; padding: 0 6px; gap: 2px; +} .tab { - flex: 1; padding: 10px 12px; - font-size: 11px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; + flex: 1; padding: 8px 10px; + font-size: 10px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--txt-ghost); cursor: pointer; text-align: center; transition: color .2s; border-bottom: 2px solid transparent; margin-bottom: -1px; } .tab:hover { color: var(--txt-dim); } .tab.active { color: var(--blue); border-bottom-color: var(--blue); } -.tab-body { flex: 1; overflow-y: auto; padding: 8px 12px; font-family: var(--mono); font-size: 11px; } + +/* The scroll container — takes remaining height, never grows page */ +.tab-body { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 6px 10px; + font-family: var(--mono); + font-size: 11px; + min-height: 0; +} .tab-body::-webkit-scrollbar { width: 3px; } .tab-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } + .tab-pane { display: none; } .tab-pane.active { display: block; } .log-entry { - display: grid; grid-template-columns: 68px 1fr auto; - gap: 8px; padding: 5px 0; + display: grid; grid-template-columns: 64px 1fr auto; + gap: 8px; padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,.04); - animation: fadeSlide .15s ease; align-items: center; + animation: fadeSlide .12s ease; align-items: center; } -@keyframes fadeSlide { from{opacity:0;transform:translateY(-3px)} to{opacity:1;transform:none} } +@keyframes fadeSlide { from{opacity:0;transform:translateY(-2px)} to{opacity:1;transform:none} } .log-ts { color: var(--txt-ghost); font-size: 10px; } .log-raw { color: var(--txt-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .log-num { color: var(--blue); text-align: right; white-space: nowrap; } -.alert-entry { display: grid; grid-template-columns: 16px 68px 1fr; gap: 8px; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,.04); align-items: center; animation: fadeSlide .15s ease; } -.alert-icon { color: var(--red); font-size: 12px; } +.alert-entry { display: grid; grid-template-columns: 14px 64px 1fr; gap: 8px; padding: 5px 0; border-bottom: 1px solid rgba(255,255,255,.04); align-items: center; animation: fadeSlide .12s ease; } +.alert-icon { color: var(--red); font-size: 11px; } .alert-ts { color: var(--txt-ghost); font-size: 10px; } -.alert-msg { color: var(--txt-dim); } +.alert-msg { color: var(--txt-dim); font-size: 11px; } .alert-badge { display: none; align-items: center; justify-content: center; - min-width: 16px; height: 16px; border-radius: 8px; + min-width: 15px; height: 15px; border-radius: 8px; background: var(--red); color: #fff; - font-size: 9px; font-weight: 600; margin-left: 5px; padding: 0 3px; + font-size: 9px; font-weight: 600; margin-left: 4px; padding: 0 3px; } .alert-badge.visible { display: inline-flex; } /* Footer */ -.mirror-footer { display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; font-size: 11px; font-family: var(--mono); color: var(--txt-ghost); } +.mirror-footer { + display: flex; justify-content: space-between; align-items: center; + padding: 8px 20px; font-size: 10px; font-family: var(--mono); color: var(--txt-ghost); + flex-shrink: 0; +} /* ══════════════════════════════════════════ ── CONSUMER VIEW ── @@ -347,157 +354,167 @@ body.consumer-mode #view-consumer { justify-content: center; min-height: 100vh; padding: 40px 24px; - gap: 0; position: relative; overflow: hidden; + gap: 0; } -/* Atmospheric background gradient */ +/* Atmospheric glow */ .consumer-wrap::before { content: ''; - position: absolute; inset: 0; + position: absolute; inset: 0; pointer-events: none; background: - radial-gradient(ellipse 80% 60% at 50% 10%, rgba(59,130,246,.08) 0%, transparent 65%), - radial-gradient(ellipse 60% 40% at 20% 80%, rgba(20,184,166,.05) 0%, transparent 60%); - pointer-events: none; + radial-gradient(ellipse 80% 55% at 50% 5%, rgba(59,130,246,.1) 0%, transparent 65%), + radial-gradient(ellipse 55% 35% at 15% 85%, rgba(20,184,166,.06) 0%, transparent 60%); } -/* ── Big Clock ── */ -.c-clock { - text-align: center; - margin-bottom: 8px; -} +/* Big clock */ +.c-clock { text-align: center; margin-bottom: 6px; } .c-time { font-family: var(--mono); - font-size: clamp(4rem, 14vw, 10rem); + font-size: clamp(4.5rem, 15vw, 11rem); font-weight: 300; letter-spacing: .04em; - color: var(--c-txt); + color: #f0f6ff; line-height: 1; - text-shadow: 0 0 80px rgba(96,165,250,.15); + text-shadow: 0 0 80px rgba(96,165,250,.18); } .c-date { - font-size: clamp(.8rem, 1.5vw, 1.1rem); + font-size: clamp(.8rem, 1.4vw, 1rem); font-weight: 400; letter-spacing: .22em; text-transform: uppercase; - color: var(--c-dim); + color: rgba(221,232,255,.5); margin-top: 10px; } -/* Divider line */ .c-divider { width: 60px; height: 1px; - background: linear-gradient(90deg, transparent, rgba(96,165,250,.35), transparent); - margin: 32px auto; + background: linear-gradient(90deg, transparent, rgba(96,165,250,.4), transparent); + margin: 28px auto; } -/* ── Weather block ── */ -.c-weather { - text-align: center; - margin-bottom: 40px; -} +/* Weather block */ +.c-weather { text-align: center; margin-bottom: 32px; } .c-location { - font-size: .7rem; + font-size: .65rem; font-weight: 500; letter-spacing: .22em; text-transform: uppercase; - color: var(--c-ghost); - margin-bottom: 18px; + color: rgba(221,232,255,.4); + margin-bottom: 16px; +} +.c-provider { + display: inline; + color: rgba(96,165,250,.55); + font-size: .6rem; + letter-spacing: .12em; } .c-weather-main { - display: flex; - align-items: center; - justify-content: center; - gap: 20px; - margin-bottom: 10px; + display: flex; align-items: center; justify-content: center; gap: 20px; margin-bottom: 8px; } .c-weather-icon { - font-size: clamp(3rem, 8vw, 5.5rem); + font-size: clamp(3rem, 7vw, 5rem); line-height: 1; - filter: drop-shadow(0 0 20px rgba(96,165,250,.3)); + filter: drop-shadow(0 0 18px rgba(96,165,250,.3)); } .c-temp-outside { font-family: var(--mono); - font-size: clamp(2.8rem, 8vw, 5.5rem); + font-size: clamp(3rem, 8vw, 5.5rem); font-weight: 300; letter-spacing: .04em; - color: var(--c-txt); + color: #f0f6ff; line-height: 1; } .c-weather-desc { - font-size: clamp(.9rem, 1.5vw, 1.1rem); - color: var(--c-dim); - letter-spacing: .06em; + font-size: clamp(.9rem, 1.3vw, 1.05rem); + color: rgba(221,232,255,.65); + letter-spacing: .05em; margin-top: 6px; } -/* ── Sensor + weather stat pills ── */ -.c-stats { - display: flex; - gap: 16px; - justify-content: center; - flex-wrap: wrap; - margin-top: 36px; +/* Rain forecast line */ +.c-rain-forecast { + font-size: .7rem; + color: rgba(221,232,255,.38); + letter-spacing: .06em; + margin-top: 10px; + font-style: italic; } +.c-rain-forecast.has-rain { color: rgba(96,165,250,.65); font-style: normal; } + +/* Stat pills grid */ +.c-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + width: 100%; + max-width: 680px; +} + .c-stat { display: flex; flex-direction: column; align-items: center; - gap: 6px; - padding: 18px 28px; - border-radius: 16px; - background: rgba(255,255,255,.03); - border: 1px solid rgba(255,255,255,.06); - min-width: 120px; - backdrop-filter: blur(10px); + gap: 5px; + padding: 16px 14px 14px; + border-radius: 14px; + background: rgba(255,255,255,.035); + border: 1px solid rgba(255,255,255,.07); + backdrop-filter: blur(12px); transition: border-color .3s, background .3s; + position: relative; } .c-stat:hover { - border-color: rgba(96,165,250,.18); - background: rgba(96,165,250,.04); -} -.c-stat-icon { font-size: 1.4rem; line-height: 1; } -.c-stat-val { - font-family: var(--mono); - font-size: clamp(1.4rem, 2.5vw, 2rem); - font-weight: 400; - color: var(--c-txt); - line-height: 1; -} -.c-stat-label { - font-size: .65rem; - font-weight: 500; - letter-spacing: .15em; - text-transform: uppercase; - color: var(--c-ghost); + border-color: rgba(96,165,250,.2); + background: rgba(96,165,250,.05); } -/* Rain probability bar */ +/* Subtle source label */ +.c-stat-source { + position: absolute; + top: 7px; right: 9px; + font-size: 8px; + font-family: var(--mono); + letter-spacing: .08em; + text-transform: uppercase; + color: rgba(221,232,255,.2); +} + +.c-stat-label { + font-size: .62rem; + font-weight: 500; + letter-spacing: .14em; + text-transform: uppercase; + color: rgba(221,232,255,.4); +} + +/* Coloured stat values */ +.c-stat-val { + font-family: var(--mono); + font-size: clamp(1.4rem, 2.8vw, 2rem); + font-weight: 400; + line-height: 1; + transition: color .5s; +} + +.c-stat-val.col-temp { color: #fb923c; } /* orange — warm */ +.c-stat-val.col-hum { color: #60a5fa; } /* blue — water */ +.c-stat-val.col-hum-out{ color: #7dd3fc; } /* lighter blue */ +.c-stat-val.col-rain { color: #818cf8; } /* indigo — rain */ +.c-stat-val.col-feels { color: #f472b6; } /* pink — feels like */ +.c-stat-val.col-wind { color: #34d399; } /* teal — wind */ +.c-stat-val.loading { color: rgba(221,232,255,.2); } + +/* Rain bar */ .c-rain-bar-wrap { - width: 100%; - height: 3px; - background: rgba(255,255,255,.08); - border-radius: 2px; - overflow: hidden; - margin-top: 4px; + width: 80%; height: 3px; + background: rgba(255,255,255,.08); border-radius: 2px; overflow: hidden; } .c-rain-bar { height: 100%; - background: linear-gradient(90deg, var(--c-accent), var(--c-accent2)); - border-radius: 2px; - transition: width 1s ease; -} - -/* Loading / no-data state */ -.c-stat-val.loading { opacity: .3; } - -/* Weather error notice */ -.c-weather-error { - font-size: .7rem; - color: var(--c-ghost); - margin-top: 16px; - font-family: var(--mono); + background: linear-gradient(90deg, #818cf8, #60a5fa); + border-radius: 2px; transition: width 1s ease; } /* ══════════════════════════════════════════ @@ -507,12 +524,11 @@ body.consumer-mode #view-consumer { .mirror-body { grid-template-columns: 1fr 1fr; } .stat-row { grid-template-columns: 1fr 1fr; } .chart-panel { grid-column: 1/-1; } - .log-panel { grid-column: 1/-1; min-height: 200px; } + .log-panel { grid-column: 1/-1; } } @media (max-width: 640px) { .mirror-body { grid-template-columns: 1fr 1fr; } .mirror-header { grid-template-columns: 1fr; } .header-brand, .conn-badge { display: none; } - .c-stats { gap: 10px; } - .c-stat { min-width: 100px; padding: 14px 18px; } + .c-stats { grid-template-columns: repeat(2, 1fr); } } diff --git a/raspi/static/js/dashboard.js b/raspi/static/js/dashboard.js index 402b8e3..588ccb5 100755 --- a/raspi/static/js/dashboard.js +++ b/raspi/static/js/dashboard.js @@ -231,6 +231,25 @@ async function fetchWeather() { const bar = document.getElementById('c-rain-bar'); if (bar && data.rain_prob != null) bar.style.width = `${data.rain_prob}%`; + // Rain forecast line + const forecastEl = document.getElementById('c-rain-forecast'); + if (forecastEl && data.rain_prob != null) { + const p = data.rain_prob; + if (p <= 10) { + forecastEl.textContent = 'Kein Regen in Sicht — weiterhin trocken'; + forecastEl.className = 'c-rain-forecast'; + } else if (p <= 30) { + forecastEl.textContent = `${p} % Regenwahrscheinlichkeit — überwiegend trocken`; + forecastEl.className = 'c-rain-forecast'; + } else if (p <= 60) { + forecastEl.textContent = `${p} % Regenwahrscheinlichkeit — möglicherweise Regen`; + forecastEl.className = 'c-rain-forecast has-rain'; + } else { + forecastEl.textContent = `${p} % Regenwahrscheinlichkeit — Regen wahrscheinlich`; + forecastEl.className = 'c-rain-forecast has-rain'; + } + } + } catch(e) { console.warn('[Mirror] Weather error:', e); } @@ -309,7 +328,7 @@ async function poll() { `${e.raw}` + (num ? `${num}` : ''); logPane.prepend(row); - while (logPane.children.length > 100) logPane.removeChild(logPane.lastChild); + while (logPane.children.length > 30) logPane.removeChild(logPane.lastChild); }); /* Alerts */ diff --git a/raspi/templates/dashboard.html b/raspi/templates/dashboard.html index 970ddf8..e07a4d5 100755 --- a/raspi/templates/dashboard.html +++ b/raspi/templates/dashboard.html @@ -7,18 +7,16 @@ data-sensors="{{ sensors_json | e }}" data-location="{{ location_json | e }}"> - - - + ══════════════════════════════ -->
@@ -27,7 +25,7 @@
🪞
{{ title }}
-
Arduino Wetterstation
+
Entwickleransicht · Arduino USB
@@ -89,7 +87,7 @@
🔌
Verbindung
-
+
@@ -103,6 +101,7 @@
+
Rohdaten
@@ -134,9 +133,9 @@
- + ══════════════════════════════ -->
@@ -148,67 +147,66 @@
- +
-
+
+ +  ·  + Open-Meteo +
—°
Wetterdaten werden geladen …
+
- +
-
-
🌡️
-
-
Innen Temp.
+ Sensor +
+
Innentemperatur
-
-
💧
-
-
Luftfeucht.
+ Sensor +
+
Innenfeuchte
-
-
🌬️
-
-
Außen Feuchte
+ Open-Meteo +
+
Außenfeuchte
-
-
🌧️
-
+ Open-Meteo +
Regenwahrsch.
-
-
🧥
-
+ Open-Meteo +
Gefühlt
-
-
💨
-
+ Open-Meteo +
Wind
-
+
-
+