wineater-bot 4.0.0 → 4.1.0
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/docs/CUSTOMIZATION.md +133 -0
- package/package.json +30 -5
- package/src/App.svelte +73 -56
- package/src/components/Footer.svelte +26 -37
- package/src/components/Header.svelte +19 -32
- package/src/components/Loader.svelte +8 -8
- package/src/components/Search.svelte +13 -24
- package/src/components/WinePopup.svelte +35 -35
- package/src/components/WineTile.svelte +56 -61
- package/src/components/ui/Action.svelte +83 -0
- package/src/services/i18n/i18n.ts +9 -1
- package/src/services/store/app-store.svelte.ts +11 -0
- package/src/services/ui-config/get-config.ts +22 -0
- package/src/services/ui-config/projects-config.ts +62 -0
- package/src/services/ui-config/types.ts +13 -0
- package/src/services/ui-config/utils.ts +12 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# ADR: Widget Customization System
|
|
2
|
+
|
|
3
|
+
**Date:** 26.11.2025
|
|
4
|
+
**PR:** https://github.com/Wineater/wineater-bot/pull/2
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Clients increasingly request widget customization for their specific brands and use cases. Typical requests include:
|
|
9
|
+
|
|
10
|
+
- Changing colors and overall color scheme.
|
|
11
|
+
- Adjusting fonts and typography.
|
|
12
|
+
- Tweaking styles of individual UI elements (buttons, inputs, popups, etc.).
|
|
13
|
+
- Customizing translations and text copy.
|
|
14
|
+
|
|
15
|
+
Previously, such changes were mostly implemented as one‑off, manual style overrides or ad‑hoc modifications in the codebase. This approach has several drawbacks:
|
|
16
|
+
|
|
17
|
+
- Poor scalability when multiple clients require different customizations.
|
|
18
|
+
- High risk of regressions when editing styles for a single client.
|
|
19
|
+
- No clear system for naming, organizing, or reusing styles and components.
|
|
20
|
+
- Limited ability to evolve towards a proper design system and reusable components.
|
|
21
|
+
|
|
22
|
+
We want to move away from manual, one‑by‑one style hacking and instead provide:
|
|
23
|
+
|
|
24
|
+
- A component library that can be configured at the level of components, not individual DOM elements.
|
|
25
|
+
- A token‑based color scheme and shared design primitives.
|
|
26
|
+
- A single configuration entry point where client‑specific overrides can be described declaratively.
|
|
27
|
+
|
|
28
|
+
## Decision
|
|
29
|
+
|
|
30
|
+
We introduced a **customization system** for the widget with the following key aspects:
|
|
31
|
+
|
|
32
|
+
1. **Class naming system using BEM**
|
|
33
|
+
- A consistent naming convention was introduced for widget elements based on the BEM methodology.
|
|
34
|
+
- This makes it easier to:
|
|
35
|
+
- Identify and target elements for styling.
|
|
36
|
+
- Maintain styles over time.
|
|
37
|
+
- Reason about component structure and responsibilities.
|
|
38
|
+
|
|
39
|
+
2. **Centralized client configuration**
|
|
40
|
+
- Client‑specific behavior and appearance are now configured from a single configuration object.
|
|
41
|
+
- The configuration contains:
|
|
42
|
+
- General options that control various parts of the system (e.g. feature toggles and UI behavior flags).
|
|
43
|
+
- A `customCss` field that allows providing custom CSS for a client.
|
|
44
|
+
|
|
45
|
+
3. **Custom CSS entry point**
|
|
46
|
+
- The `customCss` property is designed for injecting client‑specific style overrides.
|
|
47
|
+
- To increase specificity and avoid conflicts with default styles, all overrides must start from the `#wr-Custom` root selector.
|
|
48
|
+
- Example pattern:
|
|
49
|
+
- `#wr-Custom .wr-Button { /* client-specific overrides */ }`
|
|
50
|
+
|
|
51
|
+
4. **Direction towards tokens and components**
|
|
52
|
+
- The current implementation is a first step towards:
|
|
53
|
+
- A shared color system expressed via tokens.
|
|
54
|
+
- A reusable set of components that can support variants (e.g. primary/secondary buttons, different input styles).
|
|
55
|
+
- The long‑term goal is to customize **component variants and design tokens** rather than patching arbitrary DOM elements with manual CSS.
|
|
56
|
+
|
|
57
|
+
## Alternatives
|
|
58
|
+
|
|
59
|
+
1. **Styling approach: Tailwind instead of BEM**
|
|
60
|
+
- We considered using Tailwind CSS (utility‑first classes) instead of the BEM naming convention.
|
|
61
|
+
- Pros:
|
|
62
|
+
- Potentially smaller final CSS payload due to tree‑shaking.
|
|
63
|
+
- Easier merging of classes to avoid shipping unused styles.
|
|
64
|
+
- Cons:
|
|
65
|
+
- Would require a full rewrite of existing styles into utility classes.
|
|
66
|
+
- Requires an additional class‑merge utility and Tailwind tooling in the build pipeline.
|
|
67
|
+
- Makes the resulting HTML/CSS less predictable for client‑side overrides, especially when clients are not familiar with Tailwind.
|
|
68
|
+
- Given the current state of the project and the need for predictable element names for customization, BEM was chosen over Tailwind.
|
|
69
|
+
|
|
70
|
+
2. **Configuration approach: separate CSS file per client**
|
|
71
|
+
- We also considered storing custom styles for each client in a dedicated CSS file.
|
|
72
|
+
- Pros:
|
|
73
|
+
- Clear physical separation of styles per client.
|
|
74
|
+
- Simpler for some hosting scenarios.
|
|
75
|
+
- Cons:
|
|
76
|
+
- Requires injecting a client identifier into the HTML to load the correct file.
|
|
77
|
+
- Increases deployment and configuration complexity (managing many CSS files per environment).
|
|
78
|
+
- Harder to keep styles and other configuration (translations, feature flags) in sync in a single place.
|
|
79
|
+
- Instead, we opted for a single configuration entry with an inline `customCss` field.
|
|
80
|
+
|
|
81
|
+
## Expectations
|
|
82
|
+
|
|
83
|
+
From this decision we expect the following benefits:
|
|
84
|
+
|
|
85
|
+
1. **More flexible customization for clients**
|
|
86
|
+
- Ability to adjust translations, color schemes, and key UI elements (buttons, inputs, basic layout) per client.
|
|
87
|
+
- A clear mechanism for styling without touching internal implementation details of the widget.
|
|
88
|
+
|
|
89
|
+
2. **Move towards a design‑system approach**
|
|
90
|
+
- Over time, introduce configurable component variants instead of ad‑hoc overrides.
|
|
91
|
+
- Establish color tokens and a configurable theme, instead of manually redefining colors in many places.
|
|
92
|
+
|
|
93
|
+
3. **Safer and more maintainable styling changes**
|
|
94
|
+
- BEM class naming makes selectors more predictable and less fragile.
|
|
95
|
+
- Centralized configuration reduces the chance of forgetting to apply a setting in some part of the system.
|
|
96
|
+
|
|
97
|
+
## Technical Debt and Planned Improvements
|
|
98
|
+
|
|
99
|
+
The current customization system is intentionally minimal and leaves several areas as explicit technical debt:
|
|
100
|
+
|
|
101
|
+
1. **Global configuration shared across projects**
|
|
102
|
+
- A single configuration model is used across multiple projects/clients, which means some unused options are shipped everywhere.
|
|
103
|
+
- We plan to introduce a more modular configuration structure so that clients only receive relevant settings.
|
|
104
|
+
|
|
105
|
+
2. **Manual style rewrites and lack of a change strategy**
|
|
106
|
+
- Many styles are still written and updated manually without a clear evolution path.
|
|
107
|
+
- We need a defined process and guidelines for introducing and deprecating style rules.
|
|
108
|
+
|
|
109
|
+
3. **Missing color system**
|
|
110
|
+
- There is no dedicated color token system yet.
|
|
111
|
+
- Colors are not centralized, making it hard to define themes and variants.
|
|
112
|
+
|
|
113
|
+
4. **No component‑level design tokens**
|
|
114
|
+
- Components (buttons, inputs, etc.) do not expose design tokens for spacing, radii, typography, and interaction states.
|
|
115
|
+
- A future step is to define tokens per component and allow them to be configured per client.
|
|
116
|
+
|
|
117
|
+
5. **Lack of component variants**
|
|
118
|
+
- Components do not yet expose standardized variants (e.g. `primary`, `secondary`, `ghost`).
|
|
119
|
+
- Introducing variants will make customization more structured and reduce the need for one‑off overrides.
|
|
120
|
+
|
|
121
|
+
6. **Font customization limitations**
|
|
122
|
+
- At the moment, fonts are effectively tied to Google Fonts.
|
|
123
|
+
- There is no supported mechanism for loading custom font files or non‑Google font providers per client.
|
|
124
|
+
|
|
125
|
+
7. **Icon customization limitations**
|
|
126
|
+
- It is not yet possible to change icon colors or replace icons via configuration.
|
|
127
|
+
- Future work should define an icon system (e.g. via tokens or an icon registry) that can be overridden per client.
|
|
128
|
+
|
|
129
|
+
8. **No first‑class configuration for core UI elements**
|
|
130
|
+
- Core components such as buttons and inputs are not yet configurable via a structured theme API.
|
|
131
|
+
- Future improvements should expose configuration options for these components (sizes, radii, colors, typography, and states) instead of relying solely on `customCss`.
|
|
132
|
+
|
|
133
|
+
These items are expected to be addressed iteratively as we mature the customization system and move closer to a full design‑system and token‑based theming approach.
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wineater-bot",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "dist/
|
|
5
|
+
"main": "dist/wineater-chatbot.mjs",
|
|
6
6
|
"module": "src/index.js",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"scripts": {
|
|
8
9
|
"dev": "vite",
|
|
9
10
|
"build": "vite build",
|
|
10
11
|
"release:patch": "npm version patch && npm publish",
|
|
11
12
|
"release:minor": "npm version minor && npm publish",
|
|
12
|
-
"release:major": "npm version major && npm publish"
|
|
13
|
+
"release:major": "npm version major && npm publish",
|
|
14
|
+
"release:beta": "npm version prerelease --preid=beta && npm run build && npm publish --tag beta",
|
|
15
|
+
"format": "prettier --write .",
|
|
16
|
+
"lint": "prettier --check . && eslint .",
|
|
17
|
+
"test:unit": "vitest",
|
|
18
|
+
"test": "npm run test:unit -- --run && npm run test:e2e",
|
|
19
|
+
"test:e2e": "playwright test"
|
|
13
20
|
},
|
|
14
21
|
"keywords": [],
|
|
15
22
|
"author": "",
|
|
@@ -17,9 +24,27 @@
|
|
|
17
24
|
"dependencies": {
|
|
18
25
|
"@openreplay/tracker": "^14.0.4",
|
|
19
26
|
"flagsmith": "^7.0.2",
|
|
20
|
-
"vite": "^5.2.9"
|
|
27
|
+
"vite": "^5.2.9",
|
|
28
|
+
"svelte": "^5.0.0"
|
|
21
29
|
},
|
|
22
30
|
"devDependencies": {
|
|
23
|
-
"
|
|
31
|
+
"@eslint/compat": "^1.4.0",
|
|
32
|
+
"@eslint/js": "^9.36.0",
|
|
33
|
+
"@playwright/test": "^1.55.1",
|
|
34
|
+
"@types/node": "^22",
|
|
35
|
+
"@vitest/browser": "^3.2.4",
|
|
36
|
+
"eslint": "^9.36.0",
|
|
37
|
+
"eslint-config-prettier": "^10.1.8",
|
|
38
|
+
"eslint-plugin-svelte": "^3.12.4",
|
|
39
|
+
"globals": "^16.4.0",
|
|
40
|
+
"playwright": "^1.55.1",
|
|
41
|
+
"prettier": "^3.6.2",
|
|
42
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
43
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
44
|
+
"typescript-eslint": "^8.44.1",
|
|
45
|
+
"vite-plugin-devtools-json": "^1.0.0",
|
|
46
|
+
"vitest": "^3.2.4",
|
|
47
|
+
"vitest-browser-svelte": "^1.1.0",
|
|
48
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0"
|
|
24
49
|
}
|
|
25
50
|
}
|
package/src/App.svelte
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import WineTile from './components/WineTile.svelte';
|
|
13
13
|
import { appStore } from './services/store/app-store.svelte';
|
|
14
14
|
import { getTranslation, translateTexts } from './services/i18n/i18n';
|
|
15
|
+
import { generateInlineStyle } from './services/ui-config/utils';
|
|
15
16
|
|
|
16
17
|
function initCookies() {
|
|
17
18
|
setCookie('chatbotToken', window.wineaterData?.CLIENT_TOKEN || DEMO_TOKEN, 365);
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
let apiKey = $derived(appStore.getApiKey());
|
|
42
43
|
let storeId = $derived(appStore.getStoreId());
|
|
43
44
|
let isStoreInitialized = $derived(appStore.isInitialized());
|
|
45
|
+
let uiConfig = $derived(appStore.getUiConfig());
|
|
44
46
|
|
|
45
47
|
function setSearchError() {
|
|
46
48
|
errorMessage = getTranslation({ key: 'errorMessage', lang: appLanguage });
|
|
@@ -225,7 +227,7 @@
|
|
|
225
227
|
|
|
226
228
|
function onTileClick(event, wine: Wine | RecommendedWine) {
|
|
227
229
|
// Don't track clicks on buy buttons or close buttons
|
|
228
|
-
if (event.target.closest('.
|
|
230
|
+
if (event.target.closest('.js-WineTileAction') || event.target.closest('.js-WinePopupClose')) {
|
|
229
231
|
return;
|
|
230
232
|
}
|
|
231
233
|
|
|
@@ -237,7 +239,7 @@
|
|
|
237
239
|
|
|
238
240
|
function handleClickOutside(event: MouseEvent) {
|
|
239
241
|
const target = event.target as HTMLElement;
|
|
240
|
-
const isInsidePopup = target.closest('.
|
|
242
|
+
const isInsidePopup = target.closest('.js-WinePopup') || target.closest('.js-WineTileAction');
|
|
241
243
|
|
|
242
244
|
if (!isInsidePopup) {
|
|
243
245
|
selectedWineId = null;
|
|
@@ -249,6 +251,10 @@
|
|
|
249
251
|
await appStore.initializeStore();
|
|
250
252
|
if (!isStoreInitialized) return;
|
|
251
253
|
|
|
254
|
+
uiConfig = appStore.getUiConfig();
|
|
255
|
+
if (uiConfig.customCss) {
|
|
256
|
+
generateInlineStyle(uiConfig.customCss);
|
|
257
|
+
}
|
|
252
258
|
// Set dynamic color variable from widget data
|
|
253
259
|
const customColor = window?.wineaterData?.COLOR || '#7B2BEB';
|
|
254
260
|
document.documentElement.style.setProperty('--wineater-color', customColor);
|
|
@@ -267,54 +273,57 @@
|
|
|
267
273
|
</script>
|
|
268
274
|
|
|
269
275
|
{#if isStoreInitialized}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
{#if isLoading}
|
|
277
|
-
<Loader />
|
|
278
|
-
{/if}
|
|
279
|
-
|
|
280
|
-
<div id="wine-widget-right">
|
|
281
|
-
{#if errorMessage}
|
|
282
|
-
<div class="error-message">
|
|
283
|
-
<p>{errorMessage}</p>
|
|
284
|
-
</div>
|
|
285
|
-
{:else if results && results.length > 0}
|
|
286
|
-
<div id="wine-placeholder">
|
|
287
|
-
{#each results as wine, index}
|
|
288
|
-
<WineTile
|
|
289
|
-
{wine}
|
|
290
|
-
{index}
|
|
291
|
-
{hoveredWineId}
|
|
292
|
-
{selectedWineId}
|
|
293
|
-
{isAnimating}
|
|
294
|
-
translatedPopup={translatedPopups[index]}
|
|
295
|
-
onBuyClick={handleBuyBtnClick}
|
|
296
|
-
onPopupClose={closePopup}
|
|
297
|
-
{onTileEnter}
|
|
298
|
-
{onTileLeave}
|
|
299
|
-
{onTileClick}
|
|
300
|
-
/>
|
|
301
|
-
{/each}
|
|
302
|
-
</div>
|
|
303
|
-
{/if}
|
|
276
|
+
<!-- wr-Custom is used only for custom styling -->
|
|
277
|
+
<!-- id is used to increase specificity -->
|
|
278
|
+
<div id="wr-Custom" class="wr-App">
|
|
279
|
+
{#if uiConfig.logoUrl}
|
|
280
|
+
<div class="wr-App-picture">
|
|
281
|
+
<img class="wr-App-logo" src={uiConfig.logoUrl} alt="" />
|
|
304
282
|
</div>
|
|
305
|
-
|
|
306
|
-
|
|
283
|
+
{/if}
|
|
284
|
+
<Header />
|
|
285
|
+
|
|
286
|
+
<Search bind:searchQuery onSubmit={handleSearch} {initialized} />
|
|
287
|
+
|
|
288
|
+
{#if isLoading}
|
|
289
|
+
<Loader />
|
|
290
|
+
{/if}
|
|
291
|
+
|
|
292
|
+
<div class="wr-App-content">
|
|
293
|
+
{#if errorMessage}
|
|
294
|
+
<div class="wr-App-error">
|
|
295
|
+
<p>{errorMessage}</p>
|
|
296
|
+
</div>
|
|
297
|
+
{:else if results && results.length > 0}
|
|
298
|
+
<div class="wr-App-placeholder">
|
|
299
|
+
{#each results as wine, index}
|
|
300
|
+
<WineTile
|
|
301
|
+
{wine}
|
|
302
|
+
{index}
|
|
303
|
+
{hoveredWineId}
|
|
304
|
+
{selectedWineId}
|
|
305
|
+
{isAnimating}
|
|
306
|
+
translatedPopup={translatedPopups[index]}
|
|
307
|
+
onBuyClick={handleBuyBtnClick}
|
|
308
|
+
onPopupClose={closePopup}
|
|
309
|
+
{onTileEnter}
|
|
310
|
+
{onTileLeave}
|
|
311
|
+
{onTileClick}
|
|
312
|
+
/>
|
|
313
|
+
{/each}
|
|
314
|
+
</div>
|
|
315
|
+
{/if}
|
|
307
316
|
</div>
|
|
317
|
+
|
|
318
|
+
<Footer {countryCode} />
|
|
308
319
|
</div>
|
|
309
320
|
{/if}
|
|
310
321
|
|
|
311
322
|
<style>
|
|
312
|
-
/* ===================== 0) Font Import ===================== */
|
|
313
|
-
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap');
|
|
314
|
-
|
|
315
323
|
/* ===================== 1) CSS Variables ===================== */
|
|
316
324
|
:global(:root) {
|
|
317
325
|
--wineater-corp-color: #0044ff;
|
|
326
|
+
/* TODO: get color from config */
|
|
318
327
|
--wineater-color: #7b2beb;
|
|
319
328
|
}
|
|
320
329
|
|
|
@@ -327,11 +336,15 @@
|
|
|
327
336
|
display: none;
|
|
328
337
|
}
|
|
329
338
|
|
|
339
|
+
:global(body) {
|
|
340
|
+
margin: 0;
|
|
341
|
+
padding: 0;
|
|
342
|
+
|
|
343
|
+
font-family: var(--wineater-font-primary, sans-serif);
|
|
344
|
+
}
|
|
345
|
+
|
|
330
346
|
/* ===================== 3) Widget Container ===================== */
|
|
331
|
-
|
|
332
|
-
display: flex;
|
|
333
|
-
gap: 24px;
|
|
334
|
-
font-family: 'Bricolage Grotesque', sans-serif;
|
|
347
|
+
.wr-App {
|
|
335
348
|
padding: 12px 12px 0px;
|
|
336
349
|
border-radius: 16px;
|
|
337
350
|
background-color: #fff;
|
|
@@ -341,24 +354,28 @@
|
|
|
341
354
|
min-height: 500px;
|
|
342
355
|
margin: 40px auto;
|
|
343
356
|
position: relative;
|
|
344
|
-
box-sizing: border-box;
|
|
345
357
|
}
|
|
346
358
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
359
|
+
.wr-App-picture {
|
|
360
|
+
display: flex;
|
|
361
|
+
justify-content: center;
|
|
362
|
+
align-items: center;
|
|
363
|
+
margin-bottom: 12px;
|
|
350
364
|
}
|
|
351
365
|
|
|
352
|
-
.
|
|
353
|
-
width: 100%;
|
|
366
|
+
.wr-App-logo {
|
|
367
|
+
max-width: 100%;
|
|
368
|
+
height: auto;
|
|
369
|
+
width: auto;
|
|
370
|
+
max-height: 60px;
|
|
354
371
|
}
|
|
355
372
|
|
|
356
|
-
|
|
373
|
+
.wr-App-content {
|
|
357
374
|
position: relative;
|
|
358
375
|
min-height: 300px;
|
|
359
376
|
}
|
|
360
377
|
|
|
361
|
-
|
|
378
|
+
.wr-App-placeholder {
|
|
362
379
|
display: grid;
|
|
363
380
|
grid-template-columns: repeat(4, 1fr);
|
|
364
381
|
gap: 0;
|
|
@@ -367,7 +384,7 @@
|
|
|
367
384
|
}
|
|
368
385
|
|
|
369
386
|
/* ===================== 12) Error Message ===================== */
|
|
370
|
-
.error
|
|
387
|
+
.wr-App-error {
|
|
371
388
|
text-align: center;
|
|
372
389
|
padding: 24px;
|
|
373
390
|
color: #666;
|
|
@@ -375,13 +392,13 @@
|
|
|
375
392
|
|
|
376
393
|
/* ===================== 13) Mobile Styles ===================== */
|
|
377
394
|
@media screen and (max-width: 768px) {
|
|
378
|
-
|
|
395
|
+
.wr-App {
|
|
379
396
|
flex-direction: column;
|
|
380
397
|
padding: 0px 16px;
|
|
381
398
|
min-width: 100vw;
|
|
382
399
|
}
|
|
383
400
|
|
|
384
|
-
|
|
401
|
+
.wr-App-placeholder {
|
|
385
402
|
display: flex;
|
|
386
403
|
gap: 8px;
|
|
387
404
|
overflow-x: auto;
|
|
@@ -27,81 +27,70 @@
|
|
|
27
27
|
}
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
|
-
<
|
|
31
|
-
<div class="
|
|
32
|
-
<a href="https://wineater.com" target="_blank">
|
|
33
|
-
<span>{getTranslation({ key: 'poweredBy', lang: appLanguage })}</span>
|
|
30
|
+
<footer class="wr-Footer">
|
|
31
|
+
<div class="wr-Footer-center">
|
|
32
|
+
<a href="https://wineater.com" class="wr-Footer-link" target="_blank">
|
|
33
|
+
<span class="wr-Footer-text">{getTranslation({ key: 'poweredBy', lang: appLanguage })}</span>
|
|
34
34
|
<img
|
|
35
|
+
class="wr-Footer-logo"
|
|
35
36
|
src={countryCode === 'ru'
|
|
36
37
|
? 'https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media/divino_logo.svg?x=5'
|
|
37
38
|
: 'https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media/logo_medium_gray.svg'}
|
|
38
39
|
alt="Wineater Logo"
|
|
39
40
|
/>
|
|
40
|
-
<span class="
|
|
41
|
-
|
|
42
|
-
>
|
|
41
|
+
<span class="wr-Footer-text wr-Footer-text--sommelier">
|
|
42
|
+
{getTranslation({ key: 'ai_sommelier', lang: appLanguage })}
|
|
43
|
+
</span>
|
|
43
44
|
</a>
|
|
44
45
|
</div>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{lang.toUpperCase()}
|
|
56
|
-
</option>
|
|
57
|
-
{/each}
|
|
58
|
-
</select>
|
|
59
|
-
{/if}
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
46
|
+
{#if supportedLanguages.length > 1}
|
|
47
|
+
<select class="wr-Footer-dropdown" value={appLanguage} onchange={handleLanguageChange}>
|
|
48
|
+
{#each supportedLanguages as lang}
|
|
49
|
+
<option class="wr-Footer-lang" value={lang} selected={lang === appLanguage}>
|
|
50
|
+
{lang.toUpperCase()}
|
|
51
|
+
</option>
|
|
52
|
+
{/each}
|
|
53
|
+
</select>
|
|
54
|
+
{/if}
|
|
55
|
+
</footer>
|
|
62
56
|
|
|
63
57
|
<style>
|
|
64
|
-
|
|
58
|
+
.wr-Footer {
|
|
65
59
|
position: relative;
|
|
66
60
|
border-top: 2px solid #f6f7fd;
|
|
67
|
-
font-family: 'Bricolage Grotesque', sans-serif;
|
|
68
61
|
font-weight: 200;
|
|
69
62
|
padding: 12px 24px;
|
|
70
63
|
min-height: 40px;
|
|
71
64
|
}
|
|
72
65
|
|
|
73
|
-
|
|
66
|
+
.wr-Footer-center {
|
|
74
67
|
position: absolute;
|
|
75
68
|
left: 50%;
|
|
76
69
|
transform: translateX(-50%);
|
|
77
70
|
text-align: center;
|
|
78
71
|
}
|
|
79
72
|
|
|
80
|
-
|
|
73
|
+
.wr-Footer-dropdown {
|
|
81
74
|
position: absolute;
|
|
82
75
|
right: 0px;
|
|
83
76
|
top: 50%;
|
|
84
77
|
transform: translateY(-50%);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.language-dropdown {
|
|
88
78
|
padding: 4px;
|
|
89
79
|
border-radius: 6px;
|
|
90
80
|
border: 2px solid var(--wineater-color);
|
|
91
81
|
background-color: white;
|
|
92
82
|
color: var(--wineater-color);
|
|
93
83
|
font-weight: 500;
|
|
94
|
-
font-family: 'Bricolage Grotesque', sans-serif;
|
|
95
84
|
font-size: 12px;
|
|
96
85
|
cursor: pointer;
|
|
97
86
|
transition: all 0.2s ease;
|
|
98
87
|
}
|
|
99
88
|
|
|
100
|
-
.
|
|
89
|
+
.wr-Footer-dropdown:hover {
|
|
101
90
|
box-shadow: 0 4px 8px rgba(123, 43, 235, 0.2);
|
|
102
91
|
}
|
|
103
92
|
|
|
104
|
-
|
|
93
|
+
.wr-Footer-link {
|
|
105
94
|
text-decoration: none;
|
|
106
95
|
display: flex;
|
|
107
96
|
align-items: center;
|
|
@@ -113,18 +102,18 @@
|
|
|
113
102
|
transition: opacity 0.2s ease;
|
|
114
103
|
}
|
|
115
104
|
|
|
116
|
-
|
|
105
|
+
.wr-Footer-link:hover {
|
|
117
106
|
opacity: 0.8;
|
|
118
107
|
}
|
|
119
108
|
|
|
120
|
-
|
|
109
|
+
.wr-Footer-logo {
|
|
121
110
|
height: 16px;
|
|
122
111
|
width: auto;
|
|
123
112
|
color: #a8a8a8;
|
|
124
113
|
}
|
|
125
114
|
|
|
126
115
|
@media screen and (max-width: 768px) {
|
|
127
|
-
.
|
|
116
|
+
.wr-Footer-text--sommelier {
|
|
128
117
|
display: none;
|
|
129
118
|
}
|
|
130
119
|
}
|
|
@@ -6,29 +6,27 @@
|
|
|
6
6
|
const titleText = window?.wineaterData?.TITLE_TEXT || null;
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
|
-
<div
|
|
9
|
+
<div class="wr-Header">
|
|
10
10
|
{#if titleText}
|
|
11
|
-
<h1 class="
|
|
11
|
+
<h1 class="wr-Header-title">{titleText}</h1>
|
|
12
12
|
{:else}
|
|
13
|
-
|
|
14
|
-
<h1 class="wine-widget-big-title">
|
|
13
|
+
<h1 class="wr-Header-title">
|
|
15
14
|
{getTranslation({ key: 'headerLine1', lang })}
|
|
16
|
-
<span class="icon icon
|
|
17
|
-
<span class="
|
|
18
|
-
<span class="icon icon
|
|
19
|
-
<span class="
|
|
15
|
+
<span class="wr-Header-icon wr-Header-icon--meal"></span>
|
|
16
|
+
<span class="wr-Header-label">{getTranslation({ key: 'headerMeal', lang })}</span>,
|
|
17
|
+
<span class="wr-Header-icon wr-Header-icon--mood"></span>
|
|
18
|
+
<span class="wr-Header-label">{getTranslation({ key: 'headerMood', lang })}</span>
|
|
20
19
|
<br />
|
|
21
20
|
{getTranslation({ key: 'headerOr', lang })}
|
|
22
|
-
<span class="icon icon
|
|
23
|
-
<span class="
|
|
21
|
+
<span class="wr-Header-icon wr-Header-icon--occasion"></span>
|
|
22
|
+
<span class="wr-Header-label">{getTranslation({ key: 'headerOccasion', lang })}</span>
|
|
24
23
|
{getTranslation({ key: 'headerLine2', lang })}
|
|
25
24
|
</h1>
|
|
26
25
|
{/if}
|
|
27
26
|
</div>
|
|
28
27
|
|
|
29
28
|
<style>
|
|
30
|
-
|
|
31
|
-
font-family: 'Bricolage Grotesque', serif;
|
|
29
|
+
.wr-Header-title {
|
|
32
30
|
font-weight: 600;
|
|
33
31
|
font-size: 24px;
|
|
34
32
|
margin: 0 auto;
|
|
@@ -37,15 +35,7 @@
|
|
|
37
35
|
text-align: center;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
font-size: 16px;
|
|
42
|
-
color: #666;
|
|
43
|
-
margin-top: 8px;
|
|
44
|
-
text-align: center;
|
|
45
|
-
padding: 0 15%;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.icon {
|
|
38
|
+
.wr-Header-icon {
|
|
49
39
|
display: inline-block;
|
|
50
40
|
width: 1.2em;
|
|
51
41
|
height: 1.2em;
|
|
@@ -55,37 +45,34 @@
|
|
|
55
45
|
background-size: contain;
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
.icon
|
|
48
|
+
.wr-Header-icon--meal {
|
|
59
49
|
background-image: url('https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media//meal1.svg');
|
|
60
50
|
}
|
|
61
51
|
|
|
62
|
-
.icon
|
|
52
|
+
.wr-Header-icon--meal:hover {
|
|
63
53
|
background-image: url('https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media//meal3.svg');
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
.icon
|
|
56
|
+
.wr-Header-icon--mood {
|
|
67
57
|
background-image: url('https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media//mood.svg');
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
/* Not used, old icon */
|
|
61
|
+
.wr-Header-icon--budget {
|
|
71
62
|
background-image: url('https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media//budget.svg');
|
|
72
63
|
}
|
|
73
64
|
|
|
74
|
-
.icon
|
|
65
|
+
.wr-Header-icon--occasion {
|
|
75
66
|
background-image: url('https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media//ocasion.svg');
|
|
76
67
|
}
|
|
77
68
|
|
|
78
|
-
.
|
|
69
|
+
.wr-Header-label {
|
|
79
70
|
color: var(--wineater-color);
|
|
80
71
|
font-weight: 500;
|
|
81
72
|
}
|
|
82
73
|
|
|
83
74
|
@media screen and (max-width: 768px) {
|
|
84
|
-
|
|
85
|
-
display: none;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
#wine-widget-header p {
|
|
75
|
+
.wr-Header-title {
|
|
89
76
|
display: none;
|
|
90
77
|
}
|
|
91
78
|
}
|