UI polish batch + fix genuinely-invisible map pins, Apple HIG audit

Battery voltage stat cutoff 13.2V -> 12.8V (values above that are already
alternator output, not the battery); fixed the battery chart's Y-axis
"stretching" on zoom by computing it once from the full dataset; removed
two explanatory paragraphs from the battery detail view; Tankfuellung now
shows "X % / X l"; removed the "noch etwa X l im Tank" suffix; fuel bar
color now matches the headline instead of a red/yellow/green gradient;
active/primary buttons are grey again, not red (destructive stays red per
HIG); select fields gained an Apple-HIG pull-down chevron (appearance:none
had removed the native one with nothing replacing it); selected menu icon
is white on desktop, matching mobile; Reifenfoto crops from the right edge;
"Montiert" button rebuilt as a compact top-right pill with a 44px invisible
tap target; tab-bar active highlight now covers icon+label, not just the
icon; removed two leftover explanatory texts from Einstellungen.

Root-caused the map pins looking "transparent": the CI poi-car/poi icons
are pure contour forms (nonzero-fill-rule ring + thin detail lines, only
13-40% of their own bounding box actually filled). Fixed by extracting each
icon's own first sub-path (the true outer balloon silhouette, verified by
rasterizing it in isolation) as a solid-filled layer behind the original
icon, applied to both the vehicle and station markers.

