Automatismen als Zeitleiste des Tages, Liste bleibt umschaltbar

Die Uebersicht auf der Startseite zeigt die Automatiken jetzt als
Zeitleiste: eine Bahn je Geraeteart, die Spur ist der Tag. Der Umschalter
"Zeitleiste | Liste" oben in der Karte fuehrt zur bisherigen Tabelle
zurueck und wird im Browser gemerkt.

Jede Automatik erscheint genau einmal, nach festen Regeln
(restricted/zeitleiste.php), auch kuenftige:
- feste Uhrzeit oder Sonnenstand: Punkt; Zeitfenster mit Messwert: Balken;
  Kette: hinter ihrem Ausloeser, als gestrichelter Bogen verbunden
- ohne jede Zeit: Band "Jederzeit"; pausiert: Band "Pausiert"
- an diesem Tag nicht dran (Wochentag, Ferien, Feiertag, Vorabend, wie im
  Runner gerechnet): gestrichelt mit Grund, nie ausgeblendet
- Bahn aus den geschalteten Geraeten ueber bedienform(), Mehrheit gewinnt;
  Bewaesserung am Geraetetyp
- Zaehler "x von y" im Kopf

Dazu: Tag vor/zurueck, Etagen einzeln einblendbar (gemerkt), Jetzt-Linie
und Nachtschatten aus solarLog.daylight (spaetere Tage vom selben
Kalendertag eines Vorjahres), gelaufene Eintraege mit Uhrzeit aus
automation_log, Klick oeffnet den Editor. Schmal nur Symbole in den Bahnen
und Start bei der aktuellen Uhrzeit.

