@@ -389,8 +389,17 @@ function bildStand(datei) {
// loest sich damit auf.
return BILD _STAENDE [ datei ] ? ? 0 ;
}
/ * n u l l , w e n n d a s B a c k e n d m e l d e t , d a s s e s d i e s e s F o t o n i c h t g i b t ( S t a n d 0 ) .
Vorher wurde die Adresse trotzdem gebaut und der Browser holte bei JEDEM
Rendern eine 404 - am 04.09 . 2026 gemessen : fuenf Anfragen fuer
front - schraeg . webp in einem einzigen Ladevorgang , dazu heckansicht ,
cockpit , sitze und seitenansicht - winter . Die App weiss laengst , dass es
sie nicht gibt ; sie muss nicht danach fragen . * /
function bildAdresse ( datei ) {
return ` /local/bilder/ ${ datei } ?v= ${ bildStand ( datei ) } ` ;
const stand = bildStand ( datei ) ;
if ( BILD _STAENDE && stand === 0 ) return null ;
return ` /local/bilder/ ${ datei } ?v= ${ stand } ` ;
}
/ * P l a t z h a l t e r s t a t t k a p u t t e m B i l d - S y m b o l : b e i L a d e f e h l e r ( B i l d e r a u s § 7 a
@@ -406,8 +415,15 @@ function bildAdresse(datei) {
eines Fotos nicht . * /
function bildMitPlatzhalter ( src , datei , label , klasse , imgId , dateiZeigen = false ) {
const idTeil = imgId ? ` id=" ${ imgId } " ` : "" ;
return ` <div class="bildbox ${ klasse } " data-bildbox title=" ${ esc ( label ) } ">
< img class = "${klasse}" $ { idTeil } src = "${src}" alt = "${esc(label)}" data - bildlabel = "${esc(label)}" data - bilddatei = "${esc(datei)}"
// Ohne Adresse KEIN src-Attribut - ein leeres src holt die Seite selbst
// erneut, und ein <img> ganz ohne src zeigt das kaputte Bildsymbol.
// Sichtbar bleibt es nicht: .bildbox.bild-fehlt img ist ausgeblendet.
// Das <img> selbst bleibt trotzdem stehen, weil die Galerie es weiter-
// schaltet (#carimg) - ohne das Element bliebe sie stehen.
const srcTeil = src ? ` src=" ${ src } " ` : "" ;
const fehltTeil = src ? "" : " bild-fehlt" ;
return ` <div class="bildbox ${ klasse } ${ fehltTeil } " data-bildbox title=" ${ esc ( label ) } ">
< img class = "${klasse}" $ { idTeil } $ { srcTeil } alt = "${esc(label)}" data - bildlabel = "${esc(label)}" data - bilddatei = "${esc(datei)}"
onerror = "this.closest('[data-bildbox]').classList.add('bild-fehlt')" >
< div class = "platzhalter ${klasse}" >
< svg viewBox = "0 0 24 24" class = "platzhalter-icon" fill = "currentColor" > < path fill - rule = "evenodd" d = "m23.35 1.35-22 22-.7-.7 22-22zM1 3h17.5v1H2v16H1zm21 11.85V4h1v17H6v-1h16v-3.77l-3.24-3.08-4.57 4.48-2.85-2.84.7-.7 2.15 2.14 4.56-4.47 3.25 3.1ZM4 8.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0M6.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3" clip - rule = "evenodd" / > < / s v g >
@@ -602,20 +618,29 @@ function markenlogo() {
szeneKlasse ( ) , das je nach Bild und Reifensatz "strasse" oder "winter"
an . szene haengte ; die zugehoerigen : : after - Regeln sind in
audi - dashboard . css beschrieben . * /
function bildWeiter ( ) {
/ * E i n B i l d w e i t e r ( r i c h t u n g 1 ) o d e r z u r u e c k ( - 1 ) .
Die Richtung kam am 04.09 . 2026 dazu : der Eigentuemer wollte die Galerie
auch durch Wischen nach links / rechts durchschalten , nicht nur durch
Tippen ( galerieWischenVerdrahten unten , wortgleich mit der App ) . * /
function bildWeiter ( richtung = 1 ) {
// Ueber die Galerie-Plaetze, nicht ueber alle: die Draufsicht wird
// uebersprungen (siehe nurStatus in BILDER).
const jetzt = GALERIE . findIndex ( ( b ) => b === BILDER [ bild ] ) ;
const naechst = GALERIE [ ( Math . max ( 0 , jetzt ) + 1 ) % GALERIE . length ] ;
const anzahl = GALERIE . length ;
if ( ! anzahl ) return ;
const naechst = GALERIE [ ( Math . max ( 0 , jetzt ) + richtung + anzahl ) % anzahl ] ;
bild = BILDER . indexOf ( naechst ) ;
const img = ROOT . getElementById ( "carimg" ) ;
if ( ! img ) return ;
const info = bildInfo ( bild ) ;
img . src = info . src ;
// Ohne Adresse das Attribut ENTFERNEN statt auf null zu setzen: src=""
// laesst den Browser die Seite selbst erneut holen.
if ( info . src ) img . src = info . src ; else img . removeAttribute ( "src" ) ;
img . alt = info . label ;
const box = img . closest ( "[data-bildbox]" ) ;
if ( box ) {
box . classList . remove ( "bild-fehlt" ) ; // neues Bild kann laden, auch wenn das vorherige fehlte
box . classList . toggle ( "bild-fehlt" , ! info . src ) ;
box . querySelector ( ".platzhalter-label" ) . textContent = info . label ;
}
const sz = ROOT . getElementById ( "szene" ) ;
@@ -623,6 +648,34 @@ function bildWeiter() {
[ ... ROOT . getElementById ( "dots" ) . children ] . forEach ( ( d , i ) => d . classList . toggle ( "on" , GALERIE [ i ] === BILDER [ bild ] ) ) ;
}
/ * W i s c h e n i n d e r B i l d e r g a l e r i e ( W u n s c h d e s E i g e n t u e m e r s , 0 4 . 0 9 . 2 0 2 6 ) .
Zeigerereignisse statt Beruehrungsereignisse : dieselbe Verdrahtung traegt
Finger , Stift und Maus , und genau so macht es das Standort - Blatt schon
( standortMenuVerdrahten ) . Ein Wisch ab 40 px zaehlt als Wisch , alles
darunter bleibt ein Tipp - dieselbe Schwelle wie dort .
GEWISCHT SCHLUCKT DEN KLICK : ein Wisch loest anschliessend auch ein
click aus . Ohne diese Sperre schaltete die Galerie zweimal weiter . * /
let galerieZug = null ;
let galerieGewischt = false ;
function galerieWischenVerdrahten ( ) {
ROOT . addEventListener ( "pointerdown" , ( e ) => {
const feld = e . target . closest && e . target . closest ( "[data-bildgalerie]" ) ;
galerieZug = feld ? { x0 : e . clientX , y0 : e . clientY } : null ;
} ) ;
ROOT . addEventListener ( "pointerup" , ( e ) => {
const zug = galerieZug ;
galerieZug = null ;
if ( ! zug ) return ;
const dx = e . clientX - zug . x0 ;
// Senkrecht ueberwiegt: das war ein Scrollen, kein Blaettern.
if ( Math . abs ( dx ) <= 40 || Math . abs ( e . clientY - zug . y0 ) > Math . abs ( dx ) ) return ;
galerieGewischt = true ;
bildWeiter ( dx < 0 ? 1 : - 1 ) ;
} ) ;
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - T e r m i n e / V o r h e r s a g e
heute ( ) statt einer eingefrorenen Konstante - der Prototyp nutzte ein
festes Demo - Datum ( new Date ( 2026 , 7 , 3 ) ) , das für den echten Betrieb durch
@@ -637,7 +690,72 @@ const gueltigesDatum = (d) => d instanceof Date && !Number.isNaN(d.getTime());
const dedat = ( d ) => ( gueltigesDatum ( d )
? d . toLocaleDateString ( "de-DE" , { day : "2-digit" , month : "2-digit" , year : "numeric" } )
: "– " ) ;
function dat ( s ) { const [ d , m , j ] = s . split ( "." ) . map ( Number ) ; return new Date ( j , m - 1 , d ) ; }
function dat ( s ) {
// Nimmt seit dem 04.09.2026 auch ein fertiges Date entgegen: die
// Hauptuntersuchung reicht ihre Basis als Date herein (siehe
// datumAusText/hauptuntersuchungsTermin), monatePlus() ruft dat() darauf.
if ( s instanceof Date ) return new Date ( s . getTime ( ) ) ;
const [ d , m , j ] = String ( s ) . split ( "." ) . map ( Number ) ;
return new Date ( j , m - 1 , d ) ;
}
/ * F r e i t e x t - D a t u m a u s d e m P r o f i l - W O R T G L E I C H m i t a l s Z e i t p u n k t ( ) i n
companion - app / src / format . ts .
Die Felder "Erstzulassung" und "Hauptuntersuchung" sind Eingabefelder , und
der Eigentuemer traegt dort ein , was ihm richtig scheint . Am 04.09 . 2026
gemessen : das Panel leitete die Hauptuntersuchung nur dann aus der
Erstzulassung ab , wenn diese ein "/" enthielt - bei "01.03.2025" ( eine
voellig richtige Eingabe ) blieb der Termin still leer .
` genau: "monat" ` heisst : die Angabe nennt keinen Tag ( "08/2026" ) . Daraus
den Ersten des Monats zu ZEIGEN waere eine Erfindung - deshalb die
Genauigkeit mitfuehren und in datumText ( ) beruecksichtigen . * /
const DATUM _ISO = /^(\d{4})-(\d{1,2})-(\d{1,2})$/ ;
const DATUM _TAG = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/ ;
const DATUM _MONAT = /^(\d{1,2})[./](\d{4})$/ ;
const DATUM _ISO _MONAT = /^(\d{4})-(\d{1,2})$/ ;
function datumBauen ( jahr , monat , tag ) {
if ( monat < 1 || monat > 12 || tag < 1 || tag > 31 ) return null ;
const d = new Date ( jahr , monat - 1 , tag ) ;
// new Date(2025,1,31) waere der 3. Maerz - ein unmoegliches Datum soll "-"
// ergeben, nicht klammheimlich ein anderes.
if ( d . getFullYear ( ) !== jahr || d . getMonth ( ) !== monat - 1 || d . getDate ( ) !== tag ) return null ;
return d ;
}
function datumAusText ( wert ) {
if ( wert instanceof Date ) return gueltigesDatum ( wert ) ? { zeit : wert , genau : "tag" } : null ;
const text = String ( wert == null ? "" : wert ) . trim ( ) ;
if ( ! text ) return null ;
let m = DATUM _ISO . exec ( text ) ;
if ( m ) { const d = datumBauen ( + m [ 1 ] , + m [ 2 ] , + m [ 3 ] ) ; return d ? { zeit : d , genau : "tag" } : null ; }
m = DATUM _TAG . exec ( text ) ;
if ( m ) { const d = datumBauen ( + m [ 3 ] , + m [ 2 ] , + m [ 1 ] ) ; return d ? { zeit : d , genau : "tag" } : null ; }
m = DATUM _MONAT . exec ( text ) ;
if ( m ) { const d = datumBauen ( + m [ 2 ] , + m [ 1 ] , 1 ) ; return d ? { zeit : d , genau : "monat" } : null ; }
m = DATUM _ISO _MONAT . exec ( text ) ;
if ( m ) { const d = datumBauen ( + m [ 1 ] , + m [ 2 ] , 1 ) ; return d ? { zeit : d , genau : "monat" } : null ; }
return null ;
}
/* Zeigt eine monatsgenaue Angabe als Monat statt als erfundenen Ersten. */
const demonat = ( d ) => ( gueltigesDatum ( d )
? d . toLocaleDateString ( "de-DE" , { month : "long" , year : "numeric" } )
: "– " ) ;
const datumText = ( d , genau ) => ( genau === "monat" ? demonat ( d ) : dedat ( d ) ) ;
/ * D i e E r s t z u l a s s u n g , w i e s i e a n g e z e i g t w i r d : f o r m a t i e r t , w e n n s i e s i c h l e s e n
laesst , sonst der Rohwert , sonst ein Strich .
Bis zum 04.09 . 2026 stand hier immer der Rohwert und bei leerem Feld gar
nichts - waehrend die FIN daneben "– " zeigte . Drei Leerdarstellungen in
einer Kachel . Wortgleich mit datum ( fahrzeug . erstzulassung ) in der App . * /
function erstzulassungText ( ) {
const z = datumAusText ( CAR . erstzulassung ) ;
if ( z ) return datumText ( z . zeit , z . genau ) ;
return ( CAR . erstzulassung || "" ) . trim ( ) || "– " ;
}
/* Datum + Uhrzeit in einer Zeile, für Start-/Zielzeitpunkt der Einzelfahrt. */
const dezeitpunkt = ( d ) => ( gueltigesDatum ( d )
? ` ${ dedat ( d ) } · ${ d . toLocaleTimeString ( "de-DE" , { hour : "2-digit" , minute : "2-digit" } )} Uhr `
@@ -1321,6 +1439,25 @@ const MARKEN = [
die Anzeige ( Vorgabe des Eigentuemers , 04.09 . 2026 ) .
Wortgrenzen sind Absicht : "Star" darf nicht in "Starnberg" treffen . * /
/ * N i m m t e i n e m N a m e n s t e i l d i e f u e h r e n d e K o p f m a r k e a b - w o r t g l e i c h m i t
ohneMarkeVorn ( ) in companion - app / src / daten / tankstelle . ts .
Bis zum 04.09 . 2026 wurde ein Namensteil nur uebersprungen , wenn er FUER
SICH eine Marke ist . Ein von Hand eingetragener Name ohne Komma
( "Shell München Ost" ) rutschte komplett durch und landete als ORT :
"Shell, Musterstr., Shell München Ost" . Jetzt faellt die Marke vorn ab .
Steht die Marke NICHT am Anfang ( "Autohof Shell Nord" ) , bleibt der Teil
unangetastet - dort ist nicht sicher , was Marke und was Ortsname ist . * /
function ohneMarkeVorn ( teil ) {
const marke = markeErkennen ( teil ) ;
if ( ! marke ) return teil ;
// Kein Maskieren noetig: marke stammt immer aus MARKEN, und dort steht
// kein einziges Sonderzeichen der regulaeren Ausdruecke.
const muster = new RegExp ( ` ^ ${ marke } \\ b[ \\ s,]* ` , "i" ) ;
return muster . test ( teil ) ? teil . replace ( muster , "" ) . trim ( ) : teil ;
}
function markeErkennen ( name ) {
const roh = ( name || "" ) . trim ( ) ;
if ( ! roh ) return null ;
@@ -1374,14 +1511,10 @@ function tankstelleTeile(name, adresse, marke) {
const teile = { marke : ( marke || "" ) . trim ( ) || markeErkennen ( name ) || "" , strasse : "" , hausnummer : "" , plz : "" , ort : "" , roh : ( name || "" ) . trim ( ) } ;
const quellen = ( adresse || "" ) . split ( "," ) ;
for ( const stueck of ( name || "" ) . split ( "," ) ) {
const t = stueck . trim ( ) ;
// Ein Teil, der fuer sich genommen eine Kopfmarke IST, taugt nie als Or t.
// Frueher stand hier nur der Vergleich mit der gewaehlten Marke; seit die
// gespeicherte Angabe vorgeht, koennen beide auseinanderfallen, und dann
// rutschte die Marke aus dem Namen in den Ort.
const eigen = t ? markeErkennen ( t ) : null ;
if ( eigen && eigen . toLowerCase ( ) === t . toLowerCase ( ) ) continue ;
quellen . push ( t ) ;
// Die Marke faellt vorn ab - sie steht schon fest und taugt nie als Ort.
// Bleibt nichts uebrig, war der Teil nur die Marke selbs t.
const t = ohneMarkeVorn ( stueck . trim ( ) ) ;
if ( t ) quellen . push ( t ) ;
}
for ( const stueck of quellen ) {
const t = stueck . trim ( ) ;
@@ -2136,7 +2269,9 @@ function prognoseZeile(art) {
function terminText ( t ) {
const restKm = t . km !== null && CAR . odoBekannt ? t . km - CAR . odo : null ;
const restKmAbs = restKm !== null ? Math . abs ( restKm ) : null ;
const haupt = t . zuerst === "km" && restKm !== null ? "in " + de ( restKmAbs ) + " km" : dedat ( t . datum ) ;
// datumText statt dedat: eine monatsgenaue Faelligkeit ("08/2026") darf nicht
// als erfundener Erster des Monats erscheinen.
const haupt = t . zuerst === "km" && restKm !== null ? "in " + de ( restKmAbs ) + " km" : datumText ( t . datum , t . genau ) ;
const neben = t . zuerst === "km" ? "voraussichtlich " + dedat ( t . kmDatum ) + " · " + dedat ( t . datum )
: ( t . km !== null ? "in " + de ( restKmAbs ) + " km" : ( t . art === "Hauptuntersuchung" ? "" : "aus dem Wartungsplan fortgeschrieben" ) ) ;
return { haupt , neben , restKm } ;
@@ -2352,7 +2487,7 @@ function vAudi() {
<!-- Die technischen Daten stehen eine Ebene tiefer ( Identität und Technik ) ;
hier nur der Hinweis darauf , statt einer abgeschnittenen Motorzeile . -- >
< div class = "row" > < dt > Details < / d t > < d d c l a s s = " r o w - h i n w e i s " > I n f o r m a t i o n e n ü b e r d a s A u t o < / d d > < / d i v >
< div class = "row" > < dt > Erstzulassung < / d t > < d d > $ { e s c ( C A R . e r s t z u l a s s u n g ) } < / d d > < / d i v >
< div class = "row" > < dt > Erstzulassung < / d t > < d d > $ { e s c ( e r s t z u l a s s u n g T e x t ( ) ) } < / d d > < / d i v >
< div class = "row" > < dt > Ausführung < / d t > < d d > $ { C O N F I G . a u s f u e h r u n g ? e s c ( C O N F I G . a u s f u e h r u n g ) : " n o c h o f f e n " } < / d d > < / d i v >
< div class = "row" style = "border-bottom:0" > < dt > Ausstattung < / d t >
< dd > $ { CAR . ausstattung . reduce ( ( n , g ) => n + g . sonder . length , 0 ) } Positionen < / d d > < / d i v >
@@ -2906,7 +3041,7 @@ function vIdent() {
< div class = "row" > < dt > Ausführung < / d t > < d d > $ { C O N F I G . a u s f u e h r u n g ? e s c ( C O N F I G . a u s f u e h r u n g ) : " n o c h o f f e n " } < / d d > < / d i v >
< div class = "row" > < dt > FIN < / d t > < d d > $ { e s c ( C A R . f i n ) } < / d d > < / d i v >
< div class = "row" > < dt > Motor und Leistung < / d t > < d d > $ { e s c ( C A R . d e t a i l s ) } < / d d > < / d i v >
< div class = "row" > < dt > Erstzulassung < / d t > < d d > $ { e s c ( C A R . e r s t z u l a s s u n g ) } < / d d > < / d i v >
< div class = "row" > < dt > Erstzulassung < / d t > < d d > $ { e s c ( e r s t z u l a s s u n g T e x t ( ) ) } < / d d > < / d i v >
< div class = "row" > < dt > Kennzeichen < / d t > < d d > $ { e s c ( C O N F I G . k e n n z e i c h e n ) } < / d d > < / d i v >
< div class = "row" style = "border-bottom:0" > < dt > Zulassung < / d t > < d d > D e u t s c h l a n d < / d d > < / d i v >
< / d l > < / d i v >
@@ -2987,6 +3122,29 @@ function gewaehlterTermin() {
return auswahl [ Math . min ( svAuswahl , auswahl . length - 1 ) ] ;
}
/ * D i e H a u p t u n t e r s u c h u n g h a t d r e i m o e g l i c h e Q u e l l e n , i n d i e s e r R e i h e n f o l g e :
1. die im Profil EINGETRAGENE Faelligkeit ( CAR . hu ) . Sie steht auf dem
Pruefbericht und ist damit die belastbarste Angabe - bis zum
04.09 . 2026 hat das Panel sie gar nicht benutzt , obwohl sie
gespeichert war ( Befund des Eigentuemers ) .
2. der letzte Servicebuch - Eintrag plus 24 Monate .
3. die Erstzulassung plus 24 Monate .
Zu 3. : das ging vorher NUR mit einer Erstzulassung im Format MM / JJJJ .
Ein vollstaendiges Datum ( 01.03 . 2025 ) - eine voellig richtige Eingabe -
liess den Termin still leer . datumAusText ( ) nimmt jetzt beide Formen . * /
function hauptuntersuchungsTermin ( bauen ) {
const gesetzt = datumAusText ( CAR . hu ) ;
if ( gesetzt ) {
return { art : "Hauptuntersuchung" , km : null , datum : gesetzt . zeit , kmDatum : null ,
zuerst : "zeit" , basis : null , ziel : gesetzt . zeit , genau : gesetzt . genau } ;
}
const erst = datumAusText ( CAR . erstzulassung ) ;
return bauen ( "Hauptuntersuchung" , letzterEintrag ( "hauptuntersuchung" ) , null , 24 ,
erst ? erst . zeit : null ) ;
}
function termine ( ) {
const oel = CAR . oel ;
const oelKm = oel . modus === "hersteller" ? oel . herstellerKm : Math . min ( oel . km , oel . herstellerKm ) ;
@@ -3032,8 +3190,7 @@ function termine() {
return [
oelTermin ,
inspTermin ,
bauen ( "Hauptuntersuchung" , letzterEintrag ( "hauptuntersuchung" ) , null , 24 ,
CAR . erstzulassung && CAR . erstzulassung . includes ( "/" ) ? ` 01. ${ CAR . erstzulassung . replace ( "/" , "." ) } ` : null ) ,
hauptuntersuchungsTermin ( bauen ) ,
] ;
}
@@ -3143,7 +3300,7 @@ function vVers() {
$ { ciSVG ( CI . settingsS , 17 ) }
< / b u t t o n >
< div class = "fig" style = "font-size:46px;margin-top:12px" > $ { eur ( v . beitrag ) } < span class = "unit" > € < / s p a n > < / d i v >
< span class = "label" style = "margin-top:14px" > $ { esc ( v . zahlweise ) } · enthält $ { eur ( v . steueranteil ) } € Versicherungsteuer ( $ { esc ( v . steuersatz ) } ) < / s p a n >
< span class = "label" style = "margin-top:14px" > $ { esc ( v . zahlweise ) } · enthält $ { eur ( v . steueranteil ) } € Versicherungsteuer ${ ( v . steuersatz || "" ) . trim ( ) ? ` ( ${ esc ( v . steuersatz ) } ) ` : "" } < / s p a n >
< dl class = "rows" style = "margin-top:16px" >
$ { v . teile . map ( ( t , i ) => ` <div class="row" ${ i === v . teile . length - 1 ? 'style="border-bottom:0"' : "" } >
< dt > $ { esc ( t . bez ) } $ { t . sf ? ` <br><span style="color:var(--fg3);font-size:11px"> ${ esc ( t . sf ) } · Beitragssatz ${ esc ( t . satz ) } </span></dt> ` : "</dt>" }
@@ -3211,7 +3368,7 @@ function vVertragsdetails() {
< div style = "font-size:14px;margin-top:12px" > Panne , Unfall , Diebstahl , Fahrerausfall < / d i v >
< span class = "label" style = "margin-top:10px" > Nummern und Leistungen für den Ernstfall < / s p a n >
< / b u t t o n >
$ { v . leistungen . map ( ( g ) => ` <div class="tile"><span class="label"> ${ esc ( g . gruppe ) } </span>
$ { v . leistungen . filter ( ( g ) => ( g . gruppe || "" ) . trim ( ) || ( g . posten || [ ] ) . length ) . map ( ( g ) => ` <div class="tile"><span class="label"> ${ esc ( g . gruppe ) } </span>
< dl class = "rows" style = "margin-top:8px" >
$ { g . posten . map ( ( x , i ) => ` <div class="row" ${ i === g . posten . length - 1 ? 'style="border-bottom:0"' : "" } >
< dt style = "color:var(--fg)" > $ { esc ( x ) } < / d t > < d d > < / d d > < / d i v > ` ) . j o i n ( " " ) }
@@ -4479,6 +4636,20 @@ let belegPopup = null;
let swipeAktivWrap = null , swipeStartX = 0 , swipeStartY = 0 , swipeGeste = null , swipeDX = 0 ;
let swipeWarGezogen = false ;
const SWIPE _BREITE = 84 ;
/ * A b d i e s e m A n t e i l d e r Z e i l e n b r e i t e l o e s c h t d a s L o s l a s s e n s o f o r t , o h n e
zweiten Tipp - so macht es iOS , und genau das fehlte : ein voller Wisch
blieb wie ein kurzer an der Raste stehen ( Wunsch des Eigentuemers ,
04.09 . 2026 ) . 0 , 55 haelt die Geste absichtsvoll : eine halbe Zeile ist beim
seitlichen Scrollen leicht versehentlich erreicht , eine klare Mehrheit
nicht . Wortgleich mit WISCH _VOLL _ * in bausteine . tsx und
FULL _SWIPE _FRACTION in SwipeRow ( Design - System ) . * /
const SWIPE _VOLL _ANTEIL = 0.55 ;
const SWIPE _VOLL _MAX = 220 ;
const SWIPE _VOLL _MIN = 120 ;
function swipeVollAb ( wrap ) {
const breite = wrap ? wrap . offsetWidth : 0 ;
return - Math . min ( SWIPE _VOLL _MAX , Math . max ( SWIPE _VOLL _MIN , breite * SWIPE _VOLL _ANTEIL ) ) ;
}
/ * Z i e h e n z u m A k t u a l i s i e r e n ( n u r Ü b e r s i c h t ) - p t r Y 0 i s t d i e Y - P o s i t i o n d e s
Fingers / der Maus beim Start , null wenn gerade keine Geste läuft . Eigene
@@ -5865,19 +6036,40 @@ function ereignisseVerdrahten() {
// audi-dashboard.css) - sonst blitzt er dauerhaft an den Zeilenraendern durch.
swipeAktivWrap . classList . add ( "wischt" ) ;
const basis = swipeAktivWrap . classList . contains ( "swiped" ) ? - SWIPE _BREITE : 0 ;
swipeDX = Math . max ( - SWIPE _BREITE - 20 , Math . min ( 0 , basis + dx ) ) ;
// Nicht mehr bei SWIPE_BREITE abschneiden: ohne den weiteren Weg gibt es
// keinen vollen Wisch und keine Rueckmeldung, dass er loescht.
swipeDX = Math . min ( 0 , basis + dx ) ;
const flaeche = swipeAktivWrap . querySelector ( ".swipe-delete" ) ;
if ( flaeche ) {
// Je weiter gezogen, desto mehr Rot - dieselbe Lesart wie auf iOS.
flaeche . style . width = Math . max ( SWIPE _BREITE , - swipeDX ) + "px" ;
flaeche . classList . toggle ( "swipe-delete--scharf" , swipeDX <= swipeVollAb ( swipeAktivWrap ) ) ;
}
const inhalt = swipeAktivWrap . querySelector ( ".swipe-content" ) ;
inhalt . style . transition = "none" ;
inhalt . style . transform = ` translateX( ${ swipeDX } px) ` ;
} , { passive : false } ) ;
function swipeBeenden ( ) {
if ( ! swipeAktivWrap ) return ;
const wrapJetzt = swipeAktivWrap ;
const flaecheJetzt = wrapJetzt . querySelector ( ".swipe-delete" ) ;
if ( flaecheJetzt ) { flaecheJetzt . style . width = "" ; flaecheJetzt . classList . remove ( "swipe-delete--scharf" ) ; }
const inhalt = swipeAktivWrap . querySelector ( ".swipe-content" ) ;
inhalt . style . transition = "" ; inhalt . style . transform = "" ;
swipeAktivWrap . classList . remove ( "wischt" ) ;
if ( swipeGeste === "x" ) {
if ( swipeDX <= - SWIPE _BREITE / 2 ) { schliesseAlleSwipes ( ) ; swipeAktivWrap . classList . add ( "swiped" ) ; }
else { swipeAktivWrap . classList . remove ( "swiped" ) ; }
if ( swipeDX <= swipeVollAb ( wrapJetzt ) ) {
// Ganz durchgezogen: jetzt handeln. Die Rueckfrage bleibt - geloescht
// werden hier Fahrten, Belege und Messwerte, die nicht wiederkommen.
schliesseAlleSwipes ( ) ;
const knopf = wrapJetzt . querySelector ( "[data-swipe-loeschen]" ) ;
if ( knopf ) {
const [ art , id ] = knopf . dataset . swipeLoeschen . split ( ":" ) ;
bestaetigen ( LOESCH _TEXT [ art ] , LOESCH _HINWEIS [ art ] , "Löschen" , ( ) => { eintragLoeschen ( art , id ) ; schliesseAlleSwipes ( ) ; } ) ;
}
}
else if ( swipeDX <= - SWIPE _BREITE / 2 ) { schliesseAlleSwipes ( ) ; wrapJetzt . classList . add ( "swiped" ) ; }
else { wrapJetzt . classList . remove ( "swiped" ) ; }
}
swipeAktivWrap = null ; swipeGeste = null ; swipeDX = 0 ;
// Der nachlaufende Klick derselben Geste feuert synchron noch in diesem
@@ -6170,7 +6362,7 @@ function ereignisseVerdrahten() {
smartdealPopupOffen = false ; profilSpeichern ( ) ; render ( ) ;
return ;
}
if ( e . target . closest ( "[data-bildgalerie]" ) ) { bildWeiter ( ) ; return ; }
if ( e . target . closest ( "[data-bildgalerie]" ) ) { if ( galerieGewischt ) { galerieGewischt = false ; return ; } bildWeiter ( ) ; return ; }
if ( e . target . closest ( "[data-sbneu]" ) ) {
CAR . service . buch . unshift ( { datum : dedat ( heute ( ) ) , km : CAR . odoBekannt ? CAR . odo : 0 , art : SERVICE _ARTEN [ 0 ] , werkstatt : CAR . service . autohaus . name , kosten : null , arbeiten : "" } ) ;
entwurfIndex = 0 ; go ( "sbuch" , "0" ) ; return ;
@@ -7104,6 +7296,7 @@ class AudiDashboardPanel extends HTMLElement {
ereignisseVerdrahten ( ) ;
randwischenVerdrahten ( ) ;
standortMenuVerdrahten ( ) ;
galerieWischenVerdrahten ( ) ;
themeIcon ( ) ;
setInterval ( standAlterTicken , 1000 ) ;