@bytebrand/fe-ui-core-autobahn 1.0.80 → 1.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cards/CarCard.tsx CHANGED
@@ -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
- <PercentChip pct={car.dropPct} />
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 }} />
@@ -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
- // both green tiers ('top' and 'good') collapse onto one "Top-Preis" claim (Figma "similar
448
- // cars" card) — same convention the VDP FinanceCalculator now also uses for its own pill.
449
- // footnote 4 carries the disclaimer text, same convention as the hero price's 1/2/3 markers.
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 className="card-cta-row show-mobile" style={{ display: 'flex', gap: 8 }}>
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 }}
@@ -30,11 +30,11 @@ const ManageCookieModal = ({ toggleModal, handleSentryInit } : IManageCookieModa
30
30
  () => {
31
31
  const newAnalyticsSwitches = Object.values(COOKIE_SCHEMA.analytics)
32
32
  .reduce((acc, val) => acc.concat(val), [])
33
- .reduce((acc, item) => ({ ...acc, [item]: false }), {});
33
+ .reduce((acc, item) => ({ ...acc, [item]: true }), {});
34
34
 
35
35
  const newMarketingSwitches = Object.values(COOKIE_SCHEMA.marketing)
36
36
  .reduce((acc, val) => acc.concat(val), [])
37
- .reduce((acc, item) => ({ ...acc, [item]: false }), {});
37
+ .reduce((acc, item) => ({ ...acc, [item]: true }), {});
38
38
  setAnalyticsSwitches(newAnalyticsSwitches);
39
39
  setMarketingSwitches(newMarketingSwitches);
40
40
  },
@@ -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] = !isChecked
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] = !isChecked
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={analytcisMasterSwitch}
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={marketingMasterSwitch}
170
+ value={isChecked}
169
171
  onChange={() => onHandleChangeInnerSwitchMarketing(getItemsFromCategory, isChecked)}
170
172
  />
171
173
  </div>
@@ -45,7 +45,7 @@ const MdAccordionGroup: React.FC<IMdAccordionGroupProps> = ({ title, tooltipText
45
45
  ) : null}
46
46
  <Icon name='chevron-down' cls='macc-chev' />
47
47
  </div>
48
- {open ? <div style={{ padding: '4px 0 12px' }}>{children}</div> : null}
48
+ {open ? <div style={{ padding: '4px 15px 12px' }}>{children}</div> : null}
49
49
  </section>
50
50
  );
51
51
  };
