Files
audi-app/custom_components/audi_dashboard/frontend/audi-dashboard.css
T
tobias d889353cbe Termine ohne Gedaechtnis, Listen nach HIG, zwei zu schmale Felder (2026.9.2.13-.20)
TERMIN. Der Werkstatttermin wanderte bisher ins Fahrzeugprofil, stand als
Merkmal an der Karte und liess sich "verwerfen" - eine zweite Verwaltung neben
dem Kalender. Auf Ansage entfernt: er dient jetzt allein dem Kalendereintrag
"was, wann, bei welcher Werkstatt". Dabei aufgefallen: die App schrieb gar kein
LOCATION in die .ics, obwohl das Panel es seit jeher setzt.

Ohne Speicherung startete das Feld leer ("tt.mm.jjjj", so gemeldet). Es
schlaegt jetzt die Faelligkeit der gewaehlten Art vor, sonst heute - dafuer
liefert service.ts serviceKandidaten(). "Andere" ergaenzt die Auswahl.

BREITEN. Am laufenden Panel gemessen: die vier festen 132px-Datumsfelder
brauchen ab 140px, damit die letzte Ziffer nicht unter dem Kalendersymbol
liegt (jetzt 150px); die Art-Auswahl braucht 178px fuer
"Hauptuntersuchung" (jetzt 190px). In der App war nichts davon kaputt - eine
dort vorsorglich eingebaute Breite ist wieder entfernt.

LISTEN. Fahrten- und Tankliste verloren 58px an tote Breite: 22px Einrueckung
der Monatsebene plus 40px linkes Polster der Zeile. Auf dem Telefon brach
"Eichstaett -> Adelschlag" deshalb um. Apples Richtlinien gruppieren ueber
Abschnittskoepfe statt Einrueckung; die Zeilen stehen jetzt alle auf derselben
Kante (34 statt 96px), der Monat traegt Versalien und Sperrung und liest sich
als Kopf. Beschraenkt auf .liste/.dm-liste - anderswo ist das Polster gewollt.

BILDER. Die Draufsicht ist aus der Galerie genommen (nurStatus), gehoert aber
weiter in die Auswahl der Einstellungen: nur dort laesst sie sich hochladen.
Die Galerie schneidet Fotos nicht mehr an - die Frontansicht verlor unten die
Stossstange.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 16:36:25 +02:00

