@bytebrand/fe-ui-core-autobahn 1.0.56 → 1.0.58
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/chrome/TopBar.tsx +217 -10
- package/constants.ts +31 -31
- package/locales/de/autobahn.json +8 -1
- package/locales/en/autobahn.json +8 -1
- package/package.json +1 -1
- package/theme/md3.css +311 -17
- package/utils/financeFormat.ts +62 -62
- package/utils/priceRating.ts +58 -58
- package/vdp/VdpModules.tsx +32 -16
- package/vdp/VdpViewer.tsx +646 -56
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:
|
|
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
|
-
.
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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
|
-
|
|
4781
|
-
|
|
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 {
|
|
@@ -5058,6 +5102,61 @@ line-height: 31px;
|
|
|
5058
5102
|
box-shadow: var(--md-e2);
|
|
5059
5103
|
padding: 8px 16px 16px;
|
|
5060
5104
|
}
|
|
5105
|
+
|
|
5106
|
+
/* mobile-only "Account Layer" sheet (TopBar) — full-width panel under the header, per the Figma
|
|
5107
|
+
"03 | Account Layer" mockups. .show-mobile forces display:flex (see the ≤600px override
|
|
5108
|
+
below) — flex-direction:column comes from the inline style on the element itself. */
|
|
5109
|
+
.md3 .amenu-mobile {
|
|
5110
|
+
/* NOT full-width — measured against the real Figma export: the panel is anchored to the
|
|
5111
|
+
RIGHT edge, leaving ~22% of the viewport showing the dimmed page on the left (both the
|
|
5112
|
+
logged-out and logged-in mockups measured close to this, not identical px-for-px, so this
|
|
5113
|
+
is the representative middle value rather than either exact reading). */
|
|
5114
|
+
position: absolute;
|
|
5115
|
+
top: 100%;
|
|
5116
|
+
left: 22%;
|
|
5117
|
+
right: 0;
|
|
5118
|
+
background: var(--md-surface);
|
|
5119
|
+
box-shadow: var(--md-elev-2);
|
|
5120
|
+
z-index: 60;
|
|
5121
|
+
max-height: calc(100vh - 64px);
|
|
5122
|
+
overflow-y: auto;
|
|
5123
|
+
/* only the LEFT corners are visually rounded (top-left, bottom-left) — the right edge sits
|
|
5124
|
+
flush against the viewport's own right edge, so rounding there would be invisible anyway.
|
|
5125
|
+
(This element is already show-mobile-gated, so no extra media query needed here.) */
|
|
5126
|
+
border-radius: 20px 0 0 20px;
|
|
5127
|
+
}
|
|
5128
|
+
.md3 .amenu-mobile-row:active {
|
|
5129
|
+
background: color-mix(in srgb, var(--md-primary) 6%, transparent);
|
|
5130
|
+
}
|
|
5131
|
+
/* desktop "Konto" pill collapses to a plain icon-only button on mobile — the account layer above
|
|
5132
|
+
replaces the small popover the pill normally opens on wider screens. */
|
|
5133
|
+
@media (max-width: 600px) {
|
|
5134
|
+
.md3 .topbar-account-btn {
|
|
5135
|
+
background: transparent;
|
|
5136
|
+
border: 0;
|
|
5137
|
+
padding: 0;
|
|
5138
|
+
width: 40px;
|
|
5139
|
+
height: 40px;
|
|
5140
|
+
min-width: 0;
|
|
5141
|
+
border-radius: 50%;
|
|
5142
|
+
}
|
|
5143
|
+
.md3 .topbar-account-btn:hover {
|
|
5144
|
+
background: color-mix(in srgb, var(--md-on-surface) 8%, transparent);
|
|
5145
|
+
}
|
|
5146
|
+
/* Mobile header icons measured against the real Figma export read visibly smaller than the
|
|
5147
|
+
reference (the shared .icon-btn/.mbtn rules force 20px/18px everywhere in the app — fine
|
|
5148
|
+
elsewhere, but the Figma mobile header specifically wants ~24px/22px). Scoped to icon
|
|
5149
|
+
buttons INSIDE the TopBar's own <header> so it doesn't touch icon-btn/mbtn usage on other
|
|
5150
|
+
pages (VDP gallery controls, financing modal, etc.). */
|
|
5151
|
+
.md3 header .icon-btn .lucide {
|
|
5152
|
+
width: 24px;
|
|
5153
|
+
height: 24px;
|
|
5154
|
+
}
|
|
5155
|
+
.md3 header .topbar-account-btn .lucide {
|
|
5156
|
+
width: 22px;
|
|
5157
|
+
height: 22px;
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5061
5160
|
/* language segmented DE|EN — active tab filled, inactive ghost */
|
|
5062
5161
|
.md3 .lang-seg {
|
|
5063
5162
|
display: inline-flex;
|
|
@@ -5188,9 +5287,10 @@ line-height: 31px;
|
|
|
5188
5287
|
}
|
|
5189
5288
|
.md3 .hiw-section {
|
|
5190
5289
|
background: var(--md-sc-lowest);
|
|
5191
|
-
border: 1px solid
|
|
5290
|
+
border: 1px solid #D4DDE7;
|
|
5192
5291
|
border-radius: 18px;
|
|
5193
5292
|
padding: 30px 24px;
|
|
5293
|
+
box-shadow: none;
|
|
5194
5294
|
}
|
|
5195
5295
|
.md3 .hiw-heading {
|
|
5196
5296
|
text-align: left;
|
|
@@ -5777,10 +5877,10 @@ line-height: 31px;
|
|
|
5777
5877
|
grid-template-columns: 1fr 1fr;
|
|
5778
5878
|
gap: 10px;
|
|
5779
5879
|
background: var(--md-sc-lowest);
|
|
5780
|
-
border: 1px solid
|
|
5880
|
+
border: 1px solid #D4DDE7;
|
|
5781
5881
|
border-radius: 18px;
|
|
5782
5882
|
padding: 18px 16px;
|
|
5783
|
-
box-shadow:
|
|
5883
|
+
box-shadow: none;
|
|
5784
5884
|
}
|
|
5785
5885
|
.md3 .trust-strip > div {
|
|
5786
5886
|
display: flex;
|
|
@@ -5944,6 +6044,7 @@ line-height: 31px;
|
|
|
5944
6044
|
}
|
|
5945
6045
|
.md3 .vdp-push-badges {
|
|
5946
6046
|
gap: 6px;
|
|
6047
|
+
margin-bottom: 13px;
|
|
5947
6048
|
}
|
|
5948
6049
|
}
|
|
5949
6050
|
|
|
@@ -6067,21 +6168,37 @@ color: #005CFF;
|
|
|
6067
6168
|
}
|
|
6068
6169
|
|
|
6069
6170
|
@media (max-width: 600px) {
|
|
6070
|
-
/* full-bleed gallery: edge-to-edge, app-like.
|
|
6071
|
-
the
|
|
6171
|
+
/* full-bleed gallery: edge-to-edge, app-like. Extra height (vs. the plain photo height)
|
|
6172
|
+
reserves room at the bottom for the controls row, now overlaid on the image itself
|
|
6173
|
+
(round 15 — supersedes the earlier "no overlay" layout). */
|
|
6072
6174
|
.md3 .vdp-gallerybox {
|
|
6073
6175
|
margin: 0 -24px;
|
|
6074
6176
|
border-radius: 0;
|
|
6075
6177
|
border: 0;
|
|
6076
6178
|
box-shadow: none;
|
|
6077
|
-
height:
|
|
6179
|
+
height: 230px;
|
|
6078
6180
|
aspect-ratio: auto;
|
|
6079
6181
|
}
|
|
6080
|
-
/*
|
|
6182
|
+
/* .vdp-gallery is the positioning context: .gallery-controls-row is its own sibling of
|
|
6183
|
+
.vdp-gallerybox in the DOM, so it needs a relative ancestor to overlay against. */
|
|
6184
|
+
.md3 .vdp-gallery {
|
|
6185
|
+
position: relative;
|
|
6186
|
+
}
|
|
6187
|
+
/* controls row now sits ON the image as an overlay, bottom-aligned (round 15) */
|
|
6081
6188
|
.md3 .gallery-controls-row {
|
|
6082
|
-
|
|
6189
|
+
position: absolute;
|
|
6190
|
+
left: 14px;
|
|
6191
|
+
right: 14px;
|
|
6192
|
+
bottom: 12px;
|
|
6193
|
+
z-index: 3;
|
|
6194
|
+
padding: 0;
|
|
6195
|
+
}
|
|
6196
|
+
/* share/save icon buttons: scoped to THIS row (not the base .icon-btn--filled, which is
|
|
6197
|
+
also the opaque vdp-back + desktop overlay buttons) — translucent so photo shows through. */
|
|
6198
|
+
.md3 .gallery-controls-row .icon-btn--filled {
|
|
6199
|
+
background-color: hsla(0, 0%, 100%, .75);
|
|
6083
6200
|
}
|
|
6084
|
-
/* the carousel's own overlay dots move into the
|
|
6201
|
+
/* the carousel's own overlay dots move into the on-image controls row (page-rendered;
|
|
6085
6202
|
the dots div carries an inline display:flex — hence !important) */
|
|
6086
6203
|
.md3 .vdp-gallerybox .cardcar-dots {
|
|
6087
6204
|
display: none !important;
|
|
@@ -6451,11 +6568,19 @@ color: #005CFF;
|
|
|
6451
6568
|
.md3 .vdp-similar {
|
|
6452
6569
|
margin-top: 14px !important;
|
|
6453
6570
|
}
|
|
6571
|
+
.md3 #vdp-kontakt {
|
|
6572
|
+
border: 1px solid #D4DDE7;
|
|
6573
|
+
box-shadow: none;
|
|
6574
|
+
}
|
|
6454
6575
|
.md3 #vdp-bewertungen {
|
|
6455
6576
|
padding: 28px 24px !important;
|
|
6577
|
+
border: 1px solid #D4DDE7;
|
|
6578
|
+
box-shadow: none;
|
|
6456
6579
|
}
|
|
6457
6580
|
.md3 #vdp-preis {
|
|
6458
6581
|
padding: 0 !important;
|
|
6582
|
+
border: 1px solid #D4DDE7;
|
|
6583
|
+
box-shadow: none;
|
|
6459
6584
|
}
|
|
6460
6585
|
.md3 #vdp-preis h2 {
|
|
6461
6586
|
padding: 22px 22px 0 !important;
|
|
@@ -6694,6 +6819,8 @@ color: #005CFF;
|
|
|
6694
6819
|
margin-bottom: 20px !important;
|
|
6695
6820
|
padding: 18px !important;
|
|
6696
6821
|
box-sizing: border-box;
|
|
6822
|
+
border: 1px solid #D4DDE7;
|
|
6823
|
+
box-shadow: none;
|
|
6697
6824
|
display: flex;
|
|
6698
6825
|
flex-direction: column;
|
|
6699
6826
|
}
|
|
@@ -6823,6 +6950,10 @@ color: #005CFF;
|
|
|
6823
6950
|
text-align: center;
|
|
6824
6951
|
color: #ffffff;
|
|
6825
6952
|
}
|
|
6953
|
+
.md3 #vdp-eco {
|
|
6954
|
+
border: 1px solid #D4DDE7;
|
|
6955
|
+
box-shadow: none;
|
|
6956
|
+
}
|
|
6826
6957
|
.md3 #vdp-eco .md-label-sm {
|
|
6827
6958
|
font-family: Arial, Helvetica, sans-serif;
|
|
6828
6959
|
font-style: normal;
|
|
@@ -6833,6 +6964,8 @@ color: #005CFF;
|
|
|
6833
6964
|
}
|
|
6834
6965
|
.md3 #vdp-daten {
|
|
6835
6966
|
padding: 26px 24px 34px 24px;
|
|
6967
|
+
border: 1px solid #D4DDE7;
|
|
6968
|
+
box-shadow: none;
|
|
6836
6969
|
}
|
|
6837
6970
|
.md3 #vdp-daten .mbtn--text {
|
|
6838
6971
|
font-family: Arial, Helvetica, sans-serif;
|
|
@@ -6855,6 +6988,8 @@ color: #005CFF;
|
|
|
6855
6988
|
}
|
|
6856
6989
|
.md3 #vdp-ausstattung {
|
|
6857
6990
|
padding: 28px 24px 19px 24px;
|
|
6991
|
+
border: 1px solid #D4DDE7;
|
|
6992
|
+
box-shadow: none;
|
|
6858
6993
|
}
|
|
6859
6994
|
.md3 #vdp-ausstattung h2 {
|
|
6860
6995
|
margin: 0 0 30px;
|
|
@@ -7018,6 +7153,8 @@ color: #005CFF;
|
|
|
7018
7153
|
}
|
|
7019
7154
|
.md3 .enq-country {
|
|
7020
7155
|
width: 70px;
|
|
7156
|
+
}
|
|
7157
|
+
.md3 .enq-country .mselect-label {
|
|
7021
7158
|
font-family: Arial, Helvetica, sans-serif;
|
|
7022
7159
|
font-style: normal;
|
|
7023
7160
|
font-weight: 400;
|
|
@@ -8931,3 +9068,160 @@ color: #005CFF;
|
|
|
8931
9068
|
background: var(--md-sc-lowest);
|
|
8932
9069
|
box-shadow: none;
|
|
8933
9070
|
}
|
|
9071
|
+
|
|
9072
|
+
/* ==========================================================================
|
|
9073
|
+
VdpViewer "all photos" bottom sheet (libs/packages/fe-ui-core-autobahn/vdp/VdpViewer.tsx)
|
|
9074
|
+
--------------------------------------------------------------------------
|
|
9075
|
+
New selectors only, own `.vdp-photosheet*` prefix — nothing above this block
|
|
9076
|
+
is touched. Static values live here; only per-state dynamics (slide
|
|
9077
|
+
transform/transition while open/dragging, backdrop opacity/pointer-events)
|
|
9078
|
+
stay inline in the component. 3 grid columns is the default (portrait,
|
|
9079
|
+
<=693px); the 693-860px media query below is the ONLY landscape-specific
|
|
9080
|
+
override, matching the one visual difference the spec calls for.
|
|
9081
|
+
========================================================================== */
|
|
9082
|
+
.md3 .vdp-photosheet-backdrop {
|
|
9083
|
+
position: absolute;
|
|
9084
|
+
inset: 0;
|
|
9085
|
+
background: rgba(0, 0, 0, 0.5);
|
|
9086
|
+
z-index: 210;
|
|
9087
|
+
}
|
|
9088
|
+
.md3 .vdp-photosheet {
|
|
9089
|
+
position: absolute;
|
|
9090
|
+
left: 0;
|
|
9091
|
+
right: 0;
|
|
9092
|
+
bottom: 0;
|
|
9093
|
+
height: 100vh;
|
|
9094
|
+
z-index: 211;
|
|
9095
|
+
display: flex;
|
|
9096
|
+
flex-direction: column;
|
|
9097
|
+
/* same dark color as the viewer's own header bar (rgba(6,11,18,0.8)), but fully opaque: the
|
|
9098
|
+
header uses 0.8 alpha OVER a photo, whereas this panel sits over the dimmed backdrop + viewer
|
|
9099
|
+
behind it — at 0.8 alpha, high-contrast content back there (e.g. the blue CTA button) bled
|
|
9100
|
+
through visibly during testing, so opacity is dropped without changing the hue itself. */
|
|
9101
|
+
background: rgb(6, 11, 18);
|
|
9102
|
+
border-radius: 16px 16px 0 0;
|
|
9103
|
+
overflow: hidden;
|
|
9104
|
+
}
|
|
9105
|
+
.md3 .vdp-photosheet__header {
|
|
9106
|
+
position: relative;
|
|
9107
|
+
flex: none;
|
|
9108
|
+
display: flex;
|
|
9109
|
+
align-items: center;
|
|
9110
|
+
justify-content: center;
|
|
9111
|
+
min-height: 88px;
|
|
9112
|
+
padding-top: 10px;
|
|
9113
|
+
touch-action: none;
|
|
9114
|
+
}
|
|
9115
|
+
.md3 .vdp-photosheet__handle {
|
|
9116
|
+
position: absolute;
|
|
9117
|
+
top: 8px;
|
|
9118
|
+
left: 50%;
|
|
9119
|
+
transform: translateX(-50%);
|
|
9120
|
+
width: 36px;
|
|
9121
|
+
height: 4px;
|
|
9122
|
+
border-radius: 2px;
|
|
9123
|
+
background: rgba(255, 255, 255, 0.28);
|
|
9124
|
+
}
|
|
9125
|
+
.md3 .vdp-photosheet__close {
|
|
9126
|
+
position: absolute;
|
|
9127
|
+
left: 12px;
|
|
9128
|
+
top: 50%;
|
|
9129
|
+
transform: translateY(-50%);
|
|
9130
|
+
}
|
|
9131
|
+
.md3 .vdp-photosheet__logo {
|
|
9132
|
+
display: flex;
|
|
9133
|
+
align-items: center;
|
|
9134
|
+
justify-content: center;
|
|
9135
|
+
}
|
|
9136
|
+
.md3 .vdp-photosheet__logo--portrait {
|
|
9137
|
+
transform: scale(0.75);
|
|
9138
|
+
}
|
|
9139
|
+
.md3 .vdp-photosheet__titles {
|
|
9140
|
+
flex: none;
|
|
9141
|
+
padding: 0 16px 12px;
|
|
9142
|
+
text-align: left;
|
|
9143
|
+
}
|
|
9144
|
+
.md3 .vdp-photosheet__title {
|
|
9145
|
+
margin: 0;
|
|
9146
|
+
font-family: 'Inter', var(--md-font);
|
|
9147
|
+
font-size: 18px;
|
|
9148
|
+
font-weight: 700;
|
|
9149
|
+
line-height: 24px;
|
|
9150
|
+
color: #ffffff;
|
|
9151
|
+
}
|
|
9152
|
+
.md3 .vdp-photosheet__subtitle {
|
|
9153
|
+
margin: 4px 0 0;
|
|
9154
|
+
font-family: 'Inter', var(--md-font);
|
|
9155
|
+
font-size: 13px;
|
|
9156
|
+
font-weight: 400;
|
|
9157
|
+
line-height: 18px;
|
|
9158
|
+
color: rgba(255, 255, 255, 0.56);
|
|
9159
|
+
}
|
|
9160
|
+
.md3 .vdp-photosheet__scroll {
|
|
9161
|
+
flex: 1;
|
|
9162
|
+
min-height: 0;
|
|
9163
|
+
overflow-y: auto;
|
|
9164
|
+
padding: 0 12px 12px;
|
|
9165
|
+
}
|
|
9166
|
+
.md3 .vdp-photosheet__grid {
|
|
9167
|
+
display: grid;
|
|
9168
|
+
grid-template-columns: repeat(3, 1fr);
|
|
9169
|
+
gap: 8px;
|
|
9170
|
+
}
|
|
9171
|
+
.md3 .vdp-photosheet__tile {
|
|
9172
|
+
position: relative;
|
|
9173
|
+
aspect-ratio: 4 / 3;
|
|
9174
|
+
box-sizing: border-box;
|
|
9175
|
+
border-radius: 10px;
|
|
9176
|
+
overflow: hidden;
|
|
9177
|
+
border: 1px solid rgba(255, 255, 255, 0.16);
|
|
9178
|
+
padding: 0;
|
|
9179
|
+
cursor: pointer;
|
|
9180
|
+
background: rgba(255, 255, 255, 0.06);
|
|
9181
|
+
}
|
|
9182
|
+
.md3 .vdp-photosheet__tile img {
|
|
9183
|
+
width: 100%;
|
|
9184
|
+
height: 100%;
|
|
9185
|
+
object-fit: cover;
|
|
9186
|
+
display: block;
|
|
9187
|
+
}
|
|
9188
|
+
.md3 .vdp-photosheet__tile--active {
|
|
9189
|
+
border: 3px solid #0b63f6;
|
|
9190
|
+
}
|
|
9191
|
+
.md3 .vdp-photosheet__badge {
|
|
9192
|
+
position: absolute;
|
|
9193
|
+
top: 6px;
|
|
9194
|
+
right: 6px;
|
|
9195
|
+
width: 20px;
|
|
9196
|
+
height: 20px;
|
|
9197
|
+
border-radius: 50%;
|
|
9198
|
+
background: #0b63f6;
|
|
9199
|
+
display: flex;
|
|
9200
|
+
align-items: center;
|
|
9201
|
+
justify-content: center;
|
|
9202
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
|
9203
|
+
}
|
|
9204
|
+
.md3 .vdp-photosheet__footer {
|
|
9205
|
+
flex: none;
|
|
9206
|
+
display: flex;
|
|
9207
|
+
align-items: center;
|
|
9208
|
+
justify-content: flex-start;
|
|
9209
|
+
gap: 6px;
|
|
9210
|
+
height: 44px;
|
|
9211
|
+
padding: 0 16px;
|
|
9212
|
+
/* rgba(0,0,0,0.5) was invisible here — it darkens whatever's behind it, and the sheet's own
|
|
9213
|
+
panel (rgb(6,11,18)) is already almost black, so a 50%-black overlay on top of near-black
|
|
9214
|
+
reads as no change at all. #1c222c is lighter/bluer and already used elsewhere in this same
|
|
9215
|
+
file (landscape's "Anrufen" CTA background), so it's a real, visible band without a new tone. */
|
|
9216
|
+
background: #1c222c;
|
|
9217
|
+
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
|
9218
|
+
color: rgba(255, 255, 255, 0.72);
|
|
9219
|
+
font-family: 'Inter', var(--md-font);
|
|
9220
|
+
font-size: 13px;
|
|
9221
|
+
font-weight: 500;
|
|
9222
|
+
}
|
|
9223
|
+
@media (min-width: 693px) and (max-width: 860px) {
|
|
9224
|
+
.md3 .vdp-photosheet__grid {
|
|
9225
|
+
grid-template-columns: repeat(4, 1fr);
|
|
9226
|
+
}
|
|
9227
|
+
}
|
package/utils/financeFormat.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
-
//
|
|
3
|
-
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
-
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
-
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
-
// Node service.
|
|
7
|
-
//
|
|
8
|
-
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
-
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
-
|
|
11
|
-
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
-
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
-
export function formatEur(n: number): string {
|
|
14
|
-
return n.toLocaleString('de-DE');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
-
export function formatPct(n: number): string {
|
|
19
|
-
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FinanceExampleFields {
|
|
23
|
-
price: number;
|
|
24
|
-
apr: number;
|
|
25
|
-
down: number;
|
|
26
|
-
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
-
netLoanAmount?: number;
|
|
28
|
-
nominalRate?: number;
|
|
29
|
-
finalInstallment?: number;
|
|
30
|
-
grossAmount?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface FinanceExampleLabels {
|
|
34
|
-
vehiclePrice: string;
|
|
35
|
-
priceSuffix: string;
|
|
36
|
-
apr: string;
|
|
37
|
-
nominal: string;
|
|
38
|
-
net: string;
|
|
39
|
-
down: string;
|
|
40
|
-
final: string;
|
|
41
|
-
gross: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
-
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
-
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
-
* `exampleLine`, byte-for-byte).
|
|
49
|
-
*/
|
|
50
|
-
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
-
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
-
const parts: string[] = [
|
|
53
|
-
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
-
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
-
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
-
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
-
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
-
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
-
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
-
];
|
|
61
|
-
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
-
}
|
|
1
|
+
// Autobahn (auto.de redesign) — shared financing-number formatting.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from vdp/VdpModules.tsx's FinanceDetailsModal so non-React consumers (e.g. the
|
|
4
|
+
// Meta AIA marketing-image renderer) format the SAME real numbers the same way, instead of a
|
|
5
|
+
// second copy drifting from the VDP's. Pure TS, no React/JSX — safe to import from a plain
|
|
6
|
+
// Node service.
|
|
7
|
+
//
|
|
8
|
+
// The "only real figures, never a fabricated total" rule is load-bearing (legal: balloon
|
|
9
|
+
// financing means monthly×term is NOT the total) and lives here once, not per-caller.
|
|
10
|
+
|
|
11
|
+
/** Monetary figure → de-DE grouped string (e.g. 47400 → "47.400"). Feed rounded integers —
|
|
12
|
+
* toLocaleString on a fractional value prints a bogus 3-decimal grouping. */
|
|
13
|
+
export function formatEur(n: number): string {
|
|
14
|
+
return n.toLocaleString('de-DE');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rate → de-DE percent string (e.g. 6.49 → "6,49 %"). */
|
|
18
|
+
export function formatPct(n: number): string {
|
|
19
|
+
return `${n.toFixed(2).replace('.', ',')} %`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FinanceExampleFields {
|
|
23
|
+
price: number;
|
|
24
|
+
apr: number;
|
|
25
|
+
down: number;
|
|
26
|
+
/** Real config figure — falls back to price-down ONLY when genuinely absent. */
|
|
27
|
+
netLoanAmount?: number;
|
|
28
|
+
nominalRate?: number;
|
|
29
|
+
finalInstallment?: number;
|
|
30
|
+
grossAmount?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FinanceExampleLabels {
|
|
34
|
+
vehiclePrice: string;
|
|
35
|
+
priceSuffix: string;
|
|
36
|
+
apr: string;
|
|
37
|
+
nominal: string;
|
|
38
|
+
net: string;
|
|
39
|
+
down: string;
|
|
40
|
+
final: string;
|
|
41
|
+
gross: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The "Repräsentatives Beispiel" disclaimer line (PAngV). Only fields with a genuine value are
|
|
46
|
+
* included — nominalRate/finalInstallment/grossAmount are optional real config figures, never
|
|
47
|
+
* derived (see fields doc). Matches production wording/format exactly (VdpModules.tsx's
|
|
48
|
+
* `exampleLine`, byte-for-byte).
|
|
49
|
+
*/
|
|
50
|
+
export function buildFinanceExampleLine(fields: FinanceExampleFields, labels: FinanceExampleLabels, footnoteMarker = '³'): string {
|
|
51
|
+
const netto = fields.netLoanAmount != null ? fields.netLoanAmount : Math.max(0, fields.price - fields.down);
|
|
52
|
+
const parts: string[] = [
|
|
53
|
+
`${labels.vehiclePrice}: ${formatEur(fields.price)} € ${labels.priceSuffix}`,
|
|
54
|
+
...(fields.apr > 0 ? [`${labels.apr}: ${formatPct(fields.apr)}`] : []),
|
|
55
|
+
...(fields.nominalRate != null && fields.nominalRate > 0 ? [`${labels.nominal}: ${formatPct(fields.nominalRate)}`] : []),
|
|
56
|
+
`${labels.net}: ${formatEur(netto)} €`,
|
|
57
|
+
`${labels.down}: ${formatEur(fields.down)} €`,
|
|
58
|
+
...(fields.finalInstallment != null && fields.finalInstallment > 0 ? [`${labels.final}: ${formatEur(fields.finalInstallment)} €`] : []),
|
|
59
|
+
...(fields.grossAmount != null && fields.grossAmount > 0 ? [`${labels.gross}: ${formatEur(fields.grossAmount)} €`] : []),
|
|
60
|
+
];
|
|
61
|
+
return `${footnoteMarker} ${parts.join('; ')};`;
|
|
62
|
+
}
|