Musterseite verworfen, Zahnrad-Regression behoben, Fotomenue als Aktionsblatt

Musterseite entfernt (companion-app/muster, vite.muster.config.ts,
/config/www/dmmuster). Ansage des Eigentuemers: sie fuehrt nur zu neuen
Fehlerquellen. Ausloeser war ein Befund, den ich als App-Fehler gemeldet hatte
- Ringe und Typenschild schwarz auf schwarz - und der nur dort bestand.

Behoben:

* Das Zahnrad im Autohaus-Kasten hatte seine Ecke verlassen (58/288 statt
  8/8). Ursache war mein HIG-Durchgang von gestern: .zahnrad kam in die
  Gruppe fuer die 44x44-Trefferflaeche und bekam damit position:relative,
  was das absolute aus dem Basisblatt ueberschrieb. Die Auflage war
  ueberfluessig - .zahnrad ist dort schon 44x44.
* Das Fotomenue hing mit position:absolute an der Kachel statt am
  Bildschirm und lag bei der hohen Raeder-Kachel ausserhalb des Sichtfelds;
  einen Abbrechen-Knopf hatte es nie. Das Panel benutzt jetzt dasselbe
  Aktionsblatt wie die App. .bildmenu ist samt Zustand, Markup, totem
  Handler und CSS entfernt.
* Der Blattkopf las sich als dritte Auswahl (linksbuendig, 15px, --fg).
  Jetzt zentriert, 13px, gedaempft - Apples Muster, in beiden Codebasen.
* "Anstehende Termine" stand 5px ueber "Oelwechsel" und wirkte als dessen
  Beischrift. Entfernt.
* Wartungsplan-Formular: Art nach oben, Datum 124px statt Browser-Vorgabe,
  Werkstatt ueber die volle Zeile (218 -> 306px), Kosten 116px. Zwei
  Erklaertexte entfernt.
* Thema folgt jetzt einer Systemumstellung im laufenden Betrieb. Vorher las
  es prefers-color-scheme genau einmal beim Laden. Drei Tests, die ohne die
  Behebung nachweislich fallen.

