@bytebrand/fe-ui-core-autobahn 1.0.117 → 1.0.119
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 +32 -8
- package/cards/cardParts.tsx +15 -3
- package/compat/ButtonOld/Button.styl +1 -1
- package/compat/Checkbox/Checkbox.styl +6 -1
- package/compat/Checkbox/Checkbox.tsx +11 -1
- package/compat/Checkbox/FormCheckbox.styl +2 -1
- package/compat/CookieModal/modals/ManageCookieModal/ManageCookieModal.styl +1 -1
- package/compat/CookieModal/modals/PreviewCookieModal/PreviewCookieModal.styl +7 -7
- package/compat/MdField/MdField.tsx +24 -1
- package/compat/MdSelect/MdSelect.tsx +10 -1
- package/compat/RateSearchWidget/RateSearchWidget.styl +6 -1
- package/compat/StarButton/StarButton.styl +1 -1
- package/package.json +30 -30
- package/theme/autobahn.css +47 -5
- package/theme/md3.css +205 -120
- package/ui/CardCarousel.tsx +12 -4
- package/ui/Dropdown.tsx +12 -1
- package/vdp/FinanceCalculator.tsx +2 -2
- package/vdp/VdpModules.tsx +49 -49
package/cards/CarCard.tsx
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import React, { useState, useEffect, useRef } from 'react';
|
|
21
21
|
import { TranslationFunction } from '../types';
|
|
22
22
|
|
|
23
|
-
import { CardCarousel,
|
|
23
|
+
import { CardCarousel, SaveChip, Button } from '../ui';
|
|
24
24
|
import {
|
|
25
25
|
SpecLine,
|
|
26
26
|
EcoLine,
|
|
@@ -174,10 +174,30 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
174
174
|
{/* max two overlays: heart + ONE deal signal — rating moved next to the price */}
|
|
175
175
|
<div
|
|
176
176
|
className="cc-fav"
|
|
177
|
-
style={{ position: 'absolute', top:
|
|
177
|
+
style={{ position: 'absolute', top: 14, right: 9, zIndex: 6 }}
|
|
178
178
|
onClick={(e) => e.stopPropagation()}
|
|
179
179
|
>
|
|
180
|
-
|
|
180
|
+
{/* Custom heart (not lucide's default 'heart') — scoped here via a hand-rolled
|
|
181
|
+
button+svg instead of IconButton/Icon's name-based registry lookup, so this
|
|
182
|
+
doesn't change the "heart" icon anywhere else it's used (e.g. CarRow). Same
|
|
183
|
+
active/inactive color logic IconButton+Icon had: active fills solid
|
|
184
|
+
var(--md-primary), inactive stays an outline (previously 'currentColor',
|
|
185
|
+
now this Figma-exact icon's own #08162A stroke). */}
|
|
186
|
+
<button
|
|
187
|
+
className="icon-btn icon-btn--filled"
|
|
188
|
+
onClick={onToggleFavorite}
|
|
189
|
+
aria-label={t('autobahn:card.action.save')}
|
|
190
|
+
>
|
|
191
|
+
<svg width="22" height="22" viewBox="0 0 22 22" fill={isFavorite ? 'var(--md-primary)' : 'none'} xmlns="http://www.w3.org/2000/svg">
|
|
192
|
+
<path
|
|
193
|
+
d="M11 18.7915C11 18.7915 4.125 14.8498 4.125 9.34981C4.12463 8.54932 4.37379 7.76862 4.83781 7.11634C5.30183 6.46405 5.95763 5.97262 6.71396 5.71043C7.47029 5.44823 8.28955 5.42831 9.05773 5.65343C9.82591 5.87855 10.5048 6.33752 11 6.96648C11.4952 6.33752 12.1741 5.87855 12.9423 5.65343C13.7105 5.42831 14.5297 5.44823 15.286 5.71043C16.0424 5.97262 16.6982 6.46405 17.1622 7.11634C17.6262 7.76862 17.8754 8.54932 17.875 9.34981C17.875 14.8498 11 18.7915 11 18.7915Z"
|
|
194
|
+
stroke={isFavorite ? 'var(--md-primary)' : '#08162A'}
|
|
195
|
+
strokeWidth={1.8}
|
|
196
|
+
strokeLinecap="round"
|
|
197
|
+
strokeLinejoin="round"
|
|
198
|
+
/>
|
|
199
|
+
</svg>
|
|
200
|
+
</button>
|
|
181
201
|
</div>
|
|
182
202
|
{/* top-left signal stack (heart is top-right): NEU badge + ONE deal signal + the
|
|
183
203
|
"⚡ Angebot" badge (own line, Figma) below it. DropChip takes precedence over
|
|
@@ -187,7 +207,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
187
207
|
className="cc-badges"
|
|
188
208
|
style={{
|
|
189
209
|
position: 'absolute',
|
|
190
|
-
top:
|
|
210
|
+
top: 16,
|
|
191
211
|
left: 10,
|
|
192
212
|
zIndex: 3,
|
|
193
213
|
display: 'flex',
|
|
@@ -213,7 +233,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
213
233
|
background: 'rgb(204, 222, 255)',
|
|
214
234
|
border: '1px solid rgba(0, 92, 255, 0.05)',
|
|
215
235
|
borderRadius: 15,
|
|
216
|
-
fontFamily: 'Arial',
|
|
236
|
+
fontFamily: 'Arial, Arimo, sans-serif',
|
|
217
237
|
fontWeight: 700,
|
|
218
238
|
fontSize: 13,
|
|
219
239
|
lineHeight: '15px',
|
|
@@ -240,7 +260,11 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
240
260
|
<GalleryActivityPill viewsCount={car.viewsCount} favoritesCount={car.favoritesCount} />
|
|
241
261
|
</div>
|
|
242
262
|
</div>
|
|
243
|
-
|
|
263
|
+
{/* padding-top: 0 (was 8px) + marginTop: -5 — not just 0, the ask was 0 AND 5px higher
|
|
264
|
+
still than that zero-padding baseline, so a negative margin pulls the whole block up
|
|
265
|
+
past where padding-top:0 alone would leave it. Bottom/side padding (8px/18px)
|
|
266
|
+
untouched. */}
|
|
267
|
+
<div style={{ padding: '0 18px 8px', marginTop: -3, display: 'flex', flexDirection: 'column', gap: 0, flex: 1 }}>
|
|
244
268
|
<div>
|
|
245
269
|
<div className="md-title" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
246
270
|
{car.make} {car.model}
|
|
@@ -250,7 +274,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
250
274
|
</div>
|
|
251
275
|
</div>
|
|
252
276
|
<SpecLine items={cardSpec(car)} oneLine />
|
|
253
|
-
<div className="cc-price" style={{ marginTop:
|
|
277
|
+
<div className="cc-price" style={{ marginTop: 0, borderTop: '1px solid var(--md-outline-variant)' }}>
|
|
254
278
|
<PriceTabs car={car} mode={mode} onModeChange={setMode} t={t} />
|
|
255
279
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginTop: 10 }}>
|
|
256
280
|
<PriceHero car={car} mode={mode} t={t} alwaysShowUnit />
|
|
@@ -283,7 +307,7 @@ const CarCard: React.FunctionComponent<CarCardProps> = ({
|
|
|
283
307
|
style={{
|
|
284
308
|
marginTop: 'auto',
|
|
285
309
|
borderTop: 'none',
|
|
286
|
-
fontFamily: 'Arial, Helvetica, sans-serif',
|
|
310
|
+
fontFamily: 'Arial, Helvetica, Arimo, sans-serif',
|
|
287
311
|
fontStyle: 'normal',
|
|
288
312
|
fontWeight: 400,
|
|
289
313
|
fontSize: 11,
|
package/cards/cardParts.tsx
CHANGED
|
@@ -56,6 +56,18 @@ function stripParen(s: string): string {
|
|
|
56
56
|
return s.replace(/\s*\(.*\)/, '');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Opposite of stripParen, for car.power specifically: pull the PS value OUT of the trailing
|
|
61
|
+
* parenthetical instead of discarding it, e.g. '145 kW (197 PS)' → '197 PS' — the card wants
|
|
62
|
+
* PS only, no kW at all. Regex requires the paren to actually contain "N PS" (not just any
|
|
63
|
+
* parenthetical) and falls back to the plain stripParen(kW) result when it doesn't, so a power
|
|
64
|
+
* string without a PS conversion still renders something instead of going blank.
|
|
65
|
+
*/
|
|
66
|
+
function extractPS(s: string): string {
|
|
67
|
+
const m = s.match(/\((\d+\s*PS)\)\s*$/i);
|
|
68
|
+
return m ? m[1].replace(/\s+/, ' ') : stripParen(s);
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
// Compact spec for cards: short fuel/gear labels so all five values fit ONE line
|
|
60
72
|
// without clipping.
|
|
61
73
|
const FUEL_SHORT: Record<string, string> = {
|
|
@@ -75,7 +87,7 @@ export function cardSpec(car: AutobahnCar): string[] {
|
|
|
75
87
|
return [
|
|
76
88
|
fmtKm(car.km),
|
|
77
89
|
car.reg,
|
|
78
|
-
|
|
90
|
+
extractPS(car.power),
|
|
79
91
|
FUEL_SHORT[fuel] || fuel,
|
|
80
92
|
GEAR_SHORT[car.gear] || car.gear,
|
|
81
93
|
];
|
|
@@ -799,7 +811,7 @@ export const CardCtaRow: React.FunctionComponent<CardCtaRowProps> = ({ car, mode
|
|
|
799
811
|
background: '#005cff',
|
|
800
812
|
borderRadius: 16,
|
|
801
813
|
padding: '0 8px',
|
|
802
|
-
fontFamily: 'Arial',
|
|
814
|
+
fontFamily: 'Arial, Arimo, sans-serif',
|
|
803
815
|
fontStyle: 'normal',
|
|
804
816
|
fontWeight: 700,
|
|
805
817
|
fontSize: 13,
|
|
@@ -877,7 +889,7 @@ export const CardCtaRow: React.FunctionComponent<CardCtaRowProps> = ({ car, mode
|
|
|
877
889
|
border: '1.6px solid #5b6b7f',
|
|
878
890
|
borderRadius: 16,
|
|
879
891
|
padding: '0 6px',
|
|
880
|
-
fontFamily: 'Arial',
|
|
892
|
+
fontFamily: 'Arial, Arimo, sans-serif',
|
|
881
893
|
fontStyle: 'normal',
|
|
882
894
|
fontWeight: 700,
|
|
883
895
|
fontSize: 13,
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
position: relative
|
|
5
5
|
display: inline-block
|
|
6
6
|
vertical-align: middle
|
|
7
|
-
outline: none !important
|
|
7
|
+
// Was `outline: none !important` — the !important beat .container:focus below regardless
|
|
8
|
+
// of it being more specific (pseudo-class) or coming later, permanently killing the
|
|
9
|
+
// custom focus outline that rule was clearly written to show. This class only sets the
|
|
10
|
+
// resting-state default (no !important needed — nothing else here competes for it), so
|
|
11
|
+
// :focus's own outline declaration can win normally on focus, as intended.
|
|
12
|
+
outline: none
|
|
8
13
|
|
|
9
14
|
.wrapInput
|
|
10
15
|
display flex;
|
|
@@ -115,7 +115,17 @@ class Checkbox extends React.Component<ICheckboxProps, {}> {
|
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
return (
|
|
118
|
-
|
|
118
|
+
// tabIndex: was 1 (a positive value) — a well-known anti-pattern (WCAG 2.4.3):
|
|
119
|
+
// positive tabindex elements are visited in their own ascending-order pass BEFORE
|
|
120
|
+
// every tabindex="0"/naturally-focusable element on the page, regardless of where
|
|
121
|
+
// they sit in the DOM. In BaseModal's new focus trap (which walks the panel's
|
|
122
|
+
// focusable elements in DOM order to decide where Tab should wrap) that mismatch
|
|
123
|
+
// made this checkbox unreachable by Tab entirely inside a trapped modal — verified
|
|
124
|
+
// live: 0 hits in 20 Tab presses cycling the RegistrationForm consent checkbox.
|
|
125
|
+
// 0 makes it a normal stop in DOM-order tab flow, matching the trap's own model and
|
|
126
|
+
// putting it where a sighted user would visually expect it (after the fields above,
|
|
127
|
+
// before the CTA below) instead of unpredictably early.
|
|
128
|
+
<div className={containerClasses} tabIndex={0} onFocus={this.props.onFocus} onBlur={this.props.onBlur} onKeyPress={this.onKeyPress}>
|
|
119
129
|
<label className={labelClasses}>
|
|
120
130
|
<div className={wrapInputClasses}>
|
|
121
131
|
{/* Visual representation of the checkbox */}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
display: block
|
|
12
12
|
width: 100%
|
|
13
13
|
z-index: 1
|
|
14
|
-
|
|
14
|
+
// "Arial Standard" isn't a real installed font name anywhere — dead fallback, dropped.
|
|
15
|
+
font-family: Arial, Arimo, sans-serif
|
|
15
16
|
font-weight: 400;
|
|
16
17
|
font-style: normal
|
|
17
18
|
font-size: 10px
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
align-items: center !important
|
|
79
79
|
justify-content: center !important
|
|
80
80
|
text-align: center !important
|
|
81
|
-
font-family: 'Arial' !important
|
|
81
|
+
font-family: 'Arial', 'Arimo', sans-serif !important
|
|
82
82
|
font-style: normal !important
|
|
83
83
|
font-weight: 400 !important
|
|
84
84
|
font-size: 14px !important
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
margin-top: 20px
|
|
18
18
|
|
|
19
19
|
+media-tablet-down()
|
|
20
|
-
font-family: 'Arial';
|
|
20
|
+
font-family: 'Arial', 'Arimo', sans-serif;
|
|
21
21
|
font-weight: 700;
|
|
22
22
|
font-size: 16px;
|
|
23
23
|
line-height: 32px;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
.modalContent
|
|
29
29
|
margin-top: 3px
|
|
30
|
-
font-family: 'Arial'
|
|
30
|
+
font-family: 'Arial', 'Arimo', sans-serif
|
|
31
31
|
font-weight: 400
|
|
32
32
|
font-size: 14px;
|
|
33
33
|
line-height: 16px;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
letter-spacing: 0.15px;
|
|
36
36
|
color: rgba(76, 78, 100, 0.87);
|
|
37
37
|
+media-tablet-down()
|
|
38
|
-
font-family: 'Arial';
|
|
38
|
+
font-family: 'Arial', 'Arimo', sans-serif;
|
|
39
39
|
font-weight: 400;
|
|
40
40
|
font-size: 14px;
|
|
41
41
|
line-height: 16px;
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
background: #005CFF !important
|
|
78
78
|
box-shadow: 0px 4px 8px -4px rgba(76, 78, 100, 0.42) !important
|
|
79
79
|
border-radius: 16px !important
|
|
80
|
-
font-family: 'Arial' !important
|
|
80
|
+
font-family: 'Arial', 'Arimo', sans-serif !important
|
|
81
81
|
font-weight: 500 !important
|
|
82
82
|
font-size: 14px !important
|
|
83
83
|
line-height: 24px !important
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
align-items: center !important
|
|
98
98
|
justify-content: center !important
|
|
99
99
|
text-align: center !important
|
|
100
|
-
font-family: 'Arial' !important
|
|
100
|
+
font-family: 'Arial', 'Arimo', sans-serif !important
|
|
101
101
|
font-style: normal !important
|
|
102
102
|
font-weight: 400 !important
|
|
103
103
|
font-size: 14px !important
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
align-items: center !important
|
|
116
116
|
justify-content: center !important
|
|
117
117
|
text-align: center !important
|
|
118
|
-
font-family: 'Arial' !important
|
|
118
|
+
font-family: 'Arial', 'Arimo', sans-serif !important
|
|
119
119
|
font-style: normal !important
|
|
120
120
|
font-weight: 400 !important
|
|
121
121
|
font-size: 14px !important
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
.outlinedBtnLeft
|
|
126
126
|
display: flex;
|
|
127
127
|
justify-content: flex-end;
|
|
128
|
-
font-family: 'Arial';
|
|
128
|
+
font-family: 'Arial', 'Arimo', sans-serif;
|
|
129
129
|
font-style: normal;
|
|
130
130
|
font-weight: 400;
|
|
131
131
|
color: #005CCB;
|
|
@@ -45,6 +45,9 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
45
45
|
const inputType = isPassword && reveal ? 'text' : (type || 'text');
|
|
46
46
|
const hasError = !!error;
|
|
47
47
|
const generatedId = React.useId();
|
|
48
|
+
// Stable id linking the input to its (visually hidden, see .mfield-error-text sr-only
|
|
49
|
+
// CSS) error text — lets aria-describedby work even when the text itself is invisible.
|
|
50
|
+
const errorId = `${generatedId}-error`;
|
|
48
51
|
// floating label (Material pattern): while empty and unfocused, the label text is
|
|
49
52
|
// shown as the input's own native placeholder (styled via .mfield--floating
|
|
50
53
|
// input::placeholder) and the <label> itself is not rendered at all; once focused or
|
|
@@ -53,6 +56,14 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
53
56
|
const hasValue = value != null && value !== '';
|
|
54
57
|
const resting = !!floatingLabel && !focused && !hasValue;
|
|
55
58
|
const effectivePlaceholder = floatingLabel ? (resting ? (label || placeholder || '') : '') : (placeholder || '');
|
|
59
|
+
// The real <label> is skipped entirely while resting (see above), leaving the input with
|
|
60
|
+
// no programmatic name at all — `placeholder` alone is not a reliable accessible-name
|
|
61
|
+
// fallback across browser/AT combinations, and WCAG explicitly advises against relying on
|
|
62
|
+
// it. aria-label steps in for exactly that gap; once floated (focused or has a value) the
|
|
63
|
+
// real <label htmlFor> takes over as the name source and this reverts to undefined rather
|
|
64
|
+
// than sitting there as a redundant duplicate (aria-label always outranks a wired <label>
|
|
65
|
+
// in the accessible-name computation, so leaving it set wouldn't be wrong, just noise).
|
|
66
|
+
const ariaLabel = resting ? (label || placeholder || undefined) : undefined;
|
|
56
67
|
|
|
57
68
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
58
69
|
onChange(event.currentTarget.value, event.currentTarget.name || name, event);
|
|
@@ -86,6 +97,9 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
86
97
|
maxLength={maxLength}
|
|
87
98
|
placeholder={effectivePlaceholder}
|
|
88
99
|
autoComplete={autoComplete}
|
|
100
|
+
aria-label={ariaLabel}
|
|
101
|
+
aria-invalid={hasError ? true : undefined}
|
|
102
|
+
aria-describedby={hasError ? errorId : undefined}
|
|
89
103
|
style={{ width: '100%', resize: 'vertical' }}
|
|
90
104
|
/>
|
|
91
105
|
) : (
|
|
@@ -101,6 +115,9 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
101
115
|
maxLength={maxLength}
|
|
102
116
|
placeholder={effectivePlaceholder}
|
|
103
117
|
autoComplete={autoComplete}
|
|
118
|
+
aria-label={ariaLabel}
|
|
119
|
+
aria-invalid={hasError ? true : undefined}
|
|
120
|
+
aria-describedby={hasError ? errorId : undefined}
|
|
104
121
|
style={{ width: '100%' }}
|
|
105
122
|
/>
|
|
106
123
|
)}
|
|
@@ -116,7 +133,13 @@ const MdField: React.FunctionComponent<MdFieldProps> = ({
|
|
|
116
133
|
</button>
|
|
117
134
|
) : null}
|
|
118
135
|
</div>
|
|
119
|
-
{hasError ?
|
|
136
|
+
{hasError ? (
|
|
137
|
+
// role="alert" announces this to screen readers the moment it enters the DOM (e.g.
|
|
138
|
+
// right after a failed Submit) — no need to wait for focus to reach this field.
|
|
139
|
+
// Visually hidden or not is entirely a CSS concern (.mfield-error-text) — this
|
|
140
|
+
// element and its id/role/text content exist unconditionally on error either way.
|
|
141
|
+
<span id={errorId} className='mfield-error-text' role='alert'>{error}</span>
|
|
142
|
+
) : null}
|
|
120
143
|
</div>
|
|
121
144
|
);
|
|
122
145
|
};
|
|
@@ -71,6 +71,9 @@ const MdSelect: React.FunctionComponent<MdSelectProps> = ({
|
|
|
71
71
|
disabled: !!item.isDisabled
|
|
72
72
|
}));
|
|
73
73
|
const hasError = !!error;
|
|
74
|
+
// Stable id linking the trigger to its (visually hidden, see .mfield-error-text sr-only
|
|
75
|
+
// CSS) error text — lets aria-describedby work even when the text itself is invisible.
|
|
76
|
+
const errorId = React.useId() + '-error';
|
|
74
77
|
|
|
75
78
|
const handleChange = (picked: string) => {
|
|
76
79
|
const match = (items || []).find(item => normalize(item.value) === picked);
|
|
@@ -94,8 +97,14 @@ const MdSelect: React.FunctionComponent<MdSelectProps> = ({
|
|
|
94
97
|
placeholder={placeholder || (label ? label : undefined)}
|
|
95
98
|
disabled={disabled}
|
|
96
99
|
ariaLabel={label}
|
|
100
|
+
ariaInvalid={hasError}
|
|
101
|
+
ariaDescribedBy={hasError ? errorId : undefined}
|
|
97
102
|
/>
|
|
98
|
-
{hasError && typeof error === 'string' ?
|
|
103
|
+
{hasError && typeof error === 'string' ? (
|
|
104
|
+
// role="alert" announces this to screen readers the moment it enters the DOM (e.g.
|
|
105
|
+
// right after a failed Submit) — no need to wait for focus to reach this field.
|
|
106
|
+
<span id={errorId} className='mfield-error-text' role='alert'>{error}</span>
|
|
107
|
+
) : null}
|
|
99
108
|
</div>
|
|
100
109
|
);
|
|
101
110
|
};
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
box-sizing: border-box
|
|
6
6
|
padding: 12px 18px
|
|
7
7
|
background-color: white
|
|
8
|
-
|
|
8
|
+
// was 'Arial Fett', 'Arial Standard', 'Arial' — those first two aren't real installed font
|
|
9
|
+
// names anywhere ("Fett" = German for bold), a dead attempt to force bold via font-family
|
|
10
|
+
// instead of font-weight. The real @font-face (theme/autobahn.css) now gives 'Arial' a proper
|
|
11
|
+
// bold face at weight 700.
|
|
12
|
+
font-family: 'Arial', 'Arimo', sans-serif
|
|
13
|
+
font-weight: 700
|
|
9
14
|
border-radius: 10px
|
|
10
15
|
border: 1px solid $grey-e
|
|
11
16
|
width: 100%
|
package/package.json
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
"main": "index.ts",
|
|
6
|
-
"license": "UNLICENSED",
|
|
7
|
-
"peerDependencies": {
|
|
8
|
-
"react": "^18.0.0",
|
|
9
|
-
"react-dom": "^18.0.0"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
-
"@bytebrand/fe-financing": "^5.14.19",
|
|
14
|
-
"classnames": "2.2.6",
|
|
15
|
-
"clsx": "^2.1.1",
|
|
16
|
-
"d3-format": "1.4.5",
|
|
17
|
-
"js-cookie": "^2.2.0",
|
|
18
|
-
"lodash": "^4.17.21",
|
|
19
|
-
"lucide-react": "^0.288.0",
|
|
20
|
-
"mobx-react": "^7.6.0",
|
|
21
|
-
"moment": "^2.29.1",
|
|
22
|
-
"qs": "^6.6.0",
|
|
23
|
-
"react-device-detect": "^2.1.2",
|
|
24
|
-
"react-grid-system": "^5.0.2",
|
|
25
|
-
"react-lazyload": "^3.2.0",
|
|
26
|
-
"react-scroll": "^1.9.3",
|
|
27
|
-
"react-slick": "^0.29.0",
|
|
28
|
-
"slick-carousel": "1.8.1"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@bytebrand/fe-ui-core-autobahn",
|
|
3
|
+
"version": "1.0.119",
|
|
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
|
+
"main": "index.ts",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"react": "^18.0.0",
|
|
9
|
+
"react-dom": "^18.0.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@bytebrand/car-schema-selectors": "^2.0.7",
|
|
13
|
+
"@bytebrand/fe-financing": "^5.14.19",
|
|
14
|
+
"classnames": "2.2.6",
|
|
15
|
+
"clsx": "^2.1.1",
|
|
16
|
+
"d3-format": "1.4.5",
|
|
17
|
+
"js-cookie": "^2.2.0",
|
|
18
|
+
"lodash": "^4.17.21",
|
|
19
|
+
"lucide-react": "^0.288.0",
|
|
20
|
+
"mobx-react": "^7.6.0",
|
|
21
|
+
"moment": "^2.29.1",
|
|
22
|
+
"qs": "^6.6.0",
|
|
23
|
+
"react-device-detect": "^2.1.2",
|
|
24
|
+
"react-grid-system": "^5.0.2",
|
|
25
|
+
"react-lazyload": "^3.2.0",
|
|
26
|
+
"react-scroll": "^1.9.3",
|
|
27
|
+
"react-slick": "^0.29.0",
|
|
28
|
+
"slick-carousel": "1.8.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/theme/autobahn.css
CHANGED
|
@@ -8,21 +8,63 @@
|
|
|
8
8
|
subtree receive Autobahn tokens + utility classes.
|
|
9
9
|
|
|
10
10
|
Load order:
|
|
11
|
-
1. Google Fonts (Schibsted Grotesk + Hanken Grotesk + Inter). Per
|
|
12
|
-
plan the CDN @import is kept for the pilot; self-hosting woff2
|
|
13
|
-
later ticket. CSS requires @import to precede all other rules, so
|
|
14
|
-
stays first. Inter (400/500/700) is scoped to VdpViewer's mobile
|
|
11
|
+
1. Google Fonts (Schibsted Grotesk + Hanken Grotesk + Inter + Arimo). Per
|
|
12
|
+
the W1 plan the CDN @import is kept for the pilot; self-hosting woff2
|
|
13
|
+
is a later ticket. CSS requires @import to precede all other rules, so
|
|
14
|
+
it stays first. Inter (400/500/700) is scoped to VdpViewer's mobile
|
|
15
15
|
gallery — that layout is a pixel-match of a Figma frame authored in
|
|
16
16
|
Inter, not the Hanken Grotesk used by the rest of the design system.
|
|
17
|
+
Arimo (400/500/700) is NOT part of the Autobahn design language at
|
|
18
|
+
all — it's loaded purely as a REAL, always-available webfont fallback
|
|
19
|
+
for the many legacy (non-.md3) components across fe-main/fe-landing
|
|
20
|
+
that hardcode font-family: 'Arial' (see the @font-face block below).
|
|
17
21
|
2. colors_and_type.css — brand tokens (.md3 { --autode-* / --primary ... }).
|
|
18
22
|
3. md3.css — MD3 utility classes + responsive layer (.mbtn/.mcard/.chip/
|
|
19
23
|
.segmented/.mfield/.num/.md-* + 1024/980/900/820/600px breakpoints).
|
|
20
24
|
|
|
21
25
|
ADDITIVE ONLY. Do not import this from a non-`.md3` context expecting it to
|
|
22
26
|
restyle existing components — it intentionally does nothing outside `.md3`.
|
|
27
|
+
The @font-face block below is the one exception: @font-face is never scoped
|
|
28
|
+
by a selector, so it applies to the whole page (.md3 or not) the moment
|
|
29
|
+
this stylesheet loads — both fe-main (App.global.styl) and fe-landing
|
|
30
|
+
(pages/_app.tsx) load it globally already.
|
|
23
31
|
========================================================================== */
|
|
24
32
|
|
|
25
|
-
@import url('https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@500;600;700;800&family=Hanken+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;700&display=swap');
|
|
33
|
+
@import url('https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@500;600;700;800&family=Hanken+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;700&family=Arimo:wght@400;500;700&display=swap');
|
|
26
34
|
|
|
27
35
|
@import './colors_and_type.css';
|
|
28
36
|
@import './md3.css';
|
|
37
|
+
|
|
38
|
+
/* Arial weight fix: dozens of legacy (non-.md3) components across fe-main/fe-landing hardcode
|
|
39
|
+
font-family: 'Arial' + an explicit font-weight (400/700), relying entirely on whatever the
|
|
40
|
+
visitor's OS happens to have installed under that exact name. Real Arial (Windows/macOS) ships
|
|
41
|
+
true separate Regular/Bold font files, but without an explicit @font-face some browsers
|
|
42
|
+
inconsistently pick the nearest installed match or synthesize ("fake") bold instead of using
|
|
43
|
+
the real bold face — that's the "wrong font-weight" symptom. This does NOT redistribute Arial
|
|
44
|
+
(proprietary/Microsoft-licensed, can't legally be self-hosted) — local() only queries fonts
|
|
45
|
+
ALREADY installed on the device by name, same matching the browser does implicitly anyway,
|
|
46
|
+
just made explicit and given metric-compatible open fallbacks (Liberation Sans on many Linux
|
|
47
|
+
distros, Arimo — Android's own built-in substitute for "Arial") so real/near-real weight faces
|
|
48
|
+
are used more often instead of synthesized ones. Where NONE of these are installed (e.g. iOS,
|
|
49
|
+
which bundles none of the three), this still falls through to each declaration's own trailing
|
|
50
|
+
`sans-serif` same as before — it cannot add coverage that isn't on the device. */
|
|
51
|
+
@font-face {
|
|
52
|
+
font-family: 'Arial';
|
|
53
|
+
font-weight: 400;
|
|
54
|
+
font-style: normal;
|
|
55
|
+
src: local('Arial'), local('ArialMT'), local('Liberation Sans'), local('Arimo');
|
|
56
|
+
}
|
|
57
|
+
@font-face {
|
|
58
|
+
font-family: 'Arial';
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
font-style: normal;
|
|
61
|
+
/* Arial itself has no true Medium cut — Liberation Sans/Arimo do, so this at least gets a
|
|
62
|
+
real medium face on the platforms that have them instead of always synthesizing one. */
|
|
63
|
+
src: local('Liberation Sans Medium'), local('Arimo Medium'), local('Arial'), local('ArialMT');
|
|
64
|
+
}
|
|
65
|
+
@font-face {
|
|
66
|
+
font-family: 'Arial';
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
font-style: normal;
|
|
69
|
+
src: local('Arial Bold'), local('Arial-BoldMT'), local('Liberation Sans Bold'), local('Arimo Bold');
|
|
70
|
+
}
|