Files
Smart-Dashboard/css/solar.css
T
adminandClaude Opus 5 8d626abc6d Einstellungen -> Grundriss: Etagen, Raeume und Kacheln am Plan pflegen
Neuer Reiter mit Plan-Editor: Etagen anlegen, umbenennen, sortieren,
loeschen und als Standard markieren; Grundrissbild hochladen; Raeume mit
Name, Kuerzel und Thermostat-Zweig anlegen und bearbeiten; Kacheln im Plan
ziehen oder einen Raum ohne Kachel mit "Platzieren" und einem Klick setzen.
Loest tools/kachelpositionen.php ab - Raster, Geisterkachel und die
Umrechnung Mitte -> linke obere Ecke sind von dort uebernommen.

- restricted/grundriss.php: alle Pruefungen und Schreibzugriffe. Eine
  Etage mit Raeumen oder Automatiken laesst sich nicht loeschen, die
  Ablehnung nennt die Namen. Raeume loeschen laesst ihre Geraete ohne Raum.
- Grundrissbilder nach tiles/grundriss/ (beschreibbar, nicht im Git),
  nur PNG/JPEG/WebP, am Inhalt geprueft; SVG bewusst nicht. Prüfsumme im
  Dateinamen, alte hochgeladene Bilder werden weggeraeumt.
- ajax/settings.php: ?action=grundriss und sieben POST-Aktionen, jede
  antwortet mit dem ganzen neuen Stand.
- "Werte" springt in den Reiter Home-Kacheln und oeffnet die Kachel;
  Home-Kacheln und Geraete laden nach einer Grundriss-Aenderung nach.
- Startseite ohne Etage mit platziertem Raum: Hinweis auf die
  Einstellungen statt einer leeren Zeichenflaeche.
- README: Grundriss als Inhalt statt Code, neue Dateien und Endpunkte,
  Reihenfolge der Schema-Dateien fuer ein neues Haus.

Backend mit einer Probe-Etage durchgespielt (auch Fehlerfaelle, Upload,
Aufraeumen); Oberflaeche auf einer Probeseite mit echten Daten und
Zeiger-Ereignissen geprueft: Ziehen, Antippen, Platzieren, neuer Raum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 11:11:10 +02:00

2742 lines
92 KiB
CSS

