@koehler8/cms 1.0.0-beta.5

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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/cms-generate-public-assets.js +27 -0
  4. package/bin/cms-validate-extensions.js +18 -0
  5. package/bin/cms-validate-themes.js +7 -0
  6. package/extensions/manifest.schema.json +125 -0
  7. package/package.json +84 -0
  8. package/public/img/preloaders/preloader-black.svg +1 -0
  9. package/public/img/preloaders/preloader-white.svg +1 -0
  10. package/public/robots.txt +5 -0
  11. package/scripts/check-overflow.mjs +33 -0
  12. package/scripts/generate-public-assets.js +401 -0
  13. package/scripts/patch-lru-cache-tla.js +164 -0
  14. package/scripts/validate-extensions.mjs +392 -0
  15. package/scripts/validate-themes.mjs +64 -0
  16. package/src/App.vue +3 -0
  17. package/src/components/About.vue +481 -0
  18. package/src/components/AboutValue.vue +361 -0
  19. package/src/components/BackToTop.vue +42 -0
  20. package/src/components/ComingSoon.vue +411 -0
  21. package/src/components/ComingSoonModal.vue +230 -0
  22. package/src/components/Contact.vue +518 -0
  23. package/src/components/Footer.vue +65 -0
  24. package/src/components/FooterMinimal.vue +153 -0
  25. package/src/components/Header.vue +583 -0
  26. package/src/components/Hero.vue +327 -0
  27. package/src/components/Home.vue +144 -0
  28. package/src/components/Intro.vue +130 -0
  29. package/src/components/IntroGate.vue +444 -0
  30. package/src/components/Plan.vue +116 -0
  31. package/src/components/Portfolio.vue +459 -0
  32. package/src/components/Preloader.vue +20 -0
  33. package/src/components/Principles.vue +67 -0
  34. package/src/components/Spacer15.vue +9 -0
  35. package/src/components/Spacer30.vue +9 -0
  36. package/src/components/Spacer40.vue +9 -0
  37. package/src/components/Spacer60.vue +9 -0
  38. package/src/components/StickyCTA.vue +263 -0
  39. package/src/components/Team.vue +432 -0
  40. package/src/components/icons/IconLinkedIn.vue +22 -0
  41. package/src/components/icons/IconX.vue +22 -0
  42. package/src/components/ui/SbCard.vue +52 -0
  43. package/src/components/ui/SkeletonPulse.vue +117 -0
  44. package/src/components/ui/UnitChip.vue +69 -0
  45. package/src/composables/useComingSoonConfig.js +120 -0
  46. package/src/composables/useComingSoonInterstitial.js +27 -0
  47. package/src/composables/useComponentResolver.js +196 -0
  48. package/src/composables/useEngagementTracking.js +187 -0
  49. package/src/composables/useIntroGate.js +46 -0
  50. package/src/composables/useLazyImage.js +77 -0
  51. package/src/composables/usePageConfig.js +184 -0
  52. package/src/composables/usePageMeta.js +76 -0
  53. package/src/composables/usePromoBackgroundStyles.js +67 -0
  54. package/src/constants/locales.js +20 -0
  55. package/src/extensions/extensionLoader.js +512 -0
  56. package/src/main.js +175 -0
  57. package/src/router/index.js +112 -0
  58. package/src/styles/base.css +896 -0
  59. package/src/styles/layout.css +342 -0
  60. package/src/styles/theme-base.css +84 -0
  61. package/src/themes/themeLoader.js +124 -0
  62. package/src/themes/themeManager.js +257 -0
  63. package/src/themes/themeValidator.js +380 -0
  64. package/src/utils/analytics.js +100 -0
  65. package/src/utils/appInfo.js +9 -0
  66. package/src/utils/assetResolver.js +162 -0
  67. package/src/utils/componentRegistry.js +46 -0
  68. package/src/utils/contentRequirements.js +67 -0
  69. package/src/utils/cookieConsent.js +281 -0
  70. package/src/utils/ctaCopy.js +58 -0
  71. package/src/utils/formatNumber.js +115 -0
  72. package/src/utils/imageSources.js +179 -0
  73. package/src/utils/inflateFlatConfig.js +30 -0
  74. package/src/utils/loadConfig.js +271 -0
  75. package/src/utils/semver.js +49 -0
  76. package/src/utils/siteStyles.js +40 -0
  77. package/src/utils/themeColors.js +65 -0
  78. package/src/utils/trackingContext.js +142 -0
  79. package/src/utils/unwrapDefault.js +14 -0
  80. package/src/utils/useScrollReveal.js +48 -0
  81. package/templates/index.html +36 -0
  82. package/themes/base/README.md +23 -0
  83. package/themes/base/theme.config.js +214 -0
  84. package/vite-plugin.js +637 -0
