Paritaetsrunden 2 und 3, Designpruefung umgesetzt (2026.8.30.8)

Drei zusammenhaengende Runden, alle live bei 375x812 gegen audi_ha_test
geprueft und in beiden Codebasen angewandt.

Paritaetsrunde 2: der gemeinsame Rahmen war das eigentliche Problem -
Seitenrand, Kachelabstaende, Kopfleiste, Tableiste und vier Bausteine des
Design-Systems trugen noch den Stand vor der iOS-Entscheidung. Dazu sieben
Bildschirme neu aufgebaut. Zwei Panel-Fehler dabei mitbehoben: teaser()
zeigte unter "Letzte Fahrt" den aeltesten Datensatz, und "Daten bearbeiten"
war ein toter Knopf.

Paritaetsrunde 3: das Panel hat nie Audi Type gerendert. @font-face in einem
Shadow Root wird ignoriert - Schriftschnitte registriert der Browser pro
Dokument, nie pro Shadow Tree. Die drei Schnitte liegen jetzt in
audi-dashboard-schriften.css und werden ins Dokument gehaengt. Damit erledigt
sich eine ganze Reihe von "die Schrift sieht anders aus"-Eindruecken: die
beiden Anwendungen zeigten tatsaechlich verschiedene Schriften.
Ausserdem "Daten bearbeiten" im Panel gebaut und portiert, das Zeilenmenue
entfernt und die letzten neun Bildschirme angeglichen.

Designpruefung: die fuenf Punkte der Reihenfolge. Beim vierten hat das Messen
den Befund veraendert - gezaehlt waren die deklarierten Groessen, wirksam war
laengst eine saubere Sieben-Schritt-Skala mit 27 Ausreissern; die sind jetzt
auf den naechsten Schritt gezogen, keiner verschiebt sich um mehr als 1px.

