@bytebrand/fe-ui-core-autobahn 1.0.56 → 1.0.57

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.
@@ -188,7 +188,10 @@
188
188
  "requestLabel": "Jetzt Anfragen",
189
189
  "threeSixtyLabel": "360°-Ansicht",
190
190
  "counterLabel": "Bild {{current}} von {{total}}",
191
- "moreThumbsLabel": "{{count}} weitere Bilder"
191
+ "moreThumbsLabel": "{{count}} weitere Bilder",
192
+ "allPhotosTitle": "Alle {{count}} Fotos",
193
+ "allPhotosSubtitle": "Tippe auf ein Foto, um es zu öffnen.",
194
+ "allPhotosFooter": "{{count}} Fotos insgesamt"
192
195
  },
193
196
  "chrome": {
194
197
  "menu": "Menü",
@@ -188,7 +188,10 @@
188
188
  "requestLabel": "Enquire now",
189
189
  "threeSixtyLabel": "360° view",
190
190
  "counterLabel": "Image {{current}} of {{total}}",
191
- "moreThumbsLabel": "{{count}} more images"
191
+ "moreThumbsLabel": "{{count}} more images",
192
+ "allPhotosTitle": "All {{count}} photos",
193
+ "allPhotosSubtitle": "Tap a photo to open it.",
194
+ "allPhotosFooter": "{{count}} photos total"
192
195
  },
193
196
  "chrome": {
194
197
  "menu": "Menu",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/fe-ui-core-autobahn",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
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
@@ -465,7 +465,7 @@ line-height: 31px;
465
465
  height: 34px;
466
466
  padding: 0 13px;
467
467
  flex: none;
468
- background: #fff;
468
+ background-color: hsla(0, 0%, 100%, .75);
469
469
  border: 1px solid #d4dde7;
470
470
  border-radius: 16px;
471
471
  }
@@ -3258,6 +3258,16 @@ line-height: 31px;
3258
3258
  opacity: 0.92;
3259
3259
  }
3260
3260
  }
3261
+ /* VDP gallery arrows only — .car-arrow is shared with the SRP card carousels (CarCard/CarRow),
3262
+ so this is scoped to .vdp-gallerybox rather than the base rule. */
3263
+ .md3 .vdp-gallerybox .car-arrow {
3264
+ width: 36px;
3265
+ height: 36px;
3266
+ border-radius: 18px;
3267
+ background-color: hsla(0, 0%, 100%, .75);
3268
+ border: 1px solid #D4DDE7;
3269
+ box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.08);
3270
+ }
3261
3271
 
3262
3272
  /* ==========================================================================
3263
3273
  RESPONSIVE LAYOUT LAYER
@@ -4728,6 +4738,16 @@ line-height: 31px;
4728
4738
  .md3 .enquiry-form {
4729
4739
  padding: 24px;
4730
4740
  }
4741
+ /* Vorname/Nachname/E-Mail carry their border/background as inline style (enquiryInput) —
4742
+ !important is required here to win over the inline style on focus. */
4743
+ .md3 .enquiry-name-row input:focus,
4744
+ .md3 .enquiry-email-wrap input:focus {
4745
+ background: #FFFFFF !important;
4746
+ border: 1px solid #005CFF !important;
4747
+ border-radius: 10px !important;
4748
+ outline: 0;
4749
+ box-shadow: none;
4750
+ }
4731
4751
  /* outlined float-label fields (Figma phone/message row): label sits ON the top border */
4732
4752
  .md3 .enq-float {
4733
4753
  position: relative;
@@ -4754,12 +4774,25 @@ line-height: 31px;
4754
4774
  border: 0;
4755
4775
  background: none;
4756
4776
  outline: 0;
4777
+ box-shadow: none;
4757
4778
  color: var(--md-on-surface);
4758
4779
  font: inherit;
4759
4780
  font-size: 14px;
4760
4781
  height: 46px;
4761
4782
  padding: 0 12px;
4762
4783
  }