@@ -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 className={'mfield' + (hasError ? ' mfield--error' : '') + (disabled ? ' mfield--disabled' : '')}>
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={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={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';
@@ -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,13 @@
105
106
  "addServicesToggle": "Rundum-Sorglos-Paket",
106
107
  "addServicesPerMonth": "+{{value}} € / Monat",
107
108
  "addServicesOnce": "+{{value}} €",
108
- "addServicesIncluded": "inklusive"
109
+ "addServicesIncluded": "inklusive",
110
+ "addServicesTooltipIntroCash": "Das Rundum-Sorglos-Paket ist Bestandteil des Angebots und im Kaufpreis enthalten.",
111
+ "addServicesTooltipIntroFinanced": "Das Rundum-Sorglos-Paket ist verpflichtender Bestandteil des Angebots. Die Kosten fallen unabhängig von der gewählten Zahlungsweise an.",
112
+ "addServicesTooltipHeading": "Im Rundum-Sorglos-Paket enthalten:",
113
+ "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",
114
+ "addServicesTooltipOutroFinancing": "Sie können die Paketkosten entweder einmalig in Höhe von 1.499,00 € bezahlen oder in die Fahrzeugfinanzierung einbeziehen lassen. Bei der Mitfinanzierung wird keine zusätzliche Einmalzahlung für das Paket erhoben. Die gewählte Zahlungsweise sowie die daraus resultierende monatliche Finanzierungsrate und der Gesamtbetrag werden in den Finanzierungsdetails ausgewiesen. Die Paketkosten sind in jedem Fall im ausgewiesenen Gesamtbetrag berücksichtigt.",
115
+ "addServicesTooltipOutroLeasing": "Sie können die Paketkosten entweder einmalig in Höhe von 1.499,00 € bezahlen oder in die monatliche Leasingrate einrechnen lassen. Bei der Rateneinrechnung wird keine zusätzliche Einmalzahlung für das Paket erhoben. Die gewählte Zahlungsweise sowie die daraus resultierende Monatsrate und der Gesamtbetrag werden in den Leasingdetails ausgewiesen. Die Paketkosten sind in jedem Fall im ausgewiesenen Gesamtbetrag berücksichtigt."
109
116
  },
110
117
  "specTable": {
111
118
  "title": "Fahrzeugdaten",
@@ -691,7 +698,7 @@
691
698
  "vehiclePrice": "Fahrzeugpreis ggf. inkl. MwSt.",
692
699
  "leasingAmount": "Leasingbetrag",
693
700
  "netLoanAmount": "Nettodarlehensbetrag",
694
- "downPayment": "Anzahlung",
701
+ "downPayment": "Sonderzahlung",
695
702
  "annualMileage": "Laufleistung / Jahr",
696
703
  "term": "Vertragslaufzeit",
697
704
  "months": "{{months}} Monat",
@@ -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,13 @@
105
106
  "addServicesToggle": "All-round care package",
106
107
  "addServicesPerMonth": "+€{{value}} / month",
107
108
  "addServicesOnce": "+€{{value}}",
108
- "addServicesIncluded": "included"
109
+ "addServicesIncluded": "included",
110
+ "addServicesTooltipIntroCash": "The all-round care package is part of the offer and included in the purchase price.",
111
+ "addServicesTooltipIntroFinanced": "The all-round care package is a mandatory part of the offer. Its cost applies regardless of the payment method chosen.",
112
+ "addServicesTooltipHeading": "Included in the all-round care package:",
113
+ "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",
114
+ "addServicesTooltipOutroFinancing": "You can either pay the package cost as a one-off amount of €1,499.00, or have it included in the vehicle financing. If included in financing, no separate one-off payment for the package is charged. The payment method you choose, along with the resulting monthly financing rate and total amount, is shown in the financing details. The package cost is included in the total amount shown in either case.",
115
+ "addServicesTooltipOutroLeasing": "You can either pay the package cost as a one-off amount of €1,499.00, or have it included in the monthly leasing rate. If included in the rate, no separate one-off payment for the package is charged. The payment method you choose, along with the resulting monthly rate and total amount, is shown in the leasing details. The package cost is included in the total amount shown in either case."
109
116
  },
110
117
  "specTable": {
111
118
  "title": "Vehicle data",
@@ -691,7 +698,7 @@
691
698
  "vehiclePrice": "Vehicle price (incl. VAT, if applicable)",
692
699
  "leasingAmount": "Leasing amount",
693
700
  "netLoanAmount": "Net loan amount",
694
- "downPayment": "Down payment",
701
+ "downPayment": "Special payment",
695
702
  "annualMileage": "Mileage / year",
696
703
  "term": "Contract term",
697
704
  "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.80",
3
+ "version": "1.0.82",
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: 12px 16px 64px !important;
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 16px 18px !important;
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 14px 48px !important;
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,96 @@ 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
+ cursor: default;
7188
+ }
7189
+ .md3 .fin-modal .fin-toggle-addservices--cash .fin-toggle-tooltip {
7190
+ top: auto;
7191
+ bottom: auto;
7192
+ }
7193
+ .md3 .fin-modal .show-mobile .fin-toggle-tooltip {
7194
+ border-radius: 11px;
7195
+ }
7196
+ /* desktop (.hide-mobile) row sits close to the top of a short, non-scrolling card — opening
7197
+ upward has nowhere near enough room for the now-long financing/leasing copy (intro + 9
7198
+ bullets + outro). */
7199
+ .md3 .fin-modal .hide-mobile .fin-toggle-tooltip {
7200
+ bottom: auto;
7201
+ }
7202
+ .md3 .fin-calc-card .fin-toggle-tooltip-close,
7203
+ .md3 .fin-modal .fin-toggle-tooltip-close {
7204
+ position: absolute;
7205
+ top: -43px;
7206
+ right: -8px;
7207
+ display: flex;
7208
+ align-items: center;
7209
+ justify-content: center;
7210
+ width: 40px;
7211
+ height: 40px;
7212
+ border-radius: 50%;
7213
+ background: rgba(11, 19, 32, 0.45);
7214
+ border: 1.5px solid rgba(255, 255, 255, 0.25);
7215
+ padding: 0;
7216
+ cursor: pointer;
7217
+ }
7218
+ .md3 .fin-toggle-tooltip-body ul {
7219
+ display: flex;
7220
+ flex-direction: column;
7221
+ gap: 4px;
7222
+ margin: 0;
7223
+ padding-left: 14px;
7224
+ }
7225
+ /* .fin-calc-card's own cash toggle only (not the mobile sheet's — that one keeps the
7226
+ standalone look above). Drops the standalone-toggle chrome so it reads as one more
7227
+ .calcrow under Zahlung/Verfügbarkeit: same padding, no border, same label/value type. */
7228
+ .md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash {
7229
+ padding: 8px 0 !important;
7230
+ border-top: 0;
7231
+ border-bottom: 0;
7232
+ align-items: baseline;
7233
+ gap: 12px;
7234
+ }
7235
+ .md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm {
7236
+ font-family: inherit;
7237
+ font-weight: 600;
7238
+ font-size: 11px;
7239
+ line-height: normal;
7240
+ letter-spacing: 0.4px;
7241
+ color: var(--md-on-surface-variant);
7242
+ }
7243
+ .md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .md-title-sm .lucide {
7244
+ display: none;
7245
+ }
7246
+ .md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash .num,
7247
+ .md3 .fin-calc-card .fin-toggle-addservices.fin-toggle-addservices--cash > div > span:last-child {
7248
+ font-family: var(--md-font-display) !important;
7249
+ font-weight: 600 !important;
7250
+ font-size: 13.5px !important;
7251
+ line-height: normal !important;
7252
+ color: var(--md-on-surface) !important;
7253
+ }
7091
7254
  .md3 .fin-sheet-cta-row {
7092
7255
  border-top: 1px solid var(--fig-tile-border);
7093
7256
  padding-top: 20px;
@@ -8155,7 +8318,7 @@ color: #005CFF;
8155
8318
  /* modal-only: sup sits higher here than on the desktop card's mobile-cash price
8156
8319
  (same shared .fin-rate-cash-mobile), so scope via the modal's --cash modifier */
8157
8320
  .md3 .fin-price-grid--cash .fin-rate-cash-mobile sup {
8158
- top: -15px;
8321
+ top: 4px;
8159
8322
  }
8160
8323
  /* digit-count sizing (round 15): Kaufen/Barzahlung hero price shrinks as its digit
8161
8324
  count grows — repeats the class for specificity so it wins over the 52px default