@bytebrand/fe-ui-core-autobahn 1.0.21 → 1.0.23

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.
@@ -14,6 +14,7 @@
14
14
  "leaseTerms": "{{mileage}} km/Jahr · {{months}} Monate",
15
15
  "purchasePrice": "Kaufpreis",
16
16
  "priceDropAmount": "−{{amount}} €",
17
+ "priceDropSave": "{{amount}} € sparen",
17
18
  "savedAmount": "{{amount}} € gespart",
18
19
  "zeroDownCompact": "0 € Anz.",
19
20
  "zeroDown": "0 € Anzahlung",
@@ -414,7 +415,9 @@
414
415
  "back": "zurück",
415
416
  "share": "teilen",
416
417
  "photos": "Alle {{count}} Fotos",
417
- "photos_plural": "Alle {{count}} Fotos"
418
+ "photos_plural": "Alle {{count}} Fotos",
419
+ "photosCount": "{{count}} Fotos",
420
+ "photosCount_plural": "{{count}} Fotos"
418
421
  },
419
422
  "favorite": {
420
423
  "label": "merken"
@@ -14,6 +14,7 @@
14
14
  "leaseTerms": "{{mileage}} km/year · {{months}} months",
15
15
  "purchasePrice": "Purchase price",
16
16
  "priceDropAmount": "−{{amount}} €",
17
+ "priceDropSave": "Save {{amount}} €",
17
18
  "savedAmount": "€{{amount}} saved",
18
19
  "zeroDownCompact": "€0 down",
19
20
  "zeroDown": "€0 down payment",
@@ -414,7 +415,9 @@
414
415
  "back": "Back",
415
416
  "share": "Share",
416
417
  "photos": "All {{count}} photos",
417
- "photos_plural": "All {{count}} photos"
418
+ "photos_plural": "All {{count}} photos",
419
+ "photosCount": "{{count}} photos",
420
+ "photosCount_plural": "{{count}} photos"
418
421
  },