4784
+ /* focus state (Figma): the .enq-float wrapper carries the border/background — the
4785
+ input/textarea/mselect-btn inside stay borderless (see above) so only one outline shows. */
4786
+ .md3 .enq-float:focus-within {
4787
+ background: #FFFFFF;
4788
+ border: 1px solid #005CFF;
4789
+ border-radius: 10px;
4790
+ box-shadow: none;
4791
+ }
4792
+ .md3 .enq-float:focus-within .enq-float-label {
4793
+ background: #FFFFFF;
4794
+ color: #005CFF;
4795
+ }
4763
4796
  .md3 .enq-float textarea {
4764
4797
  height: 96px;
4765
4798
  padding: 12px;
@@ -4770,15 +4803,26 @@ line-height: 31px;
4770
4803
  grid-template-columns: 82px 96px 1fr;
4771
4804
  gap: 10px;
4772
4805
  }
4773
- .md3 .enq-country {
4774
- display: flex;
4775
- align-items: center;
4776
- justify-content: space-between;
4777
- gap: 4px;
4806
+ /* "Land" is a real Dropdown (.mselect) now, nested inside the .enq-float outline — strip
4807
+ its own trigger chrome (border/background/box-shadow ring) so the outer .enq-float border
4808
+ stays the only visible outline, and match the 46px height of the sibling inputs. */
4809
+ .md3 .enq-float .mselect-btn {
4778
4810
  height: 46px;
4779
4811
  padding: 0 10px 0 12px;
4780
- font-size: 14px;
4781
- color: var(--md-on-surface);
4812
+ gap: 4px;
4813
+ border: 0;
4814
+ background: none;
4815
+ outline: 0;
4816
+ box-shadow: none;
4817
+ }
4818
+ .md3 .enq-float .mselect-btn:hover,
4819
+ .md3 .enq-float .mselect-btn[aria-expanded="true"] {
4820
+ box-shadow: none;
4821
+ }
4822
+ .md3 .enq-float .mselect-btn .lucide {
4823
+ width: 14px;
4824
+ height: 14px;
4825
+ color: var(--md-on-surface-variant);
4782
4826
  }
4783
4827
  /* revocable-consent checkbox + microtext (Figma round 9) */
4784
4828
  .md3 .enq-consent {
@@ -5188,9 +5232,10 @@ line-height: 31px;
5188
5232
  }
5189
5233
  .md3 .hiw-section {
5190
5234
  background: var(--md-sc-lowest);
5191
- border: 1px solid var(--fig-border);
5235
+ border: 1px solid #D4DDE7;
5192
5236
  border-radius: 18px;
5193
5237
  padding: 30px 24px;
5238
+ box-shadow: none;
5194
5239
  }
5195
5240
  .md3 .hiw-heading {
5196
5241
  text-align: left;
@@ -5777,10 +5822,10 @@ line-height: 31px;
5777
5822
  grid-template-columns: 1fr 1fr;
5778
5823
  gap: 10px;
5779
5824
  background: var(--md-sc-lowest);
5780
- border: 1px solid var(--fig-border);
5825
+ border: 1px solid #D4DDE7;
5781
5826
  border-radius: 18px;
5782
5827
  padding: 18px 16px;
5783
- box-shadow: 0 2px 3px rgba(14, 34, 56, 0.07);
5828
+ box-shadow: none;
5784
5829
  }
5785
5830
  .md3 .trust-strip > div {
5786
5831
  display: flex;
@@ -5944,6 +5989,7 @@ line-height: 31px;
5944
5989
  }
5945
5990
  .md3 .vdp-push-badges {
5946
5991
  gap: 6px;
5992
+ margin-bottom: 13px;
5947
5993
  }
5948
5994
  }
5949
5995
 
@@ -6067,21 +6113,37 @@ color: #005CFF;
6067
6113
  }
6068
6114
 
