@bytebrand/fe-ui-core-autobahn 1.0.79 → 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 +176 -11
- 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
|
}
|
|
@@ -4623,7 +4646,11 @@ line-height: 31px;
|
|
|
4623
4646
|
.md3 .cc-card .cc-badges {
|
|
4624
4647
|
gap: 8px !important;
|
|
4625
4648
|
}
|
|
4626
|
-
|
|
4649
|
+
/* Same badge/chip treatment on the SRP desktop list-card (.listcard, CarRow.tsx) as the grid
|
|
4650
|
+
CarCard — was still the smaller/more-saturated default dealPill look (see cardParts.tsx)
|
|
4651
|
+
since these overrides were originally scoped to .cc-card only. */
|
|
4652
|
+
.md3 .cc-card .deal-chip,
|
|
4653
|
+
.md3 .listcard .deal-chip {
|
|
4627
4654
|
height: 30px !important;
|
|
4628
4655
|
padding: 0 10px !important;
|
|
4629
4656
|
border-radius: 15px !important;
|
|
@@ -4632,7 +4659,8 @@ line-height: 31px;
|
|
|
4632
4659
|
box-shadow: none !important;
|
|
4633
4660
|
border: 1px solid transparent;
|
|
4634
4661
|
}
|
|
4635
|
-
.md3 .cc-card .deal-chip--drop
|
|
4662
|
+
.md3 .cc-card .deal-chip--drop,
|
|
4663
|
+
.md3 .listcard .deal-chip--drop {
|
|
4636
4664
|
/* solid equivalent of rgba(17, 110, 57, 0.2) over white — the badge now sits over an
|
|
4637
4665
|
edge-to-edge photo (CardCarousel padding removed), so a translucent fill let the image
|
|
4638
4666
|
show through instead of the intended pastel tint. */
|
|
@@ -4640,18 +4668,21 @@ line-height: 31px;
|
|
|
4640
4668
|
border-color: rgba(17, 110, 57, 0.05);
|
|
4641
4669
|
color: #116e39 !important;
|
|
4642
4670
|
}
|
|
4643
|
-
.md3 .cc-card .deal-chip--percent
|
|
4671
|
+
.md3 .cc-card .deal-chip--percent,
|
|
4672
|
+
.md3 .listcard .deal-chip--percent {
|
|
4644
4673
|
background: rgb(247, 213, 210) !important;
|
|
4645
4674
|
border-color: rgba(217, 45, 32, 0.05);
|
|
4646
4675
|
color: #d92d20 !important;
|
|
4647
4676
|
}
|
|
4648
|
-
.md3 .cc-card .deal-chip--zerodown
|
|
4677
|
+
.md3 .cc-card .deal-chip--zerodown,
|
|
4678
|
+
.md3 .listcard .deal-chip--zerodown {
|
|
4649
4679
|
background: rgb(204, 222, 255) !important;
|
|
4650
4680
|
border-color: rgba(0, 92, 255, 0.05);
|
|
4651
4681
|
color: #005cff !important;
|
|
4652
4682
|
}
|
|
4653
4683
|
/* NEU badge — Figma: amber tint pill, not the mobile solid-primary-blue pill */
|
|
4654
|
-
.md3 .cc-card .neu-badge
|
|
4684
|
+
.md3 .cc-card .neu-badge,
|
|
4685
|
+
.md3 .listcard .neu-badge {
|
|
4655
4686
|
height: 30px;
|
|
4656
4687
|
padding: 0 10px;
|
|
4657
4688
|
background: rgb(255, 241, 204);
|
|
@@ -4661,7 +4692,8 @@ line-height: 31px;
|
|
|
4661
4692
|
}
|
|
4662
4693
|
/* "⚡ Angebot" badge — Figma: black-tinted ROUNDED RECT (10px radius, not a full pill), black
|
|
4663
4694
|
text — bigger than and visually distinct from the mobile dark-solid pill. */
|
|
4664
|
-
.md3 .cc-card .offer-badge
|
|
4695
|
+
.md3 .cc-card .offer-badge,
|
|
4696
|
+
.md3 .listcard .offer-badge {
|
|
4665
4697
|
height: 30px;
|
|
4666
4698
|
padding: 0 10px;
|
|
4667
4699
|
border-radius: 10px;
|
|
@@ -4670,7 +4702,8 @@ line-height: 31px;
|
|
|
4670
4702
|
color: #000;
|
|
4671
4703
|
font-size: 14px;
|
|
4672
4704
|
}
|
|
4673
|
-
.md3 .cc-card .offer-badge .lucide
|
|
4705
|
+
.md3 .cc-card .offer-badge .lucide,
|
|
4706
|
+
.md3 .listcard .offer-badge .lucide {
|
|
4674
4707
|
width: 15px;
|
|
4675
4708
|
height: 15px;
|
|
4676
4709
|
color: #000;
|
|
@@ -4803,6 +4836,10 @@ line-height: 31px;
|
|
|
4803
4836
|
right: -100px;
|
|
4804
4837
|
}
|
|
4805
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
|
+
}
|
|
4806
4843
|
/* floating circular close button — overlaps the tooltip's top-right corner from outside,
|
|
4807
4844
|
same "glass circle" convention as VdpViewer's close/back/next overlay buttons. */
|
|
4808
4845
|
.md3 .cc-price .pp-uvp-tooltip-close,
|
|
@@ -5189,6 +5226,22 @@ line-height: 31px;
|
|
|
5189
5226
|
width: 112px;
|
|
5190
5227
|
justify-self: end;
|
|
5191
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
|
+
}
|
|
5192
5245
|
/* pill now sits above the bar in a real flex column (see above) — stretch it to the bar's own
|
|
5193
5246
|
width instead of the base .fin-eval-pill's inline-flex/content-hugging size. */
|
|
5194
5247
|
.md3 .fin-calc-card .fin-eval-pill--good {
|
|
@@ -5332,6 +5385,29 @@ line-height: 31px;
|
|
|
5332
5385
|
transform: rotate(12.53deg);
|
|
5333
5386
|
transform-origin: center;
|
|
5334
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
|
+
}
|
|
5335
5411
|
/* sticky bottom bar struck old rate — diagonal red strike (Figma), replacing the shared
|
|
5336
5412
|
.fin-rate-old's native horizontal line-through for this instance only. */
|
|
5337
5413
|
.md3 .bb-price-row .fin-rate-old.fin-rate-old-bb {
|
|
@@ -6368,7 +6444,7 @@ line-height: 31px;
|
|
|
6368
6444
|
display: none;
|
|
6369
6445
|
}
|
|
6370
6446
|
.md3 .srp-main {
|
|
6371
|
-
padding:
|
|
6447
|
+
padding: 14px 10px 48px !important;
|
|
6372
6448
|
}
|
|
6373
6449
|
.md3 .srp-sort {
|
|
6374
6450
|
width: 146px;
|
|
@@ -6618,7 +6694,13 @@ line-height: 31px;
|
|
|
6618
6694
|
}
|
|
6619
6695
|
/* compact hero so the search card + button surface above the fold */
|
|
6620
6696
|
.md3 .hero-inner {
|
|
6621
|
-
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;
|
|
6622
6704
|
}
|
|
6623
6705
|
.md3 .hero-badge {
|
|
6624
6706
|
margin-bottom: 10px !important;
|
|
@@ -6888,11 +6970,12 @@ line-height: 31px;
|
|
|
6888
6970
|
(14px) than the 14/24px desktop default set inline. */
|
|
6889
6971
|
@media (max-width: 600px) {
|
|
6890
6972
|
.md3 .vdp-main {
|
|
6891
|
-
padding: 0
|
|
6973
|
+
padding: 0 10px 48px !important;
|
|
6892
6974
|
}
|
|
6893
6975
|
.md3 .vdp-push-badges {
|
|
6894
6976
|
gap: 6px;
|
|
6895
6977
|
margin-bottom: 13px;
|
|
6978
|
+
padding-left: 12px;
|
|
6896
6979
|
}
|
|
6897
6980
|
}
|
|
6898
6981
|
|
|
@@ -7078,6 +7161,88 @@ color: #005CFF;
|
|
|
7078
7161
|
text-align: right;
|
|
7079
7162
|
color: #1D2A38 !important;
|
|
7080
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
|
+
}
|
|
7081
7246
|
.md3 .fin-sheet-cta-row {
|
|
7082
7247
|
border-top: 1px solid var(--fig-tile-border);
|
|
7083
7248
|
padding-top: 20px;
|
|
@@ -8145,7 +8310,7 @@ color: #005CFF;
|
|
|
8145
8310
|
/* modal-only: sup sits higher here than on the desktop card's mobile-cash price
|
|
8146
8311
|
(same shared .fin-rate-cash-mobile), so scope via the modal's --cash modifier */
|
|
8147
8312
|
.md3 .fin-price-grid--cash .fin-rate-cash-mobile sup {
|
|
8148
|
-
top:
|
|
8313
|
+
top: 4px;
|
|
8149
8314
|
}
|
|
8150
8315
|
/* digit-count sizing (round 15): Kaufen/Barzahlung hero price shrinks as its digit
|
|
8151
8316
|
count grows — repeats the class for specificity so it wins over the 52px default
|