File: /var/www/quadcode.com/src/components/header/Lang.svelte
<script lang="ts">
import { page } from '$app/stores';
import { locale, defaultLocale } from '$lib/translations';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
export let className = '';
export let useShort = false;
export let isMobile = false;
$: ({ route } = $page.data);
const { status } = $page;
let lang: HTMLElement;
let buttonVariant: 'short' | 'full' = 'short';
let isMobilePopup = false;
let isMobileView = false;
let closeTimeout: number | null = null;
const langUrls = Object.keys($page.data.languages).map((lc) => {
const prefix = lc === defaultLocale ? '' : `/${lc}`;
const route = $page.data.route;
const url = `${prefix}${route !== '/' && status !== 404 ? route : ''}`;
return { url, lc };
});
function checkMobileView() {
if (browser) {
isMobileView = window.innerWidth <= 720;
}
}
onMount(() => {
buttonVariant = 'full';
const isMobile = className.includes('menu-mobile__lang');
checkMobileView();
if (browser) {
window.addEventListener('resize', checkMobileView);
}
if (lang) {
const btnLang = lang.querySelector('.lang__btn');
const listLang = lang.querySelector('.lang__list');
if (btnLang) {
btnLang.addEventListener('click', (e) => {
if (isMobileView) {
e.preventDefault();
e.stopPropagation();
openMobilePopup();
return;
}
if (isMobile) {
toggle();
}
});
}
if (!isMobile && !isMobileView) {
if (lang) {
lang.addEventListener('mouseenter', () => {
if (closeTimeout) {
clearTimeout(closeTimeout);
closeTimeout = null;
}
open();
});
lang.addEventListener('mouseleave', () => {
closeTimeout = window.setTimeout(() => {
close();
}, 200);
});
}
}
}
return () => {
if (browser) {
window.removeEventListener('resize', checkMobileView);
}
if (closeTimeout) {
clearTimeout(closeTimeout);
}
};
});
const open = () => {
lang.classList.add('lang_active');
setTimeout(() => {
lang.classList.add('lang_animation');
}, 600);
};
const close = () => {
lang.classList.remove('lang_active');
lang.classList.remove('lang_animation');
};
const toggle = () => {
if (lang.classList.contains('lang_active')) {
close();
} else {
open();
}
};
const openMobilePopup = () => {
isMobilePopup = true;
if (browser) {
document.body.style.overflow = 'hidden';
}
};
const closeMobilePopup = () => {
isMobilePopup = false;
if (browser) {
document.body.style.overflow = '';
}
};
const handleOverlayClick = (e: MouseEvent) => {
if (e.target === e.currentTarget) {
closeMobilePopup();
}
};
</script>
{#if route !== '/marketing-guide' && route !== '/cookie-policy' && route !== '/privacy-policy' && route !== '/terms-and-conditions' && route !== '/vulnerability-disclosure-policy'}
{#if isMobile}<div class="menu-mobile__title">Language</div>{/if}
<div class="lang {className}" bind:this={lang}>
<div class="lang__container">
<div class="lang__btn" on:keydown={() => false} role="button" tabindex="0">
<div class="lang__text">
{$page.data.languages[$locale][buttonVariant]}
</div>
<div class="lang__icon">
<div class="lang__arrow lang__arrow-down" />
<div class="lang__arrow lang__arrow-up" />
</div>
</div>
<div class="lang__list">
<div class="lang__inner">
{#each langUrls as item}
<a href={item.url} class={item.lc === $locale ? 'active' : ''}>
{$page.data.languages[item.lc][buttonVariant]}
</a>
{/each}
</div>
</div>
</div>
</div>
<!-- Mobile Popup (от 720px) -->
{#if isMobilePopup}
<div
class="lang-popup-overlay"
role="dialog"
aria-modal="true"
aria-labelledby="lang-popup-title"
tabindex="-1"
on:click={handleOverlayClick}
on:keydown={(e) => e.key === 'Escape' && closeMobilePopup()}
>
<div class="lang-popup">
<div class="lang-popup__header">
<h2 class="lang-popup__title" id="lang-popup-title">Choose language</h2>
<button class="lang-popup__close" on:click={closeMobilePopup} aria-label="Close">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18 6L6 18M6 6L18 18"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
</div>
<div class="lang-popup__list">
{#each langUrls as item}
<a
href={item.url}
class="lang-popup__item {item.lc === $locale ? 'lang-popup__item--active' : ''}"
on:click={closeMobilePopup}
>
<span class="lang-popup__item-text">{$page.data.languages[item.lc].full}</span>
{#if item.lc === $locale}
<svg
class="lang-popup__item-check"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.667 5L7.5 14.167 3.333 10"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
{/if}
</a>
{/each}
</div>
</div>
</div>
{/if}
{/if}
<style lang="scss">
@import 'src/scss/variables';
@import 'src/scss/media';
@import 'src/scss/mixins';
.lang {
position: relative;
z-index: 100;
&__btn {
display: flex;
cursor: pointer;
border-radius: 48px;
transition: background 0.2s ease-in-out;
}
&__text {
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #1b1c1d;
transition: 0.4s ease-in-out;
}
&__icon {
display: flex;
align-items: center;
position: relative;
overflow: hidden;
color: #1b1c1d;
}
&__arrow {
margin: 0 9px;
width: 10px;
height: 7px;
transition: 0.2s ease;
background-color: currentColor;
mask-image: url('../../assets/icons/arrow.svg');
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-image: url('../../assets/icons/arrow.svg');
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
&-up {
transform: translateY(-18px);
mask-image: url('../../assets/icons/arrow-up.svg');
-webkit-mask-image: url('../../assets/icons/arrow-up.svg');
position: absolute;
transition: 0.2s ease;
}
}
&__list {
left: 0;
top: calc(100% + 8px);
position: absolute;
width: 140px;
background: $techWhite;
border-radius: 20px;
border: 1px solid #d4d8db;
box-shadow: 0 24px 40px 0 rgba(22, 22, 24, 0.15);
transition: opacity 0.4s ease, visibility 0.4s ease, max-height 0.4s ease, padding 0.4s ease,
border-width 0.4s ease;
max-height: 0;
overflow: hidden;
padding: 0;
border-width: 0;
opacity: 0;
visibility: hidden;
pointer-events: none;
}
&__inner {
padding: 6px;
display: flex;
flex-direction: column;
gap: 4px;
a {
display: block;
padding: 10px 12px;
color: #1b1c1d;
font-size: 14px;
line-height: 20px;
font-weight: 400;
text-decoration: none;
border-radius: 12px;
transition: background 0.2s ease, color 0.2s ease;
cursor: pointer;
&:hover {
background: #f2f5f7;
}
&:active {
background: #eaecef;
}
&.active {
background: #f9fbfc;
color: #ff282b;
&:active {
background: #eaecef;
}
}
}
}
&__item {
margin-bottom: 8px;
cursor: pointer;
&:last-of-type {
margin-bottom: 0;
}
}
&:global(.lang_active) {
.lang {
&__list {
transition: 0.4s ease;
max-height: 400px;
width: 140px;
padding: 0;
border-width: 1px;
opacity: 1;
visibility: visible;
pointer-events: auto;
}
&__arrow {
transition: 0.2s ease;
&-down {
transform: translateY(18px);
}
&-up {
transform: translateY(0px);
}
}
}
}
&.ghostWhite:not(.defaultTabs) {
.lang {
&__text {
color: $fontWhite;
}
}
}
&.white:not(.defaultTabs) {
.lang {
&__text {
color: $fontWhite;
font-size: 14px;
font-family: Suisse Intl;
font-weight: 400;
}
&__btn {
padding: 0;
}
&__arrow {
margin: 0 9px;
width: 10px;
height: 7px;
transition: 0.2s ease;
&-down {
background-image: url('../../assets/icons/white-arrow.svg');
}
&-up {
background-image: url('../../assets/icons/arrow-red.svg');
}
}
&__list {
top: 40px;
}
}
}
&.menu-mobile__lang {
width: 100%;
.lang {
&__container {
width: 100%;
background: #202023;
border-radius: 12px;
padding: 16px 20px;
display: flex;
align-items: center;
justify-content: space-between;
transition: background 0.2s ease-in-out;
&:active {
background: #eaecef;
}
}
&__text {
color: #F9FBFC;
font-size: 16px;
line-height: 24px;
font-family: 'Suisse Intl', sans-serif;
font-weight: 400;
}
&__btn {
width: 100%;
padding: 0;
justify-content: space-between;
background: transparent;
&:active {
background: transparent;
}
}
&__icon {
position: static;
color: #F9FBFC;
}
&__arrow {
margin: 0;
width: 10px;
height: 7px;
transition: 0.2s ease;
&-down {
mask-image: url('../../assets/icons/arrow.svg');
-webkit-mask-image: url('../../assets/icons/arrow.svg');
mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
background-color: currentColor;
transform: rotate(-90deg);
}
&-up {
display: none;
}
}
&__list {
top: 60px;
left: 0;
width: 100%;
}
&__inner {
display: flex;
flex-direction: column;
gap: 12px;
a {
color: $fontPrimary;
font-size: 16px;
&.active {
color: $redPrimary;
}
}
}
}
&:global(.lang_active) {
.lang {
&__container {
background: #eaecef;
}
&__arrow-down {
transform: rotate(0deg);
}
}
}
}
// Mobile popup styles (от 720px)
@media (max-width: 720px) {
&:not(.menu-mobile__lang) {
.lang__list {
display: none;
}
}
}
}
// Mobile popup overlay and container
.lang-popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: flex-end;
animation: fadeIn 0.3s ease;
@media (min-width: 721px) {
display: none;
}
}
.lang-popup {
width: 100%;
background: #202023;
border-radius: 24px 24px 0 0;
padding: 30px 32px;
max-height: calc(100vh - 120px);
overflow-y: auto;
animation: slideUp 0.3s ease;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
@media (max-width: 480px) {
padding: 28px 20px;
}
@media (max-width: 393px) {
padding: 28px 20px;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
&__title {
color: $techWhite;
font-size: 16px;
line-height: 24px;
font-weight: 400;
margin: 0;
font-family: 'Suisse Intl', sans-serif;
}
&__close {
cursor: pointer;
background: #313135;
border-radius: 48px;
padding: 10px 14px;
display: flex;
align-items: center;
justify-content: center;
border: none;
color: $techWhite;
svg {
width: 20px;
height: 20px;
}
}
&__list {
display: flex;
flex-direction: column;
gap: 0;
}
&__item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px;
border-radius: 12px;
text-decoration: none;
color: $techWhite;
font-size: 16px;
line-height: 24px;
font-family: 'Suisse Intl', sans-serif;
font-weight: 400;
transition: background 0.2s ease;
background: transparent;
&:hover {
background: rgba(255, 255, 255, 0.05);
}
&--active {
background: rgba(255, 255, 255, 0.1);
}
&-text {
flex: 1;
}
&-check {
width: 20px;
height: 20px;
color: $techWhite;
flex-shrink: 0;
margin-left: 12px;
}
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.menu-mobile__title {
color: rgba(255, 255, 255, 0.5);
font-family: 'Suisse Intl', sans-serif;
font-weight: 500;
font-size: 12px;
line-height: 16px;
text-transform: uppercase;
margin-bottom: 16px;
letter-spacing: 1.3px;
}
</style>