419
422
  "favorite": {
420
423
  "label": "Save"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
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
@@ -155,12 +155,12 @@
155
155
  NOT the 38px tappable filter-chip the base .chip is. */
156
156
  .md3 .listcard-chips .chip { height: 24px; padding: 0 10px; font-size: 11.5px; border-radius: 8px; cursor: default; }
157
157
  .md3 .listcard-chips .chip:hover { background: var(--md-surface); border-color: var(--md-outline-variant); }
158
- /* VDP trust row (Sofort verfügbar/views/favorites/Geprüft) — same compact non-interactive
159
- sizing as the listcard equipment tags, standalone (not nested under .listcard-chips). */
160
- .md3 .vdp-trust-chip { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px;
161
- border-radius: 8px; font-size: 11.5px; font-weight: 500; line-height: 1; cursor: default; white-space: nowrap;
162
- background: var(--md-surface); color: var(--md-on-surface-variant); border: 1px solid var(--md-outline-variant); }
163
- .md3 .vdp-trust-chip .lucide { width: 14px; height: 14px; flex: none; }
158
+ /* VDP trust row (Sofort verfügbar/views/favorites/Geprüft) — mobile-only, so the figma
159
+ push-chip palette is used literally (light blue-gray fill, dark bold label). */
160
+ .md3 .vdp-trust-chip { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px;
161
+ border-radius: 11px; font-size: 11px; font-weight: 700; line-height: 1; cursor: default; white-space: nowrap;
162
+ background: #F5F7FA; color: #1D2A38; border: 1px solid #E5EAF0; }
163
+ .md3 .vdp-trust-chip .lucide { width: 16px; height: 16px; flex: none; }
164
164
 
165
165
  /* status / rating chip (no CTA) */
166
166
  .md3 .statchip { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px;
@@ -174,12 +174,17 @@
174
174
  border-radius: var(--md-r-full); background: rgba(0,0,0,.55); color: #fff; font-size: 12px; font-weight: 500; backdrop-filter: blur(2px); }
175
175
  .md3 .imglabel .lucide { width: 14px; height: 14px; }
176
176
 
177
- /* mobile-only row under the gallery image: photo-count (plain text link, not an overlay
178
- pill — the dark .imglabel pill only makes sense against the image itself) · favorite/share. */
179
- .md3 .gallery-controls-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 2px 0; }
180
- .md3 .gallery-photos-btn { display: inline-flex; align-items: center; gap: 6px; border: 0; background: none;
181
- cursor: pointer; font: inherit; font-size: 13px; font-weight: 600; color: var(--md-on-surface-variant); padding: 0; }
182
- .md3 .gallery-photos-btn .lucide { width: 15px; height: 15px; }
177
+ /* mobile-only row under the gallery image (mockup): bordered "X Fotos" pill · slider dots ·
178
+ share/favorite — all in ONE row in normal flow, nothing overlaid on the image. */
179
+ .md3 .gallery-controls-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 2px 0; }
180
+ .md3 .gallery-photos-btn { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font: inherit;
181
+ font-size: 12px; font-weight: 700; color: var(--md-on-surface); height: 34px; padding: 0 13px; flex: none;
182
+ background: #fff; border: 1px solid #D4DDE7; border-radius: 16px; }
183
+ .md3 .gallery-photos-btn .lucide { width: 17px; height: 17px; }
184
+ /* page-driven slider dots (the carousel's own overlay dots are hidden on the phone VDP) */
185
+ .md3 .gallery-dots { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; }
186
+ .md3 .gallery-dots .gdot { width: 6px; height: 6px; border-radius: 3px; background: #D4DDE7; flex: none; }
187
+ .md3 .gallery-dots .gdot--on { width: 30px; height: 5px; background: var(--md-on-surface); }
183
188
 
184
189
  /* ---------- cards ---------- */
185
190
  .md3 .mcard { background: var(--md-sc-lowest); border-radius: var(--md-r-md); border: 1px solid var(--md-outline-variant);
@@ -1160,8 +1165,10 @@
1160
1165
  margin-bottom: -1px; transition: color .15s, border-color .15s; text-align: center; }
1161
1166
  .md3 .fin-tab.on { color: var(--md-primary); border-bottom-color: var(--md-primary); }
1162
1167
  .md3 .fin-tab:hover:not(.on) { color: var(--md-on-surface); }
1163
- /* 2-col grid: left grows, right fixed 148px — pill (row 1) and bar (row 2) share a column edge, never wrap */
1164
- .md3 .fin-price-grid { display: grid; grid-template-columns: 1fr 148px; gap: 6px 16px; align-items: center; margin-top: 8px; }
1168
+ /* 2-col grid: left grows, right fixed 148px — pill (row 1) and bar (row 2) share a column edge, never wrap.
1169
+ minmax(0,1fr) lets the rate column SHRINK below its content (plain 1fr's min is auto, which let the
1170
+ 52px phone rate blow the whole card past the viewport). */
1171
+ .md3 .fin-price-grid { display: grid; grid-template-columns: minmax(0, 1fr) 148px; gap: 6px 16px; align-items: center; margin-top: 8px; }
1165
1172
  .md3 .fin-rate-main { min-width: 0; }
1166
1173
  /* rating pill — light tier-tinted bg, tier-colored text + icon (bg/color set inline per tier) */
1167
1174
  .md3 .fin-eval-pill { display: inline-flex; align-items: center; gap: 5px; padding: 6px 13px;
@@ -1463,13 +1470,35 @@
1463
1470
  display: flex; align-items: center; justify-content: center; rotate: 45deg; box-shadow: var(--md-e2); }
1464
1471
  .md3 .dealer-pin .lucide { rotate: -45deg; }
1465
1472
 
1466
- /* VDP: floating back button over the gallery — phone only (breadcrumb is hidden there) */
1473
+ /* VDP: floating back button over the gallery — hidden everywhere now (the phone mockup
1474
+ shows a clean image with only the nav arrows; desktop has the breadcrumb). */
1467
1475
  .md3 .vdp-back { display: none; position: absolute; top: 12px; left: 14px; z-index: 3; }
1468
1476
 
1477
+ /* mobile push-badges row ABOVE the gallery (mockup): flat tinted pills in normal flow —
1478
+ replaces the white overlay chips (DropChip & co) that sat on the image. Figma palette
1479
+ used literally; one non-wrapping swipeable line. */
1480
+ .md3 .vdp-push-badges { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; overflow-x: auto;
1481
+ scrollbar-width: none; margin-bottom: 10px; }
1482
+ .md3 .vdp-push-badges::-webkit-scrollbar { display: none; }
1483
+ .md3 .push-badge { display: inline-flex; align-items: center; justify-content: center; height: 28px; padding: 0 13px;
1484
+ border-radius: 11px; font-size: 13.5px; font-weight: 700; line-height: 1; white-space: nowrap; flex: none; }
1485
+ .md3 .push-badge--save { background: #E6F7EE; color: #168A45; border: 1px solid rgba(22,138,69,.07); }
1486
+ .md3 .push-badge--discount { background: #E60000; color: #fff; }
1487
+ .md3 .push-badge--down { background: #EAF2FF; color: #005CFF; border: 1px solid rgba(0,92,255,.05); }
1488
+
1469
1489
  @media (max-width: 600px) {
1470
1490
  /* full-bleed gallery: edge-to-edge, app-like (production pattern) */
1471
- .md3 .vdp-back { display: flex; }
1472
- .md3 .vdp-gallerybox { margin: -14px -24px 0; border-radius: 0; border: 0; box-shadow: none; }
1491
+ .md3 .vdp-gallerybox { margin: 0 -24px; border-radius: 0; border: 0; box-shadow: none; }
1492
+ /* the carousel's own overlay dots move into the below-image controls row (page-rendered;
1493
+ the dots div carries an inline display:flex — hence !important) */
1494
+ .md3 .vdp-gallerybox .cardcar-dots { display: none !important; }
1495
+ /* mockup hero rate is a notch bigger on phone (inline style says 48 — hence !important) */
1496
+ .md3 .fin-rate-main .num { font-size: 52px !important; letter-spacing: -1.5px !important; }
1497
+ /* narrower rating column (figma bar is 115px) frees room for the bigger rate; let the
1498
+ € /mtl. suffix wrap under the number instead of pushing the grid wide on 4-digit rates */
1499
+ .md3 .fin-price-grid { grid-template-columns: minmax(0, 1fr) 112px; }
1500
+ .md3 .fin-rate-main > div { flex-wrap: wrap; }
1501
+ .md3 .fin-rate-main .md-body-sm { font-size: 15px !important; font-weight: 700 !important; color: var(--md-on-surface-variant) !important; }
1473
1502
  }
1474
1503
 
1475
1504
  /* ---------- save-search bar ---------- */
@@ -99,6 +99,7 @@ const CardCarousel: React.FunctionComponent<CardCarouselProps> = ({
99
99
  <Icon name="chevron-right" size={20} />
100
100
  </button>
101
101
  <div
102
+ className="cardcar-dots"
102
103
  style={{
103
104
  position: 'absolute',
104
105
  bottom: 9,
@@ -312,7 +312,17 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
312
312
  className={'fin-tab' + (mode === k ? ' on' : '')}
313
313
  onClick={() => onModeChange(k)}
314
314
  >
315
- {t(labelKey)}
315
+ {k === 'cash' ? (
316
+ <>
317
+ {/* mockup names the cash tab "Kaufen" on phone; desktop keeps "Barzahlung" */}
318
+ <span className="hide-mobile">{t(labelKey)}</span>
319
+ <span className="show-mobile" style={{ justifyContent: 'center' }}>
320
+ {t('autobahn:card.price.buy')}
321
+ </span>
322
+ </>
323
+ ) : (
324
+ t(labelKey)
325
+ )}
316
326
  </button>
317
327
  ))}
318
328
  </div>
@@ -469,34 +479,30 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
469
479
 
470
480
  {/* ============================== MOBILE (≤600px) — new layout ============================== */}
471
481
  <div className="show-mobile" style={{ flexDirection: 'column' }}>
472
- {/* side-by-side CTAs */}
473
- <div style={{ display: 'flex', gap: 10, marginTop: 14 }}>
474
- <Button variant="filled" size="lg" icon="shield-check" style={{ flex: 1 }} onClick={onSecure}>
482
+ {/* side-by-side CTAs — mockup ratio ~2:1, with the tiny auto.de-ID sitting
483
+ directly under the Anrufen button (not in its own full-width row). */}
484
+ <div style={{ display: 'flex', gap: 10, marginTop: 14, alignItems: 'flex-start' }}>
485
+ <Button variant="filled" size="lg" icon="shield-check" style={{ flex: 2 }} onClick={onSecure}>
475
486
  {t('autobahn:finance.secureOffer')}
476
487
  </Button>
477
- <Button variant="outlined" size="lg" icon="phone" style={{ flex: 1 }} onClick={onCall}>
478
- {t('autobahn:finance.callCta')}
479
- </Button>
480
- </div>
481
-
482
- {/* auto.de-ID (left) · Finanzierungs-/Leasingdetails (right) — same row desktop shows,
483
- restored here per the mockup (round 1 had dropped it in favor of Persönliche Beratung
484
- alone; the mockup wants both rows, not one replacing the other). */}
485
- {(autoDeId || (!isCash && onShowDetails)) && (
486
- <div className="fin-info">
487
- {autoDeId && <span className="fin-id">auto.de-ID {autoDeId}</span>}
488
- {!isCash && onShowDetails && (
489
- <DetailsLink
490
- label={mode === 'leasing' ? t('autobahn:finance.leasingDetails') : t('autobahn:finance.financingDetails')}
491
- onClick={onShowDetails}
492
- />
488
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
489
+ <Button variant="outlined" size="lg" icon="phone" block onClick={onCall}>
490
+ {t('autobahn:finance.callCta')}
491
+ </Button>
492
+ {autoDeId && (
493
+ <span
494
+ className="num"
495
+ style={{ fontSize: 10, fontWeight: 700, color: 'var(--md-primary)', textAlign: 'center', whiteSpace: 'nowrap' }}
496
+ >
497
+ auto.de ID: {autoDeId}
498
+ </span>
493
499
  )}
494
500
  </div>
495
- )}
501
+ </div>
496
502
 
497
- {/* Persönliche Beratung (left) · Finanzierungs-/Leasingdetails or hint (right) */}
503
+ {/* Persönliche Beratung (left) · divider · Finanzierungs-/Leasingdetails or hint (right) */}
498
504
  <div className="fin-info">
499
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
505
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, flex: 1 }}>
500
506
  <Icon name="user" size={18} color="var(--md-on-surface-variant)" style={{ flex: 'none' }} />
501
507
  <div style={{ minWidth: 0 }}>
502
508
  <div className="md-label-sm" style={{ fontWeight: 700, color: 'var(--md-on-surface)' }}>
@@ -505,9 +511,30 @@ const FinanceCalculator: React.FunctionComponent<FinanceCalculatorProps> = ({
505
511
  <div className="md-label-sm">{t('autobahn:finance.personalConsultationSub')}</div>
506
512
  </div>
507
513
  </div>
508
- {/* non-cash: the Finanzierungsdetails link already sits on the auto.de-ID row
509
- above — showing it here too was a duplicate (round-3 regression). */}
510
- {isCash && (
514
+ <span style={{ width: 1, alignSelf: 'stretch', background: 'var(--md-outline-variant)', flex: 'none' }} />
515
+ {!isCash && onShowDetails ? (
516
+ <button
517
+ type="button"
518
+ onClick={onShowDetails}
519
+ style={{
520
+ display: 'flex',
521
+ alignItems: 'center',
522
+ gap: 8,
523
+ background: 'none',
524
+ border: 0,
525
+ padding: 0,
526
+ cursor: 'pointer',
527
+ font: 'inherit',
528
+ color: 'var(--md-on-surface)',
529
+ textAlign: 'left',
530
+ }}
531
+ >
532
+ <Icon name="info" size={18} color="var(--md-on-surface-variant)" style={{ flex: 'none' }} />
533
+ <span className="md-label-sm" style={{ maxWidth: 82, lineHeight: 1.5 }}>
534
+ {mode === 'leasing' ? t('autobahn:finance.leasingDetails') : t('autobahn:finance.financingDetails')}
535
+ </span>
536
+ </button>
537
+ ) : (
511
538
  <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: 'var(--md-on-surface-variant)' }}>
512
539
  <Icon name="info" size={15} />
513
540
  <span className="md-label-sm">{t('autobahn:finance.financingLeasingPossible')}</span>
@@ -175,7 +175,8 @@ export interface VdpTrustRowProps {
175
175
  * availability · view count · favorite count · "Geprüft" (every auto.de listing is
176
176
  * 130-point inspected, so this one is always shown, not conditional real data). */
177
177
  export const VdpTrustRow: React.FunctionComponent<VdpTrustRowProps> = ({ available, viewCount, favCount, t }) => (
178
- <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6, marginTop: 10 }}>
178
+ // one swipeable line (mockup), never a ragged second row
179
+ <div style={{ display: 'flex', flexWrap: 'nowrap', overflowX: 'auto', scrollbarWidth: 'none', alignItems: 'center', gap: 6, marginTop: 10 }}>
179
180
  {available && (
180
181
  <span className="vdp-trust-chip">
181
182
  <Icon name="check" size={13} stroke={2.4} color="var(--md-good)" />
@@ -1278,7 +1279,8 @@ export const FinanceDetailsModal: React.FunctionComponent<FinanceDetailsModalPro
1278
1279
  [
1279
1280
  ['fin', 'autobahn:finance.mode.financing'],
1280
1281
  ['leasing', 'autobahn:finance.mode.leasing'],
1281
- ['cash', 'autobahn:finance.mode.cash'],
1282
+ // this sheet is mobile-only, so the cash tab is "Kaufen" here (desktop card says "Barzahlung")
1283
+ ['cash', 'autobahn:card.price.buy'],
1282
1284
  ] as Array<[FinanceMode, string]>
1283
1285
  ).map(([k, labelKey]) => (
1284
1286
  <button