@bytebrand/fe-ui-core-autobahn 1.0.133 → 1.0.135

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cards/CarCard.tsx CHANGED
@@ -51,14 +51,8 @@ export interface CarCardProps {
51
51
  isFavorite: boolean;
52
52
  /** toggle the favorite; the heart calls this (click is kept off the card). */
53
53
  onToggleFavorite: () => void;
54
- /** fired when the card is clicked (opens the detail page). The second argument is the payment
55
- * tab the card was showing at click time (ADV-181) — pages forward it to the VDP via the
56
- * `?mode=` query flag so the originating card and the CDP always agree on the product. */
57
- onOpen: (car: AutobahnCar, mode?: PaymentMode) => void;
58
- /** ADV-181 — fired when the user explicitly switches the card's payment tab. Pages use it to
59
- * persist the per-car product choice (FinancingStore.setSelectedProduct) + analytics. The
60
- * card still owns the visual tab state; this is a notification, not a controlled input. */
61
- onModeChange?: (mode: PaymentMode) => void;
54
+ /** fired when the card is clicked (opens the detail page). */
55
+ onOpen: (car: AutobahnCar) => void;
62
56
  /** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
63
57
  onMediaEnter?: () => void;
64
58
  /**
@@ -119,16 +113,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
119
113
  onCall,
120
114
  onOpenRateAdjust,
121
115
  onShowFinancingDetails,
122
- onModeChange,
123
116
  }) => {
124
- // ADV-181: start on the centrally-selected default product (cheaper valid rate of
125
- // financing vs leasing, or the user's saved per-car choice) — computed by toAutobahnCar,
126
- // so SSR/hydration agree. Absent (older producer) → financing, the pre-ADV-181 default.
127
- const [mode, setMode] = useState<PaymentMode>(car.defaultMode ?? 'financing');
128
- const changeMode = (m: PaymentMode) => {
129
- setMode(m);
130
- onModeChange && onModeChange(m);
131
- };
117
+ const [mode, setMode] = useState<PaymentMode>('financing');
132
118
  const [photoIndex, setPhotoIndex] = useState(0);
133
119
  const [viewerOpen, setViewerOpen] = useState(false);
134
120
  const mediaRef = useRef<HTMLDivElement | null>(null);
@@ -174,7 +160,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
174
160
  return (
175
161
  <article
176
162
  className="mcard mcard--interactive cc-card"
177
- onClick={() => onOpen(car, mode)}
163
+ onClick={() => onOpen(car)}
178
164
  style={{ display: 'flex', flexDirection: 'column' }}
179
165
  >
180
166
  <div
@@ -289,7 +275,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
289
275
  </div>
290
276
  <SpecLine items={cardSpec(car)} oneLine />
291
277
  <div className="cc-price" style={{ marginTop: 0, borderTop: '1px solid var(--md-outline-variant)' }}>
292
- <PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} />
278
+ <PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
293
279
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
294
280
  <PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
295
281
  <PriceEvalCol car={car} t={t} />
package/cards/CarRow.tsx CHANGED
@@ -52,13 +52,8 @@ export interface CarRowProps {
52
52
  isFavorite: boolean;
53
53
  /** toggle the favorite; the hearts call this (click is kept off the card). */
54
54
  onToggleFavorite: () => void;
55
- /** fired when the card (or the CTA) is clicked (opens the detail page). The second argument is
56
- * the payment tab/product the card was showing at click time (ADV-181) — pages forward it to
57
- * the VDP via `?mode=` so the originating card and the CDP always agree on the product. */
58
- onOpen: (car: AutobahnCar, mode?: PaymentMode) => void;
59
- /** ADV-181 — fired when the user explicitly switches the mobile payment tab. Pages use it to
60
- * persist the per-car product choice (FinancingStore.setSelectedProduct) + analytics. */
61
- onModeChange?: (mode: PaymentMode) => void;
55
+ /** fired when the card (or the CTA) is clicked (opens the detail page). */
56
+ onOpen: (car: AutobahnCar) => void;
62
57
  /** fired on first hover/touch of the media box — the SRP uses it to lazy-load the gallery. */
63
58
  onMediaEnter?: () => void;
64
59
  /**
@@ -97,16 +92,8 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
97
92
  t,
98
93
  adminAction,
99
94
  onShowFinancingDetails,
100
- onModeChange,
101
95
  }) => {
102
- // ADV-181: start on the centrally-selected default product (see CarCard's identical init).
103
- // Drives the mobile tab switcher AND the mode reported to onOpen; the desktop branch's
104
- // untabbed PricePaths reads car.defaultMode directly.
105
- const [mode, setMode] = useState<PaymentMode>(car.defaultMode ?? 'financing');
106
- const changeMode = (m: PaymentMode) => {
107
- setMode(m);
108
- onModeChange && onModeChange(m);
109
- };
96
+ const [mode, setMode] = useState<PaymentMode>('financing');
110
97
  const [photoIndex, setPhotoIndex] = useState(0);
111
98
  const mediaRef = useRef<HTMLDivElement | null>(null);
112
99
  // always call the LATEST onMediaEnter without re-arming the observer on every render (the
@@ -159,7 +146,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
159
146
  return (
160
147
  <article
161
148
  className="mcard mcard--interactive listcard"
162
- onClick={() => onOpen(car, mode)}
149
+ onClick={() => onOpen(car)}
163
150
  style={{ background: 'var(--md-sc-lowest)' }}
164
151
  >
165
152
  <div className="listcard-media" ref={mediaRef} style={{ position: 'relative' }} onMouseEnter={onMediaEnter} onTouchStart={onMediaEnter}>
@@ -281,7 +268,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
281
268
  the previously-unused PricePaths/PriceKauf pair. Both variants share the same
282
269
  Preisbewertung meter + CTA, anchored to the bottom (margin-top:auto on .pp-purchase). */}
