Uebersicht: fuehrende Service-Kennzahl statt zwei gleichrangiger Kacheln

Kilometerstand und Service standen als zwei identisch aussehende Kacheln
nebeneinander - 166x113px, gleiche 29px-Zahl, gleiche Farbe -, obwohl nur
die rechte ein Knopf war (erkennbar allein an einem 12px-Chevron) und die
beiden Zahlen Grundverschiedenes meinen: ein gemessener Ist-Wert gegen
einen taeglich schrumpfenden Countdown.

Neu, nach Abstimmung mit dem Nutzer und belegt an beiden Regelwerken
(Apple HIG "Layout"/"Widgets" live gelesen, Audi-CI aus dem
projekteigenen AUDI_CI_ARCHIV):

  Naechster Service:                  >
  9.029 km
  bis zum Oelwechsel · vsl. 15.04.2027
  ------------------------------------
  Kilometerstand             20.845 km

Das .duo-Raster entfaellt; der Block liegt in einer .tile.flat, also in
derselben Ebene wie die Reichweite darueber, statt in einem eigenen
Kachelcontainer. Der Chevron sitzt nur am .serviceblock-Knopf, nicht ueber
der ganzen Flaeche - damit bleibt erkennbar, welcher Teil bedienbar ist
und welcher reine Anzeige (HIG: "differentiate controls from content").

