139 lines
5.3 KiB
HTML
139 lines
5.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<style>
|
|
/* Dashboard e-ink 1264x1680 — noir & blanc, fort contraste, pas de dépendance couleur. */
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
:root {
|
|
--ink: #000;
|
|
--paper: #fff;
|
|
--muted: #555;
|
|
--line: #000;
|
|
--gauge-bg: #d9d9d9;
|
|
}
|
|
html, body {
|
|
width: {{ width }}px; height: {{ height }}px;
|
|
background: var(--paper); color: var(--ink);
|
|
font-family: "DejaVu Sans", "Noto Sans", Arial, sans-serif;
|
|
-webkit-font-smoothing: none;
|
|
}
|
|
body { padding: 48px 56px; display: flex; flex-direction: column; }
|
|
.section { margin-bottom: 40px; }
|
|
.rule { border: 0; border-top: 4px solid var(--line); margin: 0 0 28px; }
|
|
|
|
/* En-tête : heure + date */
|
|
header { display: flex; justify-content: space-between; align-items: flex-end; }
|
|
.clock { font-size: 150px; font-weight: 800; line-height: 0.9; letter-spacing: -4px; }
|
|
.date { font-size: 40px; font-weight: 600; text-align: right; }
|
|
.date .dow { font-size: 52px; font-weight: 800; text-transform: capitalize; }
|
|
|
|
/* Météo */
|
|
.weather { display: flex; align-items: center; gap: 36px; }
|
|
.weather .icon { font-size: 110px; line-height: 1; }
|
|
.weather .temp { font-size: 96px; font-weight: 800; }
|
|
.weather .meta { font-size: 36px; color: var(--muted); }
|
|
.weather .meta b { color: var(--ink); }
|
|
|
|
/* Titre de section */
|
|
.title { font-size: 34px; font-weight: 800; text-transform: uppercase;
|
|
letter-spacing: 3px; margin-bottom: 22px; }
|
|
|
|
/* Jauges Claude */
|
|
.gauge { margin-bottom: 34px; }
|
|
.gauge .row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
|
|
.gauge .name { font-size: 40px; font-weight: 700; }
|
|
.gauge .pct { font-size: 64px; font-weight: 800; }
|
|
.gauge .pct small { font-size: 32px; font-weight: 600; }
|
|
.bar { position: relative; height: 46px; background: var(--gauge-bg);
|
|
border: 4px solid var(--ink); border-radius: 6px; overflow: hidden; }
|
|
.bar .fill { position: absolute; top: 0; left: 0; bottom: 0; background: var(--ink); }
|
|
.bar.low .fill { background: repeating-linear-gradient(45deg, #000 0 14px, #fff 14px 22px); }
|
|
.gauge .sub { font-size: 30px; color: var(--muted); margin-top: 10px; }
|
|
.err { font-size: 40px; font-weight: 700; padding: 24px; border: 4px dashed var(--ink); }
|
|
|
|
/* Grille Home Assistant */
|
|
.ha-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 40px; }
|
|
.ha-item { display: flex; justify-content: space-between; align-items: baseline;
|
|
border-bottom: 3px solid var(--ink); padding-bottom: 12px; }
|
|
.ha-item .k { font-size: 38px; font-weight: 600; }
|
|
.ha-item .v { font-size: 44px; font-weight: 800; }
|
|
|
|
footer { margin-top: auto; display: flex; justify-content: space-between;
|
|
font-size: 28px; color: var(--muted); padding-top: 20px; border-top: 3px solid var(--ink); }
|
|
.stale { font-weight: 800; color: var(--ink); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="section">
|
|
<div class="clock">{{ time }}</div>
|
|
<div class="date">
|
|
<div class="dow">{{ dow }}</div>
|
|
<div>{{ date }}</div>
|
|
</div>
|
|
</header>
|
|
<hr class="rule">
|
|
|
|
<div class="section weather">
|
|
{% if weather.ok %}
|
|
<div class="icon">{{ weather.icon }}</div>
|
|
<div>
|
|
<div class="temp">{{ weather.temp | round | int }}°</div>
|
|
<div class="meta">
|
|
{{ weather.label }} · ressenti <b>{{ weather.feels_like | round | int }}°</b>
|
|
</div>
|
|
</div>
|
|
<div class="meta" style="margin-left:auto; text-align:right;">
|
|
<div>min <b>{{ weather.temp_min | round | int }}°</b> / max <b>{{ weather.temp_max | round | int }}°</b></div>
|
|
{% if weather.precip_prob is not none %}<div>pluie <b>{{ weather.precip_prob }}%</b></div>{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="meta">Météo indisponible</div>
|
|
{% endif %}
|
|
</div>
|
|
<hr class="rule">
|
|
|
|
<div class="section">
|
|
<div class="title">Abonnement Claude{% if claude.ok %} · Max 5x{% endif %}</div>
|
|
{% if not claude.ok %}
|
|
<div class="err">⚠ {{ claude.error }}</div>
|
|
{% else %}
|
|
{% for g in gauges %}
|
|
<div class="gauge">
|
|
<div class="row">
|
|
<span class="name">{{ g.name }}</span>
|
|
<span class="pct">{{ g.remaining | round | int }}<small>% restant</small></span>
|
|
</div>
|
|
<div class="bar {% if g.remaining < 20 %}low{% endif %}">
|
|
<div class="fill" style="width: {{ g.remaining | round(1) }}%;"></div>
|
|
</div>
|
|
<div class="sub">reset dans {{ g.resets_in }}{% if g.extra %} · {{ g.extra }}{% endif %}</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% if claude.burn_rate %}
|
|
<div class="sub" style="font-size:32px;">Burn rate : {{ claude.burn_rate | round | int }} tok/min</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if ha_states %}
|
|
<hr class="rule">
|
|
<div class="section">
|
|
<div class="title">Maison</div>
|
|
<div class="ha-grid">
|
|
{% for s in ha_states %}
|
|
<div class="ha-item"><span class="k">{{ s.label }}</span><span class="v">{{ s.display }}</span></div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<footer>
|
|
<span>Monitorink</span>
|
|
<span class="{% if stale %}stale{% endif %}">maj {{ updated }}{% if stale %} · DONNÉE PÉRIMÉE{% endif %}</span>
|
|
</footer>
|
|
|
|
</body>
|
|
</html>
|