@bytebrand/fe-ui-core-autobahn 1.0.77 → 1.0.78
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 +7 -2
- package/constants.ts +31 -31
- 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/utils/financeFormat.ts +62 -62
- package/utils/priceRating.ts +58 -58
- package/vdp/FinanceCalculator.tsx +151 -29
- package/vdp/VdpModules.tsx +367 -51
package/cards/cardParts.tsx
CHANGED
|
@@ -597,12 +597,17 @@ export const PriceHero: React.FunctionComponent<PriceHeroProps> = ({ car, mode,
|
|
|
597
597
|
column layout exactly — so the click target covers the full area INCLUDING the
|
|
598
598
|
gap between the two stacked lines, not just their text glyphs (display:contents
|
|
599
599
|
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).
|
|
600
|
+
behind it instead of opening the tooltip). stopPropagation is required here too —
|
|
601
|
+
the whole card (CarCard.tsx) has its own onClick={() => onOpen(car)} navigation,
|
|
602
|
+
so without it a click on this block both opened the tooltip AND navigated away. */}
|
|
601
603
|
<span
|
|
602
604
|
style={{ display: 'flex', flexDirection: 'column' }}
|
|
603
605
|
role="button"
|
|
604
606
|
tabIndex={0}
|
|
605
|
-
onClick={() =>
|
|
607
|
+
onClick={(e) => {
|
|
608
|
+
e.stopPropagation();
|
|
609
|
+
setUvpTooltipOpen((v) => !v);
|
|
610
|
+
}}
|
|
606
611
|
>
|
|
607
612
|
<span className="pp-eyebrow">
|
|
608
613
|
{t('autobahn:card.price.uvpRateLabel')}
|
package/constants.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — shared lookup constants barrel.
|
|
2
|
-
// Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
|
|
3
|
-
// see that file's header) plus the legacy fe-ui-core shim re-exports below.
|
|
4
|
-
export * from './constants/autobahn';
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Legacy shared constants (moved here from fe-ui-core).
|
|
8
|
-
//
|
|
9
|
-
// This block mirrors the public symbol set of the old
|
|
10
|
-
// `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
|
|
11
|
-
// specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
|
|
12
|
-
// implementations live under ./constants/ (core.ts, app.ts, price.ts,
|
|
13
|
-
// Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
offers,
|
|
18
|
-
checkoutOffers,
|
|
19
|
-
checkoutWarranties,
|
|
20
|
-
checkoutSteps,
|
|
21
|
-
checkoutSubStep,
|
|
22
|
-
DELIVERY_PERIODS_EXTRA,
|
|
23
|
-
THREE,
|
|
24
|
-
WLTP_ENERGY_EFFICIENCY_CLASS
|
|
25
|
-
} from './constants/core';
|
|
26
|
-
|
|
27
|
-
export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
|
|
28
|
-
|
|
29
|
-
export { priceRatings } from './constants/price';
|
|
30
|
-
|
|
31
|
-
export { THROTTLE_DELAY } from './constants/Search';
|
|
1
|
+
// Autobahn (auto.de redesign) — shared lookup constants barrel.
|
|
2
|
+
// Pure data re-exported from ./constants/autobahn (kept import-safe for plain Node consumers,
|
|
3
|
+
// see that file's header) plus the legacy fe-ui-core shim re-exports below.
|
|
4
|
+
export * from './constants/autobahn';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Legacy shared constants (moved here from fe-ui-core).
|
|
8
|
+
//
|
|
9
|
+
// This block mirrors the public symbol set of the old
|
|
10
|
+
// `@bytebrand/fe-ui-core/constants` barrel so consumers can swap that
|
|
11
|
+
// specifier for `@bytebrand/fe-ui-core-autobahn/constants` 1:1. The
|
|
12
|
+
// implementations live under ./constants/ (core.ts, app.ts, price.ts,
|
|
13
|
+
// Search.ts, …); fe-ui-core re-exports them via shims until it is deleted.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
offers,
|
|
18
|
+
checkoutOffers,
|
|
19
|
+
checkoutWarranties,
|
|
20
|
+
checkoutSteps,
|
|
21
|
+
checkoutSubStep,
|
|
22
|
+
DELIVERY_PERIODS_EXTRA,
|
|
23
|
+
THREE,
|
|
24
|
+
WLTP_ENERGY_EFFICIENCY_CLASS
|
|
25
|
+
} from './constants/core';
|
|
26
|
+
|
|
27
|
+
export { CUSTOM_ERROR_MESSAGES, supportPhone, supportPhoneLink } from './constants/app';
|
|
28
|
+
|
|
29
|
+
export { priceRatings } from './constants/price';
|
|
30
|
+
|
|
31
|
+
export { THROTTLE_DELAY } from './constants/Search';
|
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.78",
|
|
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;
|
package/utils/financeFormat.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
-
//
|
|
3
|
-
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
-
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
-
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
-
// Node service.
|
|
7
|
-
//
|
|
8
|
-
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
-
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
-
|
|
11
|
-
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
-
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
-
export function formatEur(n: number): string {
|
|
14
|
-
return n.toLocaleString('de-DE');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
-
export function formatPct(n: number): string {
|
|
19
|
-
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FinanceExampleFields {
|
|
23
|
-
price: number;
|
|
24
|
-
apr: number;
|
|
25
|
-
down: number;
|
|
26
|
-
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
-
netLoanAmount?: number;
|
|
28
|
-
nominalRate?: number;
|
|
29
|
-
finalInstallment?: number;
|
|
30
|
-
grossAmount?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface FinanceExampleLabels {
|
|
34
|
-
vehiclePrice: string;
|
|
35
|
-
priceSuffix: string;
|
|
36
|
-
apr: string;
|
|
37
|
-
nominal: string;
|
|
38
|
-
net: string;
|
|
39
|
-
down: string;
|
|
40
|
-
final: string;
|
|
41
|
-
gross: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
-
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
-
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
-
* `exampleLine`, byte-for-byte).
|
|
49
|
-
*/
|
|
50
|
-
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
-
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
-
const parts: string[] = [
|
|
53
|
-
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
-
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
-
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
-
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
-
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
-
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
-
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
-
];
|
|
61
|
-
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
-
}
|
|
1
|
+
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
+
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
+
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
+
// Node service.
|
|
7
|
+
//
|
|
8
|
+
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
+
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
+
|
|
11
|
+
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
+
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
+
export function formatEur(n: number): string {
|
|
14
|
+
return n.toLocaleString('de-DE');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
+
export function formatPct(n: number): string {
|
|
19
|
+
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FinanceExampleFields {
|
|
23
|
+
price: number;
|
|
24
|
+
apr: number;
|
|
25
|
+
down: number;
|
|
26
|
+
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
+
netLoanAmount?: number;
|
|
28
|
+
nominalRate?: number;
|
|
29
|
+
finalInstallment?: number;
|
|
30
|
+
grossAmount?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FinanceExampleLabels {
|
|
34
|
+
vehiclePrice: string;
|
|
35
|
+
priceSuffix: string;
|
|
36
|
+
apr: string;
|
|
37
|
+
nominal: string;
|
|
38
|
+
net: string;
|
|
39
|
+
down: string;
|
|
40
|
+
final: string;
|
|
41
|
+
gross: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
+
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
+
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
+
* `exampleLine`, byte-for-byte).
|
|
49
|
+
*/
|
|
50
|
+
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
+
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
+
const parts: string[] = [
|
|
53
|
+
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
+
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
+
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
+
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
+
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
+
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
+
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
+
];
|
|
61
|
+
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
+
}
|