Tankstelle zweizeilig und antippbar, Belegkarte ohne toten Knopf (2026.9.4.5)

Sieben Meldungen des Eigentuemers, beide Codebasen.

Karte im Einzelbeleg: "Position laedt nicht" und "der Button funktioniert
nicht" waren dieselbe Stelle - ohne aufloesbaren Ort malte das Panel einen
beliebigen Ausschnitt und liess den Zentrieren-Knopf fuer immer disabled.
Beide zeigen jetzt die Auskunft statt einer Karte und unterscheiden dabei
"steht nicht im Beleg" von "findet niemand". Dazu ein echter Paritaetsmangel:
die App schlug nur station_address nach, das Panel faellt seit jeher auf den
Namen zurueck - von 16 Testbelegen tragen vier eine Anschrift.

Der Suchbegriff darf Name und Anschrift NICHT zusammensetzen: gemessen findet
Nominatim "Pascalstr. 8, 85057 Ingolstadt", mit dem Betreibernamen davor
nichts. Jetzt zwei Kandidaten nacheinander (Anschrift, dann Name); fuer den
Kartendienst dagegen beides zusammen, weil Apple und Google Betriebsnamen
kennen.

Tankstelle steht ueber die volle Kachelbreite, zweizeilig (Tankstelle und
Strasse / Ort) und fuehrt beim Tippen in den Standard-Kartendienst - Apple
Karten auf Apple-Geraeten, sonst Google Maps. Dieselbe Wahl jetzt auch fuer
Fahrzeugstandort und Autohaus.

SmartDeal-Ersparnis brach um, weil die dt-Spalte 150,00px breit war und ihr
Inhalt 150,20px - zwei Zehntel Pixel, Zeile dadurch 44,8 statt 26,4px hoch.

Der Regler "Fahrt beenden" schreibt nicht mehr beim Loslassen: derselbe Wert
stellt den Schlaf-Timeout des Dongles, das soll bewusst ausgeloest werden.
Speichern/Verwerfen erscheinen nur bei echter Aenderung.

Share-Erweiterung: an der ausgelieferten .ipa gemessen trug sie 1.0, die App
2026.9.3.3 - iOS registriert eine Erweiterung mit abweichender Versionsnummer
nicht, sie fehlt dann im Teilen-Blatt. ios-signieren.sh schreibt die Version
jetzt in beide Ziele und liest die fertige .ipa gegen. Zwischenablage: dritter
Weg ueber UIPasteboard.itemProviders, und ein fehlender nativer Teil wird als
solcher gemeldet statt als "Kein Zugriff".