6069
6115
  @media (max-width: 600px) {
6070
- /* full-bleed gallery: edge-to-edge, app-like. Fixed 200px height on phone (overrides
6071
- the 980px breakpoint's aspect-ratio: 4/3, which made the box too tall here). */
6116
+ /* full-bleed gallery: edge-to-edge, app-like. Extra height (vs. the plain photo height)
6117
+ reserves room at the bottom for the controls row, now overlaid on the image itself
6118
+ (round 15 — supersedes the earlier "no overlay" layout). */
6072
6119
  .md3 .vdp-gallerybox {
6073
6120
  margin: 0 -24px;
6074
6121
  border-radius: 0;
6075
6122
  border: 0;
6076
6123
  box-shadow: none;
6077
- height: 200px;
6124
+ height: 230px;
6078
6125
  aspect-ratio: auto;
6079
6126
  }
6080
- /* controls row sits BELOW the image in normal flow (per user: no overlay) */
6127
+ /* .vdp-gallery is the positioning context: .gallery-controls-row is its own sibling of
6128
+ .vdp-gallerybox in the DOM, so it needs a relative ancestor to overlay against. */
6129
+ .md3 .vdp-gallery {
6130
+ position: relative;
6131
+ }
6132
+ /* controls row now sits ON the image as an overlay, bottom-aligned (round 15) */
6081
6133
  .md3 .gallery-controls-row {
6082
- padding: 0 2px;
6134
+ position: absolute;
6135
+ left: 14px;
6136
+ right: 14px;
6137
+ bottom: 12px;
6138
+ z-index: 3;
6139
+ padding: 0;
6083
6140
  }
6084
- /* the carousel's own overlay dots move into the below-image controls row (page-rendered;
6141
+ /* share/save icon buttons: scoped to THIS row (not the base .icon-btn--filled, which is
6142
+ also the opaque vdp-back + desktop overlay buttons) — translucent so photo shows through. */
6143
+ .md3 .gallery-controls-row .icon-btn--filled {
6144
+ background-color: hsla(0, 0%, 100%, .75);
6145
+ }
6146
+ /* the carousel's own overlay dots move into the on-image controls row (page-rendered;
6085
6147
  the dots div carries an inline display:flex — hence !important) */
6086
6148
  .md3 .vdp-gallerybox .cardcar-dots {
6087
6149
  display: none !important;
@@ -6451,11 +6513,19 @@ color: #005CFF;
6451
6513
  .md3 .vdp-similar {
6452
6514
  margin-top: 14px !important;
6453
6515
  }
6516
+ .md3 #vdp-kontakt {
6517
+ border: 1px solid #D4DDE7;
6518
+ box-shadow: none;
6519
+ }
6454
6520
  .md3 #vdp-bewertungen {
6455
6521
  padding: 28px 24px !important;
6522
+ border: 1px solid #D4DDE7;
6523
+ box-shadow: none;
6456
6524
  }
6457
6525
  .md3 #vdp-preis {
6458
6526
  padding: 0 !important;
6527
+ border: 1px solid #D4DDE7;
6528
+ box-shadow: none;
6459
6529
  }
6460
6530
  .md3 #vdp-preis h2 {
6461
6531
  padding: 22px 22px 0 !important;
@@ -6694,6 +6764,8 @@ color: #005CFF;
6694
6764
  margin-bottom: 20px !important;
6695
6765
  padding: 18px !important;
6696
6766
  box-sizing: border-box;
6767
+ border: 1px solid #D4DDE7;
6768
+ box-shadow: none;
6697
6769
  display: flex;
6698
6770
  flex-direction: column;
6699
6771
  }
@@ -6823,6 +6895,10 @@ color: #005CFF;
6823
6895
  text-align: center;
6824
6896
  color: #ffffff;
6825
6897
  }
6898
+ .md3 #vdp-eco {
6899
+ border: 1px solid #D4DDE7;
6900
+ box-shadow: none;
6901
+ }
6826
6902
  .md3 #vdp-eco .md-label-sm {
6827
6903
  font-family: Arial, Helvetica, sans-serif;
6828
6904
  font-style: normal;
@@ -6833,6 +6909,8 @@ color: #005CFF;
6833
6909
  }
6834
6910
  .md3 #vdp-daten {
6835
6911
  padding: 26px 24px 34px 24px;