283
270
  <div className="listcard-price">
284
- <PriceTabs car={car} mode={mode} onModeChange={changeMode} t={t} className="show-mobile" />
271
+ <PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} className="show-mobile" />
285
272
  <div className="pp-purchase show-mobile">
286
273
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
287
274
  <PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
@@ -300,7 +287,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
300
287
  iconRight="arrow-right"
301
288
  onClick={(e) => {
302
289
  e.stopPropagation();
303
- onOpen(car, mode);
290
+ onOpen(car);
304
291
  }}
305
292
  >
306
293
  {t('autobahn:card.cta.viewOffer')}
@@ -349,7 +336,7 @@ const CarRow: React.FunctionComponent<CarRowProps> = ({
349
336
  iconRight="arrow-right"
350
337
  onClick={(e) => {
351
338
  e.stopPropagation();
352
- onOpen(car, mode);
339
+ onOpen(car);
353
340
  }}
354
341
  >
355
342
  {t('autobahn:card.cta.viewOffer')}
@@ -1103,11 +1103,6 @@ const PpRow: React.FunctionComponent<PpRowProps> = ({ label, value, unit, old, f
1103
1103
  */
1104
1104
  export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, className, onShowDetails }) => {
1105
1105
  const hasLease = car.lease != null;
1106
- // ADV-181: the hero rate follows the centrally-selected default product (cheaper valid rate,
1107
- // computed in toAutobahnCar). Leasing leads only when it actually won AND is displayable;
1108
- // 'buy'/absent both fall back to the financing hero (pre-ADV-181 layout) — the purchase price
1109
- // keeps its own PriceKauf row below either way, per the desktop rate-first rule.
1110
- const heroMode: PaymentMode = car.defaultMode === 'leasing' && hasLease ? 'leasing' : 'financing';
1111
1106
  // Terms of the config the displayed rate was actually computed with (per-car:
1112
1107
  // dealer rate + the user's saved localStorage adjustments) — default config
1113
1108
  // terms only when the car doesn't carry its own.
@@ -1135,70 +1130,42 @@ export const PricePaths: React.FunctionComponent<PricePathsProps> = ({ car, t, c
1135
1130
  ) : (
1136
1131
  <span className="pp-details-link">{t('autobahn:card.price.details')}</span>
1137
1132
  );
1138
- const finTermsLine = (
1139
- <>
1140
- {t('autobahn:card.price.financeTerms', {
1141
- firstInstallment: finFirstInstallment,
1142
- months: finMonths,
1143
- count: finMonths,
1144
- })}{' '}
1145
- · {detailsLink('financing')}
1146
- </>
1147
- );
1148
- const leasCompactTerms = hasLeasTerms && (
1149
- <div className="pp-sub-terms">
1150
- {/* dedicated keys (not a rework of leaseTerms) so mobile/VDP's own reviewed
1151
- wording there stays untouched, but includes the same Sonderzahlung figure.
1152
- Split across two lines with an explicit <br/> (column is too narrow for all
1153
- three figures + Details on one line) — forced right after km/Jahr so "36" and
1154
- "Mon." always land on the same line instead of an awkward mid-number wrap. */}
1155
- {t('autobahn:card.price.leaseTermsCompactLine1', {
1156
- firstInstallment: leasFirstInstallment,
1157
- mileage: leasMileageStr,
1158
- })}{' · '}
1159
- <br />
1160
- {t('autobahn:card.price.leaseTermsCompactLine2', { months: leasMonths })}{' '}
1161
- · {detailsLink('leasing')}
1162
- </div>
1163
- );
1164
1133
  return (
1165
1134
  <div className={'pricepaths' + (className ? ' ' + className : '')}>
1166
- <div className="pp-eyebrow">
1167
- {t(heroMode === 'leasing' ? 'autobahn:card.price.leasingFrom' : 'autobahn:card.price.financingFrom')}
1168
- </div>
1169
- {/* reuses PriceHero's own rendering — the exact same stacked "UVP-Rate ⓘ /
1135
+ <div className="pp-eyebrow">{t('autobahn:card.price.financingFrom')}</div>
1136
+ {/* reuses PriceHero's own financing rendering — the exact same stacked "UVP-Rate ⓘ /
1170
1137
  struck price / mtl." group beside the rate that .cc-price and mobile's tab switcher
1171
1138
  already use, so the rate + unit never fight for room in one flat row (the earlier
1172
1139
  hand-rolled version here put them side by side and wrapped for any 4+-digit rate). */}
1173
- <PriceHero car={car} mode={heroMode} t={t} alwaysShowUnit />
1174
- {heroMode === 'leasing' ? (
1175
- <>
1140
+ <PriceHero car={car} mode="financing" t={t} alwaysShowUnit />
1141
+ <div className="pp-hero-terms">
1142
+ {t('autobahn:card.price.financeTerms', {
1143
+ firstInstallment: finFirstInstallment,
1144
+ months: finMonths,
1145
+ count: finMonths,
1146
+ })}{' '}
1147
+ · {detailsLink('financing')}
1148
+ </div>
1149
+ {hasLease && (
1150
+ <div className="pp-lease-row">
1151
+ <PpRow label={t('autobahn:card.price.leasing')} value={car.lease as number} unit="mtl." t={t} />
1176
1152
  {hasLeasTerms && (
1177
- <div className="pp-hero-terms">
1153
+ <div className="pp-sub-terms">
1154
+ {/* dedicated keys (not a rework of leaseTerms) so mobile/VDP's own reviewed
1155
+ wording there stays untouched, but includes the same Sonderzahlung figure.
1156
+ Split across two lines with an explicit <br/> (column is too narrow for all
1157
+ three figures + Details on one line) — forced right after km/Jahr so "36" and
1158
+ "Mon." always land on the same line instead of an awkward mid-number wrap. */}
1178
1159
  {t('autobahn:card.price.leaseTermsCompactLine1', {
1179
1160
  firstInstallment: leasFirstInstallment,
1180
1161
  mileage: leasMileageStr,
1181
1162
  })}{' · '}
1163
+ <br />
1182
1164
  {t('autobahn:card.price.leaseTermsCompactLine2', { months: leasMonths })}{' '}
1183
1165
  · {detailsLink('leasing')}
1184
1166
  </div>
1185
1167
  )}
1186
- {/* financing follows as the quiet row — same layout slot the leasing row used before. */}
1187
- <div className="pp-lease-row">
1188
- <PpRow label={t('autobahn:finance.mode.financing')} value={car.monthly} unit="mtl." t={t} />
1189
- <div className="pp-sub-terms">{finTermsLine}</div>
1190
- </div>
1191
- </>
1192
- ) : (
1193
- <>
1194
- <div className="pp-hero-terms">{finTermsLine}</div>
1195
- {hasLease && (
1196
- <div className="pp-lease-row">
1197
- <PpRow label={t('autobahn:card.price.leasing')} value={car.lease as number} unit="mtl." t={t} />
1198
- {leasCompactTerms}
1199
- </div>
1200
- )}
1201
- </>
1168
+ </div>
1202
1169
  )}
1203
1170
  </div>
1204
1171
  );
package/cards/types.ts CHANGED
@@ -57,16 +57,6 @@ export interface AutobahnCar {
57
57
  // --- price (always real) ---
58
58
  /** financing monthly rate in EUR — the LEAD price on every card. */
59
59
  monthly: number;
60
- /**
61
- * ADV-181 — which payment product leads on this card. Computed centrally by
62
- * toAutobahnCar via fe-financing's pickDefaultProduct (cheaper valid rate of
63
- * financing vs leasing; tie → financing; neither valid → 'buy'), then
64
- * overridden by the user's saved explicit per-car selection when the fe-main
65
- * transform stamped one (storeCar.selectedProduct). Cards initialise their
66
- * tab state from this; absent (older producer) → 'financing', the pre-ADV-181
67
- * behavior. Derived purely from card data, so SSR and hydration agree.
68
- */
69
- defaultMode?: 'financing' | 'leasing' | 'buy';
70
60
  /**
71
61
  * financing terms the `monthly` rate was computed with — the per-car merged
72
62
  * config (dealer rate + the user's saved localStorage adjustments, ADV-157).
package/chrome/Footer.tsx CHANGED
@@ -79,6 +79,13 @@ export interface FooterProps {
79
79
  * strings as the legacy footer so /search stays compliant where prices/rates are shown.
80
80
  */
81
81
  t: TranslationFunction;
82
+ /**
83
+ * Adds footnotes ⁵ (Pkw-EnVKV possible CO₂ costs) and ⁶ (§3d KraftStG EV tax exemption) to the
84
+ * disclaimer block, right after ⁴ (price evaluation) and before the WLTP consumption note. Real
85
+ * legal copy tied to the "Laufende Kosten pro Jahr" cards on the CDP — only pass true there
86
+ * (VehicleDetailedNextPage), never site-wide, since every other page shows only footnotes 1-4.
87
+ */
88
+ showCostFootnotes?: boolean;
82
89
  }
83
90
 
84
91
  /**
@@ -86,7 +93,7 @@ export interface FooterProps {
86
93
  * disclaimer block, and a copyright + "Nach Oben" bar. Pure/presentational MD3 chrome — renders
87
94
  * correctly only inside a `<div className="md3">` subtree (scoped theme at source/theme/autobahn).
88
95
  */
89
- const Footer: React.FunctionComponent<FooterProps> = ({ t }) => {
96
+ const Footer: React.FunctionComponent<FooterProps> = ({ t, showCostFootnotes }) => {
90
97
  const scrollTop = (): void => window.scroll({ top: 0, left: 0, behavior: 'smooth' });
91
98
  const now = new Date();
92
99
  const year = now.getFullYear();
@@ -179,6 +186,7 @@ const Footer: React.FunctionComponent<FooterProps> = ({ t }) => {
179
186
  t('footer:manufacturerUvp'),
180
187
  t('footer:uvpComparisonRate'),
181
188
  t('footer:priceEvaluation', { date: today }),
189
+ ...(showCostFootnotes ? [t('footer:co2CostsInfo'), t('footer:evTaxExemption')] : []),
182
190
  t('footer:consumptionInfo'),
183
191
  t('footer:vehicleImages'),
184
192
  ].join('<br />'),
@@ -39,9 +39,10 @@ export interface IVehiclePriceItemProps {
39
39
  isMarge?: boolean;
40
40
  }
41
41
  class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
42
- // ADV-181: the old routeToActiveTab (localStorage 'activeTabCDP' write) was removed — its only
43
- // reader was the unrouted legacy VDP sidebar, so the write was a no-op on the live site. The
44
- // live VDP resolves its tab centrally (lowest-rate rule / saved per-car choice / `?mode=`).
42
+ routeToActiveTab = () => {
43
+ const { priceTitle } = this.props;
44
+ localStorage.setItem('activeTabCDP', priceTitle);
45
+ }
45
46
 
46
47
  render(): React.ReactNode {
47
48
  const {
@@ -102,6 +103,7 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
102
103
  ...routeObj,
103
104
  target,
104
105
  rel,
106
+ onClick: this.routeToActiveTab,
105
107
  className: priceItemCategoryClassName,
106
108
  href: url
107
109
  })}
@@ -186,6 +188,7 @@ class VehiclePriceItem extends React.Component<IVehiclePriceItemProps> {
186
188
  ...routeObj,
187
189
  target,
188
190
  rel,
191
+ onClick: this.routeToActiveTab,
189
192
  className: styles.priceItemPrice,
190
193
  href: url
191
194
  })}
@@ -166,8 +166,8 @@ const VehicleInfo: React.FC<IVehicleInfoProps> = (props) => {
166
166
  ),
167
167
  ...routeObj,
168
168
  className: wrapClasses,
169
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted legacy
170
- // VDP sidebar) — the live VDP resolves its tab centrally.
169
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
170
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
171
171
  href: url,
172
172
  target,
173
173
  rel
@@ -319,9 +319,8 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
319
319
  target,
320
320
  rel,
321
321
  className: `${styles.redirectClassLink} ${styles.gridAreaTitle}`,
322
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted
323
- // legacy VDP sidebar) — the live VDP resolves its tab centrally (lowest-rate rule /
324
- // saved per-car choice / `?mode=` query flag).
322
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
323
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
325
324
  href: url
326
325
  })}
327
326
 
@@ -374,9 +373,8 @@ const VehicleSmallCard: FunctionComponent<IVehicleSmallCardProps> = (props) => {
374
373
  target,
375
374
  rel,
376
375
  className: `${styles.redirectClassLink} ${styles.gridAreaImage}`,
377
- // ADV-181: the dead activeTabCDP write removed (its only reader was the unrouted
378
- // legacy VDP sidebar) — the live VDP resolves its tab centrally (lowest-rate rule /
379
- // saved per-car choice / `?mode=` query flag).
376
+ // ADV-116: opening a CDP from a card defaults to the Financing tab.
377
+ onClick: () => localStorage.setItem('activeTabCDP', t('vehicleProps:title.financing')),
380
378
  href: url
381
379
  })}
