Files
brewpi/web/style.css
T
jensandClaude Sonnet 4.6 787f02b8dc Add a browser client (web/) alongside the desktop GUI
A new HTML/CSS/JS client, added next to client/brewpi_gui.py rather
than replacing it, speaking the exact same WebSocket pub/sub protocol
- no server-side protocol changes needed. server/brewpi.py gains a
--http-port (default 8080) stdlib http.server.ThreadingHTTPServer,
in its own daemon thread and deliberately decoupled from the existing
asyncio WebSocket server, serving web/ statically.

v1 covers the Manual tab (direct heater/stirrer/controller control)
and the new Progress tab - the two most actionable surfaces, neither
needing a charting library. web/app.js ports the relevant logic from
brewpi_gui.py directly: components/sud.py's _build_step()/
_merge_defaults() for resolving the raw schedule doc, and the
StepPlate/_update_step_plates()/update_status_step_label() logic
behind the Progress tab and status line.

Deliberately deferred rather than stubbed (see web/TODO.md for the
full parity backlog against the Qt GUI): Sud control actions (Start/
Pause/Stop/Confirm), the Automatic tab's forecast plot and the Plot
tab's live strip charts, Sud file management, and auth (matching the
WebSocket server's own existing lack of it).

No new Python dependencies - functools/threading/http.server are all
stdlib.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qvu5giu7gvRCyEWzf2Vpx
2026-06-24 20:12:01 +02:00

131 lines
2.3 KiB
CSS

:root {
--led-off: #555555;
--led-green: #2ecc71;
--led-green-dim: #1b5e36;
--led-orange: #e67e22;
--led-orange-dim: #7a4111;
}
body {
font-family: sans-serif;
margin: 0;
padding: 0.5em 1em 4em 1em;
color: #222;
}
header#connection-bar {
display: flex;
align-items: center;
gap: 1em;
padding-bottom: 0.5em;
border-bottom: 1px solid #ccc;
}
.status-connected { color: #2ecc71; font-weight: bold; }
.status-disconnected { color: #999; }
#lcd-panel {
display: grid;
grid-template-columns: 10em 6em 6em;
gap: 0.2em 1em;
margin: 1em 0;
max-width: 30em;
}
.lcd-row { display: contents; }
.lcd-header { font-weight: bold; }
.lcd-label { color: #555; }
.lcd {
font-family: monospace;
font-size: 1.1em;
background: #fff;
border: 1px solid #2ecc71;
padding: 0.1em 0.3em;
text-align: right;
}
nav#tabs {
margin-top: 1em;
border-bottom: 1px solid #ccc;
}
.tab-btn {
background: none;
border: 1px solid #ccc;
border-bottom: none;
padding: 0.4em 1em;
cursor: pointer;
position: relative;
top: 1px;
}
.tab-btn.active {
background: #fff;
font-weight: bold;
border-bottom: 1px solid #fff;
}
.tab-content { display: none; padding: 1em 0; }
.tab-content.active { display: block; }
.panel {
border: 1px solid #ddd;
border-radius: 4px;
padding: 0.6em 1em;
margin-bottom: 0.8em;
max-width: 30em;
}
.panel h3 { margin: 0 0 0.4em 0; }
.panel label {
display: flex;
align-items: center;
gap: 0.6em;
margin: 0.3em 0;
}
.panel input[type="range"] { flex: 1; }
.hidden { display: none; }
#step-plates { display: flex; flex-direction: column; gap: 0.5em; max-width: 36em; }
.step-plate {
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.5em 0.8em;
display: grid;
grid-template-columns: 1.4em 1fr auto;
gap: 0.2em 0.6em;
align-items: center;
}
.step-plate .led {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--led-off);
border: 1px solid #222;
}
.step-plate .title {
font-weight: bold;
grid-column: 2;
}
.step-plate .remaining {
font-weight: bold;
text-align: right;
}
.step-plate .field {
color: #555;
font-size: 0.95em;
}
.step-plate .span2 { grid-column: span 2; }
footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #f0f0f0;
border-top: 1px solid #ccc;
padding: 0.3em 1em;
display: flex;
justify-content: space-between;
font-size: 0.9em;
}