HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
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>