Einstellungsseite, Skoda-Seite und Karte

Sammelstand der offenen Arbeit im Dashboard.

Einstellungen: eine Eingabemaske fuer die Energiepreise unter
restricted/settings.php, dazu costs.php als Modell und ajax/settings.php
als Endpunkt. Die Preise stehen jetzt in gridCosts, gasCosts und fuelCosts
statt fest im Quelltext von getStats.php; die Jahresstatistik rechnet ueber
LEAD() den jeweils gueltigen Zeitraum aus und schlaegt den anteiligen
Grundpreis auf. Eingegeben wird in gewohnten Einheiten - l/100 km,
kWh/100 km, Euro je Liter -, umgerechnet wird beim Speichern.
solarLog_costs.sql beschreibt die Umstellung der Tabellen.

Skoda: eigene Seite mit Live-Werten ueber MQTT und Historie ueber
ajax/skoda.php, Kommandos ueber ajax/skodaCmd.php.

Karte: Leaflet mit einem eigenen Kachel-Zwischenspeicher (ajax/tile.php),
dessen Ablage unter tiles/ nicht ins Repository gehoert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 21:21:27 +02:00
co-authored by Claude Opus 5
parent c9a9e16b21
commit d285ec27a0
26 changed files with 4862 additions and 190 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

+661
View File
@@ -0,0 +1,661 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
+531
View File
@@ -375,3 +375,534 @@ input[type='range']::-webkit-slider-runnable-track {
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 drei
Bedeutungsfarben stehen fuer sich: bernstein fuer "offen", gelb fuer
eingeschaltetes Licht, gruen fuer Laden.
========================================================================== */
.skoda-auto,
.skoda-teile,
.skoda-karte {
--skoda-offen: #e0904f;
--skoda-licht: #f2d16b;
--skoda-laden: #4caf7d;
--skoda-blech: 128, 138, 148;
}
/* --- 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: 215px;
height: auto;
}
.skoda-auto .karosserie {
fill: rgba(var(--skoda-blech), 0.17);
stroke: rgba(var(--skoda-blech), 0.55);
stroke-width: 2;
}
.skoda-auto .rad rect { fill: rgba(18, 20, 24, 0.9); }
.skoda-auto .spiegel rect { fill: rgba(var(--skoda-blech), 0.55); }
.skoda-auto .reling rect { fill: rgba(var(--skoda-blech), 0.62); }
.skoda-auto .spoiler { fill: rgba(var(--skoda-blech), 0.5); }
.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. */
.skoda-auto .techdeck {
fill: rgba(12, 14, 18, 0.92);
stroke: rgba(var(--skoda-blech), 0.4);
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); }
/* 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));
}
.skoda-auto .teil-stecker > path { fill: none; stroke-width: 2; }
.skoda-auto .teil-stecker.laedt > circle {
fill: var(--skoda-laden);
stroke: var(--skoda-laden);
animation: skodaPuls 1.8s ease-in-out infinite;
}
.skoda-auto .teil-stecker.laedt > path { stroke: #0d1210; }
@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 > circle { animation: none; }
}
/* --- Zustandszeile unter dem Fahrzeug ------------------------------------ */
.skoda-teile {
display: flex;
flex-wrap: wrap;
gap: .35rem;
justify-content: center;
margin-top: .9rem;
}
.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;
}
.skoda-teile .chip.offen { border-color: var(--skoda-offen); color: var(--skoda-offen); }
.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); }
/* --- Ladestandsring ------------------------------------------------------ */
.skoda-ring { width: 100%; max-width: 190px; height: auto; }
.skoda-ring .ring-bahn {
fill: none;
stroke: rgba(128, 138, 148, 0.22);
stroke-width: 16;
}
.skoda-ring .ring-wert {
fill: none;
stroke: #4caf7d;
stroke-width: 16;
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: 100px 100px;
transition: stroke-dashoffset .6s ease, stroke .3s;
}
.skoda-ring .ring-ziel {
stroke: var(--bs-body-color, #e8ede9);
stroke-width: 3;
opacity: .75;
transform-origin: 100px 100px;
}
.skoda-ring .ring-zahl {
fill: var(--bs-body-color, #e8ede9);
font-size: 46px;
font-weight: 600;
text-anchor: middle;
font-variant-numeric: tabular-nums;
}
.skoda-ring .ring-einheit {
fill: var(--bs-secondary-color, #9aa0a6);
font-size: 14px;
letter-spacing: .12em;
text-anchor: middle;
text-transform: uppercase;
}
/* --- Wertetabelle -------------------------------------------------------- */
.skoda-werte {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
gap: .75rem 1.25rem;
}
.skoda-wert { display: flex; flex-direction: column; }
.skoda-wert .k {
font-size: .7rem;
letter-spacing: .09em;
text-transform: uppercase;
color: var(--bs-secondary-color, #9aa0a6);
}
.skoda-wert .v {
font-size: 1.15rem;
font-variant-numeric: tabular-nums;
}
/* --- Herkunft des Ladestroms --------------------------------------------- */
.skoda-balken {
display: flex;
height: .7rem;
border-radius: .35rem;
overflow: hidden;
background: rgba(128, 138, 148, 0.2);
}
.skoda-balken > div { transition: width .5s ease; }
.skoda-balken .teil-pv { background: #f0b429; }
.skoda-balken .teil-netz { background: #7a8791; }
/* --- 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;
}
.skoda-auto .steuer .flaeche {
fill: rgba(75, 143, 214, .22);
stroke: var(--bs-primary, #4b8fd6);
stroke-width: 1.5;
}
.skoda-auto .steuer .glyphe {
fill: none;
stroke: var(--bs-primary, #4b8fd6);
stroke-width: 1.8;
stroke-linecap: round;
}
@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; }