382
380
  <VehicleInfo {...vehicleInfoProps} />
@@ -23,8 +23,6 @@
23
23
  // Every field path below was confirmed against the real code (file:line in
24
24
  // ./fieldMapping.ts), not guessed.
25
25
 
26
- import { pickDefaultProduct } from '@bytebrand/fe-financing';
27
-
28
26
  import { getPriceRating, getPredictablePrice } from '../utils/priceRating';
29
27
  import { formatTimestamp } from '../utils/DateUtils';
30
28
  import { getPowerLabel } from '../utils/power';
@@ -429,27 +427,6 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
429
427
  const leaseMonths = leasingActive ? num(price?.leasing?.paybackPeriod) : undefined;
430
428
  const leaseFirstInstallment = leasingActive ? num(price?.leasing?.firstInstallment) : undefined;
431
429
 
432
- // --- ADV-181: leading payment product — ONE central rule for every card surface. ---
433
- // Automatic default = fe-financing's pickDefaultProduct on the same displayed rates the
434
- // card renders (cheaper valid rate wins, tie → financing, neither valid → 'buy'). The
435
- // user's saved explicit per-car choice (storeCar.selectedProduct, stamped by fe-main's
436
- // transformDataForVehicles from FinancingStore) overrides the automatic rule — but only
437
- // while that product is actually displayable on this card, so a stale "leasing" choice on
438
- // a car that aged out of leasing eligibility falls back to the automatic rule instead of
439
- // selecting a product the card can't show. Pure input → pure output: SSR (fe-landing)
440
- // computes the identical value from the same payload, so hydration never flips the tab.
441
- const picked = pickDefaultProduct({
442
- financing: { monthlyInstallment: monthly },
443
- leasing: { isActive: leasingActive, monthlyInstallment: lease ?? null },
444
- });
445
- const savedProduct = (storeCar as any).selectedProduct;
446
- const defaultMode: 'financing' | 'leasing' | 'buy' =
447
- savedProduct === 'leasing' && lease != null
448
- ? 'leasing'
449
- : savedProduct === 'financing' && monthly > 0
450
- ? 'financing'
451
- : picked.product;
452
-
453
430
  // --- optional: list price / savings — ONLY from a real strike price ---
