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:
@@ -9952,6 +9952,17 @@ strukturell, nicht optisch belegt.
|
||||
|
||||
## CG. Eine Musterseite loest den wiederkehrenden Pruefblocker (2026.9.4.12)
|
||||
|
||||
> **Zurueckgenommen am 05.09.2026.** Die Musterseite ist entfernt -
|
||||
> `companion-app/muster/`, `vite.muster.config.ts`, `dist-muster/` und
|
||||
> `/config/www/dmmuster` im Testcontainer. Ansage des Eigentuemers: *"der
|
||||
> fehler ist nur in deiner musterseite. Verwerfe diese - die fuehrt nur zu
|
||||
> neuen Fehlerquellen"*. Ausloeser war ein Befund, den ich als App-Fehler
|
||||
> gemeldet habe (Vier Ringe und Typenschild schwarz auf schwarz im
|
||||
> Nachtmodus) und der nur auf der Musterseite bestand - genau die Klasse
|
||||
> Fehlalarm, die eine zweite Bauweise derselben Oberflaeche erzeugt. Fuer
|
||||
> Live-Pruefungen gilt jetzt allein der Weg ueber den Zugangstoken
|
||||
> (Abschnitt CP). Der folgende Text bleibt als Aufzeichnung stehen.
|
||||
|
||||
**Das Problem, das seit Wochen wiederkehrt:** die App zeigt ohne gespeicherten
|
||||
Zugang gar nichts, jede OPTISCHE Pruefung haengt damit an einem Zugangstoken -
|
||||
und fast jeder Fehler, den dieses Projekt findet, ist nur am gerenderten Bild
|
||||
@@ -11451,3 +11462,139 @@ sie war es nicht:
|
||||
Beides hat dazu gefuehrt, dass ich zwischenzeitlich alte Dateien gelesen und
|
||||
fuer neue gehalten habe. Gegenprobe, die das aufdeckt: `find <ziel> -name
|
||||
manifest.json | wc -l` muss `1` ergeben.
|
||||
|
||||
## CS. Fuenf Meldungen aus einer Sitzung - und ein Fehlalarm, den ich selbst gebaut hatte (2026.9.5.10)
|
||||
|
||||
### Der Fehlalarm zuerst: die Musterseite ist weg
|
||||
|
||||
Gemeldet als App-Fehler: "RS4 Logo ist im Nachtmodus schwarz und nicht mehr
|
||||
lesbar, Audi Ringe rechts oben sind schwarz auf schwarz". Ich habe daraufhin
|
||||
eine Ursache benannt, die keine war - `fill: rgb(0,0,0)` aus meiner Messung ist
|
||||
die geerbte CSS-Eigenschaft des `<img>`-Elements und sagt ueber das gezeichnete
|
||||
SVG nichts. Die Gegenprobe im Dunkelmodus zeigte weisse Ringe.
|
||||
|
||||
Der Befund stimmte, aber nur auf der **Musterseite**. Ansage des Eigentuemers:
|
||||
*"der fehler ist nur in deiner musterseite. Verwerfe diese - die fuehrt nur zu
|
||||
neuen Fehlerquellen"*. Sie ist entfernt (Abschnitt CG traegt jetzt den
|
||||
Ruecknahme-Vermerk). Eine zweite Bauweise derselben Oberflaeche erzeugt
|
||||
Fehlalarme - genau das ist hier passiert.
|
||||
|
||||
**Ein echter Mangel blieb aus der Suche uebrig**: `theme.ts` und das Panel
|
||||
lasen `prefers-color-scheme` genau einmal beim Laden und horchten danach nie
|
||||
wieder. Stellt das Geraet im Betrieb um - automatische Umschaltung bei
|
||||
Sonnenuntergang -, blieb die Oberflaeche bis zum naechsten Neuladen stehen.
|
||||
Beide horchen jetzt; eine selbst getroffene Wahl bleibt unangetastet. Drei
|
||||
Tests, die ohne die Behebung nachweislich fallen (gegengeprueft).
|
||||
|
||||
### Das Zahnrad im Autohaus-Kasten - meiner
|
||||
|
||||
Gemeldet: *"Das Zahnrad vom Autohaus hat seine Position (rechts oben in der
|
||||
Kachel) verlassen. Warum"*. Gemessen: 58px von oben, 288px von rechts statt
|
||||
8/8.
|
||||
|
||||
Ursache war mein eigener HIG-Durchgang vom 04.09.2026 (Commit b0ec8b4). Ich
|
||||
hatte `.zahnrad` in die Gruppe fuer die 44x44-Trefferflaeche aufgenommen:
|
||||
|
||||
#back, .ringsbtn, .icon-btn, .zahnrad { position: relative; }
|
||||
|
||||
`audi-dashboard-ios.css` laedt nach `audi-dashboard.css`, gleiche Spezifitaet -
|
||||
das `position: absolute`, das das Zahnrad in die Kachelecke heftet, war damit
|
||||
ueberschrieben. Die Auflage war ausserdem ueberfluessig: `.zahnrad` ist im
|
||||
Basisblatt schon 44x44. Ich hatte die Gruppe zusammengestellt, ohne zu pruefen,
|
||||
welche Knoepfe ihre Flaeche laengst haben. `.zahnrad` ist wieder heraus, die
|
||||
drei anderen sind nicht absolut positioniert und bleiben.
|
||||
|
||||
### Das Fotomenue: an der Kachel statt am Bildschirm, ohne Ausweg
|
||||
|
||||
Gemeldet: *"Der Popup zum ersetzen erscheint ausserhalb vom Sichtfeld... es
|
||||
fehlt ein abbrechen button wenn man zufaellig auf das rad klickt."*
|
||||
|
||||
`.bildmenu` war `position: absolute` und hing damit an der **Kachel**, nicht am
|
||||
Bildschirm. Bei der hohen Raeder-Kachel lag es unterhalb des Sichtfelds. Einen
|
||||
Abbrechen-Knopf hatte es nie.
|
||||
|
||||
Die App hatte den Fall schon geloest - `<ActionSheet>` mit abgesetztem
|
||||
Abbrechen. Das Panel zieht jetzt nach: `aktionsblatt(titel, aktionen)` neben
|
||||
`bestaetigen()`/`hinweis()`, gerendert am Wurzelelement. `.bildmenu` samt
|
||||
`bildMenuOffen`, den beiden Markup-Bloecken, dem toten
|
||||
`data-bildloeschen`-Handler und allen CSS-Regeln in beiden Blaettern ist
|
||||
entfernt. `.bildmenu-catcher` bleibt - den benutzt das SmartDeal-Blatt.
|
||||
|
||||
Gemessen am laufenden Panel: Blatt bei 553-760px in einem 794px-Fenster, drei
|
||||
Knoepfe "Foto ersetzen / Foto loeschen / Abbrechen".
|
||||
|
||||
### Der Blattkopf las sich als dritte Auswahl
|
||||
|
||||
Gemeldet: *"Die Ueberschrift des Menues Sommerrad ersetzen/loeschen lautet
|
||||
'Sommerrad'... Auf mich wirkt es wie eine dritte Auswahl"*. Zu Recht: er stand
|
||||
linksbuendig, 15px, in `--fg` - fast genau wie die Knopfzeilen darunter. Apples
|
||||
Aktionsblatt setzt den Titel **zentriert, kleiner (13px) und gedaempft**
|
||||
(`--fg2`). Beide Codebasen nachgezogen.
|
||||
|
||||
### "Anstehende Termine" gehoerte scheinbar zum Oelwechsel
|
||||
|
||||
Gemeldet: *"Aktuell wirkt es als wuerde es zu Oelwechsel gehoeren"*. Gemessen:
|
||||
13px, Gewicht 400, `--fg3`, keine Grossschrift - und nur **5px** ueber
|
||||
"Oelwechsel" (16px, `--fg`). Der Kopf ist entfernt: die drei Zeilen benennen
|
||||
sich selbst, die Seite heisst bereits "Service".
|
||||
|
||||
**Offen und nicht angefasst:** dieselbe schwache Behandlung hat die
|
||||
Kachelueberschrift ueberall im Panel (`.label`, iOS-Auflage: 13px, `--fg3`,
|
||||
keine Grossschrift). Die App setzt an derselben Stelle 10px in Grossschrift mit
|
||||
0.2em Sperrung - eine erkennbare Augenbraue. Das ist eine Abweichung ueber
|
||||
Dutzende Kacheln und gehoert nicht in einen Nebensatz.
|
||||
|
||||
### Wartungsplan-Formular
|
||||
|
||||
Vorgabe: Art nach oben, Feldbreiten nach Inhalt, zwei Erklaertexte weg.
|
||||
Gemessen vorher/nachher:
|
||||
|
||||
| Feld | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| Art | 3. Zeile | 1. Zeile |
|
||||
| Datum | ~170-190px (Browser-Vorgabe) | 124px Panel / 150px App |
|
||||
| Werkstatt | 218px | 306px, eigene Zeile |
|
||||
| Kosten | 96px | 116px |
|
||||
|
||||
Die Browser-Vorgabe ist inhaltsblind: **jedes** Textfeld ohne `size` bekommt
|
||||
dieselbe Breite. Deshalb schwamm das Datum und wurde der Werkstattname
|
||||
abgeschnitten.
|
||||
|
||||
Der Werkstattname passt in keine Spalte neben einem Label: ein echter Name
|
||||
("Audi AG Kundendienst Center Ingolstadt") braucht 322px, auf 375px Geraetebreite
|
||||
bleiben neben dem Label 218. Er steht deshalb in einer `.feld--breit`-Zeile.
|
||||
**Auch das reicht fuer diesen Extremfall nicht ganz** - 306 gegen 322px, das
|
||||
Feld scrollt dann. Weiter kaeme man nur mit kleinerer Schrift, und unter 16px
|
||||
zoomt iOS beim Antippen die ganze Seite.
|
||||
|
||||
Die App hat ein natives `type="date"` mit Kalendersymbol, das Panel ein
|
||||
Textfeld - daher 150 gegen 124px. Das ist ein Bauunterschied, kein Versehen.
|
||||
|
||||
### Raederzaehler: er rechnet, ihm fehlt nur Futter
|
||||
|
||||
Frage: *"Funktioniert der Raederzaehler ueberhaupt? Ich sehe hier wenig
|
||||
Bewegung bei den Fahrten."* Gemessen an der Testinstanz:
|
||||
|
||||
| | Anzahl |
|
||||
|---|---|
|
||||
| Fahrten gesamt | 19 |
|
||||
| mit `reifensatz` | 4 |
|
||||
| davon mit gemessener Strecke | 2 |
|
||||
| noch nicht gutgeschrieben | 0 |
|
||||
|
||||
Die vier gestempelten Fahrten summieren 9,9 km, und **genau 9,9 km** stehen auf
|
||||
den Sommerraedern. Der Zaehler arbeitet also fehlerfrei. Wenig Bewegung hat
|
||||
drei Gruende, alle beabsichtigt oder bekannt:
|
||||
|
||||
* Importierte Fahrten (6) werden **bewusst nicht** gestempelt - ihre Kilometer
|
||||
stecken schon im gespeicherten Radstand, ein erneuter Import wuerde sie sonst
|
||||
ein zweites Mal aufschlagen (Kommentar in `historienimport.py`).
|
||||
* Sieben `ha`-Fahrten stammen aus der Zeit vor dem Stempel und tragen ihn nicht
|
||||
nach.
|
||||
* Zwei der vier gestempelten Fahrten haben noch keine gemessene Strecke; sie
|
||||
werden gutgeschrieben, sobald das Screening sie fuellt.
|
||||
|
||||
Der eigentliche Grund auf der **realen** Instanz ist ein anderer und heute
|
||||
zuerst gefunden worden: dort wurden ueberhaupt keine Fahrten erkannt, weil die
|
||||
Erkennung still das Trip-Signal des Dongles bevorzugte, das nie ankam. Ohne
|
||||
Fahrten kein Radstand.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
# Bau der Musterseite (vite.muster.config.ts) - reines Ergebnis, nie in der .ipa.
|
||||
dist-muster/
|
||||
*.local
|
||||
.DS_Store
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="de" data-theme="nacht">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>DataMetric360 – Musterseite</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wurzel" class="ads-root"></div>
|
||||
<script type="module" src="./muster.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* Musterseite: die Oberflaeche der App mit Beispieldaten, ohne Server.
|
||||
*
|
||||
* WOFUER
|
||||
* ------
|
||||
* Die App braucht sonst einen gespeicherten Zugang, um ueberhaupt etwas
|
||||
* anzuzeigen - und damit war jede OPTISCHE Pruefung an ein Zugangstoken
|
||||
* gebunden. Genau daran ist in diesem Projekt wiederholt die Verifikation
|
||||
* haengengeblieben, obwohl fast jeder gefundene Fehler (CSS-Spezifitaet,
|
||||
* Flex-Basis in Spalten, unsichtbare Rahmen) nur am gerenderten Bild
|
||||
* sichtbar war und von tsc und den Tests nicht gefunden werden konnte.
|
||||
*
|
||||
* Diese Seite mountet dieselben Bildschirme mit demselben Stylesheet, aber
|
||||
* mit `beispielApi()` aus den Tests. Kein Netz, kein Token, keine echten
|
||||
* Daten - und trotzdem echtes CSS.
|
||||
*
|
||||
* NICHT TEIL DER APP
|
||||
* ------------------
|
||||
* Gebaut wird sie ueber `vite.muster.config.ts` nach `dist-muster/`. Der
|
||||
* normale `npm run build` fasst sie nicht an, sie liegt also auch nicht in
|
||||
* der .ipa.
|
||||
*
|
||||
* Aufruf: `?seite=einst` (Vorgabe), jeder Name aus navigation.ts TITEL.
|
||||
* Dazu `?seite=einrichtung` fuer den Willkommensbildschirm - der ist keine
|
||||
* Route (er steht VOR der Anmeldung) und braucht deshalb einen eigenen Zweig.
|
||||
*/
|
||||
|
||||
import { StrictMode, useState } from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
import "@audi-dash/ui/styles.css"
|
||||
import "../src/stile/grundlage.css"
|
||||
import "../src/stile/audi-schrift.css"
|
||||
import "../src/stile/rahmen.css"
|
||||
import "../src/stile/screens.css"
|
||||
|
||||
import { DatenAnbieter } from "../src/daten/DatenKontext"
|
||||
import { Shell } from "../src/Shell"
|
||||
import type { SeitenName } from "../src/navigation"
|
||||
import { TITEL } from "../src/navigation"
|
||||
import { Bestaetigungsblatt } from "../src/screens/bestaetigung"
|
||||
import { Einrichtung } from "../src/screens/Einrichtung"
|
||||
import { SeiteFuer } from "../src/screens/register"
|
||||
import { beispielApi } from "../src/tests/beispieldaten"
|
||||
|
||||
function Muster() {
|
||||
const gewaehlt = new URLSearchParams(location.search).get("seite") || "einst"
|
||||
const start = gewaehlt as SeitenName
|
||||
const [route, setzeRoute] = useState<{ name: SeitenName; id?: string }>({ name: start })
|
||||
const geheZu = (name: SeitenName, id?: string) =>
|
||||
setzeRoute(id === undefined ? { name } : { name, id })
|
||||
// Der Willkommensbildschirm haengt an keinem Zugang und an keiner Route.
|
||||
if (gewaehlt === "einrichtung") return <Einrichtung fertig={() => {}} />
|
||||
return (
|
||||
<DatenAnbieter api={beispielApi()}>
|
||||
<Bestaetigungsblatt />
|
||||
<Shell route={route} geheZu={geheZu} tabBeschriftung>
|
||||
<SeiteFuer
|
||||
route={route}
|
||||
geheZu={geheZu}
|
||||
tabBeschriftung
|
||||
setzeTabBeschriftung={() => {}}
|
||||
beiAbmeldung={() => {}}
|
||||
/>
|
||||
</Shell>
|
||||
</DatenAnbieter>
|
||||
)
|
||||
}
|
||||
|
||||
// Zum schnellen Springen zwischen den Seiten, ohne die Adresse zu tippen.
|
||||
;(window as unknown as { SEITEN: string[] }).SEITEN = Object.keys(TITEL)
|
||||
|
||||
const wurzel = document.getElementById("wurzel")
|
||||
if (!wurzel) throw new Error("Wurzelelement #wurzel fehlt")
|
||||
createRoot(wurzel).render(
|
||||
<StrictMode>
|
||||
<Muster />
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -130,7 +130,6 @@ export function Service({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
return (
|
||||
<>
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Anstehende Termine</span>
|
||||
|
||||
<div className="dm-termingruppe">
|
||||
<div className="dm-tgkopf">
|
||||
@@ -504,17 +503,6 @@ export function Servicebuch({
|
||||
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Eintrag</span>
|
||||
<Feld label="Datum">
|
||||
<Datumsfeld wert={entwurf.datum ?? ""} beiAenderung={(w) => setze("datum", w)} />
|
||||
</Feld>
|
||||
<Feld label="Kilometerstand" unit="km">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="numeric"
|
||||
value={entwurf.km ?? ""}
|
||||
onChange={(e) => setze("km", Number(e.target.value))}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Art">
|
||||
<select
|
||||
className="dm-auswahl"
|
||||
@@ -531,25 +519,38 @@ export function Servicebuch({
|
||||
))}
|
||||
</select>
|
||||
</Feld>
|
||||
<Feld label="Werkstatt">
|
||||
<Feld label="Datum">
|
||||
<Datumsfeld wert={entwurf.datum ?? ""} beiAenderung={(w) => setze("datum", w)} />
|
||||
</Feld>
|
||||
<Feld label="Kilometerstand" unit="km">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="numeric"
|
||||
value={entwurf.km ?? ""}
|
||||
onChange={(e) => setze("km", Number(e.target.value))}
|
||||
/>
|
||||
</Feld>
|
||||
{/* Label oben, Feld über die ganze Zeile - wie der Reifen-Kommentar
|
||||
und .feld--breit im Panel. Ein echter Werkstattname braucht 322px;
|
||||
neben einem Label bleiben auf 375px nur 218 (gemessen 05.09.2026). */}
|
||||
<div className="ads-feld" style={{ display: "block" }}>
|
||||
<label style={{ display: "block", marginBottom: 8 }}>Werkstatt</label>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
style={{ width: "100%", textAlign: "left" }}
|
||||
value={entwurf.werkstatt ?? ""}
|
||||
onChange={(e) => setze("werkstatt", e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
</div>
|
||||
<Feld label="Kosten" unit="€" last>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
className="dm-eingabe dm-eingabe--betrag"
|
||||
inputMode="decimal"
|
||||
placeholder="noch offen"
|
||||
value={entwurf.kosten ?? ""}
|
||||
onChange={(e) => setze("kosten", e.target.value === "" ? null : Number(e.target.value))}
|
||||
/>
|
||||
</Feld>
|
||||
<span className="ads-eyebrow" style={{ marginTop: "14px" }}>
|
||||
Kosten fließen in die Jahresübersicht ein
|
||||
</span>
|
||||
</Tile>
|
||||
|
||||
<Tile>
|
||||
@@ -561,9 +562,6 @@ export function Servicebuch({
|
||||
value={entwurf.arbeiten ?? ""}
|
||||
onChange={(e) => setze("arbeiten", e.target.value)}
|
||||
/>
|
||||
<span className="ads-eyebrow" style={{ marginTop: "var(--sp-3)" }}>
|
||||
Beim Verkauf ist diese Liste mehr wert als die Rechnung — sie zeigt, dass gepflegt wurde
|
||||
</span>
|
||||
</Tile>
|
||||
|
||||
<ActionButton variant="primary" onClick={() => void speichern()} disabled={laeuft}>
|
||||
|
||||
+2256
-2244
File diff suppressed because it is too large
Load Diff
@@ -38,3 +38,60 @@ describe("Theme", () => {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
/* Stellt das Gerät im laufenden Betrieb um - etwa durch die automatische
|
||||
Umschaltung bei Sonnenuntergang -, muss das Thema folgen. Bis zum
|
||||
05.09.2026 las theme.ts die Systemeinstellung genau einmal beim Laden des
|
||||
Moduls und horchte danach nie wieder; ein Wechsel wirkte erst nach dem
|
||||
nächsten Neuladen. */
|
||||
describe("Systemumstellung im laufenden Betrieb", () => {
|
||||
function medienabfrageStellen(dunkel: boolean) {
|
||||
const horcher: (() => void)[] = []
|
||||
const stand = { dunkel }
|
||||
// matches als Getter, nicht als kopierter Wert: theme.ts haelt die
|
||||
// Abfrage vom Laden fest und liest matches erst im Horcher wieder.
|
||||
vi.stubGlobal("matchMedia", (text: string) => ({
|
||||
get matches() {
|
||||
return text.includes("dark") ? stand.dunkel : !stand.dunkel
|
||||
},
|
||||
addEventListener: (_art: string, bei: () => void) => horcher.push(bei),
|
||||
removeEventListener: () => {},
|
||||
}))
|
||||
return {
|
||||
umstellenAuf(neuDunkel: boolean) {
|
||||
stand.dunkel = neuDunkel
|
||||
for (const bei of horcher) bei()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it("folgt dem Wechsel nach hell, ohne neu zu laden", async () => {
|
||||
const system = medienabfrageStellen(true)
|
||||
const { themeStartwert } = await import("./theme")
|
||||
expect(themeStartwert()).toBe("nacht")
|
||||
system.umstellenAuf(false)
|
||||
expect(themeStartwert()).toBe("tag")
|
||||
})
|
||||
|
||||
it("folgt dem Wechsel nach dunkel, ohne neu zu laden", async () => {
|
||||
const system = medienabfrageStellen(false)
|
||||
const { themeStartwert } = await import("./theme")
|
||||
expect(themeStartwert()).toBe("tag")
|
||||
system.umstellenAuf(true)
|
||||
expect(themeStartwert()).toBe("nacht")
|
||||
})
|
||||
|
||||
it("lässt eine selbst getroffene Wahl unangetastet", async () => {
|
||||
localStorage.setItem("dm360.theme", "tag")
|
||||
const system = medienabfrageStellen(false)
|
||||
const { themeStartwert } = await import("./theme")
|
||||
expect(themeStartwert()).toBe("tag")
|
||||
system.umstellenAuf(true)
|
||||
expect(themeStartwert()).toBe("tag")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -68,6 +68,16 @@ function einstellung<T>(
|
||||
|
||||
return {
|
||||
stand: () => wert,
|
||||
/** Ob überhaupt etwas in der Ablage steht - also eine bewusste Wahl. */
|
||||
gewaehlt: () => ablageLesen(schluessel) !== null,
|
||||
/* Setzt den gehaltenen Wert, OHNE ihn zu speichern. Für Werte, die dem
|
||||
System folgen, solange niemand selbst gewählt hat: eine Systemumstellung
|
||||
ist keine Entscheidung des Nutzers und darf deshalb keine ablegen. */
|
||||
ausSystem: (neu: T) => {
|
||||
if (neu === wert) return
|
||||
wert = neu
|
||||
for (const bei of horcher) bei()
|
||||
},
|
||||
horchen: (bei: () => void) => {
|
||||
horcher.add(bei)
|
||||
return () => horcher.delete(bei)
|
||||
@@ -95,6 +105,31 @@ const THEMA = einstellung<Theme>(
|
||||
(wert) => wert,
|
||||
)
|
||||
|
||||
/* Systemumstellung im laufenden Betrieb nachziehen.
|
||||
|
||||
Der Startwert oben liest `prefers-color-scheme` genau einmal, beim Laden des
|
||||
Moduls. Stellt das Gerät danach um - etwa durch die automatische Umschaltung
|
||||
bei Sonnenuntergang -, folgte das Stylesheet sofort (es hängt selbst an der
|
||||
Medienabfrage), der React-Zustand aber nie. Alles, was sein Bild aus
|
||||
`useTheme()` wählt statt aus CSS, blieb damit auf der alten Fassung stehen:
|
||||
die Vier Ringe und das Typenschild sind `<img>` mit eingebettetem SVG und
|
||||
je einer hellen und einer dunklen Fassung (siehe VierRinge in
|
||||
Typenschild.tsx). Auf dunklem Grund stand dann die dunkle Fassung - schwarz
|
||||
auf schwarz, vom Eigentümer am 05.09.2026 gemeldet. Ein Neuladen räumte es
|
||||
auf, weshalb es sprunghaft wirkte.
|
||||
|
||||
Nur solange niemand selbst gewählt hat: eine gespeicherte Wahl in
|
||||
"Einstellungen" bleibt unangetastet. */
|
||||
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
||||
const dunkel = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const nachziehen = () => {
|
||||
if (THEMA.gewaehlt()) return
|
||||
THEMA.ausSystem(dunkel.matches ? "nacht" : "tag")
|
||||
}
|
||||
if (typeof dunkel.addEventListener === "function") dunkel.addEventListener("change", nachziehen)
|
||||
else if (typeof dunkel.addListener === "function") dunkel.addListener(nachziehen)
|
||||
}
|
||||
|
||||
|
||||
const TABBESCHRIFTUNG = einstellung<boolean>(
|
||||
"dm360.tabbeschriftung",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Bau der Musterseite (siehe muster/muster.tsx).
|
||||
*
|
||||
* Bewusst eine EIGENE Konfiguration mit eigenem Ausgabeordner: die Seite darf
|
||||
* nicht in `dist/` landen, weil `dist/` die Huelle der iOS-App ist. Was dort
|
||||
* liegt, wird mit ausgeliefert.
|
||||
*/
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
export default defineConfig({
|
||||
root: "muster",
|
||||
base: "./",
|
||||
plugins: [react()],
|
||||
define: { __APP_VERSION__: JSON.stringify("muster") },
|
||||
build: { outDir: "../dist-muster", emptyOutDir: true },
|
||||
})
|
||||
@@ -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"}
|
||||
Binary file not shown.
@@ -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": [
|
||||
|
||||
@@ -139,18 +139,29 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Der Kopf eines Aktionsblatts ist eine Aufschrift, keine Wahl.
|
||||
|
||||
Er stand linksbündig, 15px, in --fg - also fast genau wie die Knopfzeilen
|
||||
darunter, nur eine Haarlinie davon getrennt. Beim Radfoto ("Sommerrad")
|
||||
las der Eigentümer ihn deshalb als dritte Auswahl (gemeldet 05.09.2026).
|
||||
|
||||
Apples Aktionsblatt setzt den Titel zentriert, kleiner und in der
|
||||
gedämpften Farbe - dieselben drei Unterschiede, die ihn hier wieder als
|
||||
Überschrift lesbar machen. */
|
||||
.ads-aktionsblatt__kopf {
|
||||
padding: 15px 18px 14px;
|
||||
padding: 14px 18px 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ads-aktionsblatt__kopf strong {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: var(--fg);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.ads-aktionsblatt__kopf span {
|
||||
|
||||
Reference in New Issue
Block a user