"Naechster Service" meint jede Serviceart; naechsterTermin() waehlte
ohnehin schon ueber Oelwechsel/Inspektion/Hauptuntersuchung aus, neu ist
die Beschriftung samt ART_BIS fuer den richtigen Artikel ("bis zum
Oelwechsel", aber "bis zur Inspektion"). Ohne Restkilometer
(Hauptuntersuchung) rueckt das Datum selbst ins Zahlenfeld, dann bei 30px
statt 40px.

Von der Aenderung verwaist und mitentfernt: mmjjjj() und die
.duo-Regeln in beiden Stylesheets.

Live geprueft bei 375px und 1280px: Block und Wertezeile fluchten mit der
Kachelkante, Chevron buendig rechts, Tippflaeche 88px; Klick auf den Block
oeffnet "Service", Klick auf die Kilometerstand-Zeile navigiert nicht.
Keine neuen Konsolenfehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 20:34:10 +02:00
parent 7319501ee6
commit e56f2040e8
9 changed files with 201 additions and 78 deletions
@@ -324,14 +324,9 @@ function heute() { return new Date(); }
const dTag = 86400000;
/* Ein unlesbares Datum ergibt einen Strich, keine Rechenreste. dat() erwartet
TT.MM.JJJJ; steht im Profil etwas anderes (es wird laut INSTALL.md von Hand
gepflegt, z. B. versehentlich im ISO-Format), lieferte mmjjjj() sonst
"NaN/NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */
gepflegt, z. B. versehentlich im ISO-Format), lieferte dedat() sonst
"NaN.NaN.NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */
const gueltigesDatum = (d) => d instanceof Date && !Number.isNaN(d.getTime());
/* Volles Jahr, weil das Datum überall als eigene Zeile steht und nicht mehr
in ein Zahlenfeld gequetscht wird (früher mmjj() mit "04/27"). */
const mmjjjj = (d) => (gueltigesDatum(d)
? String(d.getMonth() + 1).padStart(2, "0") + "/" + d.getFullYear()
: "");
const dedat = (d) => (gueltigesDatum(d)
? d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" })
: "");
@@ -1094,6 +1089,15 @@ function teaser() {
function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; }
/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese
Zuordnung stünde auf der Übersicht "bis zum Inspektion". */
const ART_BIS = {
"Ölwechsel": "bis zum Ölwechsel",
"Inspektion": "bis zur Inspektion",
"Hauptuntersuchung": "bis zur Hauptuntersuchung",
};
const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`;
function intervalle(t) {
const oel = CAR.oel, b = t.basis;
if (!b) return null;
@@ -1245,34 +1249,55 @@ function vHome() {
<div class="balken"><i style="width:${pct}%;background:var(--fg)"></i></div>
<span class="label" style="margin-top:12px">Reichweite</span>
</div>
<div class="duo">
<div class="tile"><span class="label">Kilometerstand</span>
<div class="fig" style="font-size:29px;margin-top:14px">${CAR.odoBekannt ? de(CAR.odo) : ""}<span class="unit">km</span></div></div>
${(() => { if (!t) return `<div class="tile"><span class="label">Service</span><div class="wert" style="margin-top:14px">kein Eintrag</div></div>`;
const artLabel = t.art === "Hauptuntersuchung" ? "Haupt-<br>untersuchung" : esc(t.art);
const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null;
const datum = t.kmDatum || t.datum;
/* Ein Wert pro Zahlenfeld, der Rest als leisere Zweitzeile darunter.
Vorher stand hier "9,0 tkm/ 04/27" - zwei Einheiten in einem Slot,
eine davon erfunden ("tkm" schreibt niemand, und die Mein-Audi-Kachel
nannte dieselbe Zahl schon immer "9.029 km"), auf 375px zweizeilig
umgebrochen. Apple HIG "Layout" ("don't obscure [essential
information] by crowding it with nonessential details") und
"Typography" (Hierarchie über Größe/Farbe) verlangen die Trennung -
deckungsgleich mit der Audi-CI, wo .fig genau EINE große Zahl trägt.
Kachel ist jetzt ein Knopf: hinter ihr liegt die Service-Seite, das
war bislang eine Sackgasse ohne Hinweis. */
const hauptwert = restKm !== null
? `${de(restKm)}<span class="unit">km</span>`
: (datum ? mmjjjj(datum) : "");
const nebenzeile = restKm !== null && datum ? `vsl. ${mmjjjj(datum)}` : "";
return `<button class="tile tilebtn" data-go="service:x">
<span class="label">${artLabel}</span>
<svg class="go" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg>
<div class="fig" style="font-size:29px;margin-top:14px">${hauptwert}</div>
${nebenzeile ? `<span class="label" style="margin-top:6px">${nebenzeile}</span>` : ""}
</button>`; })()}
</div>
${(() => {
/* Kilometerstand und Service standen bis 2026-08-17 als zwei gleich große
Kacheln nebeneinander - gleiches Maß, gleiche Zahlengröße, gleiche Farbe,
obwohl nur eine davon ein Knopf war und die beiden Zahlen Grundver-
schiedenes meinen: der Kilometerstand ist ein gemessener Ist-Wert, der
sich kaum ändert, der Service-Wert ein Countdown, der täglich kleiner
wird. Zwei gleich große Zahlen nebeneinander behaupteten damit eine
Gleichrangigkeit, die inhaltlich nicht besteht.
Jetzt: eine führende Kennzahl, darunter der Kilometerstand als ruhige
Wertezeile, getrennt durch eine Haarlinie - Audi-CI (Flächen und
Haarlinien statt gestapelter Karten, Hierarchie über Größe und Farbe)
kombiniert mit Apple HIGs "differentiate controls from content": der
Chevron sitzt nur am Serviceblock, damit erkennbar bleibt, welcher Teil
auf die Service-Seite führt und welcher reine Anzeige ist. Der Block
liegt bewusst in derselben Ebene wie die Reichweite darüber (.tile flat,
kein eigener Kachelhintergrund). */
const kmZeile = `<div class="servicezeile">
<span class="label">Kilometerstand</span>
<div class="fig" style="font-size:20px">${CAR.odoBekannt ? de(CAR.odo) : ""}<span class="unit">km</span></div>
</div>`;
if (!t) return `<div class="tile flat" style="margin-bottom:22px">
<span class="label">Nächster Service:</span>
<div class="wert" style="margin-top:8px">kein Eintrag im Servicebuch</div>
${kmZeile}
</div>`;
// naechsterTermin() waehlt bereits ueber alle drei Arten hinweg (Oelwechsel,
// Inspektion, Hauptuntersuchung) den zeitlich naechsten aus.
const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null;
const datum = t.kmDatum || t.datum;
const hauptwert = restKm !== null
? `${de(restKm)}<span class="unit">km</span>`
: (datum ? dedat(datum) : "");
// Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner,
// damit "15.04.2027" auf 375px nicht umbricht.
const zahlGroesse = restKm !== null ? 40 : 30;
const nebenzeile = restKm !== null && datum
? `${bisText(t.art)} · vsl. ${dedat(datum)}`
: bisText(t.art);
return `<div class="tile flat" style="margin-bottom:22px">
<button class="serviceblock" data-go="service:x">
<span class="label">Nächster Service:</span>
<svg class="go" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg>
<div class="fig" style="font-size:${zahlGroesse}px;margin-top:8px">${hauptwert}</div>
<span class="label" style="margin-top:8px">${nebenzeile}</span>
</button>
${kmZeile}
</div>`;
})()}
${standortKachel()}
${teaser()}`;
}
@@ -307,7 +307,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
deshalb hier mit !important. */
.rings{display:none !important}
.duo{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Nur die Box selbst hoeher setzen, nicht jedes Element mit .carfix: die
Klasse haengt auch am <img> und am Platzhalter-<div> (siehe
bildMitPlatzhalter()). Der Platzhalter ist position:absolute;inset:0 -
@@ -1 +1 @@
{"version": 1787003000}
{"version": 1787006000}
@@ -463,8 +463,26 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
/* Der Außenabstand sitzt am Raster, nicht an den Kacheln - sonst wird er im
2×2-Raster (vHome) zum zusätzlichen internen Zeilenabstand und der Abstand
innerhalb der Gruppe wäre größer als der zur nächsten. */
.duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.duo .tile { margin-bottom: 0; }
/* Übersicht: "Nächster Service" als flache, tippbare Kennzahl, darunter der
Kilometerstand als Wertezeile (siehe Kommentar in vHome()). Der Block sitzt
in einer .tile.flat, hat also keinen eigenen Kachelhintergrund - deshalb
sitzt der Chevron bündig am oberen rechten Rand statt auf --sp-5 eingerückt
wie sonst in einer gepolsterten Kachel. */
.serviceblock {
position: relative;
display: block; width: 100%;
border: 0; background: none; padding: 0;
font-family: inherit; text-align: left; color: inherit; cursor: pointer;
}
.serviceblock > .go { top: 3px; right: 0; }
/* Beschriftung nie unter dem Chevron hindurchlaufen lassen */
.serviceblock > .label:first-child { padding-right: 24px; }
.servicezeile {
display: flex; align-items: baseline; justify-content: space-between;
gap: var(--sp-4);
border-top: 1px solid var(--line);
margin-top: 14px; padding-top: 13px;
}
.badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.badge img { height: 18px; width: auto; display: none; }
+60 -35
View File
@@ -324,14 +324,9 @@ function heute() { return new Date(); }
const dTag = 86400000;
/* Ein unlesbares Datum ergibt einen Strich, keine Rechenreste. dat() erwartet
TT.MM.JJJJ; steht im Profil etwas anderes (es wird laut INSTALL.md von Hand
gepflegt, z. B. versehentlich im ISO-Format), lieferte mmjjjj() sonst
"NaN/NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */
gepflegt, z. B. versehentlich im ISO-Format), lieferte dedat() sonst
"NaN.NaN.NaN" - ein Wert, der wie eine Angabe aussieht und keine ist. */
const gueltigesDatum = (d) => d instanceof Date && !Number.isNaN(d.getTime());
/* Volles Jahr, weil das Datum überall als eigene Zeile steht und nicht mehr
in ein Zahlenfeld gequetscht wird (früher mmjj() mit "04/27"). */
const mmjjjj = (d) => (gueltigesDatum(d)
? String(d.getMonth() + 1).padStart(2, "0") + "/" + d.getFullYear()
: "");
const dedat = (d) => (gueltigesDatum(d)
? d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" })
: "");
@@ -1094,6 +1089,15 @@ function teaser() {
function naechsterTermin() { return termine().slice().sort((a, b) => a.ziel - b.ziel)[0]; }
/* "der Ölwechsel", aber "die Inspektion"/"die Hauptuntersuchung" - ohne diese
Zuordnung stünde auf der Übersicht "bis zum Inspektion". */
const ART_BIS = {
"Ölwechsel": "bis zum Ölwechsel",
"Inspektion": "bis zur Inspektion",
"Hauptuntersuchung": "bis zur Hauptuntersuchung",
};
const bisText = (art) => ART_BIS[art] || `bis zum nächsten Service (${art})`;
function intervalle(t) {
const oel = CAR.oel, b = t.basis;
if (!b) return null;
@@ -1245,34 +1249,55 @@ function vHome() {
<div class="balken"><i style="width:${pct}%;background:var(--fg)"></i></div>
<span class="label" style="margin-top:12px">Reichweite</span>
</div>
<div class="duo">
<div class="tile"><span class="label">Kilometerstand</span>
<div class="fig" style="font-size:29px;margin-top:14px">${CAR.odoBekannt ? de(CAR.odo) : ""}<span class="unit">km</span></div></div>
${(() => { if (!t) return `<div class="tile"><span class="label">Service</span><div class="wert" style="margin-top:14px">kein Eintrag</div></div>`;
const artLabel = t.art === "Hauptuntersuchung" ? "Haupt-<br>untersuchung" : esc(t.art);
const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null;
const datum = t.kmDatum || t.datum;
/* Ein Wert pro Zahlenfeld, der Rest als leisere Zweitzeile darunter.
Vorher stand hier "9,0 tkm/ 04/27" - zwei Einheiten in einem Slot,
eine davon erfunden ("tkm" schreibt niemand, und die Mein-Audi-Kachel
nannte dieselbe Zahl schon immer "9.029 km"), auf 375px zweizeilig
umgebrochen. Apple HIG "Layout" ("don't obscure [essential
information] by crowding it with nonessential details") und
"Typography" (Hierarchie über Größe/Farbe) verlangen die Trennung -
deckungsgleich mit der Audi-CI, wo .fig genau EINE große Zahl trägt.
Kachel ist jetzt ein Knopf: hinter ihr liegt die Service-Seite, das
war bislang eine Sackgasse ohne Hinweis. */
const hauptwert = restKm !== null
? `${de(restKm)}<span class="unit">km</span>`
: (datum ? mmjjjj(datum) : "");
const nebenzeile = restKm !== null && datum ? `vsl. ${mmjjjj(datum)}` : "";
return `<button class="tile tilebtn" data-go="service:x">
<span class="label">${artLabel}</span>
<svg class="go" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg>
<div class="fig" style="font-size:29px;margin-top:14px">${hauptwert}</div>
${nebenzeile ? `<span class="label" style="margin-top:6px">${nebenzeile}</span>` : ""}
</button>`; })()}
</div>
${(() => {
/* Kilometerstand und Service standen bis 2026-08-17 als zwei gleich große
Kacheln nebeneinander - gleiches Maß, gleiche Zahlengröße, gleiche Farbe,
obwohl nur eine davon ein Knopf war und die beiden Zahlen Grundver-
schiedenes meinen: der Kilometerstand ist ein gemessener Ist-Wert, der
sich kaum ändert, der Service-Wert ein Countdown, der täglich kleiner
wird. Zwei gleich große Zahlen nebeneinander behaupteten damit eine
Gleichrangigkeit, die inhaltlich nicht besteht.
Jetzt: eine führende Kennzahl, darunter der Kilometerstand als ruhige
Wertezeile, getrennt durch eine Haarlinie - Audi-CI (Flächen und
Haarlinien statt gestapelter Karten, Hierarchie über Größe und Farbe)
kombiniert mit Apple HIGs "differentiate controls from content": der
Chevron sitzt nur am Serviceblock, damit erkennbar bleibt, welcher Teil
auf die Service-Seite führt und welcher reine Anzeige ist. Der Block
liegt bewusst in derselben Ebene wie die Reichweite darüber (.tile flat,
kein eigener Kachelhintergrund). */
const kmZeile = `<div class="servicezeile">
<span class="label">Kilometerstand</span>
<div class="fig" style="font-size:20px">${CAR.odoBekannt ? de(CAR.odo) : ""}<span class="unit">km</span></div>
</div>`;
if (!t) return `<div class="tile flat" style="margin-bottom:22px">
<span class="label">Nächster Service:</span>
<div class="wert" style="margin-top:8px">kein Eintrag im Servicebuch</div>
${kmZeile}
</div>`;
// naechsterTermin() waehlt bereits ueber alle drei Arten hinweg (Oelwechsel,
// Inspektion, Hauptuntersuchung) den zeitlich naechsten aus.
const restKm = t.km !== null && CAR.odoBekannt ? Math.abs(t.km - CAR.odo) : null;
const datum = t.kmDatum || t.datum;
const hauptwert = restKm !== null
? `${de(restKm)}<span class="unit">km</span>`
: (datum ? dedat(datum) : "");
// Ohne Restkilometer steht das Datum selbst im Zahlenfeld - dann kleiner,
// damit "15.04.2027" auf 375px nicht umbricht.
const zahlGroesse = restKm !== null ? 40 : 30;
const nebenzeile = restKm !== null && datum
? `${bisText(t.art)} · vsl. ${dedat(datum)}`
: bisText(t.art);
return `<div class="tile flat" style="margin-bottom:22px">
<button class="serviceblock" data-go="service:x">
<span class="label">Nächster Service:</span>
<svg class="go" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg>
<div class="fig" style="font-size:${zahlGroesse}px;margin-top:8px">${hauptwert}</div>
<span class="label" style="margin-top:8px">${nebenzeile}</span>
</button>
${kmZeile}
</div>`;
})()}
${standortKachel()}
${teaser()}`;
}
-1
View File
@@ -307,7 +307,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
deshalb hier mit !important. */
.rings{display:none !important}
.duo{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Nur die Box selbst hoeher setzen, nicht jedes Element mit .carfix: die
Klasse haengt auch am <img> und am Platzhalter-<div> (siehe
bildMitPlatzhalter()). Der Platzhalter ist position:absolute;inset:0 -
@@ -1 +1 @@
{"version": 1787003000}
{"version": 1787006000}
+20 -2
View File
@@ -463,8 +463,26 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
/* Der Außenabstand sitzt am Raster, nicht an den Kacheln - sonst wird er im
2×2-Raster (vHome) zum zusätzlichen internen Zeilenabstand und der Abstand
innerhalb der Gruppe wäre größer als der zur nächsten. */
.duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.duo .tile { margin-bottom: 0; }
/* Übersicht: "Nächster Service" als flache, tippbare Kennzahl, darunter der
Kilometerstand als Wertezeile (siehe Kommentar in vHome()). Der Block sitzt
in einer .tile.flat, hat also keinen eigenen Kachelhintergrund - deshalb
sitzt der Chevron bündig am oberen rechten Rand statt auf --sp-5 eingerückt
wie sonst in einer gepolsterten Kachel. */
.serviceblock {
position: relative;
display: block; width: 100%;
border: 0; background: none; padding: 0;
font-family: inherit; text-align: left; color: inherit; cursor: pointer;
}
.serviceblock > .go { top: 3px; right: 0; }
/* Beschriftung nie unter dem Chevron hindurchlaufen lassen */
.serviceblock > .label:first-child { padding-right: 24px; }
.servicezeile {
display: flex; align-items: baseline; justify-content: space-between;
gap: var(--sp-4);
border-top: 1px solid var(--line);
margin-top: 14px; padding-top: 13px;
}
.badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.badge img { height: 18px; width: auto; display: none; }