454
431
  // transformDataForVehicles exposes oldMonthlyInstallment / highestPrice via
455
432
  // the financing spread, and price.historyPriceDifference. There is NO reliable
@@ -517,7 +494,6 @@ export function toAutobahnCar(storeCar: any, deps: ToAutobahnCarDeps = {}): Auto
517
494
 
518
495
  // price
519
496
  monthly,
520
- defaultMode,
521
497
  ...(finFirstInstallment != null ? { finFirstInstallment } : {}),
522
498
  ...(finMonths != null ? { finMonths } : {}),
523
499
  price: purchase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.133",
3
+ "version": "1.0.135",
4
4
  "description": "auto.de md3 (Autobahn) design system — UI primitives, cards, chrome, VDP modules, theme CSS and locales. Ships raw TypeScript source (like fe-ui-core before it); each consumer's bundler compiles it.",
5
5
  "main": "index.ts",
6
6
  "license": "UNLICENSED",
package/theme/md3.css CHANGED
@@ -7581,6 +7581,7 @@ line-height: 31px;
7581
7581
  display: flex;
7582
7582
  gap: 5px;
7583
7583
  align-items: flex-end;
7584
+ margin-bottom: 20px;
7584
7585
  }
7585
7586
  .md3 .co2-col {
7586
7587
  flex: 1;
@@ -417,7 +417,7 @@ const footnoteStyle: React.CSSProperties = {
417
417
  fontWeight: 700,
418
418
  fontSize: 18,
419
419
  lineHeight: '24px',
420
- color: '#005CFF',
420
+ color: '#0E2238',
421
421
  };
422
422
 
423
423
  const summaryCardTitleStyle: React.CSSProperties = {
@@ -445,9 +445,8 @@ export const VdpEnergyTaxSummary: React.FunctionComponent<VdpEnergyTaxSummaryPro
445
445
  display: 'flex',
446
446
  width: '100%',
447
447
  gap: 4,
448
- background: 'var(--md-sc-lowest)',
449
- borderRadius: 'var(--md-r-md)',
450
- border: '1px solid var(--md-outline-variant)',
448
+ background: '#F5F7FA',
449
+ borderRadius: 12,
451
450
  }}
