Files
tobias 27640fee35 Entwurf aus Claude Design aufnehmen (Zwischenstand)
Export des Projekts "DataMetric360 App Design" nach design/ - 80 Dateien:
die beiden .dc.html-Entwurfsdateien, support.js, die verwendeten Icons,
Logos, Typenschilder und Schriften sowie das kompilierte Bündel des
Design-Systems, damit der Entwurf für sich allein darstellbar ist.

Bewusst ein Zwischenstand, nicht der Endstand: dieser Export zeigt noch
"Audi RS 6 Avant" statt des tatsächlichen RS 4 Avant competition und
enthält erst die Hauptbildschirme. Die 16 fehlenden Seiten (Fahrzeugstatus,
Batterieverlauf, Service, Reifen, Versicherung/Steuer und weitere, siehe
SPECIFICATION.md §3) sind im Design-Projekt bereits nachbeauftragt. Beides
ist in design/README.md festgehalten, damit der Stand nachvollziehbar
bleibt; der Ordner wird beim nächsten Export ersetzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 00:36:58 +02:00

770 lines
56 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="_ds/audi-dashboard-ui-27d66fd4-ed86-43b2-b8bf-61e281a5a1a5/_ds_bundle.css">
<link rel="stylesheet" href="_ds/audi-dashboard-ui-27d66fd4-ed86-43b2-b8bf-61e281a5a1a5/styles.css">
<script src="_ds/audi-dashboard-ui-27d66fd4-ed86-43b2-b8bf-61e281a5a1a5/_ds_bundle.js"></script>
<script src="assets/icons.js"></script>
<style>
@font-face { font-family: 'Audi Type'; src: url('assets/fonts/AudiTypeVF.ttf') format('truetype-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Audi Type Fallback'; src: url('assets/fonts/AudiType-Normal.ttf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Audi Type Wide'; src: url('assets/fonts/AudiType-WideLight.ttf') format('truetype'); font-weight: 300; font-style: normal; font-display: swap; }
@font-face { font-family: 'Audi Type Wide'; src: url('assets/fonts/AudiType-WideNormal.ttf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; }
:root { --font-stack: 'Audi Type', 'Audi Type Fallback', Helvetica, Arial, sans-serif; --font-wide: 'Audi Type Wide', 'Audi Type', Helvetica, Arial, sans-serif; }
html, body { margin: 0; height: 100%; }
a { color: var(--red); text-decoration: none; }
a:hover { color: var(--fg); }
.dm-in { font: inherit; font-weight: 300; color: var(--fg); background: none; border: 0; text-align: right; outline: none; min-width: 120px; }
.dm-in::placeholder { color: var(--fg3); }
.dm-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.dm-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 999px; }
</style>
</helmet>
<div ref="{{ rootRef }}" class="ads-root" data-theme="{{ theme }}" style="height:100%;display:flex;overflow:hidden;font-weight:400;background:var(--canvas)">
<sc-if value="{{ isSetup }}" hint-placeholder-val="{{ false }}">
<div class="dm-scroll" style="flex:1;overflow:auto;display:flex;align-items:center;justify-content:center;padding:32px">
<div style="width:100%;max-width:420px">
<img src="{{ ringsSrc }}" alt="Audi" style="height:22px;width:auto;display:block;margin-bottom:26px">
<span class="ads-eyebrow">DataMetric360</span>
<div style="font-size:28px;font-weight:300;letter-spacing:-.02em;margin:10px 0 12px;font-family:var(--font-wide)">Willkommen</div>
<p style="font-size:14px;font-weight:300;line-height:1.55;color:var(--fg2);margin:0 0 22px;text-wrap:pretty">Die App holt ihre Daten von deinem eigenen Server. Trag einmal die Adresse ein und füge deinen Zugangs-Token ein. Danach läuft alles von selbst.</p>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,180px">
<x-import component-from-global-scope="AudiDashUI.Feld" label="Server-Adresse" hint-size="100%,44px">
<input class="dm-in" placeholder="192.168.1.20:8123" value="{{ setupHost }}" onChange="{{ onSetupHost }}">
</x-import>
<x-import component-from-global-scope="AudiDashUI.Feld" label="Zugangs-Token" last="{{ true }}" hint-size="100%,44px">
<input class="dm-in" placeholder="einfügen" value="{{ setupToken }}" onChange="{{ onSetupToken }}">
</x-import>
</x-import>
<div style="display:flex;flex-direction:column;gap:10px;margin-top:4px">
<x-import component-from-global-scope="AudiDashUI.ActionButton" onClick="{{ goHome }}" hint-size="100%,44px">Verbinden</x-import>
<x-import component-from-global-scope="AudiDashUI.ActionButton" onClick="{{ goHome }}" hint-size="100%,44px">
<span style="display:inline-flex;align-items:center;gap:9px">{{ icons.code }}QR-Code scannen</span>
</x-import>
</div>
<p style="font-size:11.5px;font-weight:300;line-height:1.5;color:var(--fg3);margin:18px 0 0">Den QR-Code zeigt dir dein Server unter „DataMetric360 → Gerät verbinden“. Deine Daten bleiben auf deinem Server.</p>
</div>
</div>
</sc-if>
<sc-if value="{{ isApp }}" hint-placeholder-val="{{ true }}">
<sc-if value="{{ wide }}" hint-placeholder-val="{{ false }}">
<nav style="flex:0 0 240px;width:240px;height:100%;box-sizing:border-box;border-right:1px solid var(--line);padding:24px 14px 16px;display:flex;flex-direction:column;gap:2px">
<div style="padding:0 10px 22px">
<img src="{{ ringsSrc }}" alt="Audi" style="height:15px;width:auto;display:block;margin-bottom:14px">
<span class="ads-eyebrow">DataMetric360</span>
<div style="font-size:17px;font-weight:300;margin-top:8px;letter-spacing:-.01em;font-family:var(--font-wide)">{{ modell }}</div>
<div style="font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:3px">{{ kennzeichen }}</div>
</div>
<sc-for list="{{ navItems }}" as="n" hint-placeholder-count="5">
<button onClick="{{ n.go }}" style="display:flex;align-items:center;gap:12px;width:100%;padding:11px 12px;border:0;border-left:2px solid {{ n.mark }};border-radius:0 8px 8px 0;font:inherit;font-size:13.5px;font-weight:300;text-align:left;cursor:pointer;background:{{ n.bg }};color:{{ n.fg }}">{{ n.icon }}{{ n.label }}</button>
</sc-for>
<div style="flex:1"></div>
<button onClick="{{ goSettings }}" style="display:flex;align-items:center;gap:12px;width:100%;padding:11px 12px;border:0;border-left:2px solid {{ settingsMark }};border-radius:0 8px 8px 0;font:inherit;font-size:13.5px;font-weight:300;text-align:left;cursor:pointer;background:{{ settingsBg }};color:{{ settingsFg }}">{{ icons.settings }}Einstellungen</button>
</nav>
</sc-if>
<div style="flex:1;min-width:0;height:100%;display:flex;flex-direction:column">
<header style="flex:0 0 auto;display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line)">
<sc-if value="{{ showBack }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.IconButton" onClick="{{ back }}" aria-label="Zurück" hint-size="36px,36px">{{ icons.arrowLeft }}</x-import>
</sc-if>
<div style="flex:1;min-width:0">
<span class="ads-eyebrow">{{ eyebrow }}</span>
<div class="ads-title" style="font-weight:300;font-family:var(--font-wide)">{{ title }}</div>
</div>
<sc-if value="{{ offline }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Pill" hint-size="auto,28px">
<span style="display:inline-flex;align-items:center;gap:7px">{{ icons.caution }}Offline · Stand {{ offlineTime }}</span>
</x-import>
</sc-if>
<sc-if value="{{ hideGear }}" hint-placeholder-val="{{ true }}">
<x-import component-from-global-scope="AudiDashUI.IconButton" onClick="{{ goSettings }}" aria-label="Einstellungen" hint-size="36px,36px">{{ icons.settings }}</x-import>
</sc-if>
</header>
<sc-if value="{{ offline }}" hint-placeholder-val="{{ false }}">
<div style="flex:0 0 auto;padding:9px 16px;background:var(--shade);border-bottom:1px solid var(--line);font-size:11.5px;font-weight:300;color:var(--fg2)">Server nicht erreichbar. Du siehst die zuletzt geladenen Daten von {{ offlineDate }}, {{ offlineTime }} Uhr.</div>
</sc-if>
<div class="dm-scroll" style="flex:1;min-height:0;overflow:auto;padding:16px">
<sc-if value="{{ isHome }}" hint-placeholder-val="{{ true }}">
<sc-if value="{{ driving }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" variant="button" chevron="{{ true }}" onClick="{{ goLive }}" hint-size="100%,72px">
<span style="display:flex;align-items:center;gap:12px">
<span style="color:var(--red);display:flex">{{ icons.overTheAir }}</span>
<span style="display:flex;flex-direction:column;gap:3px;text-align:left">
<span style="font-size:14px;color:var(--fg)">Fahrzeug fährt gerade</span>
<span style="font-size:11.5px;font-weight:300;color:var(--fg3)">seit {{ live.dauer }} · {{ live.strecke }} km · {{ live.tempo }} km/h</span>
</span>
</span>
</x-import>
</sc-if>
<div style="display:grid;grid-template-columns:{{ cols }};gap:14px;align-items:start">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,280px">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="Fahrzeugfoto" label="Fahrzeugfoto" hint="rs6-avant-front.jpg" hint-size="100%,150px"></x-import>
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px">
<div style="min-width:0">
<div style="font-size:17px;font-weight:300;letter-spacing:-.01em;font-family:var(--font-wide)">{{ modell }}</div>
<div style="font-size:12px;font-weight:300;color:var(--fg3);margin-top:4px">{{ kennzeichen }}</div>
</div>
<img src="{{ badgeSrc }}" alt="RS 6" style="height:26px;width:auto;flex:0 0 auto">
</div>
<div style="margin-top:14px">
<x-import component-from-global-scope="AudiDashUI.StatusRow" status="{{ parkStatus }}" title="{{ parkTitle }}" subtitle="{{ parkSub }}" hint-size="100%,40px"></x-import>
</div>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,190px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.fuelRange }}<span class="ads-label" style="margin:0">Reichweite</span></span>
<div style="margin:10px 0 16px;font-family:var(--font-wide)">
<x-import component-from-global-scope="AudiDashUI.Fig" value="{{ reichweite }}" unit="km" size="{{ 54 }}" hint-size="auto,60px"></x-import>
</div>
<x-import component-from-global-scope="AudiDashUI.ProgressBar" percent="{{ tankPercent }}" hint-size="100%,4px"></x-import>
<div style="display:flex;justify-content:space-between;margin-top:10px;font-size:12px;font-weight:300;color:var(--fg2)">
<span>Tankfüllstand {{ tankPercentLabel }} %</span>
<span>{{ tankLiter }} l von 73 l</span>
</div>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,120px">
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ homeRows }}" hint-size="100%,110px"></x-import>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" variant="button" chevron="{{ true }}" onClick="{{ goFahrten }}" hint-size="100%,96px">
<span style="display:block;text-align:left">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.planRoute }}<span class="ads-label" style="margin:0">Letzte Fahrt</span></span>
<span style="display:block;font-size:15px;font-weight:300;margin-top:8px">{{ lastTrip.title }}</span>
<span style="display:block;font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:4px">{{ lastTrip.sub }} · {{ lastTrip.value }}</span>
</span>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" variant="button" chevron="{{ true }}" onClick="{{ goTanken }}" hint-size="100%,96px">
<span style="display:block;text-align:left">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.gasStation }}<span class="ads-label" style="margin:0">Letzte Tankung</span></span>
<span style="display:block;font-size:15px;font-weight:300;margin-top:8px">{{ lastFuel.title }}</span>
<span style="display:block;font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:4px">{{ lastFuel.sub }} · {{ lastFuel.value }}</span>
</span>
</x-import>
<sc-if value="{{ offline }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,64px">
<x-import component-from-global-scope="AudiDashUI.StatusRow" status="warn" title="1 Änderung wartet auf Übertragung" subtitle="Beleg vom 06.08.2026 · wird gesendet, sobald der Server wieder da ist" hint-size="100%,40px"></x-import>
</x-import>
</sc-if>
</div>
</div>
</sc-if>
<sc-if value="{{ isLive }}" hint-placeholder-val="{{ false }}">
<div style="display:grid;grid-template-columns:{{ splitCols }};gap:18px;align-items:start">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,300px">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="Live-Karte mit aktueller Position" label="Live-Karte · aktuelle Position" hint="karte-live.png" hint-size="100%,240px"></x-import>
<div style="display:flex;align-items:center;gap:10px;margin-top:14px;color:var(--red)">
{{ icons.compass }}
<span style="font-size:12px;font-weight:300;color:var(--fg2)">{{ live.ort }} · aktualisiert vor {{ live.alter }}</span>
</div>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,180px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.topSpeed }}<span class="ads-label" style="margin:0">Geschwindigkeit</span></span>
<div style="margin:10px 0 4px;font-family:var(--font-wide)">
<x-import component-from-global-scope="AudiDashUI.Fig" value="{{ live.tempo }}" unit="km/h" size="{{ 62 }}" hint-size="auto,70px"></x-import>
</div>
<div style="font-size:12px;font-weight:300;color:var(--fg3)">Zündung an · {{ live.drehzahl }} 1/min</div>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,90px">
<x-import component-from-global-scope="AudiDashUI.StatGrid" items="{{ liveStats }}" columns="{{ 3 }}" hint-size="100%,54px"></x-import>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,140px">
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ liveRows }}" hint-size="100%,130px"></x-import>
</x-import>
</div>
</div>
</sc-if>
<sc-if value="{{ isAudi }}" hint-placeholder-val="{{ false }}">
<div style="display:grid;grid-template-columns:{{ splitCols }};gap:18px;align-items:start">
<sc-if value="{{ showList }}" hint-placeholder-val="{{ true }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,260px">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="Fahrzeugfoto" label="Titelbild" hint="rs6-avant-front.jpg" hint-size="100%,150px"></x-import>
<div style="display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:14px">
<img src="{{ badgeSrc }}" alt="RS 6" style="height:26px;width:auto">
<img src="assets/audi-sport.svg" alt="Audi Sport" style="height:18px;width:auto">
</div>
<div style="display:flex;gap:10px;margin-top:14px;overflow:auto">
<sc-for list="{{ gallery }}" as="g" hint-placeholder-count="4">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="{{ g.alt }}" label="{{ g.label }}" size="mini" hint-size="78px,78px"></x-import>
</sc-for>
</div>
</x-import>
<div style="display:grid;grid-template-columns:{{ tileCols }};gap:12px">
<sc-for list="{{ audiTiles }}" as="a" hint-placeholder-count="5">
<x-import component-from-global-scope="AudiDashUI.Tile" variant="button" chevron="{{ true }}" onClick="{{ a.go }}" hint-size="100%,84px">
<span style="display:block;text-align:left">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ a.icon }}<span class="ads-label" style="margin:0">{{ a.label }}</span></span>
<span style="display:block;font-size:14px;font-weight:300;margin-top:8px">{{ a.value }}</span>
<span style="display:block;font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:3px">{{ a.sub }}</span>
</span>
</x-import>
</sc-for>
</div>
</div>
</sc-if>
<sc-if value="{{ showDetail }}" hint-placeholder-val="{{ false }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,240px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ audiDetail.icon }}<span class="ads-label" style="margin:0">{{ audiDetail.label }}</span></span>
<div style="font-size:19px;font-weight:300;margin:8px 0 14px;letter-spacing:-.01em">{{ audiDetail.title }}</div>
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ audiDetail.rows }}" hint-size="100%,180px"></x-import>
</x-import>
<sc-if value="{{ audiDetail.hasStatus }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,64px">
<x-import component-from-global-scope="AudiDashUI.StatusRow" status="{{ audiDetail.status }}" title="{{ audiDetail.statusTitle }}" subtitle="{{ audiDetail.statusSub }}" hint-size="100%,40px"></x-import>
</x-import>
</sc-if>
</div>
</sc-if>
</div>
</sc-if>
<sc-if value="{{ isFahrten }}" hint-placeholder-val="{{ false }}">
<sc-if value="{{ empty }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,200px">
<div style="padding:26px 8px;text-align:center">
<div style="display:flex;justify-content:center;color:var(--fg3);margin-bottom:12px">{{ icons.planRoute }}</div>
<div style="font-size:17px;font-weight:300">Noch keine Fahrten</div>
<p style="font-size:13px;font-weight:300;line-height:1.55;color:var(--fg2);margin:10px auto 18px;max-width:320px;text-wrap:pretty">Sobald du das erste Mal losfährst, zeichnet DataMetric360 die Fahrt automatisch auf und sortiert sie hier nach Monat ein.</p>
<div style="max-width:260px;margin:0 auto">
<x-import component-from-global-scope="AudiDashUI.ActionButton" onClick="{{ goSettings }}" hint-size="100%,44px">Verbindung prüfen</x-import>
</div>
</div>
</x-import>
</sc-if>
<sc-if value="{{ hasData }}" hint-placeholder-val="{{ true }}">
<div style="display:grid;grid-template-columns:{{ splitCols }};gap:18px;align-items:start">
<sc-if value="{{ showList }}" hint-placeholder-val="{{ true }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,320px">
<sc-for list="{{ tripYears }}" as="y" hint-placeholder-count="2">
<x-import component-from-global-scope="AudiDashUI.Accordion" title="{{ y.year }}" summary="{{ y.summary }}" default-open="{{ y.open }}" hint-size="100%,52px">
<sc-for list="{{ y.months }}" as="m" hint-placeholder-count="2">
<x-import component-from-global-scope="AudiDashUI.Accordion" title="{{ m.name }}" summary="{{ m.summary }}" level="{{ 2 }}" default-open="{{ m.open }}" hint-size="100%,48px">
<sc-for list="{{ m.trips }}" as="t" hint-placeholder-count="3">
<x-import component-from-global-scope="AudiDashUI.LeafRow" title="{{ t.leaf }}" sub-title="{{ t.sub }}" value="{{ t.value }}" sub-value="{{ t.subValue }}" onClick="{{ t.go }}" hint-size="100%,52px"></x-import>
</sc-for>
</x-import>
</sc-for>
</x-import>
</sc-for>
</x-import>
</div>
</sc-if>
<sc-if value="{{ showDetail }}" hint-placeholder-val="{{ false }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,300px">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="Karte der Fahrt" label="Streckenverlauf" hint="fahrt-{{ trip.id }}.png" hint-size="100%,170px"></x-import>
<div style="display:flex;align-items:center;gap:10px;margin:14px 0 12px">
<div style="flex:1;min-width:0">
<div style="font-size:17px;font-weight:300">{{ trip.title }}</div>
<div style="font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:3px">{{ trip.sub }}</div>
</div>
<x-import component-from-global-scope="AudiDashUI.Pill" variant="{{ trip.pillVariant }}" hint-size="auto,28px">
<span style="display:inline-flex;align-items:center;gap:7px">{{ trip.artIcon }}{{ trip.art }}</span>
</x-import>
</div>
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ trip.rows }}" hint-size="100%,110px"></x-import>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,90px">
<x-import component-from-global-scope="AudiDashUI.StatGrid" items="{{ trip.stats }}" columns="{{ 4 }}" hint-size="100%,54px"></x-import>
</x-import>
</div>
</sc-if>
</div>
</sc-if>
</sc-if>
<sc-if value="{{ isStatistik }}" hint-placeholder-val="{{ false }}">
<div style="margin-bottom:16px">
<x-import component-from-global-scope="AudiDashUI.Seg" options="{{ periods }}" value="{{ period }}" onChange="{{ setPeriod }}" hint-size="auto,34px"></x-import>
</div>
<sc-if value="{{ empty }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,180px">
<div style="padding:26px 8px;text-align:center">
<div style="display:flex;justify-content:center;color:var(--fg3);margin-bottom:12px">{{ icons.consumption }}</div>
<div style="font-size:17px;font-weight:300">Noch nichts auszuwerten</div>
<p style="font-size:13px;font-weight:300;line-height:1.55;color:var(--fg2);margin:10px auto 0;max-width:340px;text-wrap:pretty">Nach der ersten vollständigen Fahrt siehst du hier Strecke, Fahrzeit, Verbrauch und Kosten. Für den Vergleich Tag/Nacht brauchst du etwa eine Woche Daten.</p>
</div>
</x-import>
</sc-if>
<sc-if value="{{ hasData }}" hint-placeholder-val="{{ true }}">
<div style="display:grid;grid-template-columns:{{ cols }};gap:14px;align-items:start">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,150px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.mileage }}<span class="ads-label" style="margin:0">{{ stat.label }}</span></span>
<div style="margin-top:14px">
<x-import component-from-global-scope="AudiDashUI.StatGrid" items="{{ stat.items }}" columns="{{ 2 }}" hint-size="100%,110px"></x-import>
</div>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,110px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.time }}<span class="ads-label" style="margin:0">Tag und Nacht</span></span>
<div style="margin-top:14px">
<x-import component-from-global-scope="AudiDashUI.ProgressBar" segments="{{ stat.tagNacht }}" hint-size="100%,40px"></x-import>
</div>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,110px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.commute }}<span class="ads-label" style="margin:0">Privat und Arbeitsweg</span></span>
<div style="margin-top:14px">
<x-import component-from-global-scope="AudiDashUI.ProgressBar" segments="{{ stat.zweck }}" hint-size="100%,40px"></x-import>
</div>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,180px">
<span class="ads-label">Im Detail</span>
<div style="margin-top:10px">
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ stat.rows }}" hint-size="100%,150px"></x-import>
</div>
</x-import>
</div>
</div>
</sc-if>
</sc-if>
<sc-if value="{{ isTanken }}" hint-placeholder-val="{{ false }}">
<sc-if value="{{ empty }}" hint-placeholder-val="{{ false }}">
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,200px">
<div style="padding:26px 8px;text-align:center">
<div style="display:flex;justify-content:center;color:var(--fg3);margin-bottom:12px">{{ icons.gasStation }}</div>
<div style="font-size:17px;font-weight:300">Noch keine Tankvorgänge</div>
<p style="font-size:13px;font-weight:300;line-height:1.55;color:var(--fg2);margin:10px auto 18px;max-width:320px;text-wrap:pretty">Trag deine erste Tankung ein, dann rechnet die App Verbrauch und Kosten automatisch mit. Den Beleg kannst du direkt fotografieren.</p>
<div style="max-width:260px;margin:0 auto">
<x-import component-from-global-scope="AudiDashUI.ActionButton" hint-size="100%,44px">Tankung eintragen</x-import>
</div>
</div>
</x-import>
</sc-if>
<sc-if value="{{ hasData }}" hint-placeholder-val="{{ true }}">
<div style="display:grid;grid-template-columns:{{ splitCols }};gap:18px;align-items:start">
<sc-if value="{{ showList }}" hint-placeholder-val="{{ true }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,300px">
<sc-for list="{{ fuelYears }}" as="y" hint-placeholder-count="2">
<x-import component-from-global-scope="AudiDashUI.Accordion" title="{{ y.year }}" summary="{{ y.summary }}" default-open="{{ y.open }}" hint-size="100%,52px">
<sc-for list="{{ y.months }}" as="m" hint-placeholder-count="2">
<x-import component-from-global-scope="AudiDashUI.Accordion" title="{{ m.name }}" summary="{{ m.summary }}" level="{{ 2 }}" default-open="{{ m.open }}" hint-size="100%,48px">
<sc-for list="{{ m.items }}" as="f" hint-placeholder-count="2">
<x-import component-from-global-scope="AudiDashUI.LeafRow" title="{{ f.leaf }}" sub-title="{{ f.sub }}" value="{{ f.value }}" sub-value="{{ f.subValue }}" onClick="{{ f.go }}" hint-size="100%,52px"></x-import>
</sc-for>
</x-import>
</sc-for>
</x-import>
</sc-for>
</x-import>
<div style="max-width:280px">
<x-import component-from-global-scope="AudiDashUI.ActionButton" hint-size="100%,44px">
<span style="display:inline-flex;align-items:center;gap:9px">{{ icons.add }}Tankung eintragen</span>
</x-import>
</div>
</div>
</sc-if>
<sc-if value="{{ showDetail }}" hint-placeholder-val="{{ false }}">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,240px">
<div style="display:flex;align-items:center;gap:10px">
<img src="{{ fuel.logo }}" alt="" style="height:20px;width:20px">
<span class="ads-label" style="margin:0">{{ fuel.station }}</span>
</div>
<div style="font-size:19px;font-weight:300;margin:10px 0 14px">{{ fuel.title }}</div>
<x-import component-from-global-scope="AudiDashUI.RowList" items="{{ fuel.rows }}" hint-size="100%,150px"></x-import>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,240px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.camera }}<span class="ads-label" style="margin:0">Beleg</span></span>
<div style="margin-top:12px">
<x-import component-from-global-scope="AudiDashUI.ImagePlaceholder" alt="Foto des Tankbelegs" label="Beleg fotografieren" hint="beleg-{{ fuel.id }}.jpg" hint-size="100%,150px"></x-import>
</div>
<sc-if value="{{ offline }}" hint-placeholder-val="{{ false }}">
<div style="margin-top:12px">
<x-import component-from-global-scope="AudiDashUI.StatusRow" status="warn" title="Beleg wartet auf Übertragung" subtitle="Liegt auf dem Gerät · wird gesendet, sobald der Server erreichbar ist" hint-size="100%,40px"></x-import>
</div>
</sc-if>
</x-import>
</div>
</sc-if>
</div>
</sc-if>
</sc-if>
<sc-if value="{{ isSettings }}" hint-placeholder-val="{{ false }}">
<div style="display:grid;grid-template-columns:{{ cols2 }};gap:14px;align-items:start;max-width:900px">
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,190px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.carSettings }}<span class="ads-label" style="margin:0">Fahrzeugdaten</span></span>
<div style="margin-top:8px">
<x-import component-from-global-scope="AudiDashUI.Feld" label="Modell" hint-size="100%,44px">
<input class="dm-in" value="{{ modell }}" onChange="{{ onModell }}">
</x-import>
<x-import component-from-global-scope="AudiDashUI.Feld" label="Kennzeichen" hint-size="100%,44px">
<input class="dm-in" value="{{ kennzeichen }}" onChange="{{ onKennzeichen }}">
</x-import>
<x-import component-from-global-scope="AudiDashUI.Feld" label="FIN" last="{{ true }}" hint-size="100%,44px">
<input class="dm-in" value="{{ fin }}" onChange="{{ onFin }}">
</x-import>
</div>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,150px">
<span class="ads-label">Darstellung</span>
<div style="margin-top:8px">
<x-import component-from-global-scope="AudiDashUI.Feld" label="Theme" hint-size="100%,52px">
<x-import component-from-global-scope="AudiDashUI.Seg" options="{{ themes }}" value="{{ theme }}" onChange="{{ setTheme }}" hint-size="auto,34px"></x-import>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Feld" label="Live-Daten anzeigen" last="{{ true }}" hint-size="100%,44px">
<x-import component-from-global-scope="AudiDashUI.Switch" checked="{{ driving }}" onChange="{{ setDriving }}" aria-label="Live-Daten anzeigen" hint-size="44px,26px"></x-import>
</x-import>
</div>
</x-import>
</div>
<div>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,220px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.photo }}<span class="ads-label" style="margin:0">Fotos</span></span>
<div style="margin-top:10px">
<sc-for list="{{ photos }}" as="p" hint-placeholder-count="3">
<x-import component-from-global-scope="AudiDashUI.SwipeRow" onDelete="{{ p.remove }}" delete-label="Löschen" hint-size="100%,52px">
<x-import component-from-global-scope="AudiDashUI.LeafRow" title="{{ p.name }}" sub-title="{{ p.meta }}" hint-size="100%,50px"></x-import>
</x-import>
</sc-for>
</div>
<div style="margin-top:12px">
<x-import component-from-global-scope="AudiDashUI.ActionButton" hint-size="100%,44px">
<span style="display:inline-flex;align-items:center;gap:9px">{{ icons.add }}Foto hinzufügen</span>
</x-import>
</div>
</x-import>
<x-import component-from-global-scope="AudiDashUI.Tile" hint-size="100%,190px">
<span style="display:flex;align-items:center;gap:8px;color:var(--fg3)">{{ icons.download }}<span class="ads-label" style="margin:0">Daten</span></span>
<div style="display:flex;flex-direction:column;gap:10px;margin-top:12px">
<x-import component-from-global-scope="AudiDashUI.ActionButton" hint-size="100%,44px">Daten exportieren (CSV)</x-import>
<x-import component-from-global-scope="AudiDashUI.ActionButton" hint-size="100%,44px">Backup erstellen</x-import>
<x-import component-from-global-scope="AudiDashUI.ActionButton" variant="destructive" onClick="{{ goSetup }}" hint-size="100%,44px">Verbindung trennen</x-import>
</div>
<div style="font-size:11.5px;font-weight:300;color:var(--fg3);margin-top:12px">Letztes Backup: 03.08.2026, 02:00 Uhr</div>
</x-import>
</div>
</div>
</sc-if>
</div>
<sc-if value="{{ narrow }}" hint-placeholder-val="{{ true }}">
<div style="flex:0 0 auto">
<x-import component-from-global-scope="AudiDashUI.TabBar" items="{{ tabs }}" active-key="{{ tab }}" onChange="{{ setTab }}" hint-size="100%,60px"></x-import>
</div>
</sc-if>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;screen&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;uebersicht&quot;,&quot;audi&quot;,&quot;fahrten&quot;,&quot;statistik&quot;,&quot;tanken&quot;,&quot;live&quot;,&quot;einstellungen&quot;,&quot;setup&quot;],&quot;default&quot;:&quot;uebersicht&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;theme&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;nacht&quot;,&quot;tag&quot;],&quot;default&quot;:&quot;nacht&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;offline&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;},&quot;driving&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;},&quot;empty&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;}}">
const RAW = (typeof window !== 'undefined' && window.DM360_ICONS) || {};
const ico = (name, size) => React.createElement('svg', {
viewBox: '0 0 24 24', width: size || 20, height: size || 20,
style: { flex: '0 0 auto', display: 'block' },
dangerouslySetInnerHTML: { __html: RAW[name] || '' }
});
const NAV = [
{ key: 'uebersicht', label: 'Übersicht', icon: 'dashboard' },
{ key: 'audi', label: 'Mein Audi', icon: 'car' },
{ key: 'fahrten', label: 'Fahrten', icon: 'plan-route' },
{ key: 'statistik', label: 'Statistik', icon: 'consumption' },
{ key: 'tanken', label: 'Tanken', icon: 'gas-station' }
];
const TRIPS = {
t1: { id: 't1', title: 'München → Starnberg', sub: 'Sa, 08.08.2026 · 09:12', value: '38,4 km', subValue: '41 min', art: 'Privat', start: 'Leopoldstraße 118, München', ziel: 'Seepromenade 4, Starnberg', avg: '56', max: '118', dauer: '41 min', km: '38,4' },
t2: { id: 't2', title: 'Büro → Zuhause', sub: 'Fr, 07.08.2026 · 18:04', value: '14,2 km', subValue: '28 min', art: 'Arbeitsweg', start: 'Balanstraße 73, München', ziel: 'Leopoldstraße 118, München', avg: '31', max: '74', dauer: '28 min', km: '14,2' },
t3: { id: 't3', title: 'Zuhause → Büro', sub: 'Fr, 07.08.2026 · 07:41', value: '13,9 km', subValue: '24 min', art: 'Arbeitsweg', start: 'Leopoldstraße 118, München', ziel: 'Balanstraße 73, München', avg: '35', max: '81', dauer: '24 min', km: '13,9' },
t4: { id: 't4', title: 'München → Salzburg', sub: 'Sa, 19.07.2026 · 08:30', value: '148,6 km', subValue: '1 h 34 min', art: 'Privat', start: 'Leopoldstraße 118, München', ziel: 'Getreidegasse 9, Salzburg', avg: '95', max: '181', dauer: '1 h 34 min', km: '148,6' },
t5: { id: 't5', title: 'Baumarkt und zurück', sub: 'So, 06.07.2026 · 10:15', value: '11,8 km', subValue: '22 min', art: 'Privat', start: 'Leopoldstraße 118, München', ziel: 'Ingolstädter Str. 12, München', avg: '32', max: '68', dauer: '22 min', km: '11,8' },
t6: { id: 't6', title: 'Weihnachtsbesuch Nürnberg', sub: 'Di, 24.12.2025 · 13:20', value: '171,2 km', subValue: '1 h 48 min', art: 'Privat', start: 'Leopoldstraße 118, München', ziel: 'Hauptmarkt 18, Nürnberg', avg: '94', max: '166', dauer: '1 h 48 min', km: '171,2' }
};
const SHELL = 'assets/shell.svg';
const FUEL = {
f1: { id: 'f1', title: '06.08.2026', station: 'Shell, Schleißheimer Str.', logo: SHELL, liter: '54,80', preis: '1,979', gesamt: '108,45', km: '84.061', verbrauch: '11,9' },
f2: { id: 'f2', title: '01.08.2026', station: 'Shell, Landsberger Str.', logo: SHELL, liter: '49,20', preis: '2,019', gesamt: '99,33', km: '83.640', verbrauch: '12,4' },
f3: { id: 'f3', title: '18.07.2026', station: 'Shell, A8 Rasthof Irschenberg', logo: SHELL, liter: '61,40', preis: '2,099', gesamt: '128,88', km: '83.190', verbrauch: '11,2' },
f4: { id: 'f4', title: '04.07.2026', station: 'Shell, Schleißheimer Str.', logo: SHELL, liter: '52,10', preis: '1,949', gesamt: '101,54', km: '82.640', verbrauch: '12,8' },
f5: { id: 'f5', title: '22.12.2025', station: 'Shell, A9 Rasthof Fürholzen', logo: SHELL, liter: '58,90', preis: '1,899', gesamt: '111,85', km: '76.320', verbrauch: '11,6' }
};
const STATS = {
woche: {
label: 'Diese Woche', items: [ { value: '212,4', unit: 'km', caption: 'Strecke' }, { value: '3:41', unit: 'h', caption: 'Fahrzeit' }, { value: '11,9', unit: 'l', caption: 'Ø Verbrauch' }, { value: '50,02', unit: '€', caption: 'Kosten' } ],
tagNacht: [ { value: 78, color: 'var(--fg)', label: 'Tag 78 %' }, { value: 22, color: 'var(--fg3)', label: 'Nacht 22 %' } ],
zweck: [ { value: 44, color: 'var(--red)', label: 'Arbeitsweg 44 %' }, { value: 56, color: 'var(--fg2)', label: 'Privat 56 %' } ],
rows: [ { key: 'a', label: 'Fahrten', value: '9' }, { key: 'b', label: 'Längste Fahrt', value: '38,4 km', subValue: 'Sa, 08.08.' }, { key: 'c', label: 'Ø Geschwindigkeit', value: '46 km/h' }, { key: 'd', label: 'Kosten je 100 km', value: '23,55 €' } ]
},
monat: {
label: 'Dieser Monat', items: [ { value: '1.184', unit: 'km', caption: 'Strecke' }, { value: '19:22', unit: 'h', caption: 'Fahrzeit' }, { value: '12,1', unit: 'l', caption: 'Ø Verbrauch' }, { value: '283,64', unit: '€', caption: 'Kosten' } ],
tagNacht: [ { value: 71, color: 'var(--fg)', label: 'Tag 71 %' }, { value: 29, color: 'var(--fg3)', label: 'Nacht 29 %' } ],
zweck: [ { value: 51, color: 'var(--red)', label: 'Arbeitsweg 51 %' }, { value: 49, color: 'var(--fg2)', label: 'Privat 49 %' } ],
rows: [ { key: 'a', label: 'Fahrten', value: '41' }, { key: 'b', label: 'Längste Fahrt', value: '148,6 km', subValue: 'Sa, 19.07.' }, { key: 'c', label: 'Ø Geschwindigkeit', value: '61 km/h' }, { key: 'd', label: 'Kosten je 100 km', value: '23,96 €' } ]
},
jahr: {
label: 'Dieses Jahr', items: [ { value: '14.706', unit: 'km', caption: 'Strecke' }, { value: '241:08', unit: 'h', caption: 'Fahrzeit' }, { value: '11,8', unit: 'l', caption: 'Ø Verbrauch' }, { value: '3.472,10', unit: '€', caption: 'Kosten' } ],
tagNacht: [ { value: 74, color: 'var(--fg)', label: 'Tag 74 %' }, { value: 26, color: 'var(--fg3)', label: 'Nacht 26 %' } ],
zweck: [ { value: 58, color: 'var(--red)', label: 'Arbeitsweg 58 %' }, { value: 42, color: 'var(--fg2)', label: 'Privat 42 %' } ],
rows: [ { key: 'a', label: 'Fahrten', value: '486' }, { key: 'b', label: 'Längste Fahrt', value: '412,8 km', subValue: 'Fr, 27.03.' }, { key: 'c', label: 'Ø Geschwindigkeit', value: '61 km/h' }, { key: 'd', label: 'Kosten je 100 km', value: '23,61 €' } ]
}
};
const AUDI = {
technik: { icon: 'car-settings', label: 'Fahrzeugdaten', title: 'Technik', sub: 'V8 biturbo · 600 PS', rows: [ { key: 'a', label: 'Modell', value: 'RS 6 Avant quattro' }, { key: 'b', label: 'Erstzulassung', value: '14.03.2022' }, { key: 'c', label: 'Leistung', value: '441 kW', subValue: '600 PS' }, { key: 'd', label: 'Hubraum', value: '3.996 cm³', subValue: 'V8 biturbo' }, { key: 'e', label: 'Getriebe', value: 'tiptronic, 8-Gang' }, { key: 'f', label: 'Leergewicht', value: '2.150 kg' }, { key: 'g', label: 'Höchstgeschwindigkeit', value: '250 km/h', subValue: 'abgeregelt' }, { key: 'h', label: 'FIN', value: 'WUAZZZ4G7NN012345' } ] },
service: { icon: 'service-package', label: 'Service', title: 'Nächster Termin', sub: 'in 3 Monaten fällig', rows: [ { key: 'a', label: 'Fällig am', value: '12.11.2026', subValue: 'oder in 4.700 km' }, { key: 'b', label: 'Art', value: 'Inspektion groß' }, { key: 'c', label: 'Letzter Service', value: '08.11.2025', subValue: 'bei 68.940 km' }, { key: 'd', label: 'Werkstatt', value: 'Audi Zentrum München' }, { key: 'e', label: 'Ölwechsel', value: '08.11.2025' } ], hasStatus: true, status: 'ok', statusTitle: 'Alles im grünen Bereich', statusSub: 'Noch 3 Monate bis zur nächsten Inspektion' },
reifen: { icon: 'tire', label: 'Reifen', title: 'Sommerreifen montiert', sub: 'Profil hinten 3,8 mm', rows: [ { key: 'a', label: 'Solldruck vorne', value: '2,6 bar', subValue: 'kalt gemessen' }, { key: 'b', label: 'Solldruck hinten', value: '2,5 bar', subValue: 'kalt gemessen' }, { key: 'c', label: 'Größe', value: '285/30 R22' }, { key: 'd', label: 'Profiltiefe vorne', value: '4,9 mm' }, { key: 'e', label: 'Profiltiefe hinten', value: '3,8 mm' }, { key: 'f', label: 'Letzter Wechsel', value: '11.04.2026' } ], hasStatus: true, status: 'warn', statusTitle: 'Profil hinten wird knapp', statusSub: '3,8 mm · Wechsel vor dem Winter einplanen' },
versicherung: { icon: 'car-insurance', label: 'Versicherung', title: 'Vollkasko', sub: 'HUK-Coburg · 1.842 €/Jahr', rows: [ { key: 'a', label: 'Gesellschaft', value: 'HUK-Coburg' }, { key: 'b', label: 'Vertragsnummer', value: 'VK-88 421 907' }, { key: 'c', label: 'Beitrag', value: '1.842,00 €', subValue: 'jährlich' }, { key: 'd', label: 'Selbstbeteiligung', value: '300 €', subValue: 'Teilkasko 150 €' }, { key: 'e', label: 'Verlängert sich am', value: '01.01.2027' } ] },
steuer: { icon: 'document-taxation', label: 'Steuer', title: 'Kfz-Steuer', sub: '574 € · Abbuchung 03/2027', rows: [ { key: 'a', label: 'Jahresbetrag', value: '574,00 €' }, { key: 'b', label: 'Abbuchung', value: '14.03.2027' }, { key: 'c', label: 'Emissionsklasse', value: 'Euro 6d' }, { key: 'd', label: 'CO₂', value: '275 g/km' }, { key: 'e', label: 'Hauptuntersuchung', value: '03/2027' } ] }
};
class Component extends DCLogic {
rootRef = React.createRef();
state = {
w: 0, screen: null, theme: null,
trip: 't1', tripOpen: false,
fuel: 'f1', fuelOpen: false,
audi: 'technik', audiOpen: false,
period: 'monat',
modell: 'Audi RS 6 Avant', kennzeichen: 'M-DM 360', fin: 'WUAZZZ4G7NN012345',
setupHost: '', setupToken: '', driving: null,
photos: [
{ id: 'p1', name: 'rs6-avant-front.jpg', meta: 'Titelbild · 2,4 MB' },
{ id: 'p2', name: 'rs6-avant-heck.jpg', meta: 'Galerie · 1,9 MB' },
{ id: 'p3', name: 'innenraum.jpg', meta: 'Galerie · 2,1 MB' }
],
tempo: 78, liveMin: 14, liveKm: 12.4
};
componentDidMount() {
const attach = () => {
const el = this.rootRef.current;
if (!el) { requestAnimationFrame(attach); return; }
this._ro = new ResizeObserver(() => this.setState({ w: el.clientWidth }));
this._ro.observe(el);
this.setState({ w: el.clientWidth });
};
attach();
this._iv = setInterval(() => {
if (!this.isDriving()) return;
const t = [78, 84, 92, 88, 71, 63, 79][Math.floor(Math.random() * 7)];
this.setState(s => ({ tempo: t, liveKm: Math.round((s.liveKm + t / 1800) * 10) / 10 }));
}, 2500);
}
componentWillUnmount() { if (this._ro) this._ro.disconnect(); clearInterval(this._iv); }
componentDidUpdate(prev) {
if (prev.screen !== this.props.screen) this.setState({ screen: this.props.screen, tripOpen: false, fuelOpen: false, audiOpen: false });
if (prev.theme !== this.props.theme) this.setState({ theme: this.props.theme });
if (prev.driving !== this.props.driving) this.setState({ driving: this.props.driving });
}
screen() { return this.state.screen || this.props.screen || 'uebersicht'; }
isDriving() { return this.state.driving == null ? !!this.props.driving : this.state.driving; }
go = (k) => this.setState({ screen: k, tripOpen: false, fuelOpen: false, audiOpen: false });
renderVals() {
const wide = this.state.w >= 1000;
const screen = this.screen();
const empty = !!this.props.empty;
const offline = !!this.props.offline;
const driving = this.isDriving();
const theme = this.state.theme || this.props.theme || 'nacht';
const detailOpen = { fahrten: this.state.tripOpen, tanken: this.state.fuelOpen, audi: this.state.audiOpen }[screen];
const showDetail = (wide || detailOpen) && !empty;
const trip = TRIPS[this.state.trip];
const fuel = FUEL[this.state.fuel];
const stat = STATS[this.state.period];
const titles = {
uebersicht: ['Fahrzeug', 'Übersicht'], audi: ['Fahrzeug', 'Mein Audi'], fahrten: ['Aufzeichnung', 'Fahrten'],
statistik: ['Auswertung', 'Statistik'], tanken: ['Verbrauch', 'Tanken'], einstellungen: ['App', 'Einstellungen'],
live: ['Live', 'Fahrt läuft gerade'], setup: ['Start', 'Ersteinrichtung']
}[screen];
const artIcon = (t) => ico(t.art === 'Arbeitsweg' ? 'commute' : 'poi-home', 14);
const leaf = (t) => React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 9 } }, artIcon(t), t.title);
const mk = (list) => list.map(k => ({ ...TRIPS[k], leaf: leaf(TRIPS[k]), go: () => this.setState({ trip: k, tripOpen: true }) }));
const mkf = (list) => list.map(k => ({
...FUEL[k], value: FUEL[k].liter + ' l', subValue: FUEL[k].gesamt + ' €', sub: FUEL[k].station,
leaf: React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 9 } },
React.createElement('img', { src: FUEL[k].logo, alt: '', width: 16, height: 16 }), FUEL[k].title),
go: () => this.setState({ fuel: k, fuelOpen: true })
}));
return {
rootRef: this.rootRef,
wide, narrow: !wide && screen !== 'setup', theme,
ringsSrc: theme === 'tag' ? 'assets/audi-rings-black.svg' : 'assets/audi-rings-white.svg',
badgeSrc: theme === 'tag' ? 'assets/badge-rs6-positive.svg' : 'assets/badge-rs6-negative.svg',
icons: {
settings: ico('settings'), arrowLeft: ico('arrow-left'), caution: ico('caution', 14),
overTheAir: ico('over-the-air-car', 22), fuelRange: ico('fuel-range', 15), planRoute: ico('plan-route', 15),
gasStation: ico('gas-station', 15), topSpeed: ico('top-speed', 15), compass: ico('compass', 16),
mileage: ico('mileage', 15), time: ico('time', 15), commute: ico('commute', 15),
consumption: ico('consumption', 26), photo: ico('photo', 15), camera: ico('camera', 15),
download: ico('download', 15), carSettings: ico('car-settings', 15), add: ico('add', 16), code: ico('code', 16)
},
isSetup: screen === 'setup', isApp: screen !== 'setup',
isHome: screen === 'uebersicht', isLive: screen === 'live', isAudi: screen === 'audi',
isFahrten: screen === 'fahrten', isStatistik: screen === 'statistik', isTanken: screen === 'tanken',
isSettings: screen === 'einstellungen',
empty, hasData: !empty, offline, driving,
offlineTime: '08:42', offlineDate: 'heute',
eyebrow: titles[0], title: titles[1],
cols: wide ? 'repeat(3, minmax(0,1fr))' : '1fr',
cols2: wide ? 'repeat(2, minmax(0,1fr))' : '1fr',
tileCols: wide ? 'repeat(2, minmax(0,1fr))' : '1fr',
splitCols: wide ? 'minmax(340px, 420px) 1fr' : '1fr',
showList: wide || !detailOpen, showDetail,
showBack: !wide && (detailOpen || screen === 'einstellungen' || screen === 'live'),
hideGear: screen !== 'einstellungen',
navItems: NAV.map(n => ({
label: n.label, icon: ico(n.icon), go: () => this.go(n.key),
bg: screen === n.key ? 'var(--tile)' : 'transparent',
fg: screen === n.key ? 'var(--fg)' : 'var(--fg2)',
mark: screen === n.key ? 'var(--red)' : 'transparent'
})),
settingsBg: screen === 'einstellungen' ? 'var(--tile)' : 'transparent',
settingsFg: screen === 'einstellungen' ? 'var(--fg)' : 'var(--fg2)',
settingsMark: screen === 'einstellungen' ? 'var(--red)' : 'transparent',
tabs: NAV.map(n => ({ key: n.key, label: n.label, icon: ico(n.icon, 22) })),
tab: NAV.some(n => n.key === screen) ? screen : 'uebersicht',
setTab: (k) => this.go(k),
goHome: () => this.go('uebersicht'), goSettings: () => this.go('einstellungen'),
goLive: () => this.go('live'), goFahrten: () => this.go('fahrten'),
goTanken: () => this.go('tanken'), goSetup: () => this.go('setup'),
back: () => this.setState({ tripOpen: false, fuelOpen: false, audiOpen: false, screen: (screen === 'einstellungen' || screen === 'live') ? 'uebersicht' : screen }),
modell: this.state.modell, kennzeichen: this.state.kennzeichen, fin: this.state.fin,
onModell: e => this.setState({ modell: e.target.value }),
onKennzeichen: e => this.setState({ kennzeichen: e.target.value }),
onFin: e => this.setState({ fin: e.target.value }),
setupHost: this.state.setupHost, setupToken: this.state.setupToken,
onSetupHost: e => this.setState({ setupHost: e.target.value }),
onSetupToken: e => this.setState({ setupToken: e.target.value }),
themes: [{ value: 'nacht', label: 'Nacht' }, { value: 'tag', label: 'Tag' }],
setTheme: (t) => this.setState({ theme: t }),
setDriving: (v) => this.setState({ driving: v }),
reichweite: empty ? '' : '355', tankPercent: empty ? 0 : 58,
tankPercentLabel: empty ? '' : '58', tankLiter: empty ? '' : '42,3',
parkStatus: driving ? 'warn' : 'ok',
parkTitle: driving ? 'Fahrzeug in Bewegung' : 'Sicher abgestellt',
parkSub: driving ? 'Zündung an · Position wird laufend gesendet' : 'Zuhause, Leopoldstraße · seit gestern 21:40',
homeRows: [
{ key: 'km', label: 'Kilometerstand', value: empty ? '' : '84.213 km' },
{ key: 'sv', label: 'Nächster Service', value: '12.11.2026', subValue: 'oder in 4.700 km' },
{ key: 'vb', label: 'Ø Verbrauch', value: empty ? '' : '11,9 l/100km', subValue: 'letzte 5 Tankungen' }
],
lastTrip: empty ? { title: 'Noch keine Fahrt', sub: 'Wartet auf die erste Aufzeichnung', value: '' } : { title: TRIPS.t1.title, sub: TRIPS.t1.sub, value: TRIPS.t1.value },
lastFuel: empty ? { title: 'Noch keine Tankung', sub: 'Trag deine erste Tankung ein', value: '' } : { title: FUEL.f1.station, sub: FUEL.f1.title, value: FUEL.f1.gesamt + ' €' },
live: { tempo: String(this.state.tempo), dauer: this.state.liveMin + ' min', strecke: String(this.state.liveKm).replace('.', ','), drehzahl: '2.100', ort: 'A96 bei Germering, Fahrtrichtung Lindau', alter: '3 s' },
liveStats: [
{ value: String(this.state.liveKm).replace('.', ','), unit: 'km', caption: 'Strecke' },
{ value: this.state.liveMin, unit: 'min', caption: 'Dauer' },
{ value: '53', unit: 'km/h', caption: 'Ø Tempo' }
],
liveRows: [
{ key: 'a', label: 'Start', value: 'Leopoldstraße 118', subValue: 'München · 15:04' },
{ key: 'b', label: 'Tankfüllstand', value: '54 %', subValue: 'ca. 330 km Reichweite' },
{ key: 'c', label: 'Höchstes Tempo', value: '112 km/h' }
],
gallery: [
{ alt: 'Front', label: 'Front' }, { alt: 'Heck', label: 'Heck' },
{ alt: 'Innenraum', label: 'Innen' }, { alt: 'Felge', label: 'Felge' }
],
audiTiles: Object.keys(AUDI).map(k => ({
label: AUDI[k].label, value: AUDI[k].title, sub: AUDI[k].sub, icon: ico(AUDI[k].icon, 15),
go: () => this.setState({ audi: k, audiOpen: true })
})),
audiDetail: { ...AUDI[this.state.audi], icon: ico(AUDI[this.state.audi].icon, 15) },
tripYears: [
{ year: '2026', summary: '5 Fahrten · 227 km', open: true, months: [
{ name: 'August', summary: '3 Fahrten · 66,5 km', open: true, trips: mk(['t1', 't2', 't3']) },
{ name: 'Juli', summary: '2 Fahrten · 160,4 km', open: false, trips: mk(['t4', 't5']) }
] },
{ year: '2025', summary: '1 Fahrt · 171 km', open: false, months: [
{ name: 'Dezember', summary: '1 Fahrt · 171,2 km', open: true, trips: mk(['t6']) }
] }
],
trip: {
...trip, pillVariant: trip.art === 'Arbeitsweg' ? 'work' : 'default', artIcon: artIcon(trip),
rows: [
{ key: 'a', label: 'Start', value: trip.start },
{ key: 'b', label: 'Ziel', value: trip.ziel },
{ key: 'c', label: 'Fahrtart', value: trip.art }
],
stats: [
{ value: trip.km, unit: 'km', caption: 'Strecke' },
{ value: trip.dauer, caption: 'Dauer' },
{ value: trip.avg, unit: 'km/h', caption: 'Ø Tempo' },
{ value: trip.max, unit: 'km/h', caption: 'Höchstes' }
]
},
fuelYears: [
{ year: '2026', summary: '4 Tankungen · 438,20 €', open: true, months: [
{ name: 'August', summary: '2 Tankungen · 207,78 €', open: true, items: mkf(['f1', 'f2']) },
{ name: 'Juli', summary: '2 Tankungen · 230,42 €', open: false, items: mkf(['f3', 'f4']) }
] },
{ year: '2025', summary: '1 Tankung · 111,85 €', open: false, months: [
{ name: 'Dezember', summary: '1 Tankung · 111,85 €', open: true, items: mkf(['f5']) }
] }
],
fuel: {
...fuel,
rows: [
{ key: 'a', label: 'Menge', value: fuel.liter + ' l', subValue: 'Super Plus 98' },
{ key: 'b', label: 'Preis je Liter', value: fuel.preis + ' €' },
{ key: 'c', label: 'Gesamtbetrag', value: fuel.gesamt + ' €' },
{ key: 'd', label: 'Kilometerstand', value: fuel.km + ' km' },
{ key: 'e', label: 'Verbrauch seit letzter Tankung', value: fuel.verbrauch + ' l/100km' }
]
},
periods: [{ value: 'woche', label: 'Woche' }, { value: 'monat', label: 'Monat' }, { value: 'jahr', label: 'Jahr' }],
period: this.state.period, setPeriod: (p) => this.setState({ period: p }), stat,
photos: this.state.photos.map(p => ({ ...p, remove: () => this.setState(s => ({ photos: s.photos.filter(x => x.id !== p.id) })) }))
};
}
}
</script>
</body>
</html>