@bytebrand/fe-ui-core-autobahn 1.0.77 → 1.0.79
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/cardParts.tsx +30 -14
- package/locales/de/autobahn.json +2 -2
- package/locales/en/autobahn.json +1 -1
- package/package.json +1 -1
- package/theme/md3.css +216 -83
- package/vdp/FinanceCalculator.tsx +151 -29
- package/vdp/VdpModules.tsx +367 -51
package/cards/cardParts.tsx
CHANGED
|
@@ -392,27 +392,38 @@ export interface PricePathsProps {
|
|
|
392
392
|
const TONE_TO_TIER: Record<string, number> = { top: 0, good: 1, fair: 2, raised: 3, high: 4 };
|
|
393
393
|
|
|
394
394
|
/**
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
395
|
+
* Discrete Preisbewertung tier bar + thumb (Figma: 5 solid segments — green/light-green/yellow/
|
|
396
|
+
* orange/red — with a white circular marker at the car's position, same `.fin-eval-bar`/
|
|
397
|
+
* `.fin-eval-seg` segmented treatment the grid CarCard's PriceEvalCol/EvalBar already uses; no
|
|
398
|
+
* text label here since the tier is already named by the StatChip pill next to the price).
|
|
399
|
+
* Renders nothing when there is no rating/band (needs-data: we never draw a fake market position).
|
|
399
400
|
*/
|
|
400
401
|
export const PriceMeter: React.FunctionComponent<{ car: AutobahnCar; t: TranslationFunction }> = ({ car }) => {
|
|
401
402
|
const hasMarket =
|
|
402
403
|
car.marketLow != null && car.marketAvg != null && car.marketHigh != null && car.marketHigh > car.marketLow;
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
const bar = (tier: number) => {
|
|
405
|
+
const leftPct = Math.max(4, Math.min(96, (tier / 4) * 100));
|
|
406
|
+
const tierIndex = Math.max(0, Math.min(4, Math.round(tier)));
|
|
407
|
+
return (
|
|
408
|
+
<div className="fin-eval-bar">
|
|
409
|
+
{TIER_COLORS.map((c, i) => (
|
|
410
|
+
<span key={i} className="fin-eval-seg" style={{ background: c }} />
|
|
411
|
+
))}
|
|
412
|
+
<div
|
|
413
|
+
className={`fin-eval-thumb${TIER_KEYS[tierIndex] === 'good' ? ' fin-eval-thumb--good' : ''}`}
|
|
414
|
+
style={{ left: `${leftPct}%`, borderColor: TIER_COLORS[tierIndex] }}
|
|
415
|
+
/>
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
};
|
|
408
419
|
if (hasMarket) {
|
|
409
420
|
const mpos = Math.max(0, Math.min(1, (car.price - car.marketLow!) / (car.marketHigh! - car.marketLow!)));
|
|
410
|
-
return
|
|
421
|
+
return bar(mpos * 4);
|
|
411
422
|
}
|
|
412
423
|
if (car.rating && TONE_TO_TIER[car.rating] != null) {
|
|
413
424
|
// No market BAND yet, but the real Preisbewertung tier (same signal as the rating chip)
|
|
414
|
-
// drives the
|
|
415
|
-
return
|
|
425
|
+
// drives the thumb position. Real data, not faked.
|
|
426
|
+
return bar(TONE_TO_TIER[car.rating]);
|
|
416
427
|
}
|
|
417
428
|
return null;
|
|
418
429
|
};
|
|
@@ -597,12 +608,17 @@ export const PriceHero: React.FunctionComponent<PriceHeroProps> = ({ car, mode,
|
|
|
597
608
|
column layout exactly — so the click target covers the full area INCLUDING the
|
|
598
609
|
gap between the two stacked lines, not just their text glyphs (display:contents
|
|
599
610
|
has no box of its own there, so clicks in that gap fell through to the card link
|
|
600
|
-
behind it instead of opening the tooltip).
|
|
611
|
+
behind it instead of opening the tooltip). stopPropagation is required here too —
|
|
612
|
+
the whole card (CarCard.tsx) has its own onClick={() => onOpen(car)} navigation,
|
|
613
|
+
so without it a click on this block both opened the tooltip AND navigated away. */}
|
|
601
614
|
<span
|
|
602
615
|
style={{ display: 'flex', flexDirection: 'column' }}
|
|
603
616
|
role="button"
|
|
604
617
|
tabIndex={0}
|
|
605
|
-
onClick={() =>
|
|
618
|
+
onClick={(e) => {
|
|
619
|
+
e.stopPropagation();
|
|
620
|
+
setUvpTooltipOpen((v) => !v);
|
|
621
|
+
}}
|
|
606
622
|
>
|
|
607
623
|
<span className="pp-eyebrow">
|
|
608
624
|
{t('autobahn:card.price.uvpRateLabel')}
|
package/locales/de/autobahn.json
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"card": {
|
|
11
11
|
"price": {
|
|
12
|
-
"financeTerms": "{{firstInstallment}} € Anzahlung · {{months}} Monate
|
|
13
|
-
"financeTerms_plural": "{{firstInstallment}} € Anzahlung · {{months}} Monate
|
|
12
|
+
"financeTerms": "{{firstInstallment}} € Anzahlung · {{months}} Monate",
|
|
13
|
+
"financeTerms_plural": "{{firstInstallment}} € Anzahlung · {{months}} Monate",
|
|
14
14
|
"leaseTerms": "{{firstInstallment}} € Sonderzahlung · {{months}} Mon. · {{mileage}} km/Jahr",
|
|
15
15
|
"leaseTermsCompact": "{{mileage}} km/Jahr · {{months}} Monate",
|
|
16
16
|
"detailsLink": "Details",
|
package/locales/en/autobahn.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"card": {
|
|
11
11
|
"price": {
|
|
12
|
-
"financeTerms": "€{{firstInstallment}} down payment · {{months}} months
|
|
12
|
+
"financeTerms": "€{{firstInstallment}} down payment · {{months}} months",
|
|
13
13
|
"financeTerms_plural": "€{{firstInstallment}} down payment · {{months}} months term",
|
|
14
14
|
"leaseTerms": "€{{firstInstallment}} down payment · {{months}} mo. · {{mileage}} km/year",
|
|
15
15
|
"leaseTermsCompact": "{{mileage}} km/year · {{months}} months",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.79",
|
|
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
|
@@ -122,6 +122,13 @@
|
|
|
122
122
|
.md3 *::after {
|
|
123
123
|
box-sizing: border-box;
|
|
124
124
|
}
|
|
125
|
+
/* mobile Safari/Chrome paint a solid blue tap-highlight rectangle on any clickable element on
|
|
126
|
+
touch — and when an ancestor has a CSS transform (e.g. the gallery carousel's translateX),
|
|
127
|
+
that highlight can render far larger than the tapped element, up to full-viewport. Disabling
|
|
128
|
+
it site-wide (not just on the specific role="button" spans) is the only reliable fix. */
|
|
129
|
+
.md3 * {
|
|
130
|
+
-webkit-tap-highlight-color: transparent;
|
|
131
|
+
}
|
|
125
132
|
.md3 ::selection {
|
|
126
133
|
background: var(--md-primary-container);
|
|
127
134
|
}
|
|
@@ -4777,6 +4784,25 @@ line-height: 31px;
|
|
|
4777
4784
|
top: -20px;
|
|
4778
4785
|
right: -110px;
|
|
4779
4786
|
}
|
|
4787
|
+
/* .fin-calc-card is narrower than .cc-price / .fin-modal, so the shared -84px offset lets
|
|
4788
|
+
the popover overhang the card's right edge — this rule itself is unscoped (applies at
|
|
4789
|
+
every width, see the file-wide note above), so the max-width:600px block right below
|
|
4790
|
+
narrows it further just for phones. */
|
|
4791
|
+
.md3 .fin-calc-card .pp-uvp-tooltip {
|
|
4792
|
+
right: -135px;
|
|
4793
|
+
}
|
|
4794
|
+
@media (max-width: 600px) {
|
|
4795
|
+
.md3 .fin-calc-card .pp-uvp-tooltip {
|
|
4796
|
+
right: -95px;
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
/* Kaufen tab's own struck-price tooltip (.fin-rate-old-list, "2" badge) sits at a different
|
|
4800
|
+
offset than the Finanzierung/Leasing "3" badge tooltip above — desktop only. */
|
|
4801
|
+
@media (min-width: 601px) {
|
|
4802
|
+
.md3 .fin-calc-card .fin-rate-old-list .pp-uvp-tooltip {
|
|
4803
|
+
right: -100px;
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4780
4806
|
/* floating circular close button — overlaps the tooltip's top-right corner from outside,
|
|
4781
4807
|
same "glass circle" convention as VdpViewer's close/back/next overlay buttons. */
|
|
4782
4808
|
.md3 .cc-price .pp-uvp-tooltip-close,
|
|
@@ -5108,10 +5134,30 @@ line-height: 31px;
|
|
|
5108
5134
|
align-items: center;
|
|
5109
5135
|
margin-top: 0;
|
|
5110
5136
|
}
|
|
5137
|
+
/* Kaufen tab (FinanceCalculator.tsx's own .fin-calc-card) — was an inline style={isCash ? {...} :
|
|
5138
|
+
undefined}; switched to a class modifier because React leaves a stale (but harmless-looking)
|
|
5139
|
+
style="" attribute on the node when that ternary flips to undefined on tab switch. */
|
|
5140
|
+
.md3 .fin-calc-card .fin-price-grid.fin-price-grid--cash {
|
|
5141
|
+
gap: 0px 15px;
|
|
5142
|
+
}
|
|
5111
5143
|
.md3 .fin-rate-main {
|
|
5112
5144
|
grid-area: rate;
|
|
5113
5145
|
min-width: 0;
|
|
5114
5146
|
}
|
|
5147
|
+
/* Kaufen desktop hero price footnote — same small/quiet treatment as the mobile
|
|
5148
|
+
.fin-rate-cash-mobile sup, instead of inheriting the 48px blue hero size. */
|
|
5149
|
+
.md3 .fin-rate-main .num.hide-mobile sup {
|
|
5150
|
+
position: relative;
|
|
5151
|
+
top: 6px;
|
|
5152
|
+
left: 3px;
|
|
5153
|
+
vertical-align: super;
|
|
5154
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
5155
|
+
font-style: normal;
|
|
5156
|
+
font-weight: 400;
|
|
5157
|
+
font-size: 8px;
|
|
5158
|
+
line-height: 9px;
|
|
5159
|
+
color: #111820;
|
|
5160
|
+
}
|
|
5115
5161
|
/* transparent wrapper on desktop — pill/bar resume being independent grid-area items,
|
|
5116
5162
|
identical to the original (pre-grouping) layout; mobile turns it into a real flex column */
|
|
5117
5163
|
.md3 .fin-eval-col {
|
|
@@ -5126,6 +5172,42 @@ line-height: 31px;
|
|
|
5126
5172
|
.md3 .fin-eval-bar-cell {
|
|
5127
5173
|
grid-area: bar;
|
|
5128
5174
|
}
|
|
5175
|
+
/* .fin-calc-card only — the base display:contents above splits the pill and bar across the
|
|
5176
|
+
grid's two separate rows ("rate pill" / "cond bar"), which visually strands the bar down next
|
|
5177
|
+
to the terms line once the price row grows tall. Group them into one real flex column instead
|
|
5178
|
+
(matches the mobile sheet's own .fin-eval-col treatment). */
|
|
5179
|
+
.md3 .fin-calc-card .fin-price-grid {
|
|
5180
|
+
grid-template-columns: minmax(0, 1fr) 112px;
|
|
5181
|
+
grid-template-areas: "rate evalcol" "cond cond";
|
|
5182
|
+
column-gap: 15px;
|
|
5183
|
+
}
|
|
5184
|
+
.md3 .fin-calc-card .fin-eval-col {
|
|
5185
|
+
display: flex;
|
|
5186
|
+
flex-direction: column;
|
|
5187
|
+
gap: 16px;
|
|
5188
|
+
grid-area: evalcol;
|
|
5189
|
+
width: 112px;
|
|
5190
|
+
justify-self: end;
|
|
5191
|
+
}
|
|
5192
|
+
/* pill now sits above the bar in a real flex column (see above) — stretch it to the bar's own
|
|
5193
|
+
width instead of the base .fin-eval-pill's inline-flex/content-hugging size. */
|
|
5194
|
+
.md3 .fin-calc-card .fin-eval-pill--good {
|
|
5195
|
+
width: 100%;
|
|
5196
|
+
display: flex;
|
|
5197
|
+
align-items: center;
|
|
5198
|
+
justify-content: center;
|
|
5199
|
+
}
|
|
5200
|
+
/* .fin-calc-card's own "Rate anpassen" panel is compact — the mobile-sheet/modal padding
|
|
5201
|
+
(22/28px top, 20px bottom) is too roomy here. */
|
|
5202
|
+
.md3 .fin-calc-card .fin-toggle-final,
|
|
5203
|
+
.md3 .fin-calc-card .fin-toggle-addservices {
|
|
5204
|
+
padding-top: 10px;
|
|
5205
|
+
padding-bottom: 10px;
|
|
5206
|
+
}
|
|
5207
|
+
.md3 .fin-calc-card .fin-toggle-addservices .num {
|
|
5208
|
+
font-size: 13px !important;
|
|
5209
|
+
line-height: 15px !important;
|
|
5210
|
+
}
|
|
5129
5211
|
/* rating pill — light tier-tinted bg, tier-colored text + icon (bg/color set inline per tier) */
|
|
5130
5212
|
.md3 .fin-eval-pill {
|
|
5131
5213
|
display: inline-flex;
|
|
@@ -5207,15 +5289,6 @@ line-height: 31px;
|
|
|
5207
5289
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
|
5208
5290
|
pointer-events: none;
|
|
5209
5291
|
}
|
|
5210
|
-
/* UVP (struck list price) — cash mode only */
|
|
5211
|
-
.md3 .fin-kauf {
|
|
5212
|
-
margin-top: 8px;
|
|
5213
|
-
font-size: 13px;
|
|
5214
|
-
color: var(--md-on-surface-variant);
|
|
5215
|
-
}
|
|
5216
|
-
.md3 .fin-kauf s {
|
|
5217
|
-
color: var(--md-error);
|
|
5218
|
-
}
|
|
5219
5292
|
/* struck old rate / list price next to the hero figure (Figma "329 € 4̶9̶9̶ ̶€̶ / mtl.") —
|
|
5220
5293
|
REAL isStrikeShown-gated data; used in the offer card, the Rate-anpassen sheet and the
|
|
5221
5294
|
sticky bottom bar, all payment modes. */
|
|
@@ -5227,6 +5300,38 @@ line-height: 31px;
|
|
|
5227
5300
|
letter-spacing: 0;
|
|
5228
5301
|
flex: none;
|
|
5229
5302
|
}
|
|
5303
|
+
/* .fin-rate-old-list's own position:relative (needed as the .pp-uvp-tooltip/.pp-eyebrow-badge
|
|
5304
|
+
anchor) only exists inside the mobile media query below — without it here, the desktop
|
|
5305
|
+
.fin-calc-card Kaufen tooltip anchors to some distant ancestor and effectively never shows. */
|
|
5306
|
+
.md3 .fin-rate-old.fin-rate-old-list {
|
|
5307
|
+
position: relative;
|
|
5308
|
+
display: inline-block;
|
|
5309
|
+
}
|
|
5310
|
+
/* desktop .fin-calc-card's own struck old-rate/list-price treatment (the "3"/"2" badge
|
|
5311
|
+
figures) — overrides the shared 15px/700/error-red .fin-rate-old base above, and replaces
|
|
5312
|
+
the native line-through with the same Figma diagonal red strike used on mobile. */
|
|
5313
|
+
.md3 .fin-calc-card .fin-rate-old.fin-rate-example-price,
|
|
5314
|
+
.md3 .fin-calc-card .fin-rate-old.fin-rate-old-list {
|
|
5315
|
+
position: relative;
|
|
5316
|
+
display: inline-block;
|
|
5317
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
5318
|
+
font-weight: 400;
|
|
5319
|
+
font-size: 17px;
|
|
5320
|
+
line-height: 20px;
|
|
5321
|
+
color: #0e2238;
|
|
5322
|
+
text-decoration: none;
|
|
5323
|
+
}
|
|
5324
|
+
.md3 .fin-calc-card .fin-rate-old.fin-rate-example-price::after,
|
|
5325
|
+
.md3 .fin-calc-card .fin-rate-old.fin-rate-old-list::after {
|
|
5326
|
+
content: '';
|
|
5327
|
+
position: absolute;
|
|
5328
|
+
left: -2px;
|
|
5329
|
+
right: -2px;
|
|
5330
|
+
top: 50%;
|
|
5331
|
+
border-top: 2px solid #e60000;
|
|
5332
|
+
transform: rotate(12.53deg);
|
|
5333
|
+
transform-origin: center;
|
|
5334
|
+
}
|
|
5230
5335
|
/* sticky bottom bar struck old rate — diagonal red strike (Figma), replacing the shared
|
|
5231
5336
|
.fin-rate-old's native horizontal line-through for this instance only. */
|
|
5232
5337
|
.md3 .bb-price-row .fin-rate-old.fin-rate-old-bb {
|
|
@@ -5351,6 +5456,27 @@ line-height: 31px;
|
|
|
5351
5456
|
.md3 .term-chip.on .tc-unit {
|
|
5352
5457
|
color: rgba(255, 255, 255, 0.7);
|
|
5353
5458
|
}
|
|
5459
|
+
/* desktop FinanceDetailsModal breakdown (VdpModules.tsx) and FinanceCalculator.tsx's own
|
|
5460
|
+
.fin-adjust panel — both filter out 12/96 (6 term options left) and now match the mobile
|
|
5461
|
+
"Rate anpassen" sheet's .fin-sheet-terms pill style exactly (flex row, no "Mon." unit). */
|
|
5462
|
+
.md3 .fin-modal .hide-mobile .term-chips,
|
|
5463
|
+
.md3 .fin-adjust .term-chips {
|
|
5464
|
+
display: flex;
|
|
5465
|
+
flex-wrap: wrap;
|
|
5466
|
+
gap: 8px;
|
|
5467
|
+
}
|
|
5468
|
+
.md3 .fin-modal .hide-mobile .term-chip,
|
|
5469
|
+
.md3 .fin-adjust .term-chip {
|
|
5470
|
+
flex: 1 1 0;
|
|
5471
|
+
min-width: 46px;
|
|
5472
|
+
height: 40px;
|
|
5473
|
+
border-radius: 8px;
|
|
5474
|
+
padding: 0 2px;
|
|
5475
|
+
}
|
|
5476
|
+
.md3 .fin-modal .hide-mobile .term-chip .tc-unit,
|
|
5477
|
+
.md3 .fin-adjust .term-chip .tc-unit {
|
|
5478
|
+
display: none;
|
|
5479
|
+
}
|
|
5354
5480
|
|
|
5355
5481
|
/* enriched vehicle enquiry — trust aside + form */
|
|
5356
5482
|
.md3 .enquiry-grid {
|
|
@@ -5539,7 +5665,7 @@ line-height: 31px;
|
|
|
5539
5665
|
bottom: 0;
|
|
5540
5666
|
z-index: 60;
|
|
5541
5667
|
width: 390px;
|
|
5542
|
-
height:
|
|
5668
|
+
height: 196px;
|
|
5543
5669
|
background: #F4F3F3;
|
|
5544
5670
|
border: 1px solid #D4DDE7;
|
|
5545
5671
|
border-radius: 16px 16px 0 0;
|
|
@@ -5592,6 +5718,9 @@ line-height: 31px;
|
|
|
5592
5718
|
gap: 30px;
|
|
5593
5719
|
padding-top: 10px;
|
|
5594
5720
|
}
|
|
5721
|
+
.md3 .vdp-bottom-bar .fin-cond {
|
|
5722
|
+
margin-top: 4px;
|
|
5723
|
+
}
|
|
5595
5724
|
.md3 .bb-price-row .bb-price-cash-sup {
|
|
5596
5725
|
position: relative;
|
|
5597
5726
|
top: -15px;
|
|
@@ -6794,6 +6923,7 @@ line-height: 31px;
|
|
|
6794
6923
|
white-space: nowrap;
|
|
6795
6924
|
flex: none;
|
|
6796
6925
|
border-radius: 15px;
|
|
6926
|
+
opacity: 0.8;
|
|
6797
6927
|
}
|
|
6798
6928
|
|
|
6799
6929
|
|
|
@@ -6885,6 +7015,82 @@ color: #005CFF;
|
|
|
6885
7015
|
.md3 .fin-sheet-adjust .mslider + div {
|
|
6886
7016
|
display: none !important;
|
|
6887
7017
|
}
|
|
7018
|
+
/* Mit-Schlussrate / Rundum-Sorglos-Paket toggles + the CTA row — originally mobile-sheet-only,
|
|
7019
|
+
now also rendered in the desktop .fin-modal .hide-mobile breakdown, so unscoped from the
|
|
7020
|
+
mobile media query below (was silently unstyled at desktop widths otherwise). */
|
|
7021
|
+
.md3 .fin-toggle-final {
|
|
7022
|
+
padding-top: 22px;
|
|
7023
|
+
padding-bottom: 22px;
|
|
7024
|
+
border-top: 1px solid var(--fig-tile-border);
|
|
7025
|
+
}
|
|
7026
|
+
.md3 .fin-toggle-final .md-title-sm {
|
|
7027
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
7028
|
+
font-weight: 400;
|
|
7029
|
+
font-size: 15px;
|
|
7030
|
+
line-height: 17px;
|
|
7031
|
+
color: #1D2A38;
|
|
7032
|
+
}
|
|
7033
|
+
.md3 .fin-toggle-final .mswitch-track {
|
|
7034
|
+
width: 44px;
|
|
7035
|
+
height: 26px;
|
|
7036
|
+
border-radius: 13px;
|
|
7037
|
+
}
|
|
7038
|
+
.md3 .fin-toggle-final .mswitch input:checked + .mswitch-track {
|
|
7039
|
+
background: #005CFF;
|
|
7040
|
+
}
|
|
7041
|
+
.md3 .fin-toggle-final .mswitch-thumb {
|
|
7042
|
+
background: #FFFFFF;
|
|
7043
|
+
border-radius: 10px;
|
|
7044
|
+
width: 20px;
|
|
7045
|
+
height: 20px;
|
|
7046
|
+
}
|
|
7047
|
+
.md3 .fin-toggle-final .mswitch input:checked + .mswitch-track .mswitch-thumb {
|
|
7048
|
+
transform: translateX(18px);
|
|
7049
|
+
}
|
|
7050
|
+
.md3 .fin-toggle-addservices {
|
|
7051
|
+
padding-top: 28px;
|
|
7052
|
+
padding-bottom: 20px;
|
|
7053
|
+
border-top: 1px solid var(--fig-tile-border);
|
|
7054
|
+
}
|
|
7055
|
+
.md3 .fin-toggle-addservices.fin-toggle-addservices--leasing,
|
|
7056
|
+
.md3 .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7057
|
+
padding-top: 15px;
|
|
7058
|
+
padding-bottom: 15px;
|
|
7059
|
+
}
|
|
7060
|
+
.md3 .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7061
|
+
border-bottom: 1px solid var(--fig-tile-border);
|
|
7062
|
+
}
|
|
7063
|
+
.md3 .fin-toggle-addservices .md-title-sm {
|
|
7064
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
7065
|
+
font-weight: 400;
|
|
7066
|
+
font-size: 15px;
|
|
7067
|
+
line-height: 17px;
|
|
7068
|
+
color: #1D2A38;
|
|
7069
|
+
}
|
|
7070
|
+
.md3 .fin-toggle-addservices > div {
|
|
7071
|
+
gap: 13px !important;
|
|
7072
|
+
}
|
|
7073
|
+
.md3 .fin-toggle-addservices .num {
|
|
7074
|
+
font-family: Arial, Helvetica, sans-serif !important;
|
|
7075
|
+
font-weight: 400 !important;
|
|
7076
|
+
font-size: 15px !important;
|
|
7077
|
+
line-height: 17px !important;
|
|
7078
|
+
text-align: right;
|
|
7079
|
+
color: #1D2A38 !important;
|
|
7080
|
+
}
|
|
7081
|
+
.md3 .fin-sheet-cta-row {
|
|
7082
|
+
border-top: 1px solid var(--fig-tile-border);
|
|
7083
|
+
padding-top: 20px;
|
|
7084
|
+
padding-bottom: 15px;
|
|
7085
|
+
}
|
|
7086
|
+
.md3 .fin-sheet-cta-row.fin-sheet-cta-row--cash {
|
|
7087
|
+
border-top: 0;
|
|
7088
|
+
padding-bottom: 45px;
|
|
7089
|
+
}
|
|
7090
|
+
.md3 .fin-sheet-call-btn .lucide {
|
|
7091
|
+
width: 19px !important;
|
|
7092
|
+
height: 19px !important;
|
|
7093
|
+
}
|
|
6888
7094
|
|
|
6889
7095
|
@media (max-width: 600px) {
|
|
6890
7096
|
/* full-bleed gallery: edge-to-edge, app-like. Extra height (vs. the plain photo height)
|
|
@@ -7126,79 +7332,6 @@ color: #005CFF;
|
|
|
7126
7332
|
.md3 .fin-sheet-terms {
|
|
7127
7333
|
padding-bottom: 20px;
|
|
7128
7334
|
}
|
|
7129
|
-
.md3 .fin-toggle-final {
|
|
7130
|
-
padding-top: 22px;
|
|
7131
|
-
padding-bottom: 22px;
|
|
7132
|
-
border-top: 1px solid var(--fig-tile-border);
|
|
7133
|
-
}
|
|
7134
|
-
.md3 .fin-toggle-final .md-title-sm {
|
|
7135
|
-
font-family: Arial, Helvetica, sans-serif;
|
|
7136
|
-
font-weight: 400;
|
|
7137
|
-
font-size: 15px;
|
|
7138
|
-
line-height: 17px;
|
|
7139
|
-
color: #1D2A38;
|
|
7140
|
-
}
|
|
7141
|
-
.md3 .fin-toggle-final .mswitch-track {
|
|
7142
|
-
width: 44px;
|
|
7143
|
-
height: 26px;
|
|
7144
|
-
border-radius: 13px;
|
|
7145
|
-
}
|
|
7146
|
-
.md3 .fin-toggle-final .mswitch input:checked + .mswitch-track {
|
|
7147
|
-
background: #005CFF;
|
|
7148
|
-
}
|
|
7149
|
-
.md3 .fin-toggle-final .mswitch-thumb {
|
|
7150
|
-
background: #FFFFFF;
|
|
7151
|
-
border-radius: 10px;
|
|
7152
|
-
width: 20px;
|
|
7153
|
-
height: 20px;
|
|
7154
|
-
}
|
|
7155
|
-
.md3 .fin-toggle-final .mswitch input:checked + .mswitch-track .mswitch-thumb {
|
|
7156
|
-
transform: translateX(18px);
|
|
7157
|
-
}
|
|
7158
|
-
.md3 .fin-toggle-addservices {
|
|
7159
|
-
padding-top: 28px;
|
|
7160
|
-
padding-bottom: 20px;
|
|
7161
|
-
border-top: 1px solid var(--fig-tile-border);
|
|
7162
|
-
}
|
|
7163
|
-
.md3 .fin-toggle-addservices.fin-toggle-addservices--leasing,
|
|
7164
|
-
.md3 .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7165
|
-
padding-top: 15px;
|
|
7166
|
-
padding-bottom: 15px;
|
|
7167
|
-
}
|
|
7168
|
-
.md3 .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7169
|
-
border-bottom: 1px solid var(--fig-tile-border);
|
|
7170
|
-
}
|
|
7171
|
-
.md3 .fin-toggle-addservices .md-title-sm {
|
|
7172
|
-
font-family: Arial, Helvetica, sans-serif;
|
|
7173
|
-
font-weight: 400;
|
|
7174
|
-
font-size: 15px;
|
|
7175
|
-
line-height: 17px;
|
|
7176
|
-
color: #1D2A38;
|
|
7177
|
-
}
|
|
7178
|
-
.md3 .fin-toggle-addservices > div {
|
|
7179
|
-
gap: 13px !important;
|
|
7180
|
-
}
|
|
7181
|
-
.md3 .fin-toggle-addservices .num {
|
|
7182
|
-
font-family: Arial, Helvetica, sans-serif !important;
|
|
7183
|
-
font-weight: 400 !important;
|
|
7184
|
-
font-size: 15px !important;
|
|
7185
|
-
line-height: 17px !important;
|
|
7186
|
-
text-align: right;
|
|
7187
|
-
color: #1D2A38 !important;
|
|
7188
|
-
}
|
|
7189
|
-
.md3 .fin-sheet-cta-row {
|
|
7190
|
-
border-top: 1px solid var(--fig-tile-border);
|
|
7191
|
-
padding-top: 20px;
|
|
7192
|
-
padding-bottom: 15px;
|
|
7193
|
-
}
|
|
7194
|
-
.md3 .fin-sheet-cta-row.fin-sheet-cta-row--cash {
|
|
7195
|
-
border-top: 0;
|
|
7196
|
-
padding-bottom: 45px;
|
|
7197
|
-
}
|
|
7198
|
-
.md3 .fin-sheet-call-btn .lucide {
|
|
7199
|
-
width: 19px !important;
|
|
7200
|
-
height: 19px !important;
|
|
7201
|
-
}
|
|
7202
7335
|
/* sheet cash rows (Zahlung/Garantie/Lieferung) — Figma sizes (round 14: taller still) */
|
|
7203
7336
|
.md3 .fin-modal .calcrow {
|
|
7204
7337
|
padding: 20px 0;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import React, { useState } from 'react';
|
|
17
17
|
import { TranslationFunction } from '../types';
|
|
18
18
|
|
|
19
|
-
import { Button, Icon } from '../ui';
|
|
19
|
+
import { Button, Icon, Switch } from '../ui';
|
|
20
20
|
import { TIER_LABELS, TIER_KEYS, TIER_COLORS } from '../constants';
|
|
21
21
|
|
|
22
22
|
/** Payment mode segment keys. Mirrors the design's ['fin','leasing','cash']. */
|
|
@@ -184,6 +184,41 @@ export const CalcRow: React.FunctionComponent<CalcRowProps> = ({ label, last, ch
|
|
|
184
184
|
</div>
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
+
/** full-width label(+optional trailing price) / Switch row — Switch itself is inline-flex
|
|
188
|
+
* (track+label), so the "label ...... price switch" full-row layout can't be expressed via
|
|
189
|
+
* Switch's `label` prop (which renders track-then-label, not a full-width justified row). */
|
|
190
|
+
export const ToggleRow: React.FunctionComponent<{
|
|
191
|
+
label: string;
|
|
192
|
+
price?: string;
|
|
193
|
+
checked: boolean;
|
|
194
|
+
disabled?: boolean;
|
|
195
|
+
onChange: (checked: boolean) => void;
|
|
196
|
+
className?: string;
|
|
197
|
+
/** cash mode: the package is always included — shows a static "inklusive" note instead of the switch. */
|
|
198
|
+
staticNote?: string;
|
|
199
|
+
}> = ({ label, price, checked, disabled, onChange, className, staticNote }) => (
|
|
200
|
+
<div className={className} style={{ display: 'flex', alignItems: 'center', justifyContent: staticNote ? undefined : 'space-between', gap: 10 }}>
|
|
201
|
+
<span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
|
|
202
|
+
{label}
|
|
203
|
+
<Icon name="info" size={13} color="var(--md-on-surface-variant)" />
|
|
204
|
+
</span>
|
|
205
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginLeft: staticNote ? 'auto' : undefined }}>
|
|
206
|
+
{price && (
|
|
207
|
+
<span className="num" style={{ fontWeight: 700, fontSize: 13.5, marginRight: staticNote ? 'auto' : undefined }}>
|
|
208
|
+
{price}
|
|
209
|
+
</span>
|
|
210
|
+
)}
|
|
211
|
+
{staticNote ? (
|
|
212
|
+
<span style={{ fontFamily: 'Arial', fontWeight: 400, fontSize: 15, lineHeight: '17px', color: '#0E2238' }}>
|
|
213
|
+
{staticNote}
|
|
214
|
+
</span>
|
|
215
|
+
) : (
|
|
216
|
+
<Switch checked={checked} disabled={disabled} onChange={(e) => onChange(e.target.checked)} ariaLabel={label} />
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
|
|
187
222
|
export interface FinanceCalculatorProps {
|
|
188
223
|
/** active payment mode (controlled). */
|
|
189
224
|
mode: FinanceMode;
|
|
@@ -226,6 +261,19 @@ export interface FinanceCalculatorProps {
|
|
|
226
261
|
annualMileageStep?: number;
|
|
227
262
|
/** annual-mileage slider changed. */
|
|
228
263
|
onAnnualMileageChange?: (km: number) => void;
|
|
264
|
+
/** "Mit Schlussrate" toggle state (financing only; leasing has no balloon concept). Omit
|
|
265
|
+
* onToggleFinalInstallment to hide the row. */
|
|
266
|
+
withFinalInstallment?: boolean;
|
|
267
|
+
onToggleFinalInstallment?: (checked: boolean) => void;
|
|
268
|
+
/** "Rundum-Sorglos-Paket" toggle state (fin/leasing). Omit addServicesCost or
|
|
269
|
+
* onToggleAddServices to hide the row. */
|
|
270
|
+
withAddServices?: boolean;
|
|
271
|
+
addServicesCost?: number;
|
|
272
|
+
addServicesDisabled?: boolean;
|
|
273
|
+
onToggleAddServices?: (checked: boolean) => void;
|
|
274
|
+
/** Kaufen tab only — real carStore.isAvailable, drives the Verfügbarkeit row (replaces the
|
|
275
|
+
* static Lieferung row). Same source as VdpTrustRow's "Sofort verfügbar" badge. */
|
|
276
|
+
available?: boolean;
|
|
229
277
|
/** phone number shown on the secondary CTA (touch only). */
|
|
230
278
|
phone?: string;
|
|
231
279
|
/** primary CTA click (Angebot sichern). */
|
|
@@ -289,7 +337,7 @@ export const expanderBtnStyle: React.CSSProperties = {
|
|
|
289
337
|
display: 'flex',
|
|
290
338
|
alignItems: 'center',
|
|
291
339
|
justifyContent: 'space-between',
|
|
292
|
-
padding: '
|
|
340
|
+
padding: '0 0 2px',
|
|
293
341
|
color: 'var(--md-primary)',
|
|
294
342
|
fontWeight: 600,
|
|
295
343
|
fontSize: 14,
|
|
@@ -317,6 +365,13 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
317
365
|
annualMileageMax = 60000,
|
|
318
366
|
annualMileageStep = 5000,
|
|
319
367
|
onAnnualMileageChange,
|
|
368
|
+
withFinalInstallment,
|
|
369
|
+
onToggleFinalInstallment,
|
|
370
|
+
withAddServices,
|
|
371
|
+
addServicesCost,
|
|
372
|
+
addServicesDisabled,
|
|
373
|
+
onToggleAddServices,
|
|
374
|
+
available,
|
|
320
375
|
phone = '0800 40 30 182',
|
|
321
376
|
onSecure,
|
|
322
377
|
onCall,
|
|
@@ -332,6 +387,7 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
332
387
|
// whether the "UVP-Rate" footnote popover (fin/leasing) is open — same widget as CarCard's.
|
|
333
388
|
const [uvpTooltipOpen, setUvpTooltipOpen] = useState(false);
|
|
334
389
|
const isCash = mode === 'cash';
|
|
390
|
+
const isLease = mode === 'leasing';
|
|
335
391
|
const terms = termOptions && termOptions.length ? termOptions : range(termMin, termMax, termStep);
|
|
336
392
|
const showStrike = listPrice != null && listPrice > price;
|
|
337
393
|
// price-evaluation tier from the rating label (defaults to 'Guter Preis' tier when unmapped).
|
|
@@ -414,14 +470,20 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
414
470
|
</div>
|
|
415
471
|
|
|
416
472
|
{/* 2-col grid: rate|pill (row 1) + condLine|bar (row 2) — fixed right column prevents wrapping */}
|
|
417
|
-
<div className=
|
|
473
|
+
<div className={'fin-price-grid' + (isCash ? ' fin-price-grid--cash' : '')}>
|
|
418
474
|
{/* col 1 row 1: hero rate — Figma: "329 €" all-big, then a small stacked column with
|
|
419
475
|
the struck old rate ABOVE "/mtl." (round 11; was a single baseline row) */}
|
|
420
476
|
<div className="fin-rate-main">
|
|
421
477
|
{/* mobile Kaufen (Figma round 14): struck list price ABOVE the price, right-aligned,
|
|
422
478
|
tight to the figure — replaces the Festpreis + UVP lines, which stay desktop-only */}
|
|
423
479
|
{isCash && showStrike && (
|
|
424
|
-
<div
|
|
480
|
+
<div
|
|
481
|
+
className="show-mobile"
|
|
482
|
+
style={{ justifyContent: 'flex-end', alignItems: 'baseline', gap: 4, lineHeight: 1, marginTop: 7 }}
|
|
483
|
+
role="button"
|
|
484
|
+
tabIndex={0}
|
|
485
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
486
|
+
>
|
|
425
487
|
<span style={{ fontSize: 13, color: 'var(--md-on-surface-variant)' }}>{t('autobahn:finance.uvp')}</span>
|
|
426
488
|
<span
|
|
427
489
|
className={`num pp-hero-old${
|
|
@@ -465,6 +527,50 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
465
527
|
</span>
|
|
466
528
|
</div>
|
|
467
529
|
)}
|
|
530
|
+
{/* desktop: same struck list price ABOVE the rate, same fin-rate-old-list styling +
|
|
531
|
+
clickable UVP badge/tooltip as the mobile sheet's own Kaufen block. */}
|
|
532
|
+
{isCash && showStrike && (
|
|
533
|
+
<div
|
|
534
|
+
className="hide-mobile"
|
|
535
|
+
style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0, cursor: 'pointer' }}
|
|
536
|
+
role="button"
|
|
537
|
+
tabIndex={0}
|
|
538
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
539
|
+
>
|
|
540
|
+
<s className="num fin-rate-old fin-rate-old-list">
|
|
541
|
+
{eur(listPrice as number)} €
|
|
542
|
+
<span
|
|
543
|
+
className="pp-eyebrow-badge"
|
|
544
|
+
role="button"
|
|
545
|
+
tabIndex={0}
|
|
546
|
+
onClick={(e) => {
|
|
547
|
+
e.stopPropagation();
|
|
548
|
+
setUvpTooltipOpen((v) => !v);
|
|
549
|
+
}}
|
|
550
|
+
>
|
|
551
|
+
2
|
|
552
|
+
</span>
|
|
553
|
+
{uvpTooltipOpen && (
|
|
554
|
+
<div className="pp-uvp-tooltip" onClick={(e) => e.stopPropagation()}>
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
className="pp-uvp-tooltip-close"
|
|
558
|
+
aria-label={t('autobahn:modal.close')}
|
|
559
|
+
onClick={(e) => {
|
|
560
|
+
e.stopPropagation();
|
|
561
|
+
setUvpTooltipOpen(false);
|
|
562
|
+
}}
|
|
563
|
+
>
|
|
564
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
565
|
+
<path d="M5 5L15 15M15 5L5 15" stroke="white" strokeWidth={2.4} strokeLinecap="round" />
|
|
566
|
+
</svg>
|
|
567
|
+
</button>
|
|
568
|
+
<div className="pp-uvp-tooltip-body">{t('autobahn:card.price.uvpListTooltipBody')}</div>
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
</s>
|
|
572
|
+
</div>
|
|
573
|
+
)}
|
|
468
574
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
|
469
575
|
{isCash ? (
|
|
470
576
|
<>
|
|
@@ -486,11 +592,10 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
486
592
|
</span>
|
|
487
593
|
<span
|
|
488
594
|
className="num hide-mobile"
|
|
489
|
-
style={{ fontSize:
|
|
595
|
+
style={{ fontSize: 45, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-2px' }}
|
|
490
596
|
>
|
|
491
|
-
{eur(price)}
|
|
597
|
+
{eur(price)} €<sup>1</sup>
|
|
492
598
|
</span>
|
|
493
|
-
<span className="md-body-sm hide-mobile" style={{ fontWeight: 600 }}>€</span>
|
|
494
599
|
</>
|
|
495
600
|
) : (
|
|
496
601
|
<>
|
|
@@ -511,7 +616,12 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
511
616
|
<span className="fin-rate-unit">
|
|
512
617
|
{/* struck old rate (REAL oldMonthlyInstallment; SRP strikes the same source) */}
|
|
513
618
|
{oldMonthly != null && (
|
|
514
|
-
|
|
619
|
+
<span
|
|
620
|
+
style={{ display: 'flex', flexDirection: 'column', cursor: 'pointer' }}
|
|
621
|
+
role="button"
|
|
622
|
+
tabIndex={0}
|
|
623
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
624
|
+
>
|
|
515
625
|
<span className="pp-eyebrow">
|
|
516
626
|
{t('autobahn:card.price.uvpRateLabel')}
|
|
517
627
|
<span
|
|
@@ -561,7 +671,7 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
561
671
|
>
|
|
562
672
|
{eur(Math.round(oldMonthly))} €
|
|
563
673
|
</s>
|
|
564
|
-
|
|
674
|
+
</span>
|
|
565
675
|
)}
|
|
566
676
|
<span className="fin-rate-example-suffix">
|
|
567
677
|
{t('autobahn:finance.perMonth')}
|
|
@@ -571,11 +681,6 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
571
681
|
)}
|
|
572
682
|
</div>
|
|
573
683
|
{isCash && <div className="md-label-sm hide-mobile">{t('autobahn:finance.fixedPrice')}</div>}
|
|
574
|
-
{isCash && showStrike && (
|
|
575
|
-
<div className="fin-kauf hide-mobile">
|
|
576
|
-
{t('autobahn:finance.uvp')} <s>{eur(listPrice as number)} €</s>
|
|
577
|
-
</div>
|
|
578
|
-
)}
|
|
579
684
|
</div>
|
|
580
685
|
{/* col 2: pill + eval bar grouped together (display:contents on desktop — no-op,
|
|
581
686
|
preserves the original rate|pill / cond|bar auto-placement; mobile turns this into
|
|
@@ -634,14 +739,18 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
634
739
|
(isCash ? (
|
|
635
740
|
<div style={{ marginTop: 4 }}>
|
|
636
741
|
<CalcRow label={t('autobahn:finance.paymentLabel')}>{t('autobahn:finance.paymentValue')}</CalcRow>
|
|
637
|
-
<CalcRow label={t('autobahn:finance.warrantyLabel')}
|
|
638
|
-
|
|
639
|
-
{t('autobahn:finance.deliveryValue')}
|
|
742
|
+
<CalcRow label={t('autobahn:finance.warrantyLabel')} last={!available}>
|
|
743
|
+
{t('autobahn:finance.warrantyValue')}
|
|
640
744
|
</CalcRow>
|
|
745
|
+
{available && (
|
|
746
|
+
<CalcRow label={t('autobahn:finance.availabilityLabel')} last>
|
|
747
|
+
{t('autobahn:finance.availabilityValue')}
|
|
748
|
+
</CalcRow>
|
|
749
|
+
)}
|
|
641
750
|
</div>
|
|
642
751
|
) : (
|
|
643
|
-
<div style={{ display: 'flex', flexDirection: 'column',
|
|
644
|
-
<div>
|
|
752
|
+
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 8 }}>
|
|
753
|
+
<div style={{ marginBottom: 15 }}>
|
|
645
754
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
|
646
755
|
<span className="md-title-sm">{t('autobahn:finance.downPaymentSliderLabel')}</span>
|
|
647
756
|
<span className="num" style={{ fontWeight: 700 }}>
|
|
@@ -658,7 +767,7 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
658
767
|
/>
|
|
659
768
|
</div>
|
|
660
769
|
{annualMileage != null && onAnnualMileageChange && (
|
|
661
|
-
<div>
|
|
770
|
+
<div style={{ marginBottom: 15 }}>
|
|
662
771
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
|
663
772
|
<span className="md-title-sm">{t('autobahn:finance.annualMileageLabel')}</span>
|
|
664
773
|
<span className="num" style={{ fontWeight: 700 }}>
|
|
@@ -675,19 +784,32 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
|
|
|
675
784
|
/>
|
|
676
785
|
</div>
|
|
677
786
|
)}
|
|
678
|
-
<div>
|
|
787
|
+
<div style={{ marginBottom: 15 }}>
|
|
679
788
|
<div className="md-title-sm" style={{ marginBottom: 8 }}>
|
|
680
789
|
{t('autobahn:finance.termSelection')}
|
|
681
790
|
</div>
|
|
682
|
-
<TermChips options={terms} value={term} onChange={onTermChange} t={t} />
|
|
683
|
-
</div>
|
|
684
|
-
<div>
|
|
685
|
-
<CalcRow label={t('autobahn:finance.purchasePrice')}>{eur(price)} €</CalcRow>
|
|
686
|
-
<CalcRow label={t('autobahn:finance.warrantyLabel')}>{t('autobahn:finance.warrantyValue')}</CalcRow>
|
|
687
|
-
<CalcRow label={t('autobahn:finance.deliveryLabel')} last>
|
|
688
|
-
{t('autobahn:finance.deliveryValue')}
|
|
689
|
-
</CalcRow>
|
|
791
|
+
<TermChips options={terms.filter((v) => v !== 12 && v !== 96)} value={term} onChange={onTermChange} t={t} />
|
|
690
792
|
</div>
|
|
793
|
+
{/* same Mit-Schlussrate / Rundum-Sorglos-Paket toggles as the mobile sheet / modal
|
|
794
|
+
desktop block — replaces the static Kaufpreis/Garantie/Lieferung rows. */}
|
|
795
|
+
{!isLease && onToggleFinalInstallment && (
|
|
796
|
+
<ToggleRow
|
|
797
|
+
className="fin-toggle-final"
|
|
798
|
+
label={t('autobahn:finance.finalInstallmentToggle')}
|
|
799
|
+
checked={!!withFinalInstallment}
|
|
800
|
+
onChange={onToggleFinalInstallment}
|
|
801
|
+
/>
|
|
802
|
+
)}
|
|
803
|
+
{addServicesCost != null && onToggleAddServices && (
|
|
804
|
+
<ToggleRow
|
|
805
|
+
className={'fin-toggle-addservices' + (isLease ? ' fin-toggle-addservices--leasing' : '')}
|
|
806
|
+
label={t('autobahn:finance.addServicesToggle')}
|
|
807
|
+
price={t('autobahn:finance.addServicesPerMonth', { value: eur(Math.round(addServicesCost)) })}
|
|
808
|
+
checked={!!withAddServices}
|
|
809
|
+
disabled={addServicesDisabled}
|
|
810
|
+
onChange={onToggleAddServices}
|
|
811
|
+
/>
|
|
812
|
+
)}
|
|
691
813
|
</div>
|
|
692
814
|
))}
|
|
693
815
|
</div>
|
package/vdp/VdpModules.tsx
CHANGED
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
import React, { useState } from 'react';
|
|
25
25
|
import { TranslationFunction } from '../types';
|
|
26
26
|
|
|
27
|
-
import { Button, Dropdown, DropdownOption, Icon, Spec, StatChip, Stars
|
|
27
|
+
import { Button, Dropdown, DropdownOption, Icon, Spec, StatChip, Stars } from '../ui';
|
|
28
28
|
import { CO2_CLASSES, CO2_COLORS, TIER_LABELS, TIER_KEYS, TIER_COLORS, TIER_BACKGROUNDS } from '../constants';
|
|
29
29
|
import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
|
|
30
|
-
import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips } from './FinanceCalculator';
|
|
30
|
+
import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips, ToggleRow } from './FinanceCalculator';
|
|
31
31
|
import { formatEur as eur, formatPct as pct } from '../utils/financeFormat';
|
|
32
32
|
|
|
33
33
|
/** Figma "check with open arc" glyph (modern lucide circle-check-big; the bundled lucide
|
|
@@ -1464,6 +1464,14 @@ export interface VdpBottomBarProps {
|
|
|
1464
1464
|
onCall?: () => void;
|
|
1465
1465
|
/** opens the "Rate anpassen" sheet; when omitted the button is not rendered. */
|
|
1466
1466
|
onOpenAdjust?: () => void;
|
|
1467
|
+
/** current down payment (fin) / Sonderzahlung (leasing) in EUR — drives the .fin-cond line. */
|
|
1468
|
+
down?: number;
|
|
1469
|
+
/** current term in months — drives the .fin-cond line. */
|
|
1470
|
+
term?: number;
|
|
1471
|
+
/** leasing annual mileage (km/yr) — leasing-only part of the .fin-cond line. */
|
|
1472
|
+
annualMileage?: number;
|
|
1473
|
+
/** opens the Finanzierungs-/Leasingdetails popup (expanded); when omitted the link is hidden. */
|
|
1474
|
+
onShowDetails?: () => void;
|
|
1467
1475
|
/** i18n function (drilled from the page). */
|
|
1468
1476
|
t: TranslationFunction;
|
|
1469
1477
|
}
|
|
@@ -1482,6 +1490,10 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1482
1490
|
onSecure,
|
|
1483
1491
|
onCall,
|
|
1484
1492
|
onOpenAdjust,
|
|
1493
|
+
down,
|
|
1494
|
+
term,
|
|
1495
|
+
annualMileage,
|
|
1496
|
+
onShowDetails,
|
|
1485
1497
|
t,
|
|
1486
1498
|
}) => {
|
|
1487
1499
|
const [uvpTooltipOpen, setUvpTooltipOpen] = useState(false);
|
|
@@ -1495,6 +1507,21 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1495
1507
|
const evalTierLabel = isGoodTier ? t('autobahn:tier.top') : t('autobahn:tier.' + TIER_KEYS[evalTier]);
|
|
1496
1508
|
const evalPillColor = isGoodTier ? TIER_COLORS[0] : TIER_COLORS[evalTier];
|
|
1497
1509
|
const evalPillBackground = isGoodTier ? TIER_BACKGROUNDS[0] : TIER_BACKGROUNDS[evalTier];
|
|
1510
|
+
// condition summary — same wording/order as FinanceCalculator's own .fin-cond (financing
|
|
1511
|
+
// "0 € Anzahlung · 60 Monate", leasing "0 € Sonderzahlung · 36 Mon. · 10.000 km/Jahr").
|
|
1512
|
+
const condParts: string[] = [];
|
|
1513
|
+
if (mode === 'leasing') {
|
|
1514
|
+
condParts.push(
|
|
1515
|
+
...t('autobahn:card.price.leaseTerms', {
|
|
1516
|
+
firstInstallment: eur(down || 0),
|
|
1517
|
+
months: term,
|
|
1518
|
+
mileage: annualMileage != null ? eur(annualMileage) : '',
|
|
1519
|
+
}).split(' · '),
|
|
1520
|
+
);
|
|
1521
|
+
} else if (!isCash) {
|
|
1522
|
+
condParts.push(t('autobahn:finance.downPaymentLabel', { value: eur(down || 0) }));
|
|
1523
|
+
condParts.push(t('autobahn:finance.monthsLabel', { months: term, count: term }));
|
|
1524
|
+
}
|
|
1498
1525
|
const priceStyle: React.CSSProperties = isCash
|
|
1499
1526
|
? {
|
|
1500
1527
|
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
@@ -1665,6 +1692,24 @@ export const VdpBottomBar: React.FunctionComponent<VdpBottomBarProps> = ({
|
|
|
1665
1692
|
</div>
|
|
1666
1693
|
)}
|
|
1667
1694
|
</div>
|
|
1695
|
+
{!isCash && (
|
|
1696
|
+
<div className="fin-cond">
|
|
1697
|
+
{condParts.map((part, i) => (
|
|
1698
|
+
<React.Fragment key={i}>
|
|
1699
|
+
{i > 0 && <span className="cc-terms-dot"> · </span>}
|
|
1700
|
+
{part}
|
|
1701
|
+
</React.Fragment>
|
|
1702
|
+
))}
|
|
1703
|
+
{onShowDetails && (
|
|
1704
|
+
<>
|
|
1705
|
+
<span className="cc-terms-dot"> · </span>
|
|
1706
|
+
<button type="button" className="cc-terms-link" onClick={onShowDetails}>
|
|
1707
|
+
{mode === 'leasing' ? t('autobahn:card.price.detailsLink') : t('autobahn:finance.financingDetails')}
|
|
1708
|
+
</button>
|
|
1709
|
+
</>
|
|
1710
|
+
)}
|
|
1711
|
+
</div>
|
|
1712
|
+
)}
|
|
1668
1713
|
<div className="bb-cta-row">
|
|
1669
1714
|
<Button
|
|
1670
1715
|
variant="filled"
|
|
@@ -1776,40 +1821,8 @@ export const InspectionBlock: React.FunctionComponent<InspectionBlockProps> = ({
|
|
|
1776
1821
|
// Finanzierungs-/Leasingdetails popup (desktop modal / mobile bottom-sheet)
|
|
1777
1822
|
// ---------------------------------------------------------------------------
|
|
1778
1823
|
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
* Switch's `label` prop (which renders track-then-label, not a full-width justified row). */
|
|
1782
|
-
const ToggleRow: React.FunctionComponent<{
|
|
1783
|
-
label: string;
|
|
1784
|
-
price?: string;
|
|
1785
|
-
checked: boolean;
|
|
1786
|
-
disabled?: boolean;
|
|
1787
|
-
onChange: (checked: boolean) => void;
|
|
1788
|
-
className?: string;
|
|
1789
|
-
/** cash mode: the package is always included — shows a static "inklusive" note instead of the switch. */
|
|
1790
|
-
staticNote?: string;
|
|
1791
|
-
}> = ({ label, price, checked, disabled, onChange, className, staticNote }) => (
|
|
1792
|
-
<div className={className} style={{ display: 'flex', alignItems: 'center', justifyContent: staticNote ? undefined : 'space-between', gap: 10 }}>
|
|
1793
|
-
<span className="md-title-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
|
|
1794
|
-
{label}
|
|
1795
|
-
<Icon name="info" size={13} color="var(--md-on-surface-variant)" />
|
|
1796
|
-
</span>
|
|
1797
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginLeft: staticNote ? 'auto' : undefined }}>
|
|
1798
|
-
{price && (
|
|
1799
|
-
<span className="num" style={{ fontWeight: 700, fontSize: 13.5, marginRight: staticNote ? 'auto' : undefined }}>
|
|
1800
|
-
{price}
|
|
1801
|
-
</span>
|
|
1802
|
-
)}
|
|
1803
|
-
{staticNote ? (
|
|
1804
|
-
<span style={{ fontFamily: 'Arial', fontWeight: 400, fontSize: 15, lineHeight: '17px', color: '#0E2238' }}>
|
|
1805
|
-
{staticNote}
|
|
1806
|
-
</span>
|
|
1807
|
-
) : (
|
|
1808
|
-
<Switch checked={checked} disabled={disabled} onChange={(e) => onChange(e.target.checked)} ariaLabel={label} />
|
|
1809
|
-
)}
|
|
1810
|
-
</div>
|
|
1811
|
-
</div>
|
|
1812
|
-
);
|
|
1824
|
+
// ToggleRow lives in FinanceCalculator.tsx (imported above) — shared with the desktop
|
|
1825
|
+
// .fin-calc-card's own "Rate anpassen" panel, which needs the same toggles.
|
|
1813
1826
|
|
|
1814
1827
|
export interface FinanceDetailsModalProps {
|
|
1815
1828
|
/** open flag (controlled by the page). */
|
|
@@ -1943,10 +1956,10 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1943
1956
|
// modal's mobile "Rate anpassen" sheet renders its own separate copy of that hero row, so it
|
|
1944
1957
|
// needs its own tooltip-open state (not shared with FinanceCalculator's instance).
|
|
1945
1958
|
const [uvpTooltipOpen, setUvpTooltipOpen] = React.useState(false);
|
|
1946
|
-
// opened directly via the "Finanzierungsdetails" link (initialShowBreakdown) —
|
|
1947
|
-
//
|
|
1948
|
-
// the top
|
|
1949
|
-
//
|
|
1959
|
+
// opened directly via the "Finanzierungsdetails" link (initialShowBreakdown) — on mobile the
|
|
1960
|
+
// breakdown is still gated behind its own toggle, so scroll the sheet down to reveal it instead
|
|
1961
|
+
// of leaving the user at the top. Desktop always shows the breakdown inline now (no toggle), so
|
|
1962
|
+
// it's excluded here — scrolling straight past the price/sliders/toggles on open was jarring.
|
|
1950
1963
|
const desktopBreakdownRef = React.useRef<HTMLDivElement | null>(null);
|
|
1951
1964
|
const mobileBreakdownRef = React.useRef<HTMLDivElement | null>(null);
|
|
1952
1965
|
React.useEffect(() => {
|
|
@@ -1954,8 +1967,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
1954
1967
|
setShowBreakdown(!!initialShowBreakdown);
|
|
1955
1968
|
if (!initialShowBreakdown) return;
|
|
1956
1969
|
const timer = window.setTimeout(() => {
|
|
1957
|
-
const target =
|
|
1958
|
-
target
|
|
1970
|
+
const target = mobileBreakdownRef.current;
|
|
1971
|
+
if (target && target.offsetParent !== null) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1959
1972
|
}, 50);
|
|
1960
1973
|
return () => window.clearTimeout(timer);
|
|
1961
1974
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -2176,7 +2189,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2176
2189
|
</button>
|
|
2177
2190
|
</div>
|
|
2178
2191
|
<div className="fin-modal-body">
|
|
2179
|
-
{/* ============================== DESKTOP (≥601px) —
|
|
2192
|
+
{/* ============================== DESKTOP (≥601px) — always-expanded breakdown, no tabs
|
|
2193
|
+
(the desktop .fin-calc-card owns its own tabs outside this modal) ============================== */}
|
|
2180
2194
|
<div className="hide-mobile">
|
|
2181
2195
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 14 }}>
|
|
2182
2196
|
<span className="num" style={{ fontSize: 34, fontWeight: 800, color: 'var(--md-primary)', letterSpacing: '-1px' }}>
|
|
@@ -2225,14 +2239,303 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2225
2239
|
<div className="md-title-sm" style={{ marginBottom: 8 }}>
|
|
2226
2240
|
{t('autobahn:finance.termSelection')}
|
|
2227
2241
|
</div>
|
|
2228
|
-
<TermChips
|
|
2242
|
+
<TermChips
|
|
2243
|
+
options={termVals.filter((v) => v !== 12 && v !== 96)}
|
|
2244
|
+
value={term}
|
|
2245
|
+
onChange={onTermChange as (n: number) => void}
|
|
2246
|
+
t={t}
|
|
2247
|
+
/>
|
|
2229
2248
|
</div>
|
|
2230
2249
|
</div>
|
|
2231
2250
|
)}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2251
|
+
{/* same Mit-Schlussrate / Rundum-Sorglos-Paket toggles as the mobile sheet — desktop
|
|
2252
|
+
had neither before, silently dropping two controls the mobile view already had. */}
|
|
2253
|
+
{!isCash && !isLease && onToggleFinalInstallment && (
|
|
2254
|
+
<ToggleRow
|
|
2255
|
+
className="fin-toggle-final"
|
|
2256
|
+
label={t('autobahn:finance.finalInstallmentToggle')}
|
|
2257
|
+
checked={!!withFinalInstallment}
|
|
2258
|
+
onChange={onToggleFinalInstallment}
|
|
2259
|
+
/>
|
|
2260
|
+
)}
|
|
2261
|
+
{!isCash && addServicesCost != null && onToggleAddServices && (
|
|
2262
|
+
<ToggleRow
|
|
2263
|
+
className={'fin-toggle-addservices' + (isLease ? ' fin-toggle-addservices--leasing' : '')}
|
|
2264
|
+
label={t('autobahn:finance.addServicesToggle')}
|
|
2265
|
+
price={t('autobahn:finance.addServicesPerMonth', { value: eur(Math.round(addServicesCost)) })}
|
|
2266
|
+
checked={!!withAddServices}
|
|
2267
|
+
disabled={addServicesDisabled}
|
|
2268
|
+
onChange={onToggleAddServices}
|
|
2269
|
+
/>
|
|
2270
|
+
)}
|
|
2271
|
+
<div ref={desktopBreakdownRef} style={!isCash ? { marginTop: 18 } : undefined}>
|
|
2272
|
+
{!isCash && (
|
|
2273
|
+
<>
|
|
2274
|
+
<p
|
|
2275
|
+
style={{
|
|
2276
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2277
|
+
fontWeight: 700,
|
|
2278
|
+
fontSize: 16,
|
|
2279
|
+
lineHeight: '18px',
|
|
2280
|
+
color: '#0E2238',
|
|
2281
|
+
margin: 0,
|
|
2282
|
+
marginBottom: 5,
|
|
2283
|
+
}}
|
|
2284
|
+
>
|
|
2285
|
+
{t('autobahn:modal.representativeExample')}
|
|
2286
|
+
</p>
|
|
2287
|
+
<p
|
|
2288
|
+
style={{
|
|
2289
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2290
|
+
fontWeight: 700,
|
|
2291
|
+
fontSize: 9,
|
|
2292
|
+
lineHeight: '12px',
|
|
2293
|
+
color: '#5B6B7F',
|
|
2294
|
+
marginBottom: 5,
|
|
2295
|
+
}}
|
|
2296
|
+
>
|
|
2297
|
+
{t('autobahn:modal.vatDisclaimer')}
|
|
2298
|
+
</p>
|
|
2299
|
+
</>
|
|
2300
|
+
)}
|
|
2301
|
+
{breakdownRows}
|
|
2302
|
+
</div>
|
|
2303
|
+
{isCash ? (
|
|
2304
|
+
<p className="md-label-sm" style={{ marginTop: 16, lineHeight: 1.55 }}>
|
|
2305
|
+
{isLease ? t('autobahn:modal.legalLeasing') : t('autobahn:modal.legalFinancing')}
|
|
2306
|
+
</p>
|
|
2307
|
+
) : (
|
|
2308
|
+
<>
|
|
2309
|
+
{/* Darlehensgeber disclosure box — same content as the mobile "Finanzierungs-Details"
|
|
2310
|
+
expander, just always visible on desktop instead of gated behind a toggle. */}
|
|
2311
|
+
<div
|
|
2312
|
+
style={{
|
|
2313
|
+
display: 'flex',
|
|
2314
|
+
alignItems: 'flex-start',
|
|
2315
|
+
gap: 10,
|
|
2316
|
+
background: '#FAFBFD',
|
|
2317
|
+
border: '1px solid #D4DDE7',
|
|
2318
|
+
borderRadius: 6,
|
|
2319
|
+
padding: 10,
|
|
2320
|
+
marginTop: 10,
|
|
2321
|
+
marginBottom: 10,
|
|
2322
|
+
}}
|
|
2323
|
+
>
|
|
2324
|
+
<svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ flex: 'none' }}>
|
|
2325
|
+
<path d="M2.7002 9.00001L13.5002 3.60001L24.3002 9.00001H2.7002Z" stroke="#0E2238" strokeWidth="1.6" strokeLinejoin="round" />
|
|
2326
|
+
<path d="M4.49961 10.8H22.4996M5.39961 21.6H21.5996M3.59961 24.3H23.3996" stroke="#0E2238" strokeWidth="1.6" strokeLinecap="round" />
|
|
2327
|
+
<path d="M7.2002 10.8V21.6M11.7002 10.8V21.6M15.3002 10.8V21.6M19.8002 10.8V21.6" stroke="#0E2238" strokeWidth="1.6" />
|
|
2328
|
+
</svg>
|
|
2329
|
+
<div style={{ width: '100%' }}>
|
|
2330
|
+
<p
|
|
2331
|
+
style={{
|
|
2332
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2333
|
+
fontWeight: 700,
|
|
2334
|
+
fontSize: 14,
|
|
2335
|
+
lineHeight: '13px',
|
|
2336
|
+
color: '#0E2238',
|
|
2337
|
+
margin: 0,
|
|
2338
|
+
marginBottom: 3,
|
|
2339
|
+
}}
|
|
2340
|
+
>
|
|
2341
|
+
{isLease ? t('autobahn:modal.leaseLenderLabel') : t('autobahn:modal.lenderLabel')}
|
|
2342
|
+
</p>
|
|
2343
|
+
<p
|
|
2344
|
+
style={{
|
|
2345
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2346
|
+
fontWeight: 400,
|
|
2347
|
+
fontSize: 12,
|
|
2348
|
+
lineHeight: '13px',
|
|
2349
|
+
color: '#0E2238',
|
|
2350
|
+
margin: 0,
|
|
2351
|
+
}}
|
|
2352
|
+
>
|
|
2353
|
+
{isLease ? t('autobahn:modal.leaseLenderName') : t('autobahn:modal.lenderName')}
|
|
2354
|
+
</p>
|
|
2355
|
+
{isLease && (
|
|
2356
|
+
<>
|
|
2357
|
+
<div style={{ height: 1, background: '#D6DEE8', margin: '8px 0' }} />
|
|
2358
|
+
<p
|
|
2359
|
+
style={{
|
|
2360
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2361
|
+
fontWeight: 700,
|
|
2362
|
+
fontSize: 14,
|
|
2363
|
+
lineHeight: '13px',
|
|
2364
|
+
color: '#0E2238',
|
|
2365
|
+
margin: 0,
|
|
2366
|
+
marginBottom: 3,
|
|
2367
|
+
}}
|
|
2368
|
+
>
|
|
2369
|
+
{t('autobahn:modal.contractTypeLabel')}
|
|
2370
|
+
</p>
|
|
2371
|
+
<p
|
|
2372
|
+
style={{
|
|
2373
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2374
|
+
fontWeight: 400,
|
|
2375
|
+
fontSize: 12,
|
|
2376
|
+
lineHeight: '13px',
|
|
2377
|
+
color: '#0E2238',
|
|
2378
|
+
margin: 0,
|
|
2379
|
+
}}
|
|
2380
|
+
>
|
|
2381
|
+
{t('autobahn:modal.contractTypeValue')}
|
|
2382
|
+
</p>
|
|
2383
|
+
</>
|
|
2384
|
+
)}
|
|
2385
|
+
</div>
|
|
2386
|
+
</div>
|
|
2387
|
+
{isLease && hasAddServicesRow && (
|
|
2388
|
+
<div
|
|
2389
|
+
style={{
|
|
2390
|
+
display: 'flex',
|
|
2391
|
+
alignItems: 'flex-start',
|
|
2392
|
+
gap: 10,
|
|
2393
|
+
background: '#F7FAFE',
|
|
2394
|
+
border: '0.5px solid #D6DEE8',
|
|
2395
|
+
borderRadius: 5,
|
|
2396
|
+
padding: '7px 8px',
|
|
2397
|
+
marginBottom: 10,
|
|
2398
|
+
}}
|
|
2399
|
+
>
|
|
2400
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ flex: 'none', marginTop: 2 }}>
|
|
2401
|
+
<circle cx="12" cy="12" r="10" stroke="#0E2238" strokeWidth="1.6" />
|
|
2402
|
+
<path d="M12 16v-4" stroke="#0E2238" strokeWidth="1.6" strokeLinecap="round" />
|
|
2403
|
+
<path d="M12 8h.01" stroke="#0E2238" strokeWidth="1.6" strokeLinecap="round" />
|
|
2404
|
+
</svg>
|
|
2405
|
+
<p
|
|
2406
|
+
style={{
|
|
2407
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2408
|
+
fontWeight: 400,
|
|
2409
|
+
fontSize: 10,
|
|
2410
|
+
lineHeight: '10px',
|
|
2411
|
+
textAlign: 'justify',
|
|
2412
|
+
color: '#0E2238',
|
|
2413
|
+
margin: 0,
|
|
2414
|
+
}}
|
|
2415
|
+
>
|
|
2416
|
+
{t('autobahn:modal.packageMandatoryNotice', { amount: eur(addServicesCostRaw ?? 0) })}
|
|
2417
|
+
</p>
|
|
2418
|
+
</div>
|
|
2419
|
+
)}
|
|
2420
|
+
<p
|
|
2421
|
+
style={{
|
|
2422
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2423
|
+
fontWeight: 700,
|
|
2424
|
+
fontSize: 13.3,
|
|
2425
|
+
lineHeight: '17px',
|
|
2426
|
+
color: '#0E2238',
|
|
2427
|
+
margin: 0,
|
|
2428
|
+
marginBottom: 7,
|
|
2429
|
+
}}
|
|
2430
|
+
>
|
|
2431
|
+
{t('autobahn:modal.legalNoticesTitle')}
|
|
2432
|
+
</p>
|
|
2433
|
+
{isLease ? (
|
|
2434
|
+
<>
|
|
2435
|
+
<p style={{ fontFamily: 'Arial, Helvetica, sans-serif', fontWeight: 400, fontSize: 12, lineHeight: '12px', color: '#0E2238', margin: 0, marginBottom: 5 }}>
|
|
2436
|
+
{t('autobahn:modal.legalFootnoteAcquisitionPrice')}
|
|
2437
|
+
</p>
|
|
2438
|
+
<p style={{ fontFamily: 'Arial, Helvetica, sans-serif', fontWeight: 400, fontSize: 12, lineHeight: '12px', color: '#0E2238', margin: 0, marginBottom: 10 }}>
|
|
2439
|
+
{t('autobahn:modal.legalFootnoteListPriceLease')}
|
|
2440
|
+
</p>
|
|
2441
|
+
<p
|
|
2442
|
+
style={{
|
|
2443
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2444
|
+
fontWeight: 400,
|
|
2445
|
+
fontSize: 12,
|
|
2446
|
+
lineHeight: '12px',
|
|
2447
|
+
color: '#0E2238',
|
|
2448
|
+
margin: 0,
|
|
2449
|
+
paddingBottom: 10,
|
|
2450
|
+
}}
|
|
2451
|
+
>
|
|
2452
|
+
{t('autobahn:modal.closingDisclaimerLine1')}
|
|
2453
|
+
<br />
|
|
2454
|
+
{t('autobahn:modal.closingDisclaimerLine2')}
|
|
2455
|
+
</p>
|
|
2456
|
+
</>
|
|
2457
|
+
) : (
|
|
2458
|
+
<>
|
|
2459
|
+
<ul style={{ margin: 0, marginBottom: 3, paddingLeft: 16, display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
2460
|
+
{[
|
|
2461
|
+
t('autobahn:modal.legalNoticeCreditworthiness'),
|
|
2462
|
+
t('autobahn:modal.legalNoticeExample'),
|
|
2463
|
+
t('autobahn:modal.legalNoticeTwoThirds'),
|
|
2464
|
+
t('autobahn:modal.legalNoticeVat'),
|
|
2465
|
+
t('autobahn:modal.legalNoticeAddServices'),
|
|
2466
|
+
].map((notice) => (
|
|
2467
|
+
<li
|
|
2468
|
+
key={notice}
|
|
2469
|
+
style={{
|
|
2470
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2471
|
+
fontWeight: 400,
|
|
2472
|
+
fontSize: 12,
|
|
2473
|
+
lineHeight: '12px',
|
|
2474
|
+
color: '#0E2238',
|
|
2475
|
+
}}
|
|
2476
|
+
>
|
|
2477
|
+
{notice}
|
|
2478
|
+
</li>
|
|
2479
|
+
))}
|
|
2480
|
+
</ul>
|
|
2481
|
+
<p
|
|
2482
|
+
style={{
|
|
2483
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2484
|
+
fontWeight: 400,
|
|
2485
|
+
fontSize: 12,
|
|
2486
|
+
lineHeight: '12px',
|
|
2487
|
+
color: '#0E2238',
|
|
2488
|
+
margin: 0,
|
|
2489
|
+
paddingBottom: 10,
|
|
2490
|
+
}}
|
|
2491
|
+
>
|
|
2492
|
+
{t('autobahn:modal.listPriceFootnote')}
|
|
2493
|
+
</p>
|
|
2494
|
+
</>
|
|
2495
|
+
)}
|
|
2496
|
+
<div className="fin-sheet-cta-row" style={{ display: 'flex', gap: 7, marginTop: 4 }}>
|
|
2497
|
+
<Button
|
|
2498
|
+
variant="filled"
|
|
2499
|
+
size="lg"
|
|
2500
|
+
style={{
|
|
2501
|
+
flex: 1,
|
|
2502
|
+
height: 50,
|
|
2503
|
+
background: '#005CFF',
|
|
2504
|
+
borderRadius: 16,
|
|
2505
|
+
justifyContent: 'center',
|
|
2506
|
+
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
2507
|
+
fontWeight: 700,
|
|
2508
|
+
fontSize: 14,
|
|
2509
|
+
lineHeight: '16px',
|
|
2510
|
+
textAlign: 'center',
|
|
2511
|
+
color: '#FFFFFF',
|
|
2512
|
+
}}
|
|
2513
|
+
onClick={onSecure}
|
|
2514
|
+
>
|
|
2515
|
+
<span style={{ display: 'inline-flex', marginRight: 18 }}>
|
|
2516
|
+
<DocCheckIcon size={23} />
|
|
2517
|
+
</span>
|
|
2518
|
+
{t('autobahn:finance.requestRate')}
|
|
2519
|
+
</Button>
|
|
2520
|
+
<Button
|
|
2521
|
+
variant="outlined"
|
|
2522
|
+
size="lg"
|
|
2523
|
+
icon="phone"
|
|
2524
|
+
className="fin-sheet-call-btn"
|
|
2525
|
+
style={{
|
|
2526
|
+
width: 50,
|
|
2527
|
+
height: 50,
|
|
2528
|
+
flex: 'none',
|
|
2529
|
+
padding: 0,
|
|
2530
|
+
background: '#FFFFFF',
|
|
2531
|
+
border: '1.6px solid #005CFF',
|
|
2532
|
+
borderRadius: 16,
|
|
2533
|
+
}}
|
|
2534
|
+
onClick={onCall}
|
|
2535
|
+
/>
|
|
2536
|
+
</div>
|
|
2537
|
+
</>
|
|
2538
|
+
)}
|
|
2236
2539
|
</div>
|
|
2237
2540
|
|
|
2238
2541
|
{/* ============================== MOBILE (≤600px) — full "Rate anpassen" sheet ============================== */}
|
|
@@ -2266,7 +2569,12 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2266
2569
|
{/* Figma Kaufen sheet: struck list price ABOVE the price, aligned to its right
|
|
2267
2570
|
edge (inline-beside wraps — the 44px phone hero fills the whole column) */}
|
|
2268
2571
|
{isCash && listPrice != null && listPrice > price && (
|
|
2269
|
-
<div
|
|
2572
|
+
<div
|
|
2573
|
+
style={{ display: 'flex', justifyContent: 'flex-end', lineHeight: 1, marginTop: 0 }}
|
|
2574
|
+
role="button"
|
|
2575
|
+
tabIndex={0}
|
|
2576
|
+
onClick={() => setUvpTooltipOpen((v) => !v)}
|
|
2577
|
+
>
|
|
2270
2578
|
<s className="num fin-rate-old fin-rate-old-list">
|
|
2271
2579
|
{eur(listPrice)} €
|
|
2272
2580
|
<span
|
|
@@ -2333,7 +2641,15 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2333
2641
|
</span>
|
|
2334
2642
|
<span className="fin-rate-unit">
|
|
2335
2643
|
{oldMonthly != null && (
|
|
2336
|
-
|
|
2644
|
+
<span
|
|
2645
|
+
style={{ display: 'flex', flexDirection: 'column' }}
|
|
2646
|
+
role="button"
|
|
2647
|
+
tabIndex={0}
|
|
2648
|
+
onClick={(e) => {
|
|
2649
|
+
e.stopPropagation();
|
|
2650
|
+
setUvpTooltipOpen((v) => !v);
|
|
2651
|
+
}}
|
|
2652
|
+
>
|
|
2337
2653
|
<span className="pp-eyebrow">
|
|
2338
2654
|
{t('autobahn:card.price.uvpRateLabel')}
|
|
2339
2655
|
<span
|
|
@@ -2383,7 +2699,7 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
|
|
|
2383
2699
|
>
|
|
2384
2700
|
{eur(Math.round(oldMonthly))} €
|
|
2385
2701
|
</s>
|
|
2386
|
-
|
|
2702
|
+
</span>
|
|
2387
2703
|
)}
|
|
2388
2704
|
<span className="fin-rate-example-suffix">
|
|
2389
2705
|
{t('autobahn:finance.perMonth')}
|