/* ==========================================================================
Grundton der Oberflaeche
Die Seite laeuft auf AdminLTE, also auf Bootstrap. Beide beziehen ihre
Farben aus Variablen - deshalb wird hier nicht Regel fuer Regel
ueberschrieben, sondern die Palette ausgetauscht: ein paar Variablen am
Wurzelelement, und Karten, Tabellen, Abzeichen und Knoepfe folgen von
selbst. Was danach noch kommt, sind wenige gezielte Regeln fuer die Dinge,
die Bootstrap fest verdrahtet hat (Kartenrahmen, Kopfzeilen, Trennlinien).
Die Palette selbst ist die des Jahresvergleichs: ein leicht blaustichiges
Anthrazit als Grund, eine Spur hellere Flaeche fuer Karten, Haarlinien statt
Rahmen - und Bernstein als einzige Akzentfarbe, weil das auf dieser Seite
die Farbe der eigenen Anlage ist. Gruen, Rot und Gelb bleiben den
Zustaenden vorbehalten und sind kein Schmuck.
========================================================================== */
:root,
[data-bs-theme="dark"] {
--ton-grund: #191d20; /* Seitenhintergrund */
--ton-flaeche: #23282c; /* Karten */
--ton-flaeche-2: #2b3237; /* Kopfzeilen, Tabellenkopf */
--ton-linie: rgba(138, 148, 158, .20);
--ton-linie-satt: rgba(138, 148, 158, .38);
--ton-schrift: #e8ede9;
--ton-schrift-2: #9aa6ad;
--ton-schrift-3: #6f7a81;
--ton-sonne: #f0b429; /* die eigene Anlage */
--ton-sonne-rgb: 240, 180, 41;
--ton-gut: #4caf7d;
--ton-schlecht: #e06060;
/* Zwei Radien statt einem. Der grosse gilt fuer Flaechen (Karten, Modale,
Eingabefelder), der kleine fuer alles, was in einer Reihe steht -
Schalter, kleine Knoepfe, Abzeichen. Bootstrap kennt beide, benutzte
bisher aber nur den grossen; der kleine stand auf seiner Vorgabe von
.25rem und wirkte daneben eckig. */
--ton-radius: .875rem;
--ton-radius-klein: .5rem;
/* Die Anzeigeschrift. Poppins liegt seit jeher unter assets/fonts und trug
bisher nur die Etagenknoepfe im Aussenplan - eine ganze Schrift fuer vier
Buchstaben. Als Variable traegt sie jetzt alle Ueberschriften: waermer
und eine Spur weniger technisch als die Source Sans 3 des Fliesstextes,
ohne dass eine zweite Datei geladen wird. Zahlenkolonnen und Fliesstext
bleiben unberuehrt - Poppins hat keine Tabellenziffern. */
--font-anzeige: Poppins, system-ui, -apple-system, "Segoe UI", sans-serif;
/* ... und dieselben Werte dort, wo Bootstrap sie sucht. */
--bs-body-bg: var(--ton-grund);
--bs-body-bg-rgb: 25, 29, 32;
--bs-body-color: var(--ton-schrift);
--bs-body-color-rgb: 232, 237, 233;
--bs-emphasis-color: #fff;
--bs-secondary-color: var(--ton-schrift-2);
--bs-secondary-bg: var(--ton-flaeche-2);
--bs-tertiary-color: var(--ton-schrift-3);
--bs-tertiary-bg: var(--ton-flaeche-2);
--bs-border-color: var(--ton-linie-satt);
--bs-border-radius: var(--ton-radius);
--bs-border-radius-sm: var(--ton-radius-klein);
--bs-link-color: var(--ton-sonne);
--bs-link-hover-color: #ffc94d;
--bs-heading-color: var(--ton-schrift);
}
/* Ueberschriften in der Anzeigeschrift. Bewusst nur Ueberschriften: der
Fliesstext und alle Zahlenkolonnen bleiben bei der Schrift des Themes,
weil Poppins keine Tabellenziffern mitbringt und Messwerte sonst
springen wuerden. */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6,
.card-title,
.modal-title {
font-family: var(--font-anzeige);
font-weight: 600;
letter-spacing: -.01em;
}
/* Der Rumpf traegt zwei Grundfarben: die Seite und die Leisten drumherum.
AdminLTE setzt sie ueber eigene Klassen, die hier auf denselben Ton
gebracht werden - sonst steht eine Karte auf drei verschiedenen Grautoenen. */
body.bg-body-tertiary,
.app-content,
.app-content-header { background: var(--ton-grund); }
.app-header.bg-body,
.app-sidebar.bg-body-secondary {
background: var(--ton-flaeche) !important;
border-color: var(--ton-linie) !important;
}
.app-header { box-shadow: 0 1px 0 var(--ton-linie); }
/* --- Karten --------------------------------------------------------------
Eine Karte ist eine Flaeche, kein Kasten: ein Ton heller als der Grund, eine
Haarlinie als Rand, kein Schlagschatten. Der Schatten trennte frueher, was
der Farbunterschied jetzt allein leistet. */
.card {
background: var(--ton-flaeche);
border: 1px solid var(--ton-linie);
border-radius: var(--ton-radius);
box-shadow: none;
}
.card-header {
background: transparent;
border-bottom: 1px solid var(--ton-linie);
padding-inline: 1rem;
}
.card-title {
font-size: 1rem;
font-weight: 500;
letter-spacing: -.01em;
}
.card-body { padding: 1rem; }
.card-footer {
background: transparent;
border-top: 1px solid var(--ton-linie);
}
/* Werkzeugknoepfe in der Kopfzeile: ruhig, bis man sie braucht. */
.btn-tool {
color: var(--ton-schrift-3);
transition: color .18s, background .18s;
border-radius: .35rem;
}
.btn-tool:hover,
.btn-tool:focus-visible { color: var(--ton-schrift); background: rgba(138, 148, 158, .12); }
/* --- Menue ---------------------------------------------------------------
Der aktive Punkt traegt die Akzentfarbe, nicht das Blau des Themes -
sonst haette die Seite zwei Hauptfarben. */
.sidebar-menu .nav-link {
border-radius: .45rem;
color: var(--ton-schrift-2);
}
.sidebar-menu .nav-link:hover { background: rgba(138, 148, 158, .1); color: var(--ton-schrift); }
.sidebar-menu .nav-link.active,
.sidebar-menu .nav-link.active:hover {
background: rgba(var(--ton-sonne-rgb), .16);
color: var(--ton-sonne);
}
.sidebar-brand { border-bottom: 1px solid var(--ton-linie); }
.brand-text { font-weight: 400; letter-spacing: .01em; }
/* --- Schrift -------------------------------------------------------------
Zahlen stehen ueberall in Spalten untereinander - mit Ziffern gleicher
Breite bleiben sie ausgerichtet, auch wenn sich der Wert aendert. */
.card-body,
.table,
.info-box-number,
.progress-bar { font-variant-numeric: tabular-nums; }
.app-content-header h3 {
font-weight: 500;
letter-spacing: -.01em;
}
/* --- Tabellen ------------------------------------------------------------ */
.table {
--bs-table-bg: transparent;
--bs-table-color: var(--ton-schrift);
--bs-table-border-color: var(--ton-linie);
--bs-table-hover-bg: rgba(138, 148, 158, .06);
--bs-table-striped-bg: rgba(138, 148, 158, .04);
}
.table > thead {
color: var(--ton-schrift-2);
font-size: .82rem;
font-weight: 500;
}
/* --- Abzeichen -----------------------------------------------------------
Gedaempft statt grell: ein Abzeichen soll den Zustand sagen, nicht die
Kachel dominieren. */
.badge { font-weight: 500; letter-spacing: .01em; }
.badge.text-bg-secondary {
background: rgba(138, 148, 158, .18) !important;
color: var(--ton-schrift-2) !important;
}
.badge.text-bg-success {
background: rgba(76, 175, 125, .18) !important;
color: var(--ton-gut) !important;
}
.badge.text-bg-warning {
background: rgba(240, 180, 41, .18) !important;
color: var(--ton-sonne) !important;
}
.badge.text-bg-danger {
background: rgba(224, 96, 96, .18) !important;
color: var(--ton-schlecht) !important;
}
.badge.text-bg-info {
background: rgba(122, 167, 199, .18) !important;
color: #9cc4de !important;
}
/* --- Formulare ----------------------------------------------------------- */
.form-control,
.form-select {
background: rgba(0, 0, 0, .18);
border-color: var(--ton-linie-satt);
color: var(--ton-schrift);
}
.form-control:focus,
.form-select:focus {
background: rgba(0, 0, 0, .18);
border-color: var(--ton-sonne);
box-shadow: 0 0 0 .2rem rgba(var(--ton-sonne-rgb), .2);
color: var(--ton-schrift);
}
.form-check-input:checked { background-color: var(--ton-sonne); border-color: var(--ton-sonne); }
.form-check-input:focus { box-shadow: 0 0 0 .2rem rgba(var(--ton-sonne-rgb), .2); }
/* Sichtbarer Tastaturfokus, einheitlich - AdminLTE setzt ihn stellenweise ab. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
outline: 2px solid var(--ton-sonne);
outline-offset: 2px;
}
/* --- Trennlinien und Modals ---------------------------------------------- */
hr { border-color: var(--ton-linie); opacity: 1; }
.modal-content {
background: var(--ton-flaeche);
border: 1px solid var(--ton-linie-satt);
border-radius: var(--ton-radius);
}
.modal-header,
.modal-footer { border-color: var(--ton-linie); }
/* ==========================================================================
Jahresstatistik (js/solar/jahresstatistik.js)
Die Tabelle traegt die Seite "Vergangenheit" und steht in kleiner Form auch
auf der Solarseite. Sie ist zum Lesen einer Zeile gebaut: Kennzahlen links,
Jahre nebeneinander, die Veraenderung als Zahl - nicht als blosser Pfeil.
========================================================================== */
/* Der Baustein setzt seine Bloecke selbst auf Abstand - und zwar auf denselben
wie den zwischen den Leitzahlen und den Innenabstand der Karte. Alle Nahtstellen
sind dadurch gleich weit, gleich ob die Werkzeugleiste dabei ist oder nicht. */
.jahresstatistik {
display: flex;
flex-direction: column;
gap: var(--stat-abstand, 1.25rem);
}
/* Der Innenabstand der Karte gehoert zum selben Mass - sonst sitzt die erste
Kachel enger unter der Ueberschrift als die zweite unter der ersten.
Der Vorsatz .app-content ist noetig, nicht schmueckend: weiter unten in
dieser Datei setzt .app-content .card-body auf dem Handy einen kleineren
Innenabstand, und bei gleicher Spezifitaet gewaenne die spaetere Regel. */
.app-content .card-body.statistik { padding: var(--stat-abstand, 1.25rem); }
/* Auf dem Handy ruecken alle Nahtstellen zusammen - aber gemeinsam, sonst
waere die Gleichheit wieder dahin. */
@media (max-width: 767.98px) {
.jahresstatistik,
.app-content .card-body.statistik { --stat-abstand: 1rem; }
}
/* --- Leitzahlen ---------------------------------------------------------- */
.jahresstatistik .leitzahlen {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
gap: var(--stat-abstand, 1.25rem);
}
.jahresstatistik .leitzahl {
background: rgba(138, 148, 158, .07);
border: 1px solid var(--ton-linie);
border-radius: var(--ton-radius);
padding: .9rem 1.05rem 1rem;
}
.jahresstatistik .leitzahl .name { font-size: .78rem; color: var(--ton-schrift-2); }
.jahresstatistik .leitzahl .zahl {
display: flex;
align-items: baseline;
gap: .35rem;
margin-top: .3rem;
font-size: 1.9rem;
font-weight: 500;
line-height: 1.1;
font-variant-numeric: tabular-nums;
}
.jahresstatistik .leitzahl .einheit {
font-size: .95rem;
font-weight: 400;
color: var(--ton-schrift-2);
}
.jahresstatistik .leitzahl .fuss {
margin-top: .45rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem;
font-size: .76rem;
color: var(--ton-schrift-3);
}
.jahresstatistik .leitzahl.betont { border-color: rgba(var(--ton-sonne-rgb), .45); }
.jahresstatistik .leitzahl.betont .zahl { color: var(--ton-sonne); }
/* Die Veraenderung: Richtung und Groesse, nicht nur Richtung. */
.jahresstatistik .delta {
display: inline-flex;
align-items: center;
gap: .25rem;
font-size: .78rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.jahresstatistik .delta.gut { color: var(--ton-gut); }
.jahresstatistik .delta.schlecht { color: var(--ton-schlecht); }
.jahresstatistik .delta.gleich { color: var(--ton-schrift-3); }
/* --- Werkzeugleiste ------------------------------------------------------ */
.jahresstatistik .werkzeuge {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .75rem 1.25rem;
}
.jahresstatistik .umschalter {
display: inline-flex;
border: 1px solid var(--ton-linie-satt);
border-radius: .45rem;
overflow: hidden;
}
.jahresstatistik .umschalter button {
appearance: none;
border: 0;
background: none;
color: var(--ton-schrift-2);
font: inherit;
font-size: .82rem;
padding: .4rem .9rem;
cursor: pointer;
transition: background .18s, color .18s;
}
.jahresstatistik .umschalter button + button { border-left: 1px solid var(--ton-linie-satt); }
.jahresstatistik .umschalter button:hover { background: rgba(138, 148, 158, .1); }
.jahresstatistik .umschalter button[aria-pressed="true"] {
background: rgba(var(--ton-sonne-rgb), .16);
color: var(--ton-sonne);
}
/* Das Jahrfenster: zwei Pfeile und dazwischen, was gerade zu sehen ist. */
.jahresstatistik .jahrwahl {
display: inline-flex;
align-items: center;
gap: .35rem;
font-size: .82rem;
color: var(--ton-schrift-2);
}
.jahresstatistik .jahrwahl button {
appearance: none;
border: 1px solid var(--ton-linie-satt);
background: none;
color: var(--ton-schrift-2);
border-radius: .35rem;
width: 1.9rem;
height: 1.9rem;
line-height: 1;
cursor: pointer;
transition: background .18s, color .18s, opacity .18s;
}
.jahresstatistik .jahrwahl button:hover:not(:disabled) {
background: rgba(138, 148, 158, .12);
color: var(--ton-schrift);
}
.jahresstatistik .jahrwahl button:disabled { opacity: .3; cursor: default; }
.jahresstatistik .jahrwahl .fenster { font-variant-numeric: tabular-nums; min-width: 5.5rem; text-align: center; }
.jahresstatistik .kasten {
display: inline-flex;
align-items: center;
gap: .45rem;
font-size: .82rem;
color: var(--ton-schrift-2);
cursor: pointer;
user-select: none;
}
.jahresstatistik .kasten input { accent-color: var(--ton-sonne); width: 1rem; height: 1rem; }
/* --- Die Tabelle --------------------------------------------------------- */
.jahresstatistik .tafel-scroll {
overflow-x: auto;
border: 1px solid var(--ton-linie);
border-radius: var(--ton-radius);
}
.jahresstatistik .tafel {
width: 100%;
border-collapse: collapse;
font-size: .88rem;
margin: 0;
}
.jahresstatistik .tafel thead th {
background: var(--ton-flaeche-2);
text-align: right;
font-weight: 500;
font-size: .78rem;
padding: .55rem .9rem .5rem;
border-bottom: 1px solid var(--ton-linie-satt);
white-space: nowrap;
}
.jahresstatistik .tafel thead th:first-child { text-align: left; }
.jahresstatistik .tafel thead th.jetzt { color: var(--ton-sonne); }
.jahresstatistik .tafel thead .zeitraum {
display: block;
font-weight: 400;
font-size: .68rem;
color: var(--ton-schrift-3);
letter-spacing: .03em;
}
.jahresstatistik .tafel tbody td {
padding: .4rem .9rem;
border-bottom: 1px solid var(--ton-linie);
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.jahresstatistik .tafel tbody td.name {
text-align: left;
width: 40%;
min-width: 14rem;
white-space: normal;
}
.jahresstatistik .tafel tbody tr:last-child td { border-bottom: 0; }
@media (hover: hover) {
.jahresstatistik .tafel tbody tr:hover td { background: rgba(138, 148, 158, .06); }
.jahresstatistik .tafel tbody tr.gruppe:hover td { background: none; }
}
/* Gruppenkopf: kein Kasten, nur eine Zaesur. */
.jahresstatistik .tafel tr.gruppe td {
padding: 1rem .9rem .25rem;
border-bottom: 1px solid var(--ton-linie-satt);
font-size: .7rem;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--ton-schrift-3);
text-align: left;
}
/* Die Summenzeile traegt das Gewicht, die Teile darunter nicht. */
.jahresstatistik .tafel tr.summe td { font-size: .95rem; }
.jahresstatistik .tafel tr.summe td.name { font-weight: 500; }
.jahresstatistik .tafel tr.summe .wert-jetzt { color: var(--ton-sonne); }
/* Einrueckung: "davon" teilt die Summe auf, "darin" greift etwas heraus, das
schon in einer Zeile darueber steckt. */
.jahresstatistik .tafel td.name .stufe-1 {
padding-left: 1.4rem;
position: relative;
display: inline-block;
}
.jahresstatistik .tafel td.name .stufe-1::before {
content: "";
position: absolute;
left: .45rem;
top: .75em;
width: .55rem;
border-top: 1px solid var(--ton-linie-satt);
}
.jahresstatistik .tafel tr.teilmenge td { color: var(--ton-schrift-2); font-size: .82rem; }
/* Zweite Stufe. Der Strich sitzt tiefer eingerueckt als der der ersten und
zeigt damit, an welcher Zeile die Teilmenge haengt. */
.jahresstatistik .tafel td.name .stufe-2 {
padding-left: 2.8rem;
position: relative;
display: inline-block;
}
.jahresstatistik .tafel td.name .stufe-2::before {
content: "";
position: absolute;
left: 1.85rem;
top: .75em;
width: .55rem;
border-top: 1px dashed var(--ton-linie-satt);
}
.jahresstatistik .tafel .darin {
font-size: .68rem;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--ton-schrift-3);
margin-right: .4rem;
}
.jahresstatistik .tafel .einheit-klein {
font-size: .7rem;
color: var(--ton-schrift-3);
margin-left: .4rem;
}
.jahresstatistik .tafel .wert-alt { color: var(--ton-schrift-2); }
.jahresstatistik .tafel .leer { color: var(--ton-schrift-3); text-align: left; }
/* Die Gesamtspalte ist eine andere Art Zahl als ein Jahreswert - sie steht
deshalb abgesetzt und ruhiger. */
.jahresstatistik .tafel .gesamt {
border-left: 1px solid var(--ton-linie-satt);
color: var(--ton-schrift-2);
}
.jahresstatistik .tafel thead th.gesamt { color: var(--ton-schrift-2); }
/* Aufklappbare Teilzeilen */
.jahresstatistik .tafel button.klapp {
appearance: none;
background: none;
border: 0;
color: inherit;
font: inherit;
padding: 0;
cursor: pointer;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: .2rem .4rem;
text-align: left;
}
.jahresstatistik .tafel button.klapp .pfeil {
display: inline-block;
width: .55rem;
color: var(--ton-schrift-3);
transition: transform .18s;
}
.jahresstatistik .tafel button.klapp[aria-expanded="true"] .pfeil { transform: rotate(90deg); }
/* Was hinter einer zugeklappten Zeile steckt - sonst waere das Zugeklappte
nicht nur zusammengefasst, sondern verschwunden. */
.jahresstatistik .tafel .posten {
margin-left: .55rem;
padding: .05rem .45rem;
border: 1px solid var(--ton-linie-satt);
border-radius: 1rem;
font-size: .68rem;
font-weight: 400;
letter-spacing: .03em;
color: var(--ton-schrift-3);
white-space: nowrap;
}
@media (hover: hover) {
.jahresstatistik .tafel button.klapp:hover .posten {
border-color: var(--ton-sonne);
color: var(--ton-sonne);
}
}
.jahresstatistik .tafel tr.versteckt { display: none; }
/* --- Monatsverlauf ------------------------------------------------------- */
.jahresstatistik .tafel th.verlauf,
.jahresstatistik .tafel td.verlauf { width: 7.5rem; }
.jahresstatistik .spark {
display: block;
/* Waagerecht faehrt der Finger die Monate ab, senkrecht scrollt die Seite
weiter - ohne diese Angabe verschluckt der Browser das Wischen. */
touch-action: pan-y;
}
.jahresstatistik .spark rect.balken { fill: var(--ton-linie-satt); transition: fill .15s; }
.jahresstatistik .spark rect.balken.jetzt { fill: var(--ton-sonne); }
.jahresstatistik .spark rect.balken.aktiv { fill: var(--ton-sonne); }
.jahresstatistik .spark line.basis { stroke: var(--ton-linie); stroke-width: 1; }
/* Die ganze Zelle nimmt die Beruehrung an, nicht nur die Balken darin -
ein SVG-Wurzelelement laesst sich nicht mit Innenabstand vergroessern. */
.jahresstatistik .tafel td.verlauf { touch-action: pan-y; cursor: crosshair; }
.spark-tipp {
position: fixed;
z-index: 1080;
pointer-events: none;
background: #12161a;
border: 1px solid var(--ton-linie-satt);
border-radius: .35rem;
padding: .3rem .55rem;
font-size: .75rem;
font-variant-numeric: tabular-nums;
box-shadow: 0 .4rem 1.2rem rgba(0, 0, 0, .5);
opacity: 0;
transition: opacity .12s;
}
.spark-tipp.an { opacity: 1; }
/* --- Fussnoten ----------------------------------------------------------- */
.jahresstatistik .fussnoten {
display: grid;
gap: .5rem;
font-size: .78rem;
color: var(--ton-schrift-2);
max-width: 68ch;
}
.jahresstatistik .fussnoten p { margin: 0; }
.jahresstatistik .fussnoten b { color: var(--ton-schrift); font-weight: 500; }
@media (max-width: 575.98px) {
.jahresstatistik .tafel tbody td.name { min-width: 11rem; }
.jahresstatistik .werkzeuge { gap: .6rem .9rem; }
}
@media (prefers-reduced-motion: reduce) {
.jahresstatistik .umschalter button,
.jahresstatistik .jahrwahl button,
.jahresstatistik .spark rect.balken,
.jahresstatistik .tafel button.klapp .pfeil,
.spark-tipp { transition: none; }
}
.winddir {
position:absolute;
left:60%;
width:25%;
height:40%;
padding: 0;
background-image: url('../assets/img/arrow.svg');
background-size:contain;
background-repeat: no-repeat;
background-position: center;
}
/* Schieberegler in den Modals (Autoladung, Heizstab, Thermostat).
Der gefuellte Anteil kommt aus --background-size, gesetzt von
bindModalSlider() in js/solar/common.js. */
input[type='range']::-webkit-slider-runnable-track {
background: linear-gradient(to right, #00788F, #00788F), #D7D7D7;
background-size: var(--background-size, 0%) 100%;
background-repeat: no-repeat;
}
/* Diagramme (Chart.js). Die Hoehe kommt aus diesem Container, nicht aus dem
canvas - die Charts laufen mit maintainAspectRatio: false. Auf dem Handy
ist der Container nur rund 300 px breit; mit 400 px Hoehe wird aus einer
Zeitreihe ein Hochformat, in dem die Zeitachse die kurze Seite ist. */
.chart-container {
position: relative;
width: 100%;
height: 400px;
}
@media (max-width: 767.98px) {
.chart-container {
height: 250px;
}
}
/* Auf dem Handy gehen von 375 px rund 75 an Innenabstaende verloren, und
zwischen den Karten stehen 24 px. Beides wird hier enger gefasst - nur
im Inhaltsbereich, damit Seitenleiste, Kopfzeile und Modals unberuehrt
bleiben. Die Abstandsklassen von Bootstrap (mb-4) tragen !important,
deshalb steht es auch hier. */
@media (max-width: 767.98px) {
.app-content {
padding: 0 .25rem;
}
.app-content-header {
padding: .5rem .25rem;
}
.app-content .row {
--bs-gutter-x: .5rem;
}
.app-content .card {
margin-bottom: .75rem !important;
}
/* Nur der Standardwert wird kleiner; eigene Angaben im Markup (p-0 fuer
das Realtime-SVG) gelten weiter. */
.app-content .card-body {
padding: .5rem;
}
/* Die Jahrestabelle scrollt auf dem Handy seitwaerts; damit moeglichst
viel davon ohne Schieben zu sehen ist, ruecken die Spalten enger
zusammen. */
.jahresstatistik .tafel tbody td,
.jahresstatistik .tafel thead th {
padding-inline: .55rem;
}
}
/* --------------------------------------------------------------------------
Drei Klassen, die der Automatik-Editor gebraucht hat - hier, nicht dort
Sie stehen absichtlich in der gemeinsamen Datei und nicht als Sonderfall
im Editor. Zwei davon sind allgemein genug, um anderswo zu taugen; die
dritte ist eine echte Ausnahme und als einzelne Klasse auch als solche
erkennbar.
-------------------------------------------------------------------------- */
/* Ein Auswahlfeld, das wie Text aussieht, bis man es anfaehrt.
Eine Bedingung hat sechs Bestandteile, aber nur zwei davon traegt man
wirklich ein: Vergleich und Wert. Standen alle sechs als gleich schwere
Kaesten nebeneinander, sagte nichts mehr, worauf es ankommt. Die
gepunktete Linie sagt "anklickbar", ohne den Platz und das Gewicht eines
Eingabefelds zu nehmen.
Bewusst allgemein: dieselbe Not haben das Raum-Modal und die
Kachel-Einstellungen, wo ebenfalls jede Kleinigkeit in einem Kasten
steht. Taugt fuer <select> wie fuer <button>. */
.feld-als-text {
appearance: none;
-webkit-appearance: none;
max-width: 100%;
padding: 0 0 1px;
border: 0;
border-bottom: 1px dashed var(--ton-linie-satt);
border-radius: 0;
background: none;
color: inherit;
font: inherit;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
}
.feld-als-text:hover,
.feld-als-text:focus-visible {
border-bottom-color: var(--bs-link-color);
color: var(--bs-emphasis-color);
outline: 0;
}
/* Ein Messwert, wie er in diesem Moment dasteht.
Steht neben einer Bedingung und beantwortet die Frage, die man wirklich
hat: "warum laeuft die nicht?". Das ist die Ausnahme unter den dreien -
Bedingungen gibt es sonst nirgends auf der Seite. */
.wert-jetzt {
font-size: .8125rem;
color: var(--ton-schrift-3);
font-variant-numeric: tabular-nums;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.wert-jetzt b { font-weight: 400; color: var(--ton-schrift-2); }
.wert-jetzt.erfuellt b { color: var(--ton-gut); }
/* Erfuellt oder noch nicht. Bewusst kein Rot: eine Bedingung, die gerade
nicht zutrifft, ist der Normalfall und kein Fehler. */
.punkt {
display: inline-block;
width: .7rem;
height: .7rem;
border: 1.5px solid var(--ton-schrift-3);
border-radius: 50%;
}
.punkt.an {
border-color: var(--ton-gut);
background: var(--ton-gut);
}
/* Atmet leise, wo etwas mitlaeuft - ein Wort "live" daneben braucht es dann
nicht. */
.punkt.atmet { animation: punkt-atmen 2.4s ease-in-out infinite; }
@keyframes punkt-atmen {
0%, 100% { opacity: 1; }
50% { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) {
.punkt.atmet { animation: none; }
}
/* --------------------------------------------------------------------------
Editor der Automatiken: die Bedingungszeile
Frueher eine input-group aus sechs gleich schweren Kaesten. Jetzt ein
Raster, in dem Geraet und Messwert uebereinander stehen (das Geraet
beantwortet "woher", nicht "was") und nur Vergleich und Wert noch wie
Eingabefelder aussehen.
Auf schmalen Schirmen bricht die Zeile in zwei: oben, worum es geht,
unten der Vergleich. Frueher schrumpften die Felder stattdessen mit, bis
von "Thermostat OG Bad" ein "Therm" uebrig war.
-------------------------------------------------------------------------- */
.auto-zeile {
display: grid;
grid-template-columns: 1.75rem minmax(0, 1fr) 5.25rem 8rem 8.5rem .75rem 1.75rem;
grid-template-areas: "sym was op wert jetzt punkt weg";
gap: .625rem;
align-items: center;
padding: .45rem .55rem;
border-radius: var(--ton-radius-klein);
}
.auto-zeile:nth-of-type(even) { background: rgba(255, 255, 255, .022); }
.auto-zeile > .auto-symbol { grid-area: sym; color: var(--ton-schrift-2); }
.auto-zeile > .auto-was { grid-area: was; min-width: 0; }
.auto-zeile > .auto-op { grid-area: op; }
.auto-zeile > .auto-wert { grid-area: wert; }
.auto-zeile > .wert-jetzt { grid-area: jetzt; }
.auto-zeile > .punkt { grid-area: punkt; }
.auto-zeile > .auto-weg { grid-area: weg; }
.auto-zeile > .auto-werte { grid-area: werte; }
/* Die Aktionszeile: dasselbe Raster, andere Spalten. Statt Vergleich, Wert
und laufendem Messwert steht dort ein Block mit den Parametern des
Kommandos - mal keiner ("Auf"), mal zwei ("Position+Neigung"). */
.auto-zeile.tat {
grid-template-columns: 1.75rem minmax(0, 1fr) auto 1.75rem;
grid-template-areas: "sym was werte weg";
}
/* Eine Zeile ohne Geraet: dann gibt es nur eines zu tun, und das darf die
ganze Breite haben. Vergleich, Wert und der laufende Messwert gibt es in
diesem Zustand noch gar nicht - ihre Spalten stuenden sonst nur leer
herum, waehrend die eine Schaltflaeche in eine schmale Spalte gequetscht
waere. */
.auto-zeile.leer { grid-template-areas: "sym was was was was punkt weg"; }
/* Geraet klein und matt ueber dem Messwert - und trotzdem anklickbar. */
.auto-was { display: flex; flex-direction: column; gap: .05rem; }
/* Nur in der Bedingungszeile: die Aktionszeile benutzt .auto-geraet
weiterhin als vollwertige Auswahlflaeche und darf davon nichts
abbekommen. */
/* Nicht in der leeren Zeile: dort traegt derselbe Knopf zusaetzlich
.feld-als-text und ist die einzige Handlung, die es gibt - klein und matt
waere er dort genau die Falle, die er vorher war. Die Klasse .auto-geraet
behaelt er trotzdem, weil daran haengt, WAS er ist: der Klick-Horcher
erkennt ihn daran. Verhalten und Aussehen sind absichtlich zwei
verschiedene Klassen. */
.auto-was > .auto-geraet:not(.feld-als-text) {
align-self: start;
max-width: 100%;
padding: 0;
border: 0;
background: none;
color: var(--ton-schrift-3);
font-size: .72rem;
line-height: 1.3;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
}
.auto-was > .auto-geraet:not(.feld-als-text):hover,
.auto-was > .auto-geraet:not(.feld-als-text):focus-visible {
color: var(--bs-link-color);
outline: 0;
}
.auto-messwert { align-self: start; }
/* Der Papierkorb erscheint erst beim Ueberfahren. Acht rote Knoepfe im
Ruhezustand zogen alle Aufmerksamkeit auf die seltenste Handlung.
Wo es kein Ueberfahren gibt, steht er dauerhaft da. */
.auto-zeile > .auto-weg {
padding: 0;
border: 0;
background: none;
color: var(--ton-schrift-3);
opacity: 0;
transition: opacity .15s ease, color .15s ease;
}
.auto-zeile:hover > .auto-weg,
.auto-zeile:focus-within > .auto-weg { opacity: 1; }
.auto-zeile > .auto-weg:hover { color: var(--ton-schlecht); }
@media (hover: none) {
.auto-zeile > .auto-weg { opacity: 1; }
}
/* Ein Segment je Bedingung, im Kopf des Bandes und ueber jeder Gruppe.
Eine Zahl allein ("2 von 4") liest man, die Reihe sieht man. */
.auto-segment {
width: .8rem;
height: .3rem;
border-radius: .15rem;
background: var(--ton-linie-satt);
}
.auto-segment.an { background: var(--ton-gut); }
/* Was der Browser nicht entscheiden kann, bleibt hohl - siehe ZEITARTEN in
autoActionFuncs.js. */
.auto-segment.offen {
background: none;
box-shadow: inset 0 0 0 1px var(--ton-linie-satt);
}
@media (max-width: 767.98px) {
.auto-zeile {
grid-template-columns: 1.75rem 5.25rem minmax(0, 1fr) .75rem 1.75rem;
/* Drei Zeilen statt zwei: der laufende Wert quetschte sich sonst mit
dem Wertfeld in dieselbe Reihe und blieb als "gerad…" stehen. Auf
einem Handy ist Hoehe billig und Breite teuer. */
grid-template-areas:
"sym was was punkt weg"
"sym op wert wert wert"
"sym jetzt jetzt jetzt jetzt";
row-gap: .35rem;
}
.auto-zeile.leer { grid-template-areas: "sym was was punkt weg"; }
.auto-zeile.tat {
grid-template-columns: 1.75rem minmax(0, 1fr) 1.75rem;
grid-template-areas:
"sym was weg"
"sym werte werte";
}
.auto-zeile.tat.leer { grid-template-areas: "sym was weg"; }
}
/* Raum-Modal. Ein Kommando mit mehreren Parametern ("Position+Neigung")
traegt Beschriftung, zwei Zahlenfelder und den Ausloeseknopf in einer
input-group - auf einem Handy schrumpfen die Felder dabei auf Nullbreite.
Eine Mindestbreite laesst die Zeile stattdessen umbrechen. */
.input-group.raum-cmd > .form-control,
.input-group.raum-cmd > .form-select {
min-width: 5rem;
}
/* --------------------------------------------------------------------------
Raum-Modal: Bedienelemente
Erzeugt von restricted/roomControls.php, verdrahtet in js/solar/homeMQTT.js.
-------------------------------------------------------------------------- */
/* Die Geraetegruppen als Reiter. Auf einem Handy passen fuenf
Beschriftungen nicht nebeneinander - dann scrollt die Leiste seitwaerts,
statt die Namen umzubrechen. Die durchgehende Linie unter den Reitern
darf dabei nicht mitscrollen, sie gehoert an den unteren Rand. */
/* Waehrend ein Kommando unterwegs ist. Ein "Zu" an einer Jalousie dauert
ueber eine Minute; ohne sichtbare Sperre tippt man in der Zeit nach. */
.raum-geraet.raum-warte {
opacity: 0.55;
pointer-events: none;
}
.raum-geraet.raum-warte::after {
content: "";
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 0.9rem;
height: 0.9rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: raum-dreht 0.8s linear infinite;
}
.raum-geraet {
position: relative;
}
@keyframes raum-dreht {
to { transform: rotate(360deg); }
}
/* --------------------------------------------------------------------------
Regler im Raum-Modal
Eigenes Aussehen statt des Bootstrap-Standards: dessen weisse Bahn mit dem
blauen Griff sticht auf den dunklen Karten heraus, und in einem Raum mit
drei Jalousien sind das sechs helle Balken. Bahn und Griff tragen deshalb
dasselbe Blaugruen wie der Temperaturregler, und der ungefuellte Teil ist
ein Grau, das in beiden Themes traegt.
Bewusst nur innerhalb von #roomView: die Regler in den Modals fuer
Autoladung und Heizstab bleiben, wie sie sind.
-------------------------------------------------------------------------- */
#roomView input[type='range'] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 1.5rem;
padding: 0;
margin: 0;
background: transparent;
}
#roomView input[type='range']:focus {
outline: 0;
}
/* Getrennt notiert, nicht als Liste: einen Selektor, den er nicht kennt,
wirft der Browser samt der ganzen Regel weg - und dann bliebe die Bahn
ungestaltet. */
#roomView input[type='range']::-webkit-slider-runnable-track {
height: 0.375rem;
border: 0;
border-radius: 0.375rem;
background: linear-gradient(to right, #00788F, #00788F), rgba(128, 128, 128, 0.32);
background-size: var(--background-size, 0%) 100%;
background-repeat: no-repeat;
}
#roomView input[type='range']::-moz-range-track {
height: 0.375rem;
border: 0;
border-radius: 0.375rem;
background: linear-gradient(to right, #00788F, #00788F), rgba(128, 128, 128, 0.32);
background-size: var(--background-size, 0%) 100%;
background-repeat: no-repeat;
}
#roomView input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 1.05rem;
height: 1.05rem;
/* (Bahnhoehe - Griffhoehe) / 2, damit der Griff auf der Bahn sitzt. */
margin-top: -0.34rem;
border: 2px solid #fff;
border-radius: 50%;
background: #00788F;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}
#roomView input[type='range']::-moz-range-thumb {
width: 1.05rem;
height: 1.05rem;
border: 2px solid #fff;
border-radius: 50%;
background: #00788F;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}
/* Schieberegler stehen dicht untereinander (Helligkeit, Farbe). Ohne den
Abstand kleben Beschriftung und Bahn aneinander.
Die senkrechten Spalten stehen dagegen nebeneinander - dort wuerde der
Abstand die zweite Spalte um genau diese 0,35 rem tiefer setzen. */
.raum-regler + .raum-regler:not(.raum-spalte) {
margin-top: 0.35rem;
}
/* Messwerte als Gitter statt als Textwurst: ein Stromzaehler hat sechzehn
Werte, die in einer Zeile niemand findet. auto-fill statt fester Spalten,
damit auf dem Handy zwei und auf dem Schirm vier nebeneinander passen. */
.raum-werte {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
gap: 0.4rem;
}
.raum-wert {
background: rgba(128, 128, 128, 0.08);
border-radius: 0.35rem;
padding: 0.3rem 0.45rem;
overflow: hidden;
}
.raum-wert-zahl {
font-size: 0.95rem;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.raum-wert-einheit {
font-size: 0.7rem;
font-weight: 400;
opacity: 0.6;
}
.raum-wert-name {
font-size: 0.68rem;
opacity: 0.6;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* --------------------------------------------------------------------------
Raum-Modal: Jalousien
-------------------------------------------------------------------------- */
/* Fahrknoepfe, Hoehe und Neigung nebeneinander. Die Neigung nimmt den Rest -
sie ist die einzige der drei Spalten, die von Breite profitiert. */
.raum-jalousie {
display: flex;
align-items: stretch;
justify-content: space-between;
gap: 0.4rem;
}
/* Das Fensterbild steht zwischen Knoepfen und Reglern - schmal, mit Luft
zu beiden Seiten. Der uebrige Platz verteilt sich auf die Zwischenraeume
(space-between an .raum-jalousie), statt das Bild breitzuziehen. */
.raum-jalousie .raum-lamellen {
flex: 0 1 5rem;
min-width: 2.2rem;
margin: 0 0.4rem;
}
.raum-jalousie .dropdown {
align-self: flex-start;
}
/* Auf, Stop, Zu untereinander - die Knoepfe zeigen in die Richtung, in die
sie fahren. Breit genug fuer einen Daumen: sie sind das, was man im
Vorbeigehen antippt, die Regler daneben braucht man seltener. */
.raum-fahrt {
flex: 0 0 3.1rem;
}
.raum-fahrt .btn {
padding: 0.3rem 0;
line-height: 1.5;
}
/* Eine Spalte mit senkrechtem Regler: Name, oberes Wort, Bahn, unteres
Wort, Zahl. Breit genug fuer "Position" in der kleinen Schrift. */
.raum-spalte {
flex: 0 0 3.4rem;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.raum-name {
font-size: 0.62rem;
opacity: 0.8;
white-space: nowrap;
line-height: 1.2;
}
/* Der Kasten, in dem ein gewoehnlicher Regler auf der Seite liegt.
Gedreht statt writing-mode: so gebaute Regler lassen sich am Handy kaum
anfassen - das Ziehen wird als Wischen gelesen und blaettert die Seite.
Gedreht bewegt sich der Regler in seinen eigenen Koordinaten waagerecht,
und touch-action haelt die Seite still, solange der Finger auf ihm liegt.
rotate(90deg) legt den kleinen Wert nach oben: "oben" ist 0 %. */
.raum-senkrecht {
position: relative;
width: 1.6rem;
height: 6.5rem;
margin: 0.1rem 0;
}
#roomView .raum-senkrecht input[type='range'] {
position: absolute;
top: 50%;
left: 50%;
width: 6.5rem; /* = Hoehe des Kastens, die Laenge der Bahn */
height: 1.6rem; /* = Breite des Kastens, die Flaeche zum Treffen */
transform: translate(-50%, -50%) rotate(90deg);
touch-action: none;
}
/* Die Endstellungen in Worten statt in Prozent. */
.raum-ende {
font-size: 0.68rem;
opacity: 0.65;
line-height: 1.1;
}
/* Die Lamellen zur Neigung. Ihre Hoehe setzt lamellenZeichnen() in
js/solar/homeMQTT.js - schmal mit Luft dazwischen heisst offen,
aneinanderliegend heisst geschlossen. */
.raum-lamellen {
display: block;
align-self: center;
height: 6.9rem;
/* Der Rahmen macht aus dem Streifen ein Fenster - ohne ihn liest sich das
geschlossene Bild wie eine zweite Reglerbahn. */
background: rgba(0, 120, 143, 0.14);
border: 1px solid rgba(128, 128, 128, 0.35);
border-radius: 0.2rem;
}
.raum-lamelle {
fill: currentColor;
opacity: 0.6;
stroke: rgba(0, 0, 0, 0.35);
stroke-width: 0.6;
vector-effect: non-scaling-stroke;
}
.raum-endschiene {
fill: currentColor;
opacity: 0.85;
}
/* --- Einstellungen: Preistabellen ------------------------------------ */
/* Der Spaltenkopf steht ueber der ersten Zeile und muss deren Einrueckung
treffen, sonst rutschen Beschriftung und Feld auseinander. Er bleibt
einzeilig, braucht also keinen senkrechten Gutter - und ohne den entfaellt
auch Bootstraps negative Marge von selbst. */
.preis-kopf {
--bs-gutter-y: 0;
padding: 0 0.25rem 0.35rem;
}
/* Eine Zeile ist selbst eine .row mit g-2. Bootstrap zieht so eine Reihe um
die Gutter-Hoehe nach oben und gibt jeder Spalte dieselbe Marge nach unten
zurueck - gedacht fuer Reihen, die nebeneinander im Raster stehen. Hier
stehen sie untereinander, und der Zug nach oben frisst den unteren
Innenabstand der vorigen Zeile auf: auf dem Handy, wo der Papierkorb in
einer eigenen Zeile landet, klebte er am Trennstrich.
Die senkrechte Gutter-Marge wird hier gebraucht - sie haelt die umbrochenen
Feldzeilen auseinander -, der Zug nach oben nicht. Die Regel braucht eine
Klasse mehr als Bootstraps .row: solar.css wird vor AdminLTE geladen und
verliert bei gleicher Spezifitaet. */
.preis-zeilen .preis-zeile {
margin-top: 0;
}
/* Oben um die Gutter-Hoehe weniger Innenabstand, damit ueber der ersten und
unter der letzten Feldzeile gleich viel Luft steht. */
.preis-zeile {
padding: 0.15rem 0.25rem 0.65rem;
border-top: 1px solid rgba(128, 128, 128, 0.25);
}
/* Die Einheit ist Beiwerk und darf der Zahl nicht den Platz nehmen: hinter
"kWh/100 km" bliebe in voller Schrift kaum ein Drittel der Spalte fuer das
Eingabefeld uebrig. */
.preis-zeile .input-group-text {
font-size: 0.75rem;
padding-left: 0.4rem;
padding-right: 0.4rem;
}
/* Der Zeitraum, der heute gilt - der einzige, den man beim Nachschlagen
wirklich sucht. */
.preis-heute {
background: rgba(0, 120, 143, 0.12);
border-radius: 0.4rem;
}
/* ==========================================================================
Skoda-Seite (restricted/skoda.php)
Farben kommen aus den Bootstrap-Variablen des Themes, damit die Seite dem
Hell-/Dunkelmodus folgt, ohne eine zweite Palette zu pflegen. Nur die
Bedeutungsfarben stehen fuer sich: bernstein fuer "offen", gelb fuer
eingeschaltetes Licht, gruen fuer Laden, rot fuer einen leeren Akku.
Sie haengen alle an .skoda-seite (dem <main> der Seite) und werden von dort
vererbt - Fahrzeugbild, Kennzahlen, Balken und Karte holen sie sich also
aus einer einzigen Quelle. Wo eine Farbe mit Deckkraft gebraucht wird,
steht sie zusaetzlich als RGB-Tripel bereit, denn rgba() kann eine
Hex-Variable nicht aufloesen.
========================================================================== */
.skoda-seite {
--skoda-offen: #e0904f; /* offene Tuer, Klappe, Fenster */
--skoda-licht: #f2d16b; /* eingeschaltetes Licht */
--skoda-laden: #4caf7d; /* Laden, gesunder Ladestand */
--skoda-laden-rgb: 76, 175, 125;
--skoda-niedrig: #e06060; /* Ladestand unter 15 Prozent */
--skoda-ruhe: #7aa7c7; /* Ladestand ohne Ladung */
--skoda-ruhe-rgb: 122, 167, 199;
--skoda-pv: #f0b429; /* Strom aus der eigenen Anlage */
--skoda-netz: #7a8791; /* Strom aus dem Netz */
--skoda-blech: 128, 138, 148; /* alles Unbeteiligte am Fahrzeug */
}
/* --- Reiter --------------------------------------------------------------
Eine Bauart fuer die ganze Seite: Einstellungen, Raum-Modal, Automatiken.
Gebaut werden sie von restricted/reiter.php, umgeschaltet von Bootstrap.
Statt der Ordnerlaschen von Bootstrap eine Unterstreichung: die Laschen
brauchen einen Rahmen um die Flaeche darunter, damit sie nicht in der Luft
haengen - und der Rahmen ist genau das, was diese Oberflaeche sonst
nirgends hat. Der aktive Reiter traegt die Akzentfarbe.
Jede Regel nennt .nav-tabs UND .reiter, obwohl .reiter allein die Leiste
schon trifft. Das ist noetig, nicht schmueckend: adminlte.min.css wird nach
dieser Datei geladen und bringt eigene Regeln fuer .nav-tabs .nav-link mit.
Bei gleicher Spezifitaet gewaenne die spaetere - der aktive Reiter waere
weiss statt bernstein. */
.nav-tabs.reiter {
border-bottom: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
gap: .1rem;
}
.nav-tabs.reiter .nav-link {
border: 0;
border-bottom: 2px solid transparent;
border-radius: .35rem .35rem 0 0;
padding: .45rem .9rem;
color: var(--ton-schrift-2, #9aa6ad);
background: transparent;
transition: color .18s, background .18s, border-color .18s;
}
@media (hover: hover) {
.nav-tabs.reiter .nav-link:hover {
background: rgba(138, 148, 158, .08);
color: var(--ton-schrift, #e8ede9);
border-bottom-color: var(--ton-linie-satt, rgba(138, 148, 158, .38));
}
}
.nav-tabs.reiter .nav-link.active {
background: transparent;
color: var(--ton-sonne, #f0b429);
border-bottom-color: var(--ton-sonne, #f0b429);
}
.nav-tabs.reiter .nav-link:focus-visible {
outline: 2px solid var(--ton-sonne, #f0b429);
outline-offset: -2px;
}
.nav-tabs.reiter .nav-link i { margin-right: .5rem; }
/* Schmale Fassung fuer enge Stellen (Raum-Modal): kleinere Reiter, die
waagerecht scrollen statt umzubrechen - eine Zeile bleibt eine Zeile. */
.nav-tabs.reiter-schmal {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
.nav-tabs.reiter-schmal::-webkit-scrollbar { display: none; }
.nav-tabs.reiter-schmal .nav-link {
padding: .3rem .7rem;
font-size: .8rem;
white-space: nowrap;
}
.nav-tabs.reiter-schmal .nav-link i { margin-right: .3rem; }
@media (prefers-reduced-motion: reduce) {
.nav-tabs.reiter .nav-link { transition: none; }
}
/* --- Aufbau der Fahrzeugkachel -------------------------------------------
Links das Bild, rechts die Zahlen dazu. Die Kachel bricht um, sobald die
Zahlenspalte unter 24rem faellt; das Bild behaelt dann seine Breite und
rueckt in die Mitte. Die drei Bloecke rechts verteilen sich ueber die Hoehe
des Bildes, damit die Kachel unten nicht ausfranst. */
.skoda-fahrzeug {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: clamp(1rem, 3vw, 2.5rem);
align-items: stretch;
}
.skoda-bild {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
/* Die Zahlenspalte laeuft nicht ueber die ganze Breite mit: ein Balken von
einem Meter Laenge teilt nicht besser als einer von dreissig Zentimetern.
Auf sehr breiten Schirmen steht die Kachel deshalb mittig statt gedehnt. */
/* Mittig statt oben buendig: ohne Ladung faellt der Ladestrombalken weg, und
die uebrigen Bloecke sollen sich dann gleichmaessig neben dem Fahrzeug
verteilen statt eine Luecke in der Mitte zu lassen. */
.skoda-daten {
flex: 1 1 24rem;
max-width: 48rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.5rem;
}
/* Kleine Beschriftung ueber einem Block oder einer Zahl. Einmal beschrieben,
von Kennzahlen, Ladestrom und Zustandszeile gemeinsam benutzt. */
.skoda-marke,
.kennzahl .k,
.skoda-fahrleistung .posten .k {
font-size: .7rem;
letter-spacing: .09em;
text-transform: uppercase;
color: var(--bs-secondary-color, #9aa0a6);
}
/* --- Fahrzeug in Draufsicht ----------------------------------------------
Massstaeblich: 4648 x 1877 mm ergeben das Seitenverhaeltnis 2,48:1, der
Radstand von 2765 mm die Lage der Raeder. */
.skoda-auto {
width: 100%;
max-width: 297px; /* 294,4 Einheiten viewBox, eine Einheit ein Bildpunkt */
height: auto;
}
.skoda-auto .karosserie {
fill: rgba(var(--skoda-blech), 0.17);
stroke: rgba(var(--skoda-blech), 0.55);
stroke-width: 2;
}
/* --- Strasse ------------------------------------------------------------
Sichtbar nur waehrend der Fahrt, und sie traegt die eigentliche Bewegung.
Die Masse stehen im SVG: Leitlinie 6 m Strich auf 12 m Luecke, rechts
durchgezogen, das Ganze im Viertel des Fahrzeugmassstabs. Hier bleibt die
Bewegung: eine Umdrehung ist eine Strich-Lueckenlaenge (472,5 Einheiten),
in 1,08 s - das sind 437 Einheiten je Sekunde und in diesem Massstab gut
60 km/h. Der Takt der vorbeiziehenden Striche bleibt damit derselbe wie
zuvor, die Bewegung selbst ist ruhiger.
Nur die Leitlinie laeuft. Die durchgezogene Linie hat nichts, woran man
Bewegung saehe - genau wie in echt. */
.skoda-auto .strasse { display: none; }
.skoda-auto.faehrt .strasse { display: block; }
.skoda-auto .strasse .belag {
fill: rgba(8, 10, 12, 0.35);
}
.skoda-auto .strasse line {
stroke: rgba(232, 237, 233, 0.5);
stroke-width: 4.2; /* etwas breiter als massstaeblich (3,15), damit
sich die Markierung vom Fahrtwind abhebt */
}
.skoda-auto .strasse .leitlinie {
stroke-dasharray: 157.5 315; /* 6 m Strich, 12 m Luecke, geviertelt */
animation: skoda-leitlinie 1.08s linear infinite;
}
@keyframes skoda-leitlinie {
from { transform: translateY(0); }
to { transform: translateY(472.5px); }
}
/* --- Bananenschale ------------------------------------------------------
Der Ostergruss. Ausgeloest wird er selten und nur waehrend der Fahrt (siehe
bananeVielleicht() in js/solar/skodaMQTT.js); hier steht, wie er aussieht.
Zwei Bewegungen greifen ineinander: die Schale laeuft mit der Strasse mit,
und wenn sie unter der Vorderachse ist, kommt der Wagen ins Schleudern. Der
Vorlauf der zweiten Bewegung ist deshalb kein Gefuehl, sondern gerechnet:
die Schale startet 40 Einheiten ueber dem Bild, die Vorderachse liegt bei
97, macht 137 Einheiten - bei 437 Einheiten je Sekunde sind das 0,31 s.
Der Wagen dreht sich um seinen Schwerpunkt, nicht um die Bildmitte; ohne
transform-box: view-box bezoege sich transform-origin auf den Rahmen der
Gruppe und der Bogen saehe falsch aus. */
.skoda-auto .bananenschale { display: none; }
.skoda-auto.banane .bananenschale {
display: block;
animation: skoda-schale 1.5s linear;
}
.skoda-auto .bananenschale use {
fill: #e8c74a;
stroke: rgba(120, 92, 12, 0.75);
stroke-width: 0.8;
}
.skoda-auto .bananenschale .strunk { fill: #b8912c; }
.skoda-auto.banane #wagen {
transform-box: view-box;
transform-origin: 112px 250px;
animation: skoda-schleudern 1.2s 0.31s ease-in-out;
}
@keyframes skoda-schale {
from { transform: translateY(-40px); }
to { transform: translateY(560px); }
}
/* Erst ein Ausbrechen nach rechts, dann das Gegenlenken, dann austrudeln -
in dieser Reihenfolge faengt sich ein Fahrzeug wieder. */
@keyframes skoda-schleudern {
0% { transform: rotate(0deg) translateX(0); }
18% { transform: rotate(4.5deg) translateX(5px); }
45% { transform: rotate(-3.5deg) translateX(-4px); }
70% { transform: rotate(1.8deg) translateX(2px); }
88% { transform: rotate(-0.7deg) translateX(-1px); }
100% { transform: rotate(0deg) translateX(0); }
}
/* --- Fahrtwind ----------------------------------------------------------
Vier Streifen neben dem Wagen, die von vorn nach hinten laufen. Sichtbar
nur, solange zeichneFahrt() dem SVG die Klasse "faehrt" gibt.
Die Laenge der Streifen steckt im Markup (y1/y2), die Bewegung hier: jede
Spur laeuft in eigenem Takt und mit eigenem Vorlauf ueber das Bild, sonst
marschieren die vier im Gleichschritt und es sieht nach Zebrastreifen aus
statt nach Fahrt. Der Weg reicht von weit oberhalb bis weit unterhalb der
viewBox (-8 bis 508), damit kein Streifen im Bild entsteht oder endet. */
.skoda-auto .fahrtwind { display: none; }
.skoda-auto.faehrt .fahrtwind { display: block; }
/* Die Streifen ueber dem Wagen liegen ueber den beiden Knoepfen; sie duerfen
deren Beruehrungen nicht abfangen. */
.skoda-auto .fahrtwind-oben { pointer-events: none; }
.skoda-auto .fahrtwind line {
stroke: rgba(var(--skoda-blech), 0.75);
stroke-width: 2.5;
stroke-linecap: round;
animation: skoda-fahrtwind 1.1s linear infinite;
}
/* Vier Spuren, jede mit eigenem Takt und Vorlauf. Sie laufen so schnell wie
die Strasse darunter - ein Streifen, der die Fahrbahnmarkierung ueberholt,
faellt sofort auf. Mit der Strasse halbiert sich also auch ihr Tempo. */
.skoda-auto .fahrtwind .b1 { animation-duration: 1.56s; animation-delay: -0.60s; opacity: .5; }
.skoda-auto .fahrtwind .b6 { animation-duration: 1.72s; animation-delay: -1.10s; opacity: .5; }
/* Ueber dem Fahrzeug: heller, duenner, durchsichtiger. Der Wind zieht ueber
das Blech hinweg - er soll sich darauf legen, nicht hineinschneiden. Die
Streifen stehen dafuer in einer eigenen Gruppe am Ende des SVG (siehe
dort); ihre geringe Deckung haelt Ladestand und Umrisse lesbar. */
.skoda-auto .fahrtwind .ueber {
stroke: rgba(232, 237, 233, 0.85);
stroke-width: 2;
}
.skoda-auto .fahrtwind .b7 { animation-duration: 1.48s; animation-delay: -0.40s; opacity: .34; }
.skoda-auto .fahrtwind .b10 { animation-duration: 1.64s; animation-delay: -0.90s; opacity: .34; }
@keyframes skoda-fahrtwind {
from { transform: translateY(0); }
to { transform: translateY(680px); }
}
/* Wer Bewegung abbestellt hat, bekommt sie nicht - aber auch keine leere
Stelle: die Streifen stehen dann ruhig neben dem Wagen, und das Abzeichen
"unterwegs" im Kopf der Kachel sagt es ohnehin in Worten. */
@media (prefers-reduced-motion: reduce) {
.skoda-auto .fahrtwind line {
animation: none;
transform: translateY(250px);
opacity: .45;
}
/* Auch die Leitlinie bleibt stehen - zu sehen ist dann eine Standaufnahme
statt einer Fahrt, und das Abzeichen "unterwegs" sagt den Rest. */
.skoda-auto .strasse .leitlinie {
animation: none;
}
/* Der Ostergruss faellt ganz aus: ein schleuderndes Fahrzeug ist genau die
Art Bewegung, die abbestellt wurde. Ausgeloest wird er hier gar nicht
erst - das prueft das Skript zusaetzlich. */
.skoda-auto.banane .bananenschale,
.skoda-auto.banane #wagen {
animation: none;
}
.skoda-auto.banane .bananenschale { display: none; }
}
/* Die Reifen liegen als unterste Lage unter der ganzen Karosserie (siehe die
Schichtung in restricted/skoda.php) und sind schwarz wie am Auto. Unter zwei
getoenten Flaechen bleibt davon nicht viel uebrig, vorn unter der Haube fast
nichts - das ist so gewollt. Von oben saehe man die Reifen ohnehin nicht;
sie stehen im Bild, damit die Achsen einen Ort haben, nicht damit man sie
liest. */
.skoda-auto .rad rect { fill: rgba(14, 16, 19, 0.95); }
.skoda-auto .spiegel rect { fill: rgba(var(--skoda-blech), 0.55); }
/* Die Kupplung ist schwarz und bleibt es: sie steht hinten heraus und liegt
dort auf dem Hintergrund, nicht unter dem Blech - sie braucht die
Aufhellung der Reifen also nicht. */
.skoda-auto .kupplung rect,
.skoda-auto .kupplung circle { fill: rgba(10, 12, 14, 0.95); }
/* Dachspoiler und Reling sind an diesem Fahrzeug schwarz, nicht lackiert -
und nur dadurch heben sie sich vom Dach ab, auf dem sie liegen. Vorher
hatten sie den Ton des Blechs und verschwanden darin. */
.skoda-auto .spoiler { fill: rgba(26, 29, 33, 0.85); }
/* Dachreling als Linie mit runden Enden - eine Strichstaerke beschreibt sie
vollstaendig, und die Enden runden sich von selbst. */
.skoda-auto .reling line {
stroke: rgba(26, 29, 33, 0.85);
stroke-width: 6;
stroke-linecap: round;
}
.skoda-auto .sicke {
fill: none;
stroke: rgba(var(--skoda-blech), 0.35);
stroke-width: 1.5;
}
/* Das Tech-Deck Face: beim Enyaq eine glaenzend schwarze Blende ueber die
volle Bugbreite. Von oben sieht man ihre Oberkante. */
/* Die Blende ist glaenzendes Schwarz, aber nicht das dunkelste im Bild: mit
voller Deckung sprang sie nach vorn und las sich wie ein Loch statt wie eine
Flaeche. Etwas durchscheinend und mit ruhiger Kante bleibt sie das, was sie
ist - eine Blende auf dem Blech. */
.skoda-auto .techdeck {
fill: rgba(20, 24, 30, 0.66);
stroke: rgba(var(--skoda-blech), 0.3);
stroke-width: 1;
}
.skoda-auto .scheibe,
.skoda-auto .dach {
fill: rgba(var(--skoda-blech), 0.28);
stroke: rgba(var(--skoda-blech), 0.45);
stroke-width: 1.5;
}
/* Grundzustand aller schaltbaren Teile: geschlossen und unauffaellig. */
.skoda-auto .teil > *,
.skoda-auto .teil > g > * {
fill: rgba(var(--skoda-blech), 0.34);
stroke: rgba(var(--skoda-blech), 0.6);
stroke-width: 1.5;
transition: fill .25s, stroke .25s, filter .25s;
}
.skoda-auto .teil-fenster > rect { fill: rgba(var(--skoda-blech), 0.5); }
/* Die Heckscheibe liegt in der Heckklappe - sie geht mit ihr auf, also ist
sie kein eigenes Teil. Aussehen soll sie trotzdem wie Glas und nicht wie
Blech; die Regel steht nach der allgemeinen und gewinnt damit. */
.skoda-auto .teil > .scheibe {
fill: rgba(var(--skoda-blech), 0.28);
stroke: rgba(var(--skoda-blech), 0.45);
}
/* Steht die Klappe offen, faerbt sich auch ihre Scheibe - nur durchsichtiger
als das Blech, sonst waere das Glas ploetzlich das Kraeftigste am Heck.
Der Rahmen kommt aus der allgemeinen Regel und ist damit derselbe. */
.skoda-auto .teil.offen > .scheibe { fill: rgba(224, 144, 79, 0.42); }
/* --- Antriebsbatterie ----------------------------------------------------
Die Platte im Fahrzeugboden ist die Ladestandsanzeige der Seite. Welche
Farbe sie traegt, steht in einer einzigen Variablen: die Zustandsklasse am
<svg> setzt --akku-farbe, und Huelle, Fuellung, Pegelkante, Stroemung und
Verlauf lesen sie von dort. Ein weiterer Zustand ist damit eine Zeile und
keine Suche durch die Datei. */
.skoda-auto { --akku-farbe: var(--skoda-ruhe); }
.skoda-auto.akku-laedt { --akku-farbe: var(--skoda-laden); }
.skoda-auto.akku-niedrig { --akku-farbe: var(--skoda-niedrig); }
.skoda-auto.akku-unbekannt { --akku-farbe: rgb(var(--skoda-blech)); }
/* Der Farbverlauf steht in <defs> und damit ausserhalb der Akkugruppe -
deshalb haengt --akku-farbe am <svg> und nicht an .akku. */
.skoda-auto #akkuVerlauf stop { stop-color: var(--akku-farbe); }
.skoda-auto .akku .huelle {
fill: rgba(8, 10, 12, .35);
stroke: var(--akku-farbe);
stroke-opacity: .55;
stroke-width: 1.5;
}
.skoda-auto .akku .fuellung {
fill: url(#akkuVerlauf);
stroke: none;
transition: y .8s ease, height .8s ease;
}
/* Die helle Kante auf der Fuellung. Sie macht den Stand auch dort ablesbar,
wo die getoente Flaeche darunter kaum auffaellt. */
.skoda-auto .akku .kante {
fill: var(--akku-farbe);
transition: y .8s ease;
}
.skoda-auto.akku-laedt .akku .kante {
filter: drop-shadow(0 0 5px var(--akku-farbe));
}
/* Modulfugen. Sie liegen auf 20, 40, 60 und 80 Prozent und sind damit
zugleich die Skala, an der sich der Stand ablesen laesst. */
.skoda-auto .akku .fugen line {
stroke: rgba(var(--skoda-blech), 0.4);
stroke-width: 1;
stroke-dasharray: 3 4;
}
/* Ladestrom: Streifen, die im gefuellten Teil nach vorn wandern. Der Schnitt
auf den Pegel kommt aus #akkuPegel, das zeichneAkku() mitfuehrt. */
.skoda-auto .akku .stroemung { opacity: 0; transition: opacity .4s; }
.skoda-auto .akku .stroemung rect { fill: var(--akku-farbe); }
.skoda-auto.akku-laedt .akku .stroemung { opacity: .4; }
.skoda-auto.akku-laedt .akku .stroemung .bahn {
animation: akkuStroemung 2.4s linear infinite;
}
@keyframes akkuStroemung {
from { transform: translateY(0); }
to { transform: translateY(-54px); } /* genau ein Streifenabstand */
}
/* Zielmarke. Sie wird als Ganzes verschoben, damit Linie und Beschriftung
zusammenbleiben und der Weg dorthin weich laeuft. */
.skoda-auto .akku .zielmarke { transition: transform .8s ease; }
.skoda-auto .akku .zielmarke line {
stroke: var(--bs-body-color, #e8ede9);
stroke-width: 1.5;
stroke-dasharray: 5 3;
opacity: .6;
}
.skoda-auto .akku .zielmarke text {
fill: var(--bs-body-color, #e8ede9);
font-size: 10px;
font-weight: 500;
text-anchor: end;
opacity: .8;
}
/* Zahlen in der Platte. Der dunkle Umriss liegt hinter der Fuellung der
Zeichen (paint-order), damit sie ueber geladenem wie leerem Teil der
Platte gleich gut lesbar bleiben. */
.skoda-auto .akku text {
paint-order: stroke;
stroke: rgba(8, 10, 12, .7);
stroke-linejoin: round;
stroke-width: 2.5;
}
.skoda-auto .akku .wert {
fill: var(--bs-body-color, #e8ede9);
font-size: 40px;
font-weight: 600;
text-anchor: middle;
font-variant-numeric: tabular-nums;
stroke-width: 4;
}
.skoda-auto .akku .wert .einheit {
font-size: 17px;
font-weight: 500;
opacity: .65;
}
.skoda-auto .akku .neben {
fill: var(--bs-body-color, #e8ede9);
fill-opacity: .8;
font-size: 14px;
text-anchor: middle;
}
/* Der Weg des Ladestroms vom Anschluss in die Platte. Die Striche laufen in
Fahrtrichtung des Stroms, also zum Akku hin. */
.skoda-auto .ladefluss {
fill: none;
stroke: var(--skoda-laden);
stroke-width: 2.5;
stroke-linecap: round;
stroke-dasharray: 3 7;
opacity: 0;
transition: opacity .3s;
}
.skoda-auto.akku-laedt .ladefluss {
opacity: .95;
animation: akkuFluss 1s linear infinite;
}
@keyframes akkuFluss {
from { stroke-dashoffset: 20; }
to { stroke-dashoffset: 0; }
}
/* Offen: bernstein und leicht leuchtend. Die Farbe allein traegt die Aussage
nicht - die Zeile unter dem Bild benennt jedes offene Teil zusaetzlich. */
.skoda-auto .teil.offen > *,
.skoda-auto .teil.offen > g > * {
fill: var(--skoda-offen);
stroke: var(--skoda-offen);
filter: drop-shadow(0 0 6px rgba(224, 144, 79, 0.65));
}
/* Licht: vorn die vier LED-Elemente je Seite, hinten das Leuchtenband. */
.skoda-auto .teil-licht.an > rect {
fill: var(--skoda-licht);
stroke: var(--skoda-licht);
filter: drop-shadow(0 0 8px rgba(242, 209, 107, 0.75));
}
.skoda-auto .teil-licht.an > rect.rueck {
fill: #e06060;
stroke: #e06060;
filter: drop-shadow(0 0 8px rgba(224, 96, 96, 0.7));
}
/* Ladeanschluss. Er ist gebaut wie der Klimaknopf; den Ladezustand traegt
nicht die Form, sondern die Farbe: blau wie jeder Knopf, gruen solange
geladen wird, dazu das Pulsen als Schein um die Scheibe. Die Scheibe selbst
bleibt deckend - gruen ausgefuellt verschluckte sie den Blitz. */
.skoda-auto .teil-stecker .glyphe { stroke-width: 2.2; }
.skoda-auto .teil-stecker.laedt .ring,
.skoda-auto .teil-stecker.laedt .flaeche { stroke: var(--skoda-laden); }
.skoda-auto .teil-stecker.laedt .glyphe { stroke: var(--skoda-laden); }
.skoda-auto .teil-stecker.laedt .flaeche {
animation: skodaPuls 1.8s ease-in-out infinite;
}
@keyframes skodaPuls {
0%, 100% { filter: drop-shadow(0 0 3px rgba(76, 175, 125, 0.5)); }
50% { filter: drop-shadow(0 0 12px rgba(76, 175, 125, 0.95)); }
}
/* Hervorheben, wenn der zugehoerige Eintrag darunter beruehrt wird - und
umgekehrt. Sichtbarer Fokus, damit das auch mit der Tastatur geht. */
.skoda-auto .teil.hervor > *,
.skoda-auto .teil.hervor > g > * {
stroke: var(--bs-primary, #0d6efd);
stroke-width: 2.5;
}
@media (hover: hover) {
.skoda-auto .teil:hover > *,
.skoda-auto .teil > g:hover > * {
stroke: var(--bs-primary, #0d6efd);
stroke-width: 2.5;
}
}
.skoda-auto .teil:focus,
.skoda-auto .teil > g:focus { outline: none; }
.skoda-auto .teil:focus-visible > *,
.skoda-auto .teil > g:focus-visible > * {
stroke: var(--bs-primary, #0d6efd);
stroke-width: 3;
}
@media (prefers-reduced-motion: reduce) {
.skoda-auto .teil > *,
.skoda-auto .teil > g > * { transition: none; }
.skoda-auto .teil-stecker.laedt .flaeche { animation: none; }
/* Der Ladestand bleibt sichtbar, nur nicht mehr in Bewegung. */
.skoda-auto .akku .fuellung,
.skoda-auto .akku .kante,
.skoda-auto .akku .zielmarke { transition: none; }
.skoda-auto.akku-laedt .akku .stroemung .bahn,
.skoda-auto.akku-laedt .ladefluss,
.skoda-auto.klima-laeuft .steuer .luefter .blaetter { animation: none; }
}
/* --- Zustandszeile unter dem Fahrzeug ------------------------------------ */
.skoda-teile {
display: flex;
flex-wrap: wrap;
gap: .35rem;
justify-content: flex-start;
margin-top: .45rem;
}
.skoda-teile .chip {
display: inline-flex;
align-items: center;
gap: .35rem;
font-size: .78rem;
padding: .2rem .55rem;
border-radius: 1rem;
border: 1px solid var(--bs-border-color, rgba(128, 128, 128, .35));
color: var(--bs-secondary-color, #9aa0a6);
cursor: default;
white-space: nowrap;
}
/* Ein Punkt vor dem Wort traegt dieselbe Aussage wie die Farbe im Bild und
macht die Zeile auf einen Blick ueberfliegbar. */
.skoda-teile .chip::before {
content: "";
width: .45rem;
height: .45rem;
border-radius: 50%;
background: rgba(var(--skoda-blech), .75);
flex: 0 0 auto;
}
.skoda-teile .chip.offen { border-color: var(--skoda-offen); color: var(--skoda-offen); }
.skoda-teile .chip.offen::before { background: var(--skoda-offen); }
.skoda-teile .chip.an::before { background: var(--skoda-licht); }
.skoda-teile .chip.hervor {
border-color: var(--bs-primary, #0d6efd);
color: var(--bs-body-color, #e8ede9);
}
/* Auf Beruehrungsgeraeten sind die Ziele zu klein: eine Fingerkuppe deckt
rund 9 mm ab, die Chips waren keine 6 mm hoch. Gleiches gilt fuer die
Fahrtzeilen und den Umschalter. */
@media (pointer: coarse) {
.skoda-teile { gap: .5rem; }
.skoda-teile .chip {
font-size: .85rem;
padding: .45rem .8rem;
cursor: pointer;
}
.skoda-liste .zeile { padding: .75rem .9rem; }
.skoda-umschalter button { font-size: .85rem; padding: .5rem .9rem; }
}
.skoda-teile .chip.an { border-color: var(--skoda-licht); color: var(--skoda-licht); }
/* --- Kennzahlen neben dem Fahrzeug ---------------------------------------
Was sich nicht ins Bild zeichnen laesst, steht in gleich gebauten Kacheln:
Zeichen, Bezeichnung, Wert und bei Bedarf eine Nebenzeile. Eine Kachel
bekommt die Klasse .aktiv, solange ihre Zahl gerade etwas aussagt - die
Ladeleistung waehrend des Ladens, das Klima, solange es laeuft. Welche
Farbe die Hervorhebung traegt, steht in --kennzahl-rgb. */
.skoda-kennzahlen {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
gap: .75rem;
}
.kennzahl {
--kennzahl-rgb: var(--skoda-laden-rgb);
display: flex;
align-items: center;
gap: .75rem;
padding: .7rem .85rem;
border: 1px solid var(--bs-border-color, rgba(128, 128, 128, .25));
border-radius: .7rem;
background: rgba(var(--skoda-blech), .07);
transition: border-color .3s, background .3s;
}
.kennzahl .zeichen {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2.1rem;
height: 2.1rem;
border-radius: 50%;
background: rgba(var(--skoda-blech), .16);
color: var(--bs-secondary-color, #9aa0a6);
transition: background .3s, color .3s;
}
.kennzahl .text { display: flex; flex-direction: column; min-width: 0; }
.kennzahl .v {
font-size: 1.2rem;
line-height: 1.3;
font-variant-numeric: tabular-nums;
}
.kennzahl .neben {
font-size: .72rem;
color: var(--bs-secondary-color, #9aa0a6);
}
.kennzahl .neben:empty { display: none; }
.kennzahl.aktiv {
border-color: rgba(var(--kennzahl-rgb), .5);
background: rgba(var(--kennzahl-rgb), .1);
}
.kennzahl.aktiv .zeichen {
background: rgba(var(--kennzahl-rgb), .2);
color: rgb(var(--kennzahl-rgb));
}
#skodaKachelKlima { --kennzahl-rgb: var(--skoda-ruhe-rgb); }
/* --- Fahrleistung --------------------------------------------------------
Drei Zahlen nebeneinander, durch einen Strich getrennt. Kein Kasten und
keine Kachel: es ist eine Nebenauskunft, kein Messwert des Augenblicks.
Ein Wert, dessen Zeitraum weiter zurueckreicht als die Aufzeichnung, wird
gestrichelt unterstrichen und nennt im Titel, seit wann gemessen wird. */
.skoda-fahrleistung .reihe {
display: flex;
flex-wrap: wrap;
gap: .35rem 1.4rem;
margin-top: .5rem;
}
.skoda-fahrleistung .posten {
display: flex;
flex-direction: column;
min-width: 5.5rem;
}
.skoda-fahrleistung .posten + .posten {
border-left: 1px solid var(--bs-border-color, rgba(128, 128, 128, .25));
padding-left: 1.4rem;
}
/* Auf dem Handy zaehlt jeder Millimeter: die Trennstriche weichen, damit die
drei Zahlen in einer Zeile bleiben statt in zwei umzubrechen. */
@media (max-width: 30rem) {
.skoda-fahrleistung .reihe { gap: .3rem .9rem; }
.skoda-fahrleistung .posten { min-width: 0; }
.skoda-fahrleistung .posten + .posten { border-left: none; padding-left: 0; }
}
.skoda-fahrleistung .posten .v {
font-size: 1.15rem;
line-height: 1.3;
font-variant-numeric: tabular-nums;
}
/* --- Erklärungen ---------------------------------------------------------
Ein Element mit Erklärung traegt ein Info-Zeichen. Das ist der einzige
Hinweis darauf, dass es dort etwas zu lesen gibt - am Zeiger genuegte das
Ueberfahren, am Finger gibt es das nicht. Angetippt oder ueberfahren oeffnet
setzeHinweis() ein Popover; das Zeichen sagt vorher, dass sich das lohnt. */
/* Kein Textmarkieren beim Antippen: der Finger trifft Text, gemeint ist der
Auslöser - eine blau markierte Zahl waere die falsche Antwort darauf. */
.hat-hinweis {
cursor: help;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.skoda-fahrleistung .posten.hat-hinweis .v::after,
#skodaVerbrauchBasis.hat-hinweis::after {
content: "\f431"; /* bi-info-circle */
font-family: bootstrap-icons;
font-size: .78em;
margin-left: .3em;
opacity: .65;
vertical-align: .05em;
}
.hat-hinweis:focus { outline: none; }
.hat-hinweis:focus-visible {
outline: 2px solid var(--bs-primary, #4b8fd6);
outline-offset: 3px;
border-radius: .2rem;
}
/* Das Fenster selbst: dunkel wie die Kacheln, schmal genug zum Lesen. Auf dem
Handy darf es die halbe Breite einnehmen, mehr nicht. */
.skoda-erklaerung {
--bs-popover-max-width: 22rem;
--bs-popover-bg: #23282c;
--bs-popover-border-color: rgba(128, 138, 148, .35);
--bs-popover-body-color: var(--bs-body-color, #e8ede9);
--bs-popover-body-padding-x: .8rem;
--bs-popover-body-padding-y: .6rem;
--bs-popover-font-size: .82rem;
box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .45);
}
/* --- Herkunft des Ladestroms ---------------------------------------------
Der Balken teilt die Ladeleistung in den Anteil aus der eigenen Anlage und
den Rest aus dem Netz. Die Legende nennt beide Anteile in Zahlen - ein
Farbstueck allein sagt nicht, wie viel es ist. */
.skoda-quelle .kopf {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: .75rem;
margin-bottom: .4rem;
}
.skoda-quelle .summe {
font-size: .85rem;
font-variant-numeric: tabular-nums;
color: var(--bs-secondary-color, #9aa0a6);
}
.skoda-balken {
display: flex;
height: .8rem;
border-radius: .4rem;
overflow: hidden;
background: rgba(var(--skoda-blech), 0.2);
}
.skoda-balken > div { transition: width .5s ease; }
.skoda-balken .teil-pv { background: var(--skoda-pv); }
.skoda-balken .teil-netz { background: var(--skoda-netz); }
.skoda-quelle .legende {
display: flex;
gap: 1.1rem;
margin-top: .45rem;
font-size: .78rem;
color: var(--bs-secondary-color, #9aa0a6);
}
.skoda-quelle .legende span { display: inline-flex; align-items: center; gap: .35rem; }
.skoda-quelle .legende b {
color: var(--bs-body-color, #e8ede9);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.skoda-quelle .legende .pkt { width: .55rem; height: .55rem; border-radius: 50%; }
.skoda-quelle .legende .pkt.pv { background: var(--skoda-pv); }
.skoda-quelle .legende .pkt.netz { background: var(--skoda-netz); }
/* --- Listen (Ladungen, Fahrten) ------------------------------------------ */
/* Der Innenabstand oben trennt die erste Zeile von der Ueberschrift. Ohne
ihn klebt sie am Umschalter, was auf dem Handy besonders auffaellt. */
.skoda-liste {
max-height: 400px;
overflow-y: auto;
padding-top: .5rem;
}
.skoda-liste .zeile {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: .75rem;
padding: .5rem .9rem;
border-top: 1px solid var(--bs-border-color, rgba(128, 128, 128, .25));
}
.skoda-liste .zeile:first-child { border-top: none; }
@media (hover: hover) {
.skoda-liste .zeile:hover { background: rgba(128, 138, 148, 0.08); }
}
.skoda-liste .wann { font-size: .82rem; color: var(--bs-secondary-color, #9aa0a6); }
.skoda-liste .was { font-size: .95rem; font-variant-numeric: tabular-nums; text-align: right; }
.skoda-liste .neben { display: block; font-size: .75rem; color: var(--bs-secondary-color, #9aa0a6); }
.skoda-liste .leer { padding: 1.5rem .9rem; color: var(--bs-secondary-color, #9aa0a6); }
/* Umschalter kWh <-> Prozent in der Ueberschrift der Fahrtenliste. Welche
Einheit sichtbar ist, entscheidet die Klasse auf der Kachel - so bleibt die
Auswahl bestehen, wenn die Liste neu gebaut wird. */
.zeigt-kwh .v-prozent,
.zeigt-prozent .v-kwh,
.zeigt-prozent #skodaVerbrauchBasis { display: none; }
.skoda-umschalter {
display: inline-flex;
border: 1px solid var(--bs-border-color, rgba(128, 128, 128, .35));
border-radius: .3rem;
overflow: hidden;
}
.skoda-umschalter button {
background: none;
border: none;
color: var(--bs-secondary-color, #9aa0a6);
font-size: .72rem;
line-height: 1;
padding: .25rem .5rem;
cursor: pointer;
}
.skoda-umschalter button + button {
border-left: 1px solid var(--bs-border-color, rgba(128, 128, 128, .35));
}
.skoda-umschalter button:hover { background: rgba(128, 138, 148, .12); }
.skoda-umschalter button.aktiv {
background: rgba(128, 138, 148, .22);
color: var(--bs-body-color, #e8ede9);
}
.skoda-umschalter button:focus-visible {
outline: 2px solid var(--bs-primary, #0d6efd);
outline-offset: -2px;
}
/* Fahrtzeilen, die sich auf der Karte zeigen lassen. Der Zeiger und die
Markierung sind der einzige Hinweis darauf - ohne den waere die Kopplung
nicht zu erraten. */
.skoda-liste .zeile.anklickbar { cursor: pointer; }
.skoda-liste .zeile.anklickbar:focus-visible {
outline: 2px solid var(--bs-primary, #0d6efd);
outline-offset: -2px;
}
/* Dieselbe Farbe wie die gewaehlte Linie auf der Karte - die Zuordnung
zwischen Zeile und Linie soll ohne Nachdenken erkennbar sein. */
.skoda-liste .zeile.gewaehlt {
background: rgba(224, 144, 79, 0.14);
box-shadow: inset 3px 0 0 #e0904f;
}
/* --- Karte (Leaflet) -----------------------------------------------------
Die Kacheln kommen ueber ajax/tile.php aus dem eigenen Zwischenspeicher.
OpenStreetMap-Kacheln sind hell; im dunklen Dashboard wuerden sie wie ein
Scheinwerfer wirken. invert + hue-rotate dreht sie ins Dunkle, ohne einen
zweiten Anbieter samt eigener Bedingungen ins Spiel zu bringen. Der Filter
sitzt auf den Kacheln allein - Marker, Linien und Bedienelemente behalten
ihre Farben. */
.skoda-karte {
height: 420px;
width: 100%;
border-radius: .4rem;
background: rgba(128, 138, 148, 0.12);
z-index: 0; /* sonst legt sich die Karte ueber die Seitenleiste */
}
.skoda-karte .leaflet-tile-pane {
filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.9);
}
/* Bedienelemente und Sprechblasen an das dunkle Theme angleichen. */
.skoda-karte .leaflet-bar a,
.skoda-karte .leaflet-bar a:hover {
background: #23282c;
color: var(--bs-body-color, #e8ede9);
border-bottom-color: #33393e;
}
.skoda-karte .leaflet-bar a:hover { background: #2d3338; }
.skoda-karte .leaflet-popup-content-wrapper,
.skoda-karte .leaflet-popup-tip {
background: #23282c;
color: var(--bs-body-color, #e8ede9);
box-shadow: 0 3px 14px rgba(0, 0, 0, .5);
}
.skoda-karte .leaflet-popup-content { margin: .6rem .8rem; line-height: 1.5; }
.skoda-karte .leaflet-popup-close-button { color: var(--bs-secondary-color, #9aa0a6); }
/* Die Nennung von OpenStreetMap ist Pflicht und darf nicht versteckt werden -
nur ruhiger gesetzt, damit sie das Bild nicht dominiert.
Ohne Hintergrundkasten, dafuer mit Schattenwurf: ein durchsichtiger
Hintergrund allein liesse den Text ueber hellen Kartenstellen verschwinden,
und unlesbar waere so gut wie versteckt. Der Schatten haelt ihn auf jedem
Untergrund erkennbar, ohne einen Balken ins Bild zu setzen. */
/* Der doppelte Klassenname ist noetig, nicht Zierde: Leaflet setzt den
Hintergrund ueber .leaflet-container .leaflet-control-attribution, also mit
derselben Spezifitaet wie ein einfaches .skoda-karte davor - und leaflet.css
wird spaeter geladen, gewinnt bei Gleichstand also. Der Kartencontainer
traegt beide Klassen, damit sticht diese Regel. */
.skoda-karte.leaflet-container .leaflet-control-attribution {
background: transparent;
color: #c9d0d6;
font-size: .68rem;
padding: 0 .4rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, .9), 0 0 4px rgba(0, 0, 0, .75);
}
.skoda-karte.leaflet-container .leaflet-control-attribution a {
color: #a9c8f0;
text-shadow: inherit;
}
.skoda-kartenlegende {
display: inline-flex;
gap: .9rem;
font-size: .75rem;
color: var(--bs-secondary-color, #9aa0a6);
}
.skoda-kartenlegende span { display: inline-flex; align-items: center; gap: .3rem; }
.skoda-kartenlegende .pkt { width: .55rem; height: .55rem; border-radius: 50%; }
.skoda-kartenlegende .pkt.jetzt { background: #4caf7d; }
.skoda-kartenlegende .pkt.halt { background: #8a949c; }
.skoda-kartenlegende .lin {
width: 1.1rem;
border-top: 2px dashed #8a949c;
}
/* --- Befund -------------------------------------------------------------- */
.skoda-befund .zahl {
font-size: 2.4rem;
font-weight: 600;
line-height: 1.1;
font-variant-numeric: tabular-nums;
}
.skoda-befund .zahl .einheit {
font-size: .95rem;
font-weight: 400;
color: var(--bs-secondary-color, #9aa0a6);
margin-left: .35rem;
}
.skoda-befund dl { margin: 1rem 0 0; display: grid; gap: .5rem; }
.skoda-befund dl > div { display: flex; justify-content: space-between; gap: 1rem; }
.skoda-befund dt {
font-weight: 400;
color: var(--bs-secondary-color, #9aa0a6);
font-size: .85rem;
}
.skoda-befund dd { margin: 0; font-variant-numeric: tabular-nums; font-size: .9rem; }
/* --- Steuerung ----------------------------------------------------------
Die Befehle sitzen im Modal, das ueber die anklickbaren Stellen im
Fahrzeugbild aufgeht. */
.skoda-steuerung .knoepfe { display: flex; flex-wrap: wrap; gap: .5rem; }
.skoda-steuerung .knoepfe button { flex: 1 1 11rem; }
.skoda-steuerung .temperatur {
display: flex;
align-items: center;
justify-content: space-between;
gap: .75rem;
font-size: .9rem;
}
.skoda-steuerung .temperatur span { display: flex; align-items: center; gap: .35rem; }
.skoda-steuerung .temperatur input { width: 5.5rem; }
@media (pointer: coarse) {
.skoda-steuerung .knoepfe button { padding: .7rem 1rem; }
}
.skoda-rueckmeldung {
font-size: .9rem;
padding: .5rem .75rem;
border-radius: .35rem;
display: none;
}
.skoda-rueckmeldung.warte,
.skoda-rueckmeldung.gut,
.skoda-rueckmeldung.schlecht { display: block; }
.skoda-rueckmeldung.warte { background: rgba(128, 138, 148, .16); color: var(--bs-secondary-color, #9aa0a6); }
.skoda-rueckmeldung.gut { background: rgba(76, 175, 125, .16); color: #6fd3a0; }
.skoda-rueckmeldung.schlecht { background: rgba(224, 96, 96, .16); color: #f08a8a; }
/* --- Anklickbare Stellen im Fahrzeugbild --------------------------------
Der Ring unterscheidet Bedienung von Anzeige: die uebrigen Teile zeigen
nur ihren Zustand, diese beiden aendern etwas. */
.skoda-auto .steuer { cursor: pointer; }
.skoda-auto .steuer .ring {
fill: none;
stroke: var(--bs-primary, #4b8fd6);
stroke-width: 1.5;
stroke-dasharray: 3 3;
opacity: .55;
transition: opacity .2s, stroke-width .2s;
}
/* Der Knopf sitzt mitten auf der Akkuplatte und muss sich gegen jeden
Fuellstand behaupten - gegen die dunkle leere Zelle ebenso wie gegen die
gefuellte. Eine durchscheinende Flaeche kann das nicht: sie nimmt die Farbe
an, die zufaellig darunter liegt. Deshalb eine eigene, deckende Scheibe. */
.skoda-auto .steuer .flaeche {
fill: rgba(10, 13, 17, .92);
stroke: var(--bs-primary, #4b8fd6);
stroke-width: 1.5;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, .55));
}
.skoda-auto .steuer .glyphe {
fill: none;
stroke: #7db3f0; /* dieselbe Farbe wie die Luefterfluegel */
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Luefterrad des Klimaknopfs. Es dreht sich, solange die Klimatisierung
arbeitet - dasselbe Mittel wie beim Ladeanschluss: der Zustand steht als
Klasse am <svg>, die Form bleibt, wo sie ist. */
.skoda-auto .steuer .luefter use { fill: #7db3f0; }
.skoda-auto .steuer .luefter .nabe { fill: rgba(10, 13, 17, .92); }
.skoda-auto .steuer .luefter .blaetter { transform-origin: 0 0; }
.skoda-auto.klima-laeuft .steuer .luefter .blaetter {
animation: klimaDreht 3.2s linear infinite;
}
@keyframes klimaDreht {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@media (hover: hover) {
.skoda-auto .steuer:hover .ring { opacity: 1; stroke-width: 2.5; }
}
.skoda-auto .steuer:focus { outline: none; }
.skoda-auto .steuer:focus-visible .ring { opacity: 1; stroke-width: 3; }
.skoda-hinweis {
margin: .6rem 0 0;
font-size: .75rem;
color: var(--bs-secondary-color, #9aa0a6);
text-align: center;
}
.skoda-alter { font-size: .85rem; }
/* === Einstellungen: Werte auf den Home-Kacheln ===========================
Eine Zeile je Raum, aufklappbar. Die Zeile zeigt im zugeklappten Zustand
schon, was auf der Kachel steht - sonst muesste man zwanzig Raeume
aufklappen, um einen zu finden. */
.kachel-etage { margin-bottom: 1.25rem; }
.kachel-etage:last-child { margin-bottom: 0; }
.kachel-etage-name {
font-size: .7rem;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--ton-schrift-3, #6f7a81);
margin-bottom: .35rem;
}
.kachel-zeile {
border: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
border-radius: .5rem;
margin-bottom: .4rem;
overflow: hidden;
}
.kachel-zeile.offen { border-color: var(--ton-linie-satt, rgba(138, 148, 158, .38)); }
.kachel-kopf {
display: flex;
align-items: center;
gap: .6rem;
width: 100%;
padding: .5rem .75rem;
background: none;
border: 0;
color: inherit;
font: inherit;
text-align: left;
cursor: pointer;
}
@media (hover: hover) {
.kachel-kopf:hover { background: rgba(138, 148, 158, .08); }
}
.kachel-kopf .pfeil {
color: var(--ton-schrift-3, #6f7a81);
transition: transform .18s;
flex: 0 0 auto;
}
.kachel-zeile.offen .kachel-kopf .pfeil { transform: rotate(90deg); }
.kachel-kopf .name { min-width: 8rem; }
.kachel-kopf .werte {
display: flex;
flex-wrap: wrap;
gap: .3rem;
flex: 1 1 auto;
}
.kachel-chip {
font-size: .72rem;
padding: .1rem .45rem;
border: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
border-radius: 1rem;
color: var(--ton-schrift-2, #9aa6ad);
white-space: nowrap;
}
.kachel-chip.leer { font-style: italic; }
.kachel-chip .bi { margin-right: .3rem; opacity: .8; }
.kachel-editor {
padding: .25rem .75rem .75rem;
border-top: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
}
/* Eine Wertzeile. Auf breiten Schirmen nebeneinander, auf dem Handy
untereinander - die Felder sind zu kurz, um sie zu quetschen. */
.kachel-wert {
display: grid;
grid-template-columns: 7.5rem minmax(10rem, 1fr) 7rem 5.5rem 9.5rem auto;
gap: .4rem;
align-items: center;
padding: .5rem 0;
border-bottom: 1px dashed var(--ton-linie, rgba(138, 148, 158, .2));
}
.kachel-wert:last-of-type { border-bottom: 0; }
/* Symbolname mit Vorschau davor: die Vorschau sagt, was der Name bedeutet -
"moisture" allein sagt es nicht. */
.kachel-wert .kw-symbol {
display: flex;
align-items: center;
gap: .35rem;
}
.kachel-wert .kw-symbol-schau {
flex: none;
width: 1.5rem;
text-align: center;
font-size: 1.05rem;
line-height: 1;
color: var(--ton-schrift-2, #9aa6ad);
}
.kachel-wert .kw-symbol-schau .unbekannt { color: var(--ton-schlecht, #e06060); }
.kachel-wert .kw-symbol input { min-width: 0; }
.kachel-wert .kw-schalter {
display: flex;
align-items: center;
gap: .5rem;
white-space: nowrap;
}
.kachel-wert .kw-schalter .kw-format {
width: auto;
font-size: .78rem;
padding-top: .1rem;
padding-bottom: .1rem;
}
.kachel-wert .kw-schalter label {
display: inline-flex;
align-items: center;
gap: .25rem;
font-size: .78rem;
color: var(--ton-schrift-2, #9aa6ad);
cursor: pointer;
}
@media (max-width: 991.98px) {
.kachel-wert {
grid-template-columns: 1fr 1fr;
}
.kachel-wert .kw-topic { grid-column: 1 / -1; }
}
.kachel-fuss {
display: flex;
align-items: center;
gap: .75rem;
margin-top: .75rem;
}
/* === Geraetetafel (Einstellungen) ========================================
Sechzig Zeilen, in denen die meiste Arbeit an einer Handvoll haengt: den
Geraeten ohne Raum und denen, die seit Monaten schweigen. Beide sind
markiert, damit man sie nicht suchen muss - der Rest bleibt ruhig. */
.geraete-sieb {
display: flex;
align-items: center;
gap: .9rem;
margin-bottom: .75rem;
}
.geraete-sieb input[type="search"] { max-width: 22rem; }
.geraete-sieb .form-check { flex: none; }
/* Kein eigener Rollbalken: die Tafel ist so lang, wie sie ist, und die Seite
scrollt als Ganzes. Ein Kasten mit eigenem Balken in einer Karte, die selbst
in einer scrollenden Seite steht, ist ein Balken zu viel - man weiss beim
Wischen nie, welcher der beiden gerade dran ist. */
.geraete-tafel {
/* Bewusst ohne Rahmen und ohne overflow: beides machte aus der Tafel
wieder einen eigenen Scroll-Kasten, und der klebende Tabellenkopf haette
nichts mehr, woran er kleben kann. */
margin-bottom: 0;
}
/* Der Kopf bleibt trotzdem stehen: er klebt an der Kopfzeile der Seite, denn
gescrollt wird bei AdminLTE in .app-main, und dort ist oben die Navigation.
Ab Zeile zwanzig weiss man sonst nicht mehr, welche Spalte der Raum war. */
.geraete-tafel thead th {
position: sticky;
top: 0;
z-index: 1;
background: var(--ton-flaeche-2, #2b3237);
font-size: .72rem;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--ton-schrift-3, #6f7a81);
white-space: nowrap;
border-bottom: 1px solid var(--bs-border-color, #2c3034);
box-shadow: 0 1px 0 var(--ton-grund, #191d20); /* deckt die Zeile darunter */
}
.geraete-tafel td { vertical-align: middle; }
.geraete-tafel .name { line-height: 1.25; }
.geraete-tafel .name .neben {
display: block;
font-size: .72rem;
color: var(--ton-schrift-3, #6f7a81);
}
.geraete-tafel .raum { min-width: 11rem; }
.geraete-tafel .raum select { min-width: 9rem; }
.geraete-tafel .alter { color: var(--ton-schrift-2, #9aa4ab); white-space: nowrap; }
.geraete-tafel .alter.lange { color: var(--ton-sonne, #f0b429); }
/* Ohne Raum: eine Kante links statt einer eingefaerbten Zeile - Letzteres
sieht bei zwanzig offenen Geraeten nach Fehler aus, ist aber nur Arbeit.
Als ::before und nicht als border, weil Bootstrap Tabellenzellen im
Hover mit einem inset-Schatten fuellt und den Rand damit ueberdeckt. */
.geraete-tafel tr.ohne-raum td:first-child { position: relative; }
.geraete-tafel tr.ohne-raum td:first-child::before {
content: "";
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--ton-sonne, #f0b429);
}
/* Der Papierkorb haelt sich zurueck, bis man ihn braucht. */
.geraete-tafel .geraet-weg {
color: var(--ton-schrift-3, #6f7a81);
border-color: transparent;
line-height: 1;
}
.geraete-tafel .geraet-weg:hover:not(:disabled),
.geraete-tafel .geraet-weg:focus-visible:not(:disabled) {
color: var(--ton-schlecht, #e06060);
border-color: var(--ton-schlecht, #e06060);
}
.geraete-tafel .geraet-weg:disabled { opacity: .25; }
.geraete-fuss {
display: flex;
justify-content: space-between;
align-items: center;
gap: .75rem;
margin-top: .5rem;
}
@media (max-width: 575.98px) {
.geraete-sieb { flex-wrap: wrap; }
.geraete-sieb input[type="search"] { max-width: none; }
}
/* === Protokollseite ======================================================
Ein Log liest sich nur in einer Schrift mit fester Breite: die Zeitstempel
stehen dann untereinander, und das Auge findet die Stelle, an der etwas
anders wird. Die Zeilen umbrechen statt seitlich zu scrollen - ein
Traceback ist lang, und waagerecht zu blaettern verliert man sofort. */
.log-ausgabe {
max-height: calc(100vh - 22rem);
min-height: 20rem;
overflow-y: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: .78rem;
line-height: 1.45;
background: var(--bs-body-bg, #1b1e21);
border: 1px solid var(--bs-border-color, #2c3034);
border-radius: .375rem;
}
.log-ausgabe .log-zeile {
display: flex;
gap: .6rem;
padding: .1rem .6rem;
border-left: 3px solid transparent;
white-space: pre-wrap;
word-break: break-word;
}
.log-ausgabe .log-zeile:hover { background: rgba(255, 255, 255, .04); }
.log-ausgabe .log-zeit { flex: 0 0 5rem; color: var(--bs-secondary-color, #8b9198); }
.log-ausgabe .log-level { flex: 0 0 4.5rem; font-weight: 600; }
.log-ausgabe .log-text { flex: 1 1 auto; }
/* Fortsetzungszeilen - die zweite bis letzte Zeile eines Tracebacks - tragen
kein eigenes Level. Sie bleiben in der Farbe ihrer Kopfzeile, ruecken aber
ein: so sieht man auf einen Blick, was zusammengehoert. */
.log-ausgabe .log-fortsetzung .log-text { padding-left: 1.2rem; opacity: .85; }
.log-ausgabe .log-debug { color: var(--bs-secondary-color, #8b9198); }
.log-ausgabe .log-info .log-level { color: #6ea8dc; }
.log-ausgabe .log-warning { border-left-color: #d9a441; background: rgba(217, 164, 65, .07); }
.log-ausgabe .log-warning .log-level { color: #d9a441; }
.log-ausgabe .log-error { border-left-color: #e06060; background: rgba(224, 96, 96, .09); }
.log-ausgabe .log-error .log-level { color: #f08a8a; }
/* Tagestrenner: das Datum steht einmal da, nicht in jeder Zeile. */
.log-ausgabe .log-tag {
position: sticky;
top: 0;
padding: .15rem .6rem;
font-size: .72rem;
letter-spacing: .04em;
color: var(--bs-secondary-color, #8b9198);
background: var(--bs-tertiary-bg, #23272b);
border-top: 1px solid var(--bs-border-color, #2c3034);
border-bottom: 1px solid var(--bs-border-color, #2c3034);
}
/* Die Dateiliste links: der aktive Eintrag wird von AdminLTE eingefaerbt,
die Zustandszeile darunter soll trotzdem lesbar bleiben. */
.log-liste .log-wahl.active .text-secondary,
.log-liste .log-wahl.active .text-warning { color: rgba(255, 255, 255, .75) !important; }
/* Etagenschalter im Grundriss der Home-Ansicht.
Vorher waren sie ein duenner Ring in einem Amber (#aa7713), das sonst
nirgends im Dashboard vorkommt - und alle vier sahen immer gleich aus,
die aktive Etage war an ihnen nicht zu erkennen. Jetzt tragen sie
dieselbe Scheibe wie die Kacheln im Grundriss, und die aktive Etage
bekommt die Akzentfarbe der Schieberegler. */
.floorBtn { cursor: pointer; }
.floorBtn circle {
fill: #0006;
stroke: #9aa0a6;
stroke-width: 4;
opacity: .55;
transition: stroke .25s ease, opacity .25s ease;
}
.floorBtn text {
font: 600 34px var(--font-anzeige);
fill: #9aa0a6;
opacity: .8;
transition: fill .25s ease, opacity .25s ease;
/* Der Kreis faengt den Klick, nicht die Beschriftung - sonst wandert der
Textcursor ueber die Schrift, sobald man sie streift. */
user-select: none;
pointer-events: none;
}
.floorBtn:hover circle { opacity: .8; }
.floorBtn:hover text { opacity: 1; }
.floorBtn.active circle { stroke: #00788F; opacity: 1; }
.floorBtn.active text { fill: #e8ede9; opacity: 1; }
/* ===========================================================================
Einstellungen -> Grundriss (js/solar/grundriss.js)
Links der Plan, rechts die Formulare. Die Kachel im Plan sieht aus wie auf
der Startseite (restricted/home.php): dunkles Rechteck, weisse Schrift -
nur ohne Messwerte, dafuer mit Rand, wenn sie gewaehlt ist.
=========================================================================== */
.gr-etagen {
display: flex;
flex-wrap: wrap;
gap: .4rem;
margin-bottom: .9rem;
}
/* Eine Klasse mehr als Bootstrap: solar.css laedt vor AdminLTE. */
.gr-etagen .btn.gr-etage {
display: inline-flex;
align-items: baseline;
gap: .4rem;
border: 1px solid var(--ton-linie-satt, rgba(138, 148, 158, .38));
color: var(--ton-schrift-2, #9aa6ad);
}
.gr-etagen .btn.gr-etage.aktiv {
border-color: #00788F;
color: var(--ton-schrift, #e8ede9);
background: rgba(0, 120, 143, .18);
}
.gr-etage-code { font: 600 .9rem var(--font-anzeige); }
.gr-etage-name { font-size: .75rem; }
.gr-etage-std { font-size: .65rem; color: var(--ton-sonne, #f0b429); }
.gr-plan-huelle {
position: relative;
border-radius: var(--ton-radius-klein, .5rem);
overflow: hidden;
background: #0b0d10;
}
.gr-plan-huelle.platzieren .gr-plan { cursor: crosshair; }
.gr-plan-leer {
aspect-ratio: 4 / 3;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
color: var(--ton-schrift-3, #6f7a81);
font-size: .85rem;
}
.gr-plan {
display: block;
width: 100%;
height: auto;
touch-action: none;
user-select: none;
}
.gr-ohne-bild {
fill: var(--ton-schrift-3, #6f7a81);
font: 12px sans-serif;
text-anchor: middle;
}
.gr-kachel { cursor: grab; }
.gr-kachel.zieht { cursor: grabbing; }
.gr-kachel rect {
fill: #0006;
stroke: #ffffff55;
stroke-width: .5;
}
.gr-kachel:hover rect { stroke: #ffffffaa; }
.gr-kachel.gewaehlt rect { stroke: var(--ton-sonne, #f0b429); stroke-width: 1; }
.gr-kachel text {
fill: #fff;
font: 4.8px sans-serif;
text-anchor: middle;
pointer-events: none;
}
.gr-geist rect {
fill: rgba(240, 180, 41, .28);
stroke: var(--ton-sonne, #f0b429);
stroke-width: .8;
pointer-events: none;
}
.gr-block {
border: 1px solid var(--ton-linie, rgba(138, 148, 158, .2));
border-radius: var(--ton-radius-klein, .5rem);
padding: .6rem .75rem;
margin-bottom: .6rem;
}
.gr-block-titel {
font-size: .7rem;
letter-spacing: .13em;
text-transform: uppercase;
color: var(--ton-schrift-3, #6f7a81);
margin-bottom: .35rem;
}
.gr-knoepfe {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .35rem;
margin-top: .6rem;
}
/* Papierkorb wie in der Geraetetafel: erst rot, wenn man ihn anfasst. */
.gr-knoepfe .btn.gr-etage-weg,
.gr-knoepfe .btn.gr-raum-weg {
margin-left: auto;
color: var(--ton-schrift-3, #6f7a81);
}
.gr-knoepfe .btn.gr-etage-weg:hover,
.gr-knoepfe .btn.gr-raum-weg:hover { color: var(--ton-schlecht, #e06060); }
.gr-raeume { display: flex; flex-direction: column; gap: .15rem; }
.gr-raum {
display: flex;
align-items: center;
gap: .4rem;
padding: .1rem .3rem;
border-radius: .35rem;
}
.gr-raum.gewaehlt { background: rgba(240, 180, 41, .12); }
.gr-raum-name {
flex: 1 1 auto;
background: none;
border: 0;
padding: .2rem 0;
color: inherit;
font: inherit;
font-size: .88rem;
text-align: left;
cursor: pointer;
}
.gr-raum-stand { color: var(--ton-schrift-3, #6f7a81); font-size: .8rem; }
.gr-raum .btn.gr-platzieren { padding: 0 .45rem; font-size: .75rem; }