Fuenf gemeldete Bugs behoben: Haubenschloss/Tuerschloesser entfernt, Bestaetigungsdialog-Transparenz, geteilte FMM003-Koordinaten, Laedt-Haenger, generischer Beleg-Parser
Entfernt Haubenschloss-/Tuerschloss-Erkennung dauerhaft aus Setup-Katalog und
Sicherheitscheck (auf ausdruecklichen Wunsch, nicht nur leer/unzuordenbar wie
der Rest der abgeloesten VAG-Integration). Behebt eine durchscheinende
Bestaetigungs-Sheet ("Doppelt zugeordnet" u.a.) - derselbe --tile-2-
Transparenz-Fehler, der fuer das Setup-Popup schon behoben war, hier
nachgezogen. Ergaenzt einen zweiten GPS-Pfad (STANDORT_LAT_SENSOR/
STANDORT_LON_SENSOR) fuer Integrationen wie flespi, die Breiten-/Laengengrad
als zwei eigene Sensoren statt als device_tracker-Attribute liefern. Haertet
den bekannten "Laedt ..."-Haenger beim App-Start zusaetzlich ab: Tab-Klicks
pruefen jetzt aktiv nach, ob Daten inzwischen da sind. Ergaenzt
shell_beleg_parser.py um einen stationsunabhaengigen Fallback fuer
Tankbelege unbekannter Formate (Adresse/Gesamtbetrag/Menge/Rabatt-Herleitung
wie vom Nutzer vorgegeben) - die bestehende Shell-Erkennung bleibt
unveraendert und weiterhin durch die zehn echten Testbelege abgedeckt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,7 @@ Aufbau eines Shell-eReceipts (aus dem echten Beispiel, Reihenfolge fix):
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
@@ -198,6 +199,118 @@ def _parsen(text):
|
||||
}
|
||||
|
||||
|
||||
def _parsen_generisch(text):
|
||||
"""Stationsunabhängiger Fallback für Belege, die keinem der oben fest an
|
||||
Shells Beleg-Struktur verankerten Muster entsprechen (_parsen() wirft in
|
||||
dem Fall ValueError - siehe main()). Arbeitet nach demselben Verfahren,
|
||||
das an einem echten Beleg einer bislang unbekannten Tankstelle von Hand
|
||||
nachvollzogen wurde: Adresse suchen, Gesamtbetrag suchen ("Gesamt.../
|
||||
Absolut..."), Literangabe suchen ("Menge"/"Amount" oder eine an "l"/
|
||||
"Liter" hängende Zahl), daraus den tatsächlich bezahlten Preis/Liter
|
||||
errechnen (Gesamtbetrag / Liter). Steht daneben auch ein aufgedruckter
|
||||
Preis/Liter auf dem Beleg und weicht er vom errechneten ab, wurde ein
|
||||
Rabatt gewährt - auf dem Beleg meist als eigener Betrag mit einem Minus
|
||||
davor erkennbar, danach wird zuerst gesucht, sonst aus der Preisdifferenz
|
||||
hergeleitet. Ob ein so gefundener Rabatt als "SmartDeal" behandelt wird,
|
||||
entscheidet einzig station_name (Shell-Logo/-Label im Frontend, siehe
|
||||
frontend_veroeffentlichung.py) - kein Sonderfall hier nötig, die Frontend-
|
||||
Anzeige hängt nur an einem gesetzten discount, nicht an einem eigenen
|
||||
Kennzeichen.
|
||||
|
||||
Liefert deutlich weniger Felder als _parsen() (kein Bon-Kleingedrucktes
|
||||
wie TSE-Nummer/Zahlungszeitstempel) - unproblematisch, denn
|
||||
belegverarbeitung.py liest nur receipt_key als echtes Pflichtfeld,
|
||||
alles andere per .get()."""
|
||||
|
||||
def suchen(muster, flags=re.IGNORECASE | re.MULTILINE):
|
||||
return re.search(muster, text, flags)
|
||||
|
||||
# ---- Adresse: Name-/Straßen-/PLZ-Ort-Zeile hintereinander - wie bei
|
||||
# Shell (_parsen() oben), aber ohne Ankerzeile "Obj.-Nr." danach, also
|
||||
# direkt an der PLZ-Ort-Form (5 Ziffern + Text) erkannt. Nicht Pflicht.
|
||||
adresse = suchen(r"([^\n]{2,60})\n([^\n]{2,60})\n(\d{5}\s+[^\n]{2,40})")
|
||||
station_name = adresse.group(1).strip() if adresse else None
|
||||
station_address = (
|
||||
f"{adresse.group(2).strip()}, {adresse.group(3).strip()}" if adresse else None
|
||||
)
|
||||
|
||||
# ---- Gesamtbetrag ------------------------------------------------------
|
||||
gesamt = suchen(r"(?:Gesamt\w*|Absolut\w*|Endbetrag|Summe|Total)\D{0,20}?(" + _ZAHL + r")\s*(?:€|EUR)?")
|
||||
if not gesamt:
|
||||
raise ValueError("Kein Gesamtbetrag gefunden (weder 'Gesamt' noch 'Absolut')")
|
||||
fuel_total_eur = _de_zahl(gesamt.group(1))
|
||||
|
||||
# ---- Menge in Litern ----------------------------------------------------
|
||||
menge = suchen(r"(?:Menge|Amount)\D{0,15}?(" + _ZAHL + r")\s*(?:l\b|L\b|Liter|Ltr\.?)")
|
||||
if not menge:
|
||||
menge = suchen(r"(" + _ZAHL + r")\s*(?:Liter|Ltr\.?|l)\b")
|
||||
if not menge:
|
||||
raise ValueError("Keine Literangabe gefunden (weder 'Menge'/'Amount' noch 'X l')")
|
||||
liters = _de_zahl(menge.group(1))
|
||||
if not liters:
|
||||
raise ValueError("Literangabe ist 0 oder unlesbar")
|
||||
|
||||
berechneter_preis_l = round(fuel_total_eur / liters, 3)
|
||||
|
||||
# ---- Aufgedruckter Preis/Liter - Abweichung verrät einen Rabatt -------
|
||||
aufgedruckt = suchen(
|
||||
r"(?:Preis\s*/?\s*(?:je\s*)?Liter|Price\s*/?\s*L(?:iter)?|€\s*/\s*l|EUR\s*/\s*l)\D{0,10}?(" + _ZAHL + r")"
|
||||
)
|
||||
list_price_per_l = _de_zahl(aufgedruckt.group(1)) if aufgedruckt else berechneter_preis_l
|
||||
|
||||
discount = None
|
||||
discount_per_l = None
|
||||
if aufgedruckt and abs(list_price_per_l - berechneter_preis_l) > 0.005:
|
||||
minus = suchen(r"-\s*(" + _ZAHL + r")\s*(?:€|EUR)")
|
||||
if minus:
|
||||
discount = _de_zahl(minus.group(1))
|
||||
discount_per_l = round(discount / liters, 3)
|
||||
else:
|
||||
discount_per_l = round(list_price_per_l - berechneter_preis_l, 3)
|
||||
discount = round(discount_per_l * liters, 2)
|
||||
|
||||
# ---- Zeitstempel: irgendein Datum+Uhrzeit auf dem Beleg ----------------
|
||||
zeit = suchen(r"(\d{2})\.(\d{2})\.(\d{2,4})\D{0,10}?(\d{2}):(\d{2})")
|
||||
ts = _iso(zeit.group(1), zeit.group(2), zeit.group(3), zeit.group(4), zeit.group(5)) if zeit else None
|
||||
|
||||
# ---- Belegnummer, sonst deterministischer Ersatzschlüssel --------------
|
||||
# Wichtig: kein eingebautes hash() (pro Prozess zufällig gesalzen, siehe
|
||||
# PYTHONHASHSEED) - der Parser läuft für jeden Beleg als eigener Prozess
|
||||
# (Kopfkommentar oben), ein instabiler Schlüssel würde den §7.7-Regel-3-
|
||||
# Dedup-Vergleich in belegverarbeitung.py unbrauchbar machen.
|
||||
beleg_nr = suchen(
|
||||
r"(?:Beleg-?Nr\.?|Bon-?Nr\.?|Rechnungs-?Nr\.?|Receipt\s*No\.?|Trans(?:aktions)?-?Nr\.?)\D{0,5}?([\dA-Za-z/\-]+)"
|
||||
)
|
||||
receipt_no = beleg_nr.group(1) if beleg_nr else None
|
||||
schluessel_basis = "|".join(
|
||||
str(teil) for teil in (receipt_no, ts, station_address, fuel_total_eur, liters) if teil
|
||||
) or text[:80]
|
||||
receipt_key = "generisch_" + hashlib.sha1(schluessel_basis.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
return {
|
||||
"receipt_key": receipt_key,
|
||||
"receipt_no": receipt_no,
|
||||
"tse_beleg_nr": None,
|
||||
"ts": ts,
|
||||
"ts_payment": None,
|
||||
"ts_tse": None,
|
||||
"station_id": None,
|
||||
"station_name": station_name,
|
||||
"station_address": station_address,
|
||||
"article_no": None,
|
||||
"product_name": None,
|
||||
"fuel_type": None,
|
||||
"liters": liters,
|
||||
"list_price_per_l": list_price_per_l,
|
||||
"discount": discount,
|
||||
"discount_per_l": discount_per_l,
|
||||
"fuel_total_eur": fuel_total_eur,
|
||||
"receipt_total_eur": fuel_total_eur,
|
||||
"net_eur": None,
|
||||
"vat_eur": None,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Aufruf: python3 shell_beleg_parser.py <pfad-zur-pdf>", file=sys.stderr)
|
||||
@@ -205,10 +318,20 @@ def main():
|
||||
pdf_pfad = sys.argv[1]
|
||||
try:
|
||||
text = _text_aus_pdf(pdf_pfad)
|
||||
ergebnis = _parsen(text)
|
||||
except Exception as exc:
|
||||
print(f"Beleg konnte nicht geparst werden ({type(exc).__name__}): {exc}", file=sys.stderr)
|
||||
print(f"PDF konnte nicht gelesen werden ({type(exc).__name__}): {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
ergebnis = _parsen(text)
|
||||
except Exception:
|
||||
# Kein Shell-Beleg im bekannten Format - Versuch mit dem
|
||||
# stationsunabhängigen Fallback (_parsen_generisch() oben), bevor
|
||||
# endgültig aufgegeben wird.
|
||||
try:
|
||||
ergebnis = _parsen_generisch(text)
|
||||
except Exception as exc:
|
||||
print(f"Beleg konnte nicht geparst werden ({type(exc).__name__}): {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(json.dumps(ergebnis, ensure_ascii=False))
|
||||
|
||||
|
||||
|
||||
@@ -65,14 +65,10 @@ TUER_SENSOREN = []
|
||||
# Fenster (§4.1) - keine Quelle mehr vorhanden.
|
||||
FENSTER_SENSOREN = []
|
||||
|
||||
# Verriegelung - keine Quelle mehr vorhanden.
|
||||
TUERSCHLOSS_SENSOREN = []
|
||||
|
||||
# Heckklappe und Motorhaube - keine Quelle mehr vorhanden.
|
||||
HECKKLAPPE_SENSOR = ""
|
||||
HECKKLAPPENSCHLOSS_SENSOR = ""
|
||||
HAUBE_SENSOR = ""
|
||||
HAUBENSCHLOSS_SENSOR = ""
|
||||
|
||||
# Vom Fahrzeug selbst gemeldete Service-Fälligkeit (ergänzt die App-eigene,
|
||||
# aus dem Servicebuch berechnete Prognose) - keine Quelle mehr vorhanden.
|
||||
@@ -87,6 +83,13 @@ INSPEKTION_STRECKE_SENSOR = ""
|
||||
# vom FMM003 geliefert.
|
||||
STANDORT_TRACKER = "device_tracker.testzone_fmm003"
|
||||
|
||||
# Alternative zu STANDORT_TRACKER: manche Integrationen (z. B. flespi) liefern
|
||||
# Breiten-/Längengrad nicht als Attribute einer einzelnen device_tracker-
|
||||
# Entity, sondern als zwei eigene sensor-Entities. _standort() nutzt diese
|
||||
# beiden nur, wenn STANDORT_TRACKER leer ist oder keine Koordinaten liefert.
|
||||
STANDORT_LAT_SENSOR = ""
|
||||
STANDORT_LON_SENSOR = ""
|
||||
|
||||
# Update-Funktion (Einstellungen -> "Update suchen", siehe
|
||||
# updateverwaltung.py): Git-Repository, in das dieses Projekt gepflegt wird -
|
||||
# z. B. ein privates GitHub-Repo, genau wie für die HACS-Integration bereits
|
||||
|
||||
@@ -81,10 +81,6 @@ FELDER = [
|
||||
"hinweis": "\"aus\"/off = zu.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["window"], "units": [], "liste": True, "positionen": POSITIONEN,
|
||||
"pflicht": False, "stichworte": ["fenster", "window"]},
|
||||
{"key": "TUERSCHLOSS_SENSOREN", "label": "Türschlösser", "gruppe": "sicherheit",
|
||||
"hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt (device_class \"lock\"-Konvention).",
|
||||
"domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": True, "positionen": POSITIONEN,
|
||||
"pflicht": False, "stichworte": ["schloss", "lock", "verriegel", "entriegel"]},
|
||||
{"key": "HECKKLAPPE_SENSOR", "label": "Heckklappe", "gruppe": "sicherheit",
|
||||
"hinweis": "\"aus\"/off = zu.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False,
|
||||
@@ -97,10 +93,6 @@ FELDER = [
|
||||
"hinweis": "\"aus\"/off = zu.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["haube", "hood", "bonnet", "motorhaube"]},
|
||||
{"key": "HAUBENSCHLOSS_SENSOR", "label": "Haubenschloss", "gruppe": "sicherheit",
|
||||
"hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["haube", "hood", "schloss", "lock"]},
|
||||
{"key": "NAECHSTER_OELWECHSEL_SENSOR", "label": "Nächster Ölwechsel (Datum)", "gruppe": "uebersicht",
|
||||
"hinweis": "Vom Fahrzeug selbst gemeldete Fälligkeit, ergänzt die App-eigene Servicebuch-Prognose.",
|
||||
"domains": ["sensor"], "device_classes": ["date", "timestamp"], "units": [], "liste": False, "pflicht": False,
|
||||
@@ -121,6 +113,14 @@ FELDER = [
|
||||
"hinweis": "Braucht latitude/longitude-Attribute (HA-Konvention für device_tracker).",
|
||||
"domains": ["device_tracker"], "device_classes": [], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["standort", "gps", "tracker", "position", "ortung"]},
|
||||
{"key": "STANDORT_LAT_SENSOR", "label": "GPS-Standort: Breitengrad", "gruppe": "standort",
|
||||
"hinweis": "Nur nötig, falls die Integration Breiten-/Längengrad als zwei eigene Sensoren statt als Attribute einer GPS-Standort-Entity liefert (z. B. flespi).",
|
||||
"domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["breitengrad", "latitude", "lat"]},
|
||||
{"key": "STANDORT_LON_SENSOR", "label": "GPS-Standort: Längengrad", "gruppe": "standort",
|
||||
"hinweis": "Nur nötig, falls die Integration Breiten-/Längengrad als zwei eigene Sensoren statt als Attribute einer GPS-Standort-Entity liefert (z. B. flespi).",
|
||||
"domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["längengrad", "longitude", "lon", "lng"]},
|
||||
]
|
||||
|
||||
# Mehrere @state_trigger(f"...")-Dekoratoren (fahrterkennung.py,
|
||||
|
||||
@@ -88,37 +88,59 @@ def _abs_zahl(wert):
|
||||
|
||||
|
||||
def _standort():
|
||||
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel) - liest
|
||||
latitude/longitude/gps_accuracy von der in STANDORT_TRACKER hinterlegten
|
||||
device_tracker-Entity (HA-Konvention). Fehlt die Entity-ID oder ist sie
|
||||
(noch) nicht verfügbar, liefert diese Funktion durchgehend None statt
|
||||
eines geratenen Werts - genau das zustand_oder_none()-Muster von oben,
|
||||
hier als eigene Funktion, weil zusätzlich zum Zustand noch Attribute und
|
||||
der Aktualisierungszeitpunkt gebraucht werden: state.get() liefert dafür
|
||||
ein StateVal (str-Unterklasse) mit .last_updated als datetime-Attribut -
|
||||
kein eigenständiges state.last_updated(), das gibt es in pyscript nicht."""
|
||||
entity_id = einstellungen.STANDORT_TRACKER
|
||||
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel). Zwei
|
||||
mögliche Quellen, in dieser Reihenfolge versucht: (1) STANDORT_TRACKER,
|
||||
eine device_tracker-Entity mit latitude/longitude/gps_accuracy-Attributen
|
||||
(HA-Konvention) - der Normalfall; (2) STANDORT_LAT_SENSOR/
|
||||
STANDORT_LON_SENSOR, zwei eigene sensor-Entities für Breiten-/Längengrad -
|
||||
manche Integrationen (z. B. flespi) liefern Koordinaten so, statt als
|
||||
Attribute einer einzelnen Tracker-Entity. Fehlt beides oder ist (noch)
|
||||
nicht verfügbar, liefert diese Funktion durchgehend None statt eines
|
||||
geratenen Werts - genau das zustand_oder_none()-Muster von oben, hier als
|
||||
eigene Funktion, weil zusätzlich zum Zustand noch Attribute und der
|
||||
Aktualisierungszeitpunkt gebraucht werden: state.get() liefert dafür ein
|
||||
StateVal (str-Unterklasse) mit .last_updated als datetime-Attribut - kein
|
||||
eigenständiges state.last_updated(), das gibt es in pyscript nicht."""
|
||||
leer = {"lat": None, "lon": None, "genauigkeit_m": None, "zeit": None}
|
||||
if not entity_id:
|
||||
return leer
|
||||
try:
|
||||
wert = state.get(entity_id)
|
||||
attribute = state.getattr(entity_id)
|
||||
except NameError:
|
||||
return leer
|
||||
if not attribute or attribute.get("latitude") is None:
|
||||
return leer
|
||||
zeit = None
|
||||
try:
|
||||
zeit = wert.last_updated.isoformat()
|
||||
except AttributeError:
|
||||
pass
|
||||
return {
|
||||
"lat": _zu_zahl(attribute.get("latitude")),
|
||||
"lon": _zu_zahl(attribute.get("longitude")),
|
||||
"genauigkeit_m": _zu_zahl(attribute.get("gps_accuracy")),
|
||||
"zeit": zeit,
|
||||
}
|
||||
|
||||
tracker_id = einstellungen.STANDORT_TRACKER
|
||||
if tracker_id:
|
||||
try:
|
||||
wert = state.get(tracker_id)
|
||||
attribute = state.getattr(tracker_id)
|
||||
except NameError:
|
||||
attribute = None
|
||||
if attribute and attribute.get("latitude") is not None:
|
||||
zeit = None
|
||||
try:
|
||||
zeit = wert.last_updated.isoformat()
|
||||
except AttributeError:
|
||||
pass
|
||||
return {
|
||||
"lat": _zu_zahl(attribute.get("latitude")),
|
||||
"lon": _zu_zahl(attribute.get("longitude")),
|
||||
"genauigkeit_m": _zu_zahl(attribute.get("gps_accuracy")),
|
||||
"zeit": zeit,
|
||||
}
|
||||
|
||||
lat_id, lon_id = einstellungen.STANDORT_LAT_SENSOR, einstellungen.STANDORT_LON_SENSOR
|
||||
if lat_id and lon_id:
|
||||
try:
|
||||
lat_wert = state.get(lat_id)
|
||||
lon_wert = state.get(lon_id)
|
||||
except NameError:
|
||||
return leer
|
||||
lat, lon = _zu_zahl(lat_wert), _zu_zahl(lon_wert)
|
||||
if lat is None or lon is None:
|
||||
return leer
|
||||
zeit = None
|
||||
try:
|
||||
zeit = lat_wert.last_updated.isoformat()
|
||||
except AttributeError:
|
||||
pass
|
||||
return {"lat": lat, "lon": lon, "genauigkeit_m": None, "zeit": zeit}
|
||||
|
||||
return leer
|
||||
|
||||
|
||||
POSITIONEN = ["vorne links", "vorne rechts", "hinten links", "hinten rechts"]
|
||||
@@ -137,14 +159,10 @@ def _sicherheitscheck():
|
||||
for pos, sensor in zip(POSITIONEN, einstellungen.FENSTER_SENSOREN):
|
||||
w = zustand_oder_none(sensor)
|
||||
eintraege.append({"label": f"Fenster {pos}", "ok": None if w is None else w == "off"})
|
||||
for pos, sensor in zip(POSITIONEN, einstellungen.TUERSCHLOSS_SENSOREN):
|
||||
w = zustand_oder_none(sensor)
|
||||
eintraege.append({"label": f"Türschloss {pos}", "ok": None if w is None else w == "off"})
|
||||
for label, sensor in [
|
||||
("Heckklappe", einstellungen.HECKKLAPPE_SENSOR),
|
||||
("Heckklappenschloss", einstellungen.HECKKLAPPENSCHLOSS_SENSOR),
|
||||
("Motorhaube", einstellungen.HAUBE_SENSOR),
|
||||
("Haubenschloss", einstellungen.HAUBENSCHLOSS_SENSOR),
|
||||
]:
|
||||
w = zustand_oder_none(sensor)
|
||||
eintraege.append({"label": label, "ok": None if w is None else w == "off"})
|
||||
|
||||
@@ -2992,6 +2992,15 @@ function render() {
|
||||
});
|
||||
}
|
||||
const go = (name, id) => {
|
||||
// Bekanntes Race (siehe Kopfkommentar bei nachladeAnstossen): auf einem
|
||||
// langsam startenden Backend kann die App auf "Lädt …" hängen bleiben,
|
||||
// obwohl die Daten inzwischen längst da wären - der bislang einzige
|
||||
// beobachtete Ausweg war ein Tabwechsel, der zufällig einen frischen
|
||||
// datenLaden()-Durchlauf auslöste. Hier deshalb bei jedem Tab-/Menüklick
|
||||
// aktiv nachschauen, statt nur auf den nächsten hass-Push zu warten -
|
||||
// no-op, falls die Daten längst da sind (datenLaden() rendert dann
|
||||
// ohnehin nichts doppelt Sichtbares).
|
||||
if (!DATEN_GELADEN) datenLaden(false);
|
||||
// Ein unbestätigter Servicebuch-Entwurf (siehe data-sbneu) verlässt die
|
||||
// Anwendung nie ungespeichert - wird die Eintragsseite ohne Klick auf
|
||||
// "Speichern" verlassen, verwerfen wir ihn hier statt ihn liegen zu lassen
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version": 1786744000}
|
||||
{"version": 1786752000}
|
||||
|
||||
@@ -853,7 +853,15 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
@keyframes sheet-fade { from { opacity: 0 } to { opacity: 1 } }
|
||||
@keyframes sheet-rein { from { transform: translateY(18px); opacity: 0 } to { transform: none; opacity: 1 } }
|
||||
.sheet-gruppe {
|
||||
background: var(--tile-2);
|
||||
/* --tile-2 is translucent in the iOS overlay's night theme (rgba white,
|
||||
meant for tile-on-tile layering) - a confirm sheet sits directly over
|
||||
whatever page content triggered it, so a see-through card made titles
|
||||
like "Doppelt zugeordnet" hard to read against the busy background
|
||||
bleeding through. Same fix already applied to .setup-popup/.standortmenu
|
||||
for the same reason - opaque surface token, with the --canvas fallback
|
||||
this file already uses elsewhere for when the (purely additive, in
|
||||
principle removable) overlay isn't loaded. */
|
||||
background: var(--tile-deckend, var(--canvas));
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
@@ -877,7 +885,7 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
|
||||
.sheet-gruppe button:active { background: var(--shade); }
|
||||
.sheet-gruppe button.loeschen { color: var(--bad); }
|
||||
.sheet-abbrechen {
|
||||
background: var(--tile-2); border: none; border-radius: 16px;
|
||||
background: var(--tile-deckend, var(--canvas)); border: none; border-radius: 16px;
|
||||
font-family: inherit; font-size: 16px; color: var(--fg);
|
||||
padding: 16px; min-height: 52px; cursor: pointer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user