Zuletzt: die Standortvorschau zeichnete die falsche Nadel (das Panel wechselt
den Icon-Satz ab 34px, die App nahm immer den grossen), und der Kopfabstand
der App ist auf den sicheren Bereich reduziert - die 56px des Panels liegen
dort unter der Kopfleiste von Home Assistant, in der nativen Huelle steht
darueber nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 18:31:41 +02:00
parent 25c0d073c4
commit 4a58021670
64 changed files with 4130 additions and 1432 deletions
@@ -1 +1 @@
{"version":"2026.8.30.2","sha256":"88922cb0e3a7603b6e4e2fe7daee85513ec9e16c4ae7b2e0229f514bde58915f","bytes":248241,"gebaut":"2026-08-30T04:29:47Z"}
{"version":"2026.8.30.8","sha256":"2754d426c7145d000fd2194d4dd85db096c2fcae008eb518584f189becfb0538","bytes":256098,"gebaut":"2026-08-30T16:30:51Z"}
@@ -71,6 +71,33 @@ const CSS_VERSION = (() => {
Integrationsordner würde sie das nächste Update mitlöschen. */
const STATIK = new URL(".", import.meta.url).href.replace(/\/$/, "");
/* Schriftschnitte MUESSEN im Dokument registriert werden, nicht im Shadow
Root: @font-face-Regeln innerhalb eines Schattenbaums ignoriert der Browser
grundsaetzlich, Schriftfamilien sind dokumentweit. Weil audi-dashboard.css
(mit den eingebetteten Schnitten am Anfang) hier immer nur an ROOT gehaengt
wurde, ist das Panel seit jeher auf Helvetica zurueckgefallen, obwohl
"Audi Type" ganz vorn in jedem font-family-Stapel steht - beim
Paritaetsabgleich 2026-08-30 dadurch aufgefallen, dass dieselbe
Zeichenkette im Panel exakt so breit rendert wie in Helvetica, waehrend die
companion-app mit derselben, bytegleichen Schriftdatei ~5 % schmaler setzt.
Die Schnitte liegen deshalb jetzt in audi-dashboard-schriften.css, und nur
diese Datei geht in document.head - sie enthaelt ausschliesslich
@font-face und beeinflusst die uebrige HA-Oberflaeche nicht.
Der Umkehrfall ist in diesem Projekt schon einmal passiert: das
Leaflet-Stylesheet lag in document.head und erreichte den Shadow Root nicht
(siehe AGENTS.md, "fragmentierte Leaflet-Darstellung"). Beide Faelle haben
dieselbe Ursache - Stylesheets wirken je Baum, Schriften nur im Dokument. */
const SCHRIFTEN_ID = "audi-dashboard-schriften";
function schriftenLaden() {
if (document.getElementById(SCHRIFTEN_ID)) return;
const link = document.createElement("link");
link.id = SCHRIFTEN_ID;
link.rel = "stylesheet";
link.href = `${STATIK}/audi-dashboard-schriften.css?v=${CSS_VERSION}`;
document.head.appendChild(link);
}
/* ------------------------------------------------- Namen des Backends
Die eine Stelle, an der die Entity-IDs und Dienstnamen stehen. Sie sind
ein Vertrag mit custom_components/audi_dashboard/const.py; die
@@ -1190,7 +1217,18 @@ function badge() {
function teaser() {
if (!TRIPS.length && !FILLS.length) return "";
const t = TRIPS[0], f = FILLS[0];
/* Bis 2026-08-30 stand hier TRIPS[0]/FILLS[0]. Beide Listen kommen vom
Backend aufsteigend sortiert (vTrips()/vFuel() geben sie unveraendert in
dieser Reihenfolge aus, aelteste Zeile zuerst) - Index 0 war damit der
AELTESTE Datensatz, obwohl die Kachel "Letzte Fahrt"/"Letzter
Tankvorgang" heisst. Beim Paritaetsabgleich mit der companion-app
aufgefallen, die hier von Anfang an nach Zeitstempel sortiert hat.
Jetzt beide gleich: der zeitlich juengste Datensatz. */
const juengster = (liste, feld) =>
liste.length
? liste.slice().sort((a, b) => new Date(b[feld]) - new Date(a[feld]))[0]
: null;
const t = juengster(TRIPS, "ts_start"), f = juengster(FILLS, "ts");
return `<div class="tile teaser"><span class="label">Zuletzt</span>
${t ? `<button class="leaf" style="padding-left:0" data-go="trip:${t.trip_id}">
<span class="k">Letzte Fahrt<small>${esc(t.start_address || "")} ${esc(t.end_address || "")}</small></span>
@@ -1429,7 +1467,10 @@ function vHome() {
/* C2/C5/D1: Reihenfolge der myAudi-App - randloses Fahrzeugbild, Name und
Status, große Reichweite mit farbigem Balken, dann Aktionskacheln. Die
Kacheln führen zu Ansichten statt zu Fernsteuerung, die das Panel bewusst
nicht kann. */
nicht kann.
"Zuletzt" steht seit der Designpruefung 2026-08-30 vor der Standortkarte:
zwei kurze, haeufig gelesene Textzeilen lagen bisher unter einer 190px
hohen Karte und damit auf dem Telefon unterhalb des ersten Bildschirms. */
return `
<div class="szene bleed ${szeneKlasse(startIndex())}"><div class="carbox">${carImg("fest")}</div></div>
<div class="tile flat" style="margin-bottom:20px">
@@ -1506,8 +1547,8 @@ function vHome() {
${kmZeile}
</div>`;
})()}
${standortKachel()}
${teaser()}`;
${teaser()}
${standortKachel()}`;
}
/* Kreis-Häkchen (ok) / Kreis-Kreuz (nicht ok) / Kreis-Fragezeichen (unbekannt)
@@ -1875,7 +1916,7 @@ function vBatterieverlauf() {
? `<span class="dot" style="background:${punktFarbe(soh.status)}"></span>${(soh.delta >= 0 ? "+" : "") + de(soh.delta, 2)} V`
: `<span style="color:var(--fg3)">-</span>`}</dd></div>
</dl>
<span style="display:block;margin-top:10px;color:var(--fg2);font-size:13.5px;line-height:1.6">${soh.text}</span>
<span style="display:block;margin-top:10px;color:var(--fg2);font-size:13px;line-height:1.6">${soh.text}</span>
</div>`;
}
@@ -2134,7 +2175,20 @@ function vService() {
</div>`;
}
/* "Daten bearbeiten" auf der Seite Identität und Technik. Der Knopf war bis
2026-08-30 ohne Funktion (kein data-go, kein Handler) - beim
Paritätsabgleich aufgefallen und auf Wunsch des Eigentümers jetzt umgesetzt.
Bearbeitet werden genau die Angaben, für die es sonst nirgends einen Editor
gibt: die Werte des technischen Datenblatts (CAR.technik) und die
Ausstattungslisten (CAR.ausstattung). Die Kopfdaten darüber - Modell,
Kennzeichen, Ausführung, Erstzulassung, FIN - bleiben bewusst außen vor:
die pflegt "Fahrzeug einrichten" in den Einstellungen, und zwei Eingabe-
stellen für dasselbe Feld laufen erfahrungsgemäß auseinander. */
let identBearbeitenOffen = false;
function vIdent() {
if (identBearbeitenOffen) return vIdentBearbeiten();
return `
<div class="tile"><span class="label">Fahrzeug</span>
<dl class="rows" style="margin-top:10px">
@@ -2157,7 +2211,51 @@ function vIdent() {
${g.sonder.map((x, i) => `<div class="row" ${i === g.sonder.length - 1 ? 'style="border-bottom:0"' : ""}>
<dt style="color:var(--fg)">${esc(x)}</dt><dd></dd></div>`).join("")}
</dl></div>`).join("")}
<button class="aktion">Daten bearbeiten</button>`;
<button class="aktion" data-identbearbeiten>Daten bearbeiten</button>`;
}
/* Bearbeitungsansicht: je Technikgruppe eine Kachel mit einem Eingabefeld je
Posten, je Ausstattungsgruppe eine Kachel mit einer Zeile je Position -
beide mit "×" zum Entfernen und "+ Position hinzufügen", genau im Zuschnitt
der Selbstbeteiligung in vVertragBearbeiten(). Gespeichert wird wie überall
sonst sofort je Feld über profilSpeichern(). */
function vIdentBearbeiten() {
const technik = CAR.technik || [];
const ausstattung = CAR.ausstattung || [];
return `
<div class="tile"><span class="label">Daten bearbeiten</span>
<span class="label" style="margin-top:10px">Hier stehen das technische Datenblatt und die
Ausstattung. Modell, Kennzeichen, Ausführung, Erstzulassung und Fahrgestellnummer pflegst du
unter Einstellungen Fahrzeug einrichten.</span>
</div>
${technik.map((g, gi) => `<div class="tile"><span class="label">${esc(g.gruppe)}</span>
${(g.posten || []).map((pp, i) => `
<div class="feld" ${i === g.posten.length - 1 ? 'style="border-bottom:0"' : ""}>
<label for="tk${gi}-${i}">${esc(pp[0])}</label>
<span style="display:inline-flex;align-items:center;gap:8px">
<input id="tk${gi}-${i}" value="${esc(pp[1] || "")}" placeholder="noch offen"
data-technik="${gi}|${i}">
<button class="zeile-loeschen" data-technikdel="${gi}|${i}"
aria-label="${esc(pp[0])} entfernen">×</button>
</span>
</div>`).join("")}
${!(g.posten || []).length ? `<span class="label" style="margin-top:8px">Keine Angaben</span>` : ""}
<button class="aktion" data-technikadd="${gi}">+ Position hinzufügen</button>
</div>`).join("")}
${ausstattung.map((g, gi) => `<div class="tile"><span class="label">${esc(g.gruppe)}</span>
${(g.sonder || []).map((x, i) => `
<div class="feld" ${i === g.sonder.length - 1 ? 'style="border-bottom:0"' : ""}>
<label for="as${gi}-${i}">Position ${i + 1}</label>
<span style="display:inline-flex;align-items:center;gap:8px">
<input id="as${gi}-${i}" value="${esc(x)}" data-ausstattung="${gi}|${i}">
<button class="zeile-loeschen" data-ausstattungdel="${gi}|${i}"
aria-label="Position ${i + 1} entfernen">×</button>
</span>
</div>`).join("")}
${!(g.sonder || []).length ? `<span class="label" style="margin-top:8px">Keine Ausstattung hinterlegt</span>` : ""}
<button class="aktion" data-ausstattungadd="${gi}">+ Position hinzufügen</button>
</div>`).join("")}
<button class="aktion primaer" data-identbearbeiten>Fertig</button>`;
}
function monatePlus(datum, monate) { const d = dat(datum); d.setMonth(d.getMonth() + monate); return d; }
@@ -2686,7 +2784,7 @@ function vEinst() {
if (laeuft === "installieren") return spinner("Installiere …");
if (laeuft === "neustart") return spinner("Home Assistant startet neu …");
if (u && u.installiert) {
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13px;line-height:1.6">
Version ${esc(u.version)} installiert - die neue Fassung wird erst nach einem Neustart
geladen.</span>
<button class="aktion" style="margin-top:14px" data-update="neustart">Installation abschließen - Jetzt neu starten</button>`;
@@ -2694,13 +2792,13 @@ function vEinst() {
if (laeuft === "pruefen") return spinner("Prüfe …");
if (u && u.verfuegbar) {
return `<dl class="rows"><div class="row" style="border-bottom:0"><dt>Verfügbare Version</dt><dd>${esc(u.version)}</dd></div></dl>
<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13px;line-height:1.6">
Lädt die neueste Fassung von Gitea und ersetzt den Integrationsordner. Home Assistant
muss danach neu gestartet werden. Fahrzeugprofil, Fahrten, Tankvorgänge und eigene
Fotos bleiben dabei unangetastet - die liegen außerhalb des Integrationsordners.</span>
<button class="aktion" style="margin-top:14px" data-update="installieren">Update installieren</button>`;
}
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13px;line-height:1.6">
${u ? `Integration ist aktuell${u.geprueft_am ? ` (geprüft ${dedat(new Date(u.geprueft_am))})` : ""}.` : "Noch nicht geprüft."}</span>
<button class="aktion" style="margin-top:14px" data-update="pruefen">Auf Update prüfen</button>`;
})()}
@@ -3171,6 +3269,15 @@ function vTripsFormular() {
<button class="aktion primaer" data-fahrtspeichern>Speichern</button></div>` : ""}
<button class="aktion ${fahrtOffen ? "" : "primaer"}" data-fahrt>${fahrtOffen ? "Abbrechen" : "Fahrt erfassen"}</button>`;
}
/* Jahre laufen absteigend, Monate und Zeilen liefen bis zur Designpruefung
2026-08-30 aufsteigend - in derselben Liste zwei Richtungen, und die
neueste Fahrt stand ganz unten, ausserhalb des sichtbaren Bereichs.
neuesteZuerst() dreht Zeilen und Monatsreihenfolge um; der zuerst
aufgeklappte Monat (mi === 0) ist damit automatisch der neueste. */
function neuesteZuerst(liste, feld) {
return liste.slice().sort((a, b) => new Date(b[feld]) - new Date(a[feld]));
}
function vTrips() {
const jahre = [...new Set(TRIPS.map((t) => new Date(t.ts_start).getFullYear()))].sort((a, b) => b - a);
/* D5: Leerzustand nennt die Ursache - die Fahrtenerkennung greift erst, wenn
@@ -3183,7 +3290,7 @@ function vTrips() {
${vTripsFormular()}`;
let h = `<div class="tile">`;
jahre.forEach((j, ji) => {
const tj = TRIPS.filter((t) => new Date(t.ts_start).getFullYear() === j);
const tj = neuesteZuerst(TRIPS.filter((t) => new Date(t.ts_start).getFullYear() === j), "ts_start");
h += `<button class="acc-h" aria-expanded="${ji === 0}" data-acc="y${j}"><span class="mark"></span>
<span class="k">${j}</span><span class="v">${de(sum(tj, (t) => t.distance_km || 0))} km<br>${tj.length} Fahrten</span></button>
<div class="acc-b ${ji === 0 ? "on" : ""}" id="y${j}">`;
@@ -3244,9 +3351,13 @@ function vTrip(id) {
function quad(t, v) { return `<span class="label" style="margin-top:20px">${t}</span>
<div class="quad">${v.map((x) => `<div><div class="n">${x[0]}</div><div class="l">${x[1]}</div></div>`).join("")}</div>`; }
/* Der Kachelkopf traegt seit der Designpruefung 2026-08-30 denselben
drehenden Pfeil wie die Aufklapp-Zeilen in Fahrten und Tanken. Vorher sah
die Statistik aus wie fuenf feste Anzeigen - dass sich dahinter die
gesamte Auswertung oeffnet, war an nichts zu erkennen. */
function statTile(id, label, fig, unit, extra, body) {
return `<div class="tile"><button class="tile-h" aria-expanded="false" data-acc="${id}">
<span class="label">${label}</span>
<span class="stat-kopfzeile"><span class="label">${label}</span><span class="mark"></span></span>
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin-top:12px">
<div class="fig" style="font-size:34px">${fig}<span class="unit">${unit}</span></div>
<span class="wert" style="padding-bottom:5px">${extra}</span></div>
@@ -3639,7 +3750,7 @@ function vFuel() {
${vFuelFormular()}`;
let h = `<div class="tile">`;
jahre.forEach((j, ji) => {
const fj = FILLS.filter((f) => new Date(f.ts).getFullYear() === j);
const fj = neuesteZuerst(FILLS.filter((f) => new Date(f.ts).getFullYear() === j), "ts");
const ersparnisJahr = sum(fj, (f) => f.discount || 0);
/* D2: Auf der Jahreszeile stehen nur die Kosten, groß. Preis und Menge
folgen als Zweitzeile, die Ersparnis als neutrale Pille - vorher
@@ -3649,7 +3760,7 @@ function vFuel() {
<span class="v" style="display:flex;flex-direction:column;align-items:flex-end;gap:5px">
<span style="font-size:17px;color:var(--fg)">${eur(sum(fj, (f) => f.fuel_total_eur || 0))} </span>
<span>${de(sum(fj, (f) => f.liters || 0), 1)} l · Ø ${de(schnittpreis(fj), 2)} /l</span>
${CAR.smartdeal.aktiv ? `<span class="marke"><span class="pill" style="font-size:11.5px;padding:4px 10px">${markenlogo()}<span style="margin-left:6px">${eur(ersparnisJahr)} €</span></span></span>` : ""}
${CAR.smartdeal.aktiv ? `<span class="marke"><span class="pill" style="font-size:12.5px;padding:4px 10px">${markenlogo()}<span style="margin-left:6px">${eur(ersparnisJahr)} €</span></span></span>` : ""}
</span></button>
<div class="acc-b ${ji === 0 ? "on" : ""}" id="fy${j}">`;
const monate = [...new Set(fj.map((f) => new Date(f.ts).toLocaleDateString("de-DE", { month: "long" })))];
@@ -4062,7 +4173,7 @@ function vDatensatzPopup() {
<input type="file" accept="${art === "fahrzeugprofil" ? "application/json" : "text/csv"}"
data-datensatzladen="${art}" hidden ${datensatzLaeuft ? "disabled" : ""}></label>`
).join("")}
${datensatzErgebnis ? `<div style="margin-top:14px;font-size:13.5px;line-height:1.6;color:var(--fg)">${esc(datensatzErgebnisText(datensatzErgebnis))}</div>` : ""}
${datensatzErgebnis ? `<div style="margin-top:14px;font-size:13px;line-height:1.6;color:var(--fg)">${esc(datensatzErgebnisText(datensatzErgebnis))}</div>` : ""}
${datensatzFehler ? `<span class="label" style="margin-top:10px;color:var(--red)">${esc(datensatzFehler)}</span>` : ""}
<button class="aktion primaer" style="margin-top:16px" data-datensatzzu>Fertig</button>
</div>`;
@@ -4423,6 +4534,9 @@ const go = (name, id) => {
// Ein offenes Bearbeiten-Formular gehört zu genau der Fahrt, auf der es
// geöffnet wurde - sonst begrüßt die nächste Fahrt einen mit Eingabefeldern.
fahrtBearbeitenOffen = false;
// Gleiches gilt für "Daten bearbeiten" auf Identität und Technik: wer die
// Seite verlässt, soll sie beim nächsten Mal wieder lesend vorfinden.
if (name !== "ident") identBearbeitenOffen = false;
/* B3: echter Verlauf statt statischer ZURUECK-Tabelle. Ein Tabwechsel setzt
den Stapel zurück, weil Tabs Wurzeln sind, keine Ebenen. */
if (istTab(name)) routeStack = [];
@@ -4433,6 +4547,7 @@ const go = (name, id) => {
function zurueck() {
sheet = null;
fahrtBearbeitenOffen = false;
identBearbeitenOffen = false;
const ziel = routeStack.pop() || { name: ZURUECK[route.name] || "home" };
navRichtung = "raus";
route = ziel; render();
@@ -5107,6 +5222,31 @@ function ereignisseVerdrahten() {
if (dsExport) { datensatzExportieren(dsExport.dataset.datensatzsichern); return; }
const del = e.target.closest("[data-loeschen]");
if (del) { delete CAR.service.vereinbart[del.dataset.loeschen]; profilSpeichern(); render(); return; }
/* Identität und Technik: Bearbeitungsansicht ein/aus, Positionen der
Technik- und Ausstattungsgruppen anlegen/entfernen. */
if (e.target.closest("[data-identbearbeiten]")) { identBearbeitenOffen = !identBearbeitenOffen; render(); return; }
const tkdel = e.target.closest("[data-technikdel]");
if (tkdel) {
const [gi, i] = tkdel.dataset.technikdel.split("|").map(Number);
CAR.technik[gi].posten.splice(i, 1); profilSpeichern(); render(); return;
}
const tkadd = e.target.closest("[data-technikadd]");
if (tkadd) {
const g = CAR.technik[+tkadd.dataset.technikadd];
(g.posten = g.posten || []).push(["Neue Angabe", ""]);
profilSpeichern(); render(); return;
}
const asdel = e.target.closest("[data-ausstattungdel]");
if (asdel) {
const [gi, i] = asdel.dataset.ausstattungdel.split("|").map(Number);
CAR.ausstattung[gi].sonder.splice(i, 1); profilSpeichern(); render(); return;
}
const asadd = e.target.closest("[data-ausstattungadd]");
if (asadd) {
const g = CAR.ausstattung[+asadd.dataset.ausstattungadd];
(g.sonder = g.sonder || []).push("");
profilSpeichern(); render(); return;
}
const vseldel = e.target.closest("[data-vselbstdel]");
if (vseldel) { CAR.versicherung.selbst.splice(+vseldel.dataset.vselbstdel, 1); profilSpeichern(); render(); return; }
if (e.target.closest("[data-vselbstadd]")) { CAR.versicherung.selbst.push(["", ""]); profilSpeichern(); render(); return; }
@@ -5409,6 +5549,21 @@ function ereignisseVerdrahten() {
if (vnotruf) { CAR.versicherung[vnotruf] = e.target.value; profilSpeichern(); render(); return; }
const vvertrag = e.target.dataset.vvertrag;
if (vvertrag) { CAR.versicherung[vvertrag] = e.target.value; profilSpeichern(); render(); return; }
/* Kein render() beim Tippen: der Cursor säße sonst nach jedem Zeichen
wieder am Feldanfang - dieselbe Begründung wie beim Setup-Suchfeld und
beim Tankpreisfeld weiter oben. */
const technik = e.target.dataset.technik;
if (technik) {
const [gi, i] = technik.split("|").map(Number);
CAR.technik[gi].posten[i][1] = e.target.value;
profilSpeichern(); return;
}
const ausstattung = e.target.dataset.ausstattung;
if (ausstattung) {
const [gi, i] = ausstattung.split("|").map(Number);
CAR.ausstattung[gi].sonder[i] = e.target.value;
profilSpeichern(); return;
}
const vselbst = e.target.dataset.vselbst;
if (vselbst) {
const [i, feld] = vselbst.split("|");
@@ -5719,6 +5874,8 @@ class AudiDashboardPanel extends HTMLElement {
Reload. Vorher stand die App nach jedem Neuladen wieder auf Nacht. */
HOST.dataset.theme = HOST.dataset.theme || themeGewaehlt();
schriftenLaden();
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `${STATIK}/audi-dashboard.css?v=${CSS_VERSION}`;
@@ -38,7 +38,13 @@
--tile-2:#EFEFF4;
--line:rgba(60,60,67,.13);
--line-strong:rgba(60,60,67,.24);
--fg:#000000; --fg2:#3C3C43; --fg3:#8E8E93;
/* --fg3 traegt jede Kachelueberschrift, jede Einheit und jeden Zusatz.
Auf hellem Grund lag #8E8E93 bei 2,92:1 gegen die Seitenflaeche und
3,26:1 gegen die Kachel - fuer einen kurzen Blick im Auto bei
Sonnenlicht zu wenig (Designpruefung 2026-08-30). #6B6B70 bringt beide
auf 4,75:1 bzw. 5,30:1, ohne den ruhigen Gesamteindruck zu aendern.
Der Nachtwert bleibt #8E8E93 - dort sind es ohnehin 5,86:1. */
--fg:#000000; --fg2:#3C3C43; --fg3:#6B6B70;
--ok:#34C759; --warn:#FF9F0A; --bad:#FF3B30;
--ios-sep:rgba(60,60,67,.16);
--ios-fill:rgba(120,120,128,.16);
@@ -102,7 +108,7 @@ html,body{height:100%}
.phone:has(.back.on) .topbar{padding:54px 16px 6px;gap:8px}
.phone:has(.back.on) .head{align-items:center}
.phone:has(.back.on) .title{font-size:17px;font-weight:400;letter-spacing:-.01em;text-align:center}
.phone:has(.back.on) .sync{margin-top:2px;font-size:11.5px}
.phone:has(.back.on) .sync{margin-top:2px;font-size:12.5px}
main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smooth}
@@ -195,7 +201,7 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
.switch input:checked+.track{background:var(--ok)}
.switch input:checked+.track::before{transform:translateX(20px)}
.pill{font-size:12px;letter-spacing:.01em;text-transform:none;padding:5px 11px;
.pill{font-size:12.5px;letter-spacing:.01em;text-transform:none;padding:5px 11px;
border:0;background:var(--ios-fill);color:var(--fg2)}
.status{padding:14px 0 2px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.8.30.2",
"version": "2026.8.30.8",
"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"],