452
451
  >
453
452
  <div className="vdp-ets-col vdp-ets-col--first" style={{ flex: 1 }}>
@@ -500,9 +499,8 @@ export const VdpCo2CostsSummary: React.FunctionComponent<VdpCo2CostsSummaryProps
500
499
  display: 'flex',
501
500
  width: '100%',
502
501
  gap: 4,
503
- background: 'var(--md-sc-lowest)',
504
- borderRadius: 'var(--md-r-md)',
505
- border: '1px solid var(--md-outline-variant)',
502
+ background: '#F5F7FA',
503
+ borderRadius: 12,
506
504
  }}
507
505
  >
508
506
  {rows.map((row, i) => (
@@ -520,60 +518,6 @@ export const VdpCo2CostsSummary: React.FunctionComponent<VdpCo2CostsSummaryProps
520
518
  );
521
519
  };
522
520
 
523
- // ---------------------------------------------------------------------------
524
- // Legal footnote text for the "⁵"/"⁶" markers used in VdpCo2CostsSummary/VdpEnergyTaxSummary
525
- // above — real Pkw-EnVKV/§3d KraftStG disclosure copy (verbatim, not paraphrased), plain
526
- // text (no card/background). Needs-data: each paragraph only shows when its corresponding
527
- // card above actually rendered (never an orphaned footnote for a marker nobody saw).
528
- // ---------------------------------------------------------------------------
529
-
530
- export interface VdpCostFootnotesProps {
531
- vehicle: VehicleDetail;
532
- t: TranslationFunction;
533
- }
534
-
535
- const costFootnoteStyle: React.CSSProperties = {
536
- fontFamily: 'Arimo',
537
- fontStyle: 'normal',
538
- fontWeight: 700,
539
- fontSize: 13.5,
540
- lineHeight: '16px',
541
- color: '#000000',
542
- };
543
-
544
- export const VdpCostFootnotes: React.FunctionComponent<VdpCostFootnotesProps> = ({ vehicle, t }) => {
545
- const showCo2Footnote = !!vehicle.co2CostsSummary;
546
- const showTaxFootnote = !!vehicle.energyTaxSummary;
547
- if (!showCo2Footnote && !showTaxFootnote) return null;
548
- return (
549
- <div id="vdp-cost-footnotes" style={costFootnoteStyle}>
550
- {showCo2Footnote && (
551
- <>
552
- {t('vehicleProps:value.co2CostsFootnoteTitle')}
553
- <br />
554
- <br />
555
- {t('vehicleProps:value.co2CostsFootnote')}
556
- </>
557
- )}
558
- {showCo2Footnote && showTaxFootnote && (
559
- <>
560
- <br />
561
- <br />
562
- <br />
563
- </>
564
- )}
565
- {showTaxFootnote && (
566
- <>
567
- {t('vehicleProps:value.taxExemptFootnoteTitle')}
568
- <br />
569
- <br />
570
- {t('vehicleProps:value.taxExemptFootnote')}
571
- </>
572
- )}
573
- </div>
574
- );
575
- };
576
-
577
521
  // ---------------------------------------------------------------------------
578
522
  // Equipment list (grouped + searchable)
579
523
  // ---------------------------------------------------------------------------
@@ -734,6 +678,19 @@ export const Co2Label: React.FunctionComponent<Co2LabelProps> = ({ vehicle, t })
734
678
  </div>
735
679
  {/* no EnVKV footer line — Figma drops it (the * on the heading refers to the page-footer
736
680
  disclaimer instead); the scale caption above carries the WLTP-kombiniert wording */}
681
+
682
+ {/* Pkw-EnVKV mandatory cost disclosure — moved to the bottom of this same card (Figma) rather
683
+ than standing as its own top-level block; each still gates on its own vehicle data. */}
684
+ {vehicle.energyTaxSummary && (
685
+ <div style={{ paddingTop: 20, borderTop: '1px solid #C5C6D2' }}>
686
+ <VdpEnergyTaxSummary vehicle={vehicle} t={t} />
687
+ </div>
688
+ )}
689
+ {vehicle.co2CostsSummary && (
690
+ <div style={{ marginTop: 22 }}>
691
+ <VdpCo2CostsSummary vehicle={vehicle} t={t} />
692
+ </div>
693
+ )}
737
694
  </div>
738
695
  );