Geprueft: 295 App-Tests, Panel node --check, 0 Tracebacks, alle Aenderungen
live an der Testinstanz gemessen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 02:24:08 +02:00
parent 863284e540
commit e9c16325cc
16 changed files with 2674 additions and 2467 deletions
@@ -1 +1 @@
{"version":"2026.9.5.3","sha256":"6c9e0c3ef0429fd6ec6264af982494b9c699e8b3483fea145946d991805b0e47","bytes":324399,"gebaut":"2026-09-04T23:51:45Z"}
{"version":"2026.9.5.10","sha256":"6bdd14f277d1d3f6f2c24dc0ab660f2751daac8fc71efdb14440df87fe7a5af6","bytes":324448,"gebaut":"2026-09-05T00:22:21Z"}
@@ -2906,7 +2906,12 @@ let svAuswahl = 0;
function vService() {
const sv = CAR.service;
return `
<div class="tile"><span class="label">Anstehende Termine</span>
<!-- Ohne Kachelueberschrift: "Anstehende Termine" stand 13px grau und nur
5px ueber "Oelwechsel" (16px, --fg) und las sich damit als dessen
Beischrift statt als Titel (gemessen und gemeldet am 05.09.2026). Die
drei Zeilen benennen sich selbst, und die Seite heisst bereits
"Service" - eine Ueberschrift traegt hier nichts bei. -->
<div class="tile">
${termine().map((t) => {
const artIcon = ART_ICON[t.art];
const iconMarkup = artIcon ? ciSVG(artIcon, ART_ICON_GROESSE[t.art]) : "";
@@ -3295,25 +3300,27 @@ function vSbuch() {
<span class="label" style="margin-top:12px">${e.datum} · ${esc(e.werkstatt)}</span>
</div>
<div class="tile"><span class="label">Eintrag</span>
<div class="feld"><label>Datum</label><input value="${esc(e.datum)}" data-sb="${i}|datum"></div>
<div class="feld"><label>Kilometerstand</label>
<span class="mitEinheit"><input type="number" value="${e.km}" data-sb="${i}|km"><span>km</span></span></div>
<div class="feld"><label>Art</label>
<select data-sb="${i}|art">
${!artBekannt ? `<option value="${esc(e.art)}" selected>${esc(e.art)}</option>` : ""}
${SERVICE_ARTEN.map((a) => `<option ${a === e.art ? "selected" : ""}>${esc(a)}</option>`).join("")}
</select></div>
<div class="feld"><label>Werkstatt</label><input value="${esc(e.werkstatt)}" data-sb="${i}|werkstatt"></div>
<div class="feld"><label>Datum</label><input class="w-datum" value="${esc(e.datum)}" data-sb="${i}|datum"></div>
<div class="feld"><label>Kilometerstand</label>
<span class="mitEinheit"><input type="number" value="${e.km}" data-sb="${i}|km"><span>km</span></span></div>
<!-- Label oben, Feld ueber die ganze Zeile: ein echter Werkstattname
("Audi AG Kundendienst Center Ingolstadt") braucht 322px, neben einem
Label bleiben auf 375px aber nur 218 - gemessen am 05.09.2026. Dieselbe
Bauweise wie der Reifen-Kommentar. -->
<div class="feld feld--breit"><label>Werkstatt</label>
<input value="${esc(e.werkstatt)}" data-sb="${i}|werkstatt"></div>
<div class="feld" style="border-bottom:0"><label>Kosten</label>
<span class="mitEinheit"><input type="number" step="0.01" value="${e.kosten ?? ""}"
<span class="mitEinheit"><input class="w-betrag" type="number" step="0.01" value="${e.kosten ?? ""}"
data-sb="${i}|kosten" placeholder="noch offen"><span></span></span></div>
<span class="label" style="margin-top:14px">Kosten fließen in die Jahresübersicht ein</span>
</div>
<div class="tile"><span class="label">Durchgeführte Arbeiten</span>
<textarea class="notiz" data-sb="${i}|arbeiten" rows="7"
placeholder="Was wurde gemacht, welche Teile wurden getauscht, was ist aufgefallen">${esc(e.arbeiten || "")}</textarea>
<span class="label" style="margin-top:12px">Beim Verkauf ist diese Liste mehr wert als die Rechnung
sie zeigt, dass gepflegt wurde</span>
</div>
${istEntwurf ? `<button class="aktion" data-sbspeichern>Speichern</button>`
: `<button class="aktion loeschen" data-sbdel="${i}">Eintrag löschen</button>`}`;
@@ -3647,19 +3654,13 @@ function vEinst() {
${(() => {
const info = bildInfo(startIndex());
return `<div style="margin-top:12px">
<div data-bildklick="${esc(info.datei)}">
<div data-bildklick="${esc(info.datei)}" data-bildname="${esc(info.label)}">
${/* "klein" = das Bild bestimmt die Hoehe, statt im 16:9-Kasten
beschnitten zu werden (siehe .carfix.klein). Wer hier prueft,
ob das richtige Foto hochgeladen ist, muss es ganz sehen. */""
}${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein", null, true)}
</div>
<input type="file" accept="image/*" hidden data-bildupload="${esc(info.datei)}">
${bildMenuOffen === info.datei ? `
<div class="bildmenu-catcher" data-bildmenu-schliessen></div>
<div class="bildmenu ansicht">
<button type="button" data-bildersetzen="${esc(info.datei)}">Foto ersetzen</button>
<button type="button" class="loeschen" data-bildloeschen="${esc(info.datei)}">Löschen</button>
</div>` : ""}
</div>`;
})()}
</div>
@@ -3879,7 +3880,26 @@ let debugOffen = false;
"Speichern" uebernimmt ihn, weil derselbe Wert den Schlaf-Timeout des
Dongles stellt. Siehe die Kachel in vEinst(). */
let PAUSENZEIT_ENTWURF = null;
let bildMenuOffen = null;
/* Das Blatt zu einem vorhandenen Foto: ersetzen oder loeschen. Loest
bildMenuOffen ab - der Zustand wird nicht mehr gebraucht, weil das Blatt
selbst der Zustand ist (siehe aktionsblatt()). */
function bildAktionsblatt(dateiname, name) {
aktionsblatt(name, [
{ text: "Foto ersetzen", tun: () => {
const input = ROOT.querySelector(`[data-bildupload="${dateiname}"]`);
if (input) input.click();
} },
{ text: "Foto löschen", destruktiv: true, tun: () => {
bestaetigen(LOESCH_TEXT.bild, LOESCH_HINWEIS.bild, "Foto löschen", () => {
(async () => {
try { await HASS.callService(DOMAIN, "bild_loeschen", { dateiname }); }
catch (err) { hinweis("Löschen fehlgeschlagen", err.message); return; }
bildVersion = Date.now(); render();
})();
});
} },
]);
}
let smartdealPopupOffen = false;
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist.
@@ -4172,7 +4192,7 @@ function vReifen() {
${t.km != null ? de(t.km) : ""}<span class="unit">km</span>
</div>
</div>
<div data-bildklick="${radDatei}">
<div data-bildklick="${radDatei}" data-bildname="${esc(radLabel)}">
${bildMitPlatzhalter(bildAdresse(radDatei), radDatei, radLabel, "radbild")}
</div>
</div>
@@ -4189,12 +4209,6 @@ function vReifen() {
<button class="aktion" data-kmauf="${k}">Abbrechen</button>
</div>` : ""}
<input type="file" accept="image/*" hidden data-bildupload="${radDatei}">
${bildMenuOffen === radDatei ? `
<div class="bildmenu-catcher" data-bildmenu-schliessen></div>
<div class="bildmenu rad">
<button type="button" data-bildersetzen="${radDatei}">Foto ersetzen</button>
<button type="button" class="loeschen" data-bildloeschen="${radDatei}">Löschen</button>
</div>` : ""}
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:var(--sp-4)">
<button data-satz="${k === "sommer" ? "Sommer" : "Winter"}" class="montiert ${aktiv ? "on" : ""}">Montiert</button>
<button class="iconbtn archivbtn" data-reifenarchivieren="${k}" aria-label="Neue Räder anlegen">${ciSVG(CI.packageS, 18)}</button>
@@ -5178,6 +5192,34 @@ function themeGewaehlt() {
if (gespeichert === "nacht" || gespeichert === "tag") return gespeichert;
return window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches ? "tag" : "nacht";
}
/* Systemumstellung im laufenden Betrieb nachziehen - Gegenstueck zum
gleichnamigen Horcher in companion-app/src/theme.ts.
themeGewaehlt() liest die Systemeinstellung genau einmal, beim Einhaengen
des Elements. Stellte das Geraet danach um - etwa durch die automatische
Umschaltung bei Sonnenuntergang -, blieb das Panel auf dem alten Stand
stehen, bis jemand neu lud. Das Stylesheet haengt an data-theme, nicht an
der Medienabfrage, es blieb also stimmig - aber eben stimmig falsch.
Nur solange niemand selbst gewaehlt hat: eine gespeicherte Wahl bleibt. */
function themaSystemNachziehen() {
if (!window.matchMedia) return;
const dunkel = window.matchMedia("(prefers-color-scheme: dark)");
const nachziehen = () => {
let gespeichert = null;
try { gespeichert = localStorage.getItem(THEME_KEY); } catch (e) { /* Privatmodus */ }
if (gespeichert === "nacht" || gespeichert === "tag") return;
if (!HOST) return;
const neu = dunkel.matches ? "nacht" : "tag";
if (HOST.dataset.theme === neu) return;
HOST.dataset.theme = neu;
themeIcon();
tiles();
};
if (dunkel.addEventListener) dunkel.addEventListener("change", nachziehen);
else if (dunkel.addListener) dunkel.addListener(nachziehen);
}
function themeSetzen(modus) {
HOST.dataset.theme = modus;
try { localStorage.setItem(THEME_KEY, modus); } catch (e) { /* Privatmodus */ }
@@ -5197,6 +5239,22 @@ function hinweis(titel, text) {
sheet = { titel, text, knopf: null, aktion: null };
render();
}
/* Mehrere Wahlmoeglichkeiten in einem Blatt, plus abgesetztes Abbrechen -
Apples Aktionsblatt. Gegenstueck zu <ActionSheet> in der App
(design-system), das dort schon seit dem 31.08.2026 das schwebende
Bildmenue abgeloest hat.
Warum das Panel nachzieht: das alte .bildmenu war mit position:absolute an
die KACHEL geheftet, nicht an den Bildschirm. Bei einer hohen Kachel - den
Raedern - lag es dadurch unterhalb des Sichtfelds, und einen Weg hinaus gab
es auch nicht: wer das Rad nur versehentlich antippte, fand kein Abbrechen.
Beides am 05.09.2026 vom Eigentuemer gemeldet. Das Blatt haengt am Wurzel-
Element und ist deshalb immer zu sehen. */
function aktionsblatt(titel, aktionen) {
sheet = { titel, text: null, knopf: null, aktion: null, aktionen };
render();
}
/* ------------------------------------------------------- Beleg-Upload
Ein Weg für alle drei Quellen (Ziehen, Einfügen, Dateiauswahl). Kein
serviceRufen() hier: dessen catch loggt nur in die Konsole - siehe den
@@ -5501,6 +5559,15 @@ function vBelegPopup() {
function sheetMarkup() {
if (!sheet) return "";
const kopf = `<div class="sheet-kopf"><strong>${esc(sheet.titel)}</strong>${sheet.text ? `<span>${esc(sheet.text)}</span>` : ""}</div>`;
if (sheet.aktionen) {
const knoepfe = sheet.aktionen.map((a, i) =>
`<button data-sheet-aktion="${i}" class="${a.destruktiv ? "loeschen" : ""}">${esc(a.text)}</button>`).join("");
return `<div class="sheet-catcher" data-sheet-abbrechen></div>
<div class="sheet" role="dialog" aria-modal="true">
<div class="sheet-gruppe">${kopf}${knoepfe}</div>
<button class="sheet-abbrechen" data-sheet-abbrechen>Abbrechen</button>
</div>`;
}
const aktion = sheet.knopf
? `<button data-sheet-ok class="${sheet.destruktiv ? "loeschen" : ""}">${esc(sheet.knopf)}</button>`
: `<button data-sheet-abbrechen>Verstanden</button>`;
@@ -6281,6 +6348,13 @@ function ereignisseVerdrahten() {
ROOT.addEventListener("click", (e) => {
// B4: Das Action Sheet liegt über allem und fängt daher zuerst ab.
if (e.target.closest("[data-sheet-abbrechen]")) { sheetSchliessen(); return; }
const blattAktion = e.target.closest("[data-sheet-aktion]");
if (blattAktion) {
const gewaehlt = sheet && sheet.aktionen && sheet.aktionen[Number(blattAktion.dataset.sheetAktion)];
sheet = null; render();
if (gewaehlt && gewaehlt.tun) gewaehlt.tun();
return;
}
if (e.target.closest("[data-sheet-ok]")) {
const aktion = sheet && sheet.aktion;
sheet = null; render();
@@ -6370,18 +6444,6 @@ function ereignisseVerdrahten() {
bestaetigen(LOESCH_TEXT.sbuch, LOESCH_HINWEIS.sbuch, "Löschen", () => { eintragLoeschen("sbuch", id); go("service"); });
return;
}
const bildDel = e.target.closest("[data-bildloeschen]");
if (bildDel) {
const dateiname = bildDel.dataset.bildloeschen;
bestaetigen(LOESCH_TEXT.bild, LOESCH_HINWEIS.bild, "Foto löschen", () => {
(async () => {
try { await HASS.callService(DOMAIN, "bild_loeschen", { dateiname }); }
catch (err) { hinweis("Löschen fehlgeschlagen", err.message); return; }
bildMenuOffen = null; bildVersion = Date.now(); render();
})();
});
return;
}
// Fahrzeugbild (Radbild wie Fahrzeugbilder-Slots): vorhandenes Foto -> Popup
// mit Ersetzen/Löschen, fehlendes Foto (Platzhalter, bild-fehlt) -> Dateiauswahl
// direkt öffnen.
@@ -6394,18 +6456,10 @@ function ereignisseVerdrahten() {
const input = container.querySelector(`[data-bildupload="${dateiname}"]`);
if (input) input.click();
} else {
bildMenuOffen = dateiname; render();
bildAktionsblatt(dateiname, bildKlick.dataset.bildname || "Foto");
}
return;
}
if (e.target.closest("[data-bildmenu-schliessen]")) { bildMenuOffen = null; render(); return; }
const bildErsetzen = e.target.closest("[data-bildersetzen]");
if (bildErsetzen) {
const container = bildErsetzen.closest(".tile");
const input = container.querySelector(`[data-bildupload="${bildErsetzen.dataset.bildersetzen}"]`);
if (input) input.click();
return;
}
if (e.target.closest("[data-sdabbrechen]")) { smartdealPopupOffen = false; render(); return; }
if (e.target.closest("[data-sdbestaetigen]")) {
const datum = $("#sdPopupBis").value;
@@ -6887,7 +6941,7 @@ function ereignisseVerdrahten() {
const daten_base64 = await bildAlsBase64(klein);
await HASS.callService(DOMAIN, "bild_hochladen", { dateiname, daten_base64 });
} catch (err) { hinweis("Hochladen fehlgeschlagen", err.message); }
bildMenuOffen = null; bildVersion = Date.now(); render();
bildVersion = Date.now(); render();
})();
return;
}
@@ -7312,6 +7366,7 @@ class AudiDashboardPanel extends HTMLElement {
/* A6: Standard folgt der Systemeinstellung, eine manuelle Wahl überlebt den
Reload. Vorher stand die App nach jedem Neuladen wieder auf Nacht. */
HOST.dataset.theme = HOST.dataset.theme || themeGewaehlt();
themaSystemNachziehen();
schriftenLaden();
@@ -236,15 +236,10 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
folgen der neuen Lage: das Aktionsblatt steht frei ueber der unteren
Kante und ist rundum rund, das Formularblatt sitzt auf der Kante auf und
ist nur oben rund. */
.bildmenu{border:0;border-radius:16px;background:rgba(40,44,52,.92);
-webkit-backdrop-filter:saturate(180%) blur(22px);backdrop-filter:saturate(180%) blur(22px);
box-shadow:0 12px 40px rgba(0,0,0,.5)}
.sdpopup{border:0;border-radius:16px 16px 0 0;background:rgba(40,44,52,.92);
-webkit-backdrop-filter:saturate(180%) blur(22px);backdrop-filter:saturate(180%) blur(22px);
box-shadow:0 -8px 40px rgba(0,0,0,.5)}
:host([data-theme="tag"]) .bildmenu,:host([data-theme="tag"]) .sdpopup{background:rgba(250,250,252,.92)}
.bildmenu button{padding:16px 18px;font-size:16px;min-height:52px;border-bottom:1px solid var(--ios-sep)}
.bildmenu button:last-child{border-bottom:none}
:host([data-theme="tag"]) .sdpopup{background:rgba(250,250,252,.92)}
/* ---------------- Tab-Leiste ---------------- */
.tabbar{border-top:1px solid var(--ios-sep);padding-bottom:26px;
@@ -363,7 +358,7 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
ruhigen Karte. max-width + margin:auto zentriert sie innerhalb des
bestehenden left/right-Abstands, ohne die transform-basierte
Einblend-Animation dieser Elemente zu beruehren. */
.sdpopup,.sheet,.beleg-popup,.bildmenu{max-width:560px;margin-left:auto;margin-right:auto}
.sdpopup,.sheet,.beleg-popup{max-width:560px;margin-left:auto;margin-right:auto}
/* .setup-popup breiter als die übrigen Popups: die Sensorkennung neben der
Feld-Überschrift (entitaetIdKurz()) kann ein echter Rohsensorname von
Herstellerseite sein, teils 40-60 Zeichen lang
@@ -401,11 +396,21 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo
Wortgleich mit rahmen.css/screens.css der App (04.09.2026 gemessen).
Das Overlay laesst das Bild unveraendert und vergroessert nur, was der
Finger trifft; Mindesthoehen dort, wo das Element ohnehin Flaeche ist. */
#back, .ringsbtn, .icon-btn, .zahnrad {
/* .zahnrad gehoert NICHT in diese Gruppe. Es ist in audi-dashboard.css
bereits 44x44 gross und mit position:absolute in die obere rechte
Kachelecke gesetzt. Dieses Blatt laedt danach, gleiche Spezifitaet - das
position:relative hier hat das absolute also ueberschrieben, und das
Zahnrad rutschte aus der Ecke in den Textfluss (im Autohaus-Kasten 58px
von oben, 288px von rechts; gemeldet vom Eigentuemer am 05.09.2026).
Die Auflage brauchte es ohnehin nie: die 44x44 stehen schon im Basisblatt.
Hinzugefuegt hatte ich sie beim HIG-Durchgang am 04.09.2026, ohne zu
pruefen, welche Knoepfe ihre Flaeche laengst haben. */
#back, .ringsbtn, .icon-btn {
position: relative;
}
#back::after, .ringsbtn::after, .icon-btn::after, .zahnrad::after {
#back::after, .ringsbtn::after, .icon-btn::after {
content: "";
position: absolute;
left: 50%;
@@ -808,6 +808,18 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
.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; }
/* Feldbreiten nach dem, was drinsteht, statt nach dem Browser-Standardwert.
Ohne diese Klassen bekommt jedes Textfeld dieselbe intrinsische Breite
(~170-190px, Safaris Vorgabe ohne size-Attribut) - unabhaengig vom Inhalt.
Im Wartungsplan hiess das: "01.03.2025" schwamm in einem viel zu breiten
Feld, waehrend ein Werkstattname im selben Mass abgeschnitten wurde
(gemeldet am 05.09.2026). Die Breiten sind an ihrem laengsten erwarteten
Inhalt gemessen ("01.03.2025" bzw. "1234,56"). Der Werkstattname passt in
keine Spalte neben einem Label und steht deshalb in einer .feld--breit-Zeile
ueber die ganze Breite. */
.feld input.w-datum { width: 124px; flex: 0 0 auto; }
.mitEinheit input.w-betrag { width: 116px; }
.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; }
@@ -1319,34 +1331,9 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
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); }
/* .bildmenu ist am 05.09.2026 entfallen: das Fotomenue laeuft jetzt ueber
das Aktionsblatt (aktionsblatt() in audi-dashboard-app.js), wie in der
App. .bildmenu-catcher bleibt - den benutzt das SmartDeal-Blatt. */
/* Formularblatt (siehe Kasten oben): unten angeschlagen, nur oben rund,
blickdicht. Stand vorher als Karte 60px unter dem oberen Rand - ausser
@@ -1486,12 +1473,22 @@ img.carfix.klein { aspect-ratio: auto; height: auto; }
overflow: hidden;
display: flex; flex-direction: column;
}
/* Der Kopf eines Aktionsblatts ist eine Aufschrift, keine Wahl.
Er stand linksbuendig, 15px, in --fg - also fast genau wie die Knopfzeilen
darunter, nur eine Haarlinie davon getrennt. Beim Radfoto ("Sommerrad")
las der Eigentuemer ihn deshalb als dritte Auswahl (gemeldet 05.09.2026).
Apples Aktionsblatt setzt den Titel zentriert, kleiner und in der
gedaempften Farbe - dieselben drei Unterschiede, die ihn hier wieder als
Überschrift lesbar machen. */
.sheet-kopf {
padding: 15px 18px 14px;
padding: 14px 18px 12px;
border-bottom: 1px solid var(--line);
display: flex; flex-direction: column; gap: 4px;
display: flex; flex-direction: column; align-items: center;
text-align: center; gap: 4px;
}
.sheet-kopf strong { font-size: 15px; font-weight: 400; color: var(--fg); }
.sheet-kopf strong { font-size: 13px; font-weight: 600; color: var(--fg2); }
.sheet-kopf span { font-size: 13px; color: var(--fg2); line-height: 1.45; }
.sheet-gruppe button {
background: none; border: none;
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.5.3",
"version": "2026.9.5.10",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": [