@startnext/chrome 0.3.2 → 0.3.4
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/README.md +249 -87
- package/dist/components/footer/StartnextFooter.d.ts +3 -2
- package/dist/components/footer/StartnextFooter.d.ts.map +1 -1
- package/dist/components/header/StartnextHeader.d.ts +3 -3
- package/dist/components/header/StartnextHeader.d.ts.map +1 -1
- package/dist/components/header/header.css.d.ts +1 -1
- package/dist/components/header/header.css.d.ts.map +1 -1
- package/dist/components/header/header.template.d.ts +0 -3
- package/dist/components/header/header.template.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.umd.js +2 -2
- package/dist/render.js +1 -1
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Startnext Header/Footer Web Components
|
|
4
4
|
|
|
5
|
-
Wiederverwendbare Header- und Footer-Components
|
|
5
|
+
Wiederverwendbare Header- und Footer-Components fuer alle Startnext Microservices.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ pnpm add @startnext/chrome
|
|
|
17
17
|
Es gibt zwei Integrationsmodi:
|
|
18
18
|
|
|
19
19
|
1. **Client-Side Rendering** — Web Component fetcht Daten selbst (einfach, kein Server noetig)
|
|
20
|
-
2. **Server-Side Rendering** — Server holt fertig gerendertes HTML von der API (sofort sichtbar ohne JS, bessere Performance + SEO)
|
|
20
|
+
2. **Server-Side Rendering (SSR)** — Server holt fertig gerendertes HTML von der API (sofort sichtbar ohne JS, bessere Performance + SEO)
|
|
21
21
|
|
|
22
22
|
### Client-Side: Via CDN (PHP, Vanilla HTML)
|
|
23
23
|
```html
|
|
@@ -39,7 +39,7 @@ import '@startnext/chrome';
|
|
|
39
39
|
|
|
40
40
|
### Server-Side Rendering (empfohlen)
|
|
41
41
|
|
|
42
|
-
SSR-HTML von der API holen und direkt einbinden. Die Web Component JS hydriert anschliessend (haengt Events an, statt DOM neu zu rendern).
|
|
42
|
+
SSR-HTML von der API holen und direkt einbinden. Die Web Component JS hydriert anschliessend (haengt Events an, statt DOM neu zu rendern). Die API-Daten werden als JSON im HTML eingebettet — der Component hat sofort echte Daten ohne Client-Fetch.
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
# SSR-HTML holen (z.B. serverseitig per fetch, file_get_contents, ESI)
|
|
@@ -47,6 +47,8 @@ curl "https://scs-api.vercel.app/api/header/render?lang=de&large-animation"
|
|
|
47
47
|
curl "https://scs-api.vercel.app/api/footer/render?lang=de"
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
Siehe Framework-spezifische Beispiele: [React / Next.js](#react--nextjs-app-router), [Vue / Nuxt](#vue--nuxt-3), [PHP / Vanilla](#php--vanilla-html)
|
|
51
|
+
|
|
50
52
|
## API
|
|
51
53
|
|
|
52
54
|
### `<startnext-header>`
|
|
@@ -56,8 +58,8 @@ curl "https://scs-api.vercel.app/api/footer/render?lang=de"
|
|
|
56
58
|
| Attribute | Type | Default | Description |
|
|
57
59
|
|---|---|---|---|
|
|
58
60
|
| `lang` | `string` | `'de'` | Sprache (`'de'`, `'en'`) |
|
|
59
|
-
| `light` | `boolean` | `false` | Heller Header (
|
|
60
|
-
| `large-animation` | `boolean` | `false` |
|
|
61
|
+
| `light` | `boolean` | `false` | Heller Header (weisse Schrift/Icons ueber dunklem Hero, wechselt automatisch zu dunkel beim Scrollen). Standard ohne Attribut: dunkler Header |
|
|
62
|
+
| `large-animation` | `boolean` | `false` | Grosse Lottie-Logo-Animation mit Claim und horizontaler Navigation |
|
|
61
63
|
| `authenticated` | `boolean` | `false` | Zeigt User-Avatar statt "Anmelden" Button |
|
|
62
64
|
| `user-name` | `string` | - | Name des eingeloggten Users |
|
|
63
65
|
| `user-avatar` | `string` | - | Avatar-URL des Users |
|
|
@@ -68,34 +70,34 @@ curl "https://scs-api.vercel.app/api/footer/render?lang=de"
|
|
|
68
70
|
| `show-back-link` | `boolean` | `false` | Zeigt einen "Zurueck zu Startnext"-Link (Text kommt von der API) |
|
|
69
71
|
| `back-url` | `string` | - | Ueberschreibt die Back-Link-URL |
|
|
70
72
|
| `back-label` | `string` | - | Ueberschreibt den Back-Link-Text |
|
|
71
|
-
| `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten aus Notion |
|
|
73
|
+
| `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten aus Notion (nicht noetig bei SSR) |
|
|
72
74
|
|
|
73
75
|
**Events:**
|
|
74
76
|
|
|
75
77
|
| Event | Detail | Description |
|
|
76
78
|
|---|---|---|
|
|
77
|
-
| `burger-menu-toggle` | `{ open: boolean }` | Burger-
|
|
78
|
-
| `user-menu-toggle` | `{ open: boolean }` | User-
|
|
79
|
+
| `burger-menu-toggle` | `{ open: boolean }` | Burger-Menue geoeffnet/geschlossen |
|
|
80
|
+
| `user-menu-toggle` | `{ open: boolean }` | User-Menue geoeffnet/geschlossen |
|
|
79
81
|
| `navigation-click` | `{ item: NavigationItem }` | Navigation-Link geklickt |
|
|
80
82
|
| `cta-click` | `{ url: string }` | "Projekt starten" Button geklickt |
|
|
81
83
|
| `logout` | `{}` | Abmelden geklickt |
|
|
82
84
|
| `language-change` | `{ language: string }` | Sprache gewechselt |
|
|
83
|
-
| `scroll-state-change` | `{ scrolled: boolean, slideUp: boolean }` | Scroll-Zustand
|
|
85
|
+
| `scroll-state-change` | `{ scrolled: boolean, slideUp: boolean }` | Scroll-Zustand geaendert |
|
|
84
86
|
| `color-mode-change` | `{ mode: 'light' \| 'dark' }` | Farbmodus gewechselt (per Toggle-Button) |
|
|
85
87
|
|
|
86
88
|
**Usage Examples:**
|
|
87
89
|
|
|
88
90
|
```html
|
|
89
|
-
<!-- Default: dunkler Header (dunkle Schrift
|
|
91
|
+
<!-- Default: dunkler Header (dunkle Schrift fuer helle Seiten) -->
|
|
90
92
|
<startnext-header></startnext-header>
|
|
91
93
|
|
|
92
|
-
<!-- Heller Header (
|
|
94
|
+
<!-- Heller Header (weisse Schrift ueber dunklem Hero) -->
|
|
93
95
|
<startnext-header light></startnext-header>
|
|
94
96
|
|
|
95
|
-
<!-- Heller Header +
|
|
97
|
+
<!-- Heller Header + grosse Animation (z.B. Startseite) -->
|
|
96
98
|
<startnext-header light large-animation></startnext-header>
|
|
97
99
|
|
|
98
|
-
<!-- Heller Header +
|
|
100
|
+
<!-- Heller Header + gross + eingeloggt -->
|
|
99
101
|
<startnext-header
|
|
100
102
|
light
|
|
101
103
|
large-animation
|
|
@@ -156,7 +158,7 @@ header.addEventListener('color-mode-change', (e) => {
|
|
|
156
158
|
| Attribute | Type | Default | Description |
|
|
157
159
|
|---|---|---|---|
|
|
158
160
|
| `lang` | `string` | `'de'` | Sprache (`'de'`, `'en'`) |
|
|
159
|
-
| `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten |
|
|
161
|
+
| `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten (nicht noetig bei SSR) |
|
|
160
162
|
| `lang-sync` | `boolean` | `true` | Synchronisiert Sprache automatisch wenn der Header ein `language-change` Event emittiert. Mit `lang-sync="false"` deaktivieren |
|
|
161
163
|
|
|
162
164
|
**Events:**
|
|
@@ -184,7 +186,7 @@ startnext-footer {
|
|
|
184
186
|
|
|
185
187
|
## Fonts
|
|
186
188
|
|
|
187
|
-
|
|
189
|
+
Standardmaessig nutzen die Components System-Fonts als Fallback. Fuer eigene Schriftarten `@font-face` im eigenen CSS deklarieren und `--font-family` setzen:
|
|
188
190
|
|
|
189
191
|
```css
|
|
190
192
|
@font-face {
|
|
@@ -202,68 +204,74 @@ startnext-header, startnext-footer {
|
|
|
202
204
|
|
|
203
205
|
## Server-Side Rendering (SSR)
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
### Render Entry Point
|
|
207
|
+
Die API liefert fertig gerendertes HTML mit **Declarative Shadow DOM** (`<template shadowrootmode="open">`) und eingebetteten **API-Daten als JSON** (`<script data-ssr-props>`). Der Browser zeigt den Content sofort an — ohne JavaScript. Die Web Component JS hydriert anschliessend (Events anbinden, kein Re-Render noetig).
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
import {
|
|
211
|
-
renderHeader, renderBurgerItems, renderUserItems,
|
|
212
|
-
renderFooter,
|
|
213
|
-
headerCSS, footerCSS,
|
|
214
|
-
renderHeaderCrawlerNav, renderFooterCrawlerNav,
|
|
215
|
-
toHeaderRenderData, toFooterRenderData,
|
|
216
|
-
getUiString, getIcon,
|
|
217
|
-
} from '@startnext/chrome/render';
|
|
218
|
-
```
|
|
209
|
+
### Wie es funktioniert
|
|
219
210
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
| `renderFooter(data)` | Footer Shadow DOM HTML |
|
|
228
|
-
| `headerCSS`, `footerCSS`, `resetCSS` | CSS als Strings |
|
|
229
|
-
| `getIcon(name, size?)` | SVG-Icon als String |
|
|
230
|
-
| `getUiString(key, lang)` | UI-String (ARIA Labels etc.) |
|
|
231
|
-
| `toHeaderRenderData(apiData)` | API-Daten → HeaderData (fuegt stub `theme` hinzu) |
|
|
232
|
-
| `toFooterRenderData(apiData)` | API-Daten → FooterData (fuegt stub `theme` hinzu) |
|
|
233
|
-
| `renderHeaderCrawlerNav(data)` | Crawler-`<nav>` fuer Header (Light DOM, SEO) |
|
|
234
|
-
| `renderFooterCrawlerNav(data)` | Crawler-`<nav>` fuer Footer (Light DOM, SEO) |
|
|
235
|
-
|
|
236
|
-
### Hydration
|
|
237
|
-
|
|
238
|
-
Wenn der Server HTML mit Declarative Shadow DOM (`<template shadowrootmode="open">`) liefert, erkennt die Web Component den bestehenden `shadowRoot` und **hydriert** statt neu zu rendern:
|
|
239
|
-
|
|
240
|
-
1. Browser parst DSD → `shadowRoot` existiert vor dem Custom-Element-Constructor
|
|
241
|
-
2. `BaseComponent` erkennt bestehenden `shadowRoot`, setzt `isHydrating = true`
|
|
242
|
-
3. `render()` ueberspringt `innerHTML`, ruft nur `attachEvents()` auf
|
|
243
|
-
4. `connectedCallback()` erkennt bestehenden `[data-crawler-nav]`, ueberspringt `renderLightDomNav()`
|
|
244
|
-
5. `loadApiData()` holt frische Daten → loest danach ein vollstaendiges Re-Render aus
|
|
211
|
+
1. Server fetcht SSR-HTML von `/api/header/render` bzw. `/api/footer/render`
|
|
212
|
+
2. HTML wird direkt in die Seite eingebunden
|
|
213
|
+
3. Browser parst DSD → Shadow DOM existiert sofort, Content ist sichtbar
|
|
214
|
+
4. Web Component JS laedt → erkennt bestehenden `shadowRoot`, setzt `isHydrating = true`
|
|
215
|
+
5. `render()` ueberspringt `innerHTML`, ruft nur `attachEvents()` auf
|
|
216
|
+
6. `parseSsrProps()` liest eingebettetes JSON → populiert API-Cache mit echten Daten
|
|
217
|
+
7. Attribut-Aenderungen (z.B. `color-mode`, `large-animation`) funktionieren sofort — kein Client-Fetch noetig
|
|
245
218
|
|
|
246
219
|
```html
|
|
247
|
-
<!-- Server liefert fertiges HTML -->
|
|
220
|
+
<!-- Server liefert fertiges HTML (vereinfacht) -->
|
|
248
221
|
<startnext-header lang="de" light large-animation>
|
|
249
222
|
<template shadowrootmode="open">
|
|
250
223
|
<style>/* headerCSS */</style>
|
|
251
224
|
<div><!-- Shadow DOM HTML --></div>
|
|
252
225
|
</template>
|
|
253
|
-
<
|
|
254
|
-
<!-- SEO-Links -->
|
|
255
|
-
</nav>
|
|
226
|
+
<script type="application/json" data-ssr-props>{"mainNavigation":[...],...}</script>
|
|
256
227
|
</startnext-header>
|
|
257
228
|
|
|
258
229
|
<!-- JS hydriert automatisch -->
|
|
259
230
|
<script type="module" src="@startnext/chrome"></script>
|
|
260
231
|
```
|
|
261
232
|
|
|
262
|
-
###
|
|
233
|
+
### Render-Endpoints (Query-Parameter)
|
|
234
|
+
|
|
235
|
+
**Header:** `GET /api/header/render`
|
|
236
|
+
|
|
237
|
+
| Parameter | Typ | Beschreibung |
|
|
238
|
+
|-----------|-----|-------------|
|
|
239
|
+
| `lang` | `de` \| `en` | Sprache |
|
|
240
|
+
| `light` | flag | Heller Header |
|
|
241
|
+
| `large-animation` | flag | Grosse Logo-Animation |
|
|
242
|
+
| `authenticated` | flag | Eingeloggt |
|
|
243
|
+
| `user-name` | string | User-Name |
|
|
244
|
+
| `user-avatar` | string | Avatar-URL |
|
|
245
|
+
| `hide-color-mode` | flag | Color-Mode Toggle verstecken |
|
|
246
|
+
| `hide-lang` | flag | Language-Switcher verstecken |
|
|
247
|
+
| `hide-login` | flag | Login-Button verstecken |
|
|
248
|
+
| `show-back-link` | flag | Back-Link anzeigen |
|
|
249
|
+
| `back-url` | string | Back-Link URL |
|
|
250
|
+
| `back-label` | string | Back-Link Text |
|
|
251
|
+
|
|
252
|
+
**Footer:** `GET /api/footer/render`
|
|
253
|
+
|
|
254
|
+
| Parameter | Typ | Beschreibung |
|
|
255
|
+
|-----------|-----|-------------|
|
|
256
|
+
| `lang` | `de` \| `en` | Sprache |
|
|
257
|
+
|
|
258
|
+
Beispiel: `GET /api/header/render?lang=de&large-animation&hide-login&show-back-link`
|
|
259
|
+
|
|
260
|
+
### `api-url` bei SSR nicht noetig
|
|
261
|
+
|
|
262
|
+
Bei SSR werden die API-Daten als JSON im HTML eingebettet (`<script data-ssr-props>`). Der Component liest das beim Hydrating und hat sofort echte Daten im Cache. `api-url` ist daher **nicht noetig** — der Component rendert und reagiert auf Attribut-Aenderungen ohne zusaetzlichen Fetch.
|
|
263
|
+
|
|
264
|
+
`api-url` ist nur fuer **Client-Side-Only** Rendering relevant, wenn kein SSR-HTML verfuegbar ist.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Framework-Integration (SSR)
|
|
263
269
|
|
|
264
|
-
React
|
|
270
|
+
### React / Next.js (App Router)
|
|
265
271
|
|
|
266
|
-
|
|
272
|
+
React's `dangerouslySetInnerHTML` nutzt `innerHTML`, das DSD nicht parsed. Nach der Hydration wird `setHTMLUnsafe()` als Fallback aufgerufen.
|
|
273
|
+
|
|
274
|
+
**Layout (Server Component):**
|
|
267
275
|
|
|
268
276
|
```tsx
|
|
269
277
|
// app/[locale]/layout.tsx
|
|
@@ -298,7 +306,7 @@ export default async function Layout({ children, params }) {
|
|
|
298
306
|
}
|
|
299
307
|
```
|
|
300
308
|
|
|
301
|
-
**
|
|
309
|
+
**Header (Client Component):**
|
|
302
310
|
|
|
303
311
|
```tsx
|
|
304
312
|
// components/ChromeHeader.tsx
|
|
@@ -312,50 +320,205 @@ export function ChromeHeader({ ssrHtml }: { ssrHtml?: string }) {
|
|
|
312
320
|
useEffect(() => {
|
|
313
321
|
const container = containerRef.current;
|
|
314
322
|
if (container && ssrHtml) {
|
|
315
|
-
// Falls DSD nicht geparsed wurde (innerHTML parsed kein DSD),
|
|
316
|
-
// setHTMLUnsafe() als Fallback nutzen
|
|
317
323
|
const header = container.querySelector("startnext-header");
|
|
318
324
|
if (!header?.shadowRoot && "setHTMLUnsafe" in Element.prototype) {
|
|
319
325
|
container.setHTMLUnsafe(ssrHtml);
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
|
-
import("@startnext/chrome");
|
|
328
|
+
import("@startnext/chrome");
|
|
323
329
|
setMounted(true);
|
|
324
330
|
}, [ssrHtml]);
|
|
325
331
|
|
|
332
|
+
// Attribut-Sync nach Mount (z.B. color-mode, large-animation)
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const el = containerRef.current?.querySelector("startnext-header");
|
|
335
|
+
if (!el || !mounted) return;
|
|
336
|
+
// Beispiel: el.setAttribute("color-mode", resolvedTheme);
|
|
337
|
+
}, [mounted]);
|
|
338
|
+
|
|
326
339
|
if (ssrHtml) {
|
|
327
340
|
return (
|
|
328
|
-
<div
|
|
329
|
-
ref={containerRef}
|
|
330
|
-
style={{ display: "contents" }}
|
|
341
|
+
<div ref={containerRef} style={{ display: "contents" }}
|
|
331
342
|
suppressHydrationWarning
|
|
332
343
|
dangerouslySetInnerHTML={{ __html: ssrHtml }}
|
|
333
344
|
/>
|
|
334
345
|
);
|
|
335
346
|
}
|
|
336
347
|
|
|
337
|
-
// Fallback
|
|
348
|
+
// Fallback ohne SSR
|
|
338
349
|
return <startnext-header api-url={process.env.NEXT_PUBLIC_SCS_API_URL} />;
|
|
339
350
|
}
|
|
340
351
|
```
|
|
341
352
|
|
|
342
|
-
**
|
|
353
|
+
**Footer (Client Component):**
|
|
343
354
|
|
|
344
|
-
|
|
355
|
+
```tsx
|
|
356
|
+
// components/ChromeFooter.tsx
|
|
357
|
+
"use client";
|
|
358
|
+
import { useEffect, useRef } from "react";
|
|
345
359
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
| `lang` | `de` \| `en` | Sprache |
|
|
349
|
-
| `light` | flag | Heller Header |
|
|
350
|
-
| `large-animation` | flag | Grosse Logo-Animation |
|
|
351
|
-
| `hide-color-mode` | flag | Color-Mode Toggle verstecken |
|
|
352
|
-
| `hide-lang` | flag | Language-Switcher verstecken |
|
|
353
|
-
| `hide-login` | flag | Login-Button verstecken |
|
|
354
|
-
| `show-back-link` | flag | Back-Link anzeigen |
|
|
355
|
-
| `back-url` | string | Back-Link URL |
|
|
356
|
-
| `back-label` | string | Back-Link Text |
|
|
360
|
+
export function ChromeFooter({ ssrHtml }: { ssrHtml?: string }) {
|
|
361
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
357
362
|
|
|
358
|
-
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
const container = containerRef.current;
|
|
365
|
+
if (container && ssrHtml) {
|
|
366
|
+
const footer = container.querySelector("startnext-footer");
|
|
367
|
+
if (!footer?.shadowRoot && "setHTMLUnsafe" in Element.prototype) {
|
|
368
|
+
container.setHTMLUnsafe(ssrHtml);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
import("@startnext/chrome");
|
|
372
|
+
}, [ssrHtml]);
|
|
373
|
+
|
|
374
|
+
if (ssrHtml) {
|
|
375
|
+
return (
|
|
376
|
+
<div ref={containerRef} style={{ display: "contents" }}
|
|
377
|
+
suppressHydrationWarning
|
|
378
|
+
dangerouslySetInnerHTML={{ __html: ssrHtml }}
|
|
379
|
+
/>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return <startnext-footer api-url={process.env.NEXT_PUBLIC_SCS_API_URL} />;
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Vue / Nuxt 3
|
|
388
|
+
|
|
389
|
+
Vue parsed DSD korrekt via `v-html` — kein `setHTMLUnsafe()` Workaround noetig.
|
|
390
|
+
|
|
391
|
+
**Server Plugin (SSR-HTML fetchen):**
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
// server/plugins/chrome.ts
|
|
395
|
+
export default defineEventHandler(async (event) => {
|
|
396
|
+
// Wird automatisch von useAsyncData / useFetch aufgerufen
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Layout:**
|
|
401
|
+
|
|
402
|
+
```vue
|
|
403
|
+
<!-- layouts/default.vue -->
|
|
404
|
+
<script setup lang="ts">
|
|
405
|
+
const { locale } = useI18n();
|
|
406
|
+
const config = useRuntimeConfig();
|
|
407
|
+
|
|
408
|
+
const { data: headerHtml } = await useFetch(
|
|
409
|
+
() => `${config.public.scsApiUrl}/api/header/render?lang=${locale.value}&large-animation`,
|
|
410
|
+
{ key: `chrome-header-${locale.value}` }
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const { data: footerHtml } = await useFetch(
|
|
414
|
+
() => `${config.public.scsApiUrl}/api/footer/render?lang=${locale.value}`,
|
|
415
|
+
{ key: `chrome-footer-${locale.value}` }
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
onMounted(() => {
|
|
419
|
+
import('@startnext/chrome');
|
|
420
|
+
});
|
|
421
|
+
</script>
|
|
422
|
+
|
|
423
|
+
<template>
|
|
424
|
+
<div v-if="headerHtml" v-html="headerHtml" style="display: contents" />
|
|
425
|
+
<startnext-header v-else :lang="locale" :api-url="config.public.scsApiUrl" />
|
|
426
|
+
|
|
427
|
+
<main>
|
|
428
|
+
<slot />
|
|
429
|
+
</main>
|
|
430
|
+
|
|
431
|
+
<div v-if="footerHtml" v-html="footerHtml" style="display: contents" />
|
|
432
|
+
<startnext-footer v-else :lang="locale" :api-url="config.public.scsApiUrl" />
|
|
433
|
+
</template>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Nuxt Config (Custom Elements registrieren):**
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
// nuxt.config.ts
|
|
440
|
+
export default defineNuxtConfig({
|
|
441
|
+
vue: {
|
|
442
|
+
compilerOptions: {
|
|
443
|
+
isCustomElement: (tag) => tag.startsWith('startnext-'),
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
runtimeConfig: {
|
|
447
|
+
public: {
|
|
448
|
+
scsApiUrl: process.env.SCS_API_URL || 'https://scs-api.vercel.app',
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### PHP / Vanilla HTML
|
|
455
|
+
|
|
456
|
+
```php
|
|
457
|
+
<?php
|
|
458
|
+
$apiUrl = 'https://scs-api.vercel.app';
|
|
459
|
+
$lang = 'de';
|
|
460
|
+
|
|
461
|
+
$headerHtml = @file_get_contents("$apiUrl/api/header/render?lang=$lang&large-animation");
|
|
462
|
+
$footerHtml = @file_get_contents("$apiUrl/api/footer/render?lang=$lang");
|
|
463
|
+
?>
|
|
464
|
+
<!DOCTYPE html>
|
|
465
|
+
<html lang="<?= $lang ?>">
|
|
466
|
+
<head>
|
|
467
|
+
<script type="module" src="https://unpkg.com/@startnext/chrome@latest/dist/index.js"></script>
|
|
468
|
+
</head>
|
|
469
|
+
<body>
|
|
470
|
+
<?= $headerHtml ?: '<startnext-header api-url="' . $apiUrl . '"></startnext-header>' ?>
|
|
471
|
+
|
|
472
|
+
<main><!-- Content --></main>
|
|
473
|
+
|
|
474
|
+
<?= $footerHtml ?: '<startnext-footer api-url="' . $apiUrl . '"></startnext-footer>' ?>
|
|
475
|
+
</body>
|
|
476
|
+
</html>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Edge Side Includes (ESI)
|
|
480
|
+
|
|
481
|
+
```html
|
|
482
|
+
<!-- Varnish / CDN ESI -->
|
|
483
|
+
<esi:try>
|
|
484
|
+
<esi:attempt>
|
|
485
|
+
<esi:include src="/api/header/render?lang=de&large-animation" />
|
|
486
|
+
</esi:attempt>
|
|
487
|
+
<esi:except>
|
|
488
|
+
<startnext-header api-url="https://scs-api.vercel.app"></startnext-header>
|
|
489
|
+
</esi:except>
|
|
490
|
+
</esi:try>
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Render Entry Point (Server-seitig)
|
|
496
|
+
|
|
497
|
+
Das Paket exportiert einen server-sicheren Subpath `@startnext/chrome/render` mit reinen String-Funktionen (keine Browser-Abhaengigkeiten):
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
import {
|
|
501
|
+
renderHeader, renderBurgerItems, renderUserItems,
|
|
502
|
+
renderFooter,
|
|
503
|
+
headerCSS, footerCSS,
|
|
504
|
+
toHeaderRenderData, toFooterRenderData,
|
|
505
|
+
getUiString, getIcon,
|
|
506
|
+
} from '@startnext/chrome/render';
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
| Export | Beschreibung |
|
|
510
|
+
|--------|-------------|
|
|
511
|
+
| `renderHeader(props)` | Header Shadow DOM HTML |
|
|
512
|
+
| `renderBurgerItems(data, expandedSections)` | Burger-Menue Items HTML |
|
|
513
|
+
| `renderUserItems(data, expandedSections)` | User-Menue Items HTML |
|
|
514
|
+
| `renderFooter(data)` | Footer Shadow DOM HTML |
|
|
515
|
+
| `headerCSS`, `footerCSS`, `resetCSS` | CSS als Strings |
|
|
516
|
+
| `getIcon(name, size?)` | SVG-Icon als String |
|
|
517
|
+
| `getUiString(key, lang)` | UI-String (ARIA Labels etc.) |
|
|
518
|
+
| `toHeaderRenderData(apiData)` | API-Daten -> HeaderData (fuegt stub `theme` hinzu) |
|
|
519
|
+
| `toFooterRenderData(apiData)` | API-Daten -> FooterData (fuegt stub `theme` hinzu) |
|
|
520
|
+
| `renderHeaderCrawlerNav(data)` | Crawler-`<nav>` fuer Header (optional, fuer Client-Side Rendering) |
|
|
521
|
+
| `renderFooterCrawlerNav(data)` | Crawler-`<nav>` fuer Footer (optional, fuer Client-Side Rendering) |
|
|
359
522
|
|
|
360
523
|
## Browser Support
|
|
361
524
|
|
|
@@ -417,14 +580,13 @@ src/
|
|
|
417
580
|
- Shadow DOM (open mode), kein Virtual DOM, Declarative Shadow DOM fuer SSR
|
|
418
581
|
|
|
419
582
|
**Zwei Entry Points:**
|
|
420
|
-
- `src/index.ts`
|
|
421
|
-
- `src/render.ts`
|
|
583
|
+
- `src/index.ts` -> `dist/index.js` (ESM) + `dist/index.umd.js` (UMD) — Browser, registriert Custom Elements
|
|
584
|
+
- `src/render.ts` -> `dist/render.js` (ESM) — Server, reine String-Funktionen ohne Browser-Abhaengigkeiten
|
|
422
585
|
|
|
423
586
|
## Monorepo Structure
|
|
424
587
|
|
|
425
588
|
```
|
|
426
589
|
packages/
|
|
427
|
-
scs-web-component/
|
|
428
|
-
scs-api/
|
|
590
|
+
scs-web-component/ <- dieses Paket
|
|
591
|
+
scs-api/ <- Express API Service (nutzt @startnext/chrome/render fuer SSR)
|
|
429
592
|
```
|
|
430
|
-
|
|
@@ -4,8 +4,6 @@ export declare class StartnextFooter extends BaseComponent {
|
|
|
4
4
|
private data;
|
|
5
5
|
/** Cached API data per language. */
|
|
6
6
|
private apiCache;
|
|
7
|
-
/** True while SSR content should be preserved (between hydration and first API data load). */
|
|
8
|
-
private _preserveSsrContent;
|
|
9
7
|
/** Bound handler for document-level language-change events. */
|
|
10
8
|
private _langSyncHandler;
|
|
11
9
|
private get currentLang();
|
|
@@ -17,6 +15,9 @@ export declare class StartnextFooter extends BaseComponent {
|
|
|
17
15
|
private teardownLangSync;
|
|
18
16
|
/** Return cached API data if available, otherwise defaults (theme only). */
|
|
19
17
|
private getFooterData;
|
|
18
|
+
/** Parse SSR-embedded data to populate the API cache during hydration.
|
|
19
|
+
* Eliminates the need for a client-side fetch when SSR HTML is used. */
|
|
20
|
+
private parseSsrProps;
|
|
20
21
|
/** If api-url is set, fetch footer data from API and re-render. */
|
|
21
22
|
private loadApiData;
|
|
22
23
|
private renderLightDomNav;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartnextFooter.d.ts","sourceRoot":"","sources":["../../../src/components/footer/StartnextFooter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,MAAM,CAAC,kBAAkB,WAAoC;IAE7D,OAAO,CAAC,IAAI,CAAc;IAC1B,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD
|
|
1
|
+
{"version":3,"file":"StartnextFooter.d.ts","sourceRoot":"","sources":["../../../src/components/footer/StartnextFooter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,MAAM,CAAC,kBAAkB,WAAoC;IAE7D,OAAO,CAAC,IAAI,CAAc;IAC1B,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,+DAA+D;IAC/D,OAAO,CAAC,gBAAgB,CAAqC;IAE7D,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,eAAe,GAE1B;IAED,iBAAiB,IAAI,IAAI;IAiBzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAe5C,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,gBAAgB;IAOxB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB;6EACyE;IACzE,OAAO,CAAC,aAAa;IAWrB,mEAAmE;YACrD,WAAW;IAuBzB,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,YAAY;CAUrB"}
|
|
@@ -12,9 +12,6 @@ export declare class StartnextHeader extends BaseComponent {
|
|
|
12
12
|
private pendingRaf;
|
|
13
13
|
/** Cached API data per language, so we don't re-fetch on every re-render. */
|
|
14
14
|
private apiCache;
|
|
15
|
-
/** True while SSR content should be preserved (between hydration and first API data load).
|
|
16
|
-
* Prevents attribute changes from triggering a full re-render with empty default data. */
|
|
17
|
-
private _preserveSsrContent;
|
|
18
15
|
private handleScrollBound;
|
|
19
16
|
private handleKeydownBound;
|
|
20
17
|
private handleResizeBound;
|
|
@@ -28,6 +25,9 @@ export declare class StartnextHeader extends BaseComponent {
|
|
|
28
25
|
connectedCallback(): void;
|
|
29
26
|
/** Return cached API data if available, otherwise defaults (theme only). */
|
|
30
27
|
private getHeaderData;
|
|
28
|
+
/** Parse SSR-embedded data to populate the API cache during hydration.
|
|
29
|
+
* Eliminates the need for a client-side fetch when SSR HTML is used. */
|
|
30
|
+
private parseSsrProps;
|
|
31
31
|
/** If api-url is set, fetch header data from API and re-render. */
|
|
32
32
|
private loadApiData;
|
|
33
33
|
disconnectedCallback(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartnextHeader.d.ts","sourceRoot":"","sources":["../../../src/components/header/StartnextHeader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,UAAU,CAAuB;IACzC,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAiC;
|
|
1
|
+
{"version":3,"file":"StartnextHeader.d.ts","sourceRoot":"","sources":["../../../src/components/header/StartnextHeader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,UAAU,CAAuB;IACzC,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAiC;IAEjD,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,kBAAkB,CAAiC;IAC3D,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,mBAAmB,CAAkC;IAE7D,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,KAAK,WAAW,GAEtB;IAED,6EAA6E;IAC7E,OAAO,KAAK,aAAa,GAGxB;IAED,OAAO,CAAC,EAAE;IAIV,iBAAiB,IAAI,IAAI;IAuBzB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB;6EACyE;IACzE,OAAO,CAAC,aAAa;IAWrB,mEAAmE;YACrD,WAAW;IA6BzB,oBAAoB,IAAI,IAAI;IAU5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAa5C,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAgDpB,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,iBAAiB;IAsDzB,OAAO,CAAC,MAAM;IAoCd,OAAO,CAAC,YAAY;CAkErB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const headerCSS = "\n \n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n :host {\n display: block;\n font-family: var(--font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);\n font-size: var(--font-size-base, 16px);\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n a {\n color: inherit;\n text-decoration: none;\n }\n\n button {\n font: inherit;\n cursor: pointer;\n border: none;\n background: none;\n color: inherit;\n }\n\n ul, ol {\n list-style: none;\n }\n\n img {\n max-width: 100%;\n height: auto;\n display: block;\n }\n\n\n :host {\n display: block;\n }\n\n /* \u2500\u2500 Headbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 100px;\n z-index: var(--header-z-index, 12);\n transition: transform 0.3s ease, opacity 0.3s ease, backdrop-filter 0.3s linear;\n will-change: transform, opacity, backdrop-filter;\n }\n\n .headbar::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background-color: #FFFFFF;\n z-index: -1;\n opacity: 0;\n transition: opacity 0.3s linear;\n }\n\n @media (min-width: 768px) {\n .headbar > * {\n transition: transform 0.3s ease, opacity 0.3s ease;\n }\n }\n\n /* \u2500\u2500 States \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar--light .headbar__icon {\n fill: #FFFFFF;\n color: #FFFFFF;\n }\n\n .headbar--light a,\n .headbar--light button {\n color: #FFFFFF;\n }\n\n .headbar--tight::after {\n opacity: 0.8;\n }\n\n .headbar--tight {\n backdrop-filter: blur(2px);\n }\n\n .headbar--tight .headbar__action--hide {\n opacity: 0;\n pointer-events: none;\n }\n\n .headbar--slide-up {\n pointer-events: none;\n opacity: 0;\n transform: translateY(-100%);\n }\n\n /* \u2500\u2500 Left (Burger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__left {\n position: absolute;\n left: 16px;\n top: 34px;\n display: flex;\n align-items: center;\n cursor: pointer;\n z-index: 2;\n }\n\n @media (min-width: 768px) {\n .headbar__left {\n left: 24px;\n }\n }\n\n /* \u2500\u2500 Right \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__right {\n position: absolute;\n right: 16px;\n top: 38px;\n height: 24px;\n display: flex;\n align-items: center;\n gap: 6px;\n z-index: 2;\n }\n\n @media (min-width: 768px) {\n .headbar__right {\n right: 24px;\n gap: 12px;\n }\n }\n\n /* \u2500\u2500 Icons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__icon {\n fill: var(--text-primary, #304b50);\n color: var(--text-primary, #304b50);\n transition: fill 0.3s linear, color 0.3s linear;\n will-change: fill, color;\n display: flex;\n align-items: center;\n }\n\n .headbar__icon svg {\n width: 24px;\n height: 24px;\n }\n\n .headbar__icon--clickable {\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n }\n\n .headbar__icon--clickable:hover {\n opacity: 0.7;\n }\n\n /* \u2500\u2500 CTA Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headerbar__btn {\n position: absolute;\n left: 54px;\n top: 34px;\n background-color: var(--btn-primary-bg, #06E481);\n height: 32px;\n border-radius: 16px;\n padding: 0 15px;\n font-size: 16px;\n font-weight: 700;\n display: flex;\n align-items: center;\n opacity: 0;\n transition: all 300ms ease-out;\n will-change: transform, opacity, max-width;\n z-index: 2;\n overflow: hidden;\n }\n\n .headerbar__btn--loaded {\n opacity: 1;\n }\n\n .headerbar__btn a {\n display: flex;\n align-items: center;\n white-space: nowrap;\n color: inherit;\n text-decoration: none;\n }\n\n .headerbar__btn:hover {\n background-color: var(--btn-primary-hover, #00B86F);\n }\n\n .headerbar__btn--text {\n fill: var(--text-primary, #304b50);\n transition: all 300ms ease-out;\n }\n\n .headerbar__btn--icon {\n opacity: 0;\n width: 0;\n transition: all 300ms ease-out;\n display: flex;\n align-items: center;\n }\n\n @media (max-width: 767px) {\n .headbar--tight .headerbar__btn--text {\n width: 0;\n overflow: hidden;\n }\n .headbar--tight .headerbar__btn--icon {\n width: 24px;\n opacity: 1;\n }\n }\n\n @media (min-width: 768px) {\n .headerbar__btn {\n left: 80px;\n }\n }\n\n /* \u2500\u2500 Main Nav (desktop > 1100px) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__menu {\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n @media (min-width: 1100px) {\n .headbar__menu {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n height: 100%;\n display: flex;\n align-items: center;\n transition: all 300ms ease-out;\n max-width: fit-content;\n will-change: transform, opacity, max-width;\n }\n\n .headbar__menu--faded {\n transform: translateX(calc(-50% - 60px));\n max-width: 0;\n opacity: 0;\n }\n }\n\n .headbar__menu-item {\n list-style: none;\n }\n\n .headbar__menu-link {\n display: inline-block;\n padding: 8px 15px;\n position: relative;\n font-size: 16px;\n color: var(--text-primary, #304b50);\n text-decoration: none;\n transition: color 0.3s;\n }\n\n .headbar--light .headbar__menu-link {\n color: #FFFFFF;\n }\n\n .headbar__menu-link::after {\n content: \"\";\n position: absolute;\n left: 15px;\n right: 15px;\n bottom: 2px;\n height: 2px;\n background-color: var(--text-primary, #304b50);\n opacity: 0;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__menu-link::after {\n background-color: #FFFFFF;\n }\n\n .headbar__menu-link:hover::after {\n opacity: 1;\n }\n\n /* \u2500\u2500 Logo (Lottie) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__pageLogo--animation {\n width: 100%;\n max-width: 200px;\n margin: 0 auto;\n display: block;\n position: relative;\n top: 110px;\n transform: translateY(-110px) scale(0.65);\n z-index: 1;\n text-decoration: none;\n }\n\n .headbar__pageLogo--animation svg {\n transform: unset !important;\n }\n\n .headbar__pageLogo--animation svg path[fill] {\n fill: var(--text-primary, #304b50);\n }\n .headbar__pageLogo--animation svg path[stroke] {\n stroke: var(--text-primary, #304b50);\n }\n\n .headbar--light .headbar__pageLogo--animation svg path[fill] {\n fill: #FFFFFF;\n }\n .headbar--light .headbar__pageLogo--animation svg path[stroke] {\n stroke: #FFFFFF;\n }\n\n @media (min-width: 768px) {\n .headbar__pageLogo--animation {\n max-width: 300px;\n transform: translateY(-115px) scale(0.5);\n top: 100px;\n }\n }\n\n @media (min-width: 1100px) {\n .headbar__pageLogo--animation {\n top: 120px;\n transform: translateY(-140px) scale(0.5);\n }\n }\n\n .headbar__pageLogo--animation-large {\n transform: translateY(0) scale(1);\n }\n\n /* \u2500\u2500 Claim \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__claim {\n font-size: 14px;\n transition: all 0.3s;\n width: 100%;\n text-align: center;\n color: var(--text-primary, #304b50);\n }\n\n .headbar--light .headbar__claim {\n color: #FFFFFF;\n }\n\n .headbar__claim--large {\n position: absolute;\n top: 201px;\n left: 50%;\n transform: translate(-50%);\n white-space: nowrap;\n z-index: 1;\n }\n\n .headbar--tight .headbar__claim--large {\n pointer-events: none;\n }\n\n @media (min-width: 768px) {\n .headbar__claim--large {\n top: 232px;\n }\n }\n\n @media (min-width: 1100px) {\n .headbar__claim--large {\n top: 252px;\n }\n }\n\n /* \u2500\u2500 User Avatar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n object-fit: cover;\n cursor: pointer;\n border: 2px solid transparent;\n transition: border-color 0.3s;\n }\n\n .headbar--light .headbar__avatar {\n border-color: rgba(255, 255, 255, 0.3);\n }\n\n .headbar__avatar:hover {\n opacity: 0.85;\n }\n\n /* \u2500\u2500 Login Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__login {\n padding: 4px 6px;\n font-size: 16px;\n font-family: inherit;\n white-space: nowrap;\n border: none;\n cursor: pointer;\n background: none;\n color: var(--text-primary, #304b50);\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n }\n\n .headbar--light .headbar__login {\n color: #FFFFFF;\n }\n\n .headbar__login:hover {\n opacity: 0.75;\n }\n\n .headbar__login-icon {\n display: flex;\n align-items: center;\n }\n\n .headbar__login-text {\n display: none;\n }\n\n .headbar__login-icon {\n display: flex;\n }\n\n @media (min-width: 768px) {\n .headbar__login-text {\n display: inline;\n }\n .headbar__login-icon {\n display: none;\n }\n }\n\n /* \u2500\u2500 Language Dropdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__lang {\n position: relative;\n }\n\n .headbar__lang-toggle {\n display: flex;\n align-items: center;\n gap: 4px;\n font-family: inherit;\n font-size: 16px;\n padding: 4px 6px;\n border: none;\n background: none;\n color: var(--text-primary, #304b50);\n cursor: pointer;\n border-radius: 4px;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__lang-toggle {\n color: #FFFFFF;\n }\n\n .headbar__lang-toggle:hover {\n opacity: 0.75;\n }\n\n .headbar__lang-arrow {\n display: flex;\n align-items: center;\n position: relative;\n top: -1px;\n left: 2px;\n transition: transform 0.25s ease;\n }\n\n .headbar__lang-arrow.open {\n transform: rotate(180deg);\n }\n\n .headbar__lang-menu {\n position: absolute;\n top: calc(100% + 6px);\n left: 50%;\n min-width: 120px;\n background: #FFFFFF;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n opacity: 0;\n visibility: hidden;\n transform: translateX(-50%) translateY(-8px);\n transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;\n z-index: 10;\n }\n\n .headbar__lang-menu.open {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) translateY(0);\n }\n\n .headbar__lang-option {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 14px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n color: #304b50;\n background: none;\n border: none;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n text-align: left;\n }\n\n .headbar__lang-option:hover {\n color: var(--btn-primary-bg, #06E481);\n background: var(--hover-bg, #F3F4F6);\n }\n\n .headbar__lang-option--active {\n font-weight: 700;\n color: var(--btn-primary-bg, #06E481);\n }\n\n .headbar--light .headbar__lang-option {\n color: #304b50;\n }\n\n .headbar--light .headbar__lang-option:hover,\n .headbar--light .headbar__lang-option--active {\n color: var(--btn-primary-bg, #06E481);\n }\n\n /* \u2500\u2500 Back Link \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__back-link {\n display: flex;\n align-items: center;\n gap: 6px;\n text-decoration: none;\n color: var(--text-primary, #304b50);\n font-size: 16px;\n white-space: nowrap;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__back-link {\n color: #FFFFFF;\n }\n\n .headbar__back-link:hover {\n opacity: 0.75;\n }\n\n .headbar__back-arrow {\n display: flex;\n align-items: center;\n position: relative;\n top: -1px;\n }\n\n @media (max-width: 768px) {\n .headbar__back-text { display: none; }\n }\n\n /* \u2500\u2500 Color Mode Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__color-mode {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: 50%;\n color: var(--text-primary, #304b50);\n transition: opacity 0.2s, color 0.3s;\n }\n\n .headbar--light .headbar__color-mode {\n color: #FFFFFF;\n }\n\n .headbar__color-mode:hover {\n opacity: 0.7;\n }\n\n .headbar__color-mode-sun,\n .headbar__color-mode-moon {\n display: flex;\n align-items: center;\n }\n\n .headbar__color-mode-moon {\n display: none;\n }\n\n :host([color-mode=\"dark\"]) .headbar__color-mode-sun {\n display: none;\n }\n\n :host([color-mode=\"dark\"]) .headbar__color-mode-moon {\n display: flex;\n }\n\n /* \u2500\u2500 Overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: var(--overlay-z-index, 1050);\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),\n visibility 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .overlay.open {\n opacity: 1;\n visibility: visible;\n }\n\n /* \u2500\u2500 Drawer (shared) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .drawer {\n position: fixed;\n top: 0;\n bottom: 0;\n width: 320px;\n max-width: 85vw;\n background: #FFFFFF;\n z-index: var(--drawer-z-index, 1100);\n overflow-y: auto;\n transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n color: var(--text-primary, #304b50);\n }\n\n .drawer--left {\n left: 0;\n transform: translateX(-100%);\n box-shadow: 4px 0 12px rgba(0, 0, 0, 0.1);\n }\n\n .drawer--right {\n right: 0;\n transform: translateX(100%);\n box-shadow: -4px 0 12px rgba(0, 0, 0, 0.1);\n }\n\n .drawer.open {\n transform: translateX(0);\n }\n\n .drawer__user {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid var(--border-color, #E5E7EB);\n }\n\n .drawer__user-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .drawer__user-name {\n font-weight: 600;\n font-size: 16px;\n }\n\n .drawer__nav {\n padding: 8px 0;\n }\n\n .drawer__item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 20px;\n font-size: 16px;\n font-weight: 500;\n transition: background-color 0.15s;\n cursor: pointer;\n width: 100%;\n text-align: left;\n color: var(--text-primary, #304b50);\n text-decoration: none;\n border: none;\n background: none;\n font-family: inherit;\n }\n\n .drawer__item:hover {\n background: var(--hover-bg, #F3F4F6);\n }\n\n .drawer__item--highlighted {\n color: #FFFFFF;\n background: var(--btn-primary-bg, #06E481);\n margin: 8px 16px;\n border-radius: 8px;\n justify-content: center;\n font-weight: 600;\n width: auto;\n }\n\n .drawer__item--highlighted:hover {\n background: var(--btn-primary-hover, #00B86F);\n }\n\n .drawer__item--small {\n font-size: 14px;\n color: var(--text-secondary, #6B7280);\n padding: 8px 20px;\n font-weight: 400;\n }\n\n .drawer__item--disabled {\n color: var(--text-disabled, #D1D5DB);\n pointer-events: none;\n }\n\n .drawer__item--outline {\n margin: 8px 16px;\n border: 1.5px solid var(--border-color, #E5E7EB);\n border-radius: 8px;\n justify-content: center;\n width: auto;\n background: none;\n }\n\n .drawer__item--outline:hover {\n border-color: var(--text-secondary, #6B7280);\n background: var(--hover-bg, #F3F4F6);\n }\n\n .drawer__item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n\n .drawer__item-badge {\n margin-left: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--btn-primary-bg, #06E481);\n color: #FFFFFF;\n font-size: 12px;\n font-weight: 700;\n }\n\n .drawer__item-expand {\n margin-left: auto;\n display: flex;\n align-items: center;\n transform: rotate(0deg);\n transition: transform 0.3s ease;\n }\n\n .drawer__item-expand.expanded {\n transform: rotate(180deg);\n }\n\n .drawer__item-nav-arrow {\n margin-left: auto;\n display: flex;\n align-items: center;\n transform: rotate(-90deg);\n }\n\n .drawer__divider {\n height: 1px;\n background: var(--border-color, #E5E7EB);\n margin: 8px 20px;\n }\n\n .drawer__children {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n .drawer__children.expanded {\n max-height: 500px;\n }\n\n .drawer__child {\n display: block;\n padding: 10px 20px 10px 52px;\n font-size: 14px;\n color: var(--text-secondary, #6B7280);\n transition: background-color 0.15s, color 0.15s;\n text-decoration: none;\n }\n\n .drawer__child:hover {\n background: var(--hover-bg, #F3F4F6);\n color: var(--text-primary, #304b50);\n }\n\n /* \u2500\u2500 Focus \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__icon--clickable:focus-visible,\n .headerbar__btn:focus-visible,\n .headbar__login:focus-visible,\n .headbar__menu-link:focus-visible,\n .drawer__item:focus-visible,\n .drawer__child:focus-visible {\n outline: 2px solid var(--btn-primary-bg, #06E481);\n outline-offset: 2px;\n }\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n";
|
|
1
|
+
export declare const headerCSS = "\n \n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n :host {\n display: block;\n font-family: var(--font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);\n font-size: var(--font-size-base, 16px);\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n a {\n color: inherit;\n text-decoration: none;\n }\n\n button {\n font: inherit;\n cursor: pointer;\n border: none;\n background: none;\n color: inherit;\n }\n\n ul, ol {\n list-style: none;\n }\n\n img {\n max-width: 100%;\n height: auto;\n display: block;\n }\n\n\n :host {\n display: block;\n }\n\n /* \u2500\u2500 Headbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 100px;\n z-index: var(--header-z-index, 12);\n transition: transform 0.3s ease, opacity 0.3s ease, backdrop-filter 0.3s linear;\n will-change: transform, opacity, backdrop-filter;\n }\n\n .headbar::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background-color: #FFFFFF;\n z-index: -1;\n opacity: 0;\n transition: opacity 0.3s linear;\n }\n\n @media (min-width: 768px) {\n .headbar > * {\n transition: transform 0.3s ease, opacity 0.3s ease;\n }\n }\n\n /* \u2500\u2500 States \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar--light .headbar__icon {\n fill: #FFFFFF;\n color: #FFFFFF;\n }\n\n .headbar--light a,\n .headbar--light button {\n color: #FFFFFF;\n }\n\n .headbar--tight::after {\n opacity: 0.8;\n }\n\n .headbar--tight {\n backdrop-filter: blur(2px);\n }\n\n .headbar--tight .headbar__action--hide {\n opacity: 0;\n pointer-events: none;\n }\n\n .headbar--slide-up {\n pointer-events: none;\n opacity: 0;\n transform: translateY(-100%);\n }\n\n /* \u2500\u2500 Left (Burger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__left {\n position: absolute;\n left: 16px;\n top: 34px;\n display: flex;\n align-items: center;\n cursor: pointer;\n z-index: 2;\n }\n\n @media (min-width: 768px) {\n .headbar__left {\n left: 24px;\n }\n }\n\n /* \u2500\u2500 Right \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__right {\n position: absolute;\n right: 16px;\n top: 38px;\n height: 24px;\n display: flex;\n align-items: center;\n gap: 6px;\n z-index: 2;\n }\n\n @media (min-width: 768px) {\n .headbar__right {\n right: 24px;\n gap: 12px;\n }\n }\n\n /* \u2500\u2500 Icons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__icon {\n fill: var(--text-primary, #304b50);\n color: var(--text-primary, #304b50);\n transition: fill 0.3s linear, color 0.3s linear;\n will-change: fill, color;\n display: flex;\n align-items: center;\n }\n\n .headbar__icon svg {\n width: 24px;\n height: 24px;\n }\n\n .headbar__icon--clickable {\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n }\n\n .headbar__icon--clickable:hover {\n opacity: 0.7;\n }\n\n /* \u2500\u2500 CTA Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headerbar__btn {\n position: absolute;\n left: 54px;\n top: 34px;\n background-color: var(--btn-primary-bg, #06E481);\n height: 32px;\n border-radius: 16px;\n padding: 0 15px;\n font-size: 16px;\n font-weight: 700;\n display: flex;\n align-items: center;\n opacity: 0;\n transition: all 300ms ease-out;\n will-change: transform, opacity, max-width;\n z-index: 2;\n overflow: hidden;\n }\n\n .headerbar__btn--loaded {\n opacity: 1;\n }\n\n .headerbar__btn a {\n display: flex;\n align-items: center;\n white-space: nowrap;\n color: inherit;\n text-decoration: none;\n }\n\n .headerbar__btn:hover {\n background-color: var(--btn-primary-hover, #00B86F);\n }\n\n .headerbar__btn--text {\n fill: var(--text-primary, #304b50);\n transition: all 300ms ease-out;\n }\n\n .headerbar__btn--icon {\n opacity: 0;\n width: 0;\n transition: all 300ms ease-out;\n display: flex;\n align-items: center;\n }\n\n @media (max-width: 767px) {\n .headbar--tight .headerbar__btn--text {\n width: 0;\n overflow: hidden;\n }\n .headbar--tight .headerbar__btn--icon {\n width: 24px;\n opacity: 1;\n }\n }\n\n @media (min-width: 768px) {\n .headerbar__btn {\n left: 80px;\n }\n }\n\n /* \u2500\u2500 Main Nav (desktop > 1100px) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__menu {\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n @media (min-width: 1100px) {\n .headbar__menu {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n height: 100%;\n display: flex;\n align-items: center;\n transition: all 300ms ease-out;\n max-width: fit-content;\n will-change: transform, opacity, max-width;\n }\n\n .headbar__menu--faded {\n transform: translateX(calc(-50% - 60px));\n max-width: 0;\n opacity: 0;\n }\n }\n\n .headbar__menu-item {\n list-style: none;\n }\n\n .headbar__menu-link {\n display: inline-block;\n padding: 8px 15px;\n position: relative;\n font-size: 16px;\n color: var(--text-primary, #304b50);\n text-decoration: none;\n transition: color 0.3s;\n }\n\n .headbar--light .headbar__menu-link {\n color: #FFFFFF;\n }\n\n .headbar__menu-link::after {\n content: \"\";\n position: absolute;\n left: 15px;\n right: 15px;\n bottom: 2px;\n height: 2px;\n background-color: var(--text-primary, #304b50);\n opacity: 0;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__menu-link::after {\n background-color: #FFFFFF;\n }\n\n .headbar__menu-link:hover::after {\n opacity: 1;\n }\n\n /* \u2500\u2500 Logo (Lottie) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__pageLogo--animation {\n width: 100%;\n max-width: 200px;\n margin: 0 auto;\n display: block;\n position: relative;\n top: 110px;\n transform: translateY(-110px) scale(0.65);\n z-index: 1;\n text-decoration: none;\n }\n\n .headbar__pageLogo--animation svg {\n transform: unset !important;\n }\n\n .headbar__pageLogo--animation svg path[fill] {\n fill: var(--text-primary, #304b50);\n }\n .headbar__pageLogo--animation svg path[stroke] {\n stroke: var(--text-primary, #304b50);\n }\n\n .headbar--light .headbar__pageLogo--animation svg path[fill] {\n fill: #FFFFFF;\n }\n .headbar--light .headbar__pageLogo--animation svg path[stroke] {\n stroke: #FFFFFF;\n }\n\n @media (min-width: 768px) {\n .headbar__pageLogo--animation {\n max-width: 300px;\n transform: translateY(-115px) scale(0.5);\n top: 100px;\n }\n }\n\n @media (min-width: 1100px) {\n .headbar__pageLogo--animation {\n top: 120px;\n transform: translateY(-140px) scale(0.5);\n }\n }\n\n .headbar__pageLogo--animation-large {\n transform: translateY(0) scale(1);\n }\n\n /* \u2500\u2500 Claim \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__claim {\n font-size: 14px;\n transition: all 0.3s;\n width: 100%;\n text-align: center;\n color: var(--text-primary, #304b50);\n }\n\n .headbar--light .headbar__claim {\n color: #FFFFFF;\n }\n\n .headbar__claim--large {\n position: absolute;\n top: 201px;\n left: 50%;\n transform: translate(-50%);\n white-space: nowrap;\n z-index: 1;\n }\n\n .headbar--tight .headbar__claim--large {\n pointer-events: none;\n }\n\n @media (min-width: 768px) {\n .headbar__claim--large {\n top: 232px;\n }\n }\n\n @media (min-width: 1100px) {\n .headbar__claim--large {\n top: 252px;\n }\n }\n\n /* \u2500\u2500 User Avatar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n object-fit: cover;\n cursor: pointer;\n border: 2px solid transparent;\n transition: border-color 0.3s;\n }\n\n .headbar--light .headbar__avatar {\n border-color: rgba(255, 255, 255, 0.3);\n }\n\n .headbar__avatar:hover {\n opacity: 0.85;\n }\n\n /* \u2500\u2500 Login Button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__login {\n padding: 4px 6px;\n font-size: 16px;\n font-family: inherit;\n white-space: nowrap;\n border: none;\n cursor: pointer;\n background: none;\n color: var(--text-primary, #304b50);\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n }\n\n .headbar--light .headbar__login {\n color: #FFFFFF;\n }\n\n .headbar__login:hover {\n opacity: 0.75;\n }\n\n .headbar__login-icon {\n display: flex;\n align-items: center;\n }\n\n .headbar__login-text {\n display: none;\n }\n\n .headbar__login-icon {\n display: flex;\n }\n\n @media (min-width: 768px) {\n .headbar__login-text {\n display: inline;\n }\n .headbar__login-icon {\n display: none;\n }\n }\n\n /* \u2500\u2500 Language Dropdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__lang {\n position: relative;\n }\n\n .headbar__lang-toggle {\n display: flex;\n align-items: center;\n gap: 4px;\n font-family: inherit;\n font-size: 16px;\n padding: 4px 6px;\n border: none;\n background: none;\n color: var(--text-primary, #304b50);\n cursor: pointer;\n border-radius: 4px;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__lang-toggle {\n color: #FFFFFF;\n }\n\n .headbar__lang-toggle:hover {\n opacity: 0.75;\n }\n\n .headbar__lang-arrow {\n display: flex;\n align-items: center;\n position: relative;\n top: -1px;\n left: 2px;\n transition: transform 0.25s ease;\n }\n\n .headbar__lang-arrow.open {\n transform: rotate(180deg);\n }\n\n .headbar__lang-menu {\n position: absolute;\n top: calc(100% + 6px);\n left: 50%;\n min-width: 120px;\n background: #FFFFFF;\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n opacity: 0;\n visibility: hidden;\n transform: translateX(-50%) translateY(-8px);\n transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;\n z-index: 10;\n }\n\n .headbar__lang-menu.open {\n opacity: 1;\n visibility: visible;\n transform: translateX(-50%) translateY(0);\n }\n\n .headbar__lang-option {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 14px;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n color: #304b50;\n background: none;\n border: none;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n text-align: left;\n }\n\n .headbar__lang-option:hover {\n color: var(--btn-primary-bg, #06E481);\n background: var(--hover-bg, #F3F4F6);\n }\n\n .headbar__lang-option--active {\n font-weight: 700;\n color: var(--btn-primary-bg, #06E481);\n }\n\n .headbar--light .headbar__lang-option {\n color: #304b50;\n }\n\n .headbar--light .headbar__lang-option:hover,\n .headbar--light .headbar__lang-option--active {\n color: var(--btn-primary-bg, #06E481);\n }\n\n /* \u2500\u2500 Back Link \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__back-link {\n display: flex;\n align-items: center;\n gap: 6px;\n text-decoration: none;\n color: var(--text-primary, #304b50);\n font-size: 16px;\n white-space: nowrap;\n transition: opacity 0.2s;\n }\n\n .headbar--light .headbar__back-link {\n color: #FFFFFF;\n }\n\n .headbar__back-link:hover {\n opacity: 0.75;\n }\n\n .headbar__back-arrow {\n display: flex;\n align-items: center;\n position: relative;\n top: -1px;\n }\n\n @media (max-width: 768px) {\n .headbar__back-text { display: none; }\n }\n\n /* \u2500\u2500 Color Mode Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__color-mode {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n background: none;\n cursor: pointer;\n border-radius: 50%;\n color: var(--text-primary, #304b50);\n transition: opacity 0.2s, color 0.3s;\n }\n\n .headbar--light .headbar__color-mode {\n color: #FFFFFF;\n }\n\n .headbar__color-mode:hover {\n opacity: 0.7;\n }\n\n .headbar__color-mode-sun,\n .headbar__color-mode-moon {\n display: flex;\n align-items: center;\n }\n\n .headbar__color-mode-moon {\n display: none;\n }\n\n :host([color-mode=\"dark\"]) .headbar__color-mode-sun {\n display: none;\n }\n\n :host([color-mode=\"dark\"]) .headbar__color-mode-moon {\n display: flex;\n }\n\n /* \u2500\u2500 Hide attributes (CSS-only, no re-render needed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n :host([hide-color-mode]) .headbar__color-mode { display: none !important; }\n :host([hide-lang]) .headbar__lang { display: none !important; }\n :host([hide-login]) .headbar__login,\n :host([hide-login]) .user-avatar-btn { display: none !important; }\n\n /* \u2500\u2500 Overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: var(--overlay-z-index, 1050);\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),\n visibility 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .overlay.open {\n opacity: 1;\n visibility: visible;\n }\n\n /* \u2500\u2500 Drawer (shared) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .drawer {\n position: fixed;\n top: 0;\n bottom: 0;\n width: 320px;\n max-width: 85vw;\n background: #FFFFFF;\n z-index: var(--drawer-z-index, 1100);\n overflow-y: auto;\n transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n color: var(--text-primary, #304b50);\n }\n\n .drawer--left {\n left: 0;\n transform: translateX(-100%);\n box-shadow: 4px 0 12px rgba(0, 0, 0, 0.1);\n }\n\n .drawer--right {\n right: 0;\n transform: translateX(100%);\n box-shadow: -4px 0 12px rgba(0, 0, 0, 0.1);\n }\n\n .drawer.open {\n transform: translateX(0);\n }\n\n .drawer__user {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 20px;\n border-bottom: 1px solid var(--border-color, #E5E7EB);\n }\n\n .drawer__user-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .drawer__user-name {\n font-weight: 600;\n font-size: 16px;\n }\n\n .drawer__nav {\n padding: 8px 0;\n }\n\n .drawer__item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 20px;\n font-size: 16px;\n font-weight: 500;\n transition: background-color 0.15s;\n cursor: pointer;\n width: 100%;\n text-align: left;\n color: var(--text-primary, #304b50);\n text-decoration: none;\n border: none;\n background: none;\n font-family: inherit;\n }\n\n .drawer__item:hover {\n background: var(--hover-bg, #F3F4F6);\n }\n\n .drawer__item--highlighted {\n color: #FFFFFF;\n background: var(--btn-primary-bg, #06E481);\n margin: 8px 16px;\n border-radius: 8px;\n justify-content: center;\n font-weight: 600;\n width: auto;\n }\n\n .drawer__item--highlighted:hover {\n background: var(--btn-primary-hover, #00B86F);\n }\n\n .drawer__item--small {\n font-size: 14px;\n color: var(--text-secondary, #6B7280);\n padding: 8px 20px;\n font-weight: 400;\n }\n\n .drawer__item--disabled {\n color: var(--text-disabled, #D1D5DB);\n pointer-events: none;\n }\n\n .drawer__item--outline {\n margin: 8px 16px;\n border: 1.5px solid var(--border-color, #E5E7EB);\n border-radius: 8px;\n justify-content: center;\n width: auto;\n background: none;\n }\n\n .drawer__item--outline:hover {\n border-color: var(--text-secondary, #6B7280);\n background: var(--hover-bg, #F3F4F6);\n }\n\n .drawer__item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n\n .drawer__item-badge {\n margin-left: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--btn-primary-bg, #06E481);\n color: #FFFFFF;\n font-size: 12px;\n font-weight: 700;\n }\n\n .drawer__item-expand {\n margin-left: auto;\n display: flex;\n align-items: center;\n transform: rotate(0deg);\n transition: transform 0.3s ease;\n }\n\n .drawer__item-expand.expanded {\n transform: rotate(180deg);\n }\n\n .drawer__item-nav-arrow {\n margin-left: auto;\n display: flex;\n align-items: center;\n transform: rotate(-90deg);\n }\n\n .drawer__divider {\n height: 1px;\n background: var(--border-color, #E5E7EB);\n margin: 8px 20px;\n }\n\n .drawer__children {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n .drawer__children.expanded {\n max-height: 500px;\n }\n\n .drawer__child {\n display: block;\n padding: 10px 20px 10px 52px;\n font-size: 14px;\n color: var(--text-secondary, #6B7280);\n transition: background-color 0.15s, color 0.15s;\n text-decoration: none;\n }\n\n .drawer__child:hover {\n background: var(--hover-bg, #F3F4F6);\n color: var(--text-primary, #304b50);\n }\n\n /* \u2500\u2500 Focus \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .headbar__icon--clickable:focus-visible,\n .headerbar__btn:focus-visible,\n .headbar__login:focus-visible,\n .headbar__menu-link:focus-visible,\n .drawer__item:focus-visible,\n .drawer__child:focus-visible {\n outline: 2px solid var(--btn-primary-bg, #06E481);\n outline-offset: 2px;\n }\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n";
|
|
2
2
|
//# sourceMappingURL=header.css.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../src/components/header/header.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"header.css.d.ts","sourceRoot":"","sources":["../../../src/components/header/header.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,kxtBA+yBrB,CAAC"}
|