739
696
  };
@@ -1649,18 +1606,12 @@ export interface VdpStickyBarProps {
1649
1606
  show: boolean;
1650
1607
  /** primary CTA click. */
1651
1608
  onSecure?: () => void;
1652
- /** ADV-181: monthly rate for the CURRENTLY selected payment product (fin/leasing differ) —
1653
- * falls back to vehicle.monthly (the financing rate) when omitted, the pre-ADV-181 display. */
1654
- monthly?: number;
1655
- /** ADV-181: label of the selected product ("Finanzierung"/"Leasing") — replaces the generic
1656
- * "ab" eyebrow so the sticky bar's label always matches the rate it shows. */
1657
- productLabel?: string;
1658
1609
  /** i18n function (drilled from the page). */
1659
1610
  t: TranslationFunction;
1660
1611
  }
1661
1612
 
1662
1613
  /** Desktop sticky summary bar (AS24 pattern) — docks to top, driven by `show`. */
1663
- export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, monthly, productLabel, t }) => (
1614
+ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehicle, image, show, onSecure, t }) => (
1664
1615
  <div className={'vdp-stickytop hide-mobile' + (show ? ' vdp-stickytop--on' : '')}>
1665
1616
  <div className="container" style={{ display: 'flex', alignItems: 'center', gap: 18, padding: '10px 24px' }}>
1666
1617
  <img
@@ -1686,9 +1637,9 @@ export const VdpStickyBar: React.FunctionComponent<VdpStickyBarProps> = ({ vehic
1686
1637
  </div>
1687
1638
  <div style={{ textAlign: 'right', flex: 'none' }}>
1688
1639
  <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, justifyContent: 'flex-end' }}>
1689
- <span className="md-label-sm">{productLabel || t('autobahn:sticky.from')}</span>
1640
+ <span className="md-label-sm">{t('autobahn:sticky.from')}</span>
1690
1641
  <span className="num" style={{ fontWeight: 700, fontSize: 20, color: 'var(--md-primary)', letterSpacing: '-.4px' }}>
1691
- {monthly ?? vehicle.monthly} €
1642
+ {vehicle.monthly} €
1692
1643
  </span>
1693
1644
  <span className="md-label-sm">{t('autobahn:sticky.perMonth')}</span>
1694
1645
  </div>
package/vdp/index.ts CHANGED
@@ -31,7 +31,6 @@ export {
31
31
  VdpSpecTable,
32
32
  VdpEnergyTaxSummary,
33
33
  VdpCo2CostsSummary,
34
- VdpCostFootnotes,
35
34
  EquipmentList,
36
35
  Co2Label,
37
36
  VehicleDescription,
@@ -54,7 +53,6 @@ export type {
54
53
  VdpSpecTableProps,
55
54
  VdpEnergyTaxSummaryProps,
56
55
  VdpCo2CostsSummaryProps,
57
- VdpCostFootnotesProps,
58
56
  EquipmentListProps,
59
57
  Co2LabelProps,
60
58
  VehicleDescriptionProps,