Auf einer Probeseite mit echten Daten geprueft: nichts ragt ueber, keine
ueberlappenden Etiketten, alle 22 Automatiken genau einmal, Filter,
Tageswechsel und Umschalter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:12:15 +02:00
co-authored by Claude Opus 5
parent 8d626abc6d
commit 93c34bf069
8 changed files with 1297 additions and 11 deletions
+346
View File
@@ -2739,3 +2739,349 @@ input[type='range']::-webkit-slider-runnable-track {
}
.gr-raum-stand { color: var(--ton-schrift-3, #6f7a81); font-size: .8rem; }
.gr-raum .btn.gr-platzieren { padding: 0 .45rem; font-size: .75rem; }
/* ===========================================================================
Automatismen als Zeitleiste (js/solar/zeitleiste.js)
Eine Bahn je Geraeteart, die Spur ist der Tag von 0 bis 24 Uhr. Die Nacht
liegt als leichter Schatten dahinter, jetzt ist eine duenne Linie in der
Sonnenfarbe. Jede Bahn traegt ihre Akzentfarbe als --zl-farbe; Punkte,
Balken und Etiketten nehmen sie auf.
=========================================================================== */
.zl {
--zl-linie: var(--ton-linie, rgba(138, 148, 158, .2));
--zl-farbe: #9aa6ad;
--zl-farbe-rgb: 154, 166, 173;
}
.zl-bahn-licht { --zl-farbe: #f0b429; --zl-farbe-rgb: 240, 180, 41; }
.zl-bahn-beschattung { --zl-farbe: #5fb0dc; --zl-farbe-rgb: 95, 176, 220; }
.zl-bahn-heizung { --zl-farbe: #e8836a; --zl-farbe-rgb: 232, 131, 106; }
.zl-bahn-bewaesserung { --zl-farbe: #4cc18a; --zl-farbe-rgb: 76, 193, 138; }
.zl-bahn-schalter { --zl-farbe: #b59cf0; --zl-farbe-rgb: 181, 156, 240; }
/* Kopf */
.zl-kopf {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .6rem 1rem;
margin-bottom: .9rem;
}
.zl-tag .btn.zl-tag-name { min-width: 9.5rem; font-variant-numeric: tabular-nums; }
.zl-etagen { display: flex; flex-wrap: wrap; gap: .3rem; }
.zl-chip {
display: inline-flex;
align-items: center;
gap: .35rem;
padding: .2rem .65rem;
border: 1px solid var(--zl-linie);
border-radius: 999px;
background: transparent;
color: var(--ton-schrift-2, #9aa6ad);
font: 500 .8rem var(--font-anzeige, inherit);
cursor: pointer;
transition: background .15s, border-color .15s, color .15s;
}
.zl-chip:hover { border-color: var(--ton-linie-satt, rgba(138, 148, 158, .38)); color: var(--ton-schrift, #e8ede9); }
.zl-chip.an {
background: rgba(0, 120, 143, .22);
border-color: #00788F;
color: var(--ton-schrift, #e8ede9);
}
.zl-chip.leer { opacity: .55; }
.zl-chip-zahl {
font-size: .68rem;
padding: 0 .35rem;
border-radius: 999px;
background: rgba(138, 148, 158, .18);
color: var(--ton-schrift-2, #9aa6ad);
}
.zl-zahl {
margin-left: auto;
font-size: .8rem;
color: var(--ton-schrift-2, #9aa6ad);
white-space: nowrap;
}
.zl-zahl-leise { color: var(--ton-schrift-3, #6f7a81); }
/* Raster */
.zl-rahmen {
overflow-x: auto;
border-radius: var(--ton-radius-klein, .5rem);
}
.zl-raster { position: relative; }
.zl-zeile {
display: grid;
grid-template-columns: var(--zl-kopf) minmax(0, 1fr);
}
.zl-bahn .zl-spur { border-top: 1px solid var(--zl-linie); }
.zl-bahn-kopf {
position: sticky;
left: 0;
z-index: 3;
display: flex;
align-items: flex-start;
gap: .45rem;
padding: .6rem .6rem 0 0;
background: var(--ton-flaeche, #23282c);
font: 500 .82rem var(--font-anzeige, inherit);
color: var(--ton-schrift, #e8ede9);
}
.zl-bahn-kopf .bi { color: var(--zl-farbe); font-size: .95rem; line-height: 1.1; }
/* Schmal nur das Symbol; der Name steht im Hinweis beim Antippen der Eintraege. */
.zl-rahmen.schmal .zl-bahn-kopf { justify-content: center; padding-right: 0; }
.zl-rahmen.schmal .zl-bahn-kopf span { display: none; }
.zl-bahn-zahl {
margin-left: auto;
font-size: .68rem;
color: var(--ton-schrift-3, #6f7a81);
}
.zl-spur {
position: relative;
min-height: 38px;
/* Alle drei Stunden eine Linie, jede volle Stunde ein Hauch. */
background-image:
linear-gradient(to right, rgba(138, 148, 158, .16) 1px, transparent 1px),
linear-gradient(to right, rgba(138, 148, 158, .06) 1px, transparent 1px);
background-size: 12.5% 100%, 4.1667% 100%;
}
.zl-nacht {
position: absolute;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, .24);
pointer-events: none;
}
.zl-jetzt {
position: absolute;
top: 0;
bottom: 0;
width: 0;
border-left: 1.5px solid var(--ton-sonne, #f0b429);
opacity: .8;
z-index: 2;
pointer-events: none;
}
/* Achse */
.zl-achse .zl-bahn-kopf { padding-top: 0; }
.zl-achse .zl-spur { height: 26px; min-height: 0; background: none; }
.zl-stunde {
position: absolute;
bottom: 4px;
transform: translateX(-50%);
font-size: .7rem;
color: var(--ton-schrift-3, #6f7a81);
font-variant-numeric: tabular-nums;
}
.zl-stunde.anfang { transform: none; }
.zl-stunde.ende { transform: translateX(-100%); }
.zl-sonne {
position: absolute;
top: -2px;
transform: translateX(-50%);
font-size: .78rem;
color: var(--ton-sonne, #f0b429);
opacity: .75;
cursor: help;
}
.zl-jetzt-marke {
position: absolute;
top: 1px;
transform: translateX(-50%);
padding: 0 .35rem;
border-radius: 999px;
background: var(--ton-sonne, #f0b429);
color: #1b1f22;
font: 600 .66rem var(--font-anzeige, inherit);
z-index: 2;
}
/* Eintraege */
.zl-ding {
position: absolute;
height: 24px;
display: flex;
align-items: center;
cursor: pointer;
z-index: 1;
}
.zl-ding:hover { z-index: 4; }
.zl-marke {
flex: none;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--zl-farbe);
background: var(--ton-flaeche, #23282c);
box-sizing: border-box;
}
.zl-punkt { transform: translateX(-6px); }
.zl-punkt.links { flex-direction: row-reverse; transform: translateX(calc(-100% + 6px)); }
.zl-etikett {
margin-left: 6px;
padding: .1rem .5rem;
border-radius: 6px;
border: 1px solid transparent;
background: rgba(var(--zl-farbe-rgb), .1);
color: var(--ton-schrift, #e8ede9);
font-size: .75rem;
line-height: 1.35;
white-space: nowrap;
transition: background .15s, border-color .15s;
}
.zl-punkt.links .zl-etikett { margin-left: 0; margin-right: 6px; }
.zl-etikett b {
font-weight: 600;
margin-right: .4rem;
color: var(--zl-farbe);
font-variant-numeric: tabular-nums;
}
.zl-etikett .bi-check2 { color: var(--ton-gut, #4caf7d); }
.zl-ding:hover .zl-etikett {
background: rgba(var(--zl-farbe-rgb), .2);
border-color: rgba(var(--zl-farbe-rgb), .45);
}
/* Zustaende */
.zl-ding.gelaufen .zl-marke { background: var(--zl-farbe); }
.zl-ding.vorbei { opacity: .5; }
.zl-ding.vorbei .zl-etikett b { color: var(--ton-schrift-2, #9aa6ad); }
.zl-ding.aktiv .zl-marke { box-shadow: 0 0 0 3px rgba(var(--zl-farbe-rgb), .25); }
.zl-ding.nichtdran .zl-marke { border-style: dashed; border-color: var(--ton-schrift-3, #6f7a81); background: transparent; }
.zl-ding.nichtdran .zl-etikett {
background: transparent;
border: 1px dashed var(--zl-linie);
color: var(--ton-schrift-3, #6f7a81);
}
.zl-ding.nichtdran .zl-etikett b { color: var(--ton-schrift-3, #6f7a81); }
/* Zeitfenster: der Balken traegt Laeufe und die frueheste Marke. */
.zl-fenster { min-width: 10px; }
.zl-balken {
position: absolute;
left: 0;
right: 0;
top: 8px;
height: 8px;
border-radius: 4px;
background: rgba(var(--zl-farbe-rgb), .22);
border: 1px solid rgba(var(--zl-farbe-rgb), .4);
}
.zl-fenster .zl-etikett {
position: relative;
margin-left: 0;
background: var(--ton-flaeche, #23282c);
border-color: rgba(var(--zl-farbe-rgb), .3);
}
/* Am rechten Rand endet das Etikett am Balkenende statt dahinter. */
.zl-fenster.links { justify-content: flex-end; }
.zl-lauf {
margin-left: .25rem;
color: var(--ton-gut, #4caf7d);
font-variant-numeric: tabular-nums;
}
.zl-fenster.nichtdran .zl-balken { background: transparent; border-style: dashed; border-color: var(--zl-linie); }
.zl-marke-innen {
position: absolute;
top: 50%;
width: 10px;
height: 10px;
transform: translate(-50%, -50%);
}
/* Ketten */
.zl-ketten {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
z-index: 0;
overflow: visible;
}
.zl-ketten path {
fill: none;
stroke: rgba(154, 166, 173, .55);
stroke-width: 1.2;
stroke-dasharray: 3 3;
}
.zl-leer {
padding: 1.2rem 0 .6rem 132px;
color: var(--ton-schrift-3, #6f7a81);
font-size: .85rem;
}
/* Baender */
.zl-baender {
display: flex;
flex-direction: column;
gap: .45rem;
margin-top: .8rem;
padding-top: .7rem;
border-top: 1px solid var(--zl-linie);
}
.zl-band {
display: grid;
grid-template-columns: 132px minmax(0, 1fr);
align-items: start;
}
.zl-band-chips { display: flex; flex-wrap: wrap; gap: .35rem; }
@media (max-width: 575.98px) {
.zl-band { grid-template-columns: minmax(0, 1fr); gap: .3rem; }
.zl-zahl { margin-left: 0; }
}
.zl-band-titel {
display: inline-flex;
align-items: center;
gap: .4rem;
padding-top: .2rem;
font: 500 .82rem var(--font-anzeige, inherit);
color: var(--ton-schrift-2, #9aa6ad);
}
.zl-band-chip {
display: inline-flex;
align-items: center;
gap: .4rem;
padding: .2rem .6rem;
border-radius: 6px;
border: 1px solid var(--zl-linie);
background: rgba(138, 148, 158, .07);
color: var(--ton-schrift, #e8ede9);
font-size: .75rem;
cursor: pointer;
}
.zl-band-chip:hover { border-color: var(--ton-linie-satt, rgba(138, 148, 158, .38)); }
.zl-band-pause .zl-band-chip { color: var(--ton-schrift-2, #9aa6ad); }
.zl-band-chip.nichtdran { border-style: dashed; }
.zl-band-etage {
font-size: .64rem;
padding: 0 .3rem;
border-radius: 4px;
background: rgba(138, 148, 158, .16);
color: var(--ton-schrift-3, #6f7a81);
}
/* Legende */
.zl-legende {
display: flex;
flex-wrap: wrap;
gap: .4rem 1.1rem;
margin-top: .8rem;
font-size: .72rem;
color: var(--ton-schrift-3, #6f7a81);
}
.zl-legende span { display: inline-flex; align-items: center; gap: .4rem; }
.zl-legende i { display: inline-block; }
.zl-l-punkt { width: 10px; height: 10px; border-radius: 50%; border: 2px solid #5fb0dc; }
.zl-l-punkt.gelaufen { background: #5fb0dc; }
.zl-l-punkt.nichtdran { border-style: dashed; border-color: var(--ton-schrift-3, #6f7a81); }
.zl-l-balken { width: 22px; height: 7px; border-radius: 4px; background: rgba(95, 176, 220, .22); border: 1px solid rgba(95, 176, 220, .4); }
.zl-l-kette { width: 22px; border-top: 1.2px dashed rgba(154, 166, 173, .7); }
/* Ansicht umschalten: eine Klasse mehr als Bootstrap (solar.css laedt vorher). */
.zl-ansicht .btn.active {
background: rgba(0, 120, 143, .25);
border-color: #00788F;
color: var(--ton-schrift, #e8ede9);
}