@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 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 }}
@@ -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>
@@ -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,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": "Anzahlung",
698
+ "downPayment": "Sonderzahlung",
695
699
  "annualMileage": "Laufleistung / Jahr",
696
700
  "term": "Vertragslaufzeit",
697
701
  "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,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": "Down payment",
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.79",
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
- .md3 .cc-card .deal-chip {
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: 12px 16px 64px !important;
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 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;
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 14px 48px !important;
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: -15px;
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