27640fee35
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>
770 lines
56 KiB
HTML
770 lines
56 KiB
HTML
<!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="{"screen":{"editor":"enum","options":["uebersicht","audi","fahrten","statistik","tanken","live","einstellungen","setup"],"default":"uebersicht","tsType":"string"},"theme":{"editor":"enum","options":["nacht","tag"],"default":"nacht","tsType":"string"},"offline":{"editor":"boolean","default":false,"tsType":"boolean"},"driving":{"editor":"boolean","default":false,"tsType":"boolean"},"empty":{"editor":"boolean","default":false,"tsType":"boolean"}}">
|
||
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>
|