Am Geraet gesetzt: 1003 Network Ping Timeout 0 -> 60 s, 1004 ACK Type
TCP/IP -> AVL. Beide liegen bei flespi als pending bereit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 10:10:55 +02:00
parent 7f386d19ea
commit 43f25e2e0d
17 changed files with 1000 additions and 60 deletions
@@ -1 +1 @@
{"version":"2026.9.4.1","sha256":"473807fbe06bc94882a3b53fa76d167a64a15d9470c0a6f993c7042a8bf009b6","bytes":270085,"gebaut":"2026-09-04T00:09:13Z"}
{"version":"2026.9.4.5","sha256":"ffea00a94ac842d8b6b233ebd91a1a97252c4b7b6a952c336ca7a6218eceef1e","bytes":270888,"gebaut":"2026-09-04T07:57:27Z"}
@@ -644,6 +644,11 @@ function ics(titel, datum, ort, notiz) {
let MAP = null, TILE = null, LEAFLET_LADEND = null;
// Zielpunkt des Zentrieren-Knopfs im Einzelbeleg (siehe mapBoxMitZentrieren).
let BELEG_STATION_POS = null;
/* Merker, ob zu EINEM Beleg überhaupt eine Position zu finden war. Der Abruf
ist asynchron, die Ansicht aber schon gezeichnet - ohne diesen Merker
stünde bei einem erfolglosen Nachschlag dauerhaft eine Karte ohne Nadel da
(samt Knopf, der nie scharf wird). `{ id, zustand: "keine" }`, sonst null. */
let BELEG_KARTE = null;
/* Bewusst themeunabhaengig hell: Kartendienste (Google Maps & Co.) zeigen die
Standard-Strassenkarte immer im hellen Stil, weil Strassen, Beschriftungen
und Wegfarben darauf lesbar bleiben. Die frueher bei Nacht eingeblendeten
@@ -843,18 +848,29 @@ async function initMap() {
} else if (route.name === "fill") {
BELEG_STATION_POS = null; // gehört zum vorherigen Beleg, sonst zentriert der Knopf falsch
const f = FILLS.find((x) => x.tank_id === route.id);
const suche = f ? tankstelleSuchtext(f.station_name, f.station_address) : "";
if (f && f.station_lat != null) {
tankstelleAufKarteZeigen(MAP, { lat: f.station_lat, lon: f.station_lon }, f);
} else if (f && (f.station_address || f.station_name)) {
/* Ohne Koordinaten die Anschrift vom Beleg nachschlagen. Bis die Antwort
da ist, bleibt der neutrale Ausschnitt stehen - und die Karte könnte
inzwischen zu einer anderen Ansicht gehören, deshalb vor dem Zeichnen
prüfen, ob wir noch auf demselben Beleg sind. */
} else if (suche) {
/* Ohne Koordinaten die Anschrift vom Beleg nachschlagen - und wenn der
Beleg keine trägt, den Namen (dieselbe Reihenfolge wie in der App,
tankstelleSuchtext). Bis die Antwort da ist, bleibt der neutrale
Ausschnitt stehen - und die Karte könnte inzwischen zu einer anderen
Ansicht gehören, deshalb vor dem Zeichnen prüfen, ob wir noch auf
demselben Beleg sind. */
MAP.setView(kartenMittelpunkt(), 11);
const karte = MAP, belegId = route.id;
adresseAufloesen(f.station_address || f.station_name).then((pos) => {
if (!pos || MAP !== karte || route.name !== "fill" || route.id !== belegId) return;
tankstelleAufKarteZeigen(karte, pos, f);
tankstellePosition(f.station_name, f.station_address).then((pos) => {
if (MAP !== karte || route.name !== "fill" || route.id !== belegId) return;
if (pos) return tankstelleAufKarteZeigen(karte, pos, f);
/* Kein Treffer: statt eines beliebigen Ausschnitts mit totem
Zentrieren-Knopf die ehrliche Auskunft. Genau die beiden Befunde
des Eigentümers vom 04.09.2026 - "Map lädt die Position der
Tankstelle nicht" und "der Button auf der Map funktioniert nicht"
waren dieselbe Stelle: eine Karte ohne Nadel und ein Knopf, der
nie scharf wurde. */
BELEG_KARTE = { id: belegId, zustand: "keine" };
render();
});
} else {
MAP.setView(kartenMittelpunkt(), 11);
@@ -1210,6 +1226,81 @@ async function adresseAufloesen(adresse) {
return treffer;
}
/* ------------------------------------------------------- Tankstelle
Anzeige in zwei Zeilen und der Weg in den Kartendienst des Geraets.
Wortgleich mit companion-app/src/daten/tankstelle.ts - dort steht die
ausfuehrliche Begruendung.
Kurz: die Anschrift kommt vom Belegparser als "<Strasse>, <PLZ Ort>";
geteilt wird am LETZTEN Komma, damit ein Zusatz bei der Strasse bleibt.
Erste Zeile Tankstelle und Strasse, zweite Zeile der Ort (Vorgabe des
Eigentuemers, 04.09.2026). Ohne Komma bleibt alles in der ersten Zeile -
lieber ein langer erster Eintrag als ein erfundener Ort. */
function adresseTeilen(adresse) {
const roh = (adresse || "").trim();
if (!roh) return { strasse: "", ort: "" };
const komma = roh.lastIndexOf(",");
if (komma < 0) return { strasse: roh, ort: "" };
return { strasse: roh.slice(0, komma).trim(), ort: roh.slice(komma + 1).trim() };
}
function tankstelleZeilen(name, adresse) {
const { strasse, ort } = adresseTeilen(adresse);
const oben = [(name || "").trim(), strasse].filter(Boolean).join(", ");
return { oben: oben || "unbekannt", unten: ort };
}
/* Suchbegriffe fuer die Geokodierung, in der Reihenfolge des Versuchs: erst
die Anschrift, dann der Name. NICHT beides zusammen - am 04.09.2026
gemessen: "Pascalstr. 8, 85057 Ingolstadt" findet Nominatim, mit dem
Betreibernamen davor nicht. Der Name ALLEIN ist trotzdem wertvoll
("Shell, SOELDEN" findet es), deshalb der zweite Versuch. */
function tankstelleKandidaten(name, adresse) {
return [(adresse || "").trim(), (name || "").trim()].filter(Boolean);
}
/** Der erste Suchbegriff - leer heisst: der Beleg nennt gar nichts. */
function tankstelleSuchtext(name, adresse) {
return tankstelleKandidaten(name, adresse)[0] || "";
}
/* Ziel fuer den Kartendienst. Hier gehoeren Name UND Anschrift zusammen:
Apple Karten und Google Maps kennen Betriebsnamen und treffen damit die
Zapfsaeule statt des Nachbargebaeudes. */
function tankstelleZiel(name, adresse) {
return [(name || "").trim(), (adresse || "").trim()].filter(Boolean).join(", ");
}
/* Die Kandidaten der Reihe nach nachschlagen, erster Treffer gewinnt. */
async function tankstellePosition(name, adresse) {
for (const begriff of tankstelleKandidaten(name, adresse)) {
const pos = await adresseAufloesen(begriff);
if (pos) return pos;
}
return null;
}
/** Apple-Geraet? Dort ist Karten der Standarddienst. */
function apfelgeraet() {
const kennung = `${navigator.userAgent || ""} ${navigator.platform || ""}`;
return /iPhone|iPad|iPod|Macintosh|Mac OS X/i.test(kennung);
}
function kartendienstUrl(name, adresse, pos) {
const beschriftung = (name || "").trim();
const ziel = pos ? `${pos.lat},${pos.lon}` : tankstelleZiel(name, adresse);
if (!ziel) return null;
if (apfelgeraet()) {
const q = pos && beschriftung ? `&q=${encodeURIComponent(beschriftung)}` : "";
return `https://maps.apple.com/?daddr=${encodeURIComponent(ziel)}${q}`;
}
return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(ziel)}`;
}
function kartendienstUrlZuPunkt(pos) {
return pos ? kartendienstUrl(null, null, pos) : null;
}
/* Setzt Nadel und Ausschnitt, sobald die Koordinaten da sind. Getrennt von
initMap(), weil der Abruf asynchron ist und die Karte bis dahin schon
steht. */
@@ -3107,13 +3198,24 @@ function vEinst() {
Dieselben Felder gibt es unter Einstellungen Geräte &amp; Dienste Audi Dashboard
Konfigurieren.</span>
</div>` : ""}
<div class="tile"><span class="label">Fahrt beenden</span>
${(() => {
/* Der Regler schreibt NICHT mehr beim Loslassen. Sein Wert stellt zugleich
den Schlaf-Timeout des Dongles - eine Änderung am Fahrzeug, die über zwei
Netze geht; die soll bewusst ausgelöst werden und nicht als Nebenwirkung
einer Wischgeste entstehen (Vorgabe des Eigentümers, 04.09.2026). Der
Knopf erscheint erst, wenn der Regler wirklich woanders steht - ein
dauerhaft sichtbarer, meist wirkungsloser Knopf wäre Beiwerk. */
const minuten = PAUSENZEIT_ENTWURF ?? CONFIG.pausenzeit;
const geaendert = PAUSENZEIT_ENTWURF !== null && PAUSENZEIT_ENTWURF !== CONFIG.pausenzeit;
return `<div class="tile"><span class="label">Fahrt beenden</span>
<div class="schieber" style="margin-top:14px">
<input type="range" min="1" max="60" step="1" value="${CONFIG.pausenzeit}"
style="--fuell:${((CONFIG.pausenzeit - 1) / 59) * 100}%" data-pausenzeit
<input type="range" min="1" max="60" step="1" value="${minuten}"
style="--fuell:${((minuten - 1) / 59) * 100}%" data-pausenzeit
aria-label="Minuten bis zum automatischen Fahrtende">
<output>${CONFIG.pausenzeit} Min.</output>
<output>${minuten} Min.</output>
</div>
${geaendert ? `<button class="aktion primaer" style="margin-top:14px" data-pausenzeitspeichern>Speichern</button>
<button class="aktion" data-pausenzeitverwerfen>Verwerfen</button>` : ""}
<span class="label" style="margin-top:14px;color:var(--fg2);letter-spacing:0;
text-transform:none;font-size:12.5px;line-height:1.6">
Hier können Sie einstellen, wie viele Minuten nach Ausschalten der Zündung die
@@ -3121,7 +3223,8 @@ function vEinst() {
des Dongles: so lange bleibt er nach dem Abstellen wach und wartet darauf,
dass es gleich weitergeht. Danach schläft er das spart Strom, kostet beim
Fortsetzen aber ein paar Sekunden fürs Aufwachen.</span>
</div>
</div>`;
})()}
${DONGLE && DONGLE.aktiv ? `<div class="tile"><span class="label">Dongle</span>
${DONGLE.stand && DONGLE.stand.fehler
? `<span class="label" style="margin-top:8px;color:var(--bad);letter-spacing:0;
@@ -3296,6 +3399,11 @@ function vEinst() {
let nmOffen = false;
let einrichtenOffen = false;
/* Entwurf des Reglers "Fahrt beenden" (Minuten) - null heisst: nicht angefasst.
Er liegt bewusst NEBEN CONFIG.pausenzeit und nicht darin: erst der Knopf
"Speichern" uebernimmt ihn, weil derselbe Wert den Schlaf-Timeout des
Dongles stellt. Siehe die Kachel in vEinst(). */
let PAUSENZEIT_ENTWURF = null;
let bildMenuOffen = null;
let smartdealPopupOffen = false;
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
@@ -4403,6 +4511,48 @@ function vFuel() {
${vFuelFormular()}`;
}
/* Karte des Einzelbelegs - oder die Auskunft, dass es zu dieser Tankstelle
keine Position gibt. Welches von beidem, entscheidet BELEG_KARTE: den
Merker setzt initMap(), sobald der Nachschlag erfolglos zurueckkommt.
Deckungsgleich mit TankstellenKarte.tsx in der App, samt der Unterscheidung
zwischen "steht nicht im Beleg" und "findet niemand". */
function belegKarte(f) {
const suche = tankstelleSuchtext(f.station_name, f.station_address);
const ohne =
!suche || (BELEG_KARTE && BELEG_KARTE.id === f.tank_id && BELEG_KARTE.zustand === "keine");
if (!ohne) return mapBoxMitZentrieren();
return `<div class="tile"><div class="leer">
<strong>Keine Position zur Tankstelle</strong>
<p>${
suche
? "Zu dieser Anschrift ließ sich kein Kartenpunkt finden. Tragen Sie im Beleg unter „Werte bearbeiten“ eine genauere Anschrift ein, dann erscheint die Tankstelle hier."
: "Dieser Beleg nennt weder Tankstelle noch Anschrift. Tragen Sie sie unter „Werte bearbeiten“ nach, dann erscheint die Tankstelle hier."
}</p>
</div></div>`;
}
/* Tankstelle des Belegs: eigene Zeile ueber die volle Kachelbreite statt der
engen Wertespalte, zweizeilig und antippbar.
Vorher eine gewoehnliche .row: Name rechts in der Wertespalte (rows-fest,
also 120px), die ganze Anschrift als Kleintext darunter - "Hermann Mogler
Mineraloelg. GmbH, Heidenheimerstr.6, 89551 Koenigsbronn" stand dort
vier- bis fuenfzeilig. Der Tipp fuehrt jetzt in den Kartendienst des
Geraets; ohne Name und ohne Anschrift bleibt es eine stumme Zeile, denn
ein Verweis, der nirgendwohin fuehrt, waere schlechter als keiner. */
function tankstellenZeile(f) {
const { oben, unten } = tankstelleZeilen(f.station_name, f.station_address);
const url = kartendienstUrl(f.station_name, f.station_address);
const inhalt =
`<span class="tankstellenzeile-ort">${esc(oben)}${unten ? `<small>${esc(unten)}</small>` : ""}</span>` +
(url ? `<svg class="chev" viewBox="0 0 6 10" aria-hidden="true"><polyline points="1,1 5,5 1,9"/></svg>` : "");
return `<div class="row tankstellenzeile"><dt>Tankstelle</dt><dd>${
url
? `<a href="${esc(url)}" target="_blank" rel="noopener" aria-label="${esc(oben)} in Karten öffnen">${inhalt}</a>`
: `<span>${inhalt}</span>`
}</dd></div>`;
}
function vFill(id) {
const f = FILLS.find((x) => x.tank_id === id);
if (!f) return `<div class="tile">Tankvorgang nicht gefunden</div>`;
@@ -4420,7 +4570,7 @@ function vFill(id) {
}
return `
${mapBoxMitZentrieren()}
${belegKarte(f)}
<div class="tile flat" style="margin:18px 0 14px">
<span class="label">Getankt</span>
<div class="fig" style="font-size:52px;margin-top:10px">${f.liters != null ? de(f.liters, 2) : ""}<span class="unit">l</span></div>
@@ -4433,8 +4583,8 @@ function vFill(id) {
<div class="row"><dt>Getankte Liter</dt><dd>${f.liters != null ? de(f.liters, 2) + " l" : ""}</dd></div>
<div class="row"><dt>Kosten</dt><dd>${f.fuel_total_eur != null ? eur(f.fuel_total_eur) + " " : ""}<small>real, nach Rabatt</small></dd></div>
<div class="row"><dt>Preis je Liter</dt><dd>${berechneterPreis != null ? de(berechneterPreis, 2) + " " : ""}<small>Kosten ÷ Liter</small></dd></div>
${CAR.smartdeal.aktiv && f.discount != null ? `<div class="row"><dt><span class="marke">${markenlogo()} SmartDeal-Ersparnis</span></dt><dd>${eur(f.discount)} €</dd></div>` : ""}
<div class="row" style="border-bottom:0"><dt>Tankstelle</dt><dd>${esc(f.station_name || "unbekannt")}<small>${esc(f.station_address || "")}</small></dd></div>
${CAR.smartdeal.aktiv && f.discount != null ? `<div class="row row-marke"><dt><span class="marke">${markenlogo()} SmartDeal-Ersparnis</span></dt><dd>${eur(f.discount)} €</dd></div>` : ""}
${tankstellenZeile(f)}
</dl>
${belegFehler && belegFehler.context === f.tank_id ? `<span class="label" style="margin-top:14px;color:var(--red)">${esc(belegFehler.meldung)}</span>` : ""}
<button class="aktion" data-tankbearbeiten>Werte bearbeiten</button>
@@ -5140,6 +5290,10 @@ const go = (name, id) => {
// no-op, falls die Daten längst da sind (datenLaden() rendert dann
// ohnehin nichts doppelt Sichtbares).
if (!DATEN_GELADEN) datenLaden(false);
// Der Merker "zu diesem Beleg gibt es keine Position" gilt nur so lange, wie
// man auf ihm steht: nach einer Korrektur der Anschrift soll erneut gesucht
// werden, nicht die alte Absage stehen bleiben.
BELEG_KARTE = null;
// Ein unbestätigter Servicebuch-Entwurf (siehe data-sbneu) verlässt die
// Anwendung nie ungespeichert - wird die Eintragsseite ohne Klick auf
// "Speichern" verlassen, verwerfen wir ihn hier statt ihn liegen zu lassen
@@ -5838,6 +5992,9 @@ function ereignisseVerdrahten() {
distanz: w.distanz, kraftstoff: w.kraftstoff,
receipt_key: beleg ? beleg.receipt_key : null, receipt_file: beleg ? beleg.receipt_file : null,
});
// Die Anschrift kann sich gerade geändert haben - dann soll die Karte
// erneut suchen statt bei der alten Absage zu bleiben (siehe BELEG_KARTE).
BELEG_KARTE = null;
tankBearbeitenOffen = false; belegEntwurf = null; belegFehler = null; render(); return;
}
const belegBtn = e.target.closest("[data-beleg]");
@@ -5988,6 +6145,15 @@ function ereignisseVerdrahten() {
}
return;
}
/* Regler "Fahrt beenden": erst hier wird geschrieben. profilSpeichern()
stellt ueber denselben Weg auch den Schlaf-Timeout des Dongles - genau
deshalb hat der Regler einen eigenen Knopf bekommen. */
if (e.target.closest("[data-pausenzeitspeichern]")) {
if (PAUSENZEIT_ENTWURF !== null) CONFIG.pausenzeit = PAUSENZEIT_ENTWURF;
PAUSENZEIT_ENTWURF = null;
profilSpeichern(); render(); return;
}
if (e.target.closest("[data-pausenzeitverwerfen]")) { PAUSENZEIT_ENTWURF = null; render(); return; }
const satz = e.target.closest("[data-satz]");
if (satz) { serviceRufen("reifen_wechseln", { satz: satz.dataset.satz.toLowerCase() }); CAR.reifen.aktiv = satz.dataset.satz; render(); return; }
const rfArch = e.target.closest("[data-reifenarchivieren]");
@@ -6106,12 +6272,12 @@ function ereignisseVerdrahten() {
ROOT.addEventListener("input", (e) => {
if (e.target.dataset.pausenzeit !== undefined) {
// Beim Ziehen nur die Anzeige und die Fuellung nachziehen, NICHT
// render() und nicht speichern: ein Re-Render mitten in der Geste
// erzeugt den Regler neu und der Daumen springt aus der Hand.
// Gespeichert wird beim Loslassen (change-Zweig weiter unten) -
// dieselbe Trennung wie beim Suchfeld direkt darunter.
// render(): ein Re-Render mitten in der Geste erzeugt den Regler neu und
// der Daumen springt aus der Hand. Dieselbe Trennung wie beim Suchfeld
// direkt darunter. Gespeichert wird ueberhaupt nicht mehr hier, sondern
// erst auf Knopfdruck (data-pausenzeitspeichern).
const minuten = parseInt(e.target.value, 10);
CONFIG.pausenzeit = minuten;
PAUSENZEIT_ENTWURF = minuten;
e.target.style.setProperty("--fuell", (minuten / 60) * 100 + "%");
const feld = e.target.parentElement.querySelector("output");
if (feld) feld.textContent = minuten === 0 ? "sofort" : minuten + " Min.";
@@ -6316,7 +6482,9 @@ function ereignisseVerdrahten() {
if (e.target.dataset.oelm !== undefined) { CAR.oel.monate = parseInt(e.target.value, 10); profilSpeichern(); return; }
if (e.target.dataset.setupNurpassend !== undefined) { setupNurPassend = e.target.checked; render(); return; }
if (e.target.dataset.startbild !== undefined) { CONFIG.startbild = e.target.value; profilSpeichern(); render(); return; }
if (e.target.dataset.pausenzeit !== undefined) { CONFIG.pausenzeit = parseInt(e.target.value, 10); profilSpeichern(); return; }
// Loslassen: nur den Entwurf festhalten und neu zeichnen, damit der
// Speichern-Knopf erscheint. Geschrieben wird erst dort.
if (e.target.dataset.pausenzeit !== undefined) { PAUSENZEIT_ENTWURF = parseInt(e.target.value, 10); render(); return; }
if (e.target.dataset.svsel !== undefined) { svAuswahl = parseInt(e.target.value, 10); render(); return; }
});
}
@@ -427,6 +427,29 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.row dd { margin: 0; font-size: 14px; color: var(--fg); text-align: right; }
.row dd small { display: block; font-size: 12.5px; color: var(--fg3); margin-top: 3px; }
.rows-fest dd { min-width: 120px; }
/* Tankstelle im Einzelbeleg (Vorgabe des Eigentuemers, 04.09.2026).
Vorher eine gewoehnliche Zeile: Name rechts in der 120px-Wertespalte, die
ganze Anschrift als Kleintext darunter - "Hermann Mogler Mineraloelg. GmbH,
Heidenheimerstr.6, 89551 Koenigsbronn" stand dort vier- bis fuenfzeilig.
Hier bekommt sie die volle Kachelbreite unter der Beschriftung, zwei Zeilen
(Tankstelle und Strasse / Ort), und fuehrt beim Tippen in den Kartendienst -
deshalb der Chevron rechts. Deckungsgleich mit .dm-tankstellenzeile in der
App. */
.row.tankstellenzeile { flex-direction: column; align-items: stretch; gap: 4px; border-bottom: 0; }
/* flex:0 0 auto hebt .rows-fest dt{flex:0 0 150px} auf: in einer Spalte
ist die Flex-Basis die HOEHE - der Titel waere sonst 150px hoch. */
.row.tankstellenzeile dt { font-size: 13px; color: var(--fg2); flex: 0 0 auto; }
.row.tankstellenzeile dd { min-width: 0; text-align: left; flex: 0 0 auto; }
.row.tankstellenzeile dd > a,
.row.tankstellenzeile dd > span {
display: flex; align-items: center; justify-content: space-between;
gap: var(--sp-3); color: inherit; text-decoration: none;
}
.row.tankstellenzeile dd > a:active { opacity: .6; }
.tankstellenzeile-ort { display: block; font-size: 14px; color: var(--fg); line-height: 1.35; min-width: 0; }
.tankstellenzeile-ort small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--fg3); }
/* .row-fussnote ist mit dem Umbau der Service-Seite auf .termingruppe
entfallen (2026-08-18) - dort gibt es keine dt/dd-Spalten mehr, die um
Platz konkurrieren. Die beiden Regeln dafuer standen seither ohne
@@ -695,7 +718,16 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
.dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--line-strong); }
.dots i.on { background: var(--red); }
.marke { display: inline-flex; align-items: center; gap: 6px; }
/* white-space:nowrap: ohne die brach "SmartDeal-Ersparnis" neben dem
Markenlogo um. Kein Platzmangel, sondern ein Rundungsfehler - am
Einzelbeleg gemessen (04.09.2026, 375px breit): die dt-Spalte war 150,00px
breit, ihr eigener Inhalt 150,20px, also rutschte der Text hinter dem Logo
in eine zweite Zeile und die Zeile wurde 44,8px statt 26,4px hoch. Die
Zeile hat reichlich Platz (307px fuer 150 + 16 + 55), sie darf ihn nur nicht
abgeschnitten bekommen - dafuer gibt die Wertespalte in genau dieser Zeile
ihre feste Mindestbreite auf (.row-marke). */
.marke { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.rows-fest .row-marke dd { min-width: 0; }
.marke-logo { display: inline-flex; align-items: center; }
/* 19,2px = 16px + 20%. Der Eigentuemer wollte das Markenlogo in JEDER
Darstellung um ein Fuenftel groesser; es steht ueberall neben Text der
+13 -4
View File
@@ -1,14 +1,23 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.4.1",
"version": "2026.9.4.5",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": ["@paul"],
"codeowners": [
"@paul"
],
"config_flow": true,
"integration_type": "service",
"single_config_entry": true,
"iot_class": "local_polling",
"dependencies": ["http", "frontend", "panel_custom", "recorder"],
"requirements": ["pypdf>=4.0.0"]
"dependencies": [
"http",
"frontend",
"panel_custom",
"recorder"
],
"requirements": [
"pypdf>=4.0.0"
]
}