6912
+ border: 1px solid #D4DDE7;
6913
+ box-shadow: none;
6836
6914
  }
6837
6915
  .md3 #vdp-daten .mbtn--text {
6838
6916
  font-family: Arial, Helvetica, sans-serif;
@@ -6855,6 +6933,8 @@ color: #005CFF;
6855
6933
  }
6856
6934
  .md3 #vdp-ausstattung {
6857
6935
  padding: 28px 24px 19px 24px;
6936
+ border: 1px solid #D4DDE7;
6937
+ box-shadow: none;
6858
6938
  }
6859
6939
  .md3 #vdp-ausstattung h2 {
6860
6940
  margin: 0 0 30px;
@@ -7018,6 +7098,8 @@ color: #005CFF;
7018
7098
  }
7019
7099
  .md3 .enq-country {
7020
7100
  width: 70px;
7101
+ }
7102
+ .md3 .enq-country .mselect-label {
7021
7103
  font-family: Arial, Helvetica, sans-serif;
7022
7104
  font-style: normal;
7023
7105
  font-weight: 400;
@@ -8931,3 +9013,160 @@ color: #005CFF;
8931
9013
  background: var(--md-sc-lowest);
8932
9014
  box-shadow: none;
8933
9015
  }
9016
+
9017
+ /* ==========================================================================
9018
+ VdpViewer "all photos" bottom sheet (libs/packages/fe-ui-core-autobahn/vdp/VdpViewer.tsx)
9019
+ --------------------------------------------------------------------------
9020
+ New selectors only, own `.vdp-photosheet*` prefix — nothing above this block
9021
+ is touched. Static values live here; only per-state dynamics (slide
9022
+ transform/transition while open/dragging, backdrop opacity/pointer-events)
9023
+ stay inline in the component. 3 grid columns is the default (portrait,
9024
+ <=693px); the 693-860px media query below is the ONLY landscape-specific
9025
+ override, matching the one visual difference the spec calls for.
9026
+ ========================================================================== */
9027
+ .md3 .vdp-photosheet-backdrop {
9028
+ position: absolute;
9029
+ inset: 0;
9030
+ background: rgba(0, 0, 0, 0.5);
9031
+ z-index: 210;
9032
+ }
9033
+ .md3 .vdp-photosheet {
9034
+ position: absolute;
9035
+ left: 0;
9036
+ right: 0;
9037
+ bottom: 0;
9038
+ height: 100vh;
9039
+ z-index: 211;
9040
+ display: flex;
9041
+ flex-direction: column;
9042
+ /* same dark color as the viewer's own header bar (rgba(6,11,18,0.8)), but fully opaque: the
9043
+ header uses 0.8 alpha OVER a photo, whereas this panel sits over the dimmed backdrop + viewer
9044
+ behind it — at 0.8 alpha, high-contrast content back there (e.g. the blue CTA button) bled
9045
+ through visibly during testing, so opacity is dropped without changing the hue itself. */
9046
+ background: rgb(6, 11, 18);
9047
+ border-radius: 16px 16px 0 0;
9048
+ overflow: hidden;
9049
+ }
9050
+ .md3 .vdp-photosheet__header {
9051
+ position: relative;
9052
+ flex: none;
9053
+ display: flex;
9054
+ align-items: center;
9055
+ justify-content: center;
9056
+ min-height: 88px;
9057
+ padding-top: 10px;
9058
+ touch-action: none;
9059
+ }
9060
+ .md3 .vdp-photosheet__handle {
9061
+ position: absolute;
9062
+ top: 8px;
9063
+ left: 50%;
9064
+ transform: translateX(-50%);
9065
+ width: 36px;
9066
+ height: 4px;
9067
+ border-radius: 2px;
9068
+ background: rgba(255, 255, 255, 0.28);
9069
+ }
9070
+ .md3 .vdp-photosheet__close {
9071
+ position: absolute;
9072
+ left: 12px;
9073
+ top: 50%;
9074
+ transform: translateY(-50%);
9075
+ }
9076
+ .md3 .vdp-photosheet__logo {
9077
+ display: flex;
9078
+ align-items: center;
9079
+ justify-content: center;
9080
+ }
9081
+ .md3 .vdp-photosheet__logo--portrait {
9082
+ transform: scale(0.75);
9083
+ }
9084
+ .md3 .vdp-photosheet__titles {
9085
+ flex: none;
9086
+ padding: 0 16px 12px;
9087
+ text-align: left;
9088
+ }
9089
+ .md3 .vdp-photosheet__title {
9090
+ margin: 0;
9091
+ font-family: 'Inter', var(--md-font);
9092
+ font-size: 18px;
9093
+ font-weight: 700;
9094
+ line-height: 24px;
9095
+ color: #ffffff;
9096
+ }
9097
+ .md3 .vdp-photosheet__subtitle {
9098
+ margin: 4px 0 0;
9099
+ font-family: 'Inter', var(--md-font);
9100
+ font-size: 13px;
9101
+ font-weight: 400;
9102
+ line-height: 18px;
9103
+ color: rgba(255, 255, 255, 0.56);
9104
+ }
9105
+ .md3 .vdp-photosheet__scroll {
9106
+ flex: 1;
9107
+ min-height: 0;
9108
+ overflow-y: auto;
9109
+ padding: 0 12px 12px;
9110
+ }
9111
+ .md3 .vdp-photosheet__grid {
9112
+ display: grid;
9113
+ grid-template-columns: repeat(3, 1fr);
9114
+ gap: 8px;
9115
+ }
9116
+ .md3 .vdp-photosheet__tile {
9117
+ position: relative;
9118
+ aspect-ratio: 4 / 3;
9119
+ box-sizing: border-box;
9120
+ border-radius: 10px;
9121
+ overflow: hidden;
9122
+ border: 1px solid rgba(255, 255, 255, 0.16);
9123
+ padding: 0;
9124
+ cursor: pointer;
9125
+ background: rgba(255, 255, 255, 0.06);
9126
+ }
9127
+ .md3 .vdp-photosheet__tile img {
9128
+ width: 100%;
9129
+ height: 100%;
9130
+ object-fit: cover;
9131
+ display: block;
9132
+ }
9133
+ .md3 .vdp-photosheet__tile--active {
9134
+ border: 3px solid #0b63f6;
9135
+ }
9136
+ .md3 .vdp-photosheet__badge {
9137
+ position: absolute;
9138
+ top: 6px;
9139
+ right: 6px;
9140
+ width: 20px;
9141
+ height: 20px;
9142
+ border-radius: 50%;
9143
+ background: #0b63f6;
9144
+ display: flex;
9145
+ align-items: center;
9146
+ justify-content: center;
9147
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
9148
+ }
9149
+ .md3 .vdp-photosheet__footer {
9150
+ flex: none;
9151
+ display: flex;
9152
+ align-items: center;
9153
+ justify-content: flex-start;
9154
+ gap: 6px;
9155
+ height: 44px;
9156
+ padding: 0 16px;
9157
+ /* rgba(0,0,0,0.5) was invisible here — it darkens whatever's behind it, and the sheet's own
9158
+ panel (rgb(6,11,18)) is already almost black, so a 50%-black overlay on top of near-black
9159
+ reads as no change at all. #1c222c is lighter/bluer and already used elsewhere in this same
9160
+ file (landscape's "Anrufen" CTA background), so it's a real, visible band without a new tone. */
9161
+ background: #1c222c;
9162
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
9163
+ color: rgba(255, 255, 255, 0.72);
9164
+ font-family: 'Inter', var(--md-font);
9165
+ font-size: 13px;
9166
+ font-weight: 500;
9167
+ }
9168
+ @media (min-width: 693px) and (max-width: 860px) {
9169
+ .md3 .vdp-photosheet__grid {
9170
+ grid-template-columns: repeat(4, 1fr);
9171
+ }
9172
+ }
@@ -24,7 +24,7 @@
24
24
  import React, { useState } from 'react';
