@bytebrand/fe-ui-core-autobahn 1.0.80 → 1.0.81
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 +14 -13
- package/cards/cardParts.tsx +7 -5
- package/compat/CookieModal/modals/ManageCookieModal/ManageCookieModal.tsx +8 -6
- package/compat/MdField/MdField.tsx +29 -5
- package/constants.ts +31 -31
- package/locales/de/autobahn.json +6 -2
- package/locales/en/autobahn.json +6 -2
- package/package.json +1 -1
- package/theme/md3.css +159 -4
- package/utils/financeFormat.ts +62 -62
- package/utils/priceRating.ts +58 -58
- package/vdp/FinanceCalculator.tsx +133 -39
- package/vdp/VdpModules.tsx +184 -20
package/cards/CarCard.tsx
CHANGED
|
@@ -142,8 +142,8 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
142
142
|
}}
|
|
143
143
|
>
|
|
144
144
|
<div style={{ display: 'flex', flexWrap: 'nowrap', gap: 5, alignItems: 'center' }}>
|
|
145
|
-
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
146
145
|
{car.drop != null ? <DropChip t={t} amount={car.drop} /> : showSave && <SaveChip t={t} amount={car.savings!} />}
|
|
146
|
+
<PercentChip pct={car.dropPct} />
|
|
147
147
|
<ZeroDownChip
|
|
148
148
|
t={t}
|
|
149
149
|
show={car.zeroDown}
|
|
@@ -162,7 +162,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
162
162
|
color: '#005CFF',
|
|
163
163
|
}}
|
|
164
164
|
/>
|
|
165
|
-
<
|
|
165
|
+
{car.isNew && <span className="neu-badge">NEU</span>}
|
|
166
166
|
</div>
|
|
167
167
|
<OfferBadge show={showOfferBadge} t={t} />
|
|
168
168
|
</div>
|
|
@@ -208,11 +208,23 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
208
208
|
t={t}
|
|
209
209
|
/>
|
|
210
210
|
</div>
|
|
211
|
+
{/* Mobile-only row (CardCtaRow itself carries the show-mobile class) — always
|
|
212
|
+
rendered, with real fallbacks the page can override once it wires its own
|
|
213
|
+
handlers (e.g. the "Rate anpassen" FinanceDetailsModal sheet). Never hidden. */}
|
|
214
|
+
<CardCtaRow
|
|
215
|
+
car={car}
|
|
216
|
+
mode={mode}
|
|
217
|
+
onRequestOffer={onRequestOffer || (() => onOpen(car))}
|
|
218
|
+
onCall={onCall || callDealerSalesPhone}
|
|
219
|
+
onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
|
|
220
|
+
t={t}
|
|
221
|
+
/>
|
|
211
222
|
<EcoLine
|
|
212
223
|
t={t}
|
|
213
224
|
car={car}
|
|
214
225
|
style={{
|
|
215
226
|
marginTop: 'auto',
|
|
227
|
+
borderTop: 'none',
|
|
216
228
|
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
217
229
|
fontStyle: 'normal',
|
|
218
230
|
fontWeight: 400,
|
|
@@ -222,17 +234,6 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
222
234
|
color: '#0e2238',
|
|
223
235
|
}}
|
|
224
236
|
/>
|
|
225
|
-
{/* Mobile-only row (CardCtaRow itself carries the show-mobile class) — always
|
|
226
|
-
rendered, with real fallbacks the page can override once it wires its own
|
|
227
|
-
handlers (e.g. the "Rate anpassen" FinanceDetailsModal sheet). Never hidden. */}
|
|
228
|
-
<CardCtaRow
|
|
229
|
-
car={car}
|
|
230
|
-
mode={mode}
|
|
231
|
-
onRequestOffer={onRequestOffer || (() => onOpen(car))}
|
|
232
|
-
onCall={onCall || callDealerSalesPhone}
|
|
233
|
-
onOpenRateAdjust={onOpenRateAdjust || (() => onOpen(car))}
|
|
234
|
-
t={t}
|
|
235
|
-
/>
|
|
236
237
|
{/* legacy VehicleSmallCard's "SeoText" footer line (full spec + equipment list) —
|
|
237
238
|
renders nothing when the caller hasn't populated car.seoText. */}
|
|
238
239
|
<SeoLine car={car} style={{ marginTop: 6 }} />
|
package/cards/cardParts.tsx
CHANGED
|
@@ -444,10 +444,9 @@ export const PriceEvalCol: React.FunctionComponent<{ car: AutobahnCar; t: Transl
|
|
|
444
444
|
? Math.max(4, Math.min(96, ((car.price - car.marketLow!) / (car.marketHigh! - car.marketLow!)) * 100))
|
|
445
445
|
: undefined;
|
|
446
446
|
const isGood = TIER_KEYS[tier] === 'good';
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
const tierLabel = isGood ? t('autobahn:tier.top') : t('autobahn:tier.' + car.rating);
|
|
447
|
+
// each tier shows its own real label now (Top/Guter/Fairer/Erhöht/Hoher Preis) — no more
|
|
448
|
+
// collapsing 'good' onto the "Top-Preis" claim. Pill styling (colour/class/footnote) unchanged.
|
|
449
|
+
const tierLabel = t('autobahn:tier.' + car.rating);
|
|
451
450
|
// 'good' collapses onto the 'top' tier's own color too (#168a45), matching the fixed
|
|
452
451
|
// "Top-Preis" text above rather than tier 1's lighter TIER_COLORS[1] green.
|
|
453
452
|
const pillColor = isGood ? TIER_COLORS[0] : TIER_COLORS[tier];
|
|
@@ -777,7 +776,10 @@ export interface CardCtaRowProps {
|
|
|
777
776
|
* keeps its text label — only the phone button is icon-only, matching the mockup.
|
|
778
777
|
*/
|
|
779
778
|
export const CardCtaRow: React.FunctionComponent<CardCtaRowProps> = ({ car, mode, onRequestOffer, onCall, onOpenRateAdjust, t }) => (
|
|
780
|
-
<div
|
|
779
|
+
<div
|
|
780
|
+
className="card-cta-row show-mobile"
|
|
781
|
+
style={{ display: 'flex', gap: 8, borderTop: '1px solid var(--md-outline-variant)', paddingTop: 8 }}
|
|
782
|
+
>
|
|
781
783
|
<Button
|
|
782
784
|
variant="filled"
|
|
783
785
|
style={{ flex: 2 }}
|
|
@@ -42,9 +42,11 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
const onHandleAnalyticsSwitchValue = (value: boolean) => {
|
|
45
|
+
// per-item state is stored inverted (false = this item is on — see isChecked below),
|
|
46
|
+
// so turning the master ON (value=true) must store false on every item, not true.
|
|
45
47
|
const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
|
|
46
48
|
.reduce((acc, val) => acc.concat(val), [])
|
|
47
|
-
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
49
|
+
.reduce((acc, item) => ({ ...acc, [item]: !value }), {});
|
|
48
50
|
|
|
49
51
|
setAnalyticsSwitches(newAnalyticsSwitches);
|
|
50
52
|
setAnalyticsMasterSwitch(value);
|
|
@@ -53,7 +55,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
53
55
|
const onHandleMarketingSwitchValue = (value: boolean) => {
|
|
54
56
|
const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
|
|
55
57
|
.reduce((acc, val) => acc.concat(val), [])
|
|
56
|
-
.reduce((acc, item) => ({ ...acc, [item]: value }), {});
|
|
58
|
+
.reduce((acc, item) => ({ ...acc, [item]: !value }), {});
|
|
57
59
|
setMarketingSwitches(newMarketingSwitches);
|
|
58
60
|
setMarketingMasterSwitch(value);
|
|
59
61
|
};
|
|
@@ -87,7 +89,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
87
89
|
const onHandleChangeInnerSwitchAnalytics = (cookiesFromCategory: any, isChecked: boolean) => {
|
|
88
90
|
const updatedSwitches = { ...analyticsSwitches };
|
|
89
91
|
for (const item of cookiesFromCategory) {
|
|
90
|
-
updatedSwitches[item] =
|
|
92
|
+
updatedSwitches[item] = isChecked
|
|
91
93
|
}
|
|
92
94
|
setAnalyticsSwitches(updatedSwitches);
|
|
93
95
|
};
|
|
@@ -95,7 +97,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
95
97
|
const onHandleChangeInnerSwitchMarketing = (cookiesFromCategory: any, isChecked: boolean) => {
|
|
96
98
|
const updatedSwitches = { ...marketingSwitches };
|
|
97
99
|
for (const item of cookiesFromCategory) {
|
|
98
|
-
updatedSwitches[item] =
|
|
100
|
+
updatedSwitches[item] = isChecked
|
|
99
101
|
}
|
|
100
102
|
setMarketingSwitches(updatedSwitches);
|
|
101
103
|
};
|
|
@@ -141,7 +143,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
141
143
|
<a className={styles.switchBlockLink} href={getLinkFromCategory} target='_blank'>Datenschutzbestimmungen</a>
|
|
142
144
|
<MaterialSwitch
|
|
143
145
|
sx={{ marginLeft: 'auto' }}
|
|
144
|
-
value={
|
|
146
|
+
value={isChecked}
|
|
145
147
|
onChange={() => onHandleChangeInnerSwitchAnalytics(getItemsFromCategory, isChecked)}
|
|
146
148
|
/>
|
|
147
149
|
</div>
|
|
@@ -165,7 +167,7 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
|
|
|
165
167
|
<a className={styles.switchBlockLink} href={getLinkFromCategory} target='_blank'>Datenschutzbestimmungen</a>
|
|
166
168
|
<MaterialSwitch
|
|
167
169
|
sx={{ marginLeft: 'auto' }}
|
|
168
|
-
value={
|
|
170
|
+
value={isChecked}
|
|
169
171
|
onChange={() => onHandleChangeInnerSwitchMarketing(getItemsFromCategory, isChecked)}
|
|
170
172
|
/>
|
|
171
173
|
</div>
|
|
@@ -28,18 +28,31 @@ export interface MdFieldProps {
|
|
|
28
28
|
label?: string;
|
|
29
29
|
placeholder?: string;
|
|
30
30
|
maxLength?: number;
|
|
31
|
+
/** opt-in Material floating label: label rests inside the field like a placeholder
|
|
32
|
+
* when empty and unfocused, then floats up to the normal small-label spot on focus
|
|
33
|
+
* or once a value is typed. Defaults to false — the static above-field label. */
|
|
34
|
+
floatingLabel?: boolean;
|
|
31
35
|
onChange: (value: string, name?: string, event?: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
35
39
|
className, error, sx, required, disabled, multiline, autoComplete,
|
|
36
|
-
value, type, name, rows, label, placeholder, maxLength, onChange
|
|
40
|
+
value, type, name, rows, label, placeholder, floatingLabel, maxLength, onChange
|
|
37
41
|
}) => {
|
|
38
42
|
const [reveal, setReveal] = React.useState(false);
|
|
43
|
+
const [focused, setFocused] = React.useState(false);
|
|
39
44
|
const isPassword = type === 'password';
|
|
40
45
|
const inputType = isPassword && reveal ? 'text' : (type || 'text');
|
|
41
46
|
const hasError = !!error;
|
|
42
47
|
const generatedId = React.useId();
|
|
48
|
+
// floating label (Material pattern): while empty and unfocused, the label text is
|
|
49
|
+
// shown as the input's own native placeholder (styled via .mfield--floating
|
|
50
|
+
// input::placeholder) and the <label> itself is not rendered at all; once focused or
|
|
51
|
+
// a value is typed, the real <label> appears in its normal static spot above the
|
|
52
|
+
// field and the native placeholder goes quiet — the two never show at the same time.
|
|
53
|
+
const hasValue = value != null && value !== '';
|
|
54
|
+
const resting = !!floatingLabel && !focused && !hasValue;
|
|
55
|
+
const effectivePlaceholder = floatingLabel ? (resting ? (label || placeholder || '') : '') : (placeholder || '');
|
|
43
56
|
|
|
44
57
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
45
58
|
onChange(event.currentTarget.value, event.currentTarget.name || name, event);
|
|
@@ -47,22 +60,31 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
47
60
|
|
|
48
61
|
return (
|
|
49
62
|
<div className={classnames('mfield-group', className)} style={sx as React.CSSProperties}>
|
|
50
|
-
{label ? (
|
|
63
|
+
{label && !resting ? (
|
|
51
64
|
<label className='md-label-sm mfield-label' htmlFor={generatedId}>
|
|
52
65
|
{label}{required ? ' *' : ''}
|
|
53
66
|
</label>
|
|
54
67
|
) : null}
|
|
55
|
-
<div
|
|
68
|
+
<div
|
|
69
|
+
className={
|
|
70
|
+
'mfield'
|
|
71
|
+
+ (hasError ? ' mfield--error' : '')
|
|
72
|
+
+ (disabled ? ' mfield--disabled' : '')
|
|
73
|
+
+ (floatingLabel ? ' mfield--floating' : '')
|
|
74
|
+
}
|
|
75
|
+
>
|
|
56
76
|
{multiline ? (
|
|
57
77
|
<textarea
|
|
58
78
|
id={generatedId}
|
|
59
79
|
name={name}
|
|
60
80
|
value={value == null ? '' : value}
|
|
61
81
|
onChange={handleChange}
|
|
82
|
+
onFocus={() => setFocused(true)}
|
|
83
|
+
onBlur={() => setFocused(false)}
|
|
62
84
|
disabled={disabled}
|
|
63
85
|
rows={rows || 3}
|
|
64
86
|
maxLength={maxLength}
|
|
65
|
-
placeholder={
|
|
87
|
+
placeholder={effectivePlaceholder}
|
|
66
88
|
autoComplete={autoComplete}
|
|
67
89
|
style={{ width: '100%', resize: 'vertical' }}
|
|
68
90
|
/>
|
|
@@ -73,9 +95,11 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
73
95
|
type={inputType}
|
|
74
96
|
value={value == null ? '' : value}
|
|
75
97
|
onChange={handleChange}
|
|
98
|
+
onFocus={() => setFocused(true)}
|
|
99
|
+
onBlur={() => setFocused(false)}
|
|
76
100
|
disabled={disabled}
|
|
77
101
|
maxLength={maxLength}
|
|
78
|
-
placeholder={
|
|
102
|
+
placeholder={effectivePlaceholder}
|
|
79
103
|
autoComplete={autoComplete}
|
|
80
104
|
style={{ width: '100%' }}
|
|
81
105
|
/>
|
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
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"availabilityLabel": "Verfügbarkeit",
|
|
94
94
|
"availabilityValue": "Sofort",
|
|
95
95
|
"downPaymentSliderLabel": "Anzahlung",
|
|
96
|
+
"downPaymentSliderLabelSheet": "Sonderzahlung",
|
|
96
97
|
"annualMileageLabel": "Laufleistung / Jahr",
|
|
97
98
|
"purchasePrice": "Kaufpreis",
|
|
98
99
|
"perMonth": "/mtl.",
|
|
@@ -105,7 +106,10 @@
|
|
|
105
106
|
"addServicesToggle": "Rundum-Sorglos-Paket",
|
|
106
107
|
"addServicesPerMonth": "+{{value}} € / Monat",
|
|
107
108
|
"addServicesOnce": "+{{value}} €",
|
|
108
|
-
"addServicesIncluded": "inklusive"
|
|
109
|
+
"addServicesIncluded": "inklusive",
|
|
110
|
+
"addServicesTooltipIntro": "Das Rundum-Sorglos-Paket ist Bestandteil des Angebots und im Kaufpreis enthalten.",
|
|
111
|
+
"addServicesTooltipHeading": "Im Rundum-Sorglos-Paket enthalten:",
|
|
112
|
+
"addServicesTooltipItems": "kompletter Zulassungsservice inklusive Wunschkennzeichen, soweit verfügbar\nsorgfältige Prüfung und Vorbereitung aller Zulassungsunterlagen\nbundesweite Fahrzeugauslieferung bis vor Ihre Haustür\npersönliche Fahrzeugübergabe\nhochwertige Fahrzeugaufbereitung innen und außen\nausführlicher technischer Übergabecheck\nHU und Inspektion, falls erforderlich\nTank- beziehungsweise Ladegrundfüllung bei Übergabe\npersönlicher Ansprechpartner von Beginn an"
|
|
109
113
|
},
|
|
110
114
|
"specTable": {
|
|
111
115
|
"title": "Fahrzeugdaten",
|
|
@@ -691,7 +695,7 @@
|
|
|
691
695
|
"vehiclePrice": "Fahrzeugpreis ggf. inkl. MwSt.",
|
|
692
696
|
"leasingAmount": "Leasingbetrag",
|
|
693
697
|
"netLoanAmount": "Nettodarlehensbetrag",
|
|
694
|
-
"downPayment": "
|
|
698
|
+
"downPayment": "Sonderzahlung",
|
|
695
699
|
"annualMileage": "Laufleistung / Jahr",
|
|
696
700
|
"term": "Vertragslaufzeit",
|
|
697
701
|
"months": "{{months}} Monat",
|
package/locales/en/autobahn.json
CHANGED
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"availabilityLabel": "Availability",
|
|
94
94
|
"availabilityValue": "Immediately",
|
|
95
95
|
"downPaymentSliderLabel": "Down payment",
|
|
96
|
+
"downPaymentSliderLabelSheet": "Special payment",
|
|
96
97
|
"annualMileageLabel": "Mileage / year",
|
|
97
98
|
"purchasePrice": "Purchase price",
|
|
98
99
|
"perMonth": "/mo.",
|
|
@@ -105,7 +106,10 @@
|
|
|
105
106
|
"addServicesToggle": "All-round care package",
|
|
106
107
|
"addServicesPerMonth": "+€{{value}} / month",
|
|
107
108
|
"addServicesOnce": "+€{{value}}",
|
|
108
|
-
"addServicesIncluded": "included"
|
|
109
|
+
"addServicesIncluded": "included",
|
|
110
|
+
"addServicesTooltipIntro": "The all-round care package is part of the offer and included in the purchase price.",
|
|
111
|
+
"addServicesTooltipHeading": "Included in the all-round care package:",
|
|
112
|
+
"addServicesTooltipItems": "complete registration service including a custom license plate, where available\ncareful review and preparation of all registration documents\nnationwide vehicle delivery to your doorstep\npersonal vehicle handover\nhigh-quality interior and exterior vehicle detailing\ndetailed technical handover check\nroadworthiness test (HU) and inspection, if required\nfuel or charge top-up at handover\npersonal point of contact from day one"
|
|
109
113
|
},
|
|
110
114
|
"specTable": {
|
|
111
115
|
"title": "Vehicle data",
|
|
@@ -691,7 +695,7 @@
|
|
|
691
695
|
"vehiclePrice": "Vehicle price (incl. VAT, if applicable)",
|
|
692
696
|
"leasingAmount": "Leasing amount",
|
|
693
697
|
"netLoanAmount": "Net loan amount",
|
|
694
|
-
"downPayment": "
|
|
698
|
+
"downPayment": "Special payment",
|
|
695
699
|
"annualMileage": "Mileage / year",
|
|
696
700
|
"term": "Contract term",
|
|
697
701
|
"months": "{{months}} month",
|
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.81",
|
|
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
|
@@ -233,6 +233,16 @@ line-height: 31px;
|
|
|
233
233
|
box-shadow: var(--md-e2);
|
|
234
234
|
background: var(--autode-blau-strong);
|
|
235
235
|
}
|
|
236
|
+
/* "already requested/checked out" state (legacy compat offerButtonSuccess parity) — the
|
|
237
|
+
primary VDP CTA turns green with a "continue" label instead of the default "Angebot
|
|
238
|
+
anfragen"/"Angebot sichern" once the customer already has a request/checkout in progress. */
|
|
239
|
+
.md3 .fin-cta-secured {
|
|
240
|
+
background: var(--md-good) !important;
|
|
241
|
+
}
|
|
242
|
+
.md3 .fin-cta-secured:hover {
|
|
243
|
+
background: var(--md-good) !important;
|
|
244
|
+
filter: brightness(0.92);
|
|
245
|
+
}
|
|
236
246
|
.md3 .mbtn--tonal {
|
|
237
247
|
background: var(--md-primary-container);
|
|
238
248
|
color: var(--md-on-primary-container);
|
|
@@ -637,6 +647,19 @@ line-height: 31px;
|
|
|
637
647
|
color: var(--md-on-surface-variant);
|
|
638
648
|
font-weight: 600;
|
|
639
649
|
}
|
|
650
|
+
/* MdField floatingLabel=true only: while the field is empty and unfocused, the <label>
|
|
651
|
+
isn't rendered at all — the label text shows as the input's own native placeholder
|
|
652
|
+
instead, styled to match the Figma "resting" state. Firefox dims ::placeholder by
|
|
653
|
+
default (opacity ~0.54), so opacity:1 is needed to get the real #5b6b7f. */
|
|
654
|
+
.md3 .mfield--floating input::placeholder,
|
|
655
|
+
.md3 .mfield--floating textarea::placeholder {
|
|
656
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
657
|
+
font-weight: 700;
|
|
658
|
+
font-size: 16px;
|
|
659
|
+
line-height: 14px;
|
|
660
|
+
color: #9aa5b4;
|
|
661
|
+
opacity: 1;
|
|
662
|
+
}
|
|
640
663
|
.md3 .mfield--error {
|
|
641
664
|
box-shadow: inset 0 0 0 2px var(--md-error);
|
|
642
665
|
}
|
|
@@ -4813,6 +4836,10 @@ line-height: 31px;
|
|
|
4813
4836
|
right: -100px;
|
|
4814
4837
|
}
|
|
4815
4838
|
}
|
|
4839
|
+
/* Leasing tab's "3" badge tooltip needs its own offset — Finanzierung keeps -135px/-95px above. */
|
|
4840
|
+
.md3 .fin-calc-card .pp-uvp-tooltip--leasing {
|
|
4841
|
+
right: -110px;
|
|
4842
|
+
}
|
|
4816
4843
|
/* floating circular close button — overlaps the tooltip's top-right corner from outside,
|
|
4817
4844
|
same "glass circle" convention as VdpViewer's close/back/next overlay buttons. */
|
|
4818
4845
|
.md3 .cc-price .pp-uvp-tooltip-close,
|
|
@@ -5199,6 +5226,22 @@ line-height: 31px;
|
|
|
5199
5226
|
width: 112px;
|
|
5200
5227
|
justify-self: end;
|
|
5201
5228
|
}
|
|
5229
|
+
/* .fin-modal doesn't render .fin-cond at all, so it never needed the base "rate pill"/"cond bar"
|
|
5230
|
+
2-row split — that's what was leaving the pill vertically centered in a tall empty row above
|
|
5231
|
+
the bar (the "big gap"). One row, same evalcol grouping as .fin-calc-card above. */
|
|
5232
|
+
.md3 .fin-modal .fin-price-grid {
|
|
5233
|
+
grid-template-columns: minmax(0, 1fr) 112px;
|
|
5234
|
+
grid-template-areas: "rate evalcol";
|
|
5235
|
+
column-gap: 15px;
|
|
5236
|
+
}
|
|
5237
|
+
.md3 .fin-modal .fin-eval-col {
|
|
5238
|
+
display: flex;
|
|
5239
|
+
flex-direction: column;
|
|
5240
|
+
gap: 16px;
|
|
5241
|
+
grid-area: evalcol;
|
|
5242
|
+
width: 112px;
|
|
5243
|
+
justify-self: end;
|
|
5244
|
+
}
|
|
5202
5245
|
/* pill now sits above the bar in a real flex column (see above) — stretch it to the bar's own
|
|
5203
5246
|
width instead of the base .fin-eval-pill's inline-flex/content-hugging size. */
|
|
5204
5247
|
.md3 .fin-calc-card .fin-eval-pill--good {
|
|
@@ -5342,6 +5385,29 @@ line-height: 31px;
|
|
|
5342
5385
|
transform: rotate(12.53deg);
|
|
5343
5386
|
transform-origin: center;
|
|
5344
5387
|
}
|
|
5388
|
+
/* .fin-modal's own UVP-Rate old price — same diagonal-strike treatment as .fin-calc-card
|
|
5389
|
+
above, just scoped to the modal (was still using the shared 15px/700/error/line-through
|
|
5390
|
+
.fin-rate-old base, never got the desktop-card override). */
|
|
5391
|
+
.md3 .fin-modal .fin-rate-old.fin-rate-example-price {
|
|
5392
|
+
position: relative;
|
|
5393
|
+
display: inline-block;
|
|
5394
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
5395
|
+
font-weight: 400;
|
|
5396
|
+
font-size: 17px;
|
|
5397
|
+
line-height: 20px;
|
|
5398
|
+
color: #0e2238;
|
|
5399
|
+
text-decoration: none;
|
|
5400
|
+
}
|
|
5401
|
+
.md3 .fin-modal .fin-rate-old.fin-rate-example-price::after {
|
|
5402
|
+
content: '';
|
|
5403
|
+
position: absolute;
|
|
5404
|
+
left: -2px;
|
|
5405
|
+
right: -2px;
|
|
5406
|
+
top: 50%;
|
|
5407
|
+
border-top: 2px solid #e60000;
|
|
5408
|
+
transform: rotate(12.53deg);
|
|
5409
|
+
transform-origin: center;
|
|
5410
|
+
}
|
|
5345
5411
|
/* sticky bottom bar struck old rate — diagonal red strike (Figma), replacing the shared
|
|
5346
5412
|
.fin-rate-old's native horizontal line-through for this instance only. */
|
|
5347
5413
|
.md3 .bb-price-row .fin-rate-old.fin-rate-old-bb {
|
|
@@ -6378,7 +6444,7 @@ line-height: 31px;
|
|
|
6378
6444
|
display: none;
|
|
6379
6445
|
}
|
|
6380
6446
|
.md3 .srp-main {
|
|
6381
|
-
padding:
|
|
6447
|
+
padding: 14px 10px 48px !important;
|
|
6382
6448
|
}
|
|
6383
6449
|
.md3 .srp-sort {
|
|
6384
6450
|
width: 146px;
|
|
@@ -6628,7 +6694,13 @@ line-height: 31px;
|
|
|
6628
6694
|
}
|
|
6629
6695
|
/* compact hero so the search card + button surface above the fold */
|
|
6630
6696
|
.md3 .hero-inner {
|
|
6631
|
-
padding: 16px
|
|
6697
|
+
padding: 16px 12px 18px !important;
|
|
6698
|
+
}
|
|
6699
|
+
/* every NewMainPage section reuses .container with its own inline top/bottom padding
|
|
6700
|
+
(24px sides, hardcoded per-section) — only the shared side gutter narrows on mobile. */
|
|
6701
|
+
.md3 .container {
|
|
6702
|
+
padding-left: 12px !important;
|
|
6703
|
+
padding-right: 12px !important;
|
|
6632
6704
|
}
|
|
6633
6705
|
.md3 .hero-badge {
|
|
6634
6706
|
margin-bottom: 10px !important;
|
|
@@ -6898,11 +6970,12 @@ line-height: 31px;
|
|
|
6898
6970
|
(14px) than the 14/24px desktop default set inline. */
|
|
6899
6971
|
@media (max-width: 600px) {
|
|
6900
6972
|
.md3 .vdp-main {
|
|
6901
|
-
padding: 0
|
|
6973
|
+
padding: 0 10px 48px !important;
|
|
6902
6974
|
}
|
|
6903
6975
|
.md3 .vdp-push-badges {
|
|
6904
6976
|
gap: 6px;
|
|
6905
6977
|
margin-bottom: 13px;
|
|
6978
|
+
padding-left: 12px;
|
|
6906
6979
|
}
|
|
6907
6980
|
}
|
|
6908
6981
|
|
|
@@ -7088,6 +7161,88 @@ color: #005CFF;
|
|
|
7088
7161
|
text-align: right;
|
|
7089
7162
|
color: #1D2A38 !important;
|
|
7090
7163
|
}
|
|
7164
|
+
/* Rundum-Sorglos-Paket info popover (ToggleRow's `tooltip` prop) — same pp-uvp-tooltip look as
|
|
7165
|
+
the UVP-Rate footnote (grey card, shadow, floating dark close circle), inherited via that
|
|
7166
|
+
shared class; these rules only override its POSITION, since that one is tuned for a fixed
|
|
7167
|
+
281px card anchored beside the price hero, while this one hangs off a full-width row that can
|
|
7168
|
+
sit anywhere in the sheet. left:0/right:0 pins its width to the row itself (always the full
|
|
7169
|
+
sheet/modal/card width already) so it can never overflow horizontally; opens UPWARD (bottom:
|
|
7170
|
+
100%) since the row tends to sit near the bottom of the sheet, where dropping down would get
|
|
7171
|
+
clipped by the viewport; max-height + its own scroll is the vertical safety net for the same
|
|
7172
|
+
reason (long bullet list vs. a short one-line footnote).
|
|
7173
|
+
Selectors repeat the base rule's .fin-calc-card/.fin-modal ancestor scoping ON PURPOSE — both
|
|
7174
|
+
target .pp-uvp-tooltip at 3 class-selectors deep, so a plain ".fin-toggle-tooltip" (2 deep)
|
|
7175
|
+
loses specificity and only some properties would win, leaving top/bottom (and others) mixed
|
|
7176
|
+
from both rules instead of cleanly overridden. */
|
|
7177
|
+
.md3 .fin-calc-card .fin-toggle-tooltip,
|
|
7178
|
+
.md3 .fin-modal .fin-toggle-tooltip {
|
|
7179
|
+
position: absolute;
|
|
7180
|
+
top: auto;
|
|
7181
|
+
bottom: 100%;
|
|
7182
|
+
left: 0;
|
|
7183
|
+
right: 0;
|
|
7184
|
+
width: auto;
|
|
7185
|
+
z-index: 20;
|
|
7186
|
+
margin-bottom: 10px;
|
|
7187
|
+
max-height: 260px;
|
|
7188
|
+
cursor: default;
|
|
7189
|
+
}
|
|
7190
|
+
.md3 .fin-modal .fin-toggle-addservices--cash .fin-toggle-tooltip {
|
|
7191
|
+
top: auto;
|
|
7192
|
+
bottom: auto;
|
|
7193
|
+
}
|
|
7194
|
+
.md3 .fin-calc-card .fin-toggle-tooltip-close,
|
|
7195
|
+
.md3 .fin-modal .fin-toggle-tooltip-close {
|
|
7196
|
+
position: absolute;
|
|
7197
|
+
top: -43px;
|
|
7198
|
+
right: -8px;
|
|
7199
|
+
display: flex;
|
|
7200
|
+
align-items: center;
|
|
7201
|
+
justify-content: center;
|
|
7202
|
+
width: 40px;
|
|
7203
|
+
height: 40px;
|
|
7204
|
+
border-radius: 50%;
|
|
7205
|
+
background: rgba(11, 19, 32, 0.45);
|
|
7206
|
+
border: 1.5px solid rgba(255, 255, 255, 0.25);
|
|
7207
|
+
padding: 0;
|
|
7208
|
+
cursor: pointer;
|
|
7209
|
+
}
|
|
7210
|
+
.md3 .fin-toggle-tooltip-body ul {
|
|
7211
|
+
display: flex;
|
|
7212
|
+
flex-direction: column;
|
|
7213
|
+
gap: 4px;
|
|
7214
|
+
margin: 0;
|
|
7215
|
+
padding-left: 14px;
|
|
7216
|
+
}
|
|
7217
|
+
/* .fin-calc-card's own cash toggle only (not the mobile sheet's — that one keeps the
|
|
7218
|
+
standalone look above). Drops the standalone-toggle chrome so it reads as one more
|
|
7219
|
+
.calcrow under Zahlung/Verfügbarkeit: same padding, no border, same label/value type. */
|
|
7220
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash {
|
|
7221
|
+
padding: 8px 0 !important;
|
|
7222
|
+
border-top: 0;
|
|
7223
|
+
border-bottom: 0;
|
|
7224
|
+
align-items: baseline;
|
|
7225
|
+
gap: 12px;
|
|
7226
|
+
}
|
|
7227
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm {
|
|
7228
|
+
font-family: inherit;
|
|
7229
|
+
font-weight: 600;
|
|
7230
|
+
font-size: 11px;
|
|
7231
|
+
line-height: normal;
|
|
7232
|
+
letter-spacing: 0.4px;
|
|
7233
|
+
color: var(--md-on-surface-variant);
|
|
7234
|
+
}
|
|
7235
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm .lucide {
|
|
7236
|
+
display: none;
|
|
7237
|
+
}
|
|
7238
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .num,
|
|
7239
|
+
.md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash > div > span:last-child {
|
|
7240
|
+
font-family: var(--md-font-display) !important;
|
|
7241
|
+
font-weight: 600 !important;
|
|
7242
|
+
font-size: 13.5px !important;
|
|
7243
|
+
line-height: normal !important;
|
|
7244
|
+
color: var(--md-on-surface) !important;
|
|
7245
|
+
}
|
|
7091
7246
|
.md3 .fin-sheet-cta-row {
|
|
7092
7247
|
border-top: 1px solid var(--fig-tile-border);
|
|
7093
7248
|
padding-top: 20px;
|
|
@@ -8155,7 +8310,7 @@ color: #005CFF;
|
|
|
8155
8310
|
/* modal-only: sup sits higher here than on the desktop card's mobile-cash price
|
|
8156
8311
|
(same shared .fin-rate-cash-mobile), so scope via the modal's --cash modifier */
|
|
8157
8312
|
.md3 .fin-price-grid--cash .fin-rate-cash-mobile sup {
|
|
8158
|
-
top:
|
|
8313
|
+
top: 4px;
|
|
8159
8314
|
}
|
|
8160
8315
|
/* digit-count sizing (round 15): Kaufen/Barzahlung hero price shrinks as its digit
|
|
8161
8316
|
count grows — repeats the class for specificity so it wins over the 52px default
|