/* ========================================================================== 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; --ton-radius: .7rem; /* ... 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-link-color: var(--ton-sonne); --bs-link-hover-color: #ffc94d; --bs-heading-color: var(--ton-schrift); } /* 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; } } /* Editor der Automatiken. Eine Bedingungszeile traegt Geraet, Messwert, Vergleich, Wert, Einheit und den Papierkorb nebeneinander - auf einem Handy bleiben davon "Therm OG Bad" und "te" uebrig, weil die Felder in der input-group einfach mitschrumpfen. Unter 576 px bekommen deshalb Geraet und Messwert je eine eigene Zeile; Vergleich, Wert und Einheit sind kurz genug, um zusammen in die naechste zu passen. */ @media (max-width: 575.98px) { /* Die input-group-Klasse muss mit in den Selektor: solar.css wird vor AdminLTE geladen, und Bootstraps ".input-group > .form-select" haette sonst dasselbe Gewicht und wuerde als spaeteres Blatt gewinnen. */ .input-group.auto-zeile > .auto-geraet, .input-group.auto-zeile > .auto-messwert { flex: 1 1 100%; width: auto; } } /* 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
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 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 und damit ausserhalb der Akkugruppe - deshalb haengt --akku-farbe am 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 , 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 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 Poppins, system-ui, sans-serif; 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; }