25
25
  import { TranslationFunction } from '../types';
26
26
 
27
- import { Button, Icon, Spec, StatChip, Stars, Switch } from '../ui';
27
+ import { Button, Dropdown, DropdownOption, Icon, Spec, StatChip, Stars, Switch } from '../ui';
28
28
  import { CO2_CLASSES, CO2_COLORS, TIER_LABELS, TIER_KEYS, TIER_COLORS, TIER_BACKGROUNDS } from '../constants';
29
29
  import { defaultVdpSpecs, EquipmentGroups, PriceRatingBands, VehicleDetail, VdpSpec, VdpReview } from './types';
30
30
  import { CalcRow, DocCheckIcon, EvalBar, expanderBtnStyle, FinanceMode, Slider, TermChips } from './FinanceCalculator';
@@ -2495,6 +2495,7 @@ export const ReviewsTeaser: React.FunctionComponent<ReviewsTeaserProps> = ({ cou
2495
2495
  alignItems: 'center',
2496
2496
  justifyContent: 'space-between',
2497
2497
  gap: 12,
2498
+ border: '1px solid #D4DDE7',
2498
2499
  // sits between the finance card (order:3) and the highlights card (order:4) in the
2499
2500
  // mobile .vdp-grid flex flow — tie-broken to AFTER the aside by JSX/DOM source order.
2500
2501
  order: 3,
@@ -2682,6 +2683,12 @@ const FloatField: React.FunctionComponent<{
2682
2683
  </label>
2683
2684
  );
2684
2685
 
2686
+ /** phone country-code options for the "Land" dropdown (Figma: DE/AT/CH pinned first, then A–Z). */
2687
+ const ENQUIRY_COUNTRY_CODES: DropdownOption[] = [
2688
+ '+49', '+43', '+41', '+7', '+31', '+32', '+33', '+34', '+36', '+39', '+40', '+44', '+45', '+46',
2689
+ '+47', '+48', '+352', '+358', '+359', '+380', '+385', '+386', '+420', '+421', '+423',
2690
+ ].map((code) => ({ value: code, label: code }));
2691
+
2685
2692
  /** [icon, i18n label key] — resolved against the drilled t in the component. */
2686
2693
  const ENQUIRY_BENEFITS: ReadonlyArray<[string, string]> = [
2687
2694
  ['clock', 'autobahn:enquiry.benefits.fastResponse'],
@@ -2704,6 +2711,7 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2704
2711
  const [firstName, setFirstName] = useState('');
2705
2712
  const [lastName, setLastName] = useState('');
2706
2713
  const [email, setEmail] = useState('');
2714
+ const [countryCode, setCountryCode] = useState('+49');
2707
2715
  const [areaCode, setAreaCode] = useState('');
2708
2716
  const [phoneNum, setPhoneNum] = useState('');
2709
2717
  const [consent, setConsent] = useState(false);
@@ -2719,15 +2727,18 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2719
2727
 
2720
2728
  const emailOk = /.+@.+\..+/.test(email);
2721
2729
  const phoneDigits = (areaCode + phoneNum).replace(/\D/g, '');
2722
- const phoneOk = phoneDigits.length >= 6;
2723
- // Round 13 (explicit spec): PHONE is always required (the * sits on the country-code field),
2724
- // e-mail is always optional (validated only when filled); names + consent always required.
2730
+ // backend (contact-form-users) caps the concatenated phoneMain (countryCode + areaCode +
2731
+ // phoneNum) at 22 chars total — mirrored here so an over-length number is caught before submit.
2732
+ const phoneOk = phoneDigits.length >= 6 && countryCode.length + areaCode.trim().length + phoneNum.trim().length <= 22;
2733
+ // Round 14: which channel is required follows the E-Mail/Telefon toggle — the * moves
2734
+ // between the e-mail field and the country-code field accordingly; the other channel
2735
+ // stays optional (validated only when filled); names + consent always required.
2725
2736
  const valid =
2726
2737
  firstName.trim().length > 0 &&
2727
2738
  lastName.trim().length > 0 &&
2728
2739
  consent &&
2729
- phoneOk &&
2730
- (email.trim().length === 0 || emailOk);
2740
+ (pref === 'email' ? emailOk : phoneOk) &&
2741
+ (pref === 'email' ? phoneDigits.length === 0 || phoneOk : email.trim().length === 0 || emailOk);
2731
2742
 
2732
2743
  const submit = async (): Promise<void> => {
2733
2744
  if (!valid || sending) return;
@@ -2736,7 +2747,7 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2736
2747
  const note = t('autobahn:enquiry.preferredContact', {
2737
2748
  channel: pref === 'email' ? t('autobahn:enquiry.contactMethod.email') : t('autobahn:enquiry.contactMethod.phone'),
2738
2749
  });
2739
- const phone = phoneDigits ? `+49 ${areaCode.trim()} ${phoneNum.trim()}`.replace(/\s+/g, ' ').trim() : '';
2750
+ const phone = phoneDigits ? `${countryCode}${areaCode.trim()}${phoneNum.trim()}`.replace(/\s+/g, '') : '';
2740
2751
  try {
2741
2752
  await onSubmit({
2742
2753
  name: `${firstName.trim()} ${lastName.trim()}`,
@@ -2843,26 +2854,30 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2843
2854
  <input
2844
2855
  style={enquiryInput}
2845
2856
  type="email"
2846
- // e-mail is always optional (round 13) — no required-*
2847
- placeholder={t('autobahn:enquiry.form.emailPlaceholder').replace(/\*$/, '')}
2857
+ // asterisk shows only while e-mail is the selected contact channel (round 14)
2858
+ placeholder={pref === 'email' ? t('autobahn:enquiry.form.emailPlaceholder') : t('autobahn:enquiry.form.emailPlaceholder').replace(/\*$/, '')}
2848
2859
  value={email}
2849
2860
  onChange={(e) => setEmail(e.target.value)}
2850
2861
  aria-label={t('autobahn:enquiry.contactMethod.email')}
2851
2862
  />
2852
2863
  </div>
2853
- {/* Land (+49 fixed) · Vorwahl · Telefonnummer — outlined float-label row (Figma) */}
2864
+ {/* Land (country-code dropdown) · Vorwahl · Telefonnummer — outlined float-label row (Figma) */}
2854
2865
  <div className="enq-phone-row" style={{ marginTop: 16 }}>
2855
- {/* phone is always required — the * sits on the country-code field (round 13) */}
2856
- <FloatField label={t('autobahn:enquiry.form.countryLabel') + '*'}>
2857
- <span className="enq-country num">
2858
- +49
2859
- <Icon name="chevron-down" size={14} color="var(--md-on-surface-variant)" />
2860
- </span>
2866
+ {/* * sits on the country-code field only while Telefon is the selected channel (round 14) */}
2867
+ <FloatField label={t('autobahn:enquiry.form.countryLabel') + (pref === 'phone' ? '*' : '')}>
2868
+ <Dropdown
2869
+ value={countryCode}
2870
+ options={ENQUIRY_COUNTRY_CODES}
2871
+ onChange={setCountryCode}
2872
+ ariaLabel={t('autobahn:enquiry.form.countryLabel')}
2873
+ className="enq-country"
2874
+ />
2861
2875
  </FloatField>
2862
2876
  <FloatField label={t('autobahn:enquiry.form.areaCodeLabel')}>
2863
2877
  <input
2864
2878
  type="tel"
2865
2879
  placeholder="1638"
2880
+ maxLength={5}
2866
2881
  value={areaCode}
2867
2882
  onChange={(e) => setAreaCode(e.target.value)}
2868
2883
  aria-label={t('autobahn:enquiry.form.areaCodeLabel')}
@@ -2872,6 +2887,7 @@ export const VdpEnquiry: React.FunctionComponent<VdpEnquiryProps> = ({
2872
2887
  <input
2873
2888
  type="tel"
2874
2889
  placeholder="5857458"
2890
+ maxLength={13}
2875
2891
  value={phoneNum}
2876
2892
  onChange={(e) => setPhoneNum(e.target.value)}
2877
2893
  aria-label={t('autobahn:enquiry.form.phoneNumberLabel')}