Read Color/Typography/Layout/Buttons/Materials on developer.apple.com and
applied the user-approved subset (11px text floor, 44px tap targets);
declined items (font weight, materials) documented with rationale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 19:05:54 +02:00
parent 899b770cfa
commit 367985ba44
7 changed files with 351 additions and 101 deletions
@@ -560,7 +560,9 @@ function standortBekannt() { return CAR.standortLat != null && CAR.standortLon !
function fahrzeugMarkerSVG(px = 34) {
const gross = px >= 34;
const box = gross ? 48 : 24;
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiCarL : CI.poiCar}</svg>`;
const sil = gross ? CI.poiCarLSil : CI.poiCarSil;
const icon = gross ? CI.poiCarL : CI.poiCar;
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${icon}</svg>`;
}
/* Tankstellenposition im Einzelbeleg - dieselbe Nadelform wie beim Fahrzeug,
@@ -569,7 +571,9 @@ function fahrzeugMarkerSVG(px = 34) {
function tankstellenMarkerSVG(px = 32) {
const gross = px >= 34;
const box = gross ? 48 : 24;
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${gross ? CI.poiL : CI.poiS}</svg>`;
const sil = gross ? CI.poiLSil : CI.poiSSil;
const icon = gross ? CI.poiL : CI.poiS;
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${icon}</svg>`;
}
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
@@ -928,6 +932,20 @@ const CI = {
poiL: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M16 17.5a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0m7.5-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13"/>',
poiS: '<path fill="currentColor" d="M4 8.5C4 4.32 7.32 1 11.5 1S19 4.32 19 8.5c0 1.09-.48 2.53-1.14 4.03a49 49 0 0 1-2.43 4.68A92 92 0 0 1 12 22.67l-.06.08-.01.03-.42-.28-.41.28-.02-.03-.06-.08A66 66 0 0 1 10 21.13a92 92 0 0 1-2.42-3.92 49 49 0 0 1-2.43-4.68C4.48 11.03 4 9.6 4 8.5m7.5 14-.41.28.41.6.41-.6zm0-.89a92 92 0 0 0 3.07-4.9c.87-1.5 1.73-3.1 2.38-4.58A10.4 10.4 0 0 0 18 8.5C18 4.88 15.12 2 11.5 2A6.46 6.46 0 0 0 5 8.5c0 .86.4 2.14 1.05 3.63a48 48 0 0 0 2.38 4.58 91 91 0 0 0 3.07 4.9M8 8.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0M11.5 6a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5"/>',
poiCarL: '<path fill="currentColor" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5m14.5 27-.4.3.4.55.4-.56zm0-.86a132 132 0 0 0 1.77-2.56 170 170 0 0 0 4.8-7.45 89 89 0 0 0 4.79-8.87c1.3-2.89 2.14-5.45 2.14-7.26C37 10.08 30.92 4 23.5 4S10 10.08 10 17.5c0 1.8.83 4.37 2.14 7.26a89 89 0 0 0 4.79 8.87 170 170 0 0 0 6.57 10.01M17.96 7.98A63.3 63.3 0 0 1 29.05 8l.2.02.13.16c.6.7 1.1 1.36 1.46 1.94l1.31 2.08L34 14.31v7.19c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5V21H18v.5c0 .83-.67 1.5-1.5 1.5h-2a1.5 1.5 0 0 1-1.5-1.5v-7.19l1.85-2.11 1.3-2.08c.37-.58.86-1.24 1.47-1.96l.13-.16.2-.02ZM15.48 13 14 14.69V15h3v1h-3v5.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V20h13v1.5c0 .28.22.5.5.5h2a.5.5 0 0 0 .5-.5V16h-3v-1h3v-.31L31.52 13zm15.36-1L30 10.65a16 16 0 0 0-1.25-1.67 62.4 62.4 0 0 0-10.5-.02 17 17 0 0 0-1.25 1.7L16.16 12zM19.3 15h8.42l1.4 1.4-.16.31c-.34.74-.65 1.32-.92 1.72-.14.2-.28.38-.42.5a.92.92 0 0 1-.61.27h-7a.92.92 0 0 1-.6-.27 2.4 2.4 0 0 1-.43-.5c-.27-.4-.58-.98-.92-1.72l-.15-.32zm.42 1-.6.6c.26.57.5.99.69 1.27.11.17.2.27.26.32l.01.01h6.86v-.01c.07-.05.16-.15.27-.32.2-.28.43-.7.7-1.27l-.6-.6h-7.6Z"/>',
/* Vollton-Silhouetten fuer die Kartennadeln: poi-car/poi sind reine Kontur-
formen (Ring aus zwei gegenlaeufigen Randlinien + duenne Detaillinien im
Inneren) - eine Pixelprobe ergab nur 13-40% gefuellte Flaeche und war auf
Kartenkacheln praktisch unsichtbar (Nutzer-Feedback "Pin ist transparent/
durchsichtig"). Jede Silhouette ist exakt das erste Teilpfad-Segment des
jeweiligen CI-Pfads oben (die aeussere Ballonform, vor dem Punkt, der die
Ring-Innenlinie startet) - keine Neuzeichnung, nur der bereits im CI-Satz
enthaltene Umriss, mit fester Farbe statt currentColor gefuellt. Wird in
fahrzeugMarkerSVG()/tankstellenMarkerSVG() als unterste Ebene hinter dem
Original-Icon (dann weiss statt dunkel) gerendert. */
poiCarLSil: '<path fill="#182634" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5"/>',
poiCarSil: '<path fill="#182634" d="M3.8 8.6C3.8 4.33 7.32.8 11.5.8s7.7 3.53 7.7 7.8c0 1.11-.5 2.58-1.17 4.1a48 48 0 0 1-2.5 4.7 90 90 0 0 1-3.54 5.47l-.06.09-.02.02V23l-.41-.29-.4.29h-.01l-.02-.03-.06-.09a70 70 0 0 1-1.05-1.53 90 90 0 0 1-2.49-3.93 48 48 0 0 1-2.5-4.72C4.29 11.18 3.8 9.71 3.8 8.6"/>',
poiLSil: '<path fill="#182634" d="M9 17.5C9 9.52 15.52 3 23.5 3S38 9.52 38 17.5c0 2.04-.92 4.78-2.23 7.67a90 90 0 0 1-4.84 8.98 170 170 0 0 1-6.98 10.59l-.03.04-.01.01-.41-.29-.4.3-.02-.02-.03-.04a66 66 0 0 1-.56-.8 166 166 0 0 1-6.42-9.8 90 90 0 0 1-4.84-8.97C9.92 22.27 9 19.54 9 17.5"/>',
poiSSil: '<path fill="#182634" d="M4 8.5C4 4.32 7.32 1 11.5 1S19 4.32 19 8.5c0 1.09-.48 2.53-1.14 4.03a49 49 0 0 1-2.43 4.68A92 92 0 0 1 12 22.67l-.06.08-.01.03-.42-.28-.41.28-.02-.03-.06-.08A66 66 0 0 1 10 21.13a92 92 0 0 1-2.42-3.92 49 49 0 0 1-2.43-4.68C4.48 11.03 4 9.6 4 8.5"/>',
selectAll: '<path fill="currentColor" d="M2.8 2H5v1H3v2H2V2.8a.76.76 0 0 1 .43-.71.9.9 0 0 1 .35-.09zv.5zM9 3H7V2h2zm4 0h-2V2h2zm4 0h-2V2h2zm4 0h-2V2h2.2v.5V2h.02a1 1 0 0 1 .1.01c.07.01.16.03.25.08a.76.76 0 0 1 .43.71V5h-1zM6 6h12v12H6zm1 1v10h10V7zM3 7v2H2V7zm19 0v2h-1V7zM3 11v2H2v-2zm19 0v2h-1v-2zM3 15v2H2v-2zm19 0v2h-1v-2zM3 21v-2H2v2.2a.76.76 0 0 0 .43.71.9.9 0 0 0 .35.09h.01l.01-.5v.5H5v-1zm19-2v2.2a.76.76 0 0 1-.43.71.9.9 0 0 1-.35.09h-.01l-.01-.49V22H19v-1h2v-2zM9 22H7v-1h2zm4 0h-2v-1h2zm4 0h-2v-1h2z"/>',
planRoute: '<path fill="currentColor" d="m18.3 7-3.65-3.65.7-.7 4.86 4.85-4.86 4.85-.7-.7L18.29 8H9.5C8.67 8 8 8.67 8 9.5V21H7V9.5A2.5 2.5 0 0 1 9.5 7z"/>',
upload: '<path fill="currentColor" d="M13 20h-1V9.7l-3.65 3.65-.7-.7 4.85-4.86 4.85 4.86-.7.7L13 9.71zM6 5V4h13v1z"/>',
@@ -1157,8 +1175,6 @@ function vHome() {
const statusText = g === null ? "Status unbekannt" : g ? "Sicher abgestellt" : "Nicht sicher abgestellt";
const statusDot = g === null ? "warn" : g ? "" : "bad";
const pct = CAR.tankPct;
const balkenFarbe = pct <= 10 ? "var(--bad)" : pct <= 25 ? "var(--warn)" : "var(--ok)";
const liter = CONFIG.tankvolumen ? Math.round((CONFIG.tankvolumen * pct) / 100) : null;
const t = naechsterTermin();
/* C2/C5/D1: Reihenfolge der myAudi-App - randloses Fahrzeugbild, Name und
Status, große Reichweite mit farbigem Balken, dann Aktionskacheln. Die
@@ -1180,8 +1196,8 @@ function vHome() {
<div class="fig" style="font-size:60px">${CAR.reichweite != null ? de(CAR.reichweite) : ""}<span class="unit">km</span></div>
<span class="pct">${CAR.tankBekannt ? pct + " %" : ""}</span>
</div>
<div class="balken"><i style="width:${pct}%;background:${balkenFarbe}"></i></div>
<span class="label" style="margin-top:12px">Reichweite${CAR.tankBekannt && liter != null ? ` · noch etwa ${de(liter)} l im Tank` : ""}</span>
<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>
@@ -1262,7 +1278,7 @@ function vAudi() {
</dl></button>
<div class="tile"><span class="label">Zustand</span><dl class="rows">
<div class="row"><dt>Kilometerstand</dt><dd>${CAR.odoBekannt ? de(CAR.odo) + " km" : "unbekannt"}</dd></div>
<div class="row"><dt>Tankfüllung</dt><dd>${de(CONFIG.tankvolumen * CAR.tankPct / 100, 1)} l<small>${CAR.tankPct} % von ${CONFIG.tankvolumen} l</small></dd></div>
<div class="row"><dt>Tankfüllung</dt><dd>${CAR.tankPct} % / ${de(CONFIG.tankvolumen * CAR.tankPct / 100, 1)} l</dd></div>
<div class="row" style="border-bottom:0;cursor:pointer" data-go="battverlauf:x"><dt>Batteriespannung</dt>
<dd style="display:flex;align-items:center;gap:6px;justify-content:flex-end">${CAR.batteriespannung != null ? de(CAR.batteriespannung, 1) + " V" : "unbekannt"}
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></dd></div></dl></div>
@@ -1324,7 +1340,7 @@ function vAudi() {
Ziehen mit einem Finger, Antippen wählt den nächstgelegenen Messpunkt.
bvZeichnen() aktualisiert nur die SVG/Tooltip-Inhalte (kein render()), damit
Zoom/Pan flüssig bleiben. */
let bvVollDomain = null, bvDomain = null, bvPunkte = [], bvAusgewaehlt = null;
let bvVollDomain = null, bvDomain = null, bvPunkte = [], bvAusgewaehlt = null, bvYDomain = null;
const bvZeiger = new Map(); // pointerId -> {x,y}, für Pinch (2 Finger) vs. Pan (1 Finger)
let bvGestStart = null; // {type:"pan"|"pinch", ...Ausgangswerte der Geste}
let bvTapKandidat = null, bvBewegtSeit = false;
@@ -1345,8 +1361,12 @@ const kurzdat = (d) => d.toLocaleDateString("de-DE", { day: "2-digit", month: "2
Moment kurz nach dem Start, bevor der Alternator hochfährt, und ist damit
die für den Ladezustand aussagekräftige Größe. Tage, an denen selbst das
Minimum über der Grenze liegt (Fahrzeug nie im Ruhezustand beobachtet),
tauchen entsprechend nicht in der Ladezustands-Statistik auf. */
const AGM_RUHE_MAX_V = 13.2;
tauchen entsprechend nicht in der Ladezustands-Statistik auf. Die Grenze
liegt bei 12,8 V (voller Ruhezustand laut obiger Recherche) statt z. B.
13,2 V, weil alles darüber bereits vom Generator stammt, nicht von der
Batterie selbst - solche Werte würden den Ladezustand künstlich zu gut
aussehen lassen. */
const AGM_RUHE_MAX_V = 12.8;
const BV_SOC_TABELLE = [
[10.5, 0], [11.8, 10], [11.9, 20], [12.0, 30], [12.1, 40],
[12.2, 50], [12.4, 60], [12.5, 70], [12.6, 80], [12.7, 90], [12.8, 100],
@@ -1429,6 +1449,17 @@ function vBatterieverlauf() {
const genug = bvPunkte.length >= 2;
bvVollDomain = genug ? [bvPunkte[0].t, bvPunkte[bvPunkte.length - 1].t] : null;
bvDomain = genug ? [...bvVollDomain] : null;
// Y-Achse einmalig aus allen Punkten festlegen, nicht bei jedem Zoom/Pan
// neu aus den gerade sichtbaren - sonst "streckt" sich die Achse bei jeder
// Geste neu auf den jeweils sichtbaren Min/Max-Ausschnitt.
if (genug) {
let yMin = Math.min(...bvPunkte.map((p) => p.v)), yMax = Math.max(...bvPunkte.map((p) => p.v));
if (yMax - yMin < 0.4) { const mitte = (yMax + yMin) / 2; yMin = mitte - 0.2; yMax = mitte + 0.2; }
const pad = (yMax - yMin) * 0.15;
bvYDomain = [yMin - pad, yMax + pad];
} else {
bvYDomain = null;
}
const stufen = bvFarbstufen();
const letzter = bvPunkte.length ? bvPunkte[bvPunkte.length - 1] : null;
@@ -1451,8 +1482,6 @@ function vBatterieverlauf() {
<button class="aktion" data-bvreset style="padding:8px 16px;font-size:11px;width:auto;margin-top:0">Zoom zurücksetzen</button>
</div>
<div id="bvTooltip" style="display:none;margin-top:12px;font-size:13px;color:var(--fg2)"></div>
<span class="label" style="margin-top:16px">Ziehen zum Verschieben, mit zwei Fingern oder dem Mausrad
zoomen, auf einen Punkt tippen für Datum, Uhrzeit sowie Min-/Max-Spannung</span>
` : `
<span class="label" style="margin-top:14px;color:var(--fg3)">${leerText}</span>
`}
@@ -1480,9 +1509,6 @@ function vBatterieverlauf() {
: `<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 class="label" style="margin-top:12px">Näherung auf Basis der täglich niedrigsten gemessenen Spannung (aussagekräftig für die
Entladung), keine Kapazitätsmessung - Tageswerte über ${de(AGM_RUHE_MAX_V, 1)} V (Ladevorgang während der Fahrt) fließen nicht ein.
Für eine verlässliche Aussage ist ein Lasttest an der Batterie nötig.</span>
</div>`;
}
@@ -1503,12 +1529,11 @@ function bvPlotRahmen(rect) {
function bvZeichnen() {
const svg = $("#bvSvg");
if (!svg || !bvDomain) return;
const sichtbar = bvPunkte.filter((p) => p.t >= bvDomain[0] && p.t <= bvDomain[1]);
const basis = sichtbar.length >= 2 ? sichtbar : bvPunkte;
let yMin = Math.min(...basis.map((p) => p.v)), yMax = Math.max(...basis.map((p) => p.v));
if (yMax - yMin < 0.4) { const mitte = (yMax + yMin) / 2; yMin = mitte - 0.2; yMax = mitte + 0.2; }
const pad = (yMax - yMin) * 0.15; yMin -= pad; yMax += pad;
if (!svg || !bvDomain || !bvYDomain) return;
// Feste Y-Achse (siehe vBatterieverlauf) statt Neuberechnung aus dem
// gerade sichtbaren Zoom-Ausschnitt, damit die Achse beim Zoomen/Verschieben
// nicht mehr "streckt".
const [yMin, yMax] = bvYDomain;
// etwas über den sichtbaren Bereich hinaus zeichnen, damit die Linie am
// Rand nicht mitten in der Luft abbricht, sondern zum nächsten (Rand-)Punkt
@@ -1520,12 +1545,12 @@ function bvZeichnen() {
const gitter = [0, 1, 2, 3].map((i) => {
const v = yMin + ((yMax - yMin) * i) / 3, y = bvSkalaY(v, yMin, yMax);
return `<line x1="${BV_ML}" y1="${y.toFixed(1)}" x2="${BV_W - BV_MR}" y2="${y.toFixed(1)}" stroke="var(--line)" stroke-width="0.5"/>
<text x="${BV_ML - 6}" y="${(y + 3).toFixed(1)}" font-size="8" fill="var(--fg3)" text-anchor="end">${de(v, 1)}</text>`;
<text x="${BV_ML - 6}" y="${(y + 3).toFixed(1)}" font-size="11" fill="var(--fg3)" text-anchor="end">${de(v, 1)}</text>`;
}).join("");
const xLabels = [0, 0.5, 1].map((f) => {
const x = BV_ML + f * (BV_W - BV_ML - BV_MR);
const t = bvDomain[0] + f * spanne;
return `<text x="${x.toFixed(1)}" y="${BV_H - 6}" font-size="8" fill="var(--fg3)"
return `<text x="${x.toFixed(1)}" y="${BV_H - 6}" font-size="11" fill="var(--fg3)"
text-anchor="${f === 0 ? "start" : f === 1 ? "end" : "middle"}">${kurzdat(new Date(t))}</text>`;
}).join("");
const stufen = bvFarbstufen();
@@ -1930,10 +1955,6 @@ function csvDatei(name, kopf, zeilen) {
function vEinst() {
return `
<div class="tile"><span class="label">Fahrzeug einrichten</span>
<span style="display:block;margin-top:10px;color:var(--fg2);
font-size:13.5px;line-height:1.6">
Fahrgestellnummer, Kennzeichen, Erstzulassung, Ausführung und Ölwechsel-Intervall — bei der
Ersteinrichtung oder wenn sich diese Angaben ändern.</span>
${einrichtenOffen ? `
<div class="feld" style="margin-top:16px"><label for="einFin">Fahrgestellnummer (FIN)</label>
<input id="einFin" value="${esc(CAR.fin || "")}" data-einrichten="fin"></div>
@@ -1957,8 +1978,7 @@ function vEinst() {
? `<dl class="rows" style="margin-top:16px">
<div class="row"><dt>Laufleistung</dt><dd>${de(CAR.oel.herstellerKm)} km</dd></div>
<div class="row" style="border-bottom:0"><dt>Zeit</dt><dd>${CAR.oel.herstellerMonate} Monate</dd></div>
</dl>
<span class="label" style="margin-top:14px">Aus dem Fahrzeug gelesen</span>`
</dl>`
: `<div class="feld" style="margin-top:6px"><label for="oelKm">Nach Laufleistung</label>
<select id="oelKm" data-oelkm>${[5000, 10000, 15000, 20000].map((k) => `<option value="${k}" ${k === CAR.oel.km ? "selected" : ""}>${de(k)} km</option>`).join("")}</select></div>
<div class="feld" style="border-bottom:0"><label for="oelM">Nach Zeit</label>
@@ -2329,13 +2349,11 @@ function vReifen() {
<button type="button" data-bildersetzen="${radDatei}">Foto ersetzen</button>
<button type="button" class="loeschen" data-bildloeschen="${radDatei}">Löschen</button>
</div>` : ""}
<button data-satz="${k === "sommer" ? "Sommer" : "Winter"}" class="montiert ${aktiv ? "on" : ""}">Montiert</button>
<div class="radkopf">
<span class="label">${k === "sommer" ? "Sommer" : "Winter"}</span>
<div class="fig" style="font-size:32px;margin-top:14px">${t.km != null ? de(t.km) : ""}<span class="unit">km</span></div>
</div>
<div class="seg" style="margin-top:16px">
<button data-satz="${k === "sommer" ? "Sommer" : "Winter"}" class="${aktiv ? "on" : ""}">Montiert</button>
</div>
<div style="margin-top:22px">
${feld(k, "marke", "Hersteller")}
${feld(k, "modell", "Modell")}
@@ -122,14 +122,15 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
letter-spacing:0;text-transform:none;color:var(--fg2)}
.seg button.on{background:var(--tile-2);color:var(--fg);box-shadow:0 1px 3px rgba(0,0,0,.25)}
/* Rot bleibt Akzent/Primäraktion/destruktiv, nicht Flächenfarbe jeder Aktion
(Design-Review 2026-08-13 §B): .aktion allein ist die neutrale
Sekundäraktion, .primaer die einzige rot gefüllte Fläche. */
/* Aktive/Primär-Buttons sind grau, nicht rot (Nutzerwunsch 2026-08-16): Rot
bleibt nur noch für destruktive Aktionen (.loeschen). .aktion allein ist
die neutrale Sekundäraktion, .primaer die grau gefüllte Hauptaktion -
dieselbe Fläche wie die Grundauflage (var(--fg)/var(--canvas)). */
.aktion{margin-top:18px;padding:15px;border:0;border-radius:14px;
background:var(--ios-fill);color:var(--fg);font-size:16px;font-weight:400;
letter-spacing:0;text-transform:none}
.aktion:hover{background:var(--tile-2)}
.aktion.primaer{background:var(--ios-tint);color:#fff}
.aktion.primaer{background:var(--fg);color:var(--canvas)}
.aktion.primaer:hover{filter:brightness(1.08)}
.aktion.loeschen{background:var(--ios-fill);color:var(--bad);border:0}
.aktion.loeschen:hover{background:var(--bad);color:#fff;filter:none}
@@ -151,6 +152,18 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
.feld input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=file]),
.feld select{background:var(--ios-fill);padding:8px 10px}
.feld input:focus,.feld select:focus{color:var(--fg)}
/* Pull-down-Button-Optik (Apple HIG): ein einzelner Abwaerts-Chevron statt
des (durch appearance:none in audi-dashboard.css bereits entfernten)
nativen Browser-Pfeils - macht die Auswahlfelder als solche erkennbar,
ohne ein eigenes Menue-Widget nachzubauen. Farbe fix auf --ios-grau, das
ist bereits die Farbe aller anderen Chevrons (.chev) in beiden Themes. */
.feld select{
padding-right:28px;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%238E8E93' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat:no-repeat;
background-position:right 9px center;
background-size:10px 10px;
}
.notiz{background:var(--ios-fill);border:0;border-radius:12px;font-size:16px}
.switch{width:51px;height:31px}
@@ -201,6 +214,13 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
.tab.on{color:var(--fg);border-top-color:transparent}
.tab.on .tabpille{background:rgba(255,255,255,.14)}
:host([data-theme="tag"]) .tab.on .tabpille{background:rgba(0,0,0,.07)}
/* Ist die Beschriftung sichtbar (nicht .tabbar.ohne), soll die graue Flaeche
Icon UND Text umschliessen statt nur die enge Pille ums Icon - die Flaeche
wandert dafuer vom Icon-Symbol auf den ganzen Tab-Knopf. Im reinen
Icon-Modus bleibt die enge Pille wie zuvor. */
.tabbar:not(.ohne) .tab.on{background:rgba(255,255,255,.14);border-radius:14px}
:host([data-theme="tag"]) .tabbar:not(.ohne) .tab.on{background:rgba(0,0,0,.07)}
.tabbar:not(.ohne) .tab.on .tabpille{background:none}
.tabbar.ohne .tab{padding:11px 2px 10px}
:focus-visible{outline:2px solid var(--ios-tint);outline-offset:2px}
@@ -254,9 +274,9 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
.tabpille{width:auto;height:auto;border-radius:0;background:none}
.tab.on .tabpille{background:none}
.tab:hover{background:var(--tile)}
/* Aktive Navigation: Rot bleibt Text-/Icon-Akzent, die Fläche wird neutral
statt rosa hinterlegt (Design-Review §B - Rot nicht als Flächenfarbe). */
.tab.on{background:var(--ios-fill);color:var(--ios-tint)}
/* Aktives Symbol: dieselbe Farbe wie die Kopfzeile (--fg), nicht mehr Rot -
Fläche bleibt neutral hinterlegt statt rosa (Design-Review §B). */
.tab.on{background:var(--ios-fill);color:var(--fg)}
.tabbar.ohne .tab span{display:block}
.tabbar.ohne .tab{padding:12px 14px;gap:14px}
.tabbar.ohne .tab svg{width:22px;height:22px}
@@ -148,7 +148,7 @@
align-items: center;
gap: 6px;
margin-top: 4px;
font-size: 10.5px;
font-size: 11px;
color: var(--fg3);
white-space: nowrap;
}
@@ -443,7 +443,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.carfix { width: 100%; aspect-ratio: 16 / 9; }
.carfix.klein { aspect-ratio: 16 / 8; }
.carfix.mini { width: 78px; height: 78px; aspect-ratio: auto; }
.radbild { width: 96px; height: 96px; aspect-ratio: auto; border-radius: 50%; }
/* Reifenfoto ist oft eine breite Aufnahme; statt beide Ränder symmetrisch
wegzuschneiden (object-fit:cover-Standard), bleibt bewusst der rechte Rand
des Originalfotos erhalten - dort sitzt das Rad meist im Bild. */
.radbild { width: 96px; height: 96px; aspect-ratio: auto; border-radius: 50%; object-position: right center; }
.bildbox { position: relative; width: 100%; aspect-ratio: 4 / 3; border-radius: 12px; overflow: hidden; }
.bildbox img { display: block; width: 100%; height: 100%; object-fit: cover; }
@@ -521,7 +524,18 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
appearance: none;
}
.feld input:focus, .feld select:focus, .notiz:focus { outline: none; border-color: var(--red); }
.feld select { padding-right: 30px; background-image: none; }
/* Pull-down-Button-Optik (Apple HIG): einzelner Abwaerts-Chevron statt des
durch appearance:none oben entfernten nativen Browser-Pfeils. */
.feld select {
padding-right: 30px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%238a94a3' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 11px center;
background-size: 10px 10px;
}
:host([data-theme=tag]) .feld select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%23666666' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.feld > span { flex: 0 1 auto; min-width: 0; }
.mitEinheit { display: inline-flex; align-items: center; gap: var(--sp-2); max-width: 100%; }
.mitEinheit input { min-width: 0; width: 96px; text-align: right; }
@@ -713,16 +727,20 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
}
.fahrzeug-pin-wrap { background: none; border: none; }
/* poi-car aus dem CI-Satz ist eine reine Konturform in currentColor - auf
Kartenkacheln waere sie ohne Trennung zum Untergrund kaum lesbar. Die
gestapelten weissen drop-shadows ziehen deshalb einen Halo um die Kontur,
der letzte Schatten gibt Tiefe. Feste Farbe statt Theme-Variable, damit die
Nadel in Tag- und Nachtansicht gleich aussieht. */
/* poi-car/poi aus dem CI-Satz sind reine Konturformen (Ring aus zwei
gegenlaeufigen Randlinien + duenne Detaillinien) - eine Pixelprobe ergab
nur 13-40% gefuellte Flaeche in der eigenen Bounding-Box und war auf
Kartenkacheln praktisch unsichtbar (Nutzer-Feedback "Pin ist transparent").
fahrzeugMarkerSVG()/tankstellenMarkerSVG() rendern deshalb zusaetzlich eine
Vollton-Silhouette (CI.*Sil, exakt der aeussere Ballon-Umriss desselben
CI-Pfads, keine Neuzeichnung) als unterste Ebene; die Iconfarbe hier ist
deshalb weiss statt dunkel, damit Umriss und Detaillinien auf der jetzt
massiven Flaeche lesbar bleiben. Feste Farben statt Theme-Variablen, damit
die Nadeln in Tag- und Nachtansicht gleich aussehen. */
.fahrzeug-pin {
display: flex; align-items: center; justify-content: center;
color: #182634;
filter: drop-shadow(0 0 1px #fff) drop-shadow(0 0 1px #fff)
drop-shadow(0 0 2px #fff) drop-shadow(0 2px 4px rgba(0,0,0,.4));
color: #fff;
filter: drop-shadow(0 2px 4px rgba(0,0,0,.4));
}
.user-pin-wrap { background: none; border: none; }
.user-pin {
@@ -856,6 +874,30 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.radkopf { display: flex; flex-direction: column; margin-top: var(--sp-4); }
/* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): oben rechts
in der Kachel, obere Kante auf Höhe des Radfotos, rechter Abstand
identisch zum linken Texteinzug (beides var(--sp-5), die Kachel-Innen-
polsterung). */
.montiert {
position: absolute; top: var(--sp-5); right: var(--sp-5);
display: inline-flex; align-items: center; justify-content: center;
font-family: inherit; font-size: 12px; letter-spacing: normal;
color: var(--fg2);
background: none;
border: 1px solid var(--line-strong);
border-radius: var(--r-pill);
padding: 7px 13px;
cursor: pointer;
transition: background .15s, color .15s, border-color .15s, transform .1s;
}
/* Optisch bleibt die Pille klein, die Tippflaeche wird per unsichtbarem
Pseudo-Element auf Apple-HIG-Mindestmass 44x44pt gebracht (Klick auf die
Flaeche des ::before zaehlt als Klick auf den echten Button). */
.montiert::before { content: ""; position: absolute; top: -8px; bottom: -8px; left: 0; right: 0; }
.montiert:hover { background: var(--shade); }
.montiert:active { transform: scale(.96); background: var(--shade); }
.montiert.on { background: var(--fg); border-color: var(--fg); color: var(--canvas); }
/* ------------------------------------------------- B4: Action Sheet
Ersetzt confirm()/alert(). Von unten eingeblendet, destruktive Zeile rot,
Abbrechen abgesetzt - iOS-Muster. */