@@ -0,0 +1,583 @@
1
+ <template>
2
+ <header
3
+ id="js-header"
4
+ class="site-header ui-header"
5
+ :class="{ 'site-header--compact': isHeaderCompact }"
6
+ >
7
+ <div
8
+ class="site-header__section ui-header__section"
9
+ :class="{ 'ui-header__section--compact': isHeaderCompact }"
10
+ >
11
+ <nav class="site-header__nav">
12
+ <div class="container">
13
+ <div class="site-header__inner">
14
+ <a href="/" ref="logoLinkRef" class="site-header__logo logo-flash-trigger">
15
+ <img :src="logoSrc" :alt="siteName" class="logo-flash-image" />
16
+ </a>
17
+ <div v-if="hasHeaderActions || showLocaleLinks" class="site-header__actions">
18
+ <component
19
+ v-if="headerActionsComponent"
20
+ :is="headerActionsComponent"
21
+ />
22
+ <div
23
+ v-if="showLocaleLinks && availableLocales.length > 1"
24
+ ref="localeWrapperRef"
25
+ class="locale-wrapper"
26
+ >
27
+ <button
28
+ id="languages-dropdown-invoker-2"
29
+ ref="langButtonRef"
30
+ class="locale-toggle"
31
+ type="button"
32
+ aria-controls="languages-dropdown-2"
33
+ aria-haspopup="true"
34
+ :aria-expanded="isLangOpen ? 'true' : 'false'"
35
+ @click="toggleLocaleMenu"
36
+ >
37
+ <span class="locale-flag" aria-hidden="true">
38
+ {{ localeEmojis[(currentLocale || 'en').toLowerCase()] || '🌐' }}
39
+ </span>
40
+ <span class="locale-label">
41
+ {{ (currentLocale || 'en').toUpperCase() }}
42
+ </span>
43
+ <svg class="locale-toggle__icon" viewBox="0 0 16 16" aria-hidden="true" width="16" height="16">
44
+ <path
45
+ d="M4 6l4 4 4-4"
46
+ fill="none"
47
+ stroke="currentColor"
48
+ stroke-width="1.6"
49
+ stroke-linecap="round"
50
+ stroke-linejoin="round"
51
+ />
52
+ </svg>
53
+ </button>
54
+ <ul
55
+ id="languages-dropdown-2"
56
+ ref="langMenuRef"
57
+ class="locale-menu ui-locale-menu"
58
+ :class="{ 'locale-menu--hidden': !isLangOpen }"
59
+ aria-labelledby="languages-dropdown-invoker-2"
60
+ :style="localeMenuStyle"
61
+ >
62
+ <li v-for="locale in availableLocales" :key="locale">
63
+ <a
64
+ class="locale-option"
65
+ :href="`/${locale}`"
66
+ @click="handleLocaleLinkClick(locale)"
67
+ >
68
+ <span class="locale-flag" aria-hidden="true">{{ localeEmojis[locale] || '🌐' }}</span>
69
+ <span class="locale-label">{{ localeLabels[locale] || locale.toUpperCase() }}</span>
70
+ </a>
71
+ </li>
72
+ </ul>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </nav>
78
+ </div>
79
+ </header>
80
+ </template>
81
+
82
+ <script setup>
83
+ import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
84
+ import { useRoute } from 'vue-router';
85
+ import { resolveAsset } from '../utils/assetResolver.js';
86
+ import { trackEvent } from '../utils/analytics.js';
87
+ import { getExtensionComponent } from '../extensions/extensionLoader.js';
88
+ import SUPPORTED_LOCALES from '../constants/locales.js';
89
+
90
+ const logoSrc = computed(() => resolveAsset('img/logo.png'));
91
+
92
+ const isLangOpen = ref(false);
93
+ const isHeaderCompact = ref(false);
94
+ const currentLocale = ref('');
95
+ const availableLocales = computed(() => SUPPORTED_LOCALES.map((code) => code.trim()).filter(Boolean));
96
+ const localeLabels = {
97
+ en: 'English',
98
+ fr: 'Français',
99
+ es: 'Español',
100
+ de: 'Deutsch',
101
+ ja: '日本語',
102
+ ko: '한국어',
103
+ pt: 'Português',
104
+ ru: 'Русский',
105
+ tr: 'Türkçe',
106
+ vi: 'Tiếng Việt',
107
+ id: 'Bahasa Indonesia',
108
+ zh: '简体中文',
109
+ th: 'ไทย',
110
+ hi: 'हिन्दी',
111
+ fil: 'Filipino',
112
+ };
113
+
114
+ const localeEmojis = {
115
+ en: '🇺🇸',
116
+ fr: '🇫🇷',
117
+ es: '🇪🇸',
118
+ de: '🇩🇪',
119
+ ja: '🇯🇵',
120
+ ko: '🇰🇷',
121
+ pt: '🇵🇹',
122
+ ru: '🇷🇺',
123
+ tr: '🇹🇷',
124
+ vi: '🇻🇳',
125
+ id: '🇮🇩',
126
+ zh: '🇨🇳',
127
+ th: '🇹🇭',
128
+ hi: '🇮🇳',
129
+ fil: '🇵🇭',
130
+ };
131
+ const langButtonRef = ref(null);
132
+ const langMenuRef = ref(null);
133
+ const localeWrapperRef = ref(null);
134
+ const localeMenuStyle = ref({ animationDuration: '500ms' });
135
+ const logoLinkRef = ref(null);
136
+ const route = useRoute();
137
+ const showLocaleLinks = ref(true);
138
+
139
+ // Extension slot: render HeaderActions component if an extension registered one
140
+ const headerActionsComponent = computed(() => getExtensionComponent('HeaderActions'));
141
+ const hasHeaderActions = computed(() => Boolean(headerActionsComponent.value));
142
+
143
+ let removeLogoListeners = () => {};
144
+ let logoFlashTimeoutId = null;
145
+
146
+ const handleHeaderScroll = () => {
147
+ if (typeof window === 'undefined') return;
148
+ isHeaderCompact.value = window.scrollY > 30;
149
+ };
150
+
151
+ const updateLocaleMenuPosition = () => {
152
+ if (typeof window === 'undefined') return;
153
+ const menuEl = langMenuRef.value;
154
+ const wrapperEl = localeWrapperRef.value;
155
+ const headerEl = document.getElementById('js-header');
156
+ if (!menuEl || !wrapperEl || !headerEl) return;
157
+
158
+ const wrapperRect = wrapperEl.getBoundingClientRect();
159
+ const headerRect = headerEl.getBoundingClientRect();
160
+ const topOffset = Math.max(headerRect.bottom - wrapperRect.top, 0);
161
+
162
+ localeMenuStyle.value = {
163
+ animationDuration: '500ms',
164
+ top: `${topOffset}px`,
165
+ };
166
+ };
167
+
168
+ const handleViewportChange = () => {
169
+ if (isLangOpen.value) {
170
+ updateLocaleMenuPosition();
171
+ }
172
+ };
173
+
174
+ const onDocumentClick = (e) => {
175
+ const btn = langButtonRef.value;
176
+ const menu = langMenuRef.value;
177
+ if (btn && btn.contains(e.target)) return; // toggle handled by button
178
+ if (menu && !menu.contains(e.target)) {
179
+ if (isLangOpen.value) {
180
+ isLangOpen.value = false;
181
+ trackEvent('header_locale_menu_toggle', {
182
+ state: 'close',
183
+ trigger: 'outside_click',
184
+ active_locale: currentLocale.value || 'en',
185
+ });
186
+ }
187
+ }
188
+ };
189
+
190
+ const prefersReducedMotion = () =>
191
+ typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
192
+
193
+ const triggerLogoFlash = () => {
194
+ if (prefersReducedMotion()) return;
195
+ const el = logoLinkRef.value;
196
+ if (!el || el.classList.contains('logo-flash-active')) return;
197
+
198
+ el.classList.add('logo-flash-active');
199
+ if (logoFlashTimeoutId) {
200
+ window.clearTimeout(logoFlashTimeoutId);
201
+ }
202
+ logoFlashTimeoutId = window.setTimeout(() => {
203
+ el.classList.remove('logo-flash-active');
204
+ logoFlashTimeoutId = null;
205
+ }, 460);
206
+ };
207
+
208
+ const setupLogoFlash = () => {
209
+ if (prefersReducedMotion()) return;
210
+ const el = logoLinkRef.value;
211
+ if (!el) return;
212
+
213
+ const handlePointerEnter = () => triggerLogoFlash();
214
+ const handleFocus = () => triggerLogoFlash();
215
+
216
+ el.addEventListener('pointerenter', handlePointerEnter);
217
+ el.addEventListener('focus', handleFocus);
218
+
219
+ removeLogoListeners = () => {
220
+ el.removeEventListener('pointerenter', handlePointerEnter);
221
+ el.removeEventListener('focus', handleFocus);
222
+ };
223
+
224
+ requestAnimationFrame(() => {
225
+ window.setTimeout(() => triggerLogoFlash(), 260);
226
+ });
227
+ };
228
+
229
+ const injectedSiteData = inject('siteData', ref({}));
230
+ const pageContent = inject('pageContent', ref({}));
231
+
232
+ const siteName = computed(() => injectedSiteData.value?.site?.title || '');
233
+
234
+ const resolveHeaderSettings = () => {
235
+ const pageHeader = pageContent.value?.header;
236
+ if (pageHeader && typeof pageHeader === 'object') {
237
+ return pageHeader;
238
+ }
239
+ const globalHeader = injectedSiteData.value?.header;
240
+ if (globalHeader && typeof globalHeader === 'object') {
241
+ return globalHeader;
242
+ }
243
+ return {};
244
+ };
245
+
246
+ const applyHeaderSettings = () => {
247
+ const headerSettings = resolveHeaderSettings();
248
+ showLocaleLinks.value = typeof headerSettings.showLocaleLinks === 'boolean'
249
+ ? headerSettings.showLocaleLinks
250
+ : true;
251
+ };
252
+
253
+ watch(
254
+ () => [
255
+ pageContent.value?.header,
256
+ injectedSiteData.value?.header,
257
+ ],
258
+ () => {
259
+ applyHeaderSettings();
260
+ handleViewportChange();
261
+ },
262
+ { immediate: true }
263
+ );
264
+
265
+ watch(
266
+ () => isLangOpen.value,
267
+ (open) => {
268
+ if (open) {
269
+ nextTick(() => {
270
+ updateLocaleMenuPosition();
271
+ });
272
+ }
273
+ }
274
+ );
275
+
276
+ const toggleLocaleMenu = () => {
277
+ if (!isLangOpen.value) {
278
+ updateLocaleMenuPosition();
279
+ }
280
+ const nextState = !isLangOpen.value;
281
+ isLangOpen.value = nextState;
282
+ trackEvent('header_locale_menu_toggle', {
283
+ state: nextState ? 'open' : 'close',
284
+ trigger: 'button',
285
+ active_locale: currentLocale.value || 'en',
286
+ });
287
+ if (nextState) {
288
+ nextTick(() => {
289
+ updateLocaleMenuPosition();
290
+ });
291
+ }
292
+ };
293
+
294
+ const handleLocaleLinkClick = (locale) => {
295
+ trackEvent('header_locale_selected', {
296
+ selected_locale: locale,
297
+ previous_locale: currentLocale.value || 'en',
298
+ source: 'dropdown',
299
+ });
300
+ if (isLangOpen.value) {
301
+ trackEvent('header_locale_menu_toggle', {
302
+ state: 'close',
303
+ trigger: 'selection',
304
+ active_locale: currentLocale.value || 'en',
305
+ });
306
+ }
307
+ isLangOpen.value = false;
308
+ };
309
+
310
+ onMounted(() => {
311
+ handleHeaderScroll();
312
+ const loc = (route.params.locale || (typeof localStorage !== 'undefined' && localStorage.getItem('locale')) || 'en').toString();
313
+ currentLocale.value = loc;
314
+ document.addEventListener('click', onDocumentClick);
315
+ setupLogoFlash();
316
+ if (typeof window !== 'undefined') {
317
+ window.addEventListener('resize', handleViewportChange, { passive: true });
318
+ window.addEventListener('scroll', handleViewportChange, { passive: true });
319
+ window.addEventListener('scroll', handleHeaderScroll, { passive: true });
320
+ }
321
+ updateLocaleMenuPosition();
322
+ });
323
+
324
+ onBeforeUnmount(() => {
325
+ document.removeEventListener('click', onDocumentClick);
326
+ if (typeof window !== 'undefined') {
327
+ window.removeEventListener('resize', handleViewportChange);
328
+ window.removeEventListener('scroll', handleViewportChange);
329
+ window.removeEventListener('scroll', handleHeaderScroll);
330
+ }
331
+ removeLogoListeners();
332
+ if (logoFlashTimeoutId) {
333
+ window.clearTimeout(logoFlashTimeoutId);
334
+ logoFlashTimeoutId = null;
335
+ }
336
+ });
337
+
338
+ watch(
339
+ () => route.params.locale,
340
+ (nextLocale) => {
341
+ const normalized = typeof nextLocale === 'string' && nextLocale.trim() ? nextLocale.trim() : 'en';
342
+ currentLocale.value = normalized;
343
+ }
344
+ );
345
+ </script>
346
+
347
+ <style scoped>
348
+ .site-header {
349
+ position: sticky;
350
+ top: 0;
351
+ z-index: 50;
352
+ background: color-mix(in srgb, var(--brand-header-bg, #020409) 92%, transparent);
353
+ color: var(--brand-header-text, var(--brand-fg-100, #f0eaf3));
354
+ box-shadow: var(--site-header-shadow, 0 18px 45px rgba(0, 0, 0, 0.35));
355
+ transition: transform 0.3s ease, background 0.3s ease, box-shadow 0.3s ease;
356
+ }
357
+
358
+ .site-header--compact {
359
+ background: color-mix(in srgb, var(--brand-header-bg, #020409) 98%, transparent);
360
+ box-shadow: var(--site-header-shadow-compact, 0 10px 30px rgba(1, 1, 12, 0.6));
361
+ }
362
+
363
+ .site-header__section {
364
+ padding-block: clamp(10px, 2vw, 16px);
365
+ }
366
+
367
+ .site-header--compact .site-header__section {
368
+ padding-block: clamp(6px, 1.5vw, 12px);
369
+ transition: padding 0.2s ease;
370
+ }
371
+
372
+ .site-header__logo img {
373
+ transition: transform 0.2s ease;
374
+ }
375
+
376
+ .site-header--compact .site-header__logo img {
377
+ transform: scale(0.92);
378
+ }
379
+
380
+ .site-header__inner {
381
+ display: flex;
382
+ align-items: center;
383
+ justify-content: space-between;
384
+ gap: clamp(8px, 2vw, 24px);
385
+ width: 100%;
386
+ }
387
+
388
+ .site-header__logo {
389
+ display: inline-flex;
390
+ align-items: center;
391
+ flex-shrink: 0;
392
+ }
393
+
394
+ .site-header__logo img {
395
+ max-height: 44px;
396
+ width: auto;
397
+ }
398
+
399
+ .site-header__actions {
400
+ display: inline-flex;
401
+ align-items: center;
402
+ justify-content: flex-end;
403
+ flex: 1 1 auto;
404
+ min-width: 0;
405
+ gap: clamp(8px, 2vw, 20px);
406
+ }
407
+
408
+ .locale-wrapper {
409
+ position: relative;
410
+ display: inline-flex;
411
+ align-items: center;
412
+ justify-content: flex-end;
413
+ }
414
+
415
+ .locale-wrapper .locale-toggle {
416
+ line-height: 1;
417
+ }
418
+
419
+ .ui-header .locale-toggle,
420
+ .ui-header .locale-toggle:visited {
421
+ display: inline-flex;
422
+ align-items: center;
423
+ color: var(--ui-header-chip-color, var(--brand-header-text, #ffffff));
424
+ gap: 0.35rem;
425
+ text-decoration: none;
426
+ padding: 0.4rem 0.85rem;
427
+ border-radius: 999px;
428
+ border: 1px solid var(--ui-header-chip-border, rgba(255, 255, 255, 0.25));
429
+ background: var(--ui-header-chip-bg, rgba(0, 0, 0, 0.65));
430
+ font-weight: 600;
431
+ letter-spacing: 0.12em;
432
+ opacity: 0.85;
433
+ text-transform: uppercase;
434
+ transition: opacity 0.2s ease;
435
+ }
436
+
437
+ .ui-header .locale-toggle:hover,
438
+ .ui-header .locale-toggle:focus-visible {
439
+ opacity: 1;
440
+ text-decoration: none;
441
+ color: var(--ui-header-chip-color, var(--brand-header-text, #ffffff));
442
+ border-color: color-mix(in srgb, var(--ui-header-chip-border, rgba(255, 255, 255, 0.25)) 140%, transparent);
443
+ }
444
+
445
+ .locale-toggle__icon {
446
+ margin-left: 0.25rem;
447
+ }
448
+
449
+ .ui-locale-menu {
450
+ position: absolute;
451
+ right: 0;
452
+ top: 0;
453
+ background: var(--ui-locale-menu-bg, var(--brand-surface-card-bg, #1a1624));
454
+ color: var(--ui-locale-menu-color, #ffffff);
455
+ border: 1px solid color-mix(in srgb, var(--brand-accent-electric, #4f6cf0) 25%, transparent);
456
+ border-radius: 16px;
457
+ padding: 10px 0 15px;
458
+ box-shadow: var(--brand-surface-card-shadow, 0 12px 32px rgba(0, 0, 0, 0.45));
459
+ z-index: 2;
460
+ background-clip: padding-box;
461
+ }
462
+
463
+ .locale-menu {
464
+ max-height: min(70vh, 24rem);
465
+ overflow-y: auto;
466
+ overscroll-behavior: contain;
467
+ padding-right: 0.25rem;
468
+ scrollbar-gutter: stable both-edges;
469
+ top: 0;
470
+ right: 0;
471
+ left: auto;
472
+ z-index: 400;
473
+ }
474
+
475
+ .locale-menu::-webkit-scrollbar {
476
+ width: 6px;
477
+ }
478
+
479
+ .locale-menu::-webkit-scrollbar-thumb {
480
+ background: rgba(255, 255, 255, 0.24);
481
+ border-radius: 999px;
482
+ }
483
+
484
+ .locale-menu::-webkit-scrollbar-track {
485
+ background: transparent;
486
+ }
487
+
488
+ .locale-menu--hidden {
489
+ opacity: 0;
490
+ pointer-events: none;
491
+ transform: translateY(-6px);
492
+ }
493
+
494
+ .locale-wrapper .ui-locale-menu {
495
+ position: absolute;
496
+ right: 0;
497
+ top: calc(100% + 12px);
498
+ list-style: none;
499
+ margin: 0;
500
+ min-width: 200px;
501
+ max-width: 280px;
502
+ transition: opacity 0.2s ease, transform 0.2s ease;
503
+ }
504
+
505
+ .locale-option,
506
+ .locale-option:visited {
507
+ display: flex;
508
+ align-items: center;
509
+ gap: 0.65rem;
510
+ font-size: 0.78rem;
511
+ letter-spacing: 0.08em;
512
+ text-transform: uppercase;
513
+ white-space: nowrap;
514
+ padding: 5px 20px;
515
+ color: var(--ui-locale-menu-color, #ffffff);
516
+ text-decoration: none;
517
+ opacity: 0.85;
518
+ transition: opacity 0.2s ease, color 0.2s ease, background 0.2s ease;
519
+ }
520
+
521
+ .locale-option .locale-label {
522
+ overflow: hidden;
523
+ text-overflow: ellipsis;
524
+ display: inline-flex;
525
+ align-items: center;
526
+ line-height: 1;
527
+ }
528
+
529
+ .locale-option:hover,
530
+ .locale-option:focus-visible {
531
+ opacity: 1;
532
+ color: var(--ui-locale-menu-hover-color, #ffffff);
533
+ background: var(--ui-locale-menu-hover-bg);
534
+ text-decoration: none;
535
+ }
536
+
537
+ .locale-flag {
538
+ font-size: 1rem;
539
+ line-height: 1;
540
+ display: inline-flex;
541
+ align-items: center;
542
+ vertical-align: middle;
543
+ }
544
+
545
+ .locale-toggle .locale-label,
546
+ .locale-toggle .locale-flag {
547
+ display: inline-flex;
548
+ align-items: center;
549
+ line-height: 1;
550
+ gap: 0.5rem;
551
+ }
552
+
553
+ @media (max-width: 767px) {
554
+ .site-header__inner {
555
+ gap: clamp(6px, 3vw, 16px);
556
+ }
557
+ }
558
+
559
+ @media (max-width: 520px) {
560
+ .site-header__inner {
561
+ gap: clamp(4px, 2vw, 12px);
562
+ }
563
+
564
+ .site-header__actions {
565
+ gap: clamp(4px, 2vw, 10px);
566
+ }
567
+
568
+ .locale-toggle {
569
+ padding: 0.32rem 0.7rem;
570
+ font-size: 0.7rem;
571
+ letter-spacing: 0.08em;
572
+ }
573
+ }
574
+
575
+ @media (max-width: 576px) {
576
+ .locale-menu {
577
+ min-width: clamp(12rem, 70vw, 16rem);
578
+ max-width: clamp(12rem, 80vw, 18rem);
579
+ right: -4px;
580
+ left: auto;
581
+ }
582
+ }
583
+ </style>