1546 lines
71 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* audi-dashboard.css — Stil des Panels, aufgebaut auf den Tokens und
Komponenten des Design-Systems AudiDashUI (_ds_bundle.css). Klassennamen
wie im Prototyp/Panel (unpräfixiert), Werte 1:1 aus dem Design-System. */
:host {
--red: #F50537;
--r-tile: 20px;
--r-pill: 999px;
--r-func: 8px;
--rahmen-hg: #111;
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 22px;
--canvas: #161b23;
--tile: #1f2733;
--tile-2: #2a3341;
--line: rgba(255,255,255,.10);
--line-strong: rgba(255,255,255,.20);
--fg: #FFFFFF;
--fg2: #9aa1ad;
/* A2: von #657081 (3,0:1 auf --tile) auf 4,6:1 angehoben. Unterhalb von
12 px wird diese Farbe nicht mehr verwendet. */
--fg3: #8a94a3;
--ok: #15da15;
--warn: #ffaa00;
--bad: #fd2c4e;
--shade: rgba(255,255,255,.05);
--font-stack: "Audi Type", Helvetica, Arial, sans-serif;
/* Bugfix: in der echten HA-Einbettung liefert partial-panel-resolver
(display:inline, height:auto) keine definierte Höhe - eine
height:100%-Kette von hier nach oben rechnet also gegen eine
Auto-Box und kollabiert auf die Inhaltshöhe (nur Topbar+Tabbar
sichtbar, main#view ohne Fläche zum Scrollen). Deshalb wie im
ursprünglichen Prototyp direkt an der Viewport-Höhe verankert,
unabhängig von der Elternkette - und zentriert wie eine
Telefon-Karte, statt randlos zu füllen. */
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
height: 100dvh;
overflow: hidden;
}
:host([data-theme=tag]) {
--canvas: #FFFFFF;
--tile: #f2f2f2;
--tile-2: #e5e5e5;
--line: rgba(0,0,0,.10);
--line-strong: rgba(0,0,0,.22);
--fg: #000000;
--fg2: #4c4c4c;
--fg3: #666666;
--ok: #0DA20D;
--warn: #ffaa00;
--bad: #eb0d3f;
--shade: rgba(0,0,0,.04);
}
* { box-sizing: border-box; }
/* Sichtbarer Tastatur-Fokus. Bis hierher gab es .feld input:focus als einzige
Fokus-Regel im ganzen Blatt - alle Knöpfe, Kacheln und Tabs waren zwar
fokussierbar, aber ohne jede sichtbare Markierung. Auf dem Telefon fällt das
nicht auf, per Bluetooth-Tastatur (iPadOS/Desktop über Tailscale) oder mit
iOS-Vollzugriff per Tastatur ist die App dadurch praktisch unbedienbar.
:focus-visible statt :focus, damit Maus- und Tipp-Bedienung unverändert
ohne Rahmen bleibt - der Browser entscheidet, was "echte" Tastaturnavigation
ist. */
:focus-visible {
outline: 2px solid var(--red);
outline-offset: 2px;
border-radius: 2px;
}
/* Zwischen-Wrapper, den _aufbauen() in den Shadow Root haengt - bekommt hier
die einzige "echte" (viewport-verankerte) Höhe der ganzen Kette; .phone
darunter kann sich dann gefahrlos mit height:100% darauf beziehen. */
:host > div {
width: 100%;
max-width: 412px;
height: min(880px, calc(100vh - 24px));
height: min(880px, calc(100dvh - 24px));
min-height: 0;
/* Verhindert, dass eine Zieh-Geste, die main#view unten nach oben hin
verlaesst, an die dahinterliegende HA-Seite weitergereicht wird - sonst
kann der Browser selbst ein natives Overscroll/Pull-to-Refresh starten
und der eigene Ziehen-zum-Aktualisieren-Indikator bekommt die Geste nie
zu Ende. */
overscroll-behavior: none;
}
/* ------------------------------------------------------------- Gerüst */
.phone {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
/* B4: Bezugsrahmen für Action Sheet und Popups. */
position: relative;
background: var(--canvas);
color: var(--fg);
font-family: var(--font-stack);
font-variant-numeric: tabular-nums;
overflow: hidden;
/* B2: kein graues Safari-Rechteck beim Tippen - Rückmeldung kommt aus den
:active-Regeln unten. */
-webkit-tap-highlight-color: transparent;
}
.topbar {
display: flex;
align-items: center;
gap: var(--sp-3);
/* B1: oben Platz für Statusbalken/Dynamic Island. */
padding: max(22px, calc(env(safe-area-inset-top) + 10px)) 20px 14px;
flex: 0 0 auto;
background: var(--canvas);
}
.back {
/* B6: 44 px Trefferfläche. */
width: 44px; height: 44px;
margin-left: -5px;
flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
border: none; border-radius: 50%;
background: none;
color: var(--fg3);
cursor: pointer;
padding: 0;
opacity: 0;
pointer-events: none;
}
.back.on { opacity: 1; pointer-events: auto; }
.back:hover { background: var(--tile-2); color: var(--fg); }
.back:active { background: var(--tile-2); color: var(--fg); transform: scale(.92); }
.head { flex: 1; min-width: 0; }
.eyebrow {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--fg3);
min-height: 12px;
}
.title {
font-size: 23px;
letter-spacing: -.015em;
line-height: 1.15;
color: var(--fg);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sync {
display: flex;
align-items: center;
gap: 6px;
margin-top: 4px;
font-size: 11px;
color: var(--fg3);
white-space: nowrap;
}
.themebtn, .rings {
display: inline-flex; align-items: center; justify-content: center;
border: none; background: none; color: var(--fg3);
cursor: pointer; padding: 0; flex: 0 0 auto;
}
.themebtn { width: 44px; height: 44px; border-radius: 50%; }
/* A6: Die Topbar-Taste ist entfallen - die Wahl liegt in den Einstellungen und
folgt sonst der Systemeinstellung. display:inline-flex oben würde das
hidden-Attribut sonst überstimmen. */
.themebtn[hidden] { display: none; }
.themebtn:hover { background: var(--tile-2); color: var(--fg); }
.themebtn:active { background: var(--tile-2); color: var(--fg); transform: scale(.92); }
.themebtn svg { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
/* Wie .themebtn (44px Trefferflaeche), aber ohne die Kontur-Stroke-Regel -
fuer CI-Symbole (ciSVG()), deren Pfade ihr eigenes fill="currentColor"
mitbringen (z. B. list-s im Kopf der Batteriespannungs-Kachel). */
.iconbtn {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; border-radius: 50%;
border: none; background: none; color: var(--fg3);
cursor: pointer; padding: 0; flex: 0 0 auto;
}
.iconbtn:hover { background: var(--tile-2); color: var(--fg); }
.iconbtn:active { background: var(--tile-2); color: var(--fg); transform: scale(.92); }
/* "Neue Räder anlegen" auf der Reifen-Seite: bewusst dauerhaft mit
sichtbarem Hintergrund statt nur bei Hover/Tap wie .iconbtn sonst - sitzt
direkt neben dem gefüllten "Montiert"-Knopf und wirkt ohne eigene Fläche
zu unscheinbar, um dort als eigenständiger Knopf erkannt zu werden. */
.archivbtn { background: var(--tile-2); color: var(--fg2); }
.archivbtn:hover, .archivbtn:active { color: var(--fg); }
/* D4 war urspruenglich: die Ringe sind Marke, kein Knopf. Seit die
iOS-Auflage in Telefonbreite das Zahnrad ausblendet, sind sie dort aber der
einzige Zugang zu den Einstellungen - und als reines div mit
pointer-events:none war dieser Zugang fuer Tastatur und Sprachausgabe gar
nicht erreichbar. Sie sind deshalb jetzt ein echter Knopf, in jeder Breite
bedienbar. Auf grossen Bildschirmen gibt es damit zwei Wege in die
Einstellungen (Ringe und Zahnrad) - unschaedlich, und der Zugang haengt
nicht mehr davon ab, ob audi-dashboard-ios.css geladen wurde. */
/* Design-Audit 2026-08-13: in Telefonbreite ist dies der einzige Zugang zu
den Einstellungen (siehe Kommentar oben), war mit 42px aber deutlich
kleiner als jeder andere Symbol-Knopf in der Kopfzeile (.themebtn/
.profilbtn/.back sind alle 44px) und lag damit unter der ueblichen
44px-Mindestgroesse fuer Tippflaechen. */
.rings { width: 58px; height: 32px; color: var(--fg); }
.rings svg { width: 58px; height: auto; }
/* Der Markenklon in der Seitenleiste wird vom Skript unbedingt in die
Menueleiste gehaengt; sichtbar macht ihn erst die iOS-Auflage ab 860px.
Das Verstecken gehoert deshalb hierher: laedt die Auflage nicht (404,
Cache), waere er sonst ein sechstes Element im fuenfspaltigen Raster und
die Menueleiste zerfiele. */
.navmarke { display: none; }
.profilbtn {
display: inline-flex; align-items: center; justify-content: center;
width: 44px; height: 44px; border-radius: 50%;
border: none; background: var(--tile); color: var(--fg2);
cursor: pointer; padding: 0; flex: 0 0 auto;
}
/* Flaeche statt Kontur, seit das Zahnrad settings-s aus dem CI-Satz ist
(02.09.2026): der Pfad bringt sein eigenes fill="currentColor" mit, und ein
geerbter stroke wuerde zusaetzlich um jede Flaeche gezeichnet - dieselbe
Falle wie bei .voll/RahmenFlaeche in der App. */
.profilbtn svg { width: 19px; height: 19px; fill: currentColor; stroke: none; }
.profilbtn:hover { color: var(--fg); }
.profilbtn:active { background: var(--tile-2); color: var(--fg); transform: scale(.94); }
.ptr {
height: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: var(--fg3);
font-size: 12.5px;
transition: height .2s;
flex: 0 0 auto;
}
.ptr.spin svg { animation: drehen 1s linear infinite; }
@keyframes drehen { to { transform: rotate(360deg); } }
main#view {
/* Der seitliche Rand steht als Variable, weil randlose Elemente ihn per
negativem Rand ausgleichen muessen (siehe .standort-vollbild). Wer das
Padding aendert, ohne die Variable zu benutzen, verschiebt die randlosen
Elemente unbemerkt. */
--seitenrand: 20px;
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 10px var(--seitenrand) 34px;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
/* Ohne das hier greift auf echten Touch-Geraeten oft das native
Overscroll/Pull-to-Refresh des Browsers, sobald am oberen Rand weiter
nach unten gezogen wird - das nimmt der eigenen "Ziehen zum
Aktualisieren"-Geste (siehe ptrY0-Logik in ereignisseVerdrahten()) die
Kontrolle weg, obwohl deren JS/CSS für sich genommen korrekt arbeitet. */
overscroll-behavior-y: contain;
}
main#view::-webkit-scrollbar { width: 0; height: 0; }
#overlay:empty { display: none; }
.tabbar {
display: grid;
grid-template-columns: repeat(5, 1fr);
border-top: 1px solid var(--line);
flex: 0 0 auto;
background: var(--canvas);
/* B1: Luft über dem Home-Indikator. */
padding-bottom: max(6px, env(safe-area-inset-bottom));
}
/* B5: kein roter Oberkantenstrich (Desktop-Muster), sondern eine gefüllte
Pille um das aktive Symbol - wie in der myAudi-App. */
.tab {
display: flex; flex-direction: column; align-items: center; gap: 7px;
padding: 12px 0 10px;
background: none; border: none;
color: var(--fg3);
font-family: inherit;
cursor: pointer;
transition: color .15s;
}
.tab.on { color: var(--fg); }
/* Die Pille ist ein eigenes Feld um das Symbol - so bleibt ihre Groesse
unabhaengig vom Symbol und der Wechsel laeuft sauber ueber background. */
.tabpille {
display: flex; align-items: center; justify-content: center;
width: 60px; height: 34px; border-radius: var(--r-pill);
transition: background .18s;
}
.tab.on .tabpille { background: var(--tile-2); }
.tab svg {
fill: none; stroke: currentColor; stroke-width: 1.6;
stroke-linecap: round; stroke-linejoin: round;
transition: transform .18s;
}
.tab svg.voll { fill: currentColor; stroke: none; }
.tab:active svg { transform: scale(.9); }
.tab span { font-size: 11px; letter-spacing: .01em; }
/* :not(.tabpille) - die Icon-Huelle ist selbst ein <span>, ein blosses
".tab span" hier hiess frueher auch der Pille (samt Icon) display:none.
Ueberschrieben von derselben Regel in audi-dashboard-ios.css (dieselbe
Spezifitaet, spaeter geladen), aber auch hier falsch und daher fixiert. */
.tabbar.ohne .tab span:not(.tabpille) { display: none; }
.tabbar.ohne .tab { padding: 14px 0 12px; }
.tabbar.ohne .tab svg { width: 26px; height: 26px; }
/* -------------------------------------------------------------- Kacheln */
.tile {
background: var(--tile);
border-radius: var(--r-tile);
padding: var(--sp-5);
margin-bottom: var(--sp-4);
position: relative;
}
.tile.flat { background: none; padding: 0; }
button.tile, .tilebtn {
display: block;
width: 100%;
border: none;
font-family: inherit;
text-align: left;
cursor: pointer;
color: inherit;
}
button.tile:hover { background: var(--tile-2); }
button.tile:active, .tilebtn:active { background: var(--tile-2); transform: scale(.99); }
button.tile, .tilebtn { transition: background .15s, transform .1s; }
.go {
position: absolute;
top: var(--sp-5); right: var(--sp-5);
width: 6px; height: 10px;
color: var(--fg3);
fill: none; stroke: currentColor; stroke-width: 1.4;
}
.zahnrad {
position: absolute;
top: 8px; right: 8px;
width: 44px; height: 44px;
display: inline-flex; align-items: center; justify-content: center;
border: none; border-radius: 50%;
background: none; color: var(--fg3);
cursor: pointer; padding: 0;
}
.zahnrad:hover { background: var(--tile-2); color: var(--fg); }
.zahnrad:active { background: var(--tile-2); color: var(--fg); transform: scale(.92); }
.zahnrad svg { width: 17px; height: 17px; }
/* Titelzeile nie unter dem Zahnrad/Chevron hindurchlaufen lassen - und die
Zeile hoch genug für die 44px-Trefferfläche des Zahnrads machen. Ohne die
Mindesthöhe ragte es in den Kachelinhalt und lag auf dem ersten Knopf
(Versicherung, "Inland · <Nummer>"; gemeldet 02.09.2026). Gleiche Regel in
der App (.dm-kachel--zahnrad). */
.tile:has(> .zahnrad) > .label:first-child,
.tile:has(> .go) > .label:first-child { padding-right: 48px; }
.tile:has(> .zahnrad) > .label:first-child { min-height: 34px; }
/* C1: Abschnittstitel gemischt geschrieben, 14 px, ohne Sperrung - wie in der
aktuellen myAudi-App. Versalien trägt nur noch .eyebrow über dem Seitentitel. */
.label {
font-size: 14px;
letter-spacing: normal;
color: var(--fg2);
display: block;
line-height: 1.3;
}
/* HIG "Loading": statt reinem Text ein sichtbar laufender Indikator, solange
DATEN_GELADEN false ist - hilft auch, "lädt noch" optisch von "hängt fest"
zu unterscheiden (dieser Bildschirm hatte schon ein echtes Hänger-Problem). */
.lade-spinner {
width: 16px; height: 16px; flex: none;
border: 2px solid var(--line-strong);
border-top-color: var(--fg2);
border-radius: 50%;
animation: lade-spin .8s linear infinite;
}
@keyframes lade-spin { to { transform: rotate(360deg); } }
/* Echte Überschrift innerhalb einer Kachel (z.B. "Sommerräder"/"Winterräder")
statt des sonst üblichen kleinen, gedämpften .label-Abschnittstitels -
selbe Typografie-Stufe wie .title (Seitentitel), nur etwas kleiner, weil
sie innerhalb einer Kachel steht statt in der Kopfzeile. */
.tile-headline {
font-size: 20px;
letter-spacing: -.01em;
line-height: 1.2;
color: var(--fg);
display: block;
}
/* Icon + Text statt reinem Fließtext-Label (z.B. Batterie-Symbol vor
"Ruhespannung") - dieselbe Zeilen-Geometrie wie .sync in der Kopfzeile. */
.bv-skala-kopf { display: flex; align-items: center; gap: 6px; }
/* A4: Messwerte erben nie mehr das uppercase der Label-Klasse. */
.wert {
font-size: 14px;
letter-spacing: normal;
text-transform: none;
color: var(--fg2);
}
.fig {
font-family: "Audi Type Wide", var(--font-stack);
font-weight: 300;
letter-spacing: -.02em;
line-height: .95;
color: var(--fg);
}
.fig .unit {
/* A3: relativ zur Zahl, aber nie unter der Lesbarkeitsgrenze - bei einer
29-px-Zahl ergäbe .32em nur 9,3 px. */
font-size: max(11px, .32em);
font-weight: 400;
letter-spacing: normal;
color: var(--fg2);
margin-left: .32em;
}
.werte { font-size: 14px; color: var(--fg); display: block; }
.rows { margin: 0; }
.row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--sp-4);
padding: 15px 0;
border-bottom: 1px solid var(--line);
}
.row dt { font-size: 13px; color: var(--fg2); line-height: 1.4; }
.row dd { margin: 0; font-size: 14px; color: var(--fg); text-align: right; }
.row dd small { display: block; font-size: 12.5px; color: var(--fg3); margin-top: 3px; }
.rows-fest dd { min-width: 120px; }
/* .row-fussnote ist mit dem Umbau der Service-Seite auf .termingruppe
entfallen (2026-08-18) - dort gibt es keine dt/dd-Spalten mehr, die um
Platz konkurrieren. Die beiden Regeln dafuer standen seither ohne
passendes Markup da. */
/* Sammelzeilen der Sicherheit-Ansicht (vSicherheit()): Kreis-Häkchen + Text
+ optionaler Chevron statt dt/dd. Erbt Abstand/Trennlinie von .row, ersetzt
aber dessen space-between-Layout und setzt die Button-Vorgaben zurück, wenn
die Zeile klickbar ist (Türen und Klappen). */
.sicherheitszeile {
justify-content: flex-start;
width: 100%;
background: none;
border: none;
border-bottom: 1px solid var(--line);
font: inherit;
color: inherit;
text-align: left;
cursor: default;
}
button.sicherheitszeile { cursor: pointer; }
button.sicherheitszeile:active { background: var(--tile-2); }
.sicherheitsliste .sicherheitszeile:last-child { border-bottom: 0; }
.sicherheitszeile-text { flex: 1; font-size: 14px; color: var(--fg); }
.sicherheitszeile .chev { margin-left: auto; }
/* Wert als Kommentar statt als Angabe - fuer Zeilen, deren rechte Seite nur
beschreibt, was hinter der Kachel liegt (Mein Audi, "Details"). Groesse
und Farbe wie jede andere Nebenzeile. */
.row dd.row-hinweis { font-size: 12.5px; color: var(--fg3); }
.leaf {
display: flex; align-items: center; justify-content: space-between;
gap: var(--sp-4);
width: 100%;
background: none; border: none;
font-family: inherit; text-align: left;
padding: 14px 0 14px 40px;
min-height: 56px;
border-bottom: 1px solid var(--line);
color: inherit;
}
/* Das Label nimmt den freien Platz und kuerzt bei Bedarf mit Auslassung.
Ohne flex:1 blieb in der Zeile freier Raum, und weil sie DREI Kinder hat
(Label, Werte, Pfeil), verteilte space-between ihn ZWISCHEN alle drei - die
Wertespalte stand damit in der Mitte, ihre Lage abhaengig von der Laenge des
Ortsnamens. Gemessen: rechte Kante bei 549px in der einen Zeile, 520px in
der naechsten. Mit flex:1 bleibt kein freier Raum uebrig, Werte und Pfeil
sitzen buendig rechts.
min-width:0 ist noetig, damit ein Flex-Kind ueberhaupt unter seine
Inhaltsbreite schrumpfen darf - ohne das greift text-overflow nie. */
.leaf .k { font-size: 14px; color: var(--fg); display: flex; flex-direction: column; gap: 3px; line-height: 1.35; flex: 1 1 auto; min-width: 0; }
.leaf .k > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.leaf .k small { font-size: 12.5px; color: var(--fg3); }
/* Feste Mindestbreite, damit Strecke und Verbrauch ueber alle Zeilen hinweg
in einer Spalte stehen. Ohne sie waechst der Kasten mit dem Inhalt: gemessen
86px bei "11,4 l/100 km" gegen 37px bei einer Zeile ohne Werte, und die
Zahlen standen entsprechend versetzt. 92px traegt die laengste Form
("11,4 l/100 km") ohne Umbruch. */
.leaf .v { font-size: 13px; color: var(--fg2); text-align: right; display: flex; flex-direction: column; gap: 3px; flex: 0 0 92px; }
.leaf .v small { font-size: 12.5px; color: var(--fg3); }
/* .teaser (die "Zuletzt"-Kachel auf der Übersicht, teaser()): genau zwei
Zeilen mit sehr unterschiedlich breiten Werten ("offen" vs. "51,59 l") -
.v ist sonst flex:0 0 auto und damit genau so breit wie sein eigener
Inhalt, wodurch die linke Kante des Wertes je Zeile unterschiedlich weit
rechts beginnt (rechte Kante bleibt an der Chevron-Spalte gleich, das
täuscht Ausrichtung nur vor). min-width sorgt für eine gemeinsame
Spaltenbreite, damit beide Werte auf derselben linken Kante beginnen.
Das allein reicht aber nicht: .leaf selbst verteilt seine drei Kinder
(.k/.v/Chevron) per justify-content:space-between, und .k hat kein
flex-grow - unterschiedlich lange Beschriftungen ("→" vs. "Hermann Mogler
Mineralölg. GmbH") verschieben dadurch die GESAMTE restliche Zeile
(.v + Chevron) um die Differenz, obwohl .v selbst jetzt gleich breit ist.
.k bekommt deshalb flex:1, damit es den verbleibenden Platz füllt und .v
dadurch wirklich immer am selben Abstand vom rechten Rand sitzt. */
.teaser .leaf .k { flex: 1 1 auto; min-width: 0; }
.teaser .leaf .v { min-width: 72px; }
/* cursor/:active nur für echte .leaf-Buttons (data-go, navigieren bei Klick)
- die Batterie-Messwertliste (vBatterieliste()) ist bewusst ein reines
<div class="leaf">, keine Zeilennavigation, nur Wischen zum Löschen; sie
soll deshalb weder den Hand-Cursor noch die Tipp-Rückmeldung zeigen, die
sonst "hier passiert beim Klick etwas" versprechen. */
button.leaf { cursor: pointer; transition: background .15s; }
button.leaf:active { background: var(--tile-2); }
.chev { width: 6px; height: 10px; color: var(--fg3); flex: 0 0 auto; fill: none; stroke: currentColor; stroke-width: 1.4; }
/* ------------------------------------------------------------- Status */
.status {
display: flex; align-items: center; gap: var(--sp-3);
width: 100%;
background: none; border: none; padding: 16px 0 0;
min-height: 44px;
text-align: left; font-family: inherit; cursor: pointer;
color: inherit;
}
.dot { width: 11px; height: 11px; border-radius: 50%; flex: 0 0 auto; background: var(--ok); display: inline-block; }
.dot.warn { background: var(--warn); }
.dot.bad { background: var(--bad); }
/* Kreis-Häkchen statt einfarbigem Punkt - für die Sicherheit-Ansicht
(vSicherheit()/vTuerenKlappen()), nach Nutzer-Vorlage: erfüllt/offen/
unbekannt sofort am Symbol erkennbar, nicht nur an der Farbe. */
.statuskreis { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; background: var(--ok); display: inline-flex; align-items: center; justify-content: center; }
.statuskreis.warn { background: var(--warn); }
.statuskreis.bad { background: var(--bad); }
.status .t { font-size: 14px; color: var(--fg); }
.status { transition: opacity .15s; }
.status:active { opacity: .55; }
/* -------------------------------------------------------------- Übersicht */
.tankzeile { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; margin-bottom: 14px; }
.pct { font-size: 15px; color: var(--fg2); padding-bottom: 6px; }
.balken { height: 4px; border-radius: var(--r-pill); background: var(--line); overflow: hidden; }
.balken i { display: block; height: 100%; background: var(--fg); border-radius: var(--r-pill); }
/* 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. */
/* Übersicht: "Nächster Service" als tippbare Kennzahl, darunter der
Kilometerstand als Wertezeile (siehe Kommentar in vHome()). Der Knopf sitzt
INNERHALB einer normalen .tile - nicht die Kachel selbst ist der Knopf,
sondern nur ihr oberer Teil, damit die Kilometerstand-Zeile erkennbar reine
Anzeige bleibt. Der Chevron steht deshalb auf right:0 relativ zum Knopf und
landet damit genau auf demselben Innenabstand wie ein normales .go 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; }
/* Rückmeldung beim Tippen: ein Hintergrundwechsel wie bei .tilebtn ginge hier
nicht, der Knopf liegt ja schon auf der Kachelfläche. */
.serviceblock:active { opacity: .55; }
/* Beschriftung nie unter dem Chevron hindurchlaufen lassen */
.serviceblock > .label:first-child { padding-right: 24px; }
/* Serviceart-Icon links der Kennzahl statt des Wortes "bis zum/zur ...". */
.fig-zeile { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.fig-zeile svg { color: var(--fg2); flex: 0 0 auto; }
.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;
}
/* Anstehende Termine (vService): drei sichtbar getrennte Bloecke statt einer
dt/dd-Liste - Apple HIG ("essential information... don't obscure it by
crowding it with nonessential details") plus Audi-CI (Flaeche mit Haarlinien
statt Karten, eigene Groessenklasse fuer Zahlen). Trennung ueber Abstand und
eine Haarlinie zwischen den Bloecken, nicht ueber verschachtelte Kacheln.
Jeder Block: Kopfzeile (Icon + Art-Name, optional ein Chip fuer einen
vereinbarten Werkstatttermin), optionale Herstellervorgabe-Zeile, der Wert,
optional die eigene Prognose als Fusszeile. */
.termingruppe { padding: 18px 0; border-top: 1px solid var(--line); }
.termingruppe:first-of-type { padding-top: 2px; border-top: 0; }
.tg-kopf { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tg-name { display: flex; align-items: center; gap: 9px; min-width: 0; }
.tg-name svg { color: var(--fg2); flex: 0 0 auto; }
.tg-name span { font-size: 16px; color: var(--fg); font-weight: 400; }
/* Chip statt rotem Fliesstext fuer einen vereinbarten Werkstatttermin - Rot
bleibt Akzent (Text/Rahmen), nie Flaeche, wie im Rest der App. */
.tg-chip {
font-size: 10.5px; color: var(--red);
border: 1px solid var(--red); border-radius: var(--r-pill);
padding: 3px 8px; white-space: nowrap; flex: 0 0 auto;
}
.tg-vorgabe { font-size: 12.5px; color: var(--fg2); margin-top: 8px; }
.tg-wert {
font-family: "Audi Type Wide", var(--font-stack); font-weight: 300;
font-size: 22px; color: var(--fg); letter-spacing: -.005em; margin-top: 4px;
}
.tg-wert.leer { font-family: var(--font-stack); font-weight: 400; font-size: 15px; color: var(--fg3); margin-top: 8px; }
.tg-prognose { font-size: 12.5px; color: var(--fg2); margin-top: 6px; }
.badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.badge img { height: 18px; width: auto; display: none; }
:host([data-theme=nacht]) .badge img.negative { display: block; }
:host([data-theme=tag]) .badge img.positive { display: block; }
.badge .zusatz { font-size: 13px; color: var(--fg2); }
.szene { position: relative; padding: 8px 0 18px; }
/* C5: randloses Fahrzeugbild - hebt die 20 px Seitenrand von main#view auf und
blendet nach unten in die Fläche aus. */
.szene.bleed { margin: -10px -20px 0; padding: 0 0 10px; }
.szene.bleed .carbox { padding: 0 8px; }
/* Der Ausblend-Verlauf und der Bodenschatten sind am 2026-08-31 entfallen.
Beide lagen ueber dem unteren Teil des Fotos, und der Eigentuemer hat
zweimal darum gebeten, das Fahrzeug unverdeckt zu sehen. Zurueckholen
waere ein linear-gradient auf .szene.bleed::before bzw. ein
radial-gradient auf .szene.strasse::after - beides steht in der
Versionsgeschichte. */
.carbox { position: relative; }
.carfix { width: 100%; aspect-ratio: 16 / 9; }
/* Das Fahrzeugfoto wird nicht mehr beschnitten.
Gemessen am echten Foto (2026-08-31): es ist 2000x1125 gross, also 16:9.
Der Kasten stand auf 16:8 - bei object-fit:cover wurde das Bild auf die
Kastenbreite skaliert und oben wie unten je ein Viertel abgeschnitten
(796x220 Kasten, 796x448 skaliertes Bild). Das Dach lag darin.
Statt das Seitenverhaeltnis auf 16:9 festzuschreiben - was beim naechsten
Foto in einem anderen Format wieder schneiden wuerde - bestimmt jetzt das
Bild selbst die Hoehe. Der Platzhalter behaelt sein festes Verhaeltnis,
er hat ja keines von sich aus. */
.bildbox.carfix.klein { aspect-ratio: auto; height: auto; }
img.carfix.klein { aspect-ratio: auto; height: auto; }
/* 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 { object-position: right center; }
/* .bildbox.radbild statt .radbild allein: .bildbox unten setzt width/aspect-
ratio auf derselben Spezifitaet (eine Klasse) und gewinnt als spaeter
notierte Regel sonst automatisch - die Box blieb dadurch bislang die volle
Kachelbreite im 4:3-Format statt eines echten 96x96-Quadrats. Rechtsbündig
sitzt es jetzt ueber .radkopf-row (display:flex; justify-content:space-
between), kein margin-left:auto mehr noetig. */
.bildbox.radbild { width: 96px; height: 96px; aspect-ratio: 1 / 1; }
.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; }
.bildbox .platzhalter { display: none; position: absolute; inset: 0; }
.bildbox.bild-fehlt img { display: none; }
.bildbox.bild-fehlt .platzhalter { display: flex; }
/* D1: Solange kein Foto vorliegt, bleibt die Bildfläche flach - vorher standen
260 px gestrichelte Leerfläche vor der Reichweite, der wichtigsten Zahl der
App. Mit Foto gilt wieder das volle Seitenverhältnis. */
.bildbox.bild-fehlt.carfix { aspect-ratio: auto; height: 132px; }
/* Der Platzhalter belegt denselben Kasten wie ein echtes Foto (16:8). Er war
zwischenzeitlich auf 120 und dann 72px gestaucht, um die Reichweite
hoeher zu ruecken - auf Ansage des Besitzers steht er wieder in voller
Groesse, weil ein Streifen nicht erkennen laesst, was dort hingehoert. */
.bildbox.bild-fehlt.carfix.klein { aspect-ratio: 16 / 8; height: auto; }
/* Die Draufsicht auf der Fahrzeugseite ist eine Beigabe, keine Szene: sie
steht ueber den Sicherheitszeilen und soll die Zeilen nicht nach unten
draengen. Statt randlos ueber die volle Breite (rund 374px auf einem
Telefon, mit 16:8 also 187px hoch) steht sie eingerueckt und mittig -
60% Breite, die Hoehe geht im selben Verhaeltnis mit auf rund 112px
zurueck. Auf Ansage des Eigentuemers am 02.09.2026 ("Draufsicht-Foto
Bereich ist zu gross"): schmaler, nicht flacher - flacher haette bei
einem Foto, das nicht sehr breit ist, oben und unten geschnitten. */
.szene.bleed.schmal { margin: 0; padding: 22px 0 26px; }
.szene.bleed.schmal .carbox { width: 60%; margin: 0 auto; padding: 0; }
.szene.bleed:has(.bild-fehlt)::before { display: none; }
.szene.bleed:has(.bild-fehlt) { margin-left: 0; margin-right: 0; }
.szene.bleed:has(.bild-fehlt) .carbox { padding: 0; }
/* A5/C5: kein gestrichelter Rahmen und kein Dateiname mehr - Motivname plus
Aufforderung, die Fläche ist tippbar. */
.platzhalter {
width: 100%; height: 100%;
flex-direction: column; align-items: center; justify-content: center; gap: 7px;
background: var(--shade);
border: 1px solid var(--line);
border-radius: inherit;
color: var(--fg3);
text-align: center;
padding: var(--sp-3);
}
.platzhalter-icon { width: 26px; height: 26px; color: var(--fg3); }
.platzhalter-label { font-size: 13px; letter-spacing: normal; color: var(--fg2); }
.platzhalter-aktion { font-size: 12.5px; color: var(--fg3); }
.dots { display: flex; gap: 6px; justify-content: center; margin-top: 12px; }
.dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--line-strong); }
.dots i.on { background: var(--red); }
.marke { display: inline-flex; align-items: center; gap: 6px; }
.marke-logo { display: inline-flex; align-items: center; }
/* 19,2px = 16px + 20%. Der Eigentuemer wollte das Markenlogo in JEDER
Darstellung um ein Fuenftel groesser; es steht ueberall neben Text der
Zeilenhoehe 16px, also hier eine Zahl statt mehrerer Sondergroessen. */
.marke-logo img { height: 19.2px; width: auto; }
.marke-logo .ph { display: none; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--fg3); }
.marke-logo.logo-fehlt img { display: none; }
.marke-logo.logo-fehlt .ph { display: inline; }
/* -------------------------------------------------------------- Formulare */
.feld {
display: flex; justify-content: space-between; align-items: center;
gap: var(--sp-4);
padding: 14px 0;
min-height: 56px;
border-bottom: 1px solid var(--line);
}
/* :not(.switch): der Schalter (Tanken/Einstellungen etc.) ist selbst ein
<label class="switch">, kein Textlabel - ohne den Ausschluss erbte er
dieselbe flex:1 1 auto-Regel und zog sich über die ganze Zeilenbreite. */
.feld label:not(.switch) { font-size: 14px; color: var(--fg2); flex: 1 1 auto; min-width: 0; line-height: 1.35; }
/* Eingaben nie breiter als die Kachel: die im Markup gesetzten festen
Breiten (width:132px) bleiben Obergrenze, schrumpfen aber mit. */
.feld input, .feld select, .notiz {
background: var(--tile-2);
border: 1px solid var(--line);
color: var(--fg);
font-family: inherit;
/* Muss >= 16px bleiben: Safari/WKWebView (und damit auch die HA-Companion-App
auf dem iPhone) zoomt die Seite beim Fokussieren automatisch hinein, wenn
das Feld kleiner als 16px gesetzt ist - der Nutzer muss dann nach jedem
Feld von Hand wieder herauszoomen. Vorher standen hier 15px. */
font-size: 16px;
padding: 11px 13px;
border-radius: 12px;
text-align: right;
min-width: 0;
max-width: 100%;
flex: 0 1 auto;
-webkit-appearance: none;
appearance: none;
}
.feld input:focus, .feld select:focus, .notiz:focus { outline: none; border-color: var(--red); }
/* z. B. Fahrgestellnummer bei aktivem "automatisch"-Schalter: der Wert kommt
aus den zugeordneten Sensoren, Handeingabe ist erst nach dem Ausschalten
wieder möglich - das Feld soll das auf den ersten Blick zeigen. */
.feld input:disabled { opacity: .5; cursor: not-allowed; }
/* Label und Eingabe untereinander statt nebeneinander, die Eingabe über die
volle Zeilenbreite - für Felder, deren Wert typischerweise länger ist als
ein Kurztext (Kommentar bei Sommer-/Winterrädern, live wie archiviert).
Sonst gilt .feld input oben: die Eingabe wächst nie über ihre intrinsische
Breite, nur das Label tut das (flex:1 1 auto) - für einen Reifenkommentar
("Profiltiefe 5,1 mm bei der letzten Montage") reichte das nicht. */
.feld--breit { display: block; }
.feld--breit label { display: block; margin-bottom: 8px; }
.feld--breit input { width: 100%; text-align: left; }
/* Das AUFGEKLAPPTE <select>-Popup ist Browser-/OS-Chrome, kein Teil des
Seiten-DOMs - color-scheme (siehe :host weiter oben) beeinflusst es nur
teilweise, der Rest bleibt hell. Einzige zuverlaessige Stellschraube:
Chrome (u.a.) rendert die Zeilen im Popup tatsaechlich mit dem hier
gesetzten background-color/color statt Systemfarben - deckende Werte,
var(--tile-2) im Nacht-Theme ist transparent und wuerde durchscheinen. */
.feld select option { background-color: var(--canvas); color: var(--fg); }
/* 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; }
.mitEinheit span:last-child { font-size: 13px; color: var(--fg2); }
.druckfeld { width: 58px !important; min-width: 0 !important; }
.notiz { width: 100%; text-align: left; margin-top: 14px; line-height: 1.55; resize: vertical; }
.switch { position: relative; display: inline-block; flex-shrink: 0; width: 46px; height: 27px; }
.switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; min-width: 0; }
.switch .track {
position: absolute; inset: 0;
background: var(--tile-2);
border: 1px solid var(--line);
border-radius: var(--r-pill);
transition: background .18s;
pointer-events: none;
}
.switch .track::before {
content: ""; position: absolute; left: 2px; top: 1px;
width: 23px; height: 23px; background: #fff; border-radius: 50%;
transition: transform .18s;
}
/* A7: Rot bleibt Akzent und Destruktiv. Der aktive Schalter trägt --fg mit
dunklem Knopf. */
.switch input:checked + .track { background: var(--fg); border-color: var(--fg); }
.switch input:checked + .track::before { transform: translateX(19px); background: var(--canvas); }
/* C3: gemischt geschrieben, 15 px, 48 px hoch. .primaer ist gefüllt,
.aktion allein bleibt die Umriss-Sekundäraktion. */
.aktion {
display: block; width: 100%;
font-family: inherit; font-size: 15px;
letter-spacing: normal;
color: var(--fg);
background: none;
border: 1px solid var(--line-strong);
border-radius: var(--r-pill);
padding: 14px 18px;
min-height: 48px;
margin-top: var(--sp-4);
cursor: pointer;
transition: border-color .15s, background .15s, transform .1s, opacity .15s;
}
.aktion + .aktion, .aktion + label.aktion { margin-top: 10px; }
.aktion:hover { border-color: var(--line-strong); background: var(--shade); }
.aktion:active { transform: scale(.985); background: var(--shade); }
.aktion.primaer { background: var(--fg); border-color: var(--fg); color: var(--canvas); }
.aktion.primaer:hover { opacity: .9; background: var(--fg); }
.aktion.primaer:active { opacity: .8; background: var(--fg); }
.aktion.loeschen { color: var(--bad); border-color: var(--bad); background: none; }
.aktion.loeschen:hover { background: var(--bad); color: #fff; }
.aktion.loeschen:active { background: var(--bad); color: #fff; }
/* C4: eine Bahn mit wanderndem Knopf statt zwei getrennter Umrisspillen. */
.seg {
display: flex; gap: 0;
background: var(--tile-2);
border-radius: var(--r-pill);
padding: 3px;
}
.seg button {
flex: 1;
border: none;
background: none;
color: var(--fg2);
font-family: inherit;
font-size: 14px;
letter-spacing: normal;
padding: 11px 8px;
min-height: 44px;
border-radius: var(--r-pill);
cursor: pointer;
transition: background .18s, color .18s;
}
.seg button.on { background: var(--fg); color: var(--canvas); }
.seg button:active { opacity: .8; }
.pill {
display: inline-flex; align-items: center;
font-size: 12.5px; letter-spacing: normal;
color: var(--fg2);
background: none;
border: 1px solid var(--line-strong);
border-radius: var(--r-pill);
padding: 6px 12px;
}
/* ------------------------------------------------------------ Akkordeon */
.acc-h {
display: flex; align-items: center; gap: var(--sp-3);
width: 100%;
background: none; border: none;
font-family: inherit; text-align: left;
padding: 15px 0;
min-height: 56px;
cursor: pointer;
border-bottom: 1px solid var(--line);
color: inherit;
}
.acc-h .k { flex: 1; font-size: 15px; color: var(--fg); }
.acc-h .v { font-size: 12.5px; color: var(--fg2); text-align: right; white-space: nowrap; flex: 0 0 auto; }
/* Ohne eigene Regel erbt <small> den UA-Wert "smaller" (0,833x) - im
Akkordeonkopf sind das 10,4px und damit unter der 11px-Untergrenze, die
dieses Projekt aus den Apple-Richtlinien uebernommen hat. */
.acc-h .v small { font-size: 11px; }
.acc-h { transition: background .15s; }
.acc-h:active { background: var(--tile-2); }
.mark {
/* Design-Audit 2026-08-13: die Border-Ecken-Technik (zwei Kanten +
45deg-Drehung) braucht ein Quadrat, um einen sauberen Chevron zu
ergeben - bei 6x10px (nicht quadratisch, wie fuer die SVG-Chevrons
.chev/.go gedacht) sind die beiden Kantenlaengen ungleich lang und das
Ergebnis sah eher wie ein Haekchen als ein Pfeil aus. */
width: 8px; height: 8px;
border-right: 1.4px solid var(--fg3);
border-bottom: 1.4px solid var(--fg3);
transform: rotate(-45deg);
transition: transform .15s, border-color .15s;
flex: 0 0 auto;
}
/* A8: ein Chevron, eine Farbe - nur die Drehung zeigt den Zustand. */
.acc-h[aria-expanded=true] .mark { transform: rotate(45deg); }
/* Kachelkoepfe der Statistik verhalten sich wie Aufklapp-Zeilen. */
.stat-kopfzeile { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.stat-kopfzeile .label { flex: 1; }
.tile-h[aria-expanded=true] .mark { transform: rotate(45deg); }
.acc-b { display: none; }
.acc-b.on { display: block; }
.lvl2 { margin-left: 22px; }
.lvl2 .acc-h .k { font-size: 13px; }
/* Fahrten- und Tankliste: Abschnittsköpfe statt Einrückung.
Gemessen am 02.09.2026 im laufenden Panel: von der Innenkante der
Kachel bis zum Ortsnamen lagen 58px tote Breite - 22px Einrückung der
Monatsebene plus 40px linkes Polster der Zeile. Auf einem Telefon blieb
für "Eichstätt → Adelschlag" so wenig übrig, dass der Name umbrach
(gemeldet vom Eigentümer mit Bildschirmfoto).
Apples Richtlinien lösen Gruppierung nicht über Einrückung, sondern über
Abschnittsköpfe, die über die volle Breite laufen; die Zeilen darunter
stehen alle auf derselben Kante. Genau das ist hier umgesetzt - die
Monatszeile bleibt kleiner und grau und liest sich dadurch als Kopf,
ohne Platz zu kosten. Beschränkt auf .liste, weil das linke Polster von
.leaf anderswo (Batteriewerte, Servicebuch) gewollt ist. */
.liste .lvl2 { margin-left: 0; }
/* Der Monat ist ein Abschnittskopf, kein kleingeratener Eintrag.
Vorher unterschied er sich von der Fahrtzeile nur durch Groesse (13 gegen
16px) und Farbe - beim Lesen wirkte die Liste dadurch zweistufig statt
dreistufig, und genau danach hat der Eigentuemer gefragt (02.09.2026).
Versalien plus Sperrung sind die Form, die iOS fuer Abschnittskoepfe
benutzt und die in dieser Oberflaeche schon ueber dem Seitentitel steht
(.eyebrow).
11px und nicht 10: das ist die Untergrenze, die dieses Projekt aus den
Apple-Richtlinien uebernommen hat. In beiden Oberflaechen dieselben
Werte, damit der Kopf nicht je Geraet anders aussieht. */
.liste .lvl2 .acc-h .k {
font-size: 11px;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--fg3);
}
.liste .leaf { padding-left: 0; }
.tile-h {
display: block; width: 100%;
background: none; border: none;
font-family: inherit; text-align: left;
padding: 0; cursor: pointer; color: inherit;
}
.tile-b { display: none; }
.tile-b.on { display: block; }
/* -------------------------------------------------------------- Statistik */
/* Fuenf Spalten (Jahr/Monat/Woche/Tag/Absolut) passen auf einem Telefon nicht
nebeneinander: bei 307px Innenbreite blieben je 51px, und "209.177" braucht
bei 16px Audi Type Wide rund 60. auto-fit bricht deshalb um - auf dem Telefon
drei plus zwei, auf einem breiten Bildschirm alle fuenf in einer Reihe. */
/* Vier feste Spalten - Jahr, Monat, Woche, Tag muessen in EINE Zeile passen.
Vorher stand hier repeat(auto-fit, minmax(84px, 1fr)). Auf dem Telefon ist
die Kachel innen 307px breit, damit passten bei 12px Abstand nur drei
Spalten (3 x 84 + 2 x 12 = 276; vier waeren 372) - die vierte rutschte in
eine zweite Zeile.
Mit vier festen Spalten bleiben 67,75px je Spalte. Der laengste realistisch
vorkommende Wert ist ein sechsstelliger Kilometerstand wie "209.177": am
laufenden Panel gemessen 67px in Audi Type Wide 300 bei 16px. Es passt
also, aber ohne Reserve - eine fuenfte Spalte gaebe es nur mit kleinerer
Schrift, und genau deshalb steht der Gesamtwert jetzt in der Kopfzeile der
Kachel statt in einer eigenen Spalte.
Bewusst kein white-space:nowrap: waere ein Wert doch einmal breiter,
umbricht er innerhalb seiner Spalte, statt aus der Kachel zu laufen. */
.quad { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-3); margin-top: var(--sp-3); }
.quad .n { display: block; font-family: "Audi Type Wide", var(--font-stack); font-size: 16px; font-weight: 300; color: var(--fg); line-height: 1.2; }
.quad .l { display: block; margin-top: 4px; font-size: 11px; letter-spacing: normal; color: var(--fg3); line-height: 1.3; }
.bar { display: flex; height: 4px; border-radius: var(--r-pill); overflow: hidden; gap: 2px; }
.bar i { display: block; border-radius: var(--r-pill); }
.legend { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-2); font-size: 11px; color: var(--fg2); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
/* -------------------------------------------------------------- Wischen */
.swipe-wrap { position: relative; overflow: hidden; }
/* Der rote Loeschknopf liegt dauerhaft hinter der Zeile. Zeilenhoehen sind
gebrochen (z. B. 66,28 px), dadurch fiel die Kante von .swipe-content nicht
aufs Geraetepixelraster und der rote Knopf blitzte als duenne Linie an den
Zeilenraendern durch - sichtbar als roter Teilrahmen um jede Listenzeile.
Er wird deshalb erst sichtbar, sobald wirklich gewischt wird ("wischt"
waehrend der Geste, "swiped" fuer die offen stehende Zeile). */
.swipe-delete { position: absolute; inset: 0 0 0 auto; width: 84px; display: flex; visibility: hidden; }
.swipe-wrap.wischt .swipe-delete,
.swipe-wrap.swiped .swipe-delete { visibility: visible; }
.swipe-loeschen {
width: 100%;
background: var(--bad); color: #fff; border: none;
font-family: inherit; font-size: 13px;
letter-spacing: normal;
cursor: pointer;
}
.swipe-loeschen:active { filter: brightness(.88); }
.swipe-content { position: relative; background: var(--tile); touch-action: pan-y; transition: transform .18s; }
.swipe-wrap.swiped .swipe-content { transform: translateX(-84px); }
/* -------------------------------------------------------------- Karte */
/* position:relative, damit der Zentrieren-Knopf im Einzelbeleg (absolut
positioniert, siehe .kartensteuerung-klein) sich an dieser Kachel ausrichtet
und nicht am naechsten Vorfahren weiter oben. */
.mapbox { position: relative; border-radius: var(--r-tile); overflow: hidden; height: 210px; background: var(--tile); }
.mapbox #map { width: 100%; height: 100%; }
/* --------------------------------------------------------- Standort
Vorschau-Kachel (Übersicht) + Vollbildansicht + Fahrzeug-Menü. Höhen sind
bewusst fest statt prozentual/vererbt - eine relative Höhe wird von
panel_custom nicht zuverlässig durchgereicht (siehe audi-dashboard-
ios.css-Kommentar zur selben Falle). */
.standort-kachel { padding: 0; overflow: hidden; }
.standort-kachelbtn {
display: block; width: 100%; position: relative;
border: none; padding: 0; margin: 0; background: none; cursor: pointer;
}
.standort-vorschau { width: 100%; height: 190px; background: var(--tile); }
.standort-leer {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
font-size: 13px; color: var(--fg3); text-align: center; padding: 0 30px;
background: var(--tile);
}
.fahrzeug-pin-wrap { background: none; border: none; }
/* 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: #fff;
filter: drop-shadow(0 2px 4px rgba(0,0,0,.4));
}
.user-pin-wrap { background: none; border: none; }
.user-pin {
position: relative; width: 16px; height: 16px; border-radius: 50%;
background: #0A84FF; border: 3px solid #fff;
box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.user-pin::after {
content: ""; position: absolute; inset: -14px; border-radius: 50%;
background: rgba(10,132,255,.18);
animation: user-pulse 2.4s ease-out infinite;
}
@keyframes user-pulse { 0% { transform: scale(.4); opacity: .9; } 100% { transform: scale(1); opacity: 0; } }
.standort-vollbild { position: relative; height: min(640px, calc(100dvh - 190px)); border-radius: var(--r-tile); overflow: hidden; margin: 0 calc(-1 * var(--seitenrand)); }
.standort-karte { width: 100%; height: 100%; }
.kartensteuerung {
position: absolute; z-index: 400; top: 16px; right: 16px;
display: flex; flex-direction: column; gap: 12px;
}
.kartensteuerung button {
width: 48px; height: 48px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: rgba(16, 20, 26, .84); color: #fff;
border: none; box-shadow: 0 4px 14px rgba(0,0,0,.32);
-webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
cursor: pointer; padding: 0;
}
/* Tag-Theme: helle Kapsel mit dunklem Symbol - die CI-Symbole selbst tragen
fill="currentColor" und folgen der Textfarbe, es gibt keine zweite Datei. */
:host([data-theme="tag"]) .kartensteuerung button {
background: rgba(255,255,255,.92); color: #101418;
box-shadow: 0 4px 14px rgba(0,0,0,.18);
}
.kartensteuerung button:active { transform: scale(.94); }
:host([data-theme="tag"]) .kartensteuerung button:active { background: #fff; }
/* Variante in der Belegkarte: die Karte ist dort nur 210px hoch, ein 48er-
Knopf mit 16px Abstand wuerde ein Viertel davon verdecken. */
.kartensteuerung-klein { top: 10px; right: 10px; }
.kartensteuerung-klein button { width: 36px; height: 36px; }
.kartensteuerung-klein button:disabled { opacity: .45; cursor: default; }
.kartensteuerung-klein button:disabled:active { transform: none; }
/* Sichtbarer Anteil im geschlossenen Zustand (STANDORT_PEEK_PX im Frontend) -
beide Werte müssen zusammenpassen, siehe Kommentar dort. Der Rest des
Blatts wird nach unten geschoben und mit einem Zug nach oben geöffnet. */
.standortmenu {
position: absolute; z-index: 410; left: 0; right: 0; bottom: 0;
--standort-peek: 96px;
/* Deckend, nicht durchscheinend - unter dem Blatt liegt die Karte. */
background: var(--tile-deckend, var(--canvas));
border-radius: var(--r-tile) var(--r-tile) 0 0;
box-shadow: 0 -6px 24px rgba(0,0,0,.35);
/* Kein env(safe-area-inset-bottom) mehr: das Blatt liegt nicht am unteren
Bildschirmrand, sondern ueber der Tableiste - und die haelt den Bereich
des Home-Indikators bereits frei. Der Einzug wurde dadurch doppelt
gezaehlt, das Blatt war auf dem Geraet rund 16px hoeher als noetig und
schob sich entsprechend weiter hoch. Am Rechner faellt das nicht auf,
dort ist der Einzug 0. */
padding: 0 20px 34px;
transform: translateY(calc(100% - var(--standort-peek)));
transition: transform .28s cubic-bezier(.22,.61,.36,1);
}
/* Aufgezogen bleibt unter den Knoepfen ein fester Abstand stehen, statt dass
sie buendig mit der Menueleiste abschliessen.
Vorher stand hier min-height:50% - Apples "medium detent". Gemessen ergab
das 89px (Panel) bzw. 76px (App) Luft unter dem letzten Knopf, und das war
sichtbar zu viel: das Blatt zog ueber die Haelfte des Bildschirms auf,
obwohl sein Inhalt dort laengst zu Ende war. Eine Raste ist nur dann
richtig, wenn zwischen ihr und der Inhaltshoehe kein grosses Loch klafft.
Jetzt bestimmt der Inhalt die Hoehe, und die 34px Fussabstand halten die
Knoepfe von der Leiste frei - hergeleitet aus der Beanstandung, nicht
geraten: 18px waren zu wenig, 76-89px zu viel. */
.standortmenu.offen { transform: translateY(0); }
.standortmenu-griffzone { padding: 10px 0 14px; touch-action: none; cursor: grab; position: relative; }
/* Der Griff ist jetzt ein Knopf (Tastaturzugang zum Menue). Die Knopf-
Voreinstellungen des Browsers werden deshalb zurueckgesetzt; die
Trefferflaeche waechst per Pseudo-Element auf 44px, ohne den sichtbaren
Strich zu vergroessern. */
.standortmenu-griff {
position: relative; display: block; width: 36px; height: 4px;
border: 0; padding: 0; border-radius: 2px;
background: var(--line-strong); margin: 0 auto; cursor: pointer;
}
.standortmenu-griff::after {
content: ""; position: absolute; left: 50%; top: 50%;
transform: translate(-50%, -50%); width: 88px; height: 44px;
}
/* --ios-fill statt --tile: das Blatt selbst ist deckend, im Tagmodus also
weiss - und --tile ist dort ebenfalls #FFFFFF. Der Knopf war damit weiss
auf weiss und als Knopf nicht zu erkennen. Nachts faellt es nicht auf,
weil --tile dort ein Schleier ueber dunklem Grund ist. --ios-fill ist in
diesem Projekt ohnehin das Zeichen fuer "das hier laesst sich bedienen"
und traegt in beiden Themes. */
.standortmenu-schliessen {
position: absolute; top: 14px; right: 0; width: 32px; height: 32px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: var(--ios-fill); border: none; color: var(--fg2); cursor: pointer; padding: 0;
}
/* Sichtbar nur im aufgezogenen Blatt (Begruendung am Knopf im Markup). */
.standortmenu-schliessen { display: none; }
.standortmenu.offen .standortmenu-schliessen { display: flex; }
.standortmenu-kopf { margin-top: 8px; padding-right: 44px; }
.standortmenu-name { font-size: 17px; color: var(--fg); }
.standortmenu-distanz { font-size: 12.5px; color: var(--fg3); margin-top: 4px; }
/* Abstand als leere Zeile zwischen Kopf und Details */
.standortmenu-adresse { font-size: 13px; color: var(--fg2); margin-top: 20px; }
.standortmenu-status { display: flex; align-items: center; gap: 9px; margin-top: 8px; font-size: 13px; color: var(--fg2); }
.standortmenu-status svg { color: var(--fg3); flex: 0 0 auto; }
.standortmenu-werte { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13px; color: var(--fg2); }
.standortmenu-werte svg { color: var(--fg3); flex: 0 0 auto; }
.standortmenu-aktionen { display: flex; gap: 10px; margin-top: 18px; padding-bottom: 4px; }
/* Beide Pillen folgen der Knopfsprache des restlichen Panels (.aktion /
.aktion.primaer weiter oben): Umriss fuer die Nebenaktion, gefuellt fuer
die Hauptaktion.
Vorher stand hier background: var(--tile) fuer "Teilen" und
background: var(--line-strong) fuer "Route". Beides ging im Tagmodus
schief: das Blatt darunter ist var(--tile-deckend), und die iOS-Auflage
(audi-dashboard-ios.css) setzt im Tagmodus --tile UND --tile-deckend
beide auf #FFFFFF - "Teilen" war damit weiss auf weiss, Kontrast 1,0:1,
also schlicht unsichtbar. --line-strong als Flaeche war ausserdem eine
Linienfarbe in der Rolle einer Fuellung.
Der Umriss haengt jetzt an denselben Tokens wie jeder andere Knopf und
traegt deshalb in beiden Themes und auch unter der iOS-Auflage, ohne dass
hier eine eigene Farbe gepflegt werden muss. */
.standort-pille {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
height: 40px; padding: 0 20px; border-radius: var(--r-pill);
border: 1px solid var(--line-strong); background: none; color: var(--fg);
font-family: inherit; font-size: 14px; text-decoration: none; cursor: pointer;
}
.standort-pille.primaer {
background: var(--fg); border-color: var(--fg); color: var(--canvas);
}
.standort-pille:active { transform: scale(.97); }
.standort-pille[disabled] { opacity: .4; pointer-events: none; }
/* ------------------------------------------------------- Bilderverwaltung */
/* ---------------------------------------------------------- Blätter
Apples Vorgabe kennt für etwas Modales auf dem Telefon kein frei
schwebendes Fenster: es kommt von der unteren Kante. Das Aktionsblatt
(eine kurze Liste von Wegen) und das Formularblatt (eine Eingabe) sind
die beiden Formen; die Karte in der Bildschirmmitte war keine von
beiden und stand ausserdem ohne abgedunkelten Grund über der voll
sichtbaren Seite.
Der Schleier gehört dazu und ist nicht Geschmack: modal heisst, dass
der Inhalt dahinter nicht bedienbar ist - das soll man sehen. */
.bildmenu-catcher {
position: absolute; inset: 0; z-index: 1000;
background: rgba(0,0,0,.45);
animation: sheet-fade .18s ease-out;
}
.bildmenu {
position: absolute; z-index: 1001;
left: 10px; right: 10px;
bottom: max(10px, env(safe-area-inset-bottom));
top: auto;
transform: none;
background: var(--tile-deckend, var(--canvas));
border-radius: 16px;
overflow: hidden;
padding: 0;
display: flex; flex-direction: column;
animation: sheet-rein .24s cubic-bezier(.22,.61,.36,1);
}
/* Die beiden Lage-Varianten sind gegenstandslos geworden - ein Blatt
kommt immer von unten. Die Klassen bleiben im Markup stehen, damit die
Aufrufstellen unberührt bleiben; hier tragen sie nichts mehr bei. */
.bildmenu.rad, .bildmenu.ansicht { left: 10px; right: 10px; top: auto; transform: none; }
.bildmenu button {
display: block; width: 100%; text-align: left;
background: none; border: none;
color: var(--fg); font-family: inherit; font-size: 16px;
padding: 16px 18px; min-height: 52px; border-radius: 0;
border-bottom: 1px solid var(--line);
cursor: pointer;
}
.bildmenu button:last-child { border-bottom: none; }
.bildmenu button:active { background: var(--shade); }
.bildmenu button.loeschen { color: var(--bad); }
/* Formularblatt (siehe Kasten oben): unten angeschlagen, nur oben rund,
blickdicht. Stand vorher als Karte 60px unter dem oberen Rand - ausser
Daumenreichweite und ohne sichtbaren Weg hinaus. */
.sdpopup {
position: absolute; z-index: 1001;
left: 0; right: 0; bottom: 0; top: auto;
background: var(--tile-deckend, var(--canvas));
border-radius: 16px 16px 0 0;
padding: var(--sp-4) 20px max(20px, env(safe-area-inset-bottom));
animation: blatt-hoch .28s cubic-bezier(.22,.61,.36,1);
}
@keyframes blatt-hoch { from { transform: translateY(100%) } to { transform: none } }
/* Beleg-Fenster: PDF hineinziehen (Rechner) oder einfügen (Telefon). */
.beleg-catcher {
position: absolute; inset: 0; z-index: 1000;
background: rgba(0,0,0,.45);
animation: sheet-fade .18s ease-out;
}
/* Formularblatt (siehe Kasten oben). Traegt drei Fenster: Beleg
hochladen, Daten importieren, Datensatz sichern/laden. */
.beleg-popup {
position: absolute; z-index: 1001;
left: 0; right: 0; bottom: 0;
top: auto; transform: none;
max-height: 88%;
overflow-y: auto;
/* --tile-2 ist im Nacht-Theme transluzent; ein Blatt ueber der Seite
braucht einen blickdichten Grund. */
background: var(--tile-deckend, var(--canvas));
border-radius: 16px 16px 0 0;
padding: var(--sp-4) 20px max(20px, env(safe-area-inset-bottom));
animation: blatt-hoch .28s cubic-bezier(.22,.61,.36,1);
}
.beleg-drop {
margin-top: var(--sp-3);
border: 1px dashed var(--line-strong);
border-radius: 14px;
padding: 28px var(--sp-4);
text-align: center;
display: flex; flex-direction: column; gap: 6px;
}
.beleg-drop strong { font-size: 15px; color: var(--fg); }
.beleg-drop span { font-size: 12.5px; color: var(--fg2); }
.beleg-drop.darueber { border-color: var(--fg); border-style: solid; background: var(--shade); }
/* Kopfzeile der Reifen-Kachel: Überschrift+km links, Radfoto rechts, beide
oben bündig - ersetzt die frühere Überlagerung (Montiert absolut über dem
Foto), seit Überschrift und km ihren eigenen Platz über Montiert bekommen
haben. */
.radkopf-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); }
.radkopf { display: flex; flex-direction: column; }
/* Editier-Symbol links neben der km-Anzeige (Audi-CI edit-s) - oeffnet das
Korrekturfeld fuer den manuell gepflegten Kilometerstand des Reifensatzes.
44x44pt Tippflaeche per unsichtbarem ::before wie bei .montiert/.zahnrad,
optisch bleibt nur das kleine Symbol sichtbar. */
.km-edit {
position: relative;
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 22px;
border: none; background: none; color: var(--fg3);
padding: 0; cursor: pointer; flex: 0 0 auto;
}
.km-edit::before { content: ""; position: absolute; inset: -11px; }
.km-edit:hover { color: var(--fg); }
.km-edit:active { color: var(--fg); transform: scale(.92); }
/* Zeile aus einer bearbeitbaren Liste entfernen (z. B. Selbstbeteiligung im
Vertrag) - 44x44pt Tippflaeche per unsichtbarem ::before wie bei .km-edit,
optisch bleibt nur das kleine × sichtbar. */
.zeile-loeschen {
position: relative;
display: inline-flex; align-items: center; justify-content: center;
width: 22px; height: 22px;
border: none; background: none; color: var(--fg3);
font-size: 18px; line-height: 1;
padding: 0; cursor: pointer; flex: 0 0 auto;
}
.zeile-loeschen::before { content: ""; position: absolute; inset: -11px; }
.zeile-loeschen:hover { color: var(--bad); }
.zeile-loeschen:active { color: var(--bad); transform: scale(.92); }
/* Kompakte Pille statt voller Breite (Apple-HIG-Pull-down-Maß): normale
Flusslage unter der Kachel-Überschrift/km, nicht mehr über dem Foto
überlagert (das saß nur dort, solange Überschrift+km diesen Platz noch
nicht beanspruchten). */
.montiert {
position: relative;
display: inline-flex; align-items: center; justify-content: center;
font-family: inherit; font-size: 12.5px; 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. */
.sheet-catcher {
position: absolute; inset: 0; z-index: 1200;
background: rgba(0,0,0,.45);
animation: sheet-fade .18s ease-out;
}
.sheet {
position: absolute; z-index: 1201;
left: 10px; right: 10px;
bottom: max(10px, env(safe-area-inset-bottom));
display: flex; flex-direction: column; gap: 8px;
animation: sheet-rein .24s cubic-bezier(.22,.61,.36,1);
}
@keyframes sheet-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes sheet-rein { from { transform: translateY(18px); opacity: 0 } to { transform: none; opacity: 1 } }
.sheet-gruppe {
/* --tile-2 is translucent in the iOS overlay's night theme (rgba white,
meant for tile-on-tile layering) - a confirm sheet sits directly over
whatever page content triggered it, so a see-through card made titles
like "Doppelt zugeordnet" hard to read against the busy background
bleeding through. Same fix already applied to .setup-popup/.standortmenu
for the same reason - opaque surface token, with the --canvas fallback
this file already uses elsewhere for when the (purely additive, in
principle removable) overlay isn't loaded. */
background: var(--tile-deckend, var(--canvas));
border-radius: 16px;
overflow: hidden;
display: flex; flex-direction: column;
}
.sheet-kopf {
padding: 15px 18px 14px;
border-bottom: 1px solid var(--line);
display: flex; flex-direction: column; gap: 4px;
}
.sheet-kopf strong { font-size: 15px; font-weight: 400; color: var(--fg); }
.sheet-kopf span { font-size: 13px; color: var(--fg2); line-height: 1.45; }
.sheet-gruppe button {
background: none; border: none;
font-family: inherit; font-size: 16px;
color: var(--fg);
padding: 16px 18px; min-height: 52px;
text-align: left; cursor: pointer;
border-bottom: 1px solid var(--line);
}
.sheet-gruppe button:last-child { border-bottom: none; }
.sheet-gruppe button:active { background: var(--shade); }
.sheet-gruppe button.loeschen { color: var(--bad); }
.sheet-abbrechen {
background: var(--tile-deckend, var(--canvas)); border: none; border-radius: 16px;
font-family: inherit; font-size: 16px; color: var(--fg);
padding: 16px; min-height: 52px; cursor: pointer;
}
.sheet-abbrechen:active { background: var(--shade); }
/* ------------------------------------------------------- Setup-Popup
Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten ->
Setup). Gleiche Catcher/Karten-Bauweise wie .bildmenu/.sdpopup oben,
aber großformatig und scrollbar statt Bottom-Sheet-Größe; nutzt die
bereits vorhandenen sheet-fade/sheet-rein-Keyframes. z-index bewusst
unter .sheet (1200er), damit ein Fehlerhinweis (hinweis()) bei
fehlgeschlagenem Speichern über dem noch offenen Setup-Popup liegt. */
.setup-catcher {
position: absolute; inset: 0; z-index: 1000;
background: rgba(0,0,0,.45);
animation: sheet-fade .18s ease-out;
}
.setup-popup {
position: absolute; z-index: 1001;
left: 10px; right: 10px;
top: max(10px, env(safe-area-inset-top));
bottom: max(10px, env(safe-area-inset-bottom));
/* --tile-2 ist inzwischen transluzent (rgba(255,255,255,.1) im Nacht-
Theme) - fuer ein frei ueber dem Inhalt schwebendes Popup braucht es
einen blickdichten Hintergrund, gleiches Muster wie .standortmenu
oben. */
background: var(--tile-deckend, var(--canvas));
border-radius: var(--r-tile);
display: flex; flex-direction: column;
overflow: hidden;
animation: sheet-rein .24s cubic-bezier(.22,.61,.36,1);
}
.setup-kopf {
flex: 0 0 auto;
padding: 18px 20px 14px;
border-bottom: 1px solid var(--line);
display: flex; flex-direction: column; gap: 4px;
}
.setup-kopf strong { font-size: 17px; font-weight: 400; color: var(--fg); }
.setup-kopf span { font-size: 13px; color: var(--fg2); line-height: 1.45; }
.setup-kopf-schalter { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.setup-kopf-schalter span { font-size: 13px; color: var(--fg); }
.setup-koerper { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 4px 20px 20px; }
.setup-gruppe { margin-top: 18px; }
.setup-gruppe:first-child { margin-top: 6px; }
.setup-gruppe-titel { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--fg3); margin-bottom: 6px; }
.setup-feldzeile { padding: 12px 0; border-bottom: 1px solid var(--line); }
.setup-feldzeile:last-child { border-bottom: none; }
/* Nutzerwunsch: die Kennung des zugeordneten Sensors (entitaetIdKurz() in
audi-dashboard-app.js) steht NEBEN der Feld-Überschrift, nicht mehr wie
die frühere statische Beschreibung darunter - deshalb Zeile statt Spalte.
Die erwartete Einheit (feldEinheitAnzeige()) hängt direkt am Label selbst
("Tankfüllstand [%]"), losgelöst davon, ob/welcher Sensor zugeordnet ist.
2026-08-28, Nutzerkorrektur: EINE Zeile "(z. B. sensor_id)" statt zwei -
der Klammertext "(z. B. "/")" in derselben Größe/Farbe wie das Label links
(.setup-feld-label), die Sensor-ID selbst darin in derselben kleinen/
grauen Schrift wie die Einheit daneben (.setup-feld-einheit,
"[on/off]"/"[km]") - deshalb teilt sich .setup-feld-hinweis die Klasse
.setup-feld-einheit für den inneren Span statt einer eigenen Regel. */
.setup-feld-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.setup-feld-label { font-size: 14px; color: var(--fg); flex: 0 1 auto; }
.setup-feld-einheit { color: var(--fg3); font-size: 12.5px; }
.setup-feld-hinweis {
font-size: 14px; color: var(--fg); line-height: 1.4; text-align: right;
max-width: 55%;
}
.setup-unterfelder { display: flex; flex-direction: column; gap: 12px; }
.setup-unterfeld { display: flex; flex-direction: column; gap: 4px; }
/* Positionsname links, Erwartungswert rechts - derselbe Bau wie
.setup-feld-kopf eine Ebene darueber, damit beide Zeilen dieselbe Kante
haben. Der Hinweis erbt hier die kleine graue Schrift des Labels; die
14px/--fg-Form von .setup-feld-hinweis passt nur in die Kopfzeile. */
.setup-unterfeld-label {
font-size: 12.5px; color: var(--fg3);
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.setup-unterfeld-label .setup-feld-hinweis {
font-size: inherit; color: inherit; max-width: none; flex: 0 0 auto;
}
.entitaet-combo { position: relative; }
.entitaet-combo-icon {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
color: var(--fg3); display: flex; pointer-events: none;
}
.entitaet-combo-input {
width: 100%; box-sizing: border-box;
background: var(--tile-deckend, var(--canvas)); border: 1px solid var(--line); color: var(--fg);
font-family: inherit; font-size: 16px; /* siehe .feld input weiter oben: >=16px gegen Safari-Autozoom */
padding: 11px 34px 11px 36px;
border-radius: 12px;
text-align: left;
-webkit-appearance: none; appearance: none;
}
.entitaet-combo-input:focus { outline: none; border-color: var(--red); }
/* Die Zuordnung passt zum Beispielnamen der Rolle (siehe
entitaetPasstZumBeispiel). Bewusst zurueckhaltend: eine Bestaetigung im
Vorbeigehen, kein Ausrufezeichen. Deshalb die Farbe ueber color-mix aus dem
vorhandenen Erfolgs-Ton und dem Feldgrund statt eines eigenen Gruens - so
bleibt sie in beiden Themen ruhig und passt zum Rest der Oberflaeche.
Der Rahmen traegt etwas mehr Farbe als die Flaeche, sonst verschwindet die
Markierung auf dunklem Grund. */
.entitaet-combo-input.passend {
background: color-mix(in srgb, var(--ok, #2e7d32) 9%, var(--tile-deckend, var(--canvas)));
border-color: color-mix(in srgb, var(--ok, #2e7d32) 38%, var(--line));
}
.entitaet-combo-input.passend:focus { border-color: var(--red); }
.entitaet-liste {
position: absolute; z-index: 5; left: 0; right: 0; top: calc(100% + 4px);
max-height: 240px; overflow-y: auto;
background: var(--tile-deckend, var(--canvas)); border: 1px solid var(--line-strong);
border-radius: 12px;
box-shadow: 0 12px 28px rgba(0,0,0,.35);
}
.entitaet-eintrag {
display: flex; flex-direction: column; gap: 1px;
width: 100%; text-align: left;
background: none; border: none; border-bottom: 1px solid var(--line);
color: var(--fg); font-family: inherit;
padding: 9px 12px; cursor: pointer;
}
.entitaet-eintrag:last-child { border-bottom: none; }
.entitaet-eintrag:active, .entitaet-eintrag.aktiv { background: var(--shade); }
.entitaet-eintrag-name { font-size: 14px; color: var(--fg); }
.entitaet-eintrag-id { font-size: 12.5px; color: var(--fg3); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.entitaet-eintrag-wert { font-size: 12.5px; color: var(--fg2); margin-top: 1px; }
.entitaet-eintrag-wert.unbekannt { color: var(--warn); }
.entitaet-leer { padding: 14px 12px; font-size: 13px; color: var(--fg2); }
.setup-feld-warnung { font-size: 12.5px; color: var(--warn); line-height: 1.4; margin-top: 6px; }
.setup-fuss {
flex: 0 0 auto;
display: flex; gap: 10px;
padding: 14px 20px max(14px, env(safe-area-inset-bottom));
border-top: 1px solid var(--line);
}
.setup-fuss .aktion { flex: 1; margin-top: 0; }
/* ------------------------------------------------- B3: Push/Pop
Seitenwechsel wie auf iOS: neue Ebene kommt von rechts, zurück geht sie
dorthin. */
.push-rein { animation: push-rein .26s cubic-bezier(.32,.72,0,1); }
.push-raus { animation: push-raus .26s cubic-bezier(.32,.72,0,1); }
@keyframes push-rein { from { transform: translateX(26px); opacity: 0 } to { transform: none; opacity: 1 } }
@keyframes push-raus { from { transform: translateX(-18px); opacity: 0 } to { transform: none; opacity: 1 } }
/* D5: Leerzustand nennt die Ursache und die nächste Handlung. */
.leer { display: flex; flex-direction: column; gap: 8px; padding: 4px 0 2px; }
.leer strong { font-size: 16px; font-weight: 400; color: var(--fg); }
.leer p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--fg2); text-wrap: pretty; }
/* B7 */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
scroll-behavior: auto !important;
}
}