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)
|
## 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
|
**Das Problem, das seit Wochen wiederkehrt:** die App zeigt ohne gespeicherten
|
||||||
Zugang gar nichts, jede OPTISCHE Pruefung haengt damit an einem Zugangstoken -
|
Zugang gar nichts, jede OPTISCHE Pruefung haengt damit an einem Zugangstoken -
|
||||||
und fast jeder Fehler, den dieses Projekt findet, ist nur am gerenderten Bild
|
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
|
Beides hat dazu gefuehrt, dass ich zwischenzeitlich alte Dateien gelesen und
|
||||||
fuer neue gehalten habe. Gegenprobe, die das aufdeckt: `find <ziel> -name
|
fuer neue gehalten habe. Gegenprobe, die das aufdeckt: `find <ziel> -name
|
||||||
manifest.json | wc -l` muss `1` ergeben.
|
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/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
# Bau der Musterseite (vite.muster.config.ts) - reines Ergebnis, nie in der .ipa.
|
|
||||||
dist-muster/
|
|
||||||
*.local
|
*.local
|
||||||
.DS_Store
|
.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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tile>
|
<Tile>
|
||||||
<span className="ads-eyebrow">Anstehende Termine</span>
|
|
||||||
|
|
||||||
<div className="dm-termingruppe">
|
<div className="dm-termingruppe">
|
||||||
<div className="dm-tgkopf">
|
<div className="dm-tgkopf">
|
||||||
@@ -504,17 +503,6 @@ export function Servicebuch({
|
|||||||
|
|
||||||
<Tile>
|
<Tile>
|
||||||
<span className="ads-eyebrow">Eintrag</span>
|
<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">
|
<Feld label="Art">
|
||||||
<select
|
<select
|
||||||
className="dm-auswahl"
|
className="dm-auswahl"
|
||||||
@@ -531,25 +519,38 @@ export function Servicebuch({
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</Feld>
|
</Feld>
|
||||||
<Feld label="Werkstatt">
|
<Feld label="Datum">
|
||||||
|
<Datumsfeld wert={entwurf.datum ?? ""} beiAenderung={(w) => setze("datum", w)} />
|
||||||
|
</Feld>
|
||||||
|
<Feld label="Kilometerstand" unit="km">
|
||||||
<input
|
<input
|
||||||
className="dm-eingabe"
|
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 ?? ""}
|
value={entwurf.werkstatt ?? ""}
|
||||||
onChange={(e) => setze("werkstatt", e.target.value)}
|
onChange={(e) => setze("werkstatt", e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Feld>
|
</div>
|
||||||
<Feld label="Kosten" unit="€" last>
|
<Feld label="Kosten" unit="€" last>
|
||||||
<input
|
<input
|
||||||
className="dm-eingabe"
|
className="dm-eingabe dm-eingabe--betrag"
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
placeholder="noch offen"
|
placeholder="noch offen"
|
||||||
value={entwurf.kosten ?? ""}
|
value={entwurf.kosten ?? ""}
|
||||||
onChange={(e) => setze("kosten", e.target.value === "" ? null : Number(e.target.value))}
|
onChange={(e) => setze("kosten", e.target.value === "" ? null : Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
</Feld>
|
</Feld>
|
||||||
<span className="ads-eyebrow" style={{ marginTop: "14px" }}>
|
|
||||||
Kosten fließen in die Jahresübersicht ein
|
|
||||||
</span>
|
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
<Tile>
|
<Tile>
|
||||||
@@ -561,9 +562,6 @@ export function Servicebuch({
|
|||||||
value={entwurf.arbeiten ?? ""}
|
value={entwurf.arbeiten ?? ""}
|
||||||
onChange={(e) => setze("arbeiten", e.target.value)}
|
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>
|
</Tile>
|
||||||
|
|
||||||
<ActionButton variant="primary" onClick={() => void speichern()} disabled={laeuft}>
|
<ActionButton variant="primary" onClick={() => void speichern()} disabled={laeuft}>
|
||||||
|
|||||||
@@ -20,6 +20,12 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Feldbreiten nach Inhalt statt nach Browser-Standardwert - wortgleich mit
|
||||||
|
.feld input.w-* im Panel (audi-dashboard.css). Ohne sie bekommt jedes
|
||||||
|
Textfeld dieselbe Breite, egal was drinsteht: das Datum schwamm, der
|
||||||
|
Werkstattname wurde abgeschnitten (gemeldet am 05.09.2026). */
|
||||||
|
.ads-mit-einheit input.dm-eingabe--betrag { width: 116px; }
|
||||||
|
|
||||||
.dm-eingabe::placeholder {
|
.dm-eingabe::placeholder {
|
||||||
color: var(--fg3);
|
color: var(--fg3);
|
||||||
}
|
}
|
||||||
@@ -47,7 +53,13 @@
|
|||||||
höhere Selektor-Spezifität als das geteilte ".ads-feld input" schlägt
|
höhere Selektor-Spezifität als das geteilte ".ads-feld input" schlägt
|
||||||
dessen feste min-width:132px, die hier breiter ist als "27.08.2026"
|
dessen feste min-width:132px, die hier breiter ist als "27.08.2026"
|
||||||
braucht. */
|
braucht. */
|
||||||
|
/* Breite wie .feld input.w-datum im Panel (124px), plus Platz fuer das
|
||||||
|
|
||||||
|
Kalendersymbol des nativen Feldes, das es im Panel nicht gibt. Ohne die
|
||||||
|
|
||||||
Angabe bestimmt der Browser die Breite: 170px fuer zehn Zeichen, deutlich
|
Angabe bestimmt der Browser die Breite: 170px fuer zehn Zeichen, deutlich
|
||||||
|
|
||||||
|
mehr als der Inhalt braucht (gemeldet am 05.09.2026). */
|
||||||
|
|
||||||
input[type="date"].dm-eingabe {
|
input[type="date"].dm-eingabe {
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
return {
|
||||||
stand: () => wert,
|
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) => {
|
horchen: (bei: () => void) => {
|
||||||
horcher.add(bei)
|
horcher.add(bei)
|
||||||
return () => horcher.delete(bei)
|
return () => horcher.delete(bei)
|
||||||
@@ -95,6 +105,31 @@ const THEMA = einstellung<Theme>(
|
|||||||
(wert) => wert,
|
(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>(
|
const TABBESCHRIFTUNG = einstellung<boolean>(
|
||||||
"dm360.tabbeschriftung",
|
"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() {
|
function vService() {
|
||||||
const sv = CAR.service;
|
const sv = CAR.service;
|
||||||
return `
|
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) => {
|
${termine().map((t) => {
|
||||||
const artIcon = ART_ICON[t.art];
|
const artIcon = ART_ICON[t.art];
|
||||||
const iconMarkup = artIcon ? ciSVG(artIcon, ART_ICON_GROESSE[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>
|
<span class="label" style="margin-top:12px">${e.datum} · ${esc(e.werkstatt)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile"><span class="label">Eintrag</span>
|
<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>
|
<div class="feld"><label>Art</label>
|
||||||
<select data-sb="${i}|art">
|
<select data-sb="${i}|art">
|
||||||
${!artBekannt ? `<option value="${esc(e.art)}" selected>${esc(e.art)}</option>` : ""}
|
${!artBekannt ? `<option value="${esc(e.art)}" selected>${esc(e.art)}</option>` : ""}
|
||||||
${SERVICE_ARTEN.map((a) => `<option ${a === e.art ? "selected" : ""}>${esc(a)}</option>`).join("")}
|
${SERVICE_ARTEN.map((a) => `<option ${a === e.art ? "selected" : ""}>${esc(a)}</option>`).join("")}
|
||||||
</select></div>
|
</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>
|
<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>
|
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>
|
||||||
<div class="tile"><span class="label">Durchgeführte Arbeiten</span>
|
<div class="tile"><span class="label">Durchgeführte Arbeiten</span>
|
||||||
<textarea class="notiz" data-sb="${i}|arbeiten" rows="7"
|
<textarea class="notiz" data-sb="${i}|arbeiten" rows="7"
|
||||||
placeholder="Was wurde gemacht, welche Teile wurden getauscht, was ist aufgefallen">${esc(e.arbeiten || "")}</textarea>
|
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>
|
</div>
|
||||||
${istEntwurf ? `<button class="aktion" data-sbspeichern>Speichern</button>`
|
${istEntwurf ? `<button class="aktion" data-sbspeichern>Speichern</button>`
|
||||||
: `<button class="aktion loeschen" data-sbdel="${i}">Eintrag löschen</button>`}`;
|
: `<button class="aktion loeschen" data-sbdel="${i}">Eintrag löschen</button>`}`;
|
||||||
@@ -3647,19 +3654,13 @@ function vEinst() {
|
|||||||
${(() => {
|
${(() => {
|
||||||
const info = bildInfo(startIndex());
|
const info = bildInfo(startIndex());
|
||||||
return `<div style="margin-top:12px">
|
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
|
${/* "klein" = das Bild bestimmt die Hoehe, statt im 16:9-Kasten
|
||||||
beschnitten zu werden (siehe .carfix.klein). Wer hier prueft,
|
beschnitten zu werden (siehe .carfix.klein). Wer hier prueft,
|
||||||
ob das richtige Foto hochgeladen ist, muss es ganz sehen. */""
|
ob das richtige Foto hochgeladen ist, muss es ganz sehen. */""
|
||||||
}${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein", null, true)}
|
}${bildMitPlatzhalter(info.src, info.datei, info.label, "carfix klein", null, true)}
|
||||||
</div>
|
</div>
|
||||||
<input type="file" accept="image/*" hidden data-bildupload="${esc(info.datei)}">
|
<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>`;
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
@@ -3879,7 +3880,26 @@ let debugOffen = false;
|
|||||||
"Speichern" uebernimmt ihn, weil derselbe Wert den Schlaf-Timeout des
|
"Speichern" uebernimmt ihn, weil derselbe Wert den Schlaf-Timeout des
|
||||||
Dongles stellt. Siehe die Kachel in vEinst(). */
|
Dongles stellt. Siehe die Kachel in vEinst(). */
|
||||||
let PAUSENZEIT_ENTWURF = null;
|
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;
|
let smartdealPopupOffen = false;
|
||||||
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
|
// "sommer"|"winter", welcher Reifensatz gerade sein km-Korrektur-Feld offen
|
||||||
// hat (Editier-Symbol neben der km-Anzeige) - null wenn keins offen ist.
|
// 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>
|
${t.km != null ? de(t.km) : "–"}<span class="unit">km</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-bildklick="${radDatei}">
|
<div data-bildklick="${radDatei}" data-bildname="${esc(radLabel)}">
|
||||||
${bildMitPlatzhalter(bildAdresse(radDatei), radDatei, radLabel, "radbild")}
|
${bildMitPlatzhalter(bildAdresse(radDatei), radDatei, radLabel, "radbild")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4189,12 +4209,6 @@ function vReifen() {
|
|||||||
<button class="aktion" data-kmauf="${k}">Abbrechen</button>
|
<button class="aktion" data-kmauf="${k}">Abbrechen</button>
|
||||||
</div>` : ""}
|
</div>` : ""}
|
||||||
<input type="file" accept="image/*" hidden data-bildupload="${radDatei}">
|
<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)">
|
<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 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>
|
<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;
|
if (gespeichert === "nacht" || gespeichert === "tag") return gespeichert;
|
||||||
return window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches ? "tag" : "nacht";
|
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) {
|
function themeSetzen(modus) {
|
||||||
HOST.dataset.theme = modus;
|
HOST.dataset.theme = modus;
|
||||||
try { localStorage.setItem(THEME_KEY, modus); } catch (e) { /* Privatmodus */ }
|
try { localStorage.setItem(THEME_KEY, modus); } catch (e) { /* Privatmodus */ }
|
||||||
@@ -5197,6 +5239,22 @@ function hinweis(titel, text) {
|
|||||||
sheet = { titel, text, knopf: null, aktion: null };
|
sheet = { titel, text, knopf: null, aktion: null };
|
||||||
render();
|
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
|
/* ------------------------------------------------------- Beleg-Upload
|
||||||
Ein Weg für alle drei Quellen (Ziehen, Einfügen, Dateiauswahl). Kein
|
Ein Weg für alle drei Quellen (Ziehen, Einfügen, Dateiauswahl). Kein
|
||||||
serviceRufen() hier: dessen catch loggt nur in die Konsole - siehe den
|
serviceRufen() hier: dessen catch loggt nur in die Konsole - siehe den
|
||||||
@@ -5501,6 +5559,15 @@ function vBelegPopup() {
|
|||||||
function sheetMarkup() {
|
function sheetMarkup() {
|
||||||
if (!sheet) return "";
|
if (!sheet) return "";
|
||||||
const kopf = `<div class="sheet-kopf"><strong>${esc(sheet.titel)}</strong>${sheet.text ? `<span>${esc(sheet.text)}</span>` : ""}</div>`;
|
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
|
const aktion = sheet.knopf
|
||||||
? `<button data-sheet-ok class="${sheet.destruktiv ? "loeschen" : ""}">${esc(sheet.knopf)}</button>`
|
? `<button data-sheet-ok class="${sheet.destruktiv ? "loeschen" : ""}">${esc(sheet.knopf)}</button>`
|
||||||
: `<button data-sheet-abbrechen>Verstanden</button>`;
|
: `<button data-sheet-abbrechen>Verstanden</button>`;
|
||||||
@@ -6281,6 +6348,13 @@ function ereignisseVerdrahten() {
|
|||||||
ROOT.addEventListener("click", (e) => {
|
ROOT.addEventListener("click", (e) => {
|
||||||
// B4: Das Action Sheet liegt über allem und fängt daher zuerst ab.
|
// B4: Das Action Sheet liegt über allem und fängt daher zuerst ab.
|
||||||
if (e.target.closest("[data-sheet-abbrechen]")) { sheetSchliessen(); return; }
|
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]")) {
|
if (e.target.closest("[data-sheet-ok]")) {
|
||||||
const aktion = sheet && sheet.aktion;
|
const aktion = sheet && sheet.aktion;
|
||||||
sheet = null; render();
|
sheet = null; render();
|
||||||
@@ -6370,18 +6444,6 @@ function ereignisseVerdrahten() {
|
|||||||
bestaetigen(LOESCH_TEXT.sbuch, LOESCH_HINWEIS.sbuch, "Löschen", () => { eintragLoeschen("sbuch", id); go("service"); });
|
bestaetigen(LOESCH_TEXT.sbuch, LOESCH_HINWEIS.sbuch, "Löschen", () => { eintragLoeschen("sbuch", id); go("service"); });
|
||||||
return;
|
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
|
// Fahrzeugbild (Radbild wie Fahrzeugbilder-Slots): vorhandenes Foto -> Popup
|
||||||
// mit Ersetzen/Löschen, fehlendes Foto (Platzhalter, bild-fehlt) -> Dateiauswahl
|
// mit Ersetzen/Löschen, fehlendes Foto (Platzhalter, bild-fehlt) -> Dateiauswahl
|
||||||
// direkt öffnen.
|
// direkt öffnen.
|
||||||
@@ -6394,18 +6456,10 @@ function ereignisseVerdrahten() {
|
|||||||
const input = container.querySelector(`[data-bildupload="${dateiname}"]`);
|
const input = container.querySelector(`[data-bildupload="${dateiname}"]`);
|
||||||
if (input) input.click();
|
if (input) input.click();
|
||||||
} else {
|
} else {
|
||||||
bildMenuOffen = dateiname; render();
|
bildAktionsblatt(dateiname, bildKlick.dataset.bildname || "Foto");
|
||||||
}
|
}
|
||||||
return;
|
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-sdabbrechen]")) { smartdealPopupOffen = false; render(); return; }
|
||||||
if (e.target.closest("[data-sdbestaetigen]")) {
|
if (e.target.closest("[data-sdbestaetigen]")) {
|
||||||
const datum = $("#sdPopupBis").value;
|
const datum = $("#sdPopupBis").value;
|
||||||
@@ -6887,7 +6941,7 @@ function ereignisseVerdrahten() {
|
|||||||
const daten_base64 = await bildAlsBase64(klein);
|
const daten_base64 = await bildAlsBase64(klein);
|
||||||
await HASS.callService(DOMAIN, "bild_hochladen", { dateiname, daten_base64 });
|
await HASS.callService(DOMAIN, "bild_hochladen", { dateiname, daten_base64 });
|
||||||
} catch (err) { hinweis("Hochladen fehlgeschlagen", err.message); }
|
} catch (err) { hinweis("Hochladen fehlgeschlagen", err.message); }
|
||||||
bildMenuOffen = null; bildVersion = Date.now(); render();
|
bildVersion = Date.now(); render();
|
||||||
})();
|
})();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -7312,6 +7366,7 @@ class AudiDashboardPanel extends HTMLElement {
|
|||||||
/* A6: Standard folgt der Systemeinstellung, eine manuelle Wahl überlebt den
|
/* A6: Standard folgt der Systemeinstellung, eine manuelle Wahl überlebt den
|
||||||
Reload. Vorher stand die App nach jedem Neuladen wieder auf Nacht. */
|
Reload. Vorher stand die App nach jedem Neuladen wieder auf Nacht. */
|
||||||
HOST.dataset.theme = HOST.dataset.theme || themeGewaehlt();
|
HOST.dataset.theme = HOST.dataset.theme || themeGewaehlt();
|
||||||
|
themaSystemNachziehen();
|
||||||
|
|
||||||
schriftenLaden();
|
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
|
folgen der neuen Lage: das Aktionsblatt steht frei ueber der unteren
|
||||||
Kante und ist rundum rund, das Formularblatt sitzt auf der Kante auf und
|
Kante und ist rundum rund, das Formularblatt sitzt auf der Kante auf und
|
||||||
ist nur oben rund. */
|
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);
|
.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);
|
-webkit-backdrop-filter:saturate(180%) blur(22px);backdrop-filter:saturate(180%) blur(22px);
|
||||||
box-shadow:0 -8px 40px rgba(0,0,0,.5)}
|
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)}
|
: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}
|
|
||||||
|
|
||||||
/* ---------------- Tab-Leiste ---------------- */
|
/* ---------------- Tab-Leiste ---------------- */
|
||||||
.tabbar{border-top:1px solid var(--ios-sep);padding-bottom:26px;
|
.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
|
ruhigen Karte. max-width + margin:auto zentriert sie innerhalb des
|
||||||
bestehenden left/right-Abstands, ohne die transform-basierte
|
bestehenden left/right-Abstands, ohne die transform-basierte
|
||||||
Einblend-Animation dieser Elemente zu beruehren. */
|
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
|
/* .setup-popup breiter als die übrigen Popups: die Sensorkennung neben der
|
||||||
Feld-Überschrift (entitaetIdKurz()) kann ein echter Rohsensorname von
|
Feld-Überschrift (entitaetIdKurz()) kann ein echter Rohsensorname von
|
||||||
Herstellerseite sein, teils 40-60 Zeichen lang
|
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).
|
Wortgleich mit rahmen.css/screens.css der App (04.09.2026 gemessen).
|
||||||
Das Overlay laesst das Bild unveraendert und vergroessert nur, was der
|
Das Overlay laesst das Bild unveraendert und vergroessert nur, was der
|
||||||
Finger trifft; Mindesthoehen dort, wo das Element ohnehin Flaeche ist. */
|
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;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#back::after, .ringsbtn::after, .icon-btn::after, .zahnrad::after {
|
#back::after, .ringsbtn::after, .icon-btn::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
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 input { min-width: 0; width: 96px; text-align: right; }
|
||||||
.mitEinheit span:last-child { font-size: 13px; color: var(--fg2); }
|
.mitEinheit span:last-child { font-size: 13px; color: var(--fg2); }
|
||||||
.druckfeld { width: 58px !important; min-width: 0 !important; }
|
.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; }
|
.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 { 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);
|
background: rgba(0,0,0,.45);
|
||||||
animation: sheet-fade .18s ease-out;
|
animation: sheet-fade .18s ease-out;
|
||||||
}
|
}
|
||||||
.bildmenu {
|
/* .bildmenu ist am 05.09.2026 entfallen: das Fotomenue laeuft jetzt ueber
|
||||||
position: absolute; z-index: 1001;
|
das Aktionsblatt (aktionsblatt() in audi-dashboard-app.js), wie in der
|
||||||
left: 10px; right: 10px;
|
App. .bildmenu-catcher bleibt - den benutzt das SmartDeal-Blatt. */
|
||||||
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,
|
/* Formularblatt (siehe Kasten oben): unten angeschlagen, nur oben rund,
|
||||||
blickdicht. Stand vorher als Karte 60px unter dem oberen Rand - ausser
|
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;
|
overflow: hidden;
|
||||||
display: flex; flex-direction: column;
|
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 {
|
.sheet-kopf {
|
||||||
padding: 15px 18px 14px;
|
padding: 14px 18px 12px;
|
||||||
border-bottom: 1px solid var(--line);
|
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-kopf span { font-size: 13px; color: var(--fg2); line-height: 1.45; }
|
||||||
.sheet-gruppe button {
|
.sheet-gruppe button {
|
||||||
background: none; border: none;
|
background: none; border: none;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "audi_dashboard",
|
"domain": "audi_dashboard",
|
||||||
"name": "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",
|
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
|||||||
@@ -139,18 +139,29 @@
|
|||||||
flex-direction: column;
|
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 {
|
.ads-aktionsblatt__kopf {
|
||||||
padding: 15px 18px 14px;
|
padding: 14px 18px 12px;
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ads-aktionsblatt__kopf strong {
|
.ads-aktionsblatt__kopf strong {
|
||||||
font-size: 15px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 600;
|
||||||
color: var(--fg);
|
color: var(--fg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ads-aktionsblatt__kopf span {
|
.ads-aktionsblatt__kopf span {
|
||||||
|
|||||||
Reference in New Issue
Block a user