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
+91 -1
View File
@@ -13,7 +13,11 @@ two-tone pin+car icon — see section C; a third round the same day found and fi
"fragmented Leaflet rendering" defect at its root — the Leaflet stylesheet was loaded into
`document.head` and therefore never reached the panel's shadow root — plus light-only map tiles,
the CI `poi-car` marker, the red frame on list rows, the select-field grey box, and two more
generic-receipt-parser gaps). This file is the entry point for every new agent
generic-receipt-parser gaps; a sixth round the same day fixed the map pins' real visibility problem
(the CI `poi-car`/`poi` icons are thin outline-only paths, only 13-40% filled — solved with a solid
silhouette layer extracted from each icon's own outer contour, not a redraw), applied a batch of
~20 user-reported UI polish items against Apple's Human Interface Guidelines, some via a guided
Q&A, and lowered the battery-voltage statistic's cutoff from 13.2V to 12.8V). This file is the entry point for every new agent
session: what this repo is, what is finished, what is missing, and how to work here. Detail lives in
the linked documents — this file points, it does not duplicate.
@@ -836,6 +840,92 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
app re-renders on each backend push (~30 s), rebuilding the map and re-centring it. Panning
the map from the console and checking later therefore proves nothing — spy on
`L.Map.prototype.flyTo` instead.
- [x] Sixth round the same day (2026-08-16) — a large batch of user-reported UI polish items, plus
the map pins' real visibility root cause:
(1) **Map pins were genuinely near-invisible, not just a styling nit.** The user reported the
vehicle's map pin looked "transparent"; reproduced empirically (not by eye) by rasterizing the
live marker's SVG to a canvas and sampling alpha — only ~13% of the icon's own bounding box had
any ink, and the exact center pixel was fully transparent. Root cause: `CI.poiCarL`/`poiCar`
(and `poiL`/`poiS`, used for the Einzelbeleg station pin) are pure **contour** forms — the
balloon outline is drawn as two nested, oppositely-wound paths that cancel to a 1px ring under
the default nonzero fill rule, plus thin interior detail lines; there is no filled area at all.
A first fix (solid navy circle behind just the icon's round "head", via `::before`) was
explicitly rejected by the user ("I want the whole icon solid, not just the car" / "make the
needle opaque") — correct, since it only covered ~36% of the icon and left the pointed tip as a
bare outline. Real fix: each icon's `d` attribute is multiple sub-paths (split at top-level
M/m); the **first** sub-path alone is already the complete, correct outer balloon silhouette
(verified by rasterizing it in isolation — solid center pixel, 36-41% fill ratio, matching a
normal teardrop-in-square ratio). Extracted that first sub-path verbatim (no redrawing) into
four new `CI.poiCarLSil`/`poiCarSil`/`poiLSil`/`poiSSil` constants, fixed-color-filled (not
`currentColor`), and `fahrzeugMarkerSVG()`/`tankstellenMarkerSVG()` now render it as the bottom
layer behind the original detailed icon; `.fahrzeug-pin`'s color switched from dark navy to
white so the original outline/detail lines read against the new solid fill, exactly like the
white-glyph-on-solid-teardrop convention of Google/Apple Maps pins. Applies uniformly to both
marker types now (station pin included, per explicit user request), so the earlier per-variant
`.auto` CSS modifier and its circle-position math were removed again as dead weight.
(2) **Battery voltage statistic cutoff lowered 13.2V → 12.8V** (`AGM_RUHE_MAX_V` in
`audi-dashboard-app.js`) — user's point: anything above ~12.8V (full-charge resting voltage per
the existing in-code research citation) is already alternator output, not the battery's own
state, so admitting up to 13.2V let charging-tail values distort the charge-state stats.
(3) **Battery chart Y-axis no longer "stretches."** `bvZeichnen()` recomputed `yMin`/`yMax` from
whatever points were currently zoomed into (min 0.4V span + 15% pad) on every redraw, so the
axis rescaled on every pan/zoom gesture. Now computed once from the full dataset in
`vBatterieverlauf()` (`bvYDomain`) and reused unchanged across zoom/pan.
(4) Removed two explanatory paragraphs from the Batteriespannung detail view ("Näherung auf
Basis…" and "Ziehen zum Verschieben…") per explicit request.
(5) **Tankfüllung now shows "X % / X l"** (`vAudi()`) instead of a literal-litre primary value
with a redundant "100 % von 58 l" sub-line.
(6) Removed the "· noch etwa X l im Tank" suffix from the Übersicht's "Reichweite" label
(`vHome()`); the now-unused `liter` local was removed with it.
(7) **Active/primary buttons are grey again, not red**`.aktion.primaer` in
`audi-dashboard-ios.css` filled with `--ios-tint` (red) since the 2026-08-13 iOS-overlay
decision; explicit user request to drop that and match the base stylesheet's neutral
`var(--fg)`/`var(--canvas)` fill. Destructive actions (`.aktion.loeschen`) intentionally stay
red — Apple's own HIG reserves red specifically for destructive actions, so this one use is
correct per the guideline, not just left over.
(8) **`<select>` fields now carry an Apple-HIG pull-down chevron.** They already had
`appearance:none` (native arrow removed) but no replacement indicator at all — a real gap, not
cosmetic. Added a CSS `background-image` chevron (light/dark variants) in both stylesheets;
base `audi-dashboard.css` kept independently correct (own light/dark variant via
`:host([data-theme=tag])`) per this repo's "purely additive overlay" convention for the ios
file.
(9) **Apple HIG research round.** Read Color/Typography/Layout/Buttons/Materials directly off
developer.apple.com (the WebFetch tool returned only page titles for this JS-rendered site: the
in-app Browser tool's `get_page_text` worked and was used instead) and cross-checked against
actual measurements in this codebase, then asked the user via `AskUserQuestion` which findings
to apply. Accepted: text below the 11pt iOS legibility minimum (battery-chart axis labels were
8px, the header's "Sync" line was 10.5px) raised to 11px; the new compact "Montiert" pill's
*invisible* hit area (not its visual size) expanded to the 44×44pt minimum via a `::before`
with negative insets, same technique applied to future compact controls. Declined, deliberately:
switching the app's few weight-300 ("Light") text spots to 400 — Audi Type only ships 300/400,
and the user chose to keep Audi CI's own weight over HIG's general "avoid Light" guidance;
making popups/sheets translucent to match the Materials guidance — the app already restricts
translucency to the nav layer only (tab bar), which *is* the HIG-correct pattern, and popups
were deliberately made opaque on 2026-08-16 earlier the same day to fix a real see-through bug,
so translucency there would be a regression, not an improvement.
(10) Removed "Aus dem Fahrzeug gelesen" and the Einrichten intro paragraph ("Fahrgestellnummer,
Kennzeichen, Erstzulassung…") from `vEinst()`, both per explicit request.
(11) **Fuel bar color now matches the headline** (`var(--fg)`) instead of a red/yellow/green
status gradient in `vHome()`; the `balkenFarbe` local was removed with it.
(12) **Selected menu icon is white (headline color), not red**, in the desktop side-nav
(`.tab.on` inside the `@container (min-width:860px)` block in `audi-dashboard-ios.css`) — the
mobile tab bar already used `var(--fg)` there, this brought the desktop variant in line.
(13) **Reifenfoto (Sommerrad/Winterrad) now crops from the right edge**, not the center —
`.radbild` gained `object-position: right center`; the photo is typically wider than the 96px
square slot and the wheel itself tends to sit toward the right of the source photo.
(14) **"Montiert" button rebuilt as a compact top-right pill** (new `.montiert` class,
replacing the old full-width `.seg`-wrapped button) per explicit layout instructions: top edge
aligned with the wheel photo's top (`top:var(--sp-5)`, same offset the photo already has from
the tile's own padding), right inset equal to the tile's standard left content inset (both
`var(--sp-5)`) so left/right margins match. Markup reordered so the button renders before
`.radkopf` (position is absolute either way, but keeps DOM order sensible).
(15) **Tab-bar active highlight now covers icon *and* label when the label is visible**, not
just a tight pill behind the icon — `.tabbar:not(.ohne) .tab.on` gets the highlight background
directly (whole button), with the inner `.tabpille` background suppressed in that case; the
icon-only mode (`.tabbar.ohne`) keeps the original tight icon-pill unchanged.
Deployed to `audi_ha_test` (version `1786901000`); pin fix visually confirmed live (solid
teardrop pin with white car glyph, clearly over the map's "Obergurgl" label text) and the
tab-bar highlight confirmed covering both icon and "Übersicht" label.
- [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) —
text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
@@ -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. */
+50 -32
View File
@@ -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")}
+27 -7
View File
@@ -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}
+53 -11
View File
@@ -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. */