From e888c81aef5a7460c6fdc93a167c4d17e781cb16 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Wed, 25 Feb 2026 09:23:54 +0100 Subject: [PATCH] Add product carousel module with template and database structure - Created `pp_carousel.tpl` for rendering product carousel with Swiper integration. - Added `plan.md` detailing module architecture, database schema, and implementation steps. - Initialized log files for development and production environments. --- .claude/settings.local.json | 10 + .serena/.gitignore | 1 + .serena/project.yml | 126 + .vscode/ftp-kr.json | 4 +- config/defines.inc.php | 5 +- index.php | 28 + modules/cookiesplus/cookiesplus.php | 2447 ++++++ modules/pp_carousel/config.xml | 12 + modules/pp_carousel/index.php | 16 + modules/pp_carousel/pp_carousel.php | 762 ++ modules/pp_carousel/sql/install.sql | 27 + modules/pp_carousel/sql/uninstall.sql | 2 + modules/pp_carousel/views/css/pp_carousel.css | 179 + modules/pp_carousel/views/js/pp_carousel.js | 23 + .../views/lib/swiper/swiper-bundle.min.css | 13 + .../views/lib/swiper/swiper-bundle.min.js | 7661 +++++++++++++++++ .../views/templates/hook/pp_carousel.tpl | 67 + plan.md | 196 + var/logs/dev.log | 0 var/logs/prod.log | 0 20 files changed, 11577 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml create mode 100644 index.php create mode 100644 modules/cookiesplus/cookiesplus.php create mode 100644 modules/pp_carousel/config.xml create mode 100644 modules/pp_carousel/index.php create mode 100644 modules/pp_carousel/pp_carousel.php create mode 100644 modules/pp_carousel/sql/install.sql create mode 100644 modules/pp_carousel/sql/uninstall.sql create mode 100644 modules/pp_carousel/views/css/pp_carousel.css create mode 100644 modules/pp_carousel/views/js/pp_carousel.js create mode 100644 modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css create mode 100644 modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js create mode 100644 modules/pp_carousel/views/templates/hook/pp_carousel.tpl create mode 100644 plan.md create mode 100644 var/logs/dev.log create mode 100644 var/logs/prod.log diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..2ee6c59 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "mcp__serena__activate_project", + "mcp__serena__list_dir", + "mcp__serena__read_file", + "mcp__serena__find_file" + ] + } +} diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..e83de81 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,126 @@ +# the name by which the project can be referenced within Serena +project_name: "newwalls.pl" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- php + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: diff --git a/.vscode/ftp-kr.json b/.vscode/ftp-kr.json index 9bf4188..3b847ea 100644 --- a/.vscode/ftp-kr.json +++ b/.vscode/ftp-kr.json @@ -12,6 +12,8 @@ "ignoreRemoteModification": true, "ignore": [ ".git", - "/.vscode" + "/.vscode", + "/.claude", + "/.serena" ] } \ No newline at end of file diff --git a/config/defines.inc.php b/config/defines.inc.php index 6ba618f..241ce16 100644 --- a/config/defines.inc.php +++ b/config/defines.inc.php @@ -26,7 +26,10 @@ /* Debug only */ if (!defined('_PS_MODE_DEV_')) { -define('_PS_MODE_DEV_', false); + if ( $_SERVER['REMOTE_ADDR'] == '91.189.216.43' ) + define('_PS_MODE_DEV_', false); + else + define('_PS_MODE_DEV_', false); } /* Compatibility warning */ define('_PS_DISPLAY_COMPATIBILITY_WARNING_', false); diff --git a/index.php b/index.php new file mode 100644 index 0000000..b08bc1b --- /dev/null +++ b/index.php @@ -0,0 +1,28 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +require dirname(__FILE__).'/config/config.inc.php'; +Dispatcher::getInstance()->dispatch(); diff --git a/modules/cookiesplus/cookiesplus.php b/modules/cookiesplus/cookiesplus.php new file mode 100644 index 0000000..e9e2d89 --- /dev/null +++ b/modules/cookiesplus/cookiesplus.php @@ -0,0 +1,2447 @@ +name = 'cookiesplus'; + $this->tab = 'front_office_features'; + $this->version = '1.6.0'; + $this->author = 'idnovate'; + $this->need_instance = 0; + $this->module_key = '22c3b977fe9c819543a216a2fd948f22'; + // $this->author_address = '0xd89bcCAeb29b2E6342a74Bc0e9C82718Ac702160'; + $this->bootstrap = true; + $this->addons_id_product = '21644'; + $this->ps_versions_compliancy = ['min' => '1.5', 'max' => _PS_VERSION_]; + + parent::__construct(); + + $this->displayName = $this->l('Cookies - GDPR Cookie law (block before consent)'); + $this->description = $this->l('Make your store GDPR compliant using this module. This module lets you block the cookies until the customer gives his consent accepting the notice.'); + $this->confirmUninstall = $this->l('Are you sure you want to delete the module and the related data?'); + + $this->tabs = [ + [ + 'class_name' => 'COOKIES', + 'parent_class_name' => 'CONFIGURE', + 'name' => [ + 'en' => 'Cookies configuration', + 'es' => 'Configuración de cookies', + 'de' => 'Konfiguration von Cookies', + 'fr' => 'Configuration des cookies', + 'it' => 'Configurazione dei cookie', + 'nl' => 'Cookies configuratie', + 'pl' => 'Konfiguracja plików cookie', + 'pt' => 'Configuração de cookies', + 'ro' => 'Configurarea modulelor cookie', + 'ru' => 'Конфигурация файлов cookie', + 'se' => 'Cookies konfiguration', + ], + 'module' => $this->name, + 'icon' => 'group_work', + ], + [ + 'class_name' => 'AdminCookiesPlusConfiguration', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Configuration', + 'es' => 'Configuración', + 'de' => 'Aufbau', + 'fr' => 'Configuration', + 'it' => 'Configurazione', + 'nl' => 'Configuratie', + 'pl' => 'Konfiguracja', + 'pt' => 'Configuração', + 'ro' => 'Configurație', + 'ru' => 'Конфигурация', + 'se' => 'Konfiguration', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusAppearance', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Appearance', + 'es' => 'Apariencia', + 'de' => 'Aussehen', + 'fr' => 'Apparence', + 'it' => 'Aspetto', + 'nl' => 'Uiterlijk', + 'pl' => 'Wygląd', + 'pt' => 'Aparência', + 'ro' => 'Aspect', + 'ru' => 'вид', + 'se' => 'Utseende', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusFinalities', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Cookie finalities', + 'es' => 'Finalidades de cookie', + 'de' => 'Cookie-Endgültigkeiten', + 'fr' => 'Finalités des cookies', + 'it' => 'Finalità dei cookie', + 'nl' => 'Cookie finaliteiten', + 'pl' => 'Ostateczna wersja plików cookie', + 'pt' => 'Finalidades do cookie', + 'ro' => 'Finalitățile cookie-urilor', + 'ru' => 'Окончательность файлов cookie', + 'se' => 'Cookie finaliteter', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusCookies', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Cookies', + 'es' => 'Cookies', + 'de' => 'Cookies', + 'fr' => 'Cookies', + 'it' => 'Cookies', + 'nl' => 'Cookies', + 'pl' => 'Cookies', + 'pt' => 'Cookies', + 'ro' => 'Cookies', + 'ru' => 'Cookies', + 'se' => 'Cookies', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusGTM', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Google Tag Manager (GTM)', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusFB', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Facebook Pixel', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusYT', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'YouTube', + ], + 'module' => $this->name, + ], + [ + 'class_name' => 'AdminCookiesPlusUsersConsent', + 'parent_class_name' => 'COOKIES', + 'name' => [ + 'en' => 'Users consent', + 'es' => 'Consentimiento de los usuarios', + 'de' => 'Zustimmung der Benutzer', + 'fr' => 'Consentement des utilisateurs', + 'it' => 'Consenso degli utenti', + 'nl' => 'Gebruikers toestemming', + 'pl' => 'Zgoda użytkowników', + 'pt' => 'Consentimento dos usuários', + 'ro' => 'Utilizatorii sunt de acord', + 'ru' => 'Согласие пользователей', + 'se' => 'Användarens samtycke', + ], + 'module' => $this->name, + ], + ]; + } + + public function install() + { + $result = include dirname(__FILE__) . '/sql/install.php'; + if (!$result) { + $this->_errors[] = $this->l('Error creating tables'); + return false; + } + + $result = $this->copyOverrideFolder(); + if (!$result) { + $this->_errors[] = $this->l('Error copying overrides'); + return false; + } + + $result = parent::install(); + if (!$result) { + $this->_errors[] = $this->l('Error in parent::install'); + return false; + } + + // Hooks + $result = true; + $result &= $this->registerHook('displayHeader'); + + // If there's only 1 position it returns false, so we discard the $result + $this->updatePosition(Hook::getIdByName('displayHeader'), 0, 1); + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + $result &= $this->registerHook('top'); + } + + if (version_compare(_PS_VERSION_, '1.7', '>=')) { + $result &= $this->registerHook('displayBeforeBodyClosingTag'); + } + + if (Module::isInstalled('mobile_theme')) { + $result &= $this->registerHook('displayMobileHeader'); + } + $result &= $this->registerHook('displayMyAccountBlock'); + $result &= $this->registerHook('displayMyAccountBlockfooter'); + $result &= $this->registerHook('tmMegaLayoutFooter'); + $result &= $this->registerHook('displayCookies'); + $result &= $this->registerHook('displayCookiesHeader'); + $result &= $this->registerHook('customerAccount'); + $result &= $this->registerHook('displayBackOfficeHeader'); + $result &= $this->registerHook('displayAfterBodyOpeningTag'); + $result &= $this->registerHook('actionShopDataDuplication'); + $result &= $this->registerHook('actionOutputHTMLBefore'); + $result &= $this->registerHook('actionHtaccessCreate'); + + if (Module::isInstalled('cdc_googletagmanager')) { + $cdc_googletagmanager = Module::getInstanceByName('cdc_googletagmanager'); + // if (version_compare($cdc_googletagmanager->version, '5', '')) { + $result &= $this->registerHook('displayAfterTitle'); + $this->updatePosition(Hook::getIdByName('displayAfterTitle'), 0, 1); + // } else { + $result &= $this->registerHook('displayAfterTitleTag'); + $this->updatePosition(Hook::getIdByName('displayAfterTitleTag'), 0, 1); + // } + } + + // GDPR module + $result &= $this->registerHook('registerGDPRConsent'); + + if (!$result) { + $this->_errors[] = $this->l('Error registering hooks'); + return false; + } + + // Tabs + $result = $this->installTabs($this->tabs); + if (!$result) { + $this->_errors[] = $this->l('Error installing tabs'); + return false; + } + + $result = $this->setDefaultValues(); + if (!$result) { + $this->_errors[] = $this->l('Error setting default values'); + return false; + } + + // Cache modules + /*if (Module::isInstalled('litespeedcache')) { + $litespeedcacheConfig = json_decode(Configuration::get('LITESPEED_CACHE_MODULE'), true); + + if (!isset($litespeedcacheConfig['cookiesplus'])) { + $litespeedcacheConfig['cookiesplus'] = array( + 'module' => 'cookiesplus', + 'name' => '', + 'priv' => 1, + 'ttl' => 1800, + 'tag' => 'cookiesplus', + 'events' => '', + 'ctrl' => '', + 'methods' => '!hookDisplayHeader', + 'render' => '*', + 'asvar' => 1, + 'ie' => 0, + 'ce' => 1, + ); + + Configuration::updateValue('LITESPEED_CACHE_MODULE', json_encode($litespeedcacheConfig)); + } + } + + if (Module::isInstalled('stadvancedcache')) { + $stadvancedcacheConfig = unserialize(Configuration::get('ST_ADVCACHE_DYN_HOOKS')); + + if (!isset($stadvancedcacheConfig['cookiesplus'])) { + $stadvancedcacheConfig['cookiesplus'] = array( + 'header', + 'displayBeforeBodyClosingTag' + ); + + Configuration::updateValue('ST_ADVCACHE_DYN_HOOKS', serialize($stadvancedcacheConfig)); + + $stadvancedcache = new StAdvancedCache(); + $stadvancedcache->clearAllCache(); + } + } + + if (Module::isInstalled('jprestaspeedpack')) { + $jprestaspeedpackConfig = Configuration::get('pagecache_dyn_hooks'); + + if (strpos($jprestaspeedpackConfig, 'cookiesplus') === false) { + $jprestaspeedpackConfig .= 'displaybeforebodyclosingtag|cookiesplus|0,'; + + Configuration::updateValue('pagecache_dyn_hooks', $jprestaspeedpackConfig); + + $jprestaspeedpack = new Jprestaspeedpack(); + $jprestaspeedpack->clearCache(); + } + }*/ + $result = self::clearCache(); + if (!$result) { + $this->_errors[] = $this->l('Error clearing cache'); + return false; + } + + $result = Tools::generateHtaccess(); + if (!$result) { + $this->_errors[] = $this->l('Error generating htaccess'); + return false; + } + + return true; + } + + public function installTabs($moduleTabs = null) + { + if (!$moduleTabs) { + $moduleTabs = $this->tabs; + } + + $languages = Language::getLanguages(false); + + foreach ($moduleTabs as $moduleTab) { + if (!Tab::getIdFromClassName($moduleTab['class_name'])) { + $tab = new Tab(); + $tab->class_name = $moduleTab['class_name']; + $tab->module = $moduleTab['module']; + $tab->active = 1; + + foreach ($languages as $language) { + if (is_array($moduleTab['name'])) { + if (isset($moduleTab['name'][$language['iso_code']]) && $moduleTab['name'][$language['iso_code']]) { + $tab->name[$language['id_lang']] = $moduleTab['name'][$language['iso_code']]; + } else { + $tab->name[$language['id_lang']] = $moduleTab['name']['en']; + } + } else { + $tab->name[$language['id_lang']] = $moduleTab['name']; + } + } + + if (isset($moduleTab['parent_class_name']) && is_string($moduleTab['parent_class_name'])) { + $tab->id_parent = Tab::getIdFromClassName($moduleTab['parent_class_name']); + } elseif (isset($moduleTab['id_parent'])) { + $tab->id_parent = $moduleTab['id_parent']; + } else { + $tab->id_parent = -1; + } + + if (isset($moduleTab['icon'])) { + $tab->icon = $moduleTab['icon']; + } + + $tab->add(); + if (!$tab->id) { + return false; + } + } + } + + return true; + } + + public static function clearCache() + { + if (method_exists('Tools', 'clearAllCache')) { + Tools::clearAllCache(); + } + + if (method_exists('Tools', 'clearSmartyCache')) { + Tools::clearSmartyCache(); + } + + if (method_exists('Tools', 'clearSf2Cache')) { + Tools::clearSf2Cache(); + } + + if (method_exists('Tools', 'clearCache')) { + Tools::clearCache(Context::getContext()->smarty); + } + + if (method_exists('Media', 'clearCache')) { + Media::clearCache(); + } + + $version = (int) Configuration::get('PS_CCCJS_VERSION'); + if ($version) { + Configuration::updateValue('PS_CCCJS_VERSION', ++$version); + } + + $version = (int) Configuration::get('PS_CCCCSS_VERSION'); + if ($version) { + Configuration::updateValue('PS_CCCCSS_VERSION', ++$version); + } + + return true; + } + + public function uninstall() + { + $result = true; + + $result &= $this->copyOverrideFolder(); + + $result &= parent::uninstall(); + + $result &= $this->uninstallTabs(); + + include dirname(__FILE__) . '/sql/uninstall.php'; + + /*if (Module::isInstalled('litespeedcache')) { + $litespeedcacheConfig = json_decode(Configuration::get('LITESPEED_CACHE_MODULE'), true); + + if (isset($litespeedcacheConfig['cookiesplus'])) { + unset($litespeedcacheConfig['cookiesplus']); + + Configuration::updateValue('LITESPEED_CACHE_MODULE', json_encode($litespeedcacheConfig)); + } + + if (Module::isInstalled('stadvancedcache')) { + $stadvancedcacheConfig = unserialize(Configuration::get('ST_ADVCACHE_DYN_HOOKS')); + + if (isset($stadvancedcacheConfig['cookiesplus'])) { + unset($stadvancedcacheConfig['cookiesplus']); + + Configuration::updateValue('ST_ADVCACHE_DYN_HOOKS', serialize($stadvancedcacheConfig)); + + $stadvancedcache = new StAdvancedCache(); + $stadvancedcache->clearAllCache(); + } + } + }*/ + $result &= self::clearCache(); + + $result &= Tools::generateHtaccess(); + + return (bool) $result; + } + + public function uninstallTabs($moduleTabs = null) + { + if (!$moduleTabs) { + $moduleTabs = Tab::getCollectionFromModule($this->name); + foreach ($moduleTabs as $moduleTab) { + $moduleTab->delete(); + } + } else { + foreach ($moduleTabs as $moduleTab) { + $idTab = Tab::getIdFromClassName($moduleTab['class_name']); + + if ($idTab) { + $tab = new Tab($idTab); + $tab->delete(); + } + } + } + + return true; + } + + public function enable($force_all = false) + { + if (!$this->copyOverrideFolder()) { + return false; + } + + $result = true; + + $result &= parent::enable($force_all); + + $result &= Tools::generateHtaccess(); + + return (bool) $result; + } + + public function disable($force_all = false) + { + if (!$this->copyOverrideFolder()) { + return false; + } + + $result = true; + + $result &= parent::disable($force_all); + + $result &= Tools::generateHtaccess(); + // Bypass if (!defined('PS_INSTALLATION_IN_PROGRESS')) { + $this->hookActionHtaccessCreate(); + + return (bool) $result; + } + + public function setDefaultValues() + { + Configuration::updateValue('C_P_REVOKE_CONSENT', date('Y-m-d H:i:s', time())); + + Configuration::updateValue('C_P_REFRESH', 0); + Configuration::updateValue('C_P_EXPIRY', '365'); + Configuration::updateValue('C_P_BOTS', 'Ahrefs|ADmantX|Alexa|AskJeeves|Baidu|Bing|Butterfly|Cookiebot|crawler|DuckDuckGo|exabot|Evaliant|Facebook|Firefly|Froogle|Gigabot|Google|Googlebot|Grapeshot|Inktomi|InfoSeek|Lighthouse|Looksmart|MeanPath|Mediapartners-Google|Me.dium|MJ12bot|MSN|NationalDirectory|OpenSiteExplorer|Pinterest|Proximic|Rankivabot|Scooter|Sogou|Sogouwebspider|Sosospider|Squider|TechnoratiSnoop|TECNOSEEK|Teoma|TweetmemeBot|TweetMeme|Twiceler|Twitturls|URL_Spider_SQL|WebAltaCrawler|WebFindBot|www.galaxy.com|Yaho|Yandex|Ahrefs|YodaoBot'); + Configuration::updateValue('C_P_HOOK_POSITION', 0); + Configuration::updateValue('C_P_OVERLAY_OPACITY', '0.5'); + Configuration::updateValue('C_P_GEO', '0'); + Configuration::updateValue('C_P_POSITION', 'center'); + Configuration::updateValue('C_P_WIDTH', '50'); + Configuration::updateValue('C_P_ACCEPT_DISPLAY', 1); + Configuration::updateValue('C_P_MORE_INFO_DISPLAY', 1); + Configuration::updateValue('C_P_OVERLAY', 1); + Configuration::updateValue('C_P_OVERLAY_OPACITY', '0.5'); + Configuration::updateValue('C_P_BUTTON_POSITION', '1'); + + Configuration::updateValue('C_P_FONT_COLOR', '#000'); + Configuration::updateValue('C_P_BACKGROUND_COLOR', '#FFFFFF'); + + Configuration::updateValue('C_P_ACCEPT_FONT_SIZE', '16px'); + Configuration::updateValue('C_P_ACCEPT_BACKGROUND_COLOR', '#20BF6B'); + Configuration::updateValue('C_P_ACCEPT_BORDER_COLOR', '#20BF6B'); + Configuration::updateValue('C_P_ACCEPT_FONT_COLOR', '#FFFFFF'); + + Configuration::updateValue('C_P_MORE_INFO_FONT_SIZE', '16px'); + Configuration::updateValue('C_P_MORE_INFO_BACKGROUND_COLOR', '#FFFFFF'); + Configuration::updateValue('C_P_MORE_INFO_BORDER_COLOR', '#7A7A7A'); + Configuration::updateValue('C_P_MORE_INFO_FONT_COLOR', '#000'); + + Configuration::updateValue('C_P_REJECT_FONT_SIZE', '16px'); + Configuration::updateValue('C_P_REJECT_BACKGROUND_COLOR', '#FFFFFF'); + Configuration::updateValue('C_P_REJECT_BORDER_COLOR', '#7A7A7A'); + Configuration::updateValue('C_P_REJECT_FONT_COLOR', '#000'); + + Configuration::updateValue('C_P_SAVE_FONT_SIZE', '16px'); + Configuration::updateValue('C_P_SAVE_BACKGROUND_COLOR', '#FFFFFF'); + Configuration::updateValue('C_P_SAVE_BORDER_COLOR', '#7A7A7A'); + Configuration::updateValue('C_P_SAVE_FONT_COLOR', '#000'); + + Configuration::updateValue('C_P_REJECT_DISPLAY', '1'); + Configuration::updateValue('C_P_DEFAULT_CONSENT', true); + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + Configuration::updateValue('C_P_MATERIAL_ICONS_LIBRARY', '1'); + Configuration::updateValue('C_P_MATERIAL_ICONS', 1); + } else { + if ($this->context->shop->theme->getName() === 'panda') { + Configuration::updateValue('C_P_MATERIAL_ICONS_LIBRARY', 2); + } else { + Configuration::updateValue('C_P_MATERIAL_ICONS_LIBRARY', 1); + } + } + + $cookiesDefault = []; + // English + $langCode = 'en'; + $cookiesDefault['title'][$langCode] = 'Your cookie settings'; + $cookiesDefault['text'][$langCode] = '

This store asks you to accept cookies for performance, social media and advertising purposes. Social media and advertising cookies of third parties are used to offer you social media functionalities and personalized ads. Do you accept these cookies and the processing of personal data involved?

'; + + // Spanish + $langCode = 'es'; + $cookiesDefault['title'][$langCode] = 'Tu configuración de cookies'; + $cookiesDefault['text'][$langCode] = '

Esta tienda te pide que aceptes cookies para fines de rendimiento, redes sociales y publicidad. Las redes sociales y las cookies publicitarias de terceros se utilizan para ofrecerte funciones de redes sociales y anuncios personalizados. ¿Aceptas estas cookies y el procesamiento de datos personales involucrados?

'; + + // French + $langCode = 'fr'; + $cookiesDefault['title'][$langCode] = 'Vos paramètres de cookies'; + $cookiesDefault['text'][$langCode] = '

Ce magasin vous demande d\'accepter les cookies afin d\'optimiser les performances, les fonctionnalités des réseaux sociaux et la pertinence de la publicité. Les cookies tiers liés aux réseaux sociaux et à la publicité sont utilisés pour vous offrir des fonctionnalités optimisées sur les réseaux sociaux, ainsi que des publicités personnalisées. Acceptez-vous ces cookies ainsi que les implications associées à l\'utilisation de vos données personnelles ?

'; + + // French (Canada) + $langCode = 'qc'; + $cookiesDefault['title'][$langCode] = 'Vos paramètres de cookies'; + $cookiesDefault['text'][$langCode] = '

Ce magasin vous demande d\'accepter les cookies afin d\'optimiser les performances, les fonctionnalités des réseaux sociaux et la pertinence de la publicité. Les cookies tiers liés aux réseaux sociaux et à la publicité sont utilisés pour vous offrir des fonctionnalités optimisées sur les réseaux sociaux, ainsi que des publicités personnalisées. Acceptez-vous ces cookies ainsi que les implications associées à l\'utilisation de vos données personnelles ?

'; + + // Polish + $langCode = 'pl'; + $cookiesDefault['title'][$langCode] = 'Ustawienia plików cookie'; + $cookiesDefault['text'][$langCode] = '

Niniejsza witryna wykorzystuje pliki cookies w celu świadczenia usług na najwyższym poziomie i w sposób dostosowany do indywidualnych potrzeb. Korzystanie z witryny bez zmiany ustawień dotyczących cookies oznacza, że będą one zamieszczane w urządzeniu końcowym. Jeśli nie akceptujesz opuść tę stronę internetową.

'; + + // Romanian + $langCode = 'ro'; + $cookiesDefault['title'][$langCode] = 'Setările cookie-urilor'; + $cookiesDefault['text'][$langCode] = '

Acest magazin vă solicită să acceptați cookie-uri pentru performanță, media și publicitate. Mediile sociale și cookie-urile de publicitate ale unor terțe părți sunt utilizate pentru a vă oferi funcții de social media și anunțuri personalizate. Acceptați aceste cookie-uri și procesarea datelor personale implicate?

'; + + // Portuguese + $langCode = 'pt'; + $cookiesDefault['title'][$langCode] = 'As tuas configurações de cookies'; + $cookiesDefault['text'][$langCode] = '

Esta loja pede-te para aceitares cookies para efeitos de desempenho, redes sociais e publicidade. Os cookies de publicidade e de redes sociais de terceiros são utilizados para te oferecer funcionalidades sociais e anúncios personalizados. Aceitas estes cookies e o processamento de dados pessoais envolvidos?

'; + + // Slovak + $langCode = 'sk'; + $cookiesDefault['title'][$langCode] = 'Nastavenia súborov cookie'; + $cookiesDefault['text'][$langCode] = '

Náš obchod používa súbory cookie za účelom zabezpečenia nevyhnutnej funkcionality stránok, sociálnych médií a marketingu. Súhlasíte s týmito súbormi cookies a spracovaním príslušných osobných údajov?

'; + + // Nederlands + $langCode = 'nl'; + $cookiesDefault['title'][$langCode] = 'Je cookie-instellingen'; + $cookiesDefault['text'][$langCode] = '

Deze winkel vraagt je om cookies te accepteren voor betere prestaties en voor sociale-media- en advertentiedoeleinden. Er worden sociale-media- en advertentiecookies van derden gebruikt om je sociale-mediafunctionaliteit en persoonlijke advertenties te bieden. Accepteer je deze cookies en de bijbehorende verwerking van je persoonsgegevens?

'; + + // Deutsch + $langCode = 'de'; + $cookiesDefault['title'][$langCode] = 'Ihre Cookie-Einstellungen'; + $cookiesDefault['text'][$langCode] = '

Für eine optimal Performance, eine reibungslose Verwendung sozialer Medien und aus Werbezwecken empfiehlt dir dieser Laden, der Verwendung von Cookies zuzustimmen. Durch Cookies von sozialen Medien und Werbecookies von Drittparteien hast du Zugriff auf Social-Media-Funktionen und erhältst personalisierte Werbung. Stimmst du der Verwendung dieser Cookies und der damit verbundenen Verarbeitung deiner persönlichen Daten zu?

'; + + // Greek + $langCode = 'gr'; + $cookiesDefault['title'][$langCode] = 'Οι ρυθμίσεις cookie σας'; + $cookiesDefault['text'][$langCode] = '

Αυτό το κατάστημα σου ζητά να αποδεχτείς τα cookies για σκοπούς απόδοσης, κοινωνικής δικτύωσης και διαφήμισης. Τα cookies κοινωνικής δικτύωσης και διαφήμισης παρέχονται από τρίτα μέρη για να σου προσφέρουν λειτουργίες κοινωνικής δικτύωσης και εξατομικευμένες διαφημίσεις. Αποδέχεσαι αυτά τα cookies και την συνεπαγόμενη επεξεργασία προσωπικών δεδομένων;

'; + + // Italian + $langCode = 'it'; + $cookiesDefault['title'][$langCode] = 'Impostazioni dei cookie'; + $cookiesDefault['text'][$langCode] = '

Questo negozio richiede di accettare i cookie per scopi legati a prestazioni, social media e annunci pubblicitari. I cookie di terze parti per social media e a scopo pubblicitario vengono utilizzati per offrire funzionalità social e annunci pubblicitari personalizzati. Accetti i cookie e l\'elaborazione dei dati personali interessati?

'; + + // Svenska + $langCode = 'sv'; + $cookiesDefault['title'][$langCode] = 'Dina cookieinställningar'; + $cookiesDefault['text'][$langCode] = '

Denna butik ber dig att godkänna cookies för anpassning av prestanda, sociala medier och marknadsföring. Tredjepartscookies för sociala medier och marknadsföring används för att erbjuda anpassade annonser och funktioner för sociala medier. Godkänner du dessa cookies och behandlingen av berörda personuppgifter?

'; + + // Dansk + $langCode = 'da'; + $cookiesDefault['title'][$langCode] = 'Dine indstillinger for cookies'; + $cookiesDefault['text'][$langCode] = '

Denne butik beder dig om at acceptere cookies til performance, sociale medier og reklameformål. Sociale medier og tredjeparts annoncecookies bruges til at tilbyde dig funktionaliteter og tilpassede annoncer på sociale medier. Vil du acceptere disse cookies og behandlingen af implicerede personoplysninger?

'; + + // Norsk + $langCode = 'no'; + $cookiesDefault['title'][$langCode] = 'Dine innstillinger for informasjonskapsler'; + $cookiesDefault['text'][$langCode] = '

Denne butikken spør om du godtar informasjonskapsler for ytelsesformål, sosiale medier og annonsering. Informasjonskapsler for sosiale medier og annonsering fra tredjeparter brukes for å tilby deg funksjoner på sosiale medier og tilpassede annonser. Godtar du disse informasjonskapslene og den involverte behandlingen av personopplysningene dine?

'; + + // ČEŠTINA + $langCode = 'cs'; + $cookiesDefault['title'][$langCode] = 'Tvá nastavení souborů cookie'; + $cookiesDefault['text'][$langCode] = '

Společnost tento obchod žádá o tvůj souhlas s používáním souborů cookie pro účely výkonu, sociálních médií a reklamy. Sociální média a reklamní soubory cookie třetích stran používáme k tomu, abychom ti mohli nabízet funkce sociálních médií a přizpůsobenou reklamu. Další informace nebo doplnění nastavení získáš kliknutím na tlačítko „Více informací“ nebo otevřením nabídky „Nastavení souborů cookie“ v dolní části webové stránky. Podrobnější informace o souborech cookie a zpracování tvých osobních údajů najdeš v našich Zásadách ochrany osobních údajů a používání souborů cookie. Souhlasíš s používáním souborů cookie a zpracováním souvisejících osobních údajů?

'; + + // Magyar + $langCode = 'hu'; + $cookiesDefault['title'][$langCode] = 'Cookie-beállítások'; + $cookiesDefault['text'][$langCode] = '

Ez a bolt a megfelelő teljesítmény és a közösségimédia-funkciók biztosításához, valamint a hirdetések megjelenítéséhez kéri a cookie-k elfogadását. A harmadik felek közösségimédia- és hirdetési cookie-jai használatával biztosítunk közösségimédia-funkciókat, és jelenítünk meg személyre szabott reklámokat. Ha több információra van szükséged, vagy kiegészítenéd a beállításaidat, kattints a További információ gombra, vagy keresd fel a webhely alsó részéről elérhető Cookie-beállítások területet. A cookie-kkal kapcsolatos további információért, valamint a személyes adatok feldolgozásának ismertetéséért tekintsd meg Adatvédelmi és cookie-kra vonatkozó szabályzatunkat. Elfogadod ezeket a cookie-kat és az érintett személyes adatok feldolgozását?

'; + + $cookiesDefault['cookie'][CookiesPlusFinality::NECESSARY_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::NECESSARY_COOKIE); + $cookiesDefault['cookie'][CookiesPlusFinality::PREFERENCE_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::PREFERENCE_COOKIE); + $cookiesDefault['cookie'][CookiesPlusFinality::STATISTIC_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::STATISTIC_COOKIE); + $cookiesDefault['cookie'][CookiesPlusFinality::MARKETING_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::MARKETING_COOKIE); + // $cookiesDefault['cookie'][CookiesPlusFinality::UNCLASSIFIED_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::UNCLASSIFIED_COOKIE); + // $cookiesDefault['cookie'][CookiesPlusFinality::PERFORMANCE_COOKIE] = CookiesPlusFinality::getDefaultValues(CookiesPlusFinality::PERFORMANCE_COOKIE); + + $fields = []; + $languages = Language::getLanguages(false); + foreach ($languages as $lang) { + $languageCode = strtok($lang['language_code'], '-'); + + $fields['C_P_TITLE'][$lang['id_lang']] = (isset($cookiesDefault['title'][$languageCode]) && $cookiesDefault['title'][$languageCode]) ? $cookiesDefault['title'][$languageCode] : $cookiesDefault['title']['en']; + $fields['C_P_TEXT_BASIC'][$lang['id_lang']] = (isset($cookiesDefault['text'][$languageCode]) && $cookiesDefault['text'][$languageCode]) ? $cookiesDefault['text'][$languageCode] : $cookiesDefault['text']['en']; + } + + Configuration::updateValue('C_P_TITLE', $fields['C_P_TITLE'], true); + Configuration::updateValue('C_P_TEXT_BASIC', $fields['C_P_TEXT_BASIC'], true); + + $modules = Module::getModulesOnDisk(true); + if (Shop::isFeatureActive()) { + $shops = Shop::getShops(false, null, true); + } else { + $shops = [1]; + } + + foreach ($shops as $shop) { + foreach ($cookiesDefault['cookie'] as $cookiesPlusFinalityId => $cookieDefault) { + $cookiesPlusFinality = new CookiesPlusFinality(); + $cookiesPlusFinality->id_shop = $shop; + $cookiesPlusFinality->technical = (isset($cookieDefault['technical']) && $cookieDefault['technical']) ? $cookieDefault['technical'] : 0; + $cookiesPlusFinality->active = (isset($cookieDefault['active']) && $cookieDefault['active']) ? $cookieDefault['active'] : 0; + $cookiesPlusFinality->position = $cookieDefault['position']; + + if (isset($cookieDefault['modules']) && $cookieDefault['modules']) { + $modulesIds = []; + foreach ($modules as $module) { + if ($module->installed && in_array($module->name, $cookieDefault['modules'])) { + $modulesIds[] = $module->id; + } + } + + $cookiesPlusFinality->modules = json_encode($modulesIds); + + // If store has any of the modules, enable this finality + if ($modulesIds) { + $cookiesPlusFinality->active = 1; + } + } + + foreach ($languages as $lang) { + $languageCode = strtok($lang['language_code'], '-'); + $cookiesPlusFinality->name[$lang['id_lang']] = (isset($cookieDefault['name'][$languageCode]) && $cookieDefault['name'][$languageCode]) ? $cookieDefault['name'][$languageCode] : $cookieDefault['name']['en']; + $cookiesPlusFinality->description[$lang['id_lang']] = (isset($cookieDefault['description'][$languageCode]) && $cookieDefault['description'][$languageCode]) ? $cookieDefault['description'][$languageCode] : $cookieDefault['description']['en']; + } + + $result = $cookiesPlusFinality->save(); + + if ($cookiesPlusFinalityId === CookiesPlusFinality::STATISTIC_COOKIE) { + $cookiesPlusStatisticFinalityId = $cookiesPlusFinality->id; + } + + if ($cookiesPlusFinalityId === CookiesPlusFinality::MARKETING_COOKIE) { + $cookiesPlusMarketingFinalityId = $cookiesPlusFinality->id; + } + + if (!$result) { + return false; + } + + if (isset($cookieDefault['cookies']) && $cookieDefault['cookies']) { + foreach ($cookieDefault['cookies'] as $cookie) { + $cookiesPlusCookie = new CookiesPlusCookie(); + $cookiesPlusCookie->id_shop = $shop; + $cookiesPlusCookie->id_cookiesplus_finality = $cookiesPlusFinality->id; + $cookiesPlusCookie->active = $cookie['active']; + + $cookiesPlusCookie->name = $cookie['name']; + $cookiesPlusCookie->provider = isset($cookie['provider']) ? $cookie['provider'] : ''; + $cookiesPlusCookie->provider_url = isset($cookie['provider_url']) ? $cookie['provider_url'] : ''; + + // If store has any of the modules, enable this finality + if (isset($cookie['modules']) && $cookie['modules']) { + foreach ($modules as $module) { + if ($module->installed && isset($module->name) && in_array($module->name, $cookie['modules'])) { + $cookiesPlusCookie->active = 1; + $cookiesPlusFinality = new CookiesPlusFinality($cookiesPlusFinality->id); + $cookiesPlusFinality->active = 1; + // $cookiesPlusFinality->save(); + break; + } + } + } + + foreach ($languages as $lang) { + $languageCode = strtok($lang['language_code'], '-'); + + if (isset($cookie['purpose']['en'])) { + $cookiesPlusCookie->purpose[$lang['id_lang']] = (isset($cookie['purpose'][$languageCode]) && $cookie['purpose'][$languageCode]) ? $cookie['purpose'][$languageCode] : $cookie['purpose']['en']; + } + + if (isset($cookie['expiry']['en'])) { + $cookiesPlusCookie->expiry[$lang['id_lang']] = (isset($cookie['expiry'][$languageCode]) && $cookie['expiry'][$languageCode]) ? $cookie['expiry'][$languageCode] : $cookie['expiry']['en']; + } + } + + $cookiesPlusCookie->save(); + } + } + } + + // GTM + $gtm = [ + $cookiesPlusStatisticFinalityId => [ + 'cookiesPlusFinality' => $cookiesPlusStatisticFinalityId, + 'gtmFinality' => [ + 'analytics_storage' => true, + ], + 'firingEvent' => '', + ], + $cookiesPlusMarketingFinalityId => [ + 'cookiesPlusFinality' => $cookiesPlusMarketingFinalityId, + 'gtmFinality' => [ + 'ad_storage' => true, + 'ad_user_data' => true, + 'ad_personalization' => true, + ], + 'firingEvent' => '', + ], + ]; + $gtm = json_encode($gtm); + Configuration::updateValue('C_P_GTM_CONSENT', $gtm, false, null, $shop); + } + + return true; + } + + public function getContent() + { + Tools::redirectAdmin('index.php?controller=AdminCookiesPlusConfiguration&token=' . Tools::getAdminTokenLite('AdminCookiesPlusConfiguration')); + } + + public function getWarnings($getAll = true) + { + $warnings = []; + + if (Configuration::get('PS_DISABLE_NON_NATIVE_MODULE')) { + $warnings[] = sprintf($this->l('%1$s "%2$s" at %3$s - %4$s'), $this->l('Disable'), $this->l('Disable non PrestaShop modules'), $this->l('Advanced Parameters'), $this->l('Performance')); + } + + if (Configuration::get('PS_DISABLE_OVERRIDES')) { + $warnings[] = sprintf($this->l('%1$s "%2$s" at %3$s - %4$s'), $this->l('Disable'), $this->l('Disable all overrides'), $this->l('Advanced Parameters'), $this->l('Performance')); + } + + $cookiesPlusFinalitiesList = CookiesPlusFinality::getCookiesPlusFinalities(); + $atLeastOneFinalityNonTechnical = false; + $atLeastOneFinalityTechnical = false; + foreach ($cookiesPlusFinalitiesList as $cookiesPlusFinality) { + if ($cookiesPlusFinality['active'] && $cookiesPlusFinality['technical']) { + $atLeastOneFinalityTechnical = true; + } + + if ($cookiesPlusFinality['active'] && !$cookiesPlusFinality['technical']) { + $atLeastOneFinalityNonTechnical = true; + } + } + + // If there's any technical cookie finality enabled + if (!$atLeastOneFinalityTechnical) { + $warnings[] = $this->l('Please check "Cookie finalities". You need to enable at least one technical cookie finality.'); + } + + // If there's only technical cookies, there's no need to display the warnings + if (!$atLeastOneFinalityNonTechnical) { + $warnings[] = $this->l('Please check "Cookie finalities". You need to enable at least one non-technical cookie finality. If there\'s only technical cookies finalities enabled, the cookie notice will not be displayed'); + } + + /*if (Module::isInstalled('litespeedcache')) { + $warnings[] = $this->l('It seems that you are using litespeedcache cache. An additional configuration in this module may be required.'); + } + + if (Module::isInstalled('stadvancedcache')) { + $warnings[] = $this->l('It seems that you are using stadvancedcache cache. An additional configuration in this module may be required.'); + } + + if (Module::isInstalled('jprestaspeedpack')) { + $warnings[] = $this->l('It seems that you are using jprestaspeedpack cache. An additional configuration in this module may be required.'); + }*/ + if (Module::isInstalled('litespeedcache') + || Module::isInstalled('stadvancedcache') + || Module::isInstalled('jprestaspeedpack') + || Module::isInstalled('pagecache')) { + $warnings[] = $this->l('If you are using a cache module please ensure that the cookies module is working correctly.'); + } + + if (count($warnings) && version_compare(_PS_VERSION_, '1.6.1', '<')) { + return $warnings[0]; + } + + if (!$getAll && count($warnings)) { + return $warnings[0]; + } + + return $warnings; + } + + public function getModuleList() + { + $query = 'SELECT m.`id_module`, m.`name` + FROM `' . _DB_PREFIX_ . 'module` m'; + + $module_list = Db::getInstance()->executeS($query); + + foreach ($module_list as $key => &$module) { + $module['displayName'] = Module::getModuleName($module['name']); + + if ((int) $module['id_module'] === 0) { + unset($module_list[$key]); + } + + if ($module['name'] === $this->name) { + unset($module_list[$key]); + } + } + unset($module); + + usort($module_list, static function ($a, $b) { + return strnatcasecmp($a['displayName'], $b['displayName']); + }); + + return $module_list; + } + + protected static function executeModule() + { + if (!Configuration::get('C_P_ENABLE')) { + return false; + } + + // Validate allowed IPs + if (Configuration::get('C_P_DEBUG') && !self::onlyIPDebug()) { + return false; + } + + // Validate user agent + if (self::byPassUserAgent()) { + return false; + } + + // Validate disallow IPs + if (self::bypassIP()) { + return false; + } + + return true; + } + + protected static function getGeo() + { + // Don't display outside EU + if (Configuration::get('PS_GEOLOCATION_ENABLED') + && !Configuration::get('C_P_GEO') + && !in_array(Tools::getRemoteAddr(), ['localhost', '127.0.0.1', '::1'])) { + // Check if Maxmind Database exists + if (@filemtime(_PS_GEOIP_DIR_ . _PS_GEOIP_CITY_FILE_)) { + if (version_compare(_PS_VERSION_, '1.7', '<')) { + include_once _PS_GEOIP_DIR_ . 'geoipcity.inc'; + + $gi = geoip_open(realpath(_PS_GEOIP_DIR_ . _PS_GEOIP_CITY_FILE_), GEOIP_STANDARD); + $record = geoip_record_by_addr($gi, Tools::getRemoteAddr()); + + if (is_object($record) + && $record->continent_code + && $record->continent_code !== 'EU') { + return false; + } + } else { + $reader = new GeoIp2\Database\Reader(_PS_GEOIP_DIR_ . _PS_GEOIP_CITY_FILE_); + try { + $record = $reader->city(Tools::getRemoteAddr()); + } catch (GeoIp2\Exception\AddressNotFoundException $e) { + $record = null; + } + + if (is_object($record) + && $record->continent->code + && $record->continent->code !== 'EU') { + return false; + } + } + } + } + + return true; + } + + protected static function byPassUserAgent() + { + if (isset($_SERVER['HTTP_USER_AGENT']) + && Configuration::get('C_P_BOTS') + && preg_match('/' . Configuration::get('C_P_BOTS') . '/i', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return false; + } + + protected static function bypassIP() + { + if (Configuration::get('C_P_IPS') + && in_array(Tools::getRemoteAddr(), explode('|', Configuration::get('C_P_IPS')))) { + return true; + } + + return false; + } + + protected static function onlyIPDebug() + { + if (!Configuration::get('C_P_IPS_DEBUG')) { + return true; + } + + if (in_array(Tools::getRemoteAddr(), explode('|', Configuration::get('C_P_IPS_DEBUG')))) { + return true; + } + + return false; + } + + public function getCookiesPlusCookiesList() + { + $idCookiesPlusFinality = (int) Tools::getValue('id_cookiesplus_finality'); + + if (!Tools::getIsset('addcookiesplus_finality') && !$idCookiesPlusFinality) { + return $this->displayError('Error loading cookies'); + } + + $cookiesPlusCookiesList = CookiesPlusCookie::getCookiesPlusCookies($idCookiesPlusFinality, null, false, $this->context->shop->id); + + $fields_list = [ + 'active' => [ + 'title' => $this->l('Enabled'), + 'active' => 'active', + 'type' => 'bool', + ], + 'name' => [ + 'title' => $this->l('Cookie name'), + 'filter_key' => 'a!name', + ], + 'provider' => [ + 'title' => $this->l('Provider'), + ], + 'purpose' => [ + 'title' => $this->l('Purpose'), + 'callback' => 'getCookiePurposeCallback', + 'callback_object' => 'CookiesPlusCookie', + ], + 'expiry' => [ + 'title' => $this->l('Expiry'), + ], + ]; + + $helperList = new HelperList(); + + $helperList->shopLinkType = ''; + $helperList->simple_header = false; + $helperList->show_toolbar = true; + $helperList->module = $this; + $helperList->actions = ['edit', 'deletecookie']; + $helperList->identifier = 'id_cookiesplus_cookie'; + $helperList->table = 'cookiesplus_cookie'; + $helperList->token = Tools::getAdminTokenLite('AdminCookiesPlusCookies'); + $helperList->currentIndex = $this->context->link->getAdminLink('AdminCookiesPlusCookies', false) . '&back=1&id_cookiesplus_finality=' . (int) Tools::getValue('id_cookiesplus_finality'); + + $helperList->title = $this->l('Cookies detail'); + + if (!Tools::getIsset('addcookiesplus_finality')) { + $helperList->toolbar_btn['new'] = [ + 'href' => $helperList->currentIndex . '&add' . $helperList->table . '&token=' . $helperList->token . '&id_cookiesplus_finality=' . Tools::getValue('id_cookiesplus_finality'), + 'desc' => $this->l('Add new'), + ]; + } + + $helperList->listTotal = count($cookiesPlusCookiesList); + + return $helperList->generateList($cookiesPlusCookiesList, $fields_list); + } + + public function displayDeleteCookieLink($token = null, $id = null, $name = null) + { + $tpl = $this->context->smarty->createTemplate('helpers/list/list_action_delete.tpl'); + + $tpl->assign([ + 'href' => $this->context->link->getAdminLink('AdminCookiesPlusCookies', false) . '&id_cookiesplus_cookie=' . $id . '&deletecookiesplus_cookie&token=' . $token, + 'confirm' => $this->l('Delete the selected item?') . $name, + 'action' => $this->l('Delete'), + 'consent_hash' => $id, + ]); + + return $tpl->fetch(); + } + + /* Hooks */ + public function hookDisplayAfterTitle($params) + { + return $this->hookDisplayAfterTitleTag($params); + } + + public function hookDisplayAfterTitleTag() + { + if (Module::isInstalled('cdc_googletagmanager')) { + $html = ''; + + if (Configuration::get('C_P_GTM_ENABLE')) { + $cookiesPlusCookiePreferences = self::getCookiesPlusCookiePreferences(); + + $gtmConsents = json_decode(Configuration::get('C_P_GTM_CONSENT'), true); + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities((int) $this->context->language->id, true); + $gtm = []; + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $index = 'cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']; + if (isset($cookiesPlusCookiePreferences[$index], $gtmConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']])) { + if (!isset($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'])) { + continue; + } + foreach (array_keys($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality']) as $gtmFinality) { + if ($cookiesPlusCookiePreferences[$index] === 'on') { + if (isset($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'][$gtmFinality]) + && $gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'][$gtmFinality]) { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = true; + } else { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = false; + } + + } else { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = false; + } + } + } + } + + $this->context->smarty->assign([ + 'gtm' => $gtm, + ]); + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/gtm_consentmode.tpl'); + } + + if (Configuration::get('C_P_GTM_ENABLE')) { + $html .= Configuration::get('C_P_GTM_HEAD'); + } else { + $random = Tools::substr(md5(microtime()), 0, 10); + $divName = 'hookDisplayAfterTitleTag_' . $this->id . '_' . $random; + + $this->context->smarty->assign([ + 'divName' => $divName, + 'id_module' => $this->id, + 'finalities' => implode(',', array_keys(json_decode(Configuration::get('C_P_GTM_FIRE_CONSENT'), true) ?? []) ?: []), + 'script' => json_encode(Configuration::get('C_P_GTM_HEAD')), + 'js' => '[]', + 'css' => '[]', + ]); + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/hookmoduledata.tpl'); + } + + return $html; + } + } + + /* Don't place in this header anything that can NOT be cachable */ + public function hookHeader() + { + return $this->hookDisplayHeader(); + } + + public function hookDisplayHeader() + { + $cookiesPlusCookiePreferences = self::getCookiesPlusCookiePreferences(); + if (isset($cookiesPlusCookiePreferences['consent_date']) + && date('Y-m-d H:i', strtotime(Configuration::get('C_P_REVOKE_CONSENT'))) > date('Y-m-d H:i', strtotime($cookiesPlusCookiePreferences['consent_date']))) { + $this->resetCookiesPlusPreferences(); + } + + // Check if consent file exists + if (Configuration::get('C_P_SAVE_CONSENT')) { + if (isset($cookiesPlusCookiePreferences['consent_hash'])) { + if (!CookiesPlusUserConsent::getCookiesPlusUserConsentDataByHash($cookiesPlusCookiePreferences['consent_hash'])) { + $this->resetCookiesPlusPreferences(); + } + } + } + + $this->context->controller->addCSS(_MODULE_DIR_ . $this->name . '/views/css/cookiesplus.css'); + if (Configuration::get('C_P_MATERIAL_ICONS')) { + $this->context->controller->addCSS(_MODULE_DIR_ . $this->name . '/views/css/cookiesplus-material-icons.css'); + // $html .= ''; + } + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + $this->context->controller->addJS(_MODULE_DIR_ . $this->name . '/views/js/cookiesplus-front.js'); + } else { + $this->context->controller->registerJavascript( + 'cookiesplus-front', + 'modules/' . $this->name . '/views/js/cookiesplus-front.js', + [ + 'attributes' => 'async', + ] + ); + } + + // Just assign to smarty, in case user add an IF condition in template for a custom script + $this->context->smarty->assign([ + 'C_P_COOKIE_VALUE' => (array) $cookiesPlusCookiePreferences, + ]); + + $this->context->smarty->assign([ + 'C_P_CSS' => Configuration::get('C_P_CSS'), + 'C_P_BACKGROUND_COLOR' => Configuration::get('C_P_BACKGROUND_COLOR'), + 'C_P_FONT_COLOR' => Configuration::get('C_P_FONT_COLOR'), + 'C_P_BUTTON_POSITION' => Configuration::get('C_P_BUTTON_POSITION'), + 'C_P_ACCEPT_DISPLAY' => Configuration::get('C_P_ACCEPT_DISPLAY'), + 'C_P_ACCEPT_BACKGROUND_COLOR' => Configuration::get('C_P_ACCEPT_BACKGROUND_COLOR'), + 'C_P_ACCEPT_BORDER_COLOR' => Configuration::get('C_P_ACCEPT_BORDER_COLOR'), + 'C_P_ACCEPT_FONT_COLOR' => Configuration::get('C_P_ACCEPT_FONT_COLOR'), + 'C_P_ACCEPT_FONT_SIZE' => Configuration::get('C_P_ACCEPT_FONT_SIZE'), + 'C_P_ACCEPT_PADDING' => Configuration::get('C_P_ACCEPT_PADDING'), + 'C_P_MORE_INFO_DISPLAY' => Configuration::get('C_P_MORE_INFO_DISPLAY'), + 'C_P_MORE_INFO_BACKGROUND_COLOR' => Configuration::get('C_P_MORE_INFO_BACKGROUND_COLOR'), + 'C_P_MORE_INFO_BORDER_COLOR' => Configuration::get('C_P_MORE_INFO_BORDER_COLOR'), + 'C_P_MORE_INFO_FONT_COLOR' => Configuration::get('C_P_MORE_INFO_FONT_COLOR'), + 'C_P_MORE_INFO_FONT_SIZE' => Configuration::get('C_P_MORE_INFO_FONT_SIZE'), + 'C_P_MORE_INFO_PADDING' => Configuration::get('C_P_MORE_INFO_PADDING'), + 'C_P_REJECT_DISPLAY' => Configuration::get('C_P_REJECT_DISPLAY'), + 'C_P_REJECT_BACKGROUND_COLOR' => Configuration::get('C_P_REJECT_BACKGROUND_COLOR'), + 'C_P_REJECT_BORDER_COLOR' => Configuration::get('C_P_REJECT_BORDER_COLOR'), + 'C_P_REJECT_FONT_COLOR' => Configuration::get('C_P_REJECT_FONT_COLOR'), + 'C_P_REJECT_FONT_SIZE' => Configuration::get('C_P_REJECT_FONT_SIZE'), + 'C_P_REJECT_PADDING' => Configuration::get('C_P_REJECT_PADDING'), + 'C_P_SAVE_BACKGROUND_COLOR' => Configuration::get('C_P_SAVE_BACKGROUND_COLOR'), + 'C_P_SAVE_BORDER_COLOR' => Configuration::get('C_P_SAVE_BORDER_COLOR'), + 'C_P_SAVE_FONT_COLOR' => Configuration::get('C_P_SAVE_FONT_COLOR'), + 'C_P_SAVE_FONT_SIZE' => Configuration::get('C_P_SAVE_FONT_SIZE'), + 'C_P_SAVE_PADDING' => Configuration::get('C_P_SAVE_PADDING'), + 'C_P_MATERIAL_ICONS_LIBRARY' => Configuration::get('C_P_MATERIAL_ICONS_LIBRARY'), + 'C_P_ICONS' => Configuration::get('C_P_ICONS'), + 'C_P_TAB_ENABLED' => Configuration::get('C_P_TAB_ENABLED'), + 'C_P_TAB_POSITION' => Configuration::get('C_P_TAB_POSITION'), + 'C_P_TAB_BACKGROUND_COLOR' => Configuration::get('C_P_TAB_BACKGROUND_COLOR'), + 'C_P_TAB_FONT_COLOR' => Configuration::get('C_P_TAB_FONT_COLOR'), + ]); + + $html = $this->context->smarty->fetch($this->local_path . 'views/templates/hook/cookies-style.tpl'); + + if (!Module::isInstalled('cdc_googletagmanager')) { + if (Configuration::get('C_P_GTM_ENABLE')) { + $gtmConsents = json_decode(Configuration::get('C_P_GTM_CONSENT'), true); + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities((int) $this->context->language->id, true); + $gtm = []; + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $index = 'cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']; + if (isset($cookiesPlusCookiePreferences[$index], $gtmConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']])) { + if (!isset($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'])) { + continue; + } + foreach (array_keys($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality']) as $gtmFinality) { + if ($cookiesPlusCookiePreferences[$index] === 'on') { + if (isset($gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'][$gtmFinality]) + && $gtmConsents[(int)$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'][$gtmFinality]) { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = true; + } else { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = false; + } + + } else { + $gtm[(int)$cookiesPlusFinality['id_cookiesplus_finality']][$gtmFinality] = false; + } + } + } + } + + if (!empty($gtm)) { + $this->context->smarty->assign([ + 'gtm' => call_user_func_array('array_merge', $gtm), + ]); + } + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/gtm_consentmode.tpl'); + } + + if (Configuration::get('C_P_GTM_ENABLE')) { + $html .= Configuration::get('C_P_GTM_HEAD'); + } elseif (Configuration::get('C_P_GTM_FIRE_CONSENT')) { + $random = Tools::substr(md5(microtime()), 0, 10); + $divName = 'hookDisplayHeader' . $this->id . '_' . $random; + + $this->context->smarty->assign([ + 'divName' => $divName, + 'id_module' => $this->id, + 'finalities' => implode(',', array_keys(json_decode(Configuration::get('C_P_GTM_FIRE_CONSENT'), true)) ?: []), + 'script' => json_encode(Configuration::get('C_P_GTM_HEAD')), + 'js' => '[]', + 'css' => '[]', + ]); + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/hookmoduledata.tpl'); + } + } + + return $html; + } + + public function hookDisplayAfterBodyOpeningTag() + { + $html = ''; + + if (Configuration::get('C_P_GTM_ENABLE')) { + $html .= Configuration::get('C_P_GTM_BODY'); + } elseif (Configuration::get('C_P_GTM_FIRE_CONSENT')) { + $random = Tools::substr(md5(microtime()), 0, 10); + $divName = 'hookDisplayAfterBodyOpeningTag_' . $this->id . '_' . $random; + + $this->context->smarty->assign([ + 'divName' => $divName, + 'id_module' => $this->id, + 'finalities' => implode(',', array_keys(json_decode(Configuration::get('C_P_GTM_FIRE_CONSENT'), true)) ?: []), + 'script' => json_encode(Configuration::get('C_P_GTM_HEAD')), + 'js' => '[]', + 'css' => '[]', + ]); + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/hookmoduledata.tpl'); + } + + return $html; + } + + public function hookDisplayCookiesHeader() + { + $this->hookDisplayHeader(); + } + + public function hookFooter() + { + $html = null; + + $cookiesPlusPreferences = self::getCookiesPlusCookiePreferences(); + + // Don't display modal with Creative Elements editor + /*if (Tools::isSubmit('cp_type')) { + $displayModal = false; + }*/ + // Get scripts from all finalities + $script = []; + $scriptNot = []; + $cookies = []; + $gtm = []; + $fb = []; + if (Configuration::get('C_P_GTM_ENABLE')) { + $gtmConsents = json_decode(Configuration::get('C_P_GTM_CONSENT'), true); + } + if (Configuration::get('C_P_FB_ENABLE')) { + $fbConsents = json_decode(Configuration::get('C_P_FB_CONSENT'), true); + } + + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities((int) $this->context->language->id, true); + // $atLeastOneFinalityNonTechnical = false; + foreach ($cookiesPlusFinalities as &$cookiesPlusFinality) { + $cookiesPlusFinality['cookies'] = CookiesPlusCookie::getCookiesPlusCookies($cookiesPlusFinality['id_cookiesplus_finality'], (int) $this->context->language->id, true, $this->context->shop->id); + /*if ($cookiesPlusFinality['active'] && !$cookiesPlusFinality['technical']) { + $atLeastOneFinalityNonTechnical = true; + }*/ + if ($cookiesPlusFinality['js_script']) { + // Strip #', '$1', $cookiesPlusFinality['js_script']); + + // Escape all chars + // $cookiesPlusFinality['js_script'] = str_replace('"', "'", $cookiesPlusFinality['js_script']); + + /*$escapers = array("\\", "/", "\"", "\n", "\r", "\t", "\x08", "\x0c", "'",); + $replacements = array("\\\\", "\\/", "\\\"", "\\n", "\\r", "\\t", "\\f", "\\b", "\'"); + $cookiesPlusFinality['js_script'] = str_replace($escapers, $replacements, $cookiesPlusFinality['js_script']);*/ + // $cookiesPlusFinality['js_script'] = str_replace("\r", "\\r", $cookiesPlusFinality['js_script']); + // $cookiesPlusFinality['js_script'] = str_replace("\n", "\\n", $cookiesPlusFinality['js_script']); + + // remove comments + // $cookiesPlusFinality['js_script'] = preg_replace('//', '', $cookiesPlusFinality['js_script']); + + // remove tabs, spaces, newlines, etc. + // $cookiesPlusFinality['js_script'] = str_replace(array(PHP_EOL, "\t"), '', $cookiesPlusFinality['js_script']); + // $cookiesPlusFinality['js_script'] = preg_replace('/\v(?:[\v\h]+)/', '', $cookiesPlusFinality['js_script']); + /*$cookiesPlusFinality['js_script'] = str_replace("\n", '', $cookiesPlusFinality['js_script']); + $cookiesPlusFinality['js_script'] = str_replace("\r", '', $cookiesPlusFinality['js_script']); + $cookiesPlusFinality['js_script'] = str_replace("\t", '', $cookiesPlusFinality['js_script']);*/ + // remove all spaces + // $cookiesPlusFinality['js_script'] = preg_replace('|\s\s+|', ' ', $cookiesPlusFinality['js_script']); + + // Minify fails with + /*if (version_compare(_PS_VERSION_, '1.7', '>')) { + $script[(int)$cookiesPlusFinality['id_cookiesplus_finality']] = JSMin::minify($cookiesPlusFinality['js_script']); + } else { + $script[(int)$cookiesPlusFinality['id_cookiesplus_finality']] = $cookiesPlusFinality['js_script']; + }*/ + $script[(int) $cookiesPlusFinality['id_cookiesplus_finality']] = $cookiesPlusFinality['js_script']; + } + + if ($cookiesPlusFinality['js_not_script']) { + $scriptNot[(int) $cookiesPlusFinality['id_cookiesplus_finality']] = $cookiesPlusFinality['js_not_script']; + } + + if ($cookiesPlusFinality['cookies']) { + $cookies[(int) $cookiesPlusFinality['id_cookiesplus_finality']] = $cookiesPlusFinality['cookies']; + } + + if (Configuration::get('C_P_GTM_ENABLE')) { + if ($cookiesPlusFinality['technical']) { + continue; + } + + if (isset($gtmConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']])) { + if (isset($gtmConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality']) + && $gtmConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality']) { + $gtm[(int) $cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality'] = $gtmConsents[$cookiesPlusFinality['id_cookiesplus_finality']]['gtmFinality']; + $gtm[(int) $cookiesPlusFinality['id_cookiesplus_finality']]['firingEvent'] = $gtmConsents[$cookiesPlusFinality['id_cookiesplus_finality']]['firingEvent']; + } + } + } + + if (Configuration::get('C_P_FB_ENABLE')) { + if ($cookiesPlusFinality['technical']) { + continue; + } + + if (isset($fbConsents[(int) $cookiesPlusFinality['id_cookiesplus_finality']])) { + $fb[(int) $cookiesPlusFinality['id_cookiesplus_finality']] = 'true'; + } + } + } + unset($cookiesPlusFinality); + + // If there's only technical cookies, there's no need to display the warning + /*if (!$atLeastOneFinalityNonTechnical) { + $displayModal = false; + }*/ + $script = json_encode($script); + $script = self::sanitizeJson($script); + + $scriptNot = json_encode($scriptNot); + $scriptNot = self::sanitizeJson($scriptNot); + + $cookies = json_encode($cookies); + $cookies = self::sanitizeJson($cookies); + + $gtm = json_encode($gtm); + $gtm = self::sanitizeJson($gtm); + + $fb = json_encode($fb); + $fb = self::sanitizeJson($fb); + + /*$cookie = array(); + if (isset($_COOKIE['cookiesplus'])) { + $cookie = json_decode($_COOKIE['cookiesplus'], true); + } + + $cookieExpiryTime = time() + Configuration::get('C_P_EXPIRY') * 86400; + setcookie('cookiesplus', json_encode($cookie), $cookieExpiryTime, '/'); +*/ + $this->context->smarty->assign([ + 'C_P_REFRESH' => Configuration::get('C_P_REFRESH'), + 'C_P_EXPIRY' => Configuration::get('C_P_EXPIRY') ?: 365, + 'C_P_CMS_PAGE' => (int) Configuration::get('C_P_CMS_PAGE'), + 'C_P_DATE' => date('Y-m-d H:i', time()), + 'C_P_COOKIE_VALUE_JSON' => $cookiesPlusPreferences ? json_encode($cookiesPlusPreferences) : '{}', // empty JSON + 'C_P_OVERLAY' => Configuration::get('C_P_OVERLAY'), + 'C_P_OVERLAY_OPACITY' => Configuration::get('C_P_OVERLAY_OPACITY'), + 'C_P_NOT_AVAILABLE_OUTSIDE_EU' => self::getGeo(), // Don't display modal outside EU + 'C_P_FINALITIES_COUNT' => count($cookiesPlusFinalities), + 'C_P_SCRIPT' => $script, + 'C_P_SCRIPT_NOT' => $scriptNot, + 'C_P_COOKIES' => $cookies, + 'C_P_GTM' => $gtm, + 'C_P_FB' => $fb, + ]); + + $html .= $this->context->smarty->fetch($this->local_path . 'views/templates/hook/cookies-notice-vars.tpl'); + + if (!self::executeModule()) { + return $html; + } + + $cpClass = ''; + if (Configuration::get('C_P_WIDTH') == '100') { + $cpClass = 'col-12 col-xs-12'; + } elseif (Configuration::get('C_P_WIDTH') == '75') { + if (Configuration::get('C_P_BUTTON_POSITION') == '2') { + $cpClass = 'col-11 col-xs-11 col-md-9'; + } else { + $cpClass = 'col-12 col-xs-12 col-md-9'; + } + } elseif (Configuration::get('C_P_WIDTH') == '50') { + if (Configuration::get('C_P_BUTTON_POSITION') == '2') { + $cpClass = 'col-11 col-xs-11 col-md-9 col-xl-6'; + } else { + $cpClass = 'col-12 col-xs-12 col-md-9 col-lg-6'; + } + } elseif ((int) Configuration::get('C_P_WIDTH') === 25) { + $cpClass = 'col-12 col-xs-12 col-md-6 col-lg-4 col-xl-3'; + } + + if ($this->context->language->id) { + $idLang = $this->context->language->id; + } elseif ($this->context->cookie->id_lang) { + $idLang = $this->context->cookie->id_lang; + } else { + $idLang = (int) Configuration::get('PS_LANG_DEFAULT'); + } + + $this->context->smarty->assign([ + 'link' => Context::getContext()->link, + 'C_P_COOKIE_VALUE' => (array) $cookiesPlusPreferences, + 'C_P_POSITION' => Configuration::get('C_P_POSITION'), + 'C_P_WIDTH' => Configuration::get('C_P_WIDTH'), + 'C_P_CLASS' => $cpClass, + 'C_P_BACKGROUND_COLOR' => Configuration::get('C_P_BACKGROUND_COLOR'), + 'C_P_FONT_COLOR' => Configuration::get('C_P_FONT_COLOR'), + 'C_P_DISPLAY_TITLE' => Configuration::get('C_P_DISPLAY_TITLE'), + 'C_P_TITLE' => Configuration::get('C_P_TITLE', $idLang), + 'C_P_JS' => Configuration::get('C_P_JS'), + 'C_P_TEXT_BASIC' => Configuration::get('C_P_TEXT_BASIC', $idLang), + 'C_P_TEXT_REQUIRED' => Configuration::get('C_P_TEXT_REQUIRED', $idLang), + 'C_P_TEXT_3RDPARTY' => Configuration::get('C_P_TEXT_3RDPARTY', $idLang), + 'C_P_CMS_PAGE' => Configuration::get('C_P_CMS_PAGE'), + 'C_P_BUTTON_POSITION' => Configuration::get('C_P_BUTTON_POSITION'), + 'C_P_ACCEPT_DISPLAY' => Configuration::get('C_P_ACCEPT_DISPLAY'), + 'C_P_ACCEPT_BACKGROUND_COLOR' => Configuration::get('C_P_ACCEPT_BACKGROUND_COLOR'), + 'C_P_ACCEPT_BORDER_COLOR' => Configuration::get('C_P_ACCEPT_BORDER_COLOR'), + 'C_P_ACCEPT_FONT_COLOR' => Configuration::get('C_P_ACCEPT_FONT_COLOR'), + 'C_P_ACCEPT_FONT_SIZE' => Configuration::get('C_P_ACCEPT_FONT_SIZE'), + 'C_P_ACCEPT_PADDING' => Configuration::get('C_P_ACCEPT_PADDING'), + 'C_P_MORE_INFO_DISPLAY' => Configuration::get('C_P_MORE_INFO_DISPLAY'), + 'C_P_MORE_INFO_BACKGROUND_COLOR' => Configuration::get('C_P_MORE_INFO_BACKGROUND_COLOR'), + 'C_P_MORE_INFO_BORDER_COLOR' => Configuration::get('C_P_MORE_INFO_BORDER_COLOR'), + 'C_P_MORE_INFO_FONT_COLOR' => Configuration::get('C_P_MORE_INFO_FONT_COLOR'), + 'C_P_MORE_INFO_FONT_SIZE' => Configuration::get('C_P_MORE_INFO_FONT_SIZE'), + 'C_P_MORE_INFO_PADDING' => Configuration::get('C_P_MORE_INFO_PADDING'), + 'C_P_REJECT_DISPLAY' => Configuration::get('C_P_REJECT_DISPLAY'), + 'C_P_REJECT_BACKGROUND_COLOR' => Configuration::get('C_P_REJECT_BACKGROUND_COLOR'), + 'C_P_REJECT_BORDER_COLOR' => Configuration::get('C_P_REJECT_BORDER_COLOR'), + 'C_P_REJECT_FONT_COLOR' => Configuration::get('C_P_REJECT_FONT_COLOR'), + 'C_P_REJECT_FONT_SIZE' => Configuration::get('C_P_REJECT_FONT_SIZE'), + 'C_P_REJECT_PADDING' => Configuration::get('C_P_REJECT_PADDING'), + 'C_P_SAVE_BACKGROUND_COLOR' => Configuration::get('C_P_SAVE_BACKGROUND_COLOR'), + 'C_P_SAVE_BORDER_COLOR' => Configuration::get('C_P_SAVE_BORDER_COLOR'), + 'C_P_SAVE_FONT_COLOR' => Configuration::get('C_P_SAVE_FONT_COLOR'), + 'C_P_SAVE_FONT_SIZE' => Configuration::get('C_P_SAVE_FONT_SIZE'), + 'C_P_SAVE_PADDING' => Configuration::get('C_P_SAVE_PADDING'), + 'C_P_MATERIAL_ICONS_LIBRARY' => Configuration::get('C_P_MATERIAL_ICONS_LIBRARY'), + 'C_P_FINALITIES' => $cookiesPlusFinalities, + 'C_P_ICONS' => Configuration::get('C_P_ICONS'), + 'C_P_TAB_ENABLED' => Configuration::get('C_P_TAB_ENABLED'), + 'C_P_TAB_POSITION' => Configuration::get('C_P_TAB_POSITION'), + 'C_P_TAB_BACKGROUND_COLOR' => Configuration::get('C_P_TAB_BACKGROUND_COLOR'), + 'C_P_TAB_FONT_COLOR' => Configuration::get('C_P_TAB_FONT_COLOR'), + 'C_P_SAVE_CONSENT' => (int) Configuration::get('C_P_SAVE_CONSENT'), + 'C_P_CONSENT_HASH' => (Configuration::get('C_P_SAVE_CONSENT') && isset($cookiesPlusPreferences['consent_hash'])) ? $cookiesPlusPreferences['consent_hash'] : '', + 'C_P_CONSENT_DATE' => isset($cookiesPlusPreferences['consent_date']) ? $cookiesPlusPreferences['consent_date'] : '', + 'C_P_REVOKE_CONSENT' => Tools::displayDate(date('Y-m-d', strtotime(Configuration::get('C_P_REVOKE_CONSENT'))), null, false), + 'C_P_DISPLAY_DATE' => Configuration::get('C_P_DISPLAY_DATE'), + 'C_P_DEFAULT_CONSENT' => Configuration::get('C_P_DEFAULT_CONSENT'), + 'download_link' => isset($cookiesPlusPreferences['consent_hash']) ? $this->context->link->getModuleLink('cookiesplus', 'front') . '?hash=' . $cookiesPlusPreferences['consent_hash'] . '&getPdf' : '', + ]); + + $html .= $this->display(__FILE__, 'cookies-notice.tpl'); + + return $html; + } + + public function hookDisplayMobileHeader() + { + return $this->hookDisplayHeader(); + } + + public function hookDisplayFooterLinks() + { + return $this->hookFooter(); + } + + public function hookDisplayBeforeBodyClosingTag() + { + return $this->hookFooter(); + } + + public function hookTmMegaLayoutFooter() + { + return $this->hookFooter(); + } + + public function hookBlockFooter1() + { + return $this->hookFooter(); + } + + public function hookDisplayFooterBefore() + { + return $this->hookFooter(); + } + + public function hookDisplayFooterAfter() + { + return $this->hookFooter(); + } + + public function hookDisplaySidebar() + { + return $this->hookFooter(); + } + + public function hookDisplayFooterNovOne() + { + return $this->hookFooter(); + } + + public function hookDisplayFooterNovTwo() + { + return $this->hookFooter(); + } + + public function hookDisplayBanner() + { + return $this->hookFooter(); + } + + public function hookDisplayCookies() + { + return $this->hookFooter(); + } + + public function hookDisplayMyAccountBlock() + { + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + if (version_compare(_PS_VERSION_, '1.7', '>=')) { + return $this->hookDisplayMyAccountBlockFooter(); + } + } + + public function hookDisplayMyAccountBlockFooter() + { + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + if (Configuration::get('C_P_ENABLE')) { + if (version_compare(_PS_VERSION_, '1.6', '<')) { + return $this->display(__FILE__, 'my-account-block-footer-15.tpl'); + } + + return $this->context->smarty->fetch($this->local_path . 'views/templates/hook/my-account-block-footer-17.tpl'); + } + } + + public function hookDisplayCustomerAccount() + { + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + if (Configuration::get('C_P_ENABLE')) { + if (version_compare(_PS_VERSION_, '1.6', '<')) { + return $this->display(__FILE__, 'customer_account_15.tpl'); + } + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + return $this->display(__FILE__, 'customer_account_16.tpl'); + } + + $this->context->smarty->assign([ + 'C_P_MATERIAL_ICONS_LIBRARY' => Configuration::get('C_P_MATERIAL_ICONS_LIBRARY'), + ]); + + return $this->display(__FILE__, 'customer_account_17.tpl'); + } + + return false; + } + + public function hookDisplayNav() + { + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + if (Configuration::get('C_P_ENABLE')) { + if (version_compare(_PS_VERSION_, '1.6', '<')) { + return $this->display(__FILE__, 'nav_16.tpl'); + } + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + return $this->display(__FILE__, 'nav_16.tpl'); + } + + return $this->display(__FILE__, 'nav_17.tpl'); + } + + return false; + } + + public function hookDisplayNav2() + { + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + return $this->hookDisplayNav(); + } + + public function hookDisplayTop() + { + return $this->hookFooter(); + } + + public function hookDisplayBackOfficeHeader() + { + if (version_compare(_PS_VERSION_, '1.7', '<') + && method_exists($this->context->controller, 'addCSS')) { + $this->context->controller->addCSS($this->_path . 'views/css/menuTabIcon.css'); + } + + // Remove expired CookiesPlusUserConsent + $expiredCookiesPlusUserConsents = CookiesPlusUserConsent::getCookiesPlusUserConsentExpired($this->context->shop->id); + foreach ($expiredCookiesPlusUserConsents as $expiredCookiesPlusUserConsent) { + $expiredCookiesPlusUserConsent = new CookiesPlusUserConsent((int) $expiredCookiesPlusUserConsent['id_cookiesplus_user_consent']); + $expiredCookiesPlusUserConsent->delete(); + } + } + + public function hookActionHtaccessCreate() + { + $path = _PS_ROOT_DIR_ . '/.htaccess'; + + $specific_before = $specific_after = ''; + if (file_exists($path)) { + $content = Tools::file_get_contents($path); + if (preg_match('#^(.*)\# ~~startcookiesplus~~.*\# ~~endcookiesplus~~[^\n]*(.*)$#s', $content, $m)) { + $specific_before = $m[1]; + $specific_after = $m[2]; + } else { + $specific_before = $content; + } + } + + // Write .htaccess data + if (!$write_fd = @fopen($path, 'w')) { + return false; + } + + if (method_exists('Module', 'resetStaticCache')) { + Module::resetStaticCache(); + } + + if (self::isEnabled($this->name)) { + // https://www.imd.guru/sistemas/html/evitar_que_enlacen_directamente_a_imagenes_en_tu_web-Hotlinking.html + fwrite($write_fd, "# ~~startcookiesplus~~ Cookies Plus module - Do not remove this comment\n"); + fwrite($write_fd, "\n"); + fwrite($write_fd, "RewriteRule .* - [E=Cache-Vary:cookiesplus]\n"); + fwrite($write_fd, "\n"); + fwrite($write_fd, "# ~~endcookiesplus~~ Cookies Plus module - Do not remove this comment\n\n"); + } + + if ($specific_before) { + fwrite($write_fd, trim($specific_before) . "\n\n"); + } + + if ($specific_after) { + fwrite($write_fd, "\n\n" . trim($specific_after)); + } + + fclose($write_fd); + + return true; + } + + /** + * empty listener for registerGDPRConsent hook + */ + public function hookRegisterGDPRConsent() + { + /* registerGDPRConsent is a special kind of hook that doesn't need a listener, see : + https://build.prestashop.com/howtos/module/how-to-make-your-module-compliant-with-prestashop-official-gdpr-compliance-module/ + However since Prestashop 1.7.8, modules must implement a listener for all the hooks they register: a check is made + at module installation. + */ + } + + public function hookActionShopDataDuplication($params) + { + $cookiesPlusCookies = Db::getInstance()->executeS( + 'SELECT * FROM ' . _DB_PREFIX_ . 'cookiesplus_cookie + WHERE id_shop = ' . (int) $params['old_id_shop'] + ); + + foreach ($cookiesPlusCookies as $id => $cookiesPlusCookie) { + Db::getInstance()->execute(' + INSERT IGNORE INTO ' . _DB_PREFIX_ . 'cookiesplus_cookie (id_cookiesplus_cookie, id_shop, active, id_cookiesplus_finality, name, provider, provider_url, date_add, date_upd) + VALUES (null, ' . (int) $params['new_id_shop'] . ', ' . (int) $cookiesPlusCookie['active'] . ', ' . (int) $cookiesPlusCookie['id_cookiesplus_finality'] . ', \'' . pSQL($cookiesPlusCookie['name']) . '\', \'' . pSQL($cookiesPlusCookie['provider']) . '\', \'' . pSQL($cookiesPlusCookie['provider_url']) . '\', \'' . date('Y-m-d H:i:s') . '\', \'' . date('Y-m-d H:i:s') . '\')'); + + $cookiesPlusCookies[$id]['new_id_cookiesplus_cookie'] = Db::getInstance()->Insert_ID(); + } + + foreach ($cookiesPlusCookies as $cookiesPlusCookie) { + $languages = Db::getInstance()->executeS(' + SELECT id_lang, purpose, expiry + FROM ' . _DB_PREFIX_ . 'cookiesplus_cookie_lang + WHERE id_cookiesplus_cookie = ' . (int) $cookiesPlusCookie['id_cookiesplus_cookie']); + + foreach ($languages as $language) { + Db::getInstance()->execute(' + INSERT IGNORE INTO ' . _DB_PREFIX_ . 'cookiesplus_cookie_lang (id_cookiesplus_cookie, id_lang, purpose, expiry) + VALUES (' . (int) $cookiesPlusCookie['new_id_cookiesplus_cookie'] . ', ' . (int) $language['id_lang'] . ', \'' . pSQL($language['purpose']) . '\', \'' . pSQL($language['expiry']) . '\')'); + } + } + + $cookiesPlusFinalities = Db::getInstance()->executeS( + 'SELECT * FROM ' . _DB_PREFIX_ . 'cookiesplus_finality + WHERE id_shop = ' . (int) $params['old_id_shop'] + ); + + foreach ($cookiesPlusFinalities as $id => $cookiesPlusFinality) { + Db::getInstance()->execute(' + INSERT IGNORE INTO ' . _DB_PREFIX_ . 'cookiesplus_finality (id_cookiesplus_finality, id_shop, active, technical, modules, js_script, js_not_script, position, date_add, date_upd) + VALUES (null, ' . (int) $params['new_id_shop'] . ', ' . (int) $cookiesPlusFinality['active'] . ', ' . (int) $cookiesPlusFinality['technical'] . ', \'' . pSQL($cookiesPlusFinality['modules']) . '\', \'' . pSQL($cookiesPlusFinality['js_script']) . '\', \'' . pSQL($cookiesPlusFinality['js_not_script']) . '\', ' . (int) $cookiesPlusFinality['position'] . ', \'' . date('Y-m-d H:i:s') . '\', \'' . date('Y-m-d H:i:s') . '\')'); + + $cookiesPlusFinalities[$id]['new_id_cookiesplus_finality'] = Db::getInstance()->Insert_ID(); + } + + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $languages = Db::getInstance()->executeS(' + SELECT id_lang, name, description + FROM ' . _DB_PREFIX_ . 'cookiesplus_finality_lang + WHERE id_cookiesplus_finality = ' . (int) $cookiesPlusFinality['id_cookiesplus_finality']); + + foreach ($languages as $language) { + Db::getInstance()->execute(' + INSERT IGNORE INTO ' . _DB_PREFIX_ . 'cookiesplus_finality_lang (id_cookiesplus_finality, id_lang, name, description) + VALUES (' . (int) $cookiesPlusFinality['new_id_cookiesplus_finality'] . ', ' . (int) $language['id_lang'] . ', \'' . pSQL($language['name']) . '\', \'' . pSQL($language['description']) . '\')'); + } + } + } + + public function hookActionOutputHTMLBefore($params) + { + if (!self::executeModule()) { + return; + } + + if (Configuration::get('C_P_FB_ENABLE')) { + $cookiesPlusCookiePreferences = self::getCookiesPlusCookiePreferences(); + $fbConsents = json_decode(Configuration::get('C_P_FB_CONSENT'), true) ?: []; + $fbAllConsent = true; + foreach (array_keys($fbConsents) as $fbConsent) { + $key = 'cookiesplus-finality-' . (int) $fbConsent; + if (!isset($cookiesPlusCookiePreferences[$key]) + || $cookiesPlusCookiePreferences[$key] !== 'on') { + $fbAllConsent = false; + break; + } + } + + $position = strpos($params['html'], "fbq('init'"); + + if ($position) { + if ($fbAllConsent) { + $params['html'] = substr_replace($params['html'], "fbq('consent', 'grant');", $position, 0); + } else { + $params['html'] = substr_replace($params['html'], "fbq('consent', 'revoke');", $position, 0); + } + } + } + + if (Configuration::get('C_P_YT_ENABLE')) { + $cookiesPlusCookiePreferences = self::getCookiesPlusCookiePreferences(); + $ytConsents = json_decode(Configuration::get('C_P_YT_CONSENT'), true) ?: []; + $ytAllConsent = true; + foreach (array_keys($ytConsents) as $ytConsents) { + $key = 'cookiesplus-finality-' . (int) $ytConsents; + if (!isset($cookiesPlusCookiePreferences[$key]) + || $cookiesPlusCookiePreferences[$key] !== 'on') { + $ytAllConsent = false; + break; + } + } + + if (!$ytAllConsent) { + $params['html'] = str_replace('youtube.com/embed/', 'youtube-nocookie.com/embed/', $params['html']); + // Elementor + $params['html'] = str_replace('data-video-id=', 'data-video-id-blocked=', $params['html']); + } + } + } + + /* Module functions */ + /* Backward compatibility */ + public static function updateCookie($modules) + { + return self::filterHookModuleExecList($modules); + } + + public static function filterHookModuleExecList($modules, $hook_name = null) + { + // return $modules; + if (!self::executeModule()) { + return $modules; + } + + if (!self::getGeo()) { + return $modules; + } + + // Exclude admin calls + /* + if (defined('_PS_ADMIN_DIR_')) { + return $modules; + } + */ + if (is_object(Context::getContext()->controller) + && isset(Context::getContext()->controller->controller_type) + && Context::getContext()->controller->controller_type === 'admin') { + return $modules; + } + + // Exclude .map extensions + $url = parse_url("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + if (isset($url['path']) && pathinfo($url['path'], PATHINFO_EXTENSION) === 'map') { + return $modules; + } + $url = parse_url("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + if (isset($url['path']) && pathinfo($url['path'], PATHINFO_EXTENSION) === 'map') { + return $modules; + } + + $cookiesPlusPreferences = self::getCookiesPlusCookiePreferences(); + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities(null, true); + + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + if (!$cookiesPlusFinality['technical'] + && (!isset($cookiesPlusPreferences['cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']]) + || (isset($cookiesPlusPreferences['cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']]) + && $cookiesPlusPreferences['cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']] !== 'on'))) { + $blockedModulesId = json_decode($cookiesPlusFinality['modules'], true) ?: []; + + if (is_array($modules) && is_array($blockedModulesId)) { + foreach ($modules as $key => $module) { + // Cookiesplus module can not be blocked + if ($module['module'] === 'cookiesplus') { + continue; + } + + if (in_array($module['id_module'], $blockedModulesId)) { + unset($modules[$key]); + } + } + } + } + } + + return $modules; + } + + public function blockModuleCode($params) + { + // Recursive call + if (!self::executeModule()) { + return; + } + + if (!self::getGeo()) { + return; + } + + // Exclude admin calls + /* + if (defined('_PS_ADMIN_DIR_')) { + return $modules; + } + */ + $context = Context::getContext(); + + if (!$context->controller) { + return; + } + + if (is_object($context->controller) + && isset($context->controller->controller_type) + && ($context->controller->controller_type === 'admin' + || $context->controller->controller_type === 'moduleadmin')) { + return; + } + + // Exclude .map extensions + $url = parse_url("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + if (isset($url['path']) && pathinfo($url['path'], PATHINFO_EXTENSION) === 'map') { + return; + } + $url = parse_url("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); + if (isset($url['path']) && pathinfo($url['path'], PATHINFO_EXTENSION) === 'map') { + return; + } + + $blockedModulesByFinality = self::getBlockedModulesByFinality(); + + if (version_compare(_PS_VERSION_, '1.6.1', '>=')) { + return $this->blockModuleCode17($params, $context, $blockedModulesByFinality); + } + + return $this->blockModuleCode15($params, $context, $blockedModulesByFinality); + } + + public function getBlockedModulesByFinality() + { + $cacheKey = 'CookiesPlus::blockModuleCode'; + + if (!Cache::isStored($cacheKey)) { + $cookiesPlusPreferences = self::getCookiesPlusCookiePreferences(); + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities(null, true); + + $blockedModulesByFinality = []; + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $index = 'cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']; + if (!$cookiesPlusFinality['technical'] + && (!isset($cookiesPlusPreferences[$index]) + || (isset($cookiesPlusPreferences[$index]) + && $cookiesPlusPreferences[$index] !== 'on')) + ) { + $blockedModulesId = json_decode($cookiesPlusFinality['modules'], true) ?: []; + + if (is_array($blockedModulesId)) { + foreach ($blockedModulesId as $module) { + // Cookiesplus module can not be blocked + if ($module === $this->id) { + continue; + } + + $blockedModulesByFinality[(int) $module]['finalities'][] = (int) $cookiesPlusFinality['id_cookiesplus_finality']; + } + } + } + } + + Cache::store($cacheKey, $blockedModulesByFinality); + } + + return Cache::retrieve($cacheKey); + } + + public function blockModuleCode17($params, $context, $blockedModulesByFinality) + { + if (isset($blockedModulesByFinality[$params['module']->id])) { + // Remove JS and CSS files from blocked modules + $js_files = []; + $css_files = []; + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + $jsFileList = $context->controller->js_files; + foreach ($jsFileList as $jsFile) { + if (strpos($jsFile, '/modules/' . $params['module']->name) !== false) { + $js_files[] = $jsFile; + $context->controller->removeJs($jsFile); + } + } + + $cssFileList = $context->controller->css_files; + foreach ($cssFileList as $cssFile) { + if (strpos($cssFile, '/modules/' . $params['module']->name) !== false) { + $css_files[] = $cssFile; + $context->controller->removeJs($cssFile); + } + } + } else { + $jsFileList = $context->controller->getJavascript(); + foreach ($jsFileList as $jsFileListPart) { + foreach ($jsFileListPart as $jsFileListPartContainer) { + foreach ($jsFileListPartContainer as $jsFileListPartContainerFile) { + if (strpos($jsFileListPartContainerFile['path'], '/modules/' . $params['module']->name) !== false) { + $js_files[] = $jsFileListPartContainerFile['path']; + $context->controller->removeJs($jsFileListPartContainerFile['path']); + $context->controller->unregisterJavascript($jsFileListPartContainerFile['id']); + } + } + } + } + + $cssFileList = $context->controller->getStylesheets(); + foreach ($cssFileList as $cssFileListPartContainer) { + foreach ($cssFileListPartContainer as $cssFileListPartContainerFile) { + if (strpos($cssFileListPartContainerFile['path'], '/modules/' . $params['module']->name) !== false) { + $css_files[] = $cssFileListPartContainerFile['path']; + $context->controller->removeCSS($cssFileListPartContainerFile['path']); + $context->controller->unregisterStylesheet($jsFileListPartContainerFile['id']); + } + } + } + } + + // Remove cookies + if (isset($params['headersBeforeExecution']) && $params['headersBeforeExecution']) { + // Remove the original headers + header_remove(); + + // Set old headers + foreach ($params['headersBeforeExecution'] as $header) { + header($header, false); + } + } + + if (Configuration::get('C_P_REFRESH')) { + // The module is blocked but with refresh. Don't display any content + $params['display'] = ''; + } else { + if ($params['display']) { + $originalReturn = $params['display']; + $random = Tools::substr(md5(microtime()), 0, 10); + $divName = $params['hookName'] . '_' . $params['module']->id . '_' . $random; + + $this->context->smarty->assign([ + 'divName' => $divName, + 'id_module' => $params['module']->id, + 'finalities' => implode(',', $blockedModulesByFinality[$params['module']->id]['finalities']), + 'script' => json_encode($originalReturn), + 'js' => empty($js_files) ? '[]' : json_encode($js_files), + 'css' => empty($css_files) ? '[]' : json_encode($css_files), + ]); + + $params['display'] = $this->context->smarty->fetch($this->local_path . 'views/templates/hook/hookmoduledata.tpl'); + } + } + } + } + + public function blockModuleCode15($params, $context, $blockedModulesByFinality) + { + if (!is_array($params['return'])) { + return; + } + + foreach (array_keys($params['return']) as $module) { + $module = Module::getInstanceByName($module); + if (isset($blockedModulesByFinality[$module->id])) { + // Remove JS and CSS files from blocked modules + $js_files = []; + $css_files = []; + + if (version_compare(_PS_VERSION_, '1.7', '<')) { + $jsFileList = $context->controller->js_files; + foreach ($jsFileList as $jsFile) { + if (strpos($jsFile, '/modules/' . $module->name) !== false) { + $js_files[] = $jsFile; + $context->controller->removeJs($jsFile); + } + } + + $cssFileList = $context->controller->css_files; + foreach ($cssFileList as $cssFile) { + if (strpos($cssFile, '/modules/' . $module->name) !== false) { + $css_files[] = $cssFile; + $context->controller->removeJs($cssFile); + } + } + } else { + $jsFileList = $context->controller->getJavascript(); + foreach ($jsFileList as $jsFileListPart) { + foreach ($jsFileListPart as $jsFileListPartContainer) { + foreach ($jsFileListPartContainer as $jsFileListPartContainerFile) { + if (strpos($jsFileListPartContainerFile['path'], '/modules/' . $module->name) !== false) { + $js_files[] = $jsFileListPartContainerFile['path']; + $context->controller->removeJs($jsFileListPartContainerFile['path']); + } + } + } + } + + $cssFileList = $context->controller->getStylesheets(); + foreach ($cssFileList as $cssFileListPartContainer) { + foreach ($cssFileListPartContainer as $cssFileListPartContainerFile) { + if (strpos($cssFileListPartContainerFile['path'], '/modules/' . $module->name) !== false) { + $css_files[] = $cssFileListPartContainerFile['path']; + $context->controller->removeCSS($cssFileListPartContainerFile['path']); + } + } + } + } + + if (Configuration::get('C_P_REFRESH')) { + $params['return'][$module->name] = ''; + } else { + $originalReturn = $params['return'][$module->name]; + $random = Tools::substr(md5(microtime()), 0, 10); + $divName = $params['hookName'] . '_' . $module->id . '_' . $random; + + $this->context->smarty->assign([ + 'divName' => $divName, + 'id_module' => $module->id, + 'finalities' => implode(',', $blockedModulesByFinality[$module->id]['finalities']), + 'script' => json_encode($originalReturn), + 'js' => empty($js_files) ? '[]' : json_encode($js_files), + 'css' => empty($css_files) ? '[]' : json_encode($css_files), + ]); + + $params['return'][$module->name] = $this->context->smarty->fetch($this->local_path . 'views/templates/hook/hookmoduledata.tpl'); + } + } + } + } + + public function blockModuleCache($modulesToInvoke, $hookName) + { + if (empty($modulesToInvoke)) { + return false; + } + + // Don't filter in BO + $context = Context::getContext(); + if (isset($context->controller->controller_type) && $context->controller->controller_type === 'admin') { + return $modulesToInvoke; + } + + if (!Configuration::get('C_P_REFRESH')) { + return $modulesToInvoke; + } + + $cookiesPlusPreferences = self::getCookiesPlusCookiePreferences(); + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities(null, true); + + $blockedModulesByFinality = []; + foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $index = 'cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']; + if (!$cookiesPlusFinality['technical'] + && (!isset($cookiesPlusPreferences[$index]) + || (isset($cookiesPlusPreferences[$index]) + && $cookiesPlusPreferences[$index] !== 'on')) + ) { + $blockedModulesId = json_decode($cookiesPlusFinality['modules'], true) ?: []; + + if (is_array($blockedModulesId)) { + foreach ($blockedModulesId as $module) { + // Cookiesplus module can not be blocked + if ($module === $this->id) { + continue; + } + + $blockedModulesByFinality[(int) $module]['finalities'][] = (int) $cookiesPlusFinality['id_cookiesplus_finality']; + } + } + } + } + + if (null === $hookName) { + foreach ($modulesToInvoke as $modulesToInvokeByHook) { + foreach ($modulesToInvokeByHook as $moduleToInvokeKey => $moduleToInvoke) { + if (in_array($moduleToInvoke['id_module'], array_keys($blockedModulesByFinality))) { + unset($modulesToInvoke[$moduleToInvokeKey]); + } + } + } + } else { + foreach ($modulesToInvoke as $moduleToInvokeKey => $moduleToInvoke) { + if (in_array($moduleToInvoke['id_module'], array_keys($blockedModulesByFinality))) { + unset($modulesToInvoke[$moduleToInvokeKey]); + } + } + } + + return $modulesToInvoke; + } + + public function resetCookiesPlusPreferences() + { + $cookieExpiryTime = time() + Configuration::get('C_P_EXPIRY') * 86400; + + $result = setcookie('cookiesplus', json_encode([]), $cookieExpiryTime, '/'); + + return true; + } + + public function saveCookiesPlusPreferences() + { + // $cookiesPlusFinalityValue = self::getCookiesPlusCookiePreferences(); + + $cookiesPlusFinalityValue = []; + // $cookiesPlusFinalityValue['C_P_DISPLAY_MODAL'] = false; + + if (!empty($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + + /*foreach ($cookiesPlusFinalities as $cookiesPlusFinality) { + $cookiesPlusFinalityValue['cookiesplus-finality-' . (int)$cookiesPlusFinality['id_cookiesplus_finality']] = Tools::getValue('cookiesplus-finality-' . (int)$cookiesPlusFinality['id_cookiesplus_finality']); + }*/ + // $cookieExpiryTime = time() + Configuration::get('C_P_EXPIRY') * 86400; + // $cookiesPlusFinalityValue['expiry'] = time() + Configuration::get('C_P_EXPIRY') * 86400; + // $cookiesPlusFinalityValue['consent_date'] = date('Y-m-d H:i:s', time()); + // $result = setcookie('cookiesplus', json_encode($cookiesPlusFinalityValue), $cookieExpiryTime, '/'); + + // $cookiesPlusFinalityValue['cookie'] = json_encode($cookiesPlusFinalityValue); + + // Generate PDF consent + if (Configuration::get('C_P_SAVE_CONSENT')) { + do { + $consentHash = md5(openssl_random_pseudo_bytes(20)) . '-' . Tools::substr(md5(openssl_random_pseudo_bytes(20)), 0, 8); + } while (!$consentHash); + $cookiesPlusFinalityValue['consent_hash'] = $consentHash; + $consentDate = date('Y-m-d H:i', time()); + + $data = []; + $data['cookiesPlus']['info']['last_update'] = Tools::displayDate(date('Y-m-d', strtotime(Configuration::get('C_P_REVOKE_CONSENT'))), null, false); + $data['cookiesPlus']['info']['consent_hash'] = $cookiesPlusFinalityValue['consent_hash']; + $data['cookiesPlus']['info']['consent_date'] = $consentDate; + $data['cookiesPlus']['info']['consent_ip'] = $ip; + + $cookiesPlusFinalities = CookiesPlusFinality::getCookiesPlusFinalities((int) $this->context->language->id, true); + foreach ($cookiesPlusFinalities as &$cookiesPlusFinality) { + $cookiesPlusFinality['cookies'] = CookiesPlusCookie::getCookiesPlusCookies($cookiesPlusFinality['id_cookiesplus_finality'], (int) $this->context->language->id, true, $this->context->shop->id); + if (Tools::getValue('cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']) !== 'na' + && Tools::getValue('cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']) !== 'on' + && Tools::getValue('cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']) !== 'off' + ) { + $_POST['cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']] = 'na'; + } + $cookiesPlusFinality['cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']] = Tools::getValue('cookiesplus-finality-' . (int) $cookiesPlusFinality['id_cookiesplus_finality']); + } + unset($cookiesPlusFinality); + $data['cookiesPlus']['cookiesPlusFinalities'] = $cookiesPlusFinalities; + + // Send an email to admin because of an error + /*if (!$result) { + Configuration::updateValue('C_P_SAVE_CONSENT', 0); + }*/ + // Save consent + $cookiesPlusUserConsent = new CookiesPlusUserConsent(); + $cookiesPlusUserConsent->data = json_encode($data); + $cookiesPlusUserConsent->hash = $cookiesPlusFinalityValue['consent_hash']; + $cookiesPlusUserConsent->date = $consentDate; + $cookiesPlusUserConsent->ip = $ip; + $cookiesPlusUserConsent->save(); + } + + return $cookiesPlusFinalityValue; + } + + public static function getCookiesPlusCookiePreferences() + { + if (isset($_COOKIE['cookiesplus'])) { + return json_decode($_COOKIE['cookiesplus'], true); + } + + return []; + } + + public static function isCookiesPlusFinalityAccepted($id_cookiesplus_finality) + { + $cookiesPlusCookiePreferences = self::getCookiesPlusCookiePreferences(); + + $index = 'cookiesplus-finality-' . (int) $id_cookiesplus_finality; + + if (isset($cookiesPlusCookiePreferences[$index]) + && $cookiesPlusCookiePreferences[$index] === 'on') { + return true; + } + + return false; + } + + public function copyOverrideFolder() + { + if (Module::isInstalled('pagecache')) { + return true; + } + + if (!is_writable(_PS_MODULE_DIR_ . $this->name)) { + return false; + } + + $override_folder_name = 'override'; + if (version_compare(_PS_VERSION_, '1.6.1', '>=')) { + $psVersion = '17'; + } elseif (version_compare(_PS_VERSION_, '1.6', '>=')) { + $psVersion = '16'; + } else { + $psVersion = '15'; + } + + $version_override_folder = _PS_MODULE_DIR_ . $this->name . '/' . $override_folder_name . '_' . $psVersion; + $override_folder = _PS_MODULE_DIR_ . $this->name . '/' . $override_folder_name; + + if (file_exists($override_folder) && is_dir($override_folder)) { + $this->recursiveRmdir($override_folder); + } + + if (is_dir($version_override_folder)) { + $this->copyDir($version_override_folder, $override_folder); + } + + return true; + } + + public function copyDir($src, $dst) + { + if (is_dir($src)) { + $dir = opendir($src); + if (!mkdir($dst) && !is_dir($dst)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $dst)); + } + while (false !== ($file = readdir($dir))) { + if (($file !== '.') && ($file !== '..')) { + if (is_dir($src . '/' . $file)) { + $this->copyDir($src . '/' . $file, $dst . '/' . $file); + } else { + copy($src . '/' . $file, $dst . '/' . $file); + } + } + } + closedir($dir); + } + } + + public function recursiveRmdir($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object !== '.' && $object !== '..') { + if (filetype($dir . '/' . $object) === 'dir') { + $this->recursiveRmdir($dir . '/' . $object); + } else { + unlink($dir . '/' . $object); + } + } + } + reset($objects); + rmdir($dir); + } + } + + public static function sanitizeJson($json) + { + $escapers = ['\\', '/', '"', "\n", "\r", "\t", "\x08", "\x0c", "\'"]; + $replacements = ['\\\\', '\\/', '\\"', '\\n', '\\r', '\\t', '\\f', '\\b', "\\\'"]; + + return str_replace($escapers, $replacements, $json); + } + + public function getDatabaseVersion() + { + $query = 'SELECT `version` + FROM `' . _DB_PREFIX_ . 'module` + WHERE `name` = \'' . $this->name . '\';'; + + return Db::getInstance()->getValue($query); + } +} diff --git a/modules/pp_carousel/config.xml b/modules/pp_carousel/config.xml new file mode 100644 index 0000000..782b6c2 --- /dev/null +++ b/modules/pp_carousel/config.xml @@ -0,0 +1,12 @@ + + + pp_carousel + + + + + + 1 + 0 + + diff --git a/modules/pp_carousel/index.php b/modules/pp_carousel/index.php new file mode 100644 index 0000000..8e50318 --- /dev/null +++ b/modules/pp_carousel/index.php @@ -0,0 +1,16 @@ + + * @copyright Project-Pro + * @license https://www.project-pro.pl + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); +header('Location: ../'); +exit; diff --git a/modules/pp_carousel/pp_carousel.php b/modules/pp_carousel/pp_carousel.php new file mode 100644 index 0000000..4ed4dbe --- /dev/null +++ b/modules/pp_carousel/pp_carousel.php @@ -0,0 +1,762 @@ +name = 'pp_carousel'; + $this->tab = 'front_office_features'; + $this->version = '1.0.0'; + $this->author = 'Project-Pro'; + $this->author_uri = 'https://www.project-pro.pl'; + $this->need_instance = 0; + $this->bootstrap = true; + + parent::__construct(); + + $this->displayName = $this->l('Project-Pro Karuzela Produktów'); + $this->description = $this->l('Wyświetla konfigurowalne karuzele produktów w dowolnych hookach.'); + $this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_]; + } + + public function install() + { + return $this->executeSqlFile('install') + && parent::install() + && $this->registerHook('displayHeader') + && $this->registerHook('displayHome') + && $this->registerHook('displayFooterBefore') + && $this->registerHook('displayTopColumn') + && $this->registerHook('displayLeftColumn') + && $this->registerHook('displayRightColumn') + && $this->registerHook('displayFooter'); + } + + public function uninstall() + { + return $this->executeSqlFile('uninstall') && parent::uninstall(); + } + + private function executeSqlFile($filename) + { + $path = dirname(__FILE__) . '/sql/' . $filename . '.sql'; + if (!file_exists($path)) { + return false; + } + + $sql = file_get_contents($path); + $sql = str_replace('PREFIX_', _DB_PREFIX_, $sql); + $sql = str_replace('ENGINE_TYPE', _MYSQL_ENGINE_, $sql); + + $queries = preg_split('/;\s*[\r\n]+/', $sql); + foreach ($queries as $query) { + $query = trim($query); + if (!empty($query)) { + if (!Db::getInstance()->execute($query)) { + return false; + } + } + } + + return true; + } + + // ─── ADMIN PANEL ──────────────────────────────────────────── + + public function getContent() + { + $output = ''; + + if (Tools::isSubmit('deletepp_carousel')) { + $output .= $this->deleteCarousel((int) Tools::getValue('id_carousel')); + } + + if (Tools::isSubmit('statuspp_carousel')) { + $output .= $this->toggleCarouselStatus((int) Tools::getValue('id_carousel')); + } + + if (Tools::isSubmit('submitPpCarousel')) { + $output .= $this->saveCarousel(); + } + + if (Tools::isSubmit('addpp_carousel') || Tools::isSubmit('updatepp_carousel') || Tools::getValue('id_carousel')) { + return $output . $this->renderForm((int) Tools::getValue('id_carousel')); + } + + return $output . $this->renderList(); + } + + private function renderList() + { + $fieldsList = [ + 'id_carousel' => ['title' => 'ID', 'align' => 'center', 'class' => 'fixed-width-xs'], + 'title' => ['title' => $this->l('Tytuł')], + 'hook_name' => ['title' => $this->l('Hook')], + 'source_type' => ['title' => $this->l('Źródło')], + 'limit_products' => ['title' => $this->l('Limit'), 'align' => 'center', 'class' => 'fixed-width-xs'], + 'active' => ['title' => $this->l('Aktywna'), 'active' => 'status', 'align' => 'center', 'class' => 'fixed-width-sm', 'type' => 'bool'], + ]; + + $helper = new HelperList(); + $helper->shopLinkType = ''; + $helper->simple_header = false; + $helper->actions = ['edit', 'delete']; + $helper->identifier = 'id_carousel'; + $helper->show_toolbar = true; + $helper->title = $this->l('Karuzele produktów'); + $helper->table = $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; + + $helper->toolbar_btn['new'] = [ + 'href' => AdminController::$currentIndex . '&configure=' . $this->name . '&add' . $this->name . '&token=' . Tools::getAdminTokenLite('AdminModules'), + 'desc' => $this->l('Dodaj karuzelę'), + ]; + + return $helper->generateList($this->getCarouselList(), $fieldsList); + } + + private function getCarouselList() + { + $idLang = (int) $this->context->language->id; + + $sql = 'SELECT c.*, cl.title + FROM `' . _DB_PREFIX_ . 'pp_carousel` c + LEFT JOIN `' . _DB_PREFIX_ . 'pp_carousel_lang` cl + ON c.id_carousel = cl.id_carousel AND cl.id_lang = ' . $idLang . ' + WHERE c.id_shop = ' . (int) $this->context->shop->id . ' + ORDER BY c.position ASC, c.id_carousel ASC'; + + return Db::getInstance()->executeS($sql) ?: []; + } + + private function renderForm($idCarousel = 0) + { + $carousel = $idCarousel ? $this->getCarousel($idCarousel) : []; + $defaultLang = (int) Configuration::get('PS_LANG_DEFAULT'); + $langs = Language::getLanguages(false); + $categories = $this->flattenCategories(Category::getCategories($defaultLang, true, false)); + + $hookOptions = $this->getAvailableHooks(); + + $sourceOptions = [ + ['id' => 'new', 'name' => $this->l('Nowości')], + ['id' => 'bestseller', 'name' => $this->l('Bestsellery')], + ['id' => 'category', 'name' => $this->l('Produkty z kategorii')], + ['id' => 'manual', 'name' => $this->l('Ręczne ID produktów')], + ]; + + $fieldsForm = [ + 'form' => [ + 'legend' => [ + 'title' => $idCarousel ? $this->l('Edytuj karuzelę') : $this->l('Dodaj karuzelę'), + 'icon' => 'icon-cogs', + ], + 'input' => [ + [ + 'type' => 'hidden', + 'name' => 'id_carousel', + ], + [ + 'type' => 'select', + 'label' => $this->l('Hook (miejsce wyświetlania)'), + 'name' => 'hook_name', + 'options' => ['query' => $hookOptions, 'id' => 'id', 'name' => 'name'], + 'desc' => $this->l('Wybierz hook lub wpisz niestandardowy poniżej.'), + ], + [ + 'type' => 'text', + 'label' => $this->l('Niestandardowy hook'), + 'name' => 'custom_hook', + 'desc' => $this->l('Jeśli wypełnione, zostanie użyte zamiast wybranego powyżej. Hook zostanie utworzony automatycznie.'), + ], + [ + 'type' => 'select', + 'label' => $this->l('Źródło produktów'), + 'name' => 'source_type', + 'options' => ['query' => $sourceOptions, 'id' => 'id', 'name' => 'name'], + 'id' => 'source_type_select', + ], + [ + 'type' => 'select', + 'label' => $this->l('Kategoria'), + 'name' => 'id_category', + 'options' => ['query' => $categories, 'id' => 'id', 'name' => 'name'], + 'desc' => $this->l('Widoczne gdy źródło = "Produkty z kategorii".'), + 'form_group_class' => 'pp-field-category', + ], + [ + 'type' => 'textarea', + 'label' => $this->l('ID produktów (ręczne)'), + 'name' => 'product_ids', + 'desc' => $this->l('ID produktów rozdzielone przecinkami, np. 12,45,67. Widoczne gdy źródło = "Ręczne ID".'), + 'form_group_class' => 'pp-field-manual', + ], + [ + 'type' => 'text', + 'label' => $this->l('Limit produktów'), + 'name' => 'limit_products', + 'class' => 'fixed-width-sm', + ], + [ + 'type' => 'text', + 'label' => $this->l('Tytuł'), + 'name' => 'title', + 'lang' => true, + ], + [ + 'type' => 'text', + 'label' => $this->l('Podtytuł'), + 'name' => 'subtitle', + 'lang' => true, + ], + [ + 'type' => 'text', + 'label' => $this->l('Tekst przycisku'), + 'name' => 'button_label', + 'lang' => true, + ], + [ + 'type' => 'text', + 'label' => $this->l('URL przycisku'), + 'name' => 'button_url', + 'desc' => $this->l('Pozostaw puste, aby linkować do kategorii automatycznie.'), + ], + [ + 'type' => 'text', + 'label' => $this->l('Sufiks ceny'), + 'name' => 'price_suffix', + 'lang' => true, + 'desc' => $this->l('Np. /m²'), + ], + [ + 'type' => 'text', + 'label' => $this->l('Pozycja'), + 'name' => 'position', + 'class' => 'fixed-width-sm', + ], + [ + 'type' => 'switch', + 'label' => $this->l('Aktywna'), + 'name' => 'active', + 'values' => [ + ['id' => 'active_on', 'value' => 1, 'label' => $this->l('Tak')], + ['id' => 'active_off', 'value' => 0, 'label' => $this->l('Nie')], + ], + ], + ], + 'submit' => [ + 'title' => $this->l('Zapisz'), + 'name' => 'submitPpCarousel', + ], + ], + ]; + + $helper = new HelperForm(); + $helper->show_toolbar = false; + $helper->table = $this->table; + $helper->module = $this; + $helper->default_form_language = $defaultLang; + $helper->allow_employee_form_lang = (int) Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG'); + $helper->identifier = 'id_carousel'; + $helper->submit_action = 'submitPpCarousel'; + $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->languages = $this->context->controller->getLanguages(); + $helper->id_language = (int) $this->context->language->id; + + // Fill form values + $helper->fields_value['id_carousel'] = $idCarousel; + $helper->fields_value['hook_name'] = isset($carousel['hook_name']) ? $carousel['hook_name'] : 'displayHome'; + $helper->fields_value['custom_hook'] = ''; + $helper->fields_value['source_type'] = isset($carousel['source_type']) ? $carousel['source_type'] : 'new'; + $helper->fields_value['id_category'] = isset($carousel['id_category']) ? (int) $carousel['id_category'] : 0; + $helper->fields_value['product_ids'] = isset($carousel['product_ids']) ? $carousel['product_ids'] : ''; + $helper->fields_value['limit_products'] = isset($carousel['limit_products']) ? (int) $carousel['limit_products'] : 12; + $helper->fields_value['button_url'] = isset($carousel['button_url']) ? $carousel['button_url'] : ''; + $helper->fields_value['position'] = isset($carousel['position']) ? (int) $carousel['position'] : 0; + $helper->fields_value['active'] = isset($carousel['active']) ? (int) $carousel['active'] : 1; + + foreach ($langs as $lang) { + $id = (int) $lang['id_lang']; + $langData = $idCarousel ? $this->getCarouselLang($idCarousel, $id) : []; + $helper->fields_value['title'][$id] = isset($langData['title']) ? $langData['title'] : ''; + $helper->fields_value['subtitle'][$id] = isset($langData['subtitle']) ? $langData['subtitle'] : ''; + $helper->fields_value['button_label'][$id] = isset($langData['button_label']) ? $langData['button_label'] : ''; + $helper->fields_value['price_suffix'][$id] = isset($langData['price_suffix']) ? $langData['price_suffix'] : ''; + } + + $formHtml = $helper->generateForm([$fieldsForm]); + + // Inject JS for conditional field visibility + $formHtml .= $this->getAdminFormJs(); + + return $formHtml; + } + + private function getAdminFormJs() + { + return ' + '; + } + + private function saveCarousel() + { + $idCarousel = (int) Tools::getValue('id_carousel'); + $hookName = trim(Tools::getValue('custom_hook')); + if (empty($hookName)) { + $hookName = trim(Tools::getValue('hook_name')); + } + if (empty($hookName)) { + $hookName = 'displayHome'; + } + + $sourceType = Tools::getValue('source_type'); + $idCategory = (int) Tools::getValue('id_category'); + $productIds = trim(Tools::getValue('product_ids')); + $limitProducts = (int) Tools::getValue('limit_products'); + $buttonUrl = trim(Tools::getValue('button_url')); + $position = (int) Tools::getValue('position'); + $active = (int) Tools::getValue('active'); + + if ($limitProducts <= 0) { + $limitProducts = 12; + } + + // Sanitize manual product IDs + if ($sourceType === 'manual' && $productIds) { + $ids = array_filter(array_map('intval', explode(',', $productIds))); + $productIds = implode(',', $ids); + } + + $now = date('Y-m-d H:i:s'); + $db = Db::getInstance(); + + if ($idCarousel > 0) { + $db->update('pp_carousel', [ + 'hook_name' => pSQL($hookName), + 'source_type' => pSQL($sourceType), + 'id_category' => $idCategory, + 'product_ids' => pSQL($productIds), + 'limit_products' => $limitProducts, + 'button_url' => pSQL($buttonUrl), + 'position' => $position, + 'active' => $active, + 'date_upd' => $now, + ], 'id_carousel = ' . $idCarousel); + } else { + $db->insert('pp_carousel', [ + 'hook_name' => pSQL($hookName), + 'source_type' => pSQL($sourceType), + 'id_category' => $idCategory, + 'product_ids' => pSQL($productIds), + 'limit_products' => $limitProducts, + 'button_url' => pSQL($buttonUrl), + 'position' => $position, + 'active' => $active, + 'id_shop' => (int) $this->context->shop->id, + 'date_add' => $now, + 'date_upd' => $now, + ]); + $idCarousel = (int) $db->Insert_ID(); + } + + // Save lang fields + $langs = Language::getLanguages(false); + foreach ($langs as $lang) { + $id = (int) $lang['id_lang']; + $title = Tools::substr(trim(Tools::getValue('title_' . $id)), 0, 255); + $subtitle = Tools::substr(trim(Tools::getValue('subtitle_' . $id)), 0, 255); + $buttonLabel = Tools::substr(trim(Tools::getValue('button_label_' . $id)), 0, 255); + $priceSuffix = Tools::substr(trim(Tools::getValue('price_suffix_' . $id)), 0, 64); + + $exists = $db->getValue( + 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'pp_carousel_lang` + WHERE id_carousel = ' . $idCarousel . ' AND id_lang = ' . $id + ); + + $langData = [ + 'title' => pSQL($title), + 'subtitle' => pSQL($subtitle), + 'button_label' => pSQL($buttonLabel), + 'price_suffix' => pSQL($priceSuffix), + ]; + + if ($exists) { + $db->update('pp_carousel_lang', $langData, 'id_carousel = ' . $idCarousel . ' AND id_lang = ' . $id); + } else { + $langData['id_carousel'] = $idCarousel; + $langData['id_lang'] = $id; + $db->insert('pp_carousel_lang', $langData); + } + } + + // Register custom hook if needed + $this->ensureHookRegistered($hookName); + + return $this->displayConfirmation($this->l('Karuzela została zapisana.')); + } + + private function deleteCarousel($idCarousel) + { + if ($idCarousel <= 0) { + return ''; + } + + $db = Db::getInstance(); + $db->delete('pp_carousel_lang', 'id_carousel = ' . $idCarousel); + $db->delete('pp_carousel', 'id_carousel = ' . $idCarousel); + + return $this->displayConfirmation($this->l('Karuzela została usunięta.')); + } + + private function toggleCarouselStatus($idCarousel) + { + if ($idCarousel <= 0) { + return ''; + } + + $current = (int) Db::getInstance()->getValue( + 'SELECT active FROM `' . _DB_PREFIX_ . 'pp_carousel` WHERE id_carousel = ' . $idCarousel + ); + + Db::getInstance()->update('pp_carousel', [ + 'active' => $current ? 0 : 1, + ], 'id_carousel = ' . $idCarousel); + + return $this->displayConfirmation($this->l('Status karuzeli został zmieniony.')); + } + + private function getCarousel($idCarousel) + { + return Db::getInstance()->getRow( + 'SELECT * FROM `' . _DB_PREFIX_ . 'pp_carousel` WHERE id_carousel = ' . (int) $idCarousel + ); + } + + private function getCarouselLang($idCarousel, $idLang) + { + return Db::getInstance()->getRow( + 'SELECT * FROM `' . _DB_PREFIX_ . 'pp_carousel_lang` + WHERE id_carousel = ' . (int) $idCarousel . ' AND id_lang = ' . (int) $idLang + ) ?: []; + } + + private function getAvailableHooks() + { + $hooks = [ + 'displayHome', 'displayTopColumn', 'displayFooterBefore', + 'displayFooter', 'displayLeftColumn', 'displayRightColumn', + 'displayOrderConfirmation2', 'displayCrossSellingShoppingCart', + ]; + + $options = []; + foreach ($hooks as $h) { + $options[] = ['id' => $h, 'name' => $h]; + } + + return $options; + } + + private function flattenCategories($tree, $depth = 0, &$out = []) + { + foreach ($tree as $node) { + if (!isset($node['id_category'], $node['name'])) { + continue; + } + $prefix = str_repeat('— ', max(0, $depth)); + $out[] = [ + 'id' => (int) $node['id_category'], + 'name' => $prefix . $node['name'], + ]; + if (!empty($node['children'])) { + $this->flattenCategories($node['children'], $depth + 1, $out); + } + } + return $out; + } + + private function ensureHookRegistered($hookName) + { + $idHook = Hook::getIdByName($hookName); + if (!$idHook) { + $db = Db::getInstance(); + $db->insert('hook', [ + 'name' => pSQL($hookName), + 'title' => pSQL($hookName), + ]); + } + if (!$this->isRegisteredInHook($hookName)) { + $this->registerHook($hookName); + } + } + + public function isRegisteredInHook($hookName) + { + $idHook = (int) Hook::getIdByName($hookName); + if (!$idHook) { + return false; + } + $count = (int) Db::getInstance()->getValue( + 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'hook_module` + WHERE id_hook = ' . $idHook . ' AND id_module = ' . (int) $this->id + ); + return $count > 0; + } + + // ─── FRONT HOOKS ──────────────────────────────────────────── + + public function hookDisplayHeader() + { + $this->context->controller->registerStylesheet( + 'pp_carousel_swiper_css', + 'modules/' . $this->name . '/views/lib/swiper/swiper-bundle.min.css', + ['media' => 'all', 'priority' => 150] + ); + $this->context->controller->registerStylesheet( + 'pp_carousel_css', + 'modules/' . $this->name . '/views/css/pp_carousel.css', + ['media' => 'all', 'priority' => 151] + ); + $this->context->controller->registerJavascript( + 'pp_carousel_swiper_js', + 'modules/' . $this->name . '/views/lib/swiper/swiper-bundle.min.js', + ['position' => 'bottom', 'priority' => 150] + ); + $this->context->controller->registerJavascript( + 'pp_carousel_js', + 'modules/' . $this->name . '/views/js/pp_carousel.js', + ['position' => 'bottom', 'priority' => 151] + ); + } + + public function hookDisplayHome($params) + { + return $this->renderCarouselsForHook('displayHome'); + } + + public function hookDisplayTopColumn($params) + { + return $this->renderCarouselsForHook('displayTopColumn'); + } + + public function hookDisplayFooterBefore($params) + { + return $this->renderCarouselsForHook('displayFooterBefore'); + } + + public function hookDisplayFooter($params) + { + return $this->renderCarouselsForHook('displayFooter'); + } + + public function hookDisplayLeftColumn($params) + { + return $this->renderCarouselsForHook('displayLeftColumn'); + } + + public function hookDisplayRightColumn($params) + { + return $this->renderCarouselsForHook('displayRightColumn'); + } + + /** + * Catch-all: render carousels for any hook not explicitly defined above. + */ + public function __call($method, $args) + { + if (strpos($method, 'hookDisplay') === 0) { + $hookName = lcfirst(substr($method, 4)); + return $this->renderCarouselsForHook($hookName); + } + return ''; + } + + // ─── RENDERING ────────────────────────────────────────────── + + private function renderCarouselsForHook($hookName) + { + $carousels = Db::getInstance()->executeS( + 'SELECT c.*, cl.title, cl.subtitle, cl.button_label, cl.price_suffix + FROM `' . _DB_PREFIX_ . 'pp_carousel` c + LEFT JOIN `' . _DB_PREFIX_ . 'pp_carousel_lang` cl + ON c.id_carousel = cl.id_carousel AND cl.id_lang = ' . (int) $this->context->language->id . ' + WHERE c.hook_name = "' . pSQL($hookName) . '" + AND c.active = 1 + AND c.id_shop = ' . (int) $this->context->shop->id . ' + ORDER BY c.position ASC, c.id_carousel ASC' + ); + + if (empty($carousels)) { + return ''; + } + + $html = ''; + foreach ($carousels as $carousel) { + $products = $this->getProductsByCarousel($carousel); + if (empty($products)) { + continue; + } + + $buttonUrl = trim($carousel['button_url']); + if (empty($buttonUrl) && $carousel['source_type'] === 'category' && $carousel['id_category'] > 0) { + $cat = new Category((int) $carousel['id_category'], (int) $this->context->language->id); + if (Validate::isLoadedObject($cat)) { + $buttonUrl = $this->context->link->getCategoryLink($cat); + } + } + + $this->context->smarty->assign([ + 'ppc_id' => (int) $carousel['id_carousel'], + 'ppc_title' => $carousel['title'] ?: '', + 'ppc_subtitle' => $carousel['subtitle'] ?: '', + 'ppc_button_label' => $carousel['button_label'] ?: '', + 'ppc_button_url' => $buttonUrl, + 'ppc_price_suffix' => $carousel['price_suffix'] ?: '', + 'ppc_products' => $products, + ]); + + $html .= $this->fetch('module:' . $this->name . '/views/templates/hook/pp_carousel.tpl'); + } + + return $html; + } + + // ─── PRODUCT SOURCES ──────────────────────────────────────── + + private function getProductsByCarousel($carousel) + { + $limit = (int) $carousel['limit_products']; + if ($limit <= 0) { + $limit = 12; + } + + switch ($carousel['source_type']) { + case 'new': + return $this->getNewProducts($limit); + case 'bestseller': + return $this->getBestsellers($limit); + case 'category': + return $this->getCategoryProducts((int) $carousel['id_category'], $limit); + case 'manual': + return $this->getManualProducts($carousel['product_ids'], $limit); + default: + return []; + } + } + + private function getNewProducts($limit) + { + $idLang = (int) $this->context->language->id; + $raw = Product::getNewProducts($idLang, 0, $limit); + return is_array($raw) ? $this->presentProducts($raw) : []; + } + + private function getBestsellers($limit) + { + $idLang = (int) $this->context->language->id; + $raw = ProductSale::getBestSales($idLang, 0, $limit); + return is_array($raw) ? $this->presentProducts($raw) : []; + } + + private function getCategoryProducts($idCategory, $limit) + { + if ($idCategory <= 0) { + return []; + } + $idLang = (int) $this->context->language->id; + $category = new Category($idCategory, $idLang); + if (!Validate::isLoadedObject($category)) { + return []; + } + $raw = $category->getProducts($idLang, 1, $limit, 'position', 'asc'); + return is_array($raw) ? $this->presentProducts($raw) : []; + } + + private function getManualProducts($productIdsStr, $limit) + { + if (empty($productIdsStr)) { + return []; + } + + $ids = array_filter(array_map('intval', explode(',', $productIdsStr))); + if (empty($ids)) { + return []; + } + + $idLang = (int) $this->context->language->id; + $idShop = (int) $this->context->shop->id; + + $sql = 'SELECT p.*, pl.`name`, pl.`description_short`, pl.`link_rewrite`, + cl.`name` AS category_default, cl.`link_rewrite` AS category_link_rewrite, + i.`id_image`, il.`legend`, + m.`name` AS manufacturer_name, + p.`id_category_default` + FROM `' . _DB_PREFIX_ . 'product` p + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl + ON p.id_product = pl.id_product AND pl.id_lang = ' . $idLang . ' AND pl.id_shop = ' . $idShop . ' + LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl + ON p.id_category_default = cl.id_category AND cl.id_lang = ' . $idLang . ' AND cl.id_shop = ' . $idShop . ' + LEFT JOIN `' . _DB_PREFIX_ . 'image` i + ON p.id_product = i.id_product AND i.cover = 1 + LEFT JOIN `' . _DB_PREFIX_ . 'image_lang` il + ON i.id_image = il.id_image AND il.id_lang = ' . $idLang . ' + LEFT JOIN `' . _DB_PREFIX_ . 'manufacturer` m + ON p.id_manufacturer = m.id_manufacturer + LEFT JOIN `' . _DB_PREFIX_ . 'product_shop` ps + ON p.id_product = ps.id_product AND ps.id_shop = ' . $idShop . ' + WHERE p.id_product IN (' . implode(',', $ids) . ') + AND ps.active = 1 + LIMIT ' . (int) $limit; + + $raw = Db::getInstance()->executeS($sql); + return is_array($raw) ? $this->presentProducts($raw) : []; + } + + private function presentProducts(array $rawProducts) + { + $assembler = new \ProductAssembler($this->context); + $presenterFactory = new \ProductPresenterFactory($this->context); + $presentationSettings = $presenterFactory->getPresentationSettings(); + $presenter = $presenterFactory->getPresenter(); + + $products = []; + foreach ($rawProducts as $raw) { + try { + $assembled = $assembler->assembleProduct($raw); + $products[] = $presenter->present( + $presentationSettings, + $assembled, + $this->context->language + ); + } catch (Exception $e) { + continue; + } + } + + return $products; + } +} diff --git a/modules/pp_carousel/sql/install.sql b/modules/pp_carousel/sql/install.sql new file mode 100644 index 0000000..940f6db --- /dev/null +++ b/modules/pp_carousel/sql/install.sql @@ -0,0 +1,27 @@ +CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel` ( + `id_carousel` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `hook_name` VARCHAR(128) NOT NULL DEFAULT 'displayHome', + `source_type` VARCHAR(20) NOT NULL DEFAULT 'new', + `id_category` INT(11) UNSIGNED NOT NULL DEFAULT 0, + `product_ids` TEXT, + `limit_products` INT(11) UNSIGNED NOT NULL DEFAULT 12, + `button_url` VARCHAR(512) DEFAULT '', + `position` INT(11) UNSIGNED NOT NULL DEFAULT 0, + `active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + `id_shop` INT(11) UNSIGNED NOT NULL DEFAULT 1, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + PRIMARY KEY (`id_carousel`), + KEY `hook_name` (`hook_name`), + KEY `active` (`active`) +) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel_lang` ( + `id_carousel` INT(11) UNSIGNED NOT NULL, + `id_lang` INT(11) UNSIGNED NOT NULL, + `title` VARCHAR(255) DEFAULT '', + `subtitle` VARCHAR(255) DEFAULT '', + `button_label` VARCHAR(255) DEFAULT '', + `price_suffix` VARCHAR(64) DEFAULT '', + PRIMARY KEY (`id_carousel`, `id_lang`) +) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4; diff --git a/modules/pp_carousel/sql/uninstall.sql b/modules/pp_carousel/sql/uninstall.sql new file mode 100644 index 0000000..b0e3485 --- /dev/null +++ b/modules/pp_carousel/sql/uninstall.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS `PREFIX_pp_carousel_lang`; +DROP TABLE IF EXISTS `PREFIX_pp_carousel`; diff --git a/modules/pp_carousel/views/css/pp_carousel.css b/modules/pp_carousel/views/css/pp_carousel.css new file mode 100644 index 0000000..b5809b3 --- /dev/null +++ b/modules/pp_carousel/views/css/pp_carousel.css @@ -0,0 +1,179 @@ +.pp-carousel { + margin: 40px 0; +} + +.pp-carousel__header { + margin-bottom: 18px; +} + +.pp-carousel__title { + font-size: 42px; + line-height: 1.1; + margin: 0 0 6px 0; + font-weight: 500; +} + +.pp-carousel__subtitle { + font-size: 44px; + line-height: 1.1; + font-weight: 300; + opacity: .85; +} + +.pp-carousel__slider { + position: relative; + padding: 10px 0 0 0; +} + +.pp-carousel__card { + display: block; +} + +.pp-carousel__image { + display: block; + border-radius: 2px; + overflow: hidden; + background: #f6f6f6; + position: relative; +} + +.pp-carousel__image img { + width: 100%; + height: auto; + display: block; + aspect-ratio: 1 / 1; + object-fit: cover; +} + +.pp-carousel__label { + position: absolute; + top: 12px; + left: 12px; + background: rgba(0, 0, 0, .55); + color: #fff; + font-size: 12px; + font-weight: 500; + padding: 4px 12px; + border-radius: 3px; + letter-spacing: .02em; + text-transform: capitalize; + pointer-events: none; +} + +.pp-carousel__meta { + display: flex; + justify-content: space-between; + gap: 16px; + padding: 14px 2px 0 2px; + align-items: baseline; +} + +.pp-carousel__name { + font-size: 18px; + font-weight: 600; + color: inherit; + text-decoration: none; +} + +.pp-carousel__name:hover { + text-decoration: underline; +} + +.pp-carousel__price { + font-size: 16px; + opacity: .7; + white-space: nowrap; +} + +.pp-carousel__priceSuffix { + margin-left: 2px; +} + +.pp-carousel__footer { + margin-top: 22px; +} + +.pp-carousel__more { + display: inline-flex; + align-items: center; + gap: 10px; + text-decoration: none; + opacity: .75; + font-size: 16px; + color: inherit; +} + +.pp-carousel__more:before { + content: ""; + display: inline-block; + width: 28px; + height: 1px; + background: currentColor; + opacity: .6; +} + +.pp-carousel__more:hover { + opacity: 1; +} + +/* Navigation arrows */ +.pp-carousel__nav .pp-carousel__prev, +.pp-carousel__nav .pp-carousel__next { + position: absolute; + top: 45%; + width: 40px; + height: 40px; + transform: translateY(-50%); + cursor: pointer; + opacity: .6; + z-index: 3; + transition: opacity .2s; +} + +.pp-carousel__nav .pp-carousel__prev:hover, +.pp-carousel__nav .pp-carousel__next:hover { + opacity: 1; +} + +.pp-carousel__nav .pp-carousel__prev { left: -10px; } +.pp-carousel__nav .pp-carousel__next { right: -10px; } + +.pp-carousel__nav .pp-carousel__prev:after, +.pp-carousel__nav .pp-carousel__next:after { + content: ""; + display: block; + width: 10px; + height: 10px; + border-right: 2px solid currentColor; + border-bottom: 2px solid currentColor; + position: absolute; + top: 50%; + left: 50%; +} + +.pp-carousel__nav .pp-carousel__prev:after { + transform: translate(-50%, -50%) rotate(135deg); +} + +.pp-carousel__nav .pp-carousel__next:after { + transform: translate(-50%, -50%) rotate(-45deg); +} + +.pp-carousel__nav .swiper-button-disabled { + opacity: .2; + cursor: default; +} + +/* Responsive */ +@media (max-width: 992px) { + .pp-carousel__title { font-size: 34px; } + .pp-carousel__subtitle { font-size: 34px; } + .pp-carousel__nav .pp-carousel__prev { left: 0; } + .pp-carousel__nav .pp-carousel__next { right: 0; } +} + +@media (max-width: 576px) { + .pp-carousel__title { font-size: 26px; } + .pp-carousel__subtitle { font-size: 26px; } + .pp-carousel__name { font-size: 16px; } +} diff --git a/modules/pp_carousel/views/js/pp_carousel.js b/modules/pp_carousel/views/js/pp_carousel.js new file mode 100644 index 0000000..9d799b1 --- /dev/null +++ b/modules/pp_carousel/views/js/pp_carousel.js @@ -0,0 +1,23 @@ +document.addEventListener('DOMContentLoaded', function () { + if (typeof Swiper === 'undefined') return; + + document.querySelectorAll('.pp-carousel__slider.swiper').forEach(function (el) { + var section = el.closest('.pp-carousel'); + if (!section) return; + + new Swiper(el, { + slidesPerView: 3, + spaceBetween: 26, + loop: false, + navigation: { + nextEl: section.querySelector('.pp-carousel__next'), + prevEl: section.querySelector('.pp-carousel__prev') + }, + breakpoints: { + 0: { slidesPerView: 1.15, spaceBetween: 16 }, + 576: { slidesPerView: 2, spaceBetween: 18 }, + 992: { slidesPerView: 3, spaceBetween: 26 } + } + }); + }); +}); diff --git a/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css b/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css new file mode 100644 index 0000000..c5bf0fb --- /dev/null +++ b/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css @@ -0,0 +1,13 @@ +/** + * Swiper 12.0.3 + * Most modern mobile touch slider and framework with hardware accelerated transitions + * https://swiperjs.com + * + * Copyright 2014-2025 Vladimir Kharlampidi + * + * Released under the MIT License + * + * Released on: October 21, 2025 + */ + +:root{--swiper-theme-color:#007aff}:host{display:block;margin-left:auto;margin-right:auto;position:relative;z-index:1}.swiper{display:block;list-style:none;margin-left:auto;margin-right:auto;overflow:hidden;padding:0;position:relative;z-index:1}.swiper-vertical>.swiper-wrapper{flex-direction:column}.swiper-wrapper{box-sizing:initial;display:flex;height:100%;position:relative;transition-property:transform;transition-timing-function:var(--swiper-wrapper-transition-timing-function,initial);width:100%;z-index:1}.swiper-android .swiper-slide,.swiper-ios .swiper-slide,.swiper-wrapper{transform:translateZ(0)}.swiper-horizontal{touch-action:pan-y}.swiper-vertical{touch-action:pan-x}.swiper-slide{display:block;flex-shrink:0;height:100%;position:relative;transition-property:transform;width:100%}.swiper-slide-invisible-blank{visibility:hidden}.swiper-autoheight,.swiper-autoheight .swiper-slide{height:auto}.swiper-autoheight .swiper-wrapper{align-items:flex-start;transition-property:transform,height}.swiper-backface-hidden .swiper-slide{backface-visibility:hidden;transform:translateZ(0)}.swiper-3d.swiper-css-mode .swiper-wrapper{perspective:1200px}.swiper-3d .swiper-wrapper{transform-style:preserve-3d}.swiper-3d{perspective:1200px;.swiper-cube-shadow,.swiper-slide{transform-style:preserve-3d}}.swiper-css-mode{>.swiper-wrapper{overflow:auto;scrollbar-width:none;-ms-overflow-style:none;&::-webkit-scrollbar{display:none}}>.swiper-wrapper>.swiper-slide{scroll-snap-align:start start}&.swiper-horizontal{>.swiper-wrapper{scroll-snap-type:x mandatory}}&.swiper-vertical{>.swiper-wrapper{scroll-snap-type:y mandatory}}&.swiper-free-mode{>.swiper-wrapper{scroll-snap-type:none}>.swiper-wrapper>.swiper-slide{scroll-snap-align:none}}&.swiper-centered{>.swiper-wrapper:before{content:"";flex-shrink:0;order:9999}>.swiper-wrapper>.swiper-slide{scroll-snap-align:center center;scroll-snap-stop:always}}&.swiper-centered.swiper-horizontal{>.swiper-wrapper>.swiper-slide:first-child{margin-inline-start:var(--swiper-centered-offset-before)}>.swiper-wrapper:before{height:100%;min-height:1px;width:var(--swiper-centered-offset-after)}}&.swiper-centered.swiper-vertical{>.swiper-wrapper>.swiper-slide:first-child{margin-block-start:var(--swiper-centered-offset-before)}>.swiper-wrapper:before{height:var(--swiper-centered-offset-after);min-width:1px;width:100%}}}.swiper-3d{.swiper-slide-shadow,.swiper-slide-shadow-bottom,.swiper-slide-shadow-left,.swiper-slide-shadow-right,.swiper-slide-shadow-top{height:100%;left:0;pointer-events:none;position:absolute;top:0;width:100%;z-index:10}.swiper-slide-shadow{background:#00000026}.swiper-slide-shadow-left{background-image:linear-gradient(270deg,#00000080,#0000)}.swiper-slide-shadow-right{background-image:linear-gradient(90deg,#00000080,#0000)}.swiper-slide-shadow-top{background-image:linear-gradient(0deg,#00000080,#0000)}.swiper-slide-shadow-bottom{background-image:linear-gradient(180deg,#00000080,#0000)}}.swiper-lazy-preloader{border:4px solid var(--swiper-preloader-color,var(--swiper-theme-color));border-radius:50%;border-top:4px solid #0000;box-sizing:border-box;height:42px;left:50%;margin-left:-21px;margin-top:-21px;position:absolute;top:50%;transform-origin:50%;width:42px;z-index:10}.swiper-watch-progress .swiper-slide-visible,.swiper:not(.swiper-watch-progress){.swiper-lazy-preloader{animation:swiper-preloader-spin 1s linear infinite}}.swiper-lazy-preloader-white{--swiper-preloader-color:#fff}.swiper-lazy-preloader-black{--swiper-preloader-color:#000}@keyframes swiper-preloader-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.swiper-virtual .swiper-slide{-webkit-backface-visibility:hidden;transform:translateZ(0)}.swiper-virtual.swiper-css-mode{.swiper-wrapper:after{content:"";left:0;pointer-events:none;position:absolute;top:0}}.swiper-virtual.swiper-css-mode.swiper-horizontal{.swiper-wrapper:after{height:1px;width:var(--swiper-virtual-size)}}.swiper-virtual.swiper-css-mode.swiper-vertical{.swiper-wrapper:after{height:var(--swiper-virtual-size);width:1px}}:root{--swiper-navigation-size:44px}.swiper-button-next,.swiper-button-prev{align-items:center;color:var(--swiper-navigation-color,var(--swiper-theme-color));cursor:pointer;display:flex;height:var(--swiper-navigation-size);justify-content:center;position:absolute;width:var(--swiper-navigation-size);z-index:10;&.swiper-button-disabled{cursor:auto;opacity:.35;pointer-events:none}&.swiper-button-hidden{cursor:auto;opacity:0;pointer-events:none}.swiper-navigation-disabled &{display:none!important}svg{height:100%;object-fit:contain;transform-origin:center;width:100%;fill:currentColor;pointer-events:none}}.swiper-button-lock{display:none}.swiper-button-next,.swiper-button-prev{margin-top:calc(0px - var(--swiper-navigation-size)/2);top:var(--swiper-navigation-top-offset,50%)}.swiper-button-prev{left:var(--swiper-navigation-sides-offset,4px);right:auto;.swiper-navigation-icon{transform:rotate(180deg)}}.swiper-button-next{left:auto;right:var(--swiper-navigation-sides-offset,4px)}.swiper-horizontal{.swiper-button-next,.swiper-button-prev,~.swiper-button-next,~.swiper-button-prev{margin-left:0;margin-top:calc(0px - var(--swiper-navigation-size)/2);top:var(--swiper-navigation-top-offset,50%)}&.swiper-rtl .swiper-button-next,&.swiper-rtl~.swiper-button-next,&~.swiper-button-prev,.swiper-button-prev{left:var(--swiper-navigation-sides-offset,4px);right:auto}&.swiper-rtl .swiper-button-prev,&.swiper-rtl~.swiper-button-prev,&~.swiper-button-next,.swiper-button-next{left:auto;right:var(--swiper-navigation-sides-offset,4px)}&.swiper-rtl .swiper-button-next,&.swiper-rtl~.swiper-button-next,&~.swiper-button-prev,.swiper-button-prev{.swiper-navigation-icon{transform:rotate(180deg)}}&.swiper-rtl .swiper-button-prev,&.swiper-rtl~.swiper-button-prev{.swiper-navigation-icon{transform:rotate(0deg)}}}.swiper-vertical{.swiper-button-next,.swiper-button-prev,~.swiper-button-next,~.swiper-button-prev{left:var(--swiper-navigation-top-offset,50%);margin-left:calc(0px - var(--swiper-navigation-size)/2);margin-top:0;right:auto}.swiper-button-prev,~.swiper-button-prev{bottom:auto;top:var(--swiper-navigation-sides-offset,4px);.swiper-navigation-icon{transform:rotate(-90deg)}}.swiper-button-next,~.swiper-button-next{bottom:var(--swiper-navigation-sides-offset,4px);top:auto;.swiper-navigation-icon{transform:rotate(90deg)}}}.swiper-pagination{position:absolute;text-align:center;transform:translateZ(0);transition:opacity .3s;z-index:10;&.swiper-pagination-hidden{opacity:0}&.swiper-pagination-disabled,.swiper-pagination-disabled>&{display:none!important}}.swiper-horizontal>.swiper-pagination-bullets,.swiper-pagination-bullets.swiper-pagination-horizontal,.swiper-pagination-custom,.swiper-pagination-fraction{bottom:var(--swiper-pagination-bottom,8px);left:0;top:var(--swiper-pagination-top,auto);width:100%}.swiper-pagination-bullets-dynamic{font-size:0;overflow:hidden;.swiper-pagination-bullet{position:relative;transform:scale(.33)}.swiper-pagination-bullet-active,.swiper-pagination-bullet-active-main{transform:scale(1)}.swiper-pagination-bullet-active-prev{transform:scale(.66)}.swiper-pagination-bullet-active-prev-prev{transform:scale(.33)}.swiper-pagination-bullet-active-next{transform:scale(.66)}.swiper-pagination-bullet-active-next-next{transform:scale(.33)}}.swiper-pagination-bullet{background:var(--swiper-pagination-bullet-inactive-color,#000);border-radius:var(--swiper-pagination-bullet-border-radius,50%);display:inline-block;height:var(--swiper-pagination-bullet-height,var(--swiper-pagination-bullet-size,8px));opacity:var(--swiper-pagination-bullet-inactive-opacity,.2);width:var(--swiper-pagination-bullet-width,var(--swiper-pagination-bullet-size,8px));button&{appearance:none;border:none;box-shadow:none;margin:0;padding:0}.swiper-pagination-clickable &{cursor:pointer}&:only-child{display:none!important}}.swiper-pagination-bullet-active{background:var(--swiper-pagination-color,var(--swiper-theme-color));opacity:var(--swiper-pagination-bullet-opacity,1)}.swiper-pagination-vertical.swiper-pagination-bullets,.swiper-vertical>.swiper-pagination-bullets{left:var(--swiper-pagination-left,auto);right:var(--swiper-pagination-right,8px);top:50%;transform:translate3d(0,-50%,0);.swiper-pagination-bullet{display:block;margin:var(--swiper-pagination-bullet-vertical-gap,6px) 0}&.swiper-pagination-bullets-dynamic{top:50%;transform:translateY(-50%);width:8px;.swiper-pagination-bullet{display:inline-block;transition:transform .2s,top .2s}}}.swiper-horizontal>.swiper-pagination-bullets,.swiper-pagination-horizontal.swiper-pagination-bullets{.swiper-pagination-bullet{margin:0 var(--swiper-pagination-bullet-horizontal-gap,4px)}&.swiper-pagination-bullets-dynamic{left:50%;transform:translateX(-50%);white-space:nowrap;.swiper-pagination-bullet{transition:transform .2s,left .2s}}}.swiper-horizontal.swiper-rtl>.swiper-pagination-bullets-dynamic .swiper-pagination-bullet{transition:transform .2s,right .2s}.swiper-pagination-fraction{color:var(--swiper-pagination-fraction-color,inherit)}.swiper-pagination-progressbar{background:var(--swiper-pagination-progressbar-bg-color,#00000040);position:absolute;.swiper-pagination-progressbar-fill{background:var(--swiper-pagination-color,var(--swiper-theme-color));height:100%;left:0;position:absolute;top:0;transform:scale(0);transform-origin:left top;width:100%}.swiper-rtl & .swiper-pagination-progressbar-fill{transform-origin:right top}&.swiper-pagination-horizontal,&.swiper-pagination-vertical.swiper-pagination-progressbar-opposite,.swiper-horizontal>&,.swiper-vertical>&.swiper-pagination-progressbar-opposite{height:var(--swiper-pagination-progressbar-size,4px);left:0;top:0;width:100%}&.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite,&.swiper-pagination-vertical,.swiper-horizontal>&.swiper-pagination-progressbar-opposite,.swiper-vertical>&{height:100%;left:0;top:0;width:var(--swiper-pagination-progressbar-size,4px)}}.swiper-pagination-lock{display:none}.swiper-scrollbar{background:var(--swiper-scrollbar-bg-color,#0000001a);border-radius:var(--swiper-scrollbar-border-radius,10px);position:relative;touch-action:none;&.swiper-scrollbar-disabled,.swiper-scrollbar-disabled>&{display:none!important}&.swiper-scrollbar-horizontal,.swiper-horizontal>&{bottom:var(--swiper-scrollbar-bottom,4px);height:var(--swiper-scrollbar-size,4px);left:var(--swiper-scrollbar-sides-offset,1%);position:absolute;top:var(--swiper-scrollbar-top,auto);width:calc(100% - var(--swiper-scrollbar-sides-offset, 1%)*2);z-index:50}&.swiper-scrollbar-vertical,.swiper-vertical>&{height:calc(100% - var(--swiper-scrollbar-sides-offset, 1%)*2);left:var(--swiper-scrollbar-left,auto);position:absolute;right:var(--swiper-scrollbar-right,4px);top:var(--swiper-scrollbar-sides-offset,1%);width:var(--swiper-scrollbar-size,4px);z-index:50}}.swiper-scrollbar-drag{background:var(--swiper-scrollbar-drag-bg-color,#00000080);border-radius:var(--swiper-scrollbar-border-radius,10px);height:100%;left:0;position:relative;top:0;width:100%}.swiper-scrollbar-cursor-drag{cursor:move}.swiper-scrollbar-lock{display:none}.swiper-zoom-container{align-items:center;display:flex;height:100%;justify-content:center;text-align:center;width:100%;>canvas,>img,>svg{max-height:100%;max-width:100%;object-fit:contain}}.swiper-slide-zoomed{cursor:move;touch-action:none}.swiper .swiper-notification{left:0;opacity:0;pointer-events:none;position:absolute;top:0;z-index:-1000}.swiper-free-mode>.swiper-wrapper{margin:0 auto;transition-timing-function:ease-out}.swiper-grid>.swiper-wrapper{flex-wrap:wrap}.swiper-grid-column>.swiper-wrapper{flex-direction:column;flex-wrap:wrap}.swiper-fade{&.swiper-free-mode{.swiper-slide{transition-timing-function:ease-out}}.swiper-slide{pointer-events:none;transition-property:opacity;.swiper-slide{pointer-events:none}}.swiper-slide-active{pointer-events:auto;& .swiper-slide-active{pointer-events:auto}}}.swiper.swiper-cube{overflow:visible}.swiper-cube{.swiper-slide{backface-visibility:hidden;height:100%;pointer-events:none;transform-origin:0 0;visibility:hidden;width:100%;z-index:1;.swiper-slide{pointer-events:none}}&.swiper-rtl .swiper-slide{transform-origin:100% 0}.swiper-slide-active{&,& .swiper-slide-active{pointer-events:auto}}.swiper-slide-active,.swiper-slide-next,.swiper-slide-prev{pointer-events:auto;visibility:visible}.swiper-cube-shadow{bottom:0;height:100%;left:0;opacity:.6;position:absolute;width:100%;z-index:0;&:before{background:#000;bottom:0;content:"";filter:blur(50px);left:0;position:absolute;right:0;top:0}}}.swiper-cube{.swiper-slide-next+.swiper-slide{pointer-events:auto;visibility:visible}}.swiper-cube{.swiper-slide-shadow-cube.swiper-slide-shadow-bottom,.swiper-slide-shadow-cube.swiper-slide-shadow-left,.swiper-slide-shadow-cube.swiper-slide-shadow-right,.swiper-slide-shadow-cube.swiper-slide-shadow-top{backface-visibility:hidden;z-index:0}}.swiper.swiper-flip{overflow:visible}.swiper-flip{.swiper-slide{backface-visibility:hidden;pointer-events:none;z-index:1;.swiper-slide{pointer-events:none}}.swiper-slide-active{&,& .swiper-slide-active{pointer-events:auto}}}.swiper-flip{.swiper-slide-shadow-flip.swiper-slide-shadow-bottom,.swiper-slide-shadow-flip.swiper-slide-shadow-left,.swiper-slide-shadow-flip.swiper-slide-shadow-right,.swiper-slide-shadow-flip.swiper-slide-shadow-top{backface-visibility:hidden;z-index:0}}.swiper-creative{.swiper-slide{backface-visibility:hidden;overflow:hidden;transition-property:transform,opacity,height}}.swiper.swiper-cards{overflow:visible}.swiper-cards{.swiper-slide{backface-visibility:hidden;overflow:hidden;transform-origin:center bottom}} \ No newline at end of file diff --git a/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js b/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js new file mode 100644 index 0000000..9251265 --- /dev/null +++ b/modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js @@ -0,0 +1,7661 @@ +/** + * Swiper 12.0.3 + * Most modern mobile touch slider and framework with hardware accelerated transitions + * https://swiperjs.com + * + * Copyright 2014-2025 Vladimir Kharlampidi + * + * Released under the MIT License + * + * Released on: October 21, 2025 + */ + +var Swiper = (function () { + 'use strict' + function e(e) { + return ( + null !== e && + 'object' == typeof e && + 'constructor' in e && + e.constructor === Object + ) + } + function t(s = {}, a = {}) { + const i = ['__proto__', 'constructor', 'prototype'] + Object.keys(a) + .filter((e) => i.indexOf(e) < 0) + .forEach((i) => { + void 0 === s[i] + ? (s[i] = a[i]) + : e(a[i]) && e(s[i]) && Object.keys(a[i]).length > 0 && t(s[i], a[i]) + }) + } + const s = { + body: {}, + addEventListener() {}, + removeEventListener() {}, + activeElement: { blur() {}, nodeName: '' }, + querySelector: () => null, + querySelectorAll: () => [], + getElementById: () => null, + createEvent: () => ({ initEvent() {} }), + createElement: () => ({ + children: [], + childNodes: [], + style: {}, + setAttribute() {}, + getElementsByTagName: () => [], + }), + createElementNS: () => ({}), + importNode: () => null, + location: { + hash: '', + host: '', + hostname: '', + href: '', + origin: '', + pathname: '', + protocol: '', + search: '', + }, + } + function a() { + const e = 'undefined' != typeof document ? document : {} + return t(e, s), e + } + const i = { + document: s, + navigator: { userAgent: '' }, + location: { + hash: '', + host: '', + hostname: '', + href: '', + origin: '', + pathname: '', + protocol: '', + search: '', + }, + history: { replaceState() {}, pushState() {}, go() {}, back() {} }, + CustomEvent: function () { + return this + }, + addEventListener() {}, + removeEventListener() {}, + getComputedStyle: () => ({ getPropertyValue: () => '' }), + Image() {}, + Date() {}, + screen: {}, + setTimeout() {}, + clearTimeout() {}, + matchMedia: () => ({}), + requestAnimationFrame: (e) => + 'undefined' == typeof setTimeout ? (e(), null) : setTimeout(e, 0), + cancelAnimationFrame(e) { + 'undefined' != typeof setTimeout && clearTimeout(e) + }, + } + function r() { + const e = 'undefined' != typeof window ? window : {} + return t(e, i), e + } + function n(e = '') { + return e + .trim() + .split(' ') + .filter((e) => !!e.trim()) + } + function l(e, t = 0) { + return setTimeout(e, t) + } + function o() { + return Date.now() + } + function d(e, t = 'x') { + const s = r() + let a, i, n + const l = (function (e) { + const t = r() + let s + return ( + t.getComputedStyle && (s = t.getComputedStyle(e, null)), + !s && e.currentStyle && (s = e.currentStyle), + s || (s = e.style), + s + ) + })(e) + return ( + s.WebKitCSSMatrix + ? ((i = l.transform || l.webkitTransform), + i.split(',').length > 6 && + (i = i + .split(', ') + .map((e) => e.replace(',', '.')) + .join(', ')), + (n = new s.WebKitCSSMatrix('none' === i ? '' : i))) + : ((n = + l.MozTransform || + l.OTransform || + l.MsTransform || + l.msTransform || + l.transform || + l + .getPropertyValue('transform') + .replace('translate(', 'matrix(1, 0, 0, 1,')), + (a = n.toString().split(','))), + 'x' === t && + (i = s.WebKitCSSMatrix + ? n.m41 + : 16 === a.length + ? parseFloat(a[12]) + : parseFloat(a[4])), + 'y' === t && + (i = s.WebKitCSSMatrix + ? n.m42 + : 16 === a.length + ? parseFloat(a[13]) + : parseFloat(a[5])), + i || 0 + ) + } + function c(e) { + return ( + 'object' == typeof e && + null !== e && + e.constructor && + 'Object' === Object.prototype.toString.call(e).slice(8, -1) + ) + } + function p(e) { + return 'undefined' != typeof window && void 0 !== window.HTMLElement + ? e instanceof HTMLElement + : e && (1 === e.nodeType || 11 === e.nodeType) + } + function u(...e) { + const t = Object(e[0]), + s = ['__proto__', 'constructor', 'prototype'] + for (let a = 1; a < e.length; a += 1) { + const i = e[a] + if (null != i && !p(i)) { + const e = Object.keys(Object(i)).filter((e) => s.indexOf(e) < 0) + for (let s = 0, a = e.length; s < a; s += 1) { + const a = e[s], + r = Object.getOwnPropertyDescriptor(i, a) + void 0 !== r && + r.enumerable && + (c(t[a]) && c(i[a]) + ? i[a].__swiper__ + ? (t[a] = i[a]) + : u(t[a], i[a]) + : !c(t[a]) && c(i[a]) + ? ((t[a] = {}), i[a].__swiper__ ? (t[a] = i[a]) : u(t[a], i[a])) + : (t[a] = i[a])) + } + } + } + return t + } + function m(e, t, s) { + e.style.setProperty(t, s) + } + function h({ swiper: e, targetPosition: t, side: s }) { + const a = r(), + i = -e.translate + let n, + l = null + const o = e.params.speed + ;(e.wrapperEl.style.scrollSnapType = 'none'), + a.cancelAnimationFrame(e.cssModeFrameID) + const d = t > i ? 'next' : 'prev', + c = (e, t) => ('next' === d && e >= t) || ('prev' === d && e <= t), + p = () => { + ;(n = new Date().getTime()), null === l && (l = n) + const r = Math.max(Math.min((n - l) / o, 1), 0), + d = 0.5 - Math.cos(r * Math.PI) / 2 + let u = i + d * (t - i) + if ((c(u, t) && (u = t), e.wrapperEl.scrollTo({ [s]: u }), c(u, t))) + return ( + (e.wrapperEl.style.overflow = 'hidden'), + (e.wrapperEl.style.scrollSnapType = ''), + setTimeout(() => { + ;(e.wrapperEl.style.overflow = ''), + e.wrapperEl.scrollTo({ [s]: u }) + }), + void a.cancelAnimationFrame(e.cssModeFrameID) + ) + e.cssModeFrameID = a.requestAnimationFrame(p) + } + p() + } + function f(e) { + return ( + e.querySelector('.swiper-slide-transform') || + (e.shadowRoot && e.shadowRoot.querySelector('.swiper-slide-transform')) || + e + ) + } + function g(e, t = '') { + const s = r(), + a = [...e.children] + return ( + s.HTMLSlotElement && + e instanceof HTMLSlotElement && + a.push(...e.assignedElements()), + t ? a.filter((e) => e.matches(t)) : a + ) + } + function v(e) { + try { + return void console.warn(e) + } catch (e) {} + } + function w(e, t = []) { + const s = document.createElement(e) + return s.classList.add(...(Array.isArray(t) ? t : n(t))), s + } + function b(e) { + const t = r(), + s = a(), + i = e.getBoundingClientRect(), + n = s.body, + l = e.clientTop || n.clientTop || 0, + o = e.clientLeft || n.clientLeft || 0, + d = e === t ? t.scrollY : e.scrollTop, + c = e === t ? t.scrollX : e.scrollLeft + return { top: i.top + d - l, left: i.left + c - o } + } + function y(e, t) { + return r().getComputedStyle(e, null).getPropertyValue(t) + } + function E(e) { + let t, + s = e + if (s) { + for (t = 0; null !== (s = s.previousSibling); ) + 1 === s.nodeType && (t += 1) + return t + } + } + function x(e, t) { + const s = [] + let a = e.parentElement + for (; a; ) t ? a.matches(t) && s.push(a) : s.push(a), (a = a.parentElement) + return s + } + function S(e, t) { + t && + e.addEventListener('transitionend', function s(a) { + a.target === e && + (t.call(e, a), e.removeEventListener('transitionend', s)) + }) + } + function T(e, t, s) { + const a = r() + return s + ? e['width' === t ? 'offsetWidth' : 'offsetHeight'] + + parseFloat( + a + .getComputedStyle(e, null) + .getPropertyValue('width' === t ? 'margin-right' : 'margin-top') + ) + + parseFloat( + a + .getComputedStyle(e, null) + .getPropertyValue('width' === t ? 'margin-left' : 'margin-bottom') + ) + : e.offsetWidth + } + function M(e) { + return (Array.isArray(e) ? e : [e]).filter((e) => !!e) + } + function C(e) { + return (t) => + Math.abs(t) > 0 && + e.browser && + e.browser.need3dFix && + Math.abs(t) % 90 == 0 + ? t + 0.001 + : t + } + function P(e, t = '') { + 'undefined' != typeof trustedTypes + ? (e.innerHTML = trustedTypes + .createPolicy('html', { createHTML: (e) => e }) + .createHTML(t)) + : (e.innerHTML = t) + } + let L, I, z + function A() { + return ( + L || + (L = (function () { + const e = r(), + t = a() + return { + smoothScroll: + t.documentElement && + t.documentElement.style && + 'scrollBehavior' in t.documentElement.style, + touch: !!( + 'ontouchstart' in e || + (e.DocumentTouch && t instanceof e.DocumentTouch) + ), + } + })()), + L + ) + } + function $(e = {}) { + return ( + I || + (I = (function ({ userAgent: e } = {}) { + const t = A(), + s = r(), + a = s.navigator.platform, + i = e || s.navigator.userAgent, + n = { ios: !1, android: !1 }, + l = s.screen.width, + o = s.screen.height, + d = i.match(/(Android);?[\s\/]+([\d.]+)?/) + let c = i.match(/(iPad)(?!\1).*OS\s([\d_]+)/) + const p = i.match(/(iPod)(.*OS\s([\d_]+))?/), + u = !c && i.match(/(iPhone\sOS|iOS)\s([\d_]+)/), + m = 'Win32' === a + let h = 'MacIntel' === a + return ( + !c && + h && + t.touch && + [ + '1024x1366', + '1366x1024', + '834x1194', + '1194x834', + '834x1112', + '1112x834', + '768x1024', + '1024x768', + '820x1180', + '1180x820', + '810x1080', + '1080x810', + ].indexOf(`${l}x${o}`) >= 0 && + ((c = i.match(/(Version)\/([\d.]+)/)), + c || (c = [0, 1, '13_0_0']), + (h = !1)), + d && !m && ((n.os = 'android'), (n.android = !0)), + (c || u || p) && ((n.os = 'ios'), (n.ios = !0)), + n + ) + })(e)), + I + ) + } + function k() { + return ( + z || + (z = (function () { + const e = r(), + t = $() + let s = !1 + function a() { + const t = e.navigator.userAgent.toLowerCase() + return ( + t.indexOf('safari') >= 0 && + t.indexOf('chrome') < 0 && + t.indexOf('android') < 0 + ) + } + if (a()) { + const t = String(e.navigator.userAgent) + if (t.includes('Version/')) { + const [e, a] = t + .split('Version/')[1] + .split(' ')[0] + .split('.') + .map((e) => Number(e)) + s = e < 16 || (16 === e && a < 2) + } + } + const i = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test( + e.navigator.userAgent + ), + n = a() + return { + isSafari: s || n, + needPerspectiveFix: s, + need3dFix: n || (i && t.ios), + isWebView: i, + } + })()), + z + ) + } + var O = { + on(e, t, s) { + const a = this + if (!a.eventsListeners || a.destroyed) return a + if ('function' != typeof t) return a + const i = s ? 'unshift' : 'push' + return ( + e.split(' ').forEach((e) => { + a.eventsListeners[e] || (a.eventsListeners[e] = []), + a.eventsListeners[e][i](t) + }), + a + ) + }, + once(e, t, s) { + const a = this + if (!a.eventsListeners || a.destroyed) return a + if ('function' != typeof t) return a + function i(...s) { + a.off(e, i), i.__emitterProxy && delete i.__emitterProxy, t.apply(a, s) + } + return (i.__emitterProxy = t), a.on(e, i, s) + }, + onAny(e, t) { + const s = this + if (!s.eventsListeners || s.destroyed) return s + if ('function' != typeof e) return s + const a = t ? 'unshift' : 'push' + return ( + s.eventsAnyListeners.indexOf(e) < 0 && s.eventsAnyListeners[a](e), s + ) + }, + offAny(e) { + const t = this + if (!t.eventsListeners || t.destroyed) return t + if (!t.eventsAnyListeners) return t + const s = t.eventsAnyListeners.indexOf(e) + return s >= 0 && t.eventsAnyListeners.splice(s, 1), t + }, + off(e, t) { + const s = this + return !s.eventsListeners || s.destroyed + ? s + : s.eventsListeners + ? (e.split(' ').forEach((e) => { + void 0 === t + ? (s.eventsListeners[e] = []) + : s.eventsListeners[e] && + s.eventsListeners[e].forEach((a, i) => { + ;(a === t || (a.__emitterProxy && a.__emitterProxy === t)) && + s.eventsListeners[e].splice(i, 1) + }) + }), + s) + : s + }, + emit(...e) { + const t = this + if (!t.eventsListeners || t.destroyed) return t + if (!t.eventsListeners) return t + let s, a, i + 'string' == typeof e[0] || Array.isArray(e[0]) + ? ((s = e[0]), (a = e.slice(1, e.length)), (i = t)) + : ((s = e[0].events), (a = e[0].data), (i = e[0].context || t)), + a.unshift(i) + return ( + (Array.isArray(s) ? s : s.split(' ')).forEach((e) => { + t.eventsAnyListeners && + t.eventsAnyListeners.length && + t.eventsAnyListeners.forEach((t) => { + t.apply(i, [e, ...a]) + }), + t.eventsListeners && + t.eventsListeners[e] && + t.eventsListeners[e].forEach((e) => { + e.apply(i, a) + }) + }), + t + ) + }, + } + const D = (e, t, s) => { + t && !e.classList.contains(s) + ? e.classList.add(s) + : !t && e.classList.contains(s) && e.classList.remove(s) + } + const G = (e, t, s) => { + t && !e.classList.contains(s) + ? e.classList.add(s) + : !t && e.classList.contains(s) && e.classList.remove(s) + } + const X = (e, t) => { + if (!e || e.destroyed || !e.params) return + const s = t.closest( + e.isElement ? 'swiper-slide' : `.${e.params.slideClass}` + ) + if (s) { + let t = s.querySelector(`.${e.params.lazyPreloaderClass}`) + !t && + e.isElement && + (s.shadowRoot + ? (t = s.shadowRoot.querySelector( + `.${e.params.lazyPreloaderClass}` + )) + : requestAnimationFrame(() => { + s.shadowRoot && + ((t = s.shadowRoot.querySelector( + `.${e.params.lazyPreloaderClass}` + )), + t && t.remove()) + })), + t && t.remove() + } + }, + B = (e, t) => { + if (!e.slides[t]) return + const s = e.slides[t].querySelector('[loading="lazy"]') + s && s.removeAttribute('loading') + }, + Y = (e) => { + if (!e || e.destroyed || !e.params) return + let t = e.params.lazyPreloadPrevNext + const s = e.slides.length + if (!s || !t || t < 0) return + t = Math.min(t, s) + const a = + 'auto' === e.params.slidesPerView + ? e.slidesPerViewDynamic() + : Math.ceil(e.params.slidesPerView), + i = e.activeIndex + if (e.params.grid && e.params.grid.rows > 1) { + const s = i, + r = [s - t] + return ( + r.push(...Array.from({ length: t }).map((e, t) => s + a + t)), + void e.slides.forEach((t, s) => { + r.includes(t.column) && B(e, s) + }) + ) + } + const r = i + a - 1 + if (e.params.rewind || e.params.loop) + for (let a = i - t; a <= r + t; a += 1) { + const t = ((a % s) + s) % s + ;(t < i || t > r) && B(e, t) + } + else + for (let a = Math.max(i - t, 0); a <= Math.min(r + t, s - 1); a += 1) + a !== i && (a > r || a < i) && B(e, a) + } + var H = { + updateSize: function () { + const e = this + let t, s + const a = e.el + ;(t = + void 0 !== e.params.width && null !== e.params.width + ? e.params.width + : a.clientWidth), + (s = + void 0 !== e.params.height && null !== e.params.height + ? e.params.height + : a.clientHeight), + (0 === t && e.isHorizontal()) || + (0 === s && e.isVertical()) || + ((t = + t - + parseInt(y(a, 'padding-left') || 0, 10) - + parseInt(y(a, 'padding-right') || 0, 10)), + (s = + s - + parseInt(y(a, 'padding-top') || 0, 10) - + parseInt(y(a, 'padding-bottom') || 0, 10)), + Number.isNaN(t) && (t = 0), + Number.isNaN(s) && (s = 0), + Object.assign(e, { + width: t, + height: s, + size: e.isHorizontal() ? t : s, + })) + }, + updateSlides: function () { + const e = this + function t(t, s) { + return parseFloat(t.getPropertyValue(e.getDirectionLabel(s)) || 0) + } + const s = e.params, + { wrapperEl: a, slidesEl: i, rtlTranslate: r, wrongRTL: n } = e, + l = e.virtual && s.virtual.enabled, + o = l ? e.virtual.slides.length : e.slides.length, + d = g(i, `.${e.params.slideClass}, swiper-slide`), + c = l ? e.virtual.slides.length : d.length + let p = [] + const u = [], + h = [] + let f = s.slidesOffsetBefore + 'function' == typeof f && (f = s.slidesOffsetBefore.call(e)) + let v = s.slidesOffsetAfter + 'function' == typeof v && (v = s.slidesOffsetAfter.call(e)) + const w = e.snapGrid.length, + b = e.slidesGrid.length, + E = e.size - f - v + let x = s.spaceBetween, + S = -f, + M = 0, + C = 0 + if (void 0 === E) return + 'string' == typeof x && x.indexOf('%') >= 0 + ? (x = (parseFloat(x.replace('%', '')) / 100) * E) + : 'string' == typeof x && (x = parseFloat(x)), + (e.virtualSize = -x - f - v), + d.forEach((e) => { + r ? (e.style.marginLeft = '') : (e.style.marginRight = ''), + (e.style.marginBottom = ''), + (e.style.marginTop = '') + }), + s.centeredSlides && + s.cssMode && + (m(a, '--swiper-centered-offset-before', ''), + m(a, '--swiper-centered-offset-after', '')) + const P = s.grid && s.grid.rows > 1 && e.grid + let L + P ? e.grid.initSlides(d) : e.grid && e.grid.unsetSlides() + const I = + 'auto' === s.slidesPerView && + s.breakpoints && + Object.keys(s.breakpoints).filter( + (e) => void 0 !== s.breakpoints[e].slidesPerView + ).length > 0 + for (let a = 0; a < c; a += 1) { + L = 0 + const i = d[a] + if ( + !i || + (P && e.grid.updateSlide(a, i, d), 'none' !== y(i, 'display')) + ) { + if (l && 'auto' === s.slidesPerView) + s.virtual.slidesPerViewAutoSlideSize && + (L = s.virtual.slidesPerViewAutoSlideSize), + L && + i && + (s.roundLengths && (L = Math.floor(L)), + (i.style[e.getDirectionLabel('width')] = `${L}px`)) + else if ('auto' === s.slidesPerView) { + I && (i.style[e.getDirectionLabel('width')] = '') + const a = getComputedStyle(i), + r = i.style.transform, + n = i.style.webkitTransform + if ( + (r && (i.style.transform = 'none'), + n && (i.style.webkitTransform = 'none'), + s.roundLengths) + ) + L = e.isHorizontal() ? T(i, 'width', !0) : T(i, 'height', !0) + else { + const e = t(a, 'width'), + s = t(a, 'padding-left'), + r = t(a, 'padding-right'), + n = t(a, 'margin-left'), + l = t(a, 'margin-right'), + o = a.getPropertyValue('box-sizing') + if (o && 'border-box' === o) L = e + n + l + else { + const { clientWidth: t, offsetWidth: a } = i + L = e + s + r + n + l + (a - t) + } + } + r && (i.style.transform = r), + n && (i.style.webkitTransform = n), + s.roundLengths && (L = Math.floor(L)) + } else + (L = (E - (s.slidesPerView - 1) * x) / s.slidesPerView), + s.roundLengths && (L = Math.floor(L)), + i && (i.style[e.getDirectionLabel('width')] = `${L}px`) + i && (i.swiperSlideSize = L), + h.push(L), + s.centeredSlides + ? ((S = S + L / 2 + M / 2 + x), + 0 === M && 0 !== a && (S = S - E / 2 - x), + 0 === a && (S = S - E / 2 - x), + Math.abs(S) < 0.001 && (S = 0), + s.roundLengths && (S = Math.floor(S)), + C % s.slidesPerGroup === 0 && p.push(S), + u.push(S)) + : (s.roundLengths && (S = Math.floor(S)), + (C - Math.min(e.params.slidesPerGroupSkip, C)) % + e.params.slidesPerGroup === + 0 && p.push(S), + u.push(S), + (S = S + L + x)), + (e.virtualSize += L + x), + (M = L), + (C += 1) + } + } + if ( + ((e.virtualSize = Math.max(e.virtualSize, E) + v), + r && + n && + ('slide' === s.effect || 'coverflow' === s.effect) && + (a.style.width = `${e.virtualSize + x}px`), + s.setWrapperSize && + (a.style[e.getDirectionLabel('width')] = `${e.virtualSize + x}px`), + P && e.grid.updateWrapperSize(L, p), + !s.centeredSlides) + ) { + const t = [] + for (let a = 0; a < p.length; a += 1) { + let i = p[a] + s.roundLengths && (i = Math.floor(i)), + p[a] <= e.virtualSize - E && t.push(i) + } + ;(p = t), + Math.floor(e.virtualSize - E) - Math.floor(p[p.length - 1]) > 1 && + p.push(e.virtualSize - E) + } + if (l && s.loop) { + const t = h[0] + x + if (s.slidesPerGroup > 1) { + const a = Math.ceil( + (e.virtual.slidesBefore + e.virtual.slidesAfter) / + s.slidesPerGroup + ), + i = t * s.slidesPerGroup + for (let e = 0; e < a; e += 1) p.push(p[p.length - 1] + i) + } + for ( + let a = 0; + a < e.virtual.slidesBefore + e.virtual.slidesAfter; + a += 1 + ) + 1 === s.slidesPerGroup && p.push(p[p.length - 1] + t), + u.push(u[u.length - 1] + t), + (e.virtualSize += t) + } + if ((0 === p.length && (p = [0]), 0 !== x)) { + const t = + e.isHorizontal() && r + ? 'marginLeft' + : e.getDirectionLabel('marginRight') + d.filter( + (e, t) => !(s.cssMode && !s.loop) || t !== d.length - 1 + ).forEach((e) => { + e.style[t] = `${x}px` + }) + } + if (s.centeredSlides && s.centeredSlidesBounds) { + let e = 0 + h.forEach((t) => { + e += t + (x || 0) + }), + (e -= x) + const t = e > E ? e - E : 0 + p = p.map((e) => (e <= 0 ? -f : e > t ? t + v : e)) + } + if (s.centerInsufficientSlides) { + let e = 0 + h.forEach((t) => { + e += t + (x || 0) + }), + (e -= x) + const t = (f || 0) + (v || 0) + if (e + t < E) { + const s = (E - e - t) / 2 + p.forEach((e, t) => { + p[t] = e - s + }), + u.forEach((e, t) => { + u[t] = e + s + }) + } + } + if ( + (Object.assign(e, { + slides: d, + snapGrid: p, + slidesGrid: u, + slidesSizesGrid: h, + }), + s.centeredSlides && s.cssMode && !s.centeredSlidesBounds) + ) { + m(a, '--swiper-centered-offset-before', -p[0] + 'px'), + m( + a, + '--swiper-centered-offset-after', + e.size / 2 - h[h.length - 1] / 2 + 'px' + ) + const t = -e.snapGrid[0], + s = -e.slidesGrid[0] + ;(e.snapGrid = e.snapGrid.map((e) => e + t)), + (e.slidesGrid = e.slidesGrid.map((e) => e + s)) + } + if ( + (c !== o && e.emit('slidesLengthChange'), + p.length !== w && + (e.params.watchOverflow && e.checkOverflow(), + e.emit('snapGridLengthChange')), + u.length !== b && e.emit('slidesGridLengthChange'), + s.watchSlidesProgress && e.updateSlidesOffset(), + e.emit('slidesUpdated'), + !(l || s.cssMode || ('slide' !== s.effect && 'fade' !== s.effect))) + ) { + const t = `${s.containerModifierClass}backface-hidden`, + a = e.el.classList.contains(t) + c <= s.maxBackfaceHiddenSlides + ? a || e.el.classList.add(t) + : a && e.el.classList.remove(t) + } + }, + updateAutoHeight: function (e) { + const t = this, + s = [], + a = t.virtual && t.params.virtual.enabled + let i, + r = 0 + 'number' == typeof e + ? t.setTransition(e) + : !0 === e && t.setTransition(t.params.speed) + const n = (e) => (a ? t.slides[t.getSlideIndexByData(e)] : t.slides[e]) + if ('auto' !== t.params.slidesPerView && t.params.slidesPerView > 1) + if (t.params.centeredSlides) + (t.visibleSlides || []).forEach((e) => { + s.push(e) + }) + else + for (i = 0; i < Math.ceil(t.params.slidesPerView); i += 1) { + const e = t.activeIndex + i + if (e > t.slides.length && !a) break + s.push(n(e)) + } + else s.push(n(t.activeIndex)) + for (i = 0; i < s.length; i += 1) + if (void 0 !== s[i]) { + const e = s[i].offsetHeight + r = e > r ? e : r + } + ;(r || 0 === r) && (t.wrapperEl.style.height = `${r}px`) + }, + updateSlidesOffset: function () { + const e = this, + t = e.slides, + s = e.isElement + ? e.isHorizontal() + ? e.wrapperEl.offsetLeft + : e.wrapperEl.offsetTop + : 0 + for (let a = 0; a < t.length; a += 1) + t[a].swiperSlideOffset = + (e.isHorizontal() ? t[a].offsetLeft : t[a].offsetTop) - + s - + e.cssOverflowAdjustment() + }, + updateSlidesProgress: function (e = (this && this.translate) || 0) { + const t = this, + s = t.params, + { slides: a, rtlTranslate: i, snapGrid: r } = t + if (0 === a.length) return + void 0 === a[0].swiperSlideOffset && t.updateSlidesOffset() + let n = -e + i && (n = e), (t.visibleSlidesIndexes = []), (t.visibleSlides = []) + let l = s.spaceBetween + 'string' == typeof l && l.indexOf('%') >= 0 + ? (l = (parseFloat(l.replace('%', '')) / 100) * t.size) + : 'string' == typeof l && (l = parseFloat(l)) + for (let e = 0; e < a.length; e += 1) { + const o = a[e] + let d = o.swiperSlideOffset + s.cssMode && s.centeredSlides && (d -= a[0].swiperSlideOffset) + const c = + (n + (s.centeredSlides ? t.minTranslate() : 0) - d) / + (o.swiperSlideSize + l), + p = + (n - r[0] + (s.centeredSlides ? t.minTranslate() : 0) - d) / + (o.swiperSlideSize + l), + u = -(n - d), + m = u + t.slidesSizesGrid[e], + h = u >= 0 && u <= t.size - t.slidesSizesGrid[e], + f = + (u >= 0 && u < t.size - 1) || + (m > 1 && m <= t.size) || + (u <= 0 && m >= t.size) + f && (t.visibleSlides.push(o), t.visibleSlidesIndexes.push(e)), + D(o, f, s.slideVisibleClass), + D(o, h, s.slideFullyVisibleClass), + (o.progress = i ? -c : c), + (o.originalProgress = i ? -p : p) + } + }, + updateProgress: function (e) { + const t = this + if (void 0 === e) { + const s = t.rtlTranslate ? -1 : 1 + e = (t && t.translate && t.translate * s) || 0 + } + const s = t.params, + a = t.maxTranslate() - t.minTranslate() + let { progress: i, isBeginning: r, isEnd: n, progressLoop: l } = t + const o = r, + d = n + if (0 === a) (i = 0), (r = !0), (n = !0) + else { + i = (e - t.minTranslate()) / a + const s = Math.abs(e - t.minTranslate()) < 1, + l = Math.abs(e - t.maxTranslate()) < 1 + ;(r = s || i <= 0), (n = l || i >= 1), s && (i = 0), l && (i = 1) + } + if (s.loop) { + const s = t.getSlideIndexByData(0), + a = t.getSlideIndexByData(t.slides.length - 1), + i = t.slidesGrid[s], + r = t.slidesGrid[a], + n = t.slidesGrid[t.slidesGrid.length - 1], + o = Math.abs(e) + ;(l = o >= i ? (o - i) / n : (o + n - r) / n), l > 1 && (l -= 1) + } + Object.assign(t, { + progress: i, + progressLoop: l, + isBeginning: r, + isEnd: n, + }), + (s.watchSlidesProgress || (s.centeredSlides && s.autoHeight)) && + t.updateSlidesProgress(e), + r && !o && t.emit('reachBeginning toEdge'), + n && !d && t.emit('reachEnd toEdge'), + ((o && !r) || (d && !n)) && t.emit('fromEdge'), + t.emit('progress', i) + }, + updateSlidesClasses: function () { + const e = this, + { slides: t, params: s, slidesEl: a, activeIndex: i } = e, + r = e.virtual && s.virtual.enabled, + n = e.grid && s.grid && s.grid.rows > 1, + l = (e) => g(a, `.${s.slideClass}${e}, swiper-slide${e}`)[0] + let o, d, c + if (r) + if (s.loop) { + let t = i - e.virtual.slidesBefore + t < 0 && (t = e.virtual.slides.length + t), + t >= e.virtual.slides.length && (t -= e.virtual.slides.length), + (o = l(`[data-swiper-slide-index="${t}"]`)) + } else o = l(`[data-swiper-slide-index="${i}"]`) + else + n + ? ((o = t.find((e) => e.column === i)), + (c = t.find((e) => e.column === i + 1)), + (d = t.find((e) => e.column === i - 1))) + : (o = t[i]) + o && + (n || + ((c = (function (e, t) { + const s = [] + for (; e.nextElementSibling; ) { + const a = e.nextElementSibling + t ? a.matches(t) && s.push(a) : s.push(a), (e = a) + } + return s + })(o, `.${s.slideClass}, swiper-slide`)[0]), + s.loop && !c && (c = t[0]), + (d = (function (e, t) { + const s = [] + for (; e.previousElementSibling; ) { + const a = e.previousElementSibling + t ? a.matches(t) && s.push(a) : s.push(a), (e = a) + } + return s + })(o, `.${s.slideClass}, swiper-slide`)[0]), + s.loop && 0 === !d && (d = t[t.length - 1]))), + t.forEach((e) => { + G(e, e === o, s.slideActiveClass), + G(e, e === c, s.slideNextClass), + G(e, e === d, s.slidePrevClass) + }), + e.emitSlidesClasses() + }, + updateActiveIndex: function (e) { + const t = this, + s = t.rtlTranslate ? t.translate : -t.translate, + { + snapGrid: a, + params: i, + activeIndex: r, + realIndex: n, + snapIndex: l, + } = t + let o, + d = e + const c = (e) => { + let s = e - t.virtual.slidesBefore + return ( + s < 0 && (s = t.virtual.slides.length + s), + s >= t.virtual.slides.length && (s -= t.virtual.slides.length), + s + ) + } + if ( + (void 0 === d && + (d = (function (e) { + const { slidesGrid: t, params: s } = e, + a = e.rtlTranslate ? e.translate : -e.translate + let i + for (let e = 0; e < t.length; e += 1) + void 0 !== t[e + 1] + ? a >= t[e] && a < t[e + 1] - (t[e + 1] - t[e]) / 2 + ? (i = e) + : a >= t[e] && a < t[e + 1] && (i = e + 1) + : a >= t[e] && (i = e) + return ( + s.normalizeSlideIndex && (i < 0 || void 0 === i) && (i = 0), i + ) + })(t)), + a.indexOf(s) >= 0) + ) + o = a.indexOf(s) + else { + const e = Math.min(i.slidesPerGroupSkip, d) + o = e + Math.floor((d - e) / i.slidesPerGroup) + } + if ((o >= a.length && (o = a.length - 1), d === r && !t.params.loop)) + return void (o !== l && ((t.snapIndex = o), t.emit('snapIndexChange'))) + if (d === r && t.params.loop && t.virtual && t.params.virtual.enabled) + return void (t.realIndex = c(d)) + const p = t.grid && i.grid && i.grid.rows > 1 + let u + if (t.virtual && i.virtual.enabled && i.loop) u = c(d) + else if (p) { + const e = t.slides.find((e) => e.column === d) + let s = parseInt(e.getAttribute('data-swiper-slide-index'), 10) + Number.isNaN(s) && (s = Math.max(t.slides.indexOf(e), 0)), + (u = Math.floor(s / i.grid.rows)) + } else if (t.slides[d]) { + const e = t.slides[d].getAttribute('data-swiper-slide-index') + u = e ? parseInt(e, 10) : d + } else u = d + Object.assign(t, { + previousSnapIndex: l, + snapIndex: o, + previousRealIndex: n, + realIndex: u, + previousIndex: r, + activeIndex: d, + }), + t.initialized && Y(t), + t.emit('activeIndexChange'), + t.emit('snapIndexChange'), + (t.initialized || t.params.runCallbacksOnInit) && + (n !== u && t.emit('realIndexChange'), t.emit('slideChange')) + }, + updateClickedSlide: function (e, t) { + const s = this, + a = s.params + let i = e.closest(`.${a.slideClass}, swiper-slide`) + !i && + s.isElement && + t && + t.length > 1 && + t.includes(e) && + [...t.slice(t.indexOf(e) + 1, t.length)].forEach((e) => { + !i && + e.matches && + e.matches(`.${a.slideClass}, swiper-slide`) && + (i = e) + }) + let r, + n = !1 + if (i) + for (let e = 0; e < s.slides.length; e += 1) + if (s.slides[e] === i) { + ;(n = !0), (r = e) + break + } + if (!i || !n) + return (s.clickedSlide = void 0), void (s.clickedIndex = void 0) + ;(s.clickedSlide = i), + s.virtual && s.params.virtual.enabled + ? (s.clickedIndex = parseInt( + i.getAttribute('data-swiper-slide-index'), + 10 + )) + : (s.clickedIndex = r), + a.slideToClickedSlide && + void 0 !== s.clickedIndex && + s.clickedIndex !== s.activeIndex && + s.slideToClickedSlide() + }, + } + var N = { + getTranslate: function (e = this.isHorizontal() ? 'x' : 'y') { + const { params: t, rtlTranslate: s, translate: a, wrapperEl: i } = this + if (t.virtualTranslate) return s ? -a : a + if (t.cssMode) return a + let r = d(i, e) + return (r += this.cssOverflowAdjustment()), s && (r = -r), r || 0 + }, + setTranslate: function (e, t) { + const s = this, + { rtlTranslate: a, params: i, wrapperEl: r, progress: n } = s + let l, + o = 0, + d = 0 + s.isHorizontal() ? (o = a ? -e : e) : (d = e), + i.roundLengths && ((o = Math.floor(o)), (d = Math.floor(d))), + (s.previousTranslate = s.translate), + (s.translate = s.isHorizontal() ? o : d), + i.cssMode + ? (r[s.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = s.isHorizontal() + ? -o + : -d) + : i.virtualTranslate || + (s.isHorizontal() + ? (o -= s.cssOverflowAdjustment()) + : (d -= s.cssOverflowAdjustment()), + (r.style.transform = `translate3d(${o}px, ${d}px, 0px)`)) + const c = s.maxTranslate() - s.minTranslate() + ;(l = 0 === c ? 0 : (e - s.minTranslate()) / c), + l !== n && s.updateProgress(e), + s.emit('setTranslate', s.translate, t) + }, + minTranslate: function () { + return -this.snapGrid[0] + }, + maxTranslate: function () { + return -this.snapGrid[this.snapGrid.length - 1] + }, + translateTo: function (e = 0, t = this.params.speed, s = !0, a = !0, i) { + const r = this, + { params: n, wrapperEl: l } = r + if (r.animating && n.preventInteractionOnTransition) return !1 + const o = r.minTranslate(), + d = r.maxTranslate() + let c + if ( + ((c = a && e > o ? o : a && e < d ? d : e), + r.updateProgress(c), + n.cssMode) + ) { + const e = r.isHorizontal() + if (0 === t) l[e ? 'scrollLeft' : 'scrollTop'] = -c + else { + if (!r.support.smoothScroll) + return ( + h({ swiper: r, targetPosition: -c, side: e ? 'left' : 'top' }), !0 + ) + l.scrollTo({ [e ? 'left' : 'top']: -c, behavior: 'smooth' }) + } + return !0 + } + return ( + 0 === t + ? (r.setTransition(0), + r.setTranslate(c), + s && + (r.emit('beforeTransitionStart', t, i), r.emit('transitionEnd'))) + : (r.setTransition(t), + r.setTranslate(c), + s && + (r.emit('beforeTransitionStart', t, i), + r.emit('transitionStart')), + r.animating || + ((r.animating = !0), + r.onTranslateToWrapperTransitionEnd || + (r.onTranslateToWrapperTransitionEnd = function (e) { + r && + !r.destroyed && + e.target === this && + (r.wrapperEl.removeEventListener( + 'transitionend', + r.onTranslateToWrapperTransitionEnd + ), + (r.onTranslateToWrapperTransitionEnd = null), + delete r.onTranslateToWrapperTransitionEnd, + (r.animating = !1), + s && r.emit('transitionEnd')) + }), + r.wrapperEl.addEventListener( + 'transitionend', + r.onTranslateToWrapperTransitionEnd + ))), + !0 + ) + }, + } + function R({ swiper: e, runCallbacks: t, direction: s, step: a }) { + const { activeIndex: i, previousIndex: r } = e + let n = s + n || (n = i > r ? 'next' : i < r ? 'prev' : 'reset'), + e.emit(`transition${a}`), + t && 'reset' === n + ? e.emit(`slideResetTransition${a}`) + : t && + i !== r && + (e.emit(`slideChangeTransition${a}`), + 'next' === n + ? e.emit(`slideNextTransition${a}`) + : e.emit(`slidePrevTransition${a}`)) + } + var V = { + slideTo: function (e = 0, t, s = !0, a, i) { + 'string' == typeof e && (e = parseInt(e, 10)) + const r = this + let n = e + n < 0 && (n = 0) + const { + params: l, + snapGrid: o, + slidesGrid: d, + previousIndex: c, + activeIndex: p, + rtlTranslate: u, + wrapperEl: m, + enabled: f, + } = r + if ( + (!f && !a && !i) || + r.destroyed || + (r.animating && l.preventInteractionOnTransition) + ) + return !1 + void 0 === t && (t = r.params.speed) + const g = Math.min(r.params.slidesPerGroupSkip, n) + let v = g + Math.floor((n - g) / r.params.slidesPerGroup) + v >= o.length && (v = o.length - 1) + const w = -o[v] + if (l.normalizeSlideIndex) + for (let e = 0; e < d.length; e += 1) { + const t = -Math.floor(100 * w), + s = Math.floor(100 * d[e]), + a = Math.floor(100 * d[e + 1]) + void 0 !== d[e + 1] + ? t >= s && t < a - (a - s) / 2 + ? (n = e) + : t >= s && t < a && (n = e + 1) + : t >= s && (n = e) + } + if (r.initialized && n !== p) { + if ( + !r.allowSlideNext && + (u + ? w > r.translate && w > r.minTranslate() + : w < r.translate && w < r.minTranslate()) + ) + return !1 + if ( + !r.allowSlidePrev && + w > r.translate && + w > r.maxTranslate() && + (p || 0) !== n + ) + return !1 + } + let b + n !== (c || 0) && s && r.emit('beforeSlideChangeStart'), + r.updateProgress(w), + (b = n > p ? 'next' : n < p ? 'prev' : 'reset') + const y = r.virtual && r.params.virtual.enabled + if (!(y && i) && ((u && -w === r.translate) || (!u && w === r.translate))) + return ( + r.updateActiveIndex(n), + l.autoHeight && r.updateAutoHeight(), + r.updateSlidesClasses(), + 'slide' !== l.effect && r.setTranslate(w), + 'reset' !== b && (r.transitionStart(s, b), r.transitionEnd(s, b)), + !1 + ) + if (l.cssMode) { + const e = r.isHorizontal(), + s = u ? w : -w + if (0 === t) + y && + ((r.wrapperEl.style.scrollSnapType = 'none'), + (r._immediateVirtual = !0)), + y && !r._cssModeVirtualInitialSet && r.params.initialSlide > 0 + ? ((r._cssModeVirtualInitialSet = !0), + requestAnimationFrame(() => { + m[e ? 'scrollLeft' : 'scrollTop'] = s + })) + : (m[e ? 'scrollLeft' : 'scrollTop'] = s), + y && + requestAnimationFrame(() => { + ;(r.wrapperEl.style.scrollSnapType = ''), + (r._immediateVirtual = !1) + }) + else { + if (!r.support.smoothScroll) + return ( + h({ swiper: r, targetPosition: s, side: e ? 'left' : 'top' }), !0 + ) + m.scrollTo({ [e ? 'left' : 'top']: s, behavior: 'smooth' }) + } + return !0 + } + const E = k().isSafari + return ( + y && !i && E && r.isElement && r.virtual.update(!1, !1, n), + r.setTransition(t), + r.setTranslate(w), + r.updateActiveIndex(n), + r.updateSlidesClasses(), + r.emit('beforeTransitionStart', t, a), + r.transitionStart(s, b), + 0 === t + ? r.transitionEnd(s, b) + : r.animating || + ((r.animating = !0), + r.onSlideToWrapperTransitionEnd || + (r.onSlideToWrapperTransitionEnd = function (e) { + r && + !r.destroyed && + e.target === this && + (r.wrapperEl.removeEventListener( + 'transitionend', + r.onSlideToWrapperTransitionEnd + ), + (r.onSlideToWrapperTransitionEnd = null), + delete r.onSlideToWrapperTransitionEnd, + r.transitionEnd(s, b)) + }), + r.wrapperEl.addEventListener( + 'transitionend', + r.onSlideToWrapperTransitionEnd + )), + !0 + ) + }, + slideToLoop: function (e = 0, t, s = !0, a) { + if ('string' == typeof e) { + e = parseInt(e, 10) + } + const i = this + if (i.destroyed) return + void 0 === t && (t = i.params.speed) + const r = i.grid && i.params.grid && i.params.grid.rows > 1 + let n = e + if (i.params.loop) + if (i.virtual && i.params.virtual.enabled) n += i.virtual.slidesBefore + else { + let e + if (r) { + const t = n * i.params.grid.rows + e = i.slides.find( + (e) => 1 * e.getAttribute('data-swiper-slide-index') === t + ).column + } else e = i.getSlideIndexByData(n) + const t = r + ? Math.ceil(i.slides.length / i.params.grid.rows) + : i.slides.length, + { + centeredSlides: s, + slidesOffsetBefore: l, + slidesOffsetAfter: o, + } = i.params, + d = s || !!l || !!o + let c = i.params.slidesPerView + 'auto' === c + ? (c = i.slidesPerViewDynamic()) + : ((c = Math.ceil(parseFloat(i.params.slidesPerView, 10))), + d && c % 2 == 0 && (c += 1)) + let p = t - e < c + if ( + (d && (p = p || e < Math.ceil(c / 2)), + a && d && 'auto' !== i.params.slidesPerView && !r && (p = !1), + p) + ) { + const s = d + ? e < i.activeIndex + ? 'prev' + : 'next' + : e - i.activeIndex - 1 < i.params.slidesPerView + ? 'next' + : 'prev' + i.loopFix({ + direction: s, + slideTo: !0, + activeSlideIndex: 'next' === s ? e + 1 : e - t + 1, + slideRealIndex: 'next' === s ? i.realIndex : void 0, + }) + } + if (r) { + const e = n * i.params.grid.rows + n = i.slides.find( + (t) => 1 * t.getAttribute('data-swiper-slide-index') === e + ).column + } else n = i.getSlideIndexByData(n) + } + return ( + requestAnimationFrame(() => { + i.slideTo(n, t, s, a) + }), + i + ) + }, + slideNext: function (e, t = !0, s) { + const a = this, + { enabled: i, params: r, animating: n } = a + if (!i || a.destroyed) return a + void 0 === e && (e = a.params.speed) + let l = r.slidesPerGroup + 'auto' === r.slidesPerView && + 1 === r.slidesPerGroup && + r.slidesPerGroupAuto && + (l = Math.max(a.slidesPerViewDynamic('current', !0), 1)) + const o = a.activeIndex < r.slidesPerGroupSkip ? 1 : l, + d = a.virtual && r.virtual.enabled + if (r.loop) { + if (n && !d && r.loopPreventsSliding) return !1 + if ( + (a.loopFix({ direction: 'next' }), + (a._clientLeft = a.wrapperEl.clientLeft), + a.activeIndex === a.slides.length - 1 && r.cssMode) + ) + return ( + requestAnimationFrame(() => { + a.slideTo(a.activeIndex + o, e, t, s) + }), + !0 + ) + } + return r.rewind && a.isEnd + ? a.slideTo(0, e, t, s) + : a.slideTo(a.activeIndex + o, e, t, s) + }, + slidePrev: function (e, t = !0, s) { + const a = this, + { + params: i, + snapGrid: r, + slidesGrid: n, + rtlTranslate: l, + enabled: o, + animating: d, + } = a + if (!o || a.destroyed) return a + void 0 === e && (e = a.params.speed) + const c = a.virtual && i.virtual.enabled + if (i.loop) { + if (d && !c && i.loopPreventsSliding) return !1 + a.loopFix({ direction: 'prev' }), + (a._clientLeft = a.wrapperEl.clientLeft) + } + function p(e) { + return e < 0 ? -Math.floor(Math.abs(e)) : Math.floor(e) + } + const u = p(l ? a.translate : -a.translate), + m = r.map((e) => p(e)), + h = i.freeMode && i.freeMode.enabled + let f = r[m.indexOf(u) - 1] + if (void 0 === f && (i.cssMode || h)) { + let e + r.forEach((t, s) => { + u >= t && (e = s) + }), + void 0 !== e && (f = h ? r[e] : r[e > 0 ? e - 1 : e]) + } + let g = 0 + if ( + (void 0 !== f && + ((g = n.indexOf(f)), + g < 0 && (g = a.activeIndex - 1), + 'auto' === i.slidesPerView && + 1 === i.slidesPerGroup && + i.slidesPerGroupAuto && + ((g = g - a.slidesPerViewDynamic('previous', !0) + 1), + (g = Math.max(g, 0)))), + i.rewind && a.isBeginning) + ) { + const i = + a.params.virtual && a.params.virtual.enabled && a.virtual + ? a.virtual.slides.length - 1 + : a.slides.length - 1 + return a.slideTo(i, e, t, s) + } + return i.loop && 0 === a.activeIndex && i.cssMode + ? (requestAnimationFrame(() => { + a.slideTo(g, e, t, s) + }), + !0) + : a.slideTo(g, e, t, s) + }, + slideReset: function (e, t = !0, s) { + const a = this + if (!a.destroyed) + return ( + void 0 === e && (e = a.params.speed), + a.slideTo(a.activeIndex, e, t, s) + ) + }, + slideToClosest: function (e, t = !0, s, a = 0.5) { + const i = this + if (i.destroyed) return + void 0 === e && (e = i.params.speed) + let r = i.activeIndex + const n = Math.min(i.params.slidesPerGroupSkip, r), + l = n + Math.floor((r - n) / i.params.slidesPerGroup), + o = i.rtlTranslate ? i.translate : -i.translate + if (o >= i.snapGrid[l]) { + const e = i.snapGrid[l] + o - e > (i.snapGrid[l + 1] - e) * a && (r += i.params.slidesPerGroup) + } else { + const e = i.snapGrid[l - 1] + o - e <= (i.snapGrid[l] - e) * a && (r -= i.params.slidesPerGroup) + } + return ( + (r = Math.max(r, 0)), + (r = Math.min(r, i.slidesGrid.length - 1)), + i.slideTo(r, e, t, s) + ) + }, + slideToClickedSlide: function () { + const e = this + if (e.destroyed) return + const { params: t, slidesEl: s } = e, + a = + 'auto' === t.slidesPerView + ? e.slidesPerViewDynamic() + : t.slidesPerView + let i, + r = e.getSlideIndexWhenGrid(e.clickedIndex) + const n = e.isElement ? 'swiper-slide' : `.${t.slideClass}`, + o = e.grid && e.params.grid && e.params.grid.rows > 1 + if (t.loop) { + if (e.animating) return + ;(i = parseInt( + e.clickedSlide.getAttribute('data-swiper-slide-index'), + 10 + )), + t.centeredSlides + ? e.slideToLoop(i) + : r > + (o + ? (e.slides.length - a) / 2 - (e.params.grid.rows - 1) + : e.slides.length - a) + ? (e.loopFix(), + (r = e.getSlideIndex( + g(s, `${n}[data-swiper-slide-index="${i}"]`)[0] + )), + l(() => { + e.slideTo(r) + })) + : e.slideTo(r) + } else e.slideTo(r) + }, + } + var q = { + loopCreate: function (e, t) { + const s = this, + { params: a, slidesEl: i } = s + if (!a.loop || (s.virtual && s.params.virtual.enabled)) return + const r = () => { + g(i, `.${a.slideClass}, swiper-slide`).forEach((e, t) => { + e.setAttribute('data-swiper-slide-index', t) + }) + }, + n = s.grid && a.grid && a.grid.rows > 1 + a.loopAddBlankSlides && + (a.slidesPerGroup > 1 || n) && + (() => { + const e = g(i, `.${a.slideBlankClass}`) + e.forEach((e) => { + e.remove() + }), + e.length > 0 && (s.recalcSlides(), s.updateSlides()) + })() + const l = a.slidesPerGroup * (n ? a.grid.rows : 1), + o = s.slides.length % l !== 0, + d = n && s.slides.length % a.grid.rows !== 0, + c = (e) => { + for (let t = 0; t < e; t += 1) { + const e = s.isElement + ? w('swiper-slide', [a.slideBlankClass]) + : w('div', [a.slideClass, a.slideBlankClass]) + s.slidesEl.append(e) + } + } + if (o) { + if (a.loopAddBlankSlides) { + c(l - (s.slides.length % l)), s.recalcSlides(), s.updateSlides() + } else + v( + 'Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)' + ) + r() + } else if (d) { + if (a.loopAddBlankSlides) { + c(a.grid.rows - (s.slides.length % a.grid.rows)), + s.recalcSlides(), + s.updateSlides() + } else + v( + 'Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)' + ) + r() + } else r() + const p = + a.centeredSlides || !!a.slidesOffsetBefore || !!a.slidesOffsetAfter + s.loopFix({ + slideRealIndex: e, + direction: p ? void 0 : 'next', + initial: t, + }) + }, + loopFix: function ({ + slideRealIndex: e, + slideTo: t = !0, + direction: s, + setTranslate: a, + activeSlideIndex: i, + initial: r, + byController: n, + byMousewheel: l, + } = {}) { + const o = this + if (!o.params.loop) return + o.emit('beforeLoopFix') + const { + slides: d, + allowSlidePrev: c, + allowSlideNext: p, + slidesEl: u, + params: m, + } = o, + { + centeredSlides: h, + slidesOffsetBefore: f, + slidesOffsetAfter: g, + initialSlide: w, + } = m, + b = h || !!f || !!g + if ( + ((o.allowSlidePrev = !0), + (o.allowSlideNext = !0), + o.virtual && m.virtual.enabled) + ) + return ( + t && + (b || 0 !== o.snapIndex + ? b && o.snapIndex < m.slidesPerView + ? o.slideTo(o.virtual.slides.length + o.snapIndex, 0, !1, !0) + : o.snapIndex === o.snapGrid.length - 1 && + o.slideTo(o.virtual.slidesBefore, 0, !1, !0) + : o.slideTo(o.virtual.slides.length, 0, !1, !0)), + (o.allowSlidePrev = c), + (o.allowSlideNext = p), + void o.emit('loopFix') + ) + let y = m.slidesPerView + 'auto' === y + ? (y = o.slidesPerViewDynamic()) + : ((y = Math.ceil(parseFloat(m.slidesPerView, 10))), + b && y % 2 == 0 && (y += 1)) + const E = m.slidesPerGroupAuto ? y : m.slidesPerGroup + let x = b ? Math.max(E, Math.ceil(y / 2)) : E + x % E !== 0 && (x += E - (x % E)), + (x += m.loopAdditionalSlides), + (o.loopedSlides = x) + const S = o.grid && m.grid && m.grid.rows > 1 + d.length < y + x || ('cards' === o.params.effect && d.length < y + 2 * x) + ? v( + 'Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled or not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters' + ) + : S && + 'row' === m.grid.fill && + v( + 'Swiper Loop Warning: Loop mode is not compatible with grid.fill = `row`' + ) + const T = [], + M = [], + C = S ? Math.ceil(d.length / m.grid.rows) : d.length, + P = r && C - w < y && !b + let L = P ? w : o.activeIndex + void 0 === i + ? (i = o.getSlideIndex( + d.find((e) => e.classList.contains(m.slideActiveClass)) + )) + : (L = i) + const I = 'next' === s || !s, + z = 'prev' === s || !s + let A = 0, + $ = 0 + const k = (S ? d[i].column : i) + (b && void 0 === a ? -y / 2 + 0.5 : 0) + if (k < x) { + A = Math.max(x - k, E) + for (let e = 0; e < x - k; e += 1) { + const t = e - Math.floor(e / C) * C + if (S) { + const e = C - t - 1 + for (let t = d.length - 1; t >= 0; t -= 1) + d[t].column === e && T.push(t) + } else T.push(C - t - 1) + } + } else if (k + y > C - x) { + ;($ = Math.max(k - (C - 2 * x), E)), + P && ($ = Math.max($, y - C + w + 1)) + for (let e = 0; e < $; e += 1) { + const t = e - Math.floor(e / C) * C + S + ? d.forEach((e, s) => { + e.column === t && M.push(s) + }) + : M.push(t) + } + } + if ( + ((o.__preventObserver__ = !0), + requestAnimationFrame(() => { + o.__preventObserver__ = !1 + }), + 'cards' === o.params.effect && + d.length < y + 2 * x && + (M.includes(i) && M.splice(M.indexOf(i), 1), + T.includes(i) && T.splice(T.indexOf(i), 1)), + z && + T.forEach((e) => { + ;(d[e].swiperLoopMoveDOM = !0), + u.prepend(d[e]), + (d[e].swiperLoopMoveDOM = !1) + }), + I && + M.forEach((e) => { + ;(d[e].swiperLoopMoveDOM = !0), + u.append(d[e]), + (d[e].swiperLoopMoveDOM = !1) + }), + o.recalcSlides(), + 'auto' === m.slidesPerView + ? o.updateSlides() + : S && + ((T.length > 0 && z) || (M.length > 0 && I)) && + o.slides.forEach((e, t) => { + o.grid.updateSlide(t, e, o.slides) + }), + m.watchSlidesProgress && o.updateSlidesOffset(), + t) + ) + if (T.length > 0 && z) { + if (void 0 === e) { + const e = o.slidesGrid[L], + t = o.slidesGrid[L + A] - e + l + ? o.setTranslate(o.translate - t) + : (o.slideTo(L + Math.ceil(A), 0, !1, !0), + a && + ((o.touchEventsData.startTranslate = + o.touchEventsData.startTranslate - t), + (o.touchEventsData.currentTranslate = + o.touchEventsData.currentTranslate - t))) + } else if (a) { + const e = S ? T.length / m.grid.rows : T.length + o.slideTo(o.activeIndex + e, 0, !1, !0), + (o.touchEventsData.currentTranslate = o.translate) + } + } else if (M.length > 0 && I) + if (void 0 === e) { + const e = o.slidesGrid[L], + t = o.slidesGrid[L - $] - e + l + ? o.setTranslate(o.translate - t) + : (o.slideTo(L - $, 0, !1, !0), + a && + ((o.touchEventsData.startTranslate = + o.touchEventsData.startTranslate - t), + (o.touchEventsData.currentTranslate = + o.touchEventsData.currentTranslate - t))) + } else { + const e = S ? M.length / m.grid.rows : M.length + o.slideTo(o.activeIndex - e, 0, !1, !0) + } + if ( + ((o.allowSlidePrev = c), + (o.allowSlideNext = p), + o.controller && o.controller.control && !n) + ) { + const r = { + slideRealIndex: e, + direction: s, + setTranslate: a, + activeSlideIndex: i, + byController: !0, + } + Array.isArray(o.controller.control) + ? o.controller.control.forEach((e) => { + !e.destroyed && + e.params.loop && + e.loopFix({ + ...r, + slideTo: e.params.slidesPerView === m.slidesPerView && t, + }) + }) + : o.controller.control instanceof o.constructor && + o.controller.control.params.loop && + o.controller.control.loopFix({ + ...r, + slideTo: + o.controller.control.params.slidesPerView === m.slidesPerView && + t, + }) + } + o.emit('loopFix') + }, + loopDestroy: function () { + const e = this, + { params: t, slidesEl: s } = e + if (!t.loop || !s || (e.virtual && e.params.virtual.enabled)) return + e.recalcSlides() + const a = [] + e.slides.forEach((e) => { + const t = + void 0 === e.swiperSlideIndex + ? 1 * e.getAttribute('data-swiper-slide-index') + : e.swiperSlideIndex + a[t] = e + }), + e.slides.forEach((e) => { + e.removeAttribute('data-swiper-slide-index') + }), + a.forEach((e) => { + s.append(e) + }), + e.recalcSlides(), + e.slideTo(e.realIndex, 0) + }, + } + function _(e, t, s) { + const a = r(), + { params: i } = e, + n = i.edgeSwipeDetection, + l = i.edgeSwipeThreshold + return ( + !n || + !(s <= l || s >= a.innerWidth - l) || + ('prevent' === n && (t.preventDefault(), !0)) + ) + } + function F(e) { + const t = this, + s = a() + let i = e + i.originalEvent && (i = i.originalEvent) + const n = t.touchEventsData + if ('pointerdown' === i.type) { + if (null !== n.pointerId && n.pointerId !== i.pointerId) return + n.pointerId = i.pointerId + } else + 'touchstart' === i.type && + 1 === i.targetTouches.length && + (n.touchId = i.targetTouches[0].identifier) + if ('touchstart' === i.type) return void _(t, i, i.targetTouches[0].pageX) + const { params: l, touches: d, enabled: c } = t + if (!c) return + if (!l.simulateTouch && 'mouse' === i.pointerType) return + if (t.animating && l.preventInteractionOnTransition) return + !t.animating && l.cssMode && l.loop && t.loopFix() + let p = i.target + if ( + 'wrapper' === l.touchEventsTarget && + !(function (e, t) { + const s = r() + let a = t.contains(e) + !a && + s.HTMLSlotElement && + t instanceof HTMLSlotElement && + ((a = [...t.assignedElements()].includes(e)), + a || + (a = (function (e, t) { + const s = [t] + for (; s.length > 0; ) { + const t = s.shift() + if (e === t) return !0 + s.push( + ...t.children, + ...(t.shadowRoot ? t.shadowRoot.children : []), + ...(t.assignedElements ? t.assignedElements() : []) + ) + } + })(e, t))) + return a + })(p, t.wrapperEl) + ) + return + if ('which' in i && 3 === i.which) return + if ('button' in i && i.button > 0) return + if (n.isTouched && n.isMoved) return + const u = !!l.noSwipingClass && '' !== l.noSwipingClass, + m = i.composedPath ? i.composedPath() : i.path + u && i.target && i.target.shadowRoot && m && (p = m[0]) + const h = l.noSwipingSelector + ? l.noSwipingSelector + : `.${l.noSwipingClass}`, + f = !(!i.target || !i.target.shadowRoot) + if ( + l.noSwiping && + (f + ? (function (e, t = this) { + return (function t(s) { + if (!s || s === a() || s === r()) return null + s.assignedSlot && (s = s.assignedSlot) + const i = s.closest(e) + return i || s.getRootNode ? i || t(s.getRootNode().host) : null + })(t) + })(h, p) + : p.closest(h)) + ) + return void (t.allowClick = !0) + if (l.swipeHandler && !p.closest(l.swipeHandler)) return + ;(d.currentX = i.pageX), (d.currentY = i.pageY) + const g = d.currentX, + v = d.currentY + if (!_(t, i, g)) return + Object.assign(n, { + isTouched: !0, + isMoved: !1, + allowTouchCallbacks: !0, + isScrolling: void 0, + startMoving: void 0, + }), + (d.startX = g), + (d.startY = v), + (n.touchStartTime = o()), + (t.allowClick = !0), + t.updateSize(), + (t.swipeDirection = void 0), + l.threshold > 0 && (n.allowThresholdMove = !1) + let w = !0 + p.matches(n.focusableElements) && + ((w = !1), 'SELECT' === p.nodeName && (n.isTouched = !1)), + s.activeElement && + s.activeElement.matches(n.focusableElements) && + s.activeElement !== p && + ('mouse' === i.pointerType || + ('mouse' !== i.pointerType && !p.matches(n.focusableElements))) && + s.activeElement.blur() + const b = w && t.allowTouchMove && l.touchStartPreventDefault + ;(!l.touchStartForcePreventDefault && !b) || + p.isContentEditable || + i.preventDefault(), + l.freeMode && + l.freeMode.enabled && + t.freeMode && + t.animating && + !l.cssMode && + t.freeMode.onTouchStart(), + t.emit('touchStart', i) + } + function W(e) { + const t = a(), + s = this, + i = s.touchEventsData, + { params: r, touches: n, rtlTranslate: l, enabled: d } = s + if (!d) return + if (!r.simulateTouch && 'mouse' === e.pointerType) return + let c, + p = e + if ((p.originalEvent && (p = p.originalEvent), 'pointermove' === p.type)) { + if (null !== i.touchId) return + if (p.pointerId !== i.pointerId) return + } + if ('touchmove' === p.type) { + if ( + ((c = [...p.changedTouches].find((e) => e.identifier === i.touchId)), + !c || c.identifier !== i.touchId) + ) + return + } else c = p + if (!i.isTouched) + return void ( + i.startMoving && + i.isScrolling && + s.emit('touchMoveOpposite', p) + ) + const u = c.pageX, + m = c.pageY + if (p.preventedByNestedSwiper) return (n.startX = u), void (n.startY = m) + if (!s.allowTouchMove) + return ( + p.target.matches(i.focusableElements) || (s.allowClick = !1), + void ( + i.isTouched && + (Object.assign(n, { startX: u, startY: m, currentX: u, currentY: m }), + (i.touchStartTime = o())) + ) + ) + if (r.touchReleaseOnEdges && !r.loop) + if (s.isVertical()) { + if ( + (m < n.startY && s.translate <= s.maxTranslate()) || + (m > n.startY && s.translate >= s.minTranslate()) + ) + return (i.isTouched = !1), void (i.isMoved = !1) + } else { + if ( + l && + ((u > n.startX && -s.translate <= s.maxTranslate()) || + (u < n.startX && -s.translate >= s.minTranslate())) + ) + return + if ( + !l && + ((u < n.startX && s.translate <= s.maxTranslate()) || + (u > n.startX && s.translate >= s.minTranslate())) + ) + return + } + if ( + (t.activeElement && + t.activeElement.matches(i.focusableElements) && + t.activeElement !== p.target && + 'mouse' !== p.pointerType && + t.activeElement.blur(), + t.activeElement && + p.target === t.activeElement && + p.target.matches(i.focusableElements)) + ) + return (i.isMoved = !0), void (s.allowClick = !1) + i.allowTouchCallbacks && s.emit('touchMove', p), + (n.previousX = n.currentX), + (n.previousY = n.currentY), + (n.currentX = u), + (n.currentY = m) + const h = n.currentX - n.startX, + f = n.currentY - n.startY + if (s.params.threshold && Math.sqrt(h ** 2 + f ** 2) < s.params.threshold) + return + if (void 0 === i.isScrolling) { + let e + ;(s.isHorizontal() && n.currentY === n.startY) || + (s.isVertical() && n.currentX === n.startX) + ? (i.isScrolling = !1) + : h * h + f * f >= 25 && + ((e = (180 * Math.atan2(Math.abs(f), Math.abs(h))) / Math.PI), + (i.isScrolling = s.isHorizontal() + ? e > r.touchAngle + : 90 - e > r.touchAngle)) + } + if ( + (i.isScrolling && s.emit('touchMoveOpposite', p), + void 0 === i.startMoving && + ((n.currentX === n.startX && n.currentY === n.startY) || + (i.startMoving = !0)), + i.isScrolling || + ('touchmove' === p.type && i.preventTouchMoveFromPointerMove)) + ) + return void (i.isTouched = !1) + if (!i.startMoving) return + ;(s.allowClick = !1), + !r.cssMode && p.cancelable && p.preventDefault(), + r.touchMoveStopPropagation && !r.nested && p.stopPropagation() + let g = s.isHorizontal() ? h : f, + v = s.isHorizontal() ? n.currentX - n.previousX : n.currentY - n.previousY + r.oneWayMovement && + ((g = Math.abs(g) * (l ? 1 : -1)), (v = Math.abs(v) * (l ? 1 : -1))), + (n.diff = g), + (g *= r.touchRatio), + l && ((g = -g), (v = -v)) + const w = s.touchesDirection + ;(s.swipeDirection = g > 0 ? 'prev' : 'next'), + (s.touchesDirection = v > 0 ? 'prev' : 'next') + const b = s.params.loop && !r.cssMode, + y = + ('next' === s.touchesDirection && s.allowSlideNext) || + ('prev' === s.touchesDirection && s.allowSlidePrev) + if (!i.isMoved) { + if ( + (b && y && s.loopFix({ direction: s.swipeDirection }), + (i.startTranslate = s.getTranslate()), + s.setTransition(0), + s.animating) + ) { + const e = new window.CustomEvent('transitionend', { + bubbles: !0, + cancelable: !0, + detail: { bySwiperTouchMove: !0 }, + }) + s.wrapperEl.dispatchEvent(e) + } + ;(i.allowMomentumBounce = !1), + !r.grabCursor || + (!0 !== s.allowSlideNext && !0 !== s.allowSlidePrev) || + s.setGrabCursor(!0), + s.emit('sliderFirstMove', p) + } + if ( + (new Date().getTime(), + !1 !== r._loopSwapReset && + i.isMoved && + i.allowThresholdMove && + w !== s.touchesDirection && + b && + y && + Math.abs(g) >= 1) + ) + return ( + Object.assign(n, { + startX: u, + startY: m, + currentX: u, + currentY: m, + startTranslate: i.currentTranslate, + }), + (i.loopSwapReset = !0), + void (i.startTranslate = i.currentTranslate) + ) + s.emit('sliderMove', p), + (i.isMoved = !0), + (i.currentTranslate = g + i.startTranslate) + let E = !0, + x = r.resistanceRatio + if ( + (r.touchReleaseOnEdges && (x = 0), + g > 0 + ? (b && + y && + i.allowThresholdMove && + i.currentTranslate > + (r.centeredSlides + ? s.minTranslate() - + s.slidesSizesGrid[s.activeIndex + 1] - + ('auto' !== r.slidesPerView && + s.slides.length - r.slidesPerView >= 2 + ? s.slidesSizesGrid[s.activeIndex + 1] + + s.params.spaceBetween + : 0) - + s.params.spaceBetween + : s.minTranslate()) && + s.loopFix({ + direction: 'prev', + setTranslate: !0, + activeSlideIndex: 0, + }), + i.currentTranslate > s.minTranslate() && + ((E = !1), + r.resistance && + (i.currentTranslate = + s.minTranslate() - + 1 + + (-s.minTranslate() + i.startTranslate + g) ** x))) + : g < 0 && + (b && + y && + i.allowThresholdMove && + i.currentTranslate < + (r.centeredSlides + ? s.maxTranslate() + + s.slidesSizesGrid[s.slidesSizesGrid.length - 1] + + s.params.spaceBetween + + ('auto' !== r.slidesPerView && + s.slides.length - r.slidesPerView >= 2 + ? s.slidesSizesGrid[s.slidesSizesGrid.length - 1] + + s.params.spaceBetween + : 0) + : s.maxTranslate()) && + s.loopFix({ + direction: 'next', + setTranslate: !0, + activeSlideIndex: + s.slides.length - + ('auto' === r.slidesPerView + ? s.slidesPerViewDynamic() + : Math.ceil(parseFloat(r.slidesPerView, 10))), + }), + i.currentTranslate < s.maxTranslate() && + ((E = !1), + r.resistance && + (i.currentTranslate = + s.maxTranslate() + + 1 - + (s.maxTranslate() - i.startTranslate - g) ** x))), + E && (p.preventedByNestedSwiper = !0), + !s.allowSlideNext && + 'next' === s.swipeDirection && + i.currentTranslate < i.startTranslate && + (i.currentTranslate = i.startTranslate), + !s.allowSlidePrev && + 'prev' === s.swipeDirection && + i.currentTranslate > i.startTranslate && + (i.currentTranslate = i.startTranslate), + s.allowSlidePrev || + s.allowSlideNext || + (i.currentTranslate = i.startTranslate), + r.threshold > 0) + ) { + if (!(Math.abs(g) > r.threshold || i.allowThresholdMove)) + return void (i.currentTranslate = i.startTranslate) + if (!i.allowThresholdMove) + return ( + (i.allowThresholdMove = !0), + (n.startX = n.currentX), + (n.startY = n.currentY), + (i.currentTranslate = i.startTranslate), + void (n.diff = s.isHorizontal() + ? n.currentX - n.startX + : n.currentY - n.startY) + ) + } + r.followFinger && + !r.cssMode && + (((r.freeMode && r.freeMode.enabled && s.freeMode) || + r.watchSlidesProgress) && + (s.updateActiveIndex(), s.updateSlidesClasses()), + r.freeMode && + r.freeMode.enabled && + s.freeMode && + s.freeMode.onTouchMove(), + s.updateProgress(i.currentTranslate), + s.setTranslate(i.currentTranslate)) + } + function j(e) { + const t = this, + s = t.touchEventsData + let a, + i = e + i.originalEvent && (i = i.originalEvent) + if ('touchend' === i.type || 'touchcancel' === i.type) { + if ( + ((a = [...i.changedTouches].find((e) => e.identifier === s.touchId)), + !a || a.identifier !== s.touchId) + ) + return + } else { + if (null !== s.touchId) return + if (i.pointerId !== s.pointerId) return + a = i + } + if ( + ['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes( + i.type + ) + ) { + if ( + !( + ['pointercancel', 'contextmenu'].includes(i.type) && + (t.browser.isSafari || t.browser.isWebView) + ) + ) + return + } + ;(s.pointerId = null), (s.touchId = null) + const { + params: r, + touches: n, + rtlTranslate: d, + slidesGrid: c, + enabled: p, + } = t + if (!p) return + if (!r.simulateTouch && 'mouse' === i.pointerType) return + if ( + (s.allowTouchCallbacks && t.emit('touchEnd', i), + (s.allowTouchCallbacks = !1), + !s.isTouched) + ) + return ( + s.isMoved && r.grabCursor && t.setGrabCursor(!1), + (s.isMoved = !1), + void (s.startMoving = !1) + ) + r.grabCursor && + s.isMoved && + s.isTouched && + (!0 === t.allowSlideNext || !0 === t.allowSlidePrev) && + t.setGrabCursor(!1) + const u = o(), + m = u - s.touchStartTime + if (t.allowClick) { + const e = i.path || (i.composedPath && i.composedPath()) + t.updateClickedSlide((e && e[0]) || i.target, e), + t.emit('tap click', i), + m < 300 && + u - s.lastClickTime < 300 && + t.emit('doubleTap doubleClick', i) + } + if ( + ((s.lastClickTime = o()), + l(() => { + t.destroyed || (t.allowClick = !0) + }), + !s.isTouched || + !s.isMoved || + !t.swipeDirection || + (0 === n.diff && !s.loopSwapReset) || + (s.currentTranslate === s.startTranslate && !s.loopSwapReset)) + ) + return (s.isTouched = !1), (s.isMoved = !1), void (s.startMoving = !1) + let h + if ( + ((s.isTouched = !1), + (s.isMoved = !1), + (s.startMoving = !1), + (h = r.followFinger + ? d + ? t.translate + : -t.translate + : -s.currentTranslate), + r.cssMode) + ) + return + if (r.freeMode && r.freeMode.enabled) + return void t.freeMode.onTouchEnd({ currentPos: h }) + const f = h >= -t.maxTranslate() && !t.params.loop + let g = 0, + v = t.slidesSizesGrid[0] + for ( + let e = 0; + e < c.length; + e += e < r.slidesPerGroupSkip ? 1 : r.slidesPerGroup + ) { + const t = e < r.slidesPerGroupSkip - 1 ? 1 : r.slidesPerGroup + void 0 !== c[e + t] + ? (f || (h >= c[e] && h < c[e + t])) && ((g = e), (v = c[e + t] - c[e])) + : (f || h >= c[e]) && ((g = e), (v = c[c.length - 1] - c[c.length - 2])) + } + let w = null, + b = null + r.rewind && + (t.isBeginning + ? (b = + r.virtual && r.virtual.enabled && t.virtual + ? t.virtual.slides.length - 1 + : t.slides.length - 1) + : t.isEnd && (w = 0)) + const y = (h - c[g]) / v, + E = g < r.slidesPerGroupSkip - 1 ? 1 : r.slidesPerGroup + if (m > r.longSwipesMs) { + if (!r.longSwipes) return void t.slideTo(t.activeIndex) + 'next' === t.swipeDirection && + (y >= r.longSwipesRatio + ? t.slideTo(r.rewind && t.isEnd ? w : g + E) + : t.slideTo(g)), + 'prev' === t.swipeDirection && + (y > 1 - r.longSwipesRatio + ? t.slideTo(g + E) + : null !== b && y < 0 && Math.abs(y) > r.longSwipesRatio + ? t.slideTo(b) + : t.slideTo(g)) + } else { + if (!r.shortSwipes) return void t.slideTo(t.activeIndex) + t.navigation && + (i.target === t.navigation.nextEl || i.target === t.navigation.prevEl) + ? i.target === t.navigation.nextEl + ? t.slideTo(g + E) + : t.slideTo(g) + : ('next' === t.swipeDirection && t.slideTo(null !== w ? w : g + E), + 'prev' === t.swipeDirection && t.slideTo(null !== b ? b : g)) + } + } + function U() { + const e = this, + { params: t, el: s } = e + if (s && 0 === s.offsetWidth) return + t.breakpoints && e.setBreakpoint() + const { allowSlideNext: a, allowSlidePrev: i, snapGrid: r } = e, + n = e.virtual && e.params.virtual.enabled + ;(e.allowSlideNext = !0), + (e.allowSlidePrev = !0), + e.updateSize(), + e.updateSlides(), + e.updateSlidesClasses() + const l = n && t.loop + !('auto' === t.slidesPerView || t.slidesPerView > 1) || + !e.isEnd || + e.isBeginning || + e.params.centeredSlides || + l + ? e.params.loop && !n + ? e.slideToLoop(e.realIndex, 0, !1, !0) + : e.slideTo(e.activeIndex, 0, !1, !0) + : e.slideTo(e.slides.length - 1, 0, !1, !0), + e.autoplay && + e.autoplay.running && + e.autoplay.paused && + (clearTimeout(e.autoplay.resizeTimeout), + (e.autoplay.resizeTimeout = setTimeout(() => { + e.autoplay && + e.autoplay.running && + e.autoplay.paused && + e.autoplay.resume() + }, 500))), + (e.allowSlidePrev = i), + (e.allowSlideNext = a), + e.params.watchOverflow && r !== e.snapGrid && e.checkOverflow() + } + function K(e) { + const t = this + t.enabled && + (t.allowClick || + (t.params.preventClicks && e.preventDefault(), + t.params.preventClicksPropagation && + t.animating && + (e.stopPropagation(), e.stopImmediatePropagation()))) + } + function Z() { + const e = this, + { wrapperEl: t, rtlTranslate: s, enabled: a } = e + if (!a) return + let i + ;(e.previousTranslate = e.translate), + e.isHorizontal() + ? (e.translate = -t.scrollLeft) + : (e.translate = -t.scrollTop), + 0 === e.translate && (e.translate = 0), + e.updateActiveIndex(), + e.updateSlidesClasses() + const r = e.maxTranslate() - e.minTranslate() + ;(i = 0 === r ? 0 : (e.translate - e.minTranslate()) / r), + i !== e.progress && e.updateProgress(s ? -e.translate : e.translate), + e.emit('setTranslate', e.translate, !1) + } + function Q(e) { + const t = this + X(t, e.target), + t.params.cssMode || + ('auto' !== t.params.slidesPerView && !t.params.autoHeight) || + t.update() + } + function J() { + const e = this + e.documentTouchHandlerProceeded || + ((e.documentTouchHandlerProceeded = !0), + e.params.touchReleaseOnEdges && (e.el.style.touchAction = 'auto')) + } + const ee = (e, t) => { + const s = a(), + { params: i, el: r, wrapperEl: n, device: l } = e, + o = !!i.nested, + d = 'on' === t ? 'addEventListener' : 'removeEventListener', + c = t + r && + 'string' != typeof r && + (s[d]('touchstart', e.onDocumentTouchStart, { passive: !1, capture: o }), + r[d]('touchstart', e.onTouchStart, { passive: !1 }), + r[d]('pointerdown', e.onTouchStart, { passive: !1 }), + s[d]('touchmove', e.onTouchMove, { passive: !1, capture: o }), + s[d]('pointermove', e.onTouchMove, { passive: !1, capture: o }), + s[d]('touchend', e.onTouchEnd, { passive: !0 }), + s[d]('pointerup', e.onTouchEnd, { passive: !0 }), + s[d]('pointercancel', e.onTouchEnd, { passive: !0 }), + s[d]('touchcancel', e.onTouchEnd, { passive: !0 }), + s[d]('pointerout', e.onTouchEnd, { passive: !0 }), + s[d]('pointerleave', e.onTouchEnd, { passive: !0 }), + s[d]('contextmenu', e.onTouchEnd, { passive: !0 }), + (i.preventClicks || i.preventClicksPropagation) && + r[d]('click', e.onClick, !0), + i.cssMode && n[d]('scroll', e.onScroll), + i.updateOnWindowResize + ? e[c]( + l.ios || l.android + ? 'resize orientationchange observerUpdate' + : 'resize observerUpdate', + U, + !0 + ) + : e[c]('observerUpdate', U, !0), + r[d]('load', e.onLoad, { capture: !0 })) + } + const te = (e, t) => e.grid && t.grid && t.grid.rows > 1 + var se = { + init: !0, + direction: 'horizontal', + oneWayMovement: !1, + swiperElementNodeName: 'SWIPER-CONTAINER', + touchEventsTarget: 'wrapper', + initialSlide: 0, + speed: 300, + cssMode: !1, + updateOnWindowResize: !0, + resizeObserver: !0, + nested: !1, + createElements: !1, + eventsPrefix: 'swiper', + enabled: !0, + focusableElements: 'input, select, option, textarea, button, video, label', + width: null, + height: null, + preventInteractionOnTransition: !1, + userAgent: null, + url: null, + edgeSwipeDetection: !1, + edgeSwipeThreshold: 20, + autoHeight: !1, + setWrapperSize: !1, + virtualTranslate: !1, + effect: 'slide', + breakpoints: void 0, + breakpointsBase: 'window', + spaceBetween: 0, + slidesPerView: 1, + slidesPerGroup: 1, + slidesPerGroupSkip: 0, + slidesPerGroupAuto: !1, + centeredSlides: !1, + centeredSlidesBounds: !1, + slidesOffsetBefore: 0, + slidesOffsetAfter: 0, + normalizeSlideIndex: !0, + centerInsufficientSlides: !1, + watchOverflow: !0, + roundLengths: !1, + touchRatio: 1, + touchAngle: 45, + simulateTouch: !0, + shortSwipes: !0, + longSwipes: !0, + longSwipesRatio: 0.5, + longSwipesMs: 300, + followFinger: !0, + allowTouchMove: !0, + threshold: 5, + touchMoveStopPropagation: !1, + touchStartPreventDefault: !0, + touchStartForcePreventDefault: !1, + touchReleaseOnEdges: !1, + uniqueNavElements: !0, + resistance: !0, + resistanceRatio: 0.85, + watchSlidesProgress: !1, + grabCursor: !1, + preventClicks: !0, + preventClicksPropagation: !0, + slideToClickedSlide: !1, + loop: !1, + loopAddBlankSlides: !0, + loopAdditionalSlides: 0, + loopPreventsSliding: !0, + rewind: !1, + allowSlidePrev: !0, + allowSlideNext: !0, + swipeHandler: null, + noSwiping: !0, + noSwipingClass: 'swiper-no-swiping', + noSwipingSelector: null, + passiveListeners: !0, + maxBackfaceHiddenSlides: 10, + containerModifierClass: 'swiper-', + slideClass: 'swiper-slide', + slideBlankClass: 'swiper-slide-blank', + slideActiveClass: 'swiper-slide-active', + slideVisibleClass: 'swiper-slide-visible', + slideFullyVisibleClass: 'swiper-slide-fully-visible', + slideNextClass: 'swiper-slide-next', + slidePrevClass: 'swiper-slide-prev', + wrapperClass: 'swiper-wrapper', + lazyPreloaderClass: 'swiper-lazy-preloader', + lazyPreloadPrevNext: 0, + runCallbacksOnInit: !0, + _emitClasses: !1, + } + function ae(e, t) { + return function (s = {}) { + const a = Object.keys(s)[0], + i = s[a] + 'object' == typeof i && null !== i + ? (!0 === e[a] && (e[a] = { enabled: !0 }), + 'navigation' === a && + e[a] && + e[a].enabled && + !e[a].prevEl && + !e[a].nextEl && + (e[a].auto = !0), + ['pagination', 'scrollbar'].indexOf(a) >= 0 && + e[a] && + e[a].enabled && + !e[a].el && + (e[a].auto = !0), + a in e && 'enabled' in i + ? ('object' != typeof e[a] || + 'enabled' in e[a] || + (e[a].enabled = !0), + e[a] || (e[a] = { enabled: !1 }), + u(t, s)) + : u(t, s)) + : u(t, s) + } + } + const ie = { + eventsEmitter: O, + update: H, + translate: N, + transition: { + setTransition: function (e, t) { + const s = this + s.params.cssMode || + ((s.wrapperEl.style.transitionDuration = `${e}ms`), + (s.wrapperEl.style.transitionDelay = 0 === e ? '0ms' : '')), + s.emit('setTransition', e, t) + }, + transitionStart: function (e = !0, t) { + const s = this, + { params: a } = s + a.cssMode || + (a.autoHeight && s.updateAutoHeight(), + R({ swiper: s, runCallbacks: e, direction: t, step: 'Start' })) + }, + transitionEnd: function (e = !0, t) { + const s = this, + { params: a } = s + ;(s.animating = !1), + a.cssMode || + (s.setTransition(0), + R({ swiper: s, runCallbacks: e, direction: t, step: 'End' })) + }, + }, + slide: V, + loop: q, + grabCursor: { + setGrabCursor: function (e) { + const t = this + if ( + !t.params.simulateTouch || + (t.params.watchOverflow && t.isLocked) || + t.params.cssMode + ) + return + const s = + 'container' === t.params.touchEventsTarget ? t.el : t.wrapperEl + t.isElement && (t.__preventObserver__ = !0), + (s.style.cursor = 'move'), + (s.style.cursor = e ? 'grabbing' : 'grab'), + t.isElement && + requestAnimationFrame(() => { + t.__preventObserver__ = !1 + }) + }, + unsetGrabCursor: function () { + const e = this + ;(e.params.watchOverflow && e.isLocked) || + e.params.cssMode || + (e.isElement && (e.__preventObserver__ = !0), + (e[ + 'container' === e.params.touchEventsTarget ? 'el' : 'wrapperEl' + ].style.cursor = ''), + e.isElement && + requestAnimationFrame(() => { + e.__preventObserver__ = !1 + })) + }, + }, + events: { + attachEvents: function () { + const e = this, + { params: t } = e + ;(e.onTouchStart = F.bind(e)), + (e.onTouchMove = W.bind(e)), + (e.onTouchEnd = j.bind(e)), + (e.onDocumentTouchStart = J.bind(e)), + t.cssMode && (e.onScroll = Z.bind(e)), + (e.onClick = K.bind(e)), + (e.onLoad = Q.bind(e)), + ee(e, 'on') + }, + detachEvents: function () { + ee(this, 'off') + }, + }, + breakpoints: { + setBreakpoint: function () { + const e = this, + { realIndex: t, initialized: s, params: i, el: r } = e, + n = i.breakpoints + if (!n || (n && 0 === Object.keys(n).length)) return + const l = a(), + o = + 'window' !== i.breakpointsBase && i.breakpointsBase + ? 'container' + : i.breakpointsBase, + d = + ['window', 'container'].includes(i.breakpointsBase) || + !i.breakpointsBase + ? e.el + : l.querySelector(i.breakpointsBase), + c = e.getBreakpoint(n, o, d) + if (!c || e.currentBreakpoint === c) return + const p = (c in n ? n[c] : void 0) || e.originalParams, + m = te(e, i), + h = te(e, p), + f = e.params.grabCursor, + g = p.grabCursor, + v = i.enabled + m && !h + ? (r.classList.remove( + `${i.containerModifierClass}grid`, + `${i.containerModifierClass}grid-column` + ), + e.emitContainerClasses()) + : !m && + h && + (r.classList.add(`${i.containerModifierClass}grid`), + ((p.grid.fill && 'column' === p.grid.fill) || + (!p.grid.fill && 'column' === i.grid.fill)) && + r.classList.add(`${i.containerModifierClass}grid-column`), + e.emitContainerClasses()), + f && !g ? e.unsetGrabCursor() : !f && g && e.setGrabCursor(), + ['navigation', 'pagination', 'scrollbar'].forEach((t) => { + if (void 0 === p[t]) return + const s = i[t] && i[t].enabled, + a = p[t] && p[t].enabled + s && !a && e[t].disable(), !s && a && e[t].enable() + }) + const w = p.direction && p.direction !== i.direction, + b = i.loop && (p.slidesPerView !== i.slidesPerView || w), + y = i.loop + w && s && e.changeDirection(), u(e.params, p) + const E = e.params.enabled, + x = e.params.loop + Object.assign(e, { + allowTouchMove: e.params.allowTouchMove, + allowSlideNext: e.params.allowSlideNext, + allowSlidePrev: e.params.allowSlidePrev, + }), + v && !E ? e.disable() : !v && E && e.enable(), + (e.currentBreakpoint = c), + e.emit('_beforeBreakpoint', p), + s && + (b + ? (e.loopDestroy(), e.loopCreate(t), e.updateSlides()) + : !y && x + ? (e.loopCreate(t), e.updateSlides()) + : y && !x && e.loopDestroy()), + e.emit('breakpoint', p) + }, + getBreakpoint: function (e, t = 'window', s) { + if (!e || ('container' === t && !s)) return + let a = !1 + const i = r(), + n = 'window' === t ? i.innerHeight : s.clientHeight, + l = Object.keys(e).map((e) => { + if ('string' == typeof e && 0 === e.indexOf('@')) { + const t = parseFloat(e.substr(1)) + return { value: n * t, point: e } + } + return { value: e, point: e } + }) + l.sort((e, t) => parseInt(e.value, 10) - parseInt(t.value, 10)) + for (let e = 0; e < l.length; e += 1) { + const { point: r, value: n } = l[e] + 'window' === t + ? i.matchMedia(`(min-width: ${n}px)`).matches && (a = r) + : n <= s.clientWidth && (a = r) + } + return a || 'max' + }, + }, + checkOverflow: { + checkOverflow: function () { + const e = this, + { isLocked: t, params: s } = e, + { slidesOffsetBefore: a } = s + if (a) { + const t = e.slides.length - 1, + s = e.slidesGrid[t] + e.slidesSizesGrid[t] + 2 * a + e.isLocked = e.size > s + } else e.isLocked = 1 === e.snapGrid.length + !0 === s.allowSlideNext && (e.allowSlideNext = !e.isLocked), + !0 === s.allowSlidePrev && (e.allowSlidePrev = !e.isLocked), + t && t !== e.isLocked && (e.isEnd = !1), + t !== e.isLocked && e.emit(e.isLocked ? 'lock' : 'unlock') + }, + }, + classes: { + addClasses: function () { + const e = this, + { classNames: t, params: s, rtl: a, el: i, device: r } = e, + n = (function (e, t) { + const s = [] + return ( + e.forEach((e) => { + 'object' == typeof e + ? Object.keys(e).forEach((a) => { + e[a] && s.push(t + a) + }) + : 'string' == typeof e && s.push(t + e) + }), + s + ) + })( + [ + 'initialized', + s.direction, + { 'free-mode': e.params.freeMode && s.freeMode.enabled }, + { autoheight: s.autoHeight }, + { rtl: a }, + { grid: s.grid && s.grid.rows > 1 }, + { + 'grid-column': + s.grid && s.grid.rows > 1 && 'column' === s.grid.fill, + }, + { android: r.android }, + { ios: r.ios }, + { 'css-mode': s.cssMode }, + { centered: s.cssMode && s.centeredSlides }, + { 'watch-progress': s.watchSlidesProgress }, + ], + s.containerModifierClass + ) + t.push(...n), i.classList.add(...t), e.emitContainerClasses() + }, + removeClasses: function () { + const { el: e, classNames: t } = this + e && + 'string' != typeof e && + (e.classList.remove(...t), this.emitContainerClasses()) + }, + }, + }, + re = {} + class ne { + constructor(...e) { + let t, s + 1 === e.length && + e[0].constructor && + 'Object' === Object.prototype.toString.call(e[0]).slice(8, -1) + ? (s = e[0]) + : ([t, s] = e), + s || (s = {}), + (s = u({}, s)), + t && !s.el && (s.el = t) + const i = a() + if ( + s.el && + 'string' == typeof s.el && + i.querySelectorAll(s.el).length > 1 + ) { + const e = [] + return ( + i.querySelectorAll(s.el).forEach((t) => { + const a = u({}, s, { el: t }) + e.push(new ne(a)) + }), + e + ) + } + const r = this + ;(r.__swiper__ = !0), + (r.support = A()), + (r.device = $({ userAgent: s.userAgent })), + (r.browser = k()), + (r.eventsListeners = {}), + (r.eventsAnyListeners = []), + (r.modules = [...r.__modules__]), + s.modules && Array.isArray(s.modules) && r.modules.push(...s.modules) + const n = {} + r.modules.forEach((e) => { + e({ + params: s, + swiper: r, + extendParams: ae(s, n), + on: r.on.bind(r), + once: r.once.bind(r), + off: r.off.bind(r), + emit: r.emit.bind(r), + }) + }) + const l = u({}, se, n) + return ( + (r.params = u({}, l, re, s)), + (r.originalParams = u({}, r.params)), + (r.passedParams = u({}, s)), + r.params && + r.params.on && + Object.keys(r.params.on).forEach((e) => { + r.on(e, r.params.on[e]) + }), + r.params && r.params.onAny && r.onAny(r.params.onAny), + Object.assign(r, { + enabled: r.params.enabled, + el: t, + classNames: [], + slides: [], + slidesGrid: [], + snapGrid: [], + slidesSizesGrid: [], + isHorizontal: () => 'horizontal' === r.params.direction, + isVertical: () => 'vertical' === r.params.direction, + activeIndex: 0, + realIndex: 0, + isBeginning: !0, + isEnd: !1, + translate: 0, + previousTranslate: 0, + progress: 0, + velocity: 0, + animating: !1, + cssOverflowAdjustment() { + return Math.trunc(this.translate / 2 ** 23) * 2 ** 23 + }, + allowSlideNext: r.params.allowSlideNext, + allowSlidePrev: r.params.allowSlidePrev, + touchEventsData: { + isTouched: void 0, + isMoved: void 0, + allowTouchCallbacks: void 0, + touchStartTime: void 0, + isScrolling: void 0, + currentTranslate: void 0, + startTranslate: void 0, + allowThresholdMove: void 0, + focusableElements: r.params.focusableElements, + lastClickTime: 0, + clickTimeout: void 0, + velocities: [], + allowMomentumBounce: void 0, + startMoving: void 0, + pointerId: null, + touchId: null, + }, + allowClick: !0, + allowTouchMove: r.params.allowTouchMove, + touches: { startX: 0, startY: 0, currentX: 0, currentY: 0, diff: 0 }, + imagesToLoad: [], + imagesLoaded: 0, + }), + r.emit('_swiper'), + r.params.init && r.init(), + r + ) + } + getDirectionLabel(e) { + return this.isHorizontal() + ? e + : { + width: 'height', + 'margin-top': 'margin-left', + 'margin-bottom ': 'margin-right', + 'margin-left': 'margin-top', + 'margin-right': 'margin-bottom', + 'padding-left': 'padding-top', + 'padding-right': 'padding-bottom', + marginRight: 'marginBottom', + }[e] + } + getSlideIndex(e) { + const { slidesEl: t, params: s } = this, + a = E(g(t, `.${s.slideClass}, swiper-slide`)[0]) + return E(e) - a + } + getSlideIndexByData(e) { + return this.getSlideIndex( + this.slides.find( + (t) => 1 * t.getAttribute('data-swiper-slide-index') === e + ) + ) + } + getSlideIndexWhenGrid(e) { + return ( + this.grid && + this.params.grid && + this.params.grid.rows > 1 && + ('column' === this.params.grid.fill + ? (e = Math.floor(e / this.params.grid.rows)) + : 'row' === this.params.grid.fill && + (e %= Math.ceil(this.slides.length / this.params.grid.rows))), + e + ) + } + recalcSlides() { + const { slidesEl: e, params: t } = this + this.slides = g(e, `.${t.slideClass}, swiper-slide`) + } + enable() { + const e = this + e.enabled || + ((e.enabled = !0), + e.params.grabCursor && e.setGrabCursor(), + e.emit('enable')) + } + disable() { + const e = this + e.enabled && + ((e.enabled = !1), + e.params.grabCursor && e.unsetGrabCursor(), + e.emit('disable')) + } + setProgress(e, t) { + const s = this + e = Math.min(Math.max(e, 0), 1) + const a = s.minTranslate(), + i = (s.maxTranslate() - a) * e + a + s.translateTo(i, void 0 === t ? 0 : t), + s.updateActiveIndex(), + s.updateSlidesClasses() + } + emitContainerClasses() { + const e = this + if (!e.params._emitClasses || !e.el) return + const t = e.el.className + .split(' ') + .filter( + (t) => + 0 === t.indexOf('swiper') || + 0 === t.indexOf(e.params.containerModifierClass) + ) + e.emit('_containerClasses', t.join(' ')) + } + getSlideClasses(e) { + const t = this + return t.destroyed + ? '' + : e.className + .split(' ') + .filter( + (e) => + 0 === e.indexOf('swiper-slide') || + 0 === e.indexOf(t.params.slideClass) + ) + .join(' ') + } + emitSlidesClasses() { + const e = this + if (!e.params._emitClasses || !e.el) return + const t = [] + e.slides.forEach((s) => { + const a = e.getSlideClasses(s) + t.push({ slideEl: s, classNames: a }), e.emit('_slideClass', s, a) + }), + e.emit('_slideClasses', t) + } + slidesPerViewDynamic(e = 'current', t = !1) { + const { + params: s, + slides: a, + slidesGrid: i, + slidesSizesGrid: r, + size: n, + activeIndex: l, + } = this + let o = 1 + if ('number' == typeof s.slidesPerView) return s.slidesPerView + if (s.centeredSlides) { + let e, + t = a[l] ? Math.ceil(a[l].swiperSlideSize) : 0 + for (let s = l + 1; s < a.length; s += 1) + a[s] && + !e && + ((t += Math.ceil(a[s].swiperSlideSize)), + (o += 1), + t > n && (e = !0)) + for (let s = l - 1; s >= 0; s -= 1) + a[s] && + !e && + ((t += a[s].swiperSlideSize), (o += 1), t > n && (e = !0)) + } else if ('current' === e) + for (let e = l + 1; e < a.length; e += 1) { + ;(t ? i[e] + r[e] - i[l] < n : i[e] - i[l] < n) && (o += 1) + } + else + for (let e = l - 1; e >= 0; e -= 1) { + i[l] - i[e] < n && (o += 1) + } + return o + } + update() { + const e = this + if (!e || e.destroyed) return + const { snapGrid: t, params: s } = e + function a() { + const t = e.rtlTranslate ? -1 * e.translate : e.translate, + s = Math.min(Math.max(t, e.maxTranslate()), e.minTranslate()) + e.setTranslate(s), e.updateActiveIndex(), e.updateSlidesClasses() + } + let i + if ( + (s.breakpoints && e.setBreakpoint(), + [...e.el.querySelectorAll('[loading="lazy"]')].forEach((t) => { + t.complete && X(e, t) + }), + e.updateSize(), + e.updateSlides(), + e.updateProgress(), + e.updateSlidesClasses(), + s.freeMode && s.freeMode.enabled && !s.cssMode) + ) + a(), s.autoHeight && e.updateAutoHeight() + else { + if ( + ('auto' === s.slidesPerView || s.slidesPerView > 1) && + e.isEnd && + !s.centeredSlides + ) { + const t = e.virtual && s.virtual.enabled ? e.virtual.slides : e.slides + i = e.slideTo(t.length - 1, 0, !1, !0) + } else i = e.slideTo(e.activeIndex, 0, !1, !0) + i || a() + } + s.watchOverflow && t !== e.snapGrid && e.checkOverflow(), e.emit('update') + } + changeDirection(e, t = !0) { + const s = this, + a = s.params.direction + return ( + e || (e = 'horizontal' === a ? 'vertical' : 'horizontal'), + e === a || + ('horizontal' !== e && 'vertical' !== e) || + (s.el.classList.remove(`${s.params.containerModifierClass}${a}`), + s.el.classList.add(`${s.params.containerModifierClass}${e}`), + s.emitContainerClasses(), + (s.params.direction = e), + s.slides.forEach((t) => { + 'vertical' === e ? (t.style.width = '') : (t.style.height = '') + }), + s.emit('changeDirection'), + t && s.update()), + s + ) + } + changeLanguageDirection(e) { + const t = this + ;(t.rtl && 'rtl' === e) || + (!t.rtl && 'ltr' === e) || + ((t.rtl = 'rtl' === e), + (t.rtlTranslate = 'horizontal' === t.params.direction && t.rtl), + t.rtl + ? (t.el.classList.add(`${t.params.containerModifierClass}rtl`), + (t.el.dir = 'rtl')) + : (t.el.classList.remove(`${t.params.containerModifierClass}rtl`), + (t.el.dir = 'ltr')), + t.update()) + } + mount(e) { + const t = this + if (t.mounted) return !0 + let s = e || t.params.el + if (('string' == typeof s && (s = document.querySelector(s)), !s)) + return !1 + ;(s.swiper = t), + s.parentNode && + s.parentNode.host && + s.parentNode.host.nodeName === + t.params.swiperElementNodeName.toUpperCase() && + (t.isElement = !0) + const a = () => + `.${(t.params.wrapperClass || '').trim().split(' ').join('.')}` + let i = (() => { + if (s && s.shadowRoot && s.shadowRoot.querySelector) { + return s.shadowRoot.querySelector(a()) + } + return g(s, a())[0] + })() + return ( + !i && + t.params.createElements && + ((i = w('div', t.params.wrapperClass)), + s.append(i), + g(s, `.${t.params.slideClass}`).forEach((e) => { + i.append(e) + })), + Object.assign(t, { + el: s, + wrapperEl: i, + slidesEl: + t.isElement && !s.parentNode.host.slideSlots + ? s.parentNode.host + : i, + hostEl: t.isElement ? s.parentNode.host : s, + mounted: !0, + rtl: 'rtl' === s.dir.toLowerCase() || 'rtl' === y(s, 'direction'), + rtlTranslate: + 'horizontal' === t.params.direction && + ('rtl' === s.dir.toLowerCase() || 'rtl' === y(s, 'direction')), + wrongRTL: '-webkit-box' === y(i, 'display'), + }), + !0 + ) + } + init(e) { + const t = this + if (t.initialized) return t + if (!1 === t.mount(e)) return t + t.emit('beforeInit'), + t.params.breakpoints && t.setBreakpoint(), + t.addClasses(), + t.updateSize(), + t.updateSlides(), + t.params.watchOverflow && t.checkOverflow(), + t.params.grabCursor && t.enabled && t.setGrabCursor(), + t.params.loop && t.virtual && t.params.virtual.enabled + ? t.slideTo( + t.params.initialSlide + t.virtual.slidesBefore, + 0, + t.params.runCallbacksOnInit, + !1, + !0 + ) + : t.slideTo( + t.params.initialSlide, + 0, + t.params.runCallbacksOnInit, + !1, + !0 + ), + t.params.loop && t.loopCreate(void 0, !0), + t.attachEvents() + const s = [...t.el.querySelectorAll('[loading="lazy"]')] + return ( + t.isElement && s.push(...t.hostEl.querySelectorAll('[loading="lazy"]')), + s.forEach((e) => { + e.complete + ? X(t, e) + : e.addEventListener('load', (e) => { + X(t, e.target) + }) + }), + Y(t), + (t.initialized = !0), + Y(t), + t.emit('init'), + t.emit('afterInit'), + t + ) + } + destroy(e = !0, t = !0) { + const s = this, + { params: a, el: i, wrapperEl: r, slides: n } = s + return ( + void 0 === s.params || + s.destroyed || + (s.emit('beforeDestroy'), + (s.initialized = !1), + s.detachEvents(), + a.loop && s.loopDestroy(), + t && + (s.removeClasses(), + i && 'string' != typeof i && i.removeAttribute('style'), + r && r.removeAttribute('style'), + n && + n.length && + n.forEach((e) => { + e.classList.remove( + a.slideVisibleClass, + a.slideFullyVisibleClass, + a.slideActiveClass, + a.slideNextClass, + a.slidePrevClass + ), + e.removeAttribute('style'), + e.removeAttribute('data-swiper-slide-index') + })), + s.emit('destroy'), + Object.keys(s.eventsListeners).forEach((e) => { + s.off(e) + }), + !1 !== e && + (s.el && 'string' != typeof s.el && (s.el.swiper = null), + (function (e) { + const t = e + Object.keys(t).forEach((e) => { + try { + t[e] = null + } catch (e) {} + try { + delete t[e] + } catch (e) {} + }) + })(s)), + (s.destroyed = !0)), + null + ) + } + static extendDefaults(e) { + u(re, e) + } + static get extendedDefaults() { + return re + } + static get defaults() { + return se + } + static installModule(e) { + ne.prototype.__modules__ || (ne.prototype.__modules__ = []) + const t = ne.prototype.__modules__ + 'function' == typeof e && t.indexOf(e) < 0 && t.push(e) + } + static use(e) { + return Array.isArray(e) + ? (e.forEach((e) => ne.installModule(e)), ne) + : (ne.installModule(e), ne) + } + } + function le(e, t, s, a) { + return ( + e.params.createElements && + Object.keys(a).forEach((i) => { + if (!s[i] && !0 === s.auto) { + let r = g(e.el, `.${a[i]}`)[0] + r || ((r = w('div', a[i])), (r.className = a[i]), e.el.append(r)), + (s[i] = r), + (t[i] = r) + } + }), + s + ) + } + Object.keys(ie).forEach((e) => { + Object.keys(ie[e]).forEach((t) => { + ne.prototype[t] = ie[e][t] + }) + }), + ne.use([ + function ({ swiper: e, on: t, emit: s }) { + const a = r() + let i = null, + n = null + const l = () => { + e && + !e.destroyed && + e.initialized && + (s('beforeResize'), s('resize')) + }, + o = () => { + e && !e.destroyed && e.initialized && s('orientationchange') + } + t('init', () => { + e.params.resizeObserver && void 0 !== a.ResizeObserver + ? e && + !e.destroyed && + e.initialized && + ((i = new ResizeObserver((t) => { + n = a.requestAnimationFrame(() => { + const { width: s, height: a } = e + let i = s, + r = a + t.forEach( + ({ contentBoxSize: t, contentRect: s, target: a }) => { + ;(a && a !== e.el) || + ((i = s ? s.width : (t[0] || t).inlineSize), + (r = s ? s.height : (t[0] || t).blockSize)) + } + ), + (i === s && r === a) || l() + }) + })), + i.observe(e.el)) + : (a.addEventListener('resize', l), + a.addEventListener('orientationchange', o)) + }), + t('destroy', () => { + n && a.cancelAnimationFrame(n), + i && i.unobserve && e.el && (i.unobserve(e.el), (i = null)), + a.removeEventListener('resize', l), + a.removeEventListener('orientationchange', o) + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: a }) { + const i = [], + n = r(), + l = (t, s = {}) => { + const r = new (n.MutationObserver || n.WebkitMutationObserver)( + (t) => { + if (e.__preventObserver__) return + if (1 === t.length) return void a('observerUpdate', t[0]) + const s = function () { + a('observerUpdate', t[0]) + } + n.requestAnimationFrame + ? n.requestAnimationFrame(s) + : n.setTimeout(s, 0) + } + ) + r.observe(t, { + attributes: void 0 === s.attributes || s.attributes, + childList: e.isElement || (void 0 === s.childList || s).childList, + characterData: void 0 === s.characterData || s.characterData, + }), + i.push(r) + } + t({ observer: !1, observeParents: !1, observeSlideChildren: !1 }), + s('init', () => { + if (e.params.observer) { + if (e.params.observeParents) { + const t = x(e.hostEl) + for (let e = 0; e < t.length; e += 1) l(t[e]) + } + l(e.hostEl, { childList: e.params.observeSlideChildren }), + l(e.wrapperEl, { attributes: !1 }) + } + }), + s('destroy', () => { + i.forEach((e) => { + e.disconnect() + }), + i.splice(0, i.length) + }) + }, + ]) + const oe = + '' + function de(e = '') { + return `.${e + .trim() + .replace(/([\.:!+\/()[\]])/g, '\\$1') + .replace(/ /g, '.')}` + } + function ce(e) { + const t = this, + { params: s, slidesEl: a } = t + s.loop && t.loopDestroy() + const i = (e) => { + if ('string' == typeof e) { + const t = document.createElement('div') + P(t, e), a.append(t.children[0]), P(t, '') + } else a.append(e) + } + if ('object' == typeof e && 'length' in e) + for (let t = 0; t < e.length; t += 1) e[t] && i(e[t]) + else i(e) + t.recalcSlides(), + s.loop && t.loopCreate(), + (s.observer && !t.isElement) || t.update() + } + function pe(e) { + const t = this, + { params: s, activeIndex: a, slidesEl: i } = t + s.loop && t.loopDestroy() + let r = a + 1 + const n = (e) => { + if ('string' == typeof e) { + const t = document.createElement('div') + P(t, e), i.prepend(t.children[0]), P(t, '') + } else i.prepend(e) + } + if ('object' == typeof e && 'length' in e) { + for (let t = 0; t < e.length; t += 1) e[t] && n(e[t]) + r = a + e.length + } else n(e) + t.recalcSlides(), + s.loop && t.loopCreate(), + (s.observer && !t.isElement) || t.update(), + t.slideTo(r, 0, !1) + } + function ue(e, t) { + const s = this, + { params: a, activeIndex: i, slidesEl: r } = s + let n = i + a.loop && ((n -= s.loopedSlides), s.loopDestroy(), s.recalcSlides()) + const l = s.slides.length + if (e <= 0) return void s.prependSlide(t) + if (e >= l) return void s.appendSlide(t) + let o = n > e ? n + 1 : n + const d = [] + for (let t = l - 1; t >= e; t -= 1) { + const e = s.slides[t] + e.remove(), d.unshift(e) + } + if ('object' == typeof t && 'length' in t) { + for (let e = 0; e < t.length; e += 1) t[e] && r.append(t[e]) + o = n > e ? n + t.length : n + } else r.append(t) + for (let e = 0; e < d.length; e += 1) r.append(d[e]) + s.recalcSlides(), + a.loop && s.loopCreate(), + (a.observer && !s.isElement) || s.update(), + a.loop ? s.slideTo(o + s.loopedSlides, 0, !1) : s.slideTo(o, 0, !1) + } + function me(e) { + const t = this, + { params: s, activeIndex: a } = t + let i = a + s.loop && ((i -= t.loopedSlides), t.loopDestroy()) + let r, + n = i + if ('object' == typeof e && 'length' in e) { + for (let s = 0; s < e.length; s += 1) + (r = e[s]), t.slides[r] && t.slides[r].remove(), r < n && (n -= 1) + n = Math.max(n, 0) + } else + (r = e), + t.slides[r] && t.slides[r].remove(), + r < n && (n -= 1), + (n = Math.max(n, 0)) + t.recalcSlides(), + s.loop && t.loopCreate(), + (s.observer && !t.isElement) || t.update(), + s.loop ? t.slideTo(n + t.loopedSlides, 0, !1) : t.slideTo(n, 0, !1) + } + function he() { + const e = this, + t = [] + for (let s = 0; s < e.slides.length; s += 1) t.push(s) + e.removeSlide(t) + } + function fe(e) { + const { + effect: t, + swiper: s, + on: a, + setTranslate: i, + setTransition: r, + overwriteParams: n, + perspective: l, + recreateShadows: o, + getEffectParams: d, + } = e + let c + a('beforeInit', () => { + if (s.params.effect !== t) return + s.classNames.push(`${s.params.containerModifierClass}${t}`), + l && l() && s.classNames.push(`${s.params.containerModifierClass}3d`) + const e = n ? n() : {} + Object.assign(s.params, e), Object.assign(s.originalParams, e) + }), + a('setTranslate _virtualUpdated', () => { + s.params.effect === t && i() + }), + a('setTransition', (e, a) => { + s.params.effect === t && r(a) + }), + a('transitionEnd', () => { + if (s.params.effect === t && o) { + if (!d || !d().slideShadows) return + s.slides.forEach((e) => { + e.querySelectorAll( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ).forEach((e) => e.remove()) + }), + o() + } + }), + a('virtualUpdate', () => { + s.params.effect === t && + (s.slides.length || (c = !0), + requestAnimationFrame(() => { + c && s.slides && s.slides.length && (i(), (c = !1)) + })) + }) + } + function ge(e, t) { + const s = f(t) + return ( + s !== t && + ((s.style.backfaceVisibility = 'hidden'), + (s.style['-webkit-backface-visibility'] = 'hidden')), + s + ) + } + function ve({ swiper: e, duration: t, transformElements: s, allSlides: a }) { + const { activeIndex: i } = e + if (e.params.virtualTranslate && 0 !== t) { + let t, + r = !1 + ;(t = a + ? s + : s.filter((t) => { + const s = t.classList.contains('swiper-slide-transform') + ? ((t) => { + if (!t.parentElement) + return e.slides.find( + (e) => e.shadowRoot && e.shadowRoot === t.parentNode + ) + return t.parentElement + })(t) + : t + return e.getSlideIndex(s) === i + })), + t.forEach((t) => { + S(t, () => { + if (r) return + if (!e || e.destroyed) return + ;(r = !0), (e.animating = !1) + const t = new window.CustomEvent('transitionend', { + bubbles: !0, + cancelable: !0, + }) + e.wrapperEl.dispatchEvent(t) + }) + }) + } + } + function we(e, t, s) { + const a = `swiper-slide-shadow${s ? `-${s}` : ''}${ + e ? ` swiper-slide-shadow-${e}` : '' + }`, + i = f(t) + let r = i.querySelector(`.${a.split(' ').join('.')}`) + return r || ((r = w('div', a.split(' '))), i.append(r)), r + } + const be = [ + function ({ swiper: e, extendParams: t, on: s, emit: i }) { + let r + t({ + virtual: { + enabled: !1, + slides: [], + cache: !0, + slidesPerViewAutoSlideSize: 320, + renderSlide: null, + renderExternal: null, + renderExternalUpdate: !0, + addSlidesBefore: 0, + addSlidesAfter: 0, + }, + }) + const n = a() + e.virtual = { + cache: {}, + from: void 0, + to: void 0, + slides: [], + offset: 0, + slidesGrid: [], + } + const l = n.createElement('div') + function o(t, s) { + const a = e.params.virtual + if (a.cache && e.virtual.cache[s]) return e.virtual.cache[s] + let i + return ( + a.renderSlide + ? ((i = a.renderSlide.call(e, t, s)), + 'string' == typeof i && (P(l, i), (i = l.children[0]))) + : (i = e.isElement + ? w('swiper-slide') + : w('div', e.params.slideClass)), + i.setAttribute('data-swiper-slide-index', s), + a.renderSlide || P(i, t), + a.cache && (e.virtual.cache[s] = i), + i + ) + } + function d(t, s, a) { + const { + slidesPerGroup: r, + centeredSlides: n, + slidesPerView: l, + loop: d, + initialSlide: c, + } = e.params + if (s && !d && c > 0) return + const { + addSlidesBefore: p, + addSlidesAfter: u, + slidesPerViewAutoSlideSize: m, + } = e.params.virtual, + { from: h, to: f, slides: v, slidesGrid: w, offset: b } = e.virtual + e.params.cssMode || e.updateActiveIndex() + const y = void 0 === a ? e.activeIndex || 0 : a + let E, x, S, T + if ( + ((E = e.rtlTranslate ? 'right' : e.isHorizontal() ? 'left' : 'top'), + 'auto' === l) + ) + if (m) { + let t = e.size + t || + (t = e.isHorizontal() + ? e.el.getBoundingClientRect().width + : e.el.getBoundingClientRect().height), + (x = Math.max(1, Math.ceil(t / m))) + } else x = 1 + else x = l + n + ? ((S = Math.floor(x / 2) + r + u), (T = Math.floor(x / 2) + r + p)) + : ((S = x + (r - 1) + u), (T = (d ? x : r) + p)) + let M = y - T, + C = y + S + d || ((M = Math.max(M, 0)), (C = Math.min(C, v.length - 1))) + let P = (e.slidesGrid[M] || 0) - (e.slidesGrid[0] || 0) + function L() { + e.updateSlides(), + e.updateProgress(), + e.updateSlidesClasses(), + i('virtualUpdate') + } + if ( + (d && y >= T + ? ((M -= T), n || (P += e.slidesGrid[0])) + : d && y < T && ((M = -T), n && (P += e.slidesGrid[0])), + Object.assign(e.virtual, { + from: M, + to: C, + offset: P, + slidesGrid: e.slidesGrid, + slidesBefore: T, + slidesAfter: S, + }), + h === M && f === C && !t) + ) + return ( + e.slidesGrid !== w && + P !== b && + e.slides.forEach((t) => { + t.style[E] = P - Math.abs(e.cssOverflowAdjustment()) + 'px' + }), + e.updateProgress(), + void i('virtualUpdate') + ) + if (e.params.virtual.renderExternal) + return ( + e.params.virtual.renderExternal.call(e, { + offset: P, + from: M, + to: C, + slides: (function () { + const e = [] + for (let t = M; t <= C; t += 1) e.push(v[t]) + return e + })(), + }), + void (e.params.virtual.renderExternalUpdate + ? L() + : i('virtualUpdate')) + ) + const I = [], + z = [], + A = (e) => { + let t = e + return ( + e < 0 ? (t = v.length + e) : t >= v.length && (t -= v.length), t + ) + } + if (t) + e.slides + .filter((t) => t.matches(`.${e.params.slideClass}, swiper-slide`)) + .forEach((e) => { + e.remove() + }) + else + for (let t = h; t <= f; t += 1) + if (t < M || t > C) { + const s = A(t) + e.slides + .filter((t) => + t.matches( + `.${e.params.slideClass}[data-swiper-slide-index="${s}"], swiper-slide[data-swiper-slide-index="${s}"]` + ) + ) + .forEach((e) => { + e.remove() + }) + } + const $ = d ? -v.length : 0, + k = d ? 2 * v.length : v.length + for (let e = $; e < k; e += 1) + if (e >= M && e <= C) { + const s = A(e) + void 0 === f || t + ? z.push(s) + : (e > f && z.push(s), e < h && I.push(s)) + } + if ( + (z.forEach((t) => { + e.slidesEl.append(o(v[t], t)) + }), + d) + ) + for (let t = I.length - 1; t >= 0; t -= 1) { + const s = I[t] + e.slidesEl.prepend(o(v[s], s)) + } + else + I.sort((e, t) => t - e), + I.forEach((t) => { + e.slidesEl.prepend(o(v[t], t)) + }) + g(e.slidesEl, '.swiper-slide, swiper-slide').forEach((t) => { + t.style[E] = P - Math.abs(e.cssOverflowAdjustment()) + 'px' + }), + L() + } + s('beforeInit', () => { + if (!e.params.virtual.enabled) return + let t + if (void 0 === e.passedParams.virtual.slides) { + const s = [...e.slidesEl.children].filter((t) => + t.matches(`.${e.params.slideClass}, swiper-slide`) + ) + s && + s.length && + ((e.virtual.slides = [...s]), + (t = !0), + s.forEach((t, s) => { + t.setAttribute('data-swiper-slide-index', s), + (e.virtual.cache[s] = t), + t.remove() + })) + } + t || (e.virtual.slides = e.params.virtual.slides), + e.classNames.push(`${e.params.containerModifierClass}virtual`), + (e.params.watchSlidesProgress = !0), + (e.originalParams.watchSlidesProgress = !0), + d(!1, !0) + }), + s('setTranslate', () => { + e.params.virtual.enabled && + (e.params.cssMode && !e._immediateVirtual + ? (clearTimeout(r), + (r = setTimeout(() => { + d() + }, 100))) + : d()) + }), + s('init update resize', () => { + e.params.virtual.enabled && + e.params.cssMode && + m(e.wrapperEl, '--swiper-virtual-size', `${e.virtualSize}px`) + }), + Object.assign(e.virtual, { + appendSlide: function (t) { + if ('object' == typeof t && 'length' in t) + for (let s = 0; s < t.length; s += 1) + t[s] && e.virtual.slides.push(t[s]) + else e.virtual.slides.push(t) + d(!0) + }, + prependSlide: function (t) { + const s = e.activeIndex + let a = s + 1, + i = 1 + if (Array.isArray(t)) { + for (let s = 0; s < t.length; s += 1) + t[s] && e.virtual.slides.unshift(t[s]) + ;(a = s + t.length), (i = t.length) + } else e.virtual.slides.unshift(t) + if (e.params.virtual.cache) { + const t = e.virtual.cache, + s = {} + Object.keys(t).forEach((e) => { + const a = t[e], + r = a.getAttribute('data-swiper-slide-index') + r && + a.setAttribute( + 'data-swiper-slide-index', + parseInt(r, 10) + i + ), + (s[parseInt(e, 10) + i] = a) + }), + (e.virtual.cache = s) + } + d(!0), e.slideTo(a, 0) + }, + removeSlide: function (t) { + if (null == t) return + let s = e.activeIndex + if (Array.isArray(t)) + for (let a = t.length - 1; a >= 0; a -= 1) + e.params.virtual.cache && + (delete e.virtual.cache[t[a]], + Object.keys(e.virtual.cache).forEach((s) => { + s > t && + ((e.virtual.cache[s - 1] = e.virtual.cache[s]), + e.virtual.cache[s - 1].setAttribute( + 'data-swiper-slide-index', + s - 1 + ), + delete e.virtual.cache[s]) + })), + e.virtual.slides.splice(t[a], 1), + t[a] < s && (s -= 1), + (s = Math.max(s, 0)) + else + e.params.virtual.cache && + (delete e.virtual.cache[t], + Object.keys(e.virtual.cache).forEach((s) => { + s > t && + ((e.virtual.cache[s - 1] = e.virtual.cache[s]), + e.virtual.cache[s - 1].setAttribute( + 'data-swiper-slide-index', + s - 1 + ), + delete e.virtual.cache[s]) + })), + e.virtual.slides.splice(t, 1), + t < s && (s -= 1), + (s = Math.max(s, 0)) + d(!0), e.slideTo(s, 0) + }, + removeAllSlides: function () { + ;(e.virtual.slides = []), + e.params.virtual.cache && (e.virtual.cache = {}), + d(!0), + e.slideTo(0, 0) + }, + update: d, + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: i }) { + const n = a(), + l = r() + function o(t) { + if (!e.enabled) return + const { rtlTranslate: s } = e + let a = t + a.originalEvent && (a = a.originalEvent) + const r = a.keyCode || a.charCode, + o = e.params.keyboard.pageUpDown, + d = o && 33 === r, + c = o && 34 === r, + p = 37 === r, + u = 39 === r, + m = 38 === r, + h = 40 === r + if ( + !e.allowSlideNext && + ((e.isHorizontal() && u) || (e.isVertical() && h) || c) + ) + return !1 + if ( + !e.allowSlidePrev && + ((e.isHorizontal() && p) || (e.isVertical() && m) || d) + ) + return !1 + if ( + !( + a.shiftKey || + a.altKey || + a.ctrlKey || + a.metaKey || + (n.activeElement && + (n.activeElement.isContentEditable || + (n.activeElement.nodeName && + ('input' === n.activeElement.nodeName.toLowerCase() || + 'textarea' === n.activeElement.nodeName.toLowerCase())))) + ) + ) { + if ( + e.params.keyboard.onlyInViewport && + (d || c || p || u || m || h) + ) { + let t = !1 + if ( + x(e.el, `.${e.params.slideClass}, swiper-slide`).length > 0 && + 0 === x(e.el, `.${e.params.slideActiveClass}`).length + ) + return + const a = e.el, + i = a.clientWidth, + r = a.clientHeight, + n = l.innerWidth, + o = l.innerHeight, + d = b(a) + s && (d.left -= a.scrollLeft) + const c = [ + [d.left, d.top], + [d.left + i, d.top], + [d.left, d.top + r], + [d.left + i, d.top + r], + ] + for (let e = 0; e < c.length; e += 1) { + const s = c[e] + if (s[0] >= 0 && s[0] <= n && s[1] >= 0 && s[1] <= o) { + if (0 === s[0] && 0 === s[1]) continue + t = !0 + } + } + if (!t) return + } + e.isHorizontal() + ? ((d || c || p || u) && + (a.preventDefault ? a.preventDefault() : (a.returnValue = !1)), + (((c || u) && !s) || ((d || p) && s)) && e.slideNext(), + (((d || p) && !s) || ((c || u) && s)) && e.slidePrev()) + : ((d || c || m || h) && + (a.preventDefault ? a.preventDefault() : (a.returnValue = !1)), + (c || h) && e.slideNext(), + (d || m) && e.slidePrev()), + i('keyPress', r) + } + } + function d() { + e.keyboard.enabled || + (n.addEventListener('keydown', o), (e.keyboard.enabled = !0)) + } + function c() { + e.keyboard.enabled && + (n.removeEventListener('keydown', o), (e.keyboard.enabled = !1)) + } + ;(e.keyboard = { enabled: !1 }), + t({ keyboard: { enabled: !1, onlyInViewport: !0, pageUpDown: !0 } }), + s('init', () => { + e.params.keyboard.enabled && d() + }), + s('destroy', () => { + e.keyboard.enabled && c() + }), + Object.assign(e.keyboard, { enable: d, disable: c }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: a }) { + const i = r() + let n + t({ + mousewheel: { + enabled: !1, + releaseOnEdges: !1, + invert: !1, + forceToAxis: !1, + sensitivity: 1, + eventsTarget: 'container', + thresholdDelta: null, + thresholdTime: null, + noMousewheelClass: 'swiper-no-mousewheel', + }, + }), + (e.mousewheel = { enabled: !1 }) + let d, + c = o() + const p = [] + function u() { + e.enabled && (e.mouseEntered = !0) + } + function m() { + e.enabled && (e.mouseEntered = !1) + } + function h(t) { + return ( + !( + e.params.mousewheel.thresholdDelta && + t.delta < e.params.mousewheel.thresholdDelta + ) && + !( + e.params.mousewheel.thresholdTime && + o() - c < e.params.mousewheel.thresholdTime + ) && + ((t.delta >= 6 && o() - c < 60) || + (t.direction < 0 + ? (e.isEnd && !e.params.loop) || + e.animating || + (e.slideNext(), a('scroll', t.raw)) + : (e.isBeginning && !e.params.loop) || + e.animating || + (e.slidePrev(), a('scroll', t.raw)), + (c = new i.Date().getTime()), + !1)) + ) + } + function f(t) { + let s = t, + i = !0 + if (!e.enabled) return + if (t.target.closest(`.${e.params.mousewheel.noMousewheelClass}`)) + return + const r = e.params.mousewheel + e.params.cssMode && s.preventDefault() + let c = e.el + 'container' !== e.params.mousewheel.eventsTarget && + (c = document.querySelector(e.params.mousewheel.eventsTarget)) + const u = c && c.contains(s.target) + if (!e.mouseEntered && !u && !r.releaseOnEdges) return !0 + s.originalEvent && (s = s.originalEvent) + let m = 0 + const f = e.rtlTranslate ? -1 : 1, + g = (function (e) { + let t = 0, + s = 0, + a = 0, + i = 0 + return ( + 'detail' in e && (s = e.detail), + 'wheelDelta' in e && (s = -e.wheelDelta / 120), + 'wheelDeltaY' in e && (s = -e.wheelDeltaY / 120), + 'wheelDeltaX' in e && (t = -e.wheelDeltaX / 120), + 'axis' in e && e.axis === e.HORIZONTAL_AXIS && ((t = s), (s = 0)), + (a = 10 * t), + (i = 10 * s), + 'deltaY' in e && (i = e.deltaY), + 'deltaX' in e && (a = e.deltaX), + e.shiftKey && !a && ((a = i), (i = 0)), + (a || i) && + e.deltaMode && + (1 === e.deltaMode + ? ((a *= 40), (i *= 40)) + : ((a *= 800), (i *= 800))), + a && !t && (t = a < 1 ? -1 : 1), + i && !s && (s = i < 1 ? -1 : 1), + { spinX: t, spinY: s, pixelX: a, pixelY: i } + ) + })(s) + if (r.forceToAxis) + if (e.isHorizontal()) { + if (!(Math.abs(g.pixelX) > Math.abs(g.pixelY))) return !0 + m = -g.pixelX * f + } else { + if (!(Math.abs(g.pixelY) > Math.abs(g.pixelX))) return !0 + m = -g.pixelY + } + else + m = + Math.abs(g.pixelX) > Math.abs(g.pixelY) ? -g.pixelX * f : -g.pixelY + if (0 === m) return !0 + r.invert && (m = -m) + let v = e.getTranslate() + m * r.sensitivity + if ( + (v >= e.minTranslate() && (v = e.minTranslate()), + v <= e.maxTranslate() && (v = e.maxTranslate()), + (i = + !!e.params.loop || + !(v === e.minTranslate() || v === e.maxTranslate())), + i && e.params.nested && s.stopPropagation(), + e.params.freeMode && e.params.freeMode.enabled) + ) { + const t = { time: o(), delta: Math.abs(m), direction: Math.sign(m) }, + i = + d && + t.time < d.time + 500 && + t.delta <= d.delta && + t.direction === d.direction + if (!i) { + d = void 0 + let o = e.getTranslate() + m * r.sensitivity + const c = e.isBeginning, + u = e.isEnd + if ( + (o >= e.minTranslate() && (o = e.minTranslate()), + o <= e.maxTranslate() && (o = e.maxTranslate()), + e.setTransition(0), + e.setTranslate(o), + e.updateProgress(), + e.updateActiveIndex(), + e.updateSlidesClasses(), + ((!c && e.isBeginning) || (!u && e.isEnd)) && + e.updateSlidesClasses(), + e.params.loop && + e.loopFix({ + direction: t.direction < 0 ? 'next' : 'prev', + byMousewheel: !0, + }), + e.params.freeMode.sticky) + ) { + clearTimeout(n), (n = void 0), p.length >= 15 && p.shift() + const s = p.length ? p[p.length - 1] : void 0, + a = p[0] + if ( + (p.push(t), + s && (t.delta > s.delta || t.direction !== s.direction)) + ) + p.splice(0) + else if ( + p.length >= 15 && + t.time - a.time < 500 && + a.delta - t.delta >= 1 && + t.delta <= 6 + ) { + const s = m > 0 ? 0.8 : 0.2 + ;(d = t), + p.splice(0), + (n = l(() => { + !e.destroyed && + e.params && + e.slideToClosest(e.params.speed, !0, void 0, s) + }, 0)) + } + n || + (n = l(() => { + if (e.destroyed || !e.params) return + ;(d = t), + p.splice(0), + e.slideToClosest(e.params.speed, !0, void 0, 0.5) + }, 500)) + } + if ( + (i || a('scroll', s), + e.params.autoplay && + e.params.autoplay.disableOnInteraction && + e.autoplay.stop(), + r.releaseOnEdges && + (o === e.minTranslate() || o === e.maxTranslate())) + ) + return !0 + } + } else { + const s = { + time: o(), + delta: Math.abs(m), + direction: Math.sign(m), + raw: t, + } + p.length >= 2 && p.shift() + const a = p.length ? p[p.length - 1] : void 0 + if ( + (p.push(s), + a + ? (s.direction !== a.direction || + s.delta > a.delta || + s.time > a.time + 150) && + h(s) + : h(s), + (function (t) { + const s = e.params.mousewheel + if (t.direction < 0) { + if (e.isEnd && !e.params.loop && s.releaseOnEdges) return !0 + } else if (e.isBeginning && !e.params.loop && s.releaseOnEdges) + return !0 + return !1 + })(s)) + ) + return !0 + } + return s.preventDefault ? s.preventDefault() : (s.returnValue = !1), !1 + } + function g(t) { + let s = e.el + 'container' !== e.params.mousewheel.eventsTarget && + (s = document.querySelector(e.params.mousewheel.eventsTarget)), + s[t]('mouseenter', u), + s[t]('mouseleave', m), + s[t]('wheel', f) + } + function v() { + return e.params.cssMode + ? (e.wrapperEl.removeEventListener('wheel', f), !0) + : !e.mousewheel.enabled && + (g('addEventListener'), (e.mousewheel.enabled = !0), !0) + } + function w() { + return e.params.cssMode + ? (e.wrapperEl.addEventListener(event, f), !0) + : !!e.mousewheel.enabled && + (g('removeEventListener'), (e.mousewheel.enabled = !1), !0) + } + s('init', () => { + !e.params.mousewheel.enabled && e.params.cssMode && w(), + e.params.mousewheel.enabled && v() + }), + s('destroy', () => { + e.params.cssMode && v(), e.mousewheel.enabled && w() + }), + Object.assign(e.mousewheel, { enable: v, disable: w }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: a }) { + function i(t) { + let s + return t && + 'string' == typeof t && + e.isElement && + ((s = e.el.querySelector(t) || e.hostEl.querySelector(t)), s) + ? s + : (t && + ('string' == typeof t && (s = [...document.querySelectorAll(t)]), + e.params.uniqueNavElements && + 'string' == typeof t && + s && + s.length > 1 && + 1 === e.el.querySelectorAll(t).length + ? (s = e.el.querySelector(t)) + : s && 1 === s.length && (s = s[0])), + t && !s ? t : s) + } + function r(t, s) { + const a = e.params.navigation + ;(t = M(t)).forEach((t) => { + t && + (t.classList[s ? 'add' : 'remove'](...a.disabledClass.split(' ')), + 'BUTTON' === t.tagName && (t.disabled = s), + e.params.watchOverflow && + e.enabled && + t.classList[e.isLocked ? 'add' : 'remove'](a.lockClass)) + }) + } + function n() { + const { nextEl: t, prevEl: s } = e.navigation + if (e.params.loop) return r(s, !1), void r(t, !1) + r(s, e.isBeginning && !e.params.rewind), + r(t, e.isEnd && !e.params.rewind) + } + function l(t) { + t.preventDefault(), + (!e.isBeginning || e.params.loop || e.params.rewind) && + (e.slidePrev(), a('navigationPrev')) + } + function o(t) { + t.preventDefault(), + (!e.isEnd || e.params.loop || e.params.rewind) && + (e.slideNext(), a('navigationNext')) + } + function d() { + const t = e.params.navigation + if ( + ((e.params.navigation = le( + e, + e.originalParams.navigation, + e.params.navigation, + { nextEl: 'swiper-button-next', prevEl: 'swiper-button-prev' } + )), + !t.nextEl && !t.prevEl) + ) + return + let s = i(t.nextEl), + a = i(t.prevEl) + Object.assign(e.navigation, { nextEl: s, prevEl: a }), + (s = M(s)), + (a = M(a)) + const r = (s, a) => { + if (s) { + if ( + t.addIcons && + s.matches('.swiper-button-next,.swiper-button-prev') && + !s.querySelector('svg') + ) { + const e = document.createElement('div') + P(e, oe), s.appendChild(e.querySelector('svg')), e.remove() + } + s.addEventListener('click', 'next' === a ? o : l) + } + !e.enabled && s && s.classList.add(...t.lockClass.split(' ')) + } + s.forEach((e) => r(e, 'next')), a.forEach((e) => r(e, 'prev')) + } + function c() { + let { nextEl: t, prevEl: s } = e.navigation + ;(t = M(t)), (s = M(s)) + const a = (t, s) => { + t.removeEventListener('click', 'next' === s ? o : l), + t.classList.remove(...e.params.navigation.disabledClass.split(' ')) + } + t.forEach((e) => a(e, 'next')), s.forEach((e) => a(e, 'prev')) + } + t({ + navigation: { + nextEl: null, + prevEl: null, + addIcons: !0, + hideOnClick: !1, + disabledClass: 'swiper-button-disabled', + hiddenClass: 'swiper-button-hidden', + lockClass: 'swiper-button-lock', + navigationDisabledClass: 'swiper-navigation-disabled', + }, + }), + (e.navigation = { nextEl: null, prevEl: null, arrowSvg: oe }), + s('init', () => { + !1 === e.params.navigation.enabled ? p() : (d(), n()) + }), + s('toEdge fromEdge lock unlock', () => { + n() + }), + s('destroy', () => { + c() + }), + s('enable disable', () => { + let { nextEl: t, prevEl: s } = e.navigation + ;(t = M(t)), + (s = M(s)), + e.enabled + ? n() + : [...t, ...s] + .filter((e) => !!e) + .forEach((t) => + t.classList.add(e.params.navigation.lockClass) + ) + }), + s('click', (t, s) => { + let { nextEl: i, prevEl: r } = e.navigation + ;(i = M(i)), (r = M(r)) + const n = s.target + let l = r.includes(n) || i.includes(n) + if (e.isElement && !l) { + const e = s.path || (s.composedPath && s.composedPath()) + e && (l = e.find((e) => i.includes(e) || r.includes(e))) + } + if (e.params.navigation.hideOnClick && !l) { + if ( + e.pagination && + e.params.pagination && + e.params.pagination.clickable && + (e.pagination.el === n || e.pagination.el.contains(n)) + ) + return + let t + i.length + ? (t = i[0].classList.contains(e.params.navigation.hiddenClass)) + : r.length && + (t = r[0].classList.contains(e.params.navigation.hiddenClass)), + a(!0 === t ? 'navigationShow' : 'navigationHide'), + [...i, ...r] + .filter((e) => !!e) + .forEach((t) => + t.classList.toggle(e.params.navigation.hiddenClass) + ) + } + }) + const p = () => { + e.el.classList.add( + ...e.params.navigation.navigationDisabledClass.split(' ') + ), + c() + } + Object.assign(e.navigation, { + enable: () => { + e.el.classList.remove( + ...e.params.navigation.navigationDisabledClass.split(' ') + ), + d(), + n() + }, + disable: p, + update: n, + init: d, + destroy: c, + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: a }) { + const i = 'swiper-pagination' + let r + t({ + pagination: { + el: null, + bulletElement: 'span', + clickable: !1, + hideOnClick: !1, + renderBullet: null, + renderProgressbar: null, + renderFraction: null, + renderCustom: null, + progressbarOpposite: !1, + type: 'bullets', + dynamicBullets: !1, + dynamicMainBullets: 1, + formatFractionCurrent: (e) => e, + formatFractionTotal: (e) => e, + bulletClass: `${i}-bullet`, + bulletActiveClass: `${i}-bullet-active`, + modifierClass: `${i}-`, + currentClass: `${i}-current`, + totalClass: `${i}-total`, + hiddenClass: `${i}-hidden`, + progressbarFillClass: `${i}-progressbar-fill`, + progressbarOppositeClass: `${i}-progressbar-opposite`, + clickableClass: `${i}-clickable`, + lockClass: `${i}-lock`, + horizontalClass: `${i}-horizontal`, + verticalClass: `${i}-vertical`, + paginationDisabledClass: `${i}-disabled`, + }, + }), + (e.pagination = { el: null, bullets: [] }) + let n = 0 + function l() { + return ( + !e.params.pagination.el || + !e.pagination.el || + (Array.isArray(e.pagination.el) && 0 === e.pagination.el.length) + ) + } + function o(t, s) { + const { bulletActiveClass: a } = e.params.pagination + t && + (t = t[('prev' === s ? 'previous' : 'next') + 'ElementSibling']) && + (t.classList.add(`${a}-${s}`), + (t = t[('prev' === s ? 'previous' : 'next') + 'ElementSibling']) && + t.classList.add(`${a}-${s}-${s}`)) + } + function d(t) { + const s = t.target.closest(de(e.params.pagination.bulletClass)) + if (!s) return + t.preventDefault() + const a = E(s) * e.params.slidesPerGroup + if (e.params.loop) { + if (e.realIndex === a) return + const t = + ((i = e.realIndex), + (r = a), + (n = e.slides.length), + (r %= n) === 1 + (i %= n) + ? 'next' + : r === i - 1 + ? 'previous' + : void 0) + 'next' === t + ? e.slideNext() + : 'previous' === t + ? e.slidePrev() + : e.slideToLoop(a) + } else e.slideTo(a) + var i, r, n + } + function c() { + const t = e.rtl, + s = e.params.pagination + if (l()) return + let i, + d, + c = e.pagination.el + c = M(c) + const p = + e.virtual && e.params.virtual.enabled + ? e.virtual.slides.length + : e.slides.length, + u = e.params.loop + ? Math.ceil(p / e.params.slidesPerGroup) + : e.snapGrid.length + if ( + (e.params.loop + ? ((d = e.previousRealIndex || 0), + (i = + e.params.slidesPerGroup > 1 + ? Math.floor(e.realIndex / e.params.slidesPerGroup) + : e.realIndex)) + : void 0 !== e.snapIndex + ? ((i = e.snapIndex), (d = e.previousSnapIndex)) + : ((d = e.previousIndex || 0), (i = e.activeIndex || 0)), + 'bullets' === s.type && + e.pagination.bullets && + e.pagination.bullets.length > 0) + ) { + const a = e.pagination.bullets + let l, p, u + if ( + (s.dynamicBullets && + ((r = T(a[0], e.isHorizontal() ? 'width' : 'height', !0)), + c.forEach((t) => { + t.style[e.isHorizontal() ? 'width' : 'height'] = + r * (s.dynamicMainBullets + 4) + 'px' + }), + s.dynamicMainBullets > 1 && + void 0 !== d && + ((n += i - (d || 0)), + n > s.dynamicMainBullets - 1 + ? (n = s.dynamicMainBullets - 1) + : n < 0 && (n = 0)), + (l = Math.max(i - n, 0)), + (p = l + (Math.min(a.length, s.dynamicMainBullets) - 1)), + (u = (p + l) / 2)), + a.forEach((e) => { + const t = [ + ...[ + '', + '-next', + '-next-next', + '-prev', + '-prev-prev', + '-main', + ].map((e) => `${s.bulletActiveClass}${e}`), + ] + .map((e) => + 'string' == typeof e && e.includes(' ') ? e.split(' ') : e + ) + .flat() + e.classList.remove(...t) + }), + c.length > 1) + ) + a.forEach((t) => { + const a = E(t) + a === i + ? t.classList.add(...s.bulletActiveClass.split(' ')) + : e.isElement && t.setAttribute('part', 'bullet'), + s.dynamicBullets && + (a >= l && + a <= p && + t.classList.add( + ...`${s.bulletActiveClass}-main`.split(' ') + ), + a === l && o(t, 'prev'), + a === p && o(t, 'next')) + }) + else { + const t = a[i] + if ( + (t && t.classList.add(...s.bulletActiveClass.split(' ')), + e.isElement && + a.forEach((e, t) => { + e.setAttribute('part', t === i ? 'bullet-active' : 'bullet') + }), + s.dynamicBullets) + ) { + const e = a[l], + t = a[p] + for (let e = l; e <= p; e += 1) + a[e] && + a[e].classList.add( + ...`${s.bulletActiveClass}-main`.split(' ') + ) + o(e, 'prev'), o(t, 'next') + } + } + if (s.dynamicBullets) { + const i = Math.min(a.length, s.dynamicMainBullets + 4), + n = (r * i - r) / 2 - u * r, + l = t ? 'right' : 'left' + a.forEach((t) => { + t.style[e.isHorizontal() ? l : 'top'] = `${n}px` + }) + } + } + c.forEach((t, r) => { + if ( + ('fraction' === s.type && + (t.querySelectorAll(de(s.currentClass)).forEach((e) => { + e.textContent = s.formatFractionCurrent(i + 1) + }), + t.querySelectorAll(de(s.totalClass)).forEach((e) => { + e.textContent = s.formatFractionTotal(u) + })), + 'progressbar' === s.type) + ) { + let a + a = s.progressbarOpposite + ? e.isHorizontal() + ? 'vertical' + : 'horizontal' + : e.isHorizontal() + ? 'horizontal' + : 'vertical' + const r = (i + 1) / u + let n = 1, + l = 1 + 'horizontal' === a ? (n = r) : (l = r), + t.querySelectorAll(de(s.progressbarFillClass)).forEach((t) => { + ;(t.style.transform = `translate3d(0,0,0) scaleX(${n}) scaleY(${l})`), + (t.style.transitionDuration = `${e.params.speed}ms`) + }) + } + 'custom' === s.type && s.renderCustom + ? (P(t, s.renderCustom(e, i + 1, u)), + 0 === r && a('paginationRender', t)) + : (0 === r && a('paginationRender', t), a('paginationUpdate', t)), + e.params.watchOverflow && + e.enabled && + t.classList[e.isLocked ? 'add' : 'remove'](s.lockClass) + }) + } + function p() { + const t = e.params.pagination + if (l()) return + const s = + e.virtual && e.params.virtual.enabled + ? e.virtual.slides.length + : e.grid && e.params.grid.rows > 1 + ? e.slides.length / Math.ceil(e.params.grid.rows) + : e.slides.length + let i = e.pagination.el + i = M(i) + let r = '' + if ('bullets' === t.type) { + let a = e.params.loop + ? Math.ceil(s / e.params.slidesPerGroup) + : e.snapGrid.length + e.params.freeMode && e.params.freeMode.enabled && a > s && (a = s) + for (let s = 0; s < a; s += 1) + t.renderBullet + ? (r += t.renderBullet.call(e, s, t.bulletClass)) + : (r += `<${t.bulletElement} ${ + e.isElement ? 'part="bullet"' : '' + } class="${t.bulletClass}">`) + } + 'fraction' === t.type && + (r = t.renderFraction + ? t.renderFraction.call(e, t.currentClass, t.totalClass) + : ` / `), + 'progressbar' === t.type && + (r = t.renderProgressbar + ? t.renderProgressbar.call(e, t.progressbarFillClass) + : ``), + (e.pagination.bullets = []), + i.forEach((s) => { + 'custom' !== t.type && P(s, r || ''), + 'bullets' === t.type && + e.pagination.bullets.push( + ...s.querySelectorAll(de(t.bulletClass)) + ) + }), + 'custom' !== t.type && a('paginationRender', i[0]) + } + function u() { + e.params.pagination = le( + e, + e.originalParams.pagination, + e.params.pagination, + { el: 'swiper-pagination' } + ) + const t = e.params.pagination + if (!t.el) return + let s + 'string' == typeof t.el && + e.isElement && + (s = e.el.querySelector(t.el)), + s || + 'string' != typeof t.el || + (s = [...document.querySelectorAll(t.el)]), + s || (s = t.el), + s && + 0 !== s.length && + (e.params.uniqueNavElements && + 'string' == typeof t.el && + Array.isArray(s) && + s.length > 1 && + ((s = [...e.el.querySelectorAll(t.el)]), + s.length > 1 && (s = s.find((t) => x(t, '.swiper')[0] === e.el))), + Array.isArray(s) && 1 === s.length && (s = s[0]), + Object.assign(e.pagination, { el: s }), + (s = M(s)), + s.forEach((s) => { + 'bullets' === t.type && + t.clickable && + s.classList.add(...(t.clickableClass || '').split(' ')), + s.classList.add(t.modifierClass + t.type), + s.classList.add( + e.isHorizontal() ? t.horizontalClass : t.verticalClass + ), + 'bullets' === t.type && + t.dynamicBullets && + (s.classList.add(`${t.modifierClass}${t.type}-dynamic`), + (n = 0), + t.dynamicMainBullets < 1 && (t.dynamicMainBullets = 1)), + 'progressbar' === t.type && + t.progressbarOpposite && + s.classList.add(t.progressbarOppositeClass), + t.clickable && s.addEventListener('click', d), + e.enabled || s.classList.add(t.lockClass) + })) + } + function m() { + const t = e.params.pagination + if (l()) return + let s = e.pagination.el + s && + ((s = M(s)), + s.forEach((s) => { + s.classList.remove(t.hiddenClass), + s.classList.remove(t.modifierClass + t.type), + s.classList.remove( + e.isHorizontal() ? t.horizontalClass : t.verticalClass + ), + t.clickable && + (s.classList.remove(...(t.clickableClass || '').split(' ')), + s.removeEventListener('click', d)) + })), + e.pagination.bullets && + e.pagination.bullets.forEach((e) => + e.classList.remove(...t.bulletActiveClass.split(' ')) + ) + } + s('changeDirection', () => { + if (!e.pagination || !e.pagination.el) return + const t = e.params.pagination + let { el: s } = e.pagination + ;(s = M(s)), + s.forEach((s) => { + s.classList.remove(t.horizontalClass, t.verticalClass), + s.classList.add( + e.isHorizontal() ? t.horizontalClass : t.verticalClass + ) + }) + }), + s('init', () => { + !1 === e.params.pagination.enabled ? h() : (u(), p(), c()) + }), + s('activeIndexChange', () => { + void 0 === e.snapIndex && c() + }), + s('snapIndexChange', () => { + c() + }), + s('snapGridLengthChange', () => { + p(), c() + }), + s('destroy', () => { + m() + }), + s('enable disable', () => { + let { el: t } = e.pagination + t && + ((t = M(t)), + t.forEach((t) => + t.classList[e.enabled ? 'remove' : 'add']( + e.params.pagination.lockClass + ) + )) + }), + s('lock unlock', () => { + c() + }), + s('click', (t, s) => { + const i = s.target, + r = M(e.pagination.el) + if ( + e.params.pagination.el && + e.params.pagination.hideOnClick && + r && + r.length > 0 && + !i.classList.contains(e.params.pagination.bulletClass) + ) { + if ( + e.navigation && + ((e.navigation.nextEl && i === e.navigation.nextEl) || + (e.navigation.prevEl && i === e.navigation.prevEl)) + ) + return + const t = r[0].classList.contains(e.params.pagination.hiddenClass) + a(!0 === t ? 'paginationShow' : 'paginationHide'), + r.forEach((t) => + t.classList.toggle(e.params.pagination.hiddenClass) + ) + } + }) + const h = () => { + e.el.classList.add(e.params.pagination.paginationDisabledClass) + let { el: t } = e.pagination + t && + ((t = M(t)), + t.forEach((t) => + t.classList.add(e.params.pagination.paginationDisabledClass) + )), + m() + } + Object.assign(e.pagination, { + enable: () => { + e.el.classList.remove(e.params.pagination.paginationDisabledClass) + let { el: t } = e.pagination + t && + ((t = M(t)), + t.forEach((t) => + t.classList.remove(e.params.pagination.paginationDisabledClass) + )), + u(), + p(), + c() + }, + disable: h, + render: p, + update: c, + init: u, + destroy: m, + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: i }) { + const r = a() + let o, + d, + c, + p, + u = !1, + m = null, + h = null + function f() { + if (!e.params.scrollbar.el || !e.scrollbar.el) return + const { scrollbar: t, rtlTranslate: s } = e, + { dragEl: a, el: i } = t, + r = e.params.scrollbar, + n = e.params.loop ? e.progressLoop : e.progress + let l = d, + o = (c - d) * n + s + ? ((o = -o), + o > 0 ? ((l = d - o), (o = 0)) : -o + d > c && (l = c + o)) + : o < 0 + ? ((l = d + o), (o = 0)) + : o + d > c && (l = c - o), + e.isHorizontal() + ? ((a.style.transform = `translate3d(${o}px, 0, 0)`), + (a.style.width = `${l}px`)) + : ((a.style.transform = `translate3d(0px, ${o}px, 0)`), + (a.style.height = `${l}px`)), + r.hide && + (clearTimeout(m), + (i.style.opacity = 1), + (m = setTimeout(() => { + ;(i.style.opacity = 0), (i.style.transitionDuration = '400ms') + }, 1e3))) + } + function g() { + if (!e.params.scrollbar.el || !e.scrollbar.el) return + const { scrollbar: t } = e, + { dragEl: s, el: a } = t + ;(s.style.width = ''), + (s.style.height = ''), + (c = e.isHorizontal() ? a.offsetWidth : a.offsetHeight), + (p = + e.size / + (e.virtualSize + + e.params.slidesOffsetBefore - + (e.params.centeredSlides ? e.snapGrid[0] : 0))), + (d = + 'auto' === e.params.scrollbar.dragSize + ? c * p + : parseInt(e.params.scrollbar.dragSize, 10)), + e.isHorizontal() + ? (s.style.width = `${d}px`) + : (s.style.height = `${d}px`), + (a.style.display = p >= 1 ? 'none' : ''), + e.params.scrollbar.hide && (a.style.opacity = 0), + e.params.watchOverflow && + e.enabled && + t.el.classList[e.isLocked ? 'add' : 'remove']( + e.params.scrollbar.lockClass + ) + } + function v(t) { + return e.isHorizontal() ? t.clientX : t.clientY + } + function y(t) { + const { scrollbar: s, rtlTranslate: a } = e, + { el: i } = s + let r + ;(r = + (v(t) - + b(i)[e.isHorizontal() ? 'left' : 'top'] - + (null !== o ? o : d / 2)) / + (c - d)), + (r = Math.max(Math.min(r, 1), 0)), + a && (r = 1 - r) + const n = e.minTranslate() + (e.maxTranslate() - e.minTranslate()) * r + e.updateProgress(n), + e.setTranslate(n), + e.updateActiveIndex(), + e.updateSlidesClasses() + } + function E(t) { + const s = e.params.scrollbar, + { scrollbar: a, wrapperEl: r } = e, + { el: n, dragEl: l } = a + ;(u = !0), + (o = + t.target === l + ? v(t) - + t.target.getBoundingClientRect()[ + e.isHorizontal() ? 'left' : 'top' + ] + : null), + t.preventDefault(), + t.stopPropagation(), + (r.style.transitionDuration = '100ms'), + (l.style.transitionDuration = '100ms'), + y(t), + clearTimeout(h), + (n.style.transitionDuration = '0ms'), + s.hide && (n.style.opacity = 1), + e.params.cssMode && (e.wrapperEl.style['scroll-snap-type'] = 'none'), + i('scrollbarDragStart', t) + } + function x(t) { + const { scrollbar: s, wrapperEl: a } = e, + { el: r, dragEl: n } = s + u && + (t.preventDefault && t.cancelable + ? t.preventDefault() + : (t.returnValue = !1), + y(t), + (a.style.transitionDuration = '0ms'), + (r.style.transitionDuration = '0ms'), + (n.style.transitionDuration = '0ms'), + i('scrollbarDragMove', t)) + } + function S(t) { + const s = e.params.scrollbar, + { scrollbar: a, wrapperEl: r } = e, + { el: n } = a + u && + ((u = !1), + e.params.cssMode && + ((e.wrapperEl.style['scroll-snap-type'] = ''), + (r.style.transitionDuration = '')), + s.hide && + (clearTimeout(h), + (h = l(() => { + ;(n.style.opacity = 0), (n.style.transitionDuration = '400ms') + }, 1e3))), + i('scrollbarDragEnd', t), + s.snapOnRelease && e.slideToClosest()) + } + function T(t) { + const { scrollbar: s, params: a } = e, + i = s.el + if (!i) return + const n = i, + l = !!a.passiveListeners && { passive: !1, capture: !1 }, + o = !!a.passiveListeners && { passive: !0, capture: !1 } + if (!n) return + const d = 'on' === t ? 'addEventListener' : 'removeEventListener' + n[d]('pointerdown', E, l), + r[d]('pointermove', x, l), + r[d]('pointerup', S, o) + } + function C() { + const { scrollbar: t, el: s } = e + e.params.scrollbar = le( + e, + e.originalParams.scrollbar, + e.params.scrollbar, + { el: 'swiper-scrollbar' } + ) + const a = e.params.scrollbar + if (!a.el) return + let i, l + if ( + ('string' == typeof a.el && + e.isElement && + (i = e.el.querySelector(a.el)), + i || 'string' != typeof a.el) + ) + i || (i = a.el) + else if (((i = r.querySelectorAll(a.el)), !i.length)) return + e.params.uniqueNavElements && + 'string' == typeof a.el && + i.length > 1 && + 1 === s.querySelectorAll(a.el).length && + (i = s.querySelector(a.el)), + i.length > 0 && (i = i[0]), + i.classList.add( + e.isHorizontal() ? a.horizontalClass : a.verticalClass + ), + i && + ((l = i.querySelector(de(e.params.scrollbar.dragClass))), + l || ((l = w('div', e.params.scrollbar.dragClass)), i.append(l))), + Object.assign(t, { el: i, dragEl: l }), + a.draggable && e.params.scrollbar.el && e.scrollbar.el && T('on'), + i && + i.classList[e.enabled ? 'remove' : 'add']( + ...n(e.params.scrollbar.lockClass) + ) + } + function P() { + const t = e.params.scrollbar, + s = e.scrollbar.el + s && + s.classList.remove( + ...n(e.isHorizontal() ? t.horizontalClass : t.verticalClass) + ), + e.params.scrollbar.el && e.scrollbar.el && T('off') + } + t({ + scrollbar: { + el: null, + dragSize: 'auto', + hide: !1, + draggable: !1, + snapOnRelease: !0, + lockClass: 'swiper-scrollbar-lock', + dragClass: 'swiper-scrollbar-drag', + scrollbarDisabledClass: 'swiper-scrollbar-disabled', + horizontalClass: 'swiper-scrollbar-horizontal', + verticalClass: 'swiper-scrollbar-vertical', + }, + }), + (e.scrollbar = { el: null, dragEl: null }), + s('changeDirection', () => { + if (!e.scrollbar || !e.scrollbar.el) return + const t = e.params.scrollbar + let { el: s } = e.scrollbar + ;(s = M(s)), + s.forEach((s) => { + s.classList.remove(t.horizontalClass, t.verticalClass), + s.classList.add( + e.isHorizontal() ? t.horizontalClass : t.verticalClass + ) + }) + }), + s('init', () => { + !1 === e.params.scrollbar.enabled ? L() : (C(), g(), f()) + }), + s('update resize observerUpdate lock unlock changeDirection', () => { + g() + }), + s('setTranslate', () => { + f() + }), + s('setTransition', (t, s) => { + !(function (t) { + e.params.scrollbar.el && + e.scrollbar.el && + (e.scrollbar.dragEl.style.transitionDuration = `${t}ms`) + })(s) + }), + s('enable disable', () => { + const { el: t } = e.scrollbar + t && + t.classList[e.enabled ? 'remove' : 'add']( + ...n(e.params.scrollbar.lockClass) + ) + }), + s('destroy', () => { + P() + }) + const L = () => { + e.el.classList.add(...n(e.params.scrollbar.scrollbarDisabledClass)), + e.scrollbar.el && + e.scrollbar.el.classList.add( + ...n(e.params.scrollbar.scrollbarDisabledClass) + ), + P() + } + Object.assign(e.scrollbar, { + enable: () => { + e.el.classList.remove( + ...n(e.params.scrollbar.scrollbarDisabledClass) + ), + e.scrollbar.el && + e.scrollbar.el.classList.remove( + ...n(e.params.scrollbar.scrollbarDisabledClass) + ), + C(), + g(), + f() + }, + disable: L, + updateSize: g, + setTranslate: f, + init: C, + destroy: P, + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ parallax: { enabled: !1 } }) + const a = + '[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y], [data-swiper-parallax-opacity], [data-swiper-parallax-scale]', + i = (t, s) => { + const { rtl: a } = e, + i = a ? -1 : 1, + r = t.getAttribute('data-swiper-parallax') || '0' + let n = t.getAttribute('data-swiper-parallax-x'), + l = t.getAttribute('data-swiper-parallax-y') + const o = t.getAttribute('data-swiper-parallax-scale'), + d = t.getAttribute('data-swiper-parallax-opacity'), + c = t.getAttribute('data-swiper-parallax-rotate') + if ( + (n || l + ? ((n = n || '0'), (l = l || '0')) + : e.isHorizontal() + ? ((n = r), (l = '0')) + : ((l = r), (n = '0')), + (n = + n.indexOf('%') >= 0 + ? parseInt(n, 10) * s * i + '%' + : n * s * i + 'px'), + (l = + l.indexOf('%') >= 0 ? parseInt(l, 10) * s + '%' : l * s + 'px'), + null != d) + ) { + const e = d - (d - 1) * (1 - Math.abs(s)) + t.style.opacity = e + } + let p = `translate3d(${n}, ${l}, 0px)` + if (null != o) { + p += ` scale(${o - (o - 1) * (1 - Math.abs(s))})` + } + if (c && null != c) { + p += ` rotate(${c * s * -1}deg)` + } + t.style.transform = p + }, + r = () => { + const { + el: t, + slides: s, + progress: r, + snapGrid: n, + isElement: l, + } = e, + o = g(t, a) + e.isElement && o.push(...g(e.hostEl, a)), + o.forEach((e) => { + i(e, r) + }), + s.forEach((t, s) => { + let l = t.progress + e.params.slidesPerGroup > 1 && + 'auto' !== e.params.slidesPerView && + (l += Math.ceil(s / 2) - r * (n.length - 1)), + (l = Math.min(Math.max(l, -1), 1)), + t + .querySelectorAll(`${a}, [data-swiper-parallax-rotate]`) + .forEach((e) => { + i(e, l) + }) + }) + } + s('beforeInit', () => { + e.params.parallax.enabled && + ((e.params.watchSlidesProgress = !0), + (e.originalParams.watchSlidesProgress = !0)) + }), + s('init', () => { + e.params.parallax.enabled && r() + }), + s('setTranslate', () => { + e.params.parallax.enabled && r() + }), + s('setTransition', (t, s) => { + e.params.parallax.enabled && + ((t = e.params.speed) => { + const { el: s, hostEl: i } = e, + r = [...s.querySelectorAll(a)] + e.isElement && r.push(...i.querySelectorAll(a)), + r.forEach((e) => { + let s = + parseInt( + e.getAttribute('data-swiper-parallax-duration'), + 10 + ) || t + 0 === t && (s = 0), (e.style.transitionDuration = `${s}ms`) + }) + })(s) + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: a }) { + const i = r() + t({ + zoom: { + enabled: !1, + limitToOriginalSize: !1, + maxRatio: 3, + minRatio: 1, + panOnMouseMove: !1, + toggle: !0, + containerClass: 'swiper-zoom-container', + zoomedSlideClass: 'swiper-slide-zoomed', + }, + }), + (e.zoom = { enabled: !1 }) + let n, + l, + o = 1, + c = !1, + p = !1, + u = { x: 0, y: 0 } + const m = [], + h = { + originX: 0, + originY: 0, + slideEl: void 0, + slideWidth: void 0, + slideHeight: void 0, + imageEl: void 0, + imageWrapEl: void 0, + maxRatio: 3, + }, + f = { + isTouched: void 0, + isMoved: void 0, + currentX: void 0, + currentY: void 0, + minX: void 0, + minY: void 0, + maxX: void 0, + maxY: void 0, + width: void 0, + height: void 0, + startX: void 0, + startY: void 0, + touchesStart: {}, + touchesCurrent: {}, + }, + v = { + x: void 0, + y: void 0, + prevPositionX: void 0, + prevPositionY: void 0, + prevTime: void 0, + } + let w, + y = 1 + function E() { + if (m.length < 2) return 1 + const e = m[0].pageX, + t = m[0].pageY, + s = m[1].pageX, + a = m[1].pageY + return Math.sqrt((s - e) ** 2 + (a - t) ** 2) + } + function S() { + const t = e.params.zoom, + s = h.imageWrapEl.getAttribute('data-swiper-zoom') || t.maxRatio + if (t.limitToOriginalSize && h.imageEl && h.imageEl.naturalWidth) { + const e = h.imageEl.naturalWidth / h.imageEl.offsetWidth + return Math.min(e, s) + } + return s + } + function T(t) { + const s = e.isElement ? 'swiper-slide' : `.${e.params.slideClass}` + return ( + !!t.target.matches(s) || + e.slides.filter((e) => e.contains(t.target)).length > 0 + ) + } + function M(t) { + const s = `.${e.params.zoom.containerClass}` + return ( + !!t.target.matches(s) || + [...e.hostEl.querySelectorAll(s)].filter((e) => e.contains(t.target)) + .length > 0 + ) + } + function C(t) { + if (('mouse' === t.pointerType && m.splice(0, m.length), !T(t))) return + const s = e.params.zoom + if (((n = !1), (l = !1), m.push(t), !(m.length < 2))) { + if (((n = !0), (h.scaleStart = E()), !h.slideEl)) { + ;(h.slideEl = t.target.closest( + `.${e.params.slideClass}, swiper-slide` + )), + h.slideEl || (h.slideEl = e.slides[e.activeIndex]) + let a = h.slideEl.querySelector(`.${s.containerClass}`) + if ( + (a && + (a = a.querySelectorAll( + 'picture, img, svg, canvas, .swiper-zoom-target' + )[0]), + (h.imageEl = a), + (h.imageWrapEl = a + ? x(h.imageEl, `.${s.containerClass}`)[0] + : void 0), + !h.imageWrapEl) + ) + return void (h.imageEl = void 0) + h.maxRatio = S() + } + if (h.imageEl) { + const [e, t] = (function () { + if (m.length < 2) return { x: null, y: null } + const e = h.imageEl.getBoundingClientRect() + return [ + (m[0].pageX + (m[1].pageX - m[0].pageX) / 2 - e.x - i.scrollX) / + o, + (m[0].pageY + (m[1].pageY - m[0].pageY) / 2 - e.y - i.scrollY) / + o, + ] + })() + ;(h.originX = e), + (h.originY = t), + (h.imageEl.style.transitionDuration = '0ms') + } + c = !0 + } + } + function P(t) { + if (!T(t)) return + const s = e.params.zoom, + a = e.zoom, + i = m.findIndex((e) => e.pointerId === t.pointerId) + i >= 0 && (m[i] = t), + m.length < 2 || + ((l = !0), + (h.scaleMove = E()), + h.imageEl && + ((a.scale = (h.scaleMove / h.scaleStart) * o), + a.scale > h.maxRatio && + (a.scale = h.maxRatio - 1 + (a.scale - h.maxRatio + 1) ** 0.5), + a.scale < s.minRatio && + (a.scale = s.minRatio + 1 - (s.minRatio - a.scale + 1) ** 0.5), + (h.imageEl.style.transform = `translate3d(0,0,0) scale(${a.scale})`))) + } + function L(t) { + if (!T(t)) return + if ('mouse' === t.pointerType && 'pointerout' === t.type) return + const s = e.params.zoom, + a = e.zoom, + i = m.findIndex((e) => e.pointerId === t.pointerId) + i >= 0 && m.splice(i, 1), + n && + l && + ((n = !1), + (l = !1), + h.imageEl && + ((a.scale = Math.max(Math.min(a.scale, h.maxRatio), s.minRatio)), + (h.imageEl.style.transitionDuration = `${e.params.speed}ms`), + (h.imageEl.style.transform = `translate3d(0,0,0) scale(${a.scale})`), + (o = a.scale), + (c = !1), + a.scale > 1 && h.slideEl + ? h.slideEl.classList.add(`${s.zoomedSlideClass}`) + : a.scale <= 1 && + h.slideEl && + h.slideEl.classList.remove(`${s.zoomedSlideClass}`), + 1 === a.scale && + ((h.originX = 0), (h.originY = 0), (h.slideEl = void 0)))) + } + function I() { + e.touchEventsData.preventTouchMoveFromPointerMove = !1 + } + function z(t) { + const s = 'mouse' === t.pointerType && e.params.zoom.panOnMouseMove + if (!T(t) || !M(t)) return + const a = e.zoom + if (!h.imageEl) return + if (!f.isTouched || !h.slideEl) return void (s && $(t)) + if (s) return void $(t) + f.isMoved || + ((f.width = h.imageEl.offsetWidth || h.imageEl.clientWidth), + (f.height = h.imageEl.offsetHeight || h.imageEl.clientHeight), + (f.startX = d(h.imageWrapEl, 'x') || 0), + (f.startY = d(h.imageWrapEl, 'y') || 0), + (h.slideWidth = h.slideEl.offsetWidth), + (h.slideHeight = h.slideEl.offsetHeight), + (h.imageWrapEl.style.transitionDuration = '0ms')) + const i = f.width * a.scale, + r = f.height * a.scale + ;(f.minX = Math.min(h.slideWidth / 2 - i / 2, 0)), + (f.maxX = -f.minX), + (f.minY = Math.min(h.slideHeight / 2 - r / 2, 0)), + (f.maxY = -f.minY), + (f.touchesCurrent.x = m.length > 0 ? m[0].pageX : t.pageX), + (f.touchesCurrent.y = m.length > 0 ? m[0].pageY : t.pageY) + if ( + (Math.max( + Math.abs(f.touchesCurrent.x - f.touchesStart.x), + Math.abs(f.touchesCurrent.y - f.touchesStart.y) + ) > 5 && (e.allowClick = !1), + !f.isMoved && !c) + ) { + if ( + e.isHorizontal() && + ((Math.floor(f.minX) === Math.floor(f.startX) && + f.touchesCurrent.x < f.touchesStart.x) || + (Math.floor(f.maxX) === Math.floor(f.startX) && + f.touchesCurrent.x > f.touchesStart.x)) + ) + return (f.isTouched = !1), void I() + if ( + !e.isHorizontal() && + ((Math.floor(f.minY) === Math.floor(f.startY) && + f.touchesCurrent.y < f.touchesStart.y) || + (Math.floor(f.maxY) === Math.floor(f.startY) && + f.touchesCurrent.y > f.touchesStart.y)) + ) + return (f.isTouched = !1), void I() + } + t.cancelable && t.preventDefault(), + t.stopPropagation(), + clearTimeout(w), + (e.touchEventsData.preventTouchMoveFromPointerMove = !0), + (w = setTimeout(() => { + e.destroyed || I() + })), + (f.isMoved = !0) + const n = (a.scale - o) / (h.maxRatio - e.params.zoom.minRatio), + { originX: l, originY: p } = h + ;(f.currentX = + f.touchesCurrent.x - + f.touchesStart.x + + f.startX + + n * (f.width - 2 * l)), + (f.currentY = + f.touchesCurrent.y - + f.touchesStart.y + + f.startY + + n * (f.height - 2 * p)), + f.currentX < f.minX && + (f.currentX = f.minX + 1 - (f.minX - f.currentX + 1) ** 0.8), + f.currentX > f.maxX && + (f.currentX = f.maxX - 1 + (f.currentX - f.maxX + 1) ** 0.8), + f.currentY < f.minY && + (f.currentY = f.minY + 1 - (f.minY - f.currentY + 1) ** 0.8), + f.currentY > f.maxY && + (f.currentY = f.maxY - 1 + (f.currentY - f.maxY + 1) ** 0.8), + v.prevPositionX || (v.prevPositionX = f.touchesCurrent.x), + v.prevPositionY || (v.prevPositionY = f.touchesCurrent.y), + v.prevTime || (v.prevTime = Date.now()), + (v.x = + (f.touchesCurrent.x - v.prevPositionX) / + (Date.now() - v.prevTime) / + 2), + (v.y = + (f.touchesCurrent.y - v.prevPositionY) / + (Date.now() - v.prevTime) / + 2), + Math.abs(f.touchesCurrent.x - v.prevPositionX) < 2 && (v.x = 0), + Math.abs(f.touchesCurrent.y - v.prevPositionY) < 2 && (v.y = 0), + (v.prevPositionX = f.touchesCurrent.x), + (v.prevPositionY = f.touchesCurrent.y), + (v.prevTime = Date.now()), + (h.imageWrapEl.style.transform = `translate3d(${f.currentX}px, ${f.currentY}px,0)`) + } + function A() { + const t = e.zoom + h.slideEl && + e.activeIndex !== e.slides.indexOf(h.slideEl) && + (h.imageEl && + (h.imageEl.style.transform = 'translate3d(0,0,0) scale(1)'), + h.imageWrapEl && + (h.imageWrapEl.style.transform = 'translate3d(0,0,0)'), + h.slideEl.classList.remove(`${e.params.zoom.zoomedSlideClass}`), + (t.scale = 1), + (o = 1), + (h.slideEl = void 0), + (h.imageEl = void 0), + (h.imageWrapEl = void 0), + (h.originX = 0), + (h.originY = 0)) + } + function $(e) { + if (o <= 1 || !h.imageWrapEl) return + if (!T(e) || !M(e)) return + const t = i.getComputedStyle(h.imageWrapEl).transform, + s = new i.DOMMatrix(t) + if (!p) + return ( + (p = !0), + (u.x = e.clientX), + (u.y = e.clientY), + (f.startX = s.e), + (f.startY = s.f), + (f.width = h.imageEl.offsetWidth || h.imageEl.clientWidth), + (f.height = h.imageEl.offsetHeight || h.imageEl.clientHeight), + (h.slideWidth = h.slideEl.offsetWidth), + void (h.slideHeight = h.slideEl.offsetHeight) + ) + const a = -3 * (e.clientX - u.x), + r = -3 * (e.clientY - u.y), + n = f.width * o, + l = f.height * o, + d = h.slideWidth, + c = h.slideHeight, + m = Math.min(d / 2 - n / 2, 0), + g = -m, + v = Math.min(c / 2 - l / 2, 0), + w = -v, + b = Math.max(Math.min(f.startX + a, g), m), + y = Math.max(Math.min(f.startY + r, w), v) + ;(h.imageWrapEl.style.transitionDuration = '0ms'), + (h.imageWrapEl.style.transform = `translate3d(${b}px, ${y}px, 0)`), + (u.x = e.clientX), + (u.y = e.clientY), + (f.startX = b), + (f.startY = y), + (f.currentX = b), + (f.currentY = y) + } + function k(t) { + const s = e.zoom, + a = e.params.zoom + if (!h.slideEl) { + t && + t.target && + (h.slideEl = t.target.closest( + `.${e.params.slideClass}, swiper-slide` + )), + h.slideEl || + (e.params.virtual && e.params.virtual.enabled && e.virtual + ? (h.slideEl = g( + e.slidesEl, + `.${e.params.slideActiveClass}` + )[0]) + : (h.slideEl = e.slides[e.activeIndex])) + let s = h.slideEl.querySelector(`.${a.containerClass}`) + s && + (s = s.querySelectorAll( + 'picture, img, svg, canvas, .swiper-zoom-target' + )[0]), + (h.imageEl = s), + (h.imageWrapEl = s + ? x(h.imageEl, `.${a.containerClass}`)[0] + : void 0) + } + if (!h.imageEl || !h.imageWrapEl) return + let r, n, l, d, c, p, u, m, v, w, y, E, T, M, C, P, L, I + e.params.cssMode && + ((e.wrapperEl.style.overflow = 'hidden'), + (e.wrapperEl.style.touchAction = 'none')), + h.slideEl.classList.add(`${a.zoomedSlideClass}`), + void 0 === f.touchesStart.x && t + ? ((r = t.pageX), (n = t.pageY)) + : ((r = f.touchesStart.x), (n = f.touchesStart.y)) + const z = o, + A = 'number' == typeof t ? t : null + 1 === o && + A && + ((r = void 0), + (n = void 0), + (f.touchesStart.x = void 0), + (f.touchesStart.y = void 0)) + const $ = S() + ;(s.scale = A || $), + (o = A || $), + !t || (1 === o && A) + ? ((u = 0), (m = 0)) + : ((L = h.slideEl.offsetWidth), + (I = h.slideEl.offsetHeight), + (l = b(h.slideEl).left + i.scrollX), + (d = b(h.slideEl).top + i.scrollY), + (c = l + L / 2 - r), + (p = d + I / 2 - n), + (v = h.imageEl.offsetWidth || h.imageEl.clientWidth), + (w = h.imageEl.offsetHeight || h.imageEl.clientHeight), + (y = v * s.scale), + (E = w * s.scale), + (T = Math.min(L / 2 - y / 2, 0)), + (M = Math.min(I / 2 - E / 2, 0)), + (C = -T), + (P = -M), + z > 0 && + A && + 'number' == typeof f.currentX && + 'number' == typeof f.currentY + ? ((u = (f.currentX * s.scale) / z), + (m = (f.currentY * s.scale) / z)) + : ((u = c * s.scale), (m = p * s.scale)), + u < T && (u = T), + u > C && (u = C), + m < M && (m = M), + m > P && (m = P)), + A && 1 === s.scale && ((h.originX = 0), (h.originY = 0)), + (f.currentX = u), + (f.currentY = m), + (h.imageWrapEl.style.transitionDuration = '300ms'), + (h.imageWrapEl.style.transform = `translate3d(${u}px, ${m}px,0)`), + (h.imageEl.style.transitionDuration = '300ms'), + (h.imageEl.style.transform = `translate3d(0,0,0) scale(${s.scale})`) + } + function O() { + const t = e.zoom, + s = e.params.zoom + if (!h.slideEl) { + e.params.virtual && e.params.virtual.enabled && e.virtual + ? (h.slideEl = g(e.slidesEl, `.${e.params.slideActiveClass}`)[0]) + : (h.slideEl = e.slides[e.activeIndex]) + let t = h.slideEl.querySelector(`.${s.containerClass}`) + t && + (t = t.querySelectorAll( + 'picture, img, svg, canvas, .swiper-zoom-target' + )[0]), + (h.imageEl = t), + (h.imageWrapEl = t + ? x(h.imageEl, `.${s.containerClass}`)[0] + : void 0) + } + h.imageEl && + h.imageWrapEl && + (e.params.cssMode && + ((e.wrapperEl.style.overflow = ''), + (e.wrapperEl.style.touchAction = '')), + (t.scale = 1), + (o = 1), + (f.currentX = void 0), + (f.currentY = void 0), + (f.touchesStart.x = void 0), + (f.touchesStart.y = void 0), + (h.imageWrapEl.style.transitionDuration = '300ms'), + (h.imageWrapEl.style.transform = 'translate3d(0,0,0)'), + (h.imageEl.style.transitionDuration = '300ms'), + (h.imageEl.style.transform = 'translate3d(0,0,0) scale(1)'), + h.slideEl.classList.remove(`${s.zoomedSlideClass}`), + (h.slideEl = void 0), + (h.originX = 0), + (h.originY = 0), + e.params.zoom.panOnMouseMove && + ((u = { x: 0, y: 0 }), + p && ((p = !1), (f.startX = 0), (f.startY = 0)))) + } + function D(t) { + const s = e.zoom + s.scale && 1 !== s.scale ? O() : k(t) + } + function G() { + return { + passiveListener: !!e.params.passiveListeners && { + passive: !0, + capture: !1, + }, + activeListenerWithCapture: !e.params.passiveListeners || { + passive: !1, + capture: !0, + }, + } + } + function X() { + const t = e.zoom + if (t.enabled) return + t.enabled = !0 + const { passiveListener: s, activeListenerWithCapture: a } = G() + e.wrapperEl.addEventListener('pointerdown', C, s), + e.wrapperEl.addEventListener('pointermove', P, a), + ['pointerup', 'pointercancel', 'pointerout'].forEach((t) => { + e.wrapperEl.addEventListener(t, L, s) + }), + e.wrapperEl.addEventListener('pointermove', z, a) + } + function B() { + const t = e.zoom + if (!t.enabled) return + t.enabled = !1 + const { passiveListener: s, activeListenerWithCapture: a } = G() + e.wrapperEl.removeEventListener('pointerdown', C, s), + e.wrapperEl.removeEventListener('pointermove', P, a), + ['pointerup', 'pointercancel', 'pointerout'].forEach((t) => { + e.wrapperEl.removeEventListener(t, L, s) + }), + e.wrapperEl.removeEventListener('pointermove', z, a) + } + Object.defineProperty(e.zoom, 'scale', { + get: () => y, + set(e) { + if (y !== e) { + const t = h.imageEl, + s = h.slideEl + a('zoomChange', e, t, s) + } + y = e + }, + }), + s('init', () => { + e.params.zoom.enabled && X() + }), + s('destroy', () => { + B() + }), + s('touchStart', (t, s) => { + e.zoom.enabled && + (function (t) { + const s = e.device + if (!h.imageEl) return + if (f.isTouched) return + s.android && t.cancelable && t.preventDefault(), + (f.isTouched = !0) + const a = m.length > 0 ? m[0] : t + ;(f.touchesStart.x = a.pageX), (f.touchesStart.y = a.pageY) + })(s) + }), + s('touchEnd', (t, s) => { + e.zoom.enabled && + (function () { + const t = e.zoom + if (((m.length = 0), !h.imageEl)) return + if (!f.isTouched || !f.isMoved) + return (f.isTouched = !1), void (f.isMoved = !1) + ;(f.isTouched = !1), (f.isMoved = !1) + let s = 300, + a = 300 + const i = v.x * s, + r = f.currentX + i, + n = v.y * a, + l = f.currentY + n + 0 !== v.x && (s = Math.abs((r - f.currentX) / v.x)), + 0 !== v.y && (a = Math.abs((l - f.currentY) / v.y)) + const o = Math.max(s, a) + ;(f.currentX = r), (f.currentY = l) + const d = f.width * t.scale, + c = f.height * t.scale + ;(f.minX = Math.min(h.slideWidth / 2 - d / 2, 0)), + (f.maxX = -f.minX), + (f.minY = Math.min(h.slideHeight / 2 - c / 2, 0)), + (f.maxY = -f.minY), + (f.currentX = Math.max(Math.min(f.currentX, f.maxX), f.minX)), + (f.currentY = Math.max(Math.min(f.currentY, f.maxY), f.minY)), + (h.imageWrapEl.style.transitionDuration = `${o}ms`), + (h.imageWrapEl.style.transform = `translate3d(${f.currentX}px, ${f.currentY}px,0)`) + })() + }), + s('doubleTap', (t, s) => { + !e.animating && + e.params.zoom.enabled && + e.zoom.enabled && + e.params.zoom.toggle && + D(s) + }), + s('transitionEnd', () => { + e.zoom.enabled && e.params.zoom.enabled && A() + }), + s('slideChange', () => { + e.zoom.enabled && e.params.zoom.enabled && e.params.cssMode && A() + }), + Object.assign(e.zoom, { + enable: X, + disable: B, + in: k, + out: O, + toggle: D, + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + function a(e, t) { + const s = (function () { + let e, t, s + return (a, i) => { + for (t = -1, e = a.length; e - t > 1; ) + (s = (e + t) >> 1), a[s] <= i ? (t = s) : (e = s) + return e + } + })() + let a, i + return ( + (this.x = e), + (this.y = t), + (this.lastIndex = e.length - 1), + (this.interpolate = function (e) { + return e + ? ((i = s(this.x, e)), + (a = i - 1), + ((e - this.x[a]) * (this.y[i] - this.y[a])) / + (this.x[i] - this.x[a]) + + this.y[a]) + : 0 + }), + this + ) + } + function i() { + e.controller.control && + e.controller.spline && + ((e.controller.spline = void 0), delete e.controller.spline) + } + t({ controller: { control: void 0, inverse: !1, by: 'slide' } }), + (e.controller = { control: void 0 }), + s('beforeInit', () => { + if ( + 'undefined' != typeof window && + ('string' == typeof e.params.controller.control || + e.params.controller.control instanceof HTMLElement) + ) { + return void ( + 'string' == typeof e.params.controller.control + ? [...document.querySelectorAll(e.params.controller.control)] + : [e.params.controller.control] + ).forEach((t) => { + if ( + (e.controller.control || (e.controller.control = []), + t && t.swiper) + ) + e.controller.control.push(t.swiper) + else if (t) { + const s = `${e.params.eventsPrefix}init`, + a = (i) => { + e.controller.control.push(i.detail[0]), + e.update(), + t.removeEventListener(s, a) + } + t.addEventListener(s, a) + } + }) + } + e.controller.control = e.params.controller.control + }), + s('update', () => { + i() + }), + s('resize', () => { + i() + }), + s('observerUpdate', () => { + i() + }), + s('setTranslate', (t, s, a) => { + e.controller.control && + !e.controller.control.destroyed && + e.controller.setTranslate(s, a) + }), + s('setTransition', (t, s, a) => { + e.controller.control && + !e.controller.control.destroyed && + e.controller.setTransition(s, a) + }), + Object.assign(e.controller, { + setTranslate: function (t, s) { + const i = e.controller.control + let r, n + const l = e.constructor + function o(t) { + if (t.destroyed) return + const s = e.rtlTranslate ? -e.translate : e.translate + 'slide' === e.params.controller.by && + (!(function (t) { + e.controller.spline = e.params.loop + ? new a(e.slidesGrid, t.slidesGrid) + : new a(e.snapGrid, t.snapGrid) + })(t), + (n = -e.controller.spline.interpolate(-s))), + (n && 'container' !== e.params.controller.by) || + ((r = + (t.maxTranslate() - t.minTranslate()) / + (e.maxTranslate() - e.minTranslate())), + (!Number.isNaN(r) && Number.isFinite(r)) || (r = 1), + (n = (s - e.minTranslate()) * r + t.minTranslate())), + e.params.controller.inverse && (n = t.maxTranslate() - n), + t.updateProgress(n), + t.setTranslate(n, e), + t.updateActiveIndex(), + t.updateSlidesClasses() + } + if (Array.isArray(i)) + for (let e = 0; e < i.length; e += 1) + i[e] !== s && i[e] instanceof l && o(i[e]) + else i instanceof l && s !== i && o(i) + }, + setTransition: function (t, s) { + const a = e.constructor, + i = e.controller.control + let r + function n(s) { + s.destroyed || + (s.setTransition(t, e), + 0 !== t && + (s.transitionStart(), + s.params.autoHeight && + l(() => { + s.updateAutoHeight() + }), + S(s.wrapperEl, () => { + i && s.transitionEnd() + }))) + } + if (Array.isArray(i)) + for (r = 0; r < i.length; r += 1) + i[r] !== s && i[r] instanceof a && n(i[r]) + else i instanceof a && s !== i && n(i) + }, + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + a11y: { + enabled: !0, + notificationClass: 'swiper-notification', + prevSlideMessage: 'Previous slide', + nextSlideMessage: 'Next slide', + firstSlideMessage: 'This is the first slide', + lastSlideMessage: 'This is the last slide', + paginationBulletMessage: 'Go to slide {{index}}', + slideLabelMessage: '{{index}} / {{slidesLength}}', + containerMessage: null, + containerRoleDescriptionMessage: null, + containerRole: null, + itemRoleDescriptionMessage: null, + slideRole: 'group', + id: null, + scrollOnFocus: !0, + wrapperLiveRegion: !0, + }, + }), + (e.a11y = { clicked: !1 }) + let i, + r, + n = null, + l = new Date().getTime() + function o(e) { + const t = n + 0 !== t.length && P(t, e) + } + function d(e) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('tabIndex', '0') + }) + } + function c(e) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('tabIndex', '-1') + }) + } + function p(e, t) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('role', t) + }) + } + function u(e, t) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-roledescription', t) + }) + } + function m(e, t) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-label', t) + }) + } + function h(e) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-disabled', !0) + }) + } + function f(e) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-disabled', !1) + }) + } + function g(t) { + if (13 !== t.keyCode && 32 !== t.keyCode) return + const s = e.params.a11y, + a = t.target + if ( + !e.pagination || + !e.pagination.el || + (a !== e.pagination.el && !e.pagination.el.contains(t.target)) || + t.target.matches(de(e.params.pagination.bulletClass)) + ) { + if (e.navigation && e.navigation.prevEl && e.navigation.nextEl) { + const t = M(e.navigation.prevEl) + M(e.navigation.nextEl).includes(a) && + ((e.isEnd && !e.params.loop) || e.slideNext(), + e.isEnd ? o(s.lastSlideMessage) : o(s.nextSlideMessage)), + t.includes(a) && + ((e.isBeginning && !e.params.loop) || e.slidePrev(), + e.isBeginning ? o(s.firstSlideMessage) : o(s.prevSlideMessage)) + } + e.pagination && + a.matches(de(e.params.pagination.bulletClass)) && + a.click() + } + } + function v() { + return ( + e.pagination && e.pagination.bullets && e.pagination.bullets.length + ) + } + function b() { + return v() && e.params.pagination.clickable + } + const y = (e, t, s) => { + d(e), + 'BUTTON' !== e.tagName && + (p(e, 'button'), e.addEventListener('keydown', g)), + m(e, s), + (function (e, t) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-controls', t) + }) + })(e, t) + }, + x = (t) => { + r && r !== t.target && !r.contains(t.target) && (i = !0), + (e.a11y.clicked = !0) + }, + S = () => { + ;(i = !1), + requestAnimationFrame(() => { + requestAnimationFrame(() => { + e.destroyed || (e.a11y.clicked = !1) + }) + }) + }, + T = (e) => { + l = new Date().getTime() + }, + C = (t) => { + if (e.a11y.clicked || !e.params.a11y.scrollOnFocus) return + if (new Date().getTime() - l < 100) return + const s = t.target.closest(`.${e.params.slideClass}, swiper-slide`) + if (!s || !e.slides.includes(s)) return + r = s + const a = e.slides.indexOf(s) === e.activeIndex, + n = + e.params.watchSlidesProgress && + e.visibleSlides && + e.visibleSlides.includes(s) + a || + n || + (t.sourceCapabilities && t.sourceCapabilities.firesTouchEvents) || + (e.isHorizontal() ? (e.el.scrollLeft = 0) : (e.el.scrollTop = 0), + requestAnimationFrame(() => { + i || + (e.params.loop + ? e.slideToLoop( + e.getSlideIndexWhenGrid( + parseInt(s.getAttribute('data-swiper-slide-index')) + ), + 0 + ) + : e.slideTo(e.getSlideIndexWhenGrid(e.slides.indexOf(s)), 0), + (i = !1)) + })) + }, + L = () => { + const t = e.params.a11y + t.itemRoleDescriptionMessage && + u(e.slides, t.itemRoleDescriptionMessage), + t.slideRole && p(e.slides, t.slideRole) + const s = e.slides.length + t.slideLabelMessage && + e.slides.forEach((a, i) => { + const r = e.params.loop + ? parseInt(a.getAttribute('data-swiper-slide-index'), 10) + : i + m( + a, + t.slideLabelMessage + .replace(/\{\{index\}\}/, r + 1) + .replace(/\{\{slidesLength\}\}/, s) + ) + }) + }, + I = () => { + const t = e.params.a11y + e.el.append(n) + const s = e.el + t.containerRoleDescriptionMessage && + u(s, t.containerRoleDescriptionMessage), + t.containerMessage && m(s, t.containerMessage), + t.containerRole && p(s, t.containerRole) + const i = e.wrapperEl, + r = + t.id || + i.getAttribute('id') || + `swiper-wrapper-${(function (e = 16) { + return 'x' + .repeat(e) + .replace(/x/g, () => + Math.round(16 * Math.random()).toString(16) + ) + })(16)}` + var l + if ( + ((l = r), + M(i).forEach((e) => { + e.setAttribute('id', l) + }), + t.wrapperLiveRegion) + ) { + !(function (e, t) { + ;(e = M(e)).forEach((e) => { + e.setAttribute('aria-live', t) + }) + })( + i, + e.params.autoplay && e.params.autoplay.enabled ? 'off' : 'polite' + ) + } + L() + let { nextEl: o, prevEl: d } = e.navigation ? e.navigation : {} + if ( + ((o = M(o)), + (d = M(d)), + o && o.forEach((e) => y(e, r, t.nextSlideMessage)), + d && d.forEach((e) => y(e, r, t.prevSlideMessage)), + b()) + ) { + M(e.pagination.el).forEach((e) => { + e.addEventListener('keydown', g) + }) + } + a().addEventListener('visibilitychange', T), + e.el.addEventListener('focus', C, !0), + e.el.addEventListener('focus', C, !0), + e.el.addEventListener('pointerdown', x, !0), + e.el.addEventListener('pointerup', S, !0) + } + s('beforeInit', () => { + ;(n = w('span', e.params.a11y.notificationClass)), + n.setAttribute('aria-live', 'assertive'), + n.setAttribute('aria-atomic', 'true') + }), + s('afterInit', () => { + e.params.a11y.enabled && I() + }), + s( + 'slidesLengthChange snapGridLengthChange slidesGridLengthChange', + () => { + e.params.a11y.enabled && L() + } + ), + s('fromEdge toEdge afterInit lock unlock', () => { + e.params.a11y.enabled && + (function () { + if (e.params.loop || e.params.rewind || !e.navigation) return + const { nextEl: t, prevEl: s } = e.navigation + s && (e.isBeginning ? (h(s), c(s)) : (f(s), d(s))), + t && (e.isEnd ? (h(t), c(t)) : (f(t), d(t))) + })() + }), + s('paginationUpdate', () => { + e.params.a11y.enabled && + (function () { + const t = e.params.a11y + v() && + e.pagination.bullets.forEach((s) => { + e.params.pagination.clickable && + (d(s), + e.params.pagination.renderBullet || + (p(s, 'button'), + m( + s, + t.paginationBulletMessage.replace( + /\{\{index\}\}/, + E(s) + 1 + ) + ))), + s.matches(de(e.params.pagination.bulletActiveClass)) + ? s.setAttribute('aria-current', 'true') + : s.removeAttribute('aria-current') + }) + })() + }), + s('destroy', () => { + e.params.a11y.enabled && + (function () { + n && n.remove() + let { nextEl: t, prevEl: s } = e.navigation ? e.navigation : {} + ;(t = M(t)), + (s = M(s)), + t && t.forEach((e) => e.removeEventListener('keydown', g)), + s && s.forEach((e) => e.removeEventListener('keydown', g)), + b() && + M(e.pagination.el).forEach((e) => { + e.removeEventListener('keydown', g) + }) + a().removeEventListener('visibilitychange', T), + e.el && + 'string' != typeof e.el && + (e.el.removeEventListener('focus', C, !0), + e.el.removeEventListener('pointerdown', x, !0), + e.el.removeEventListener('pointerup', S, !0)) + })() + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + history: { + enabled: !1, + root: '', + replaceState: !1, + key: 'slides', + keepQuery: !1, + }, + }) + let a = !1, + i = {} + const n = (e) => + e + .toString() + .replace(/\s+/g, '-') + .replace(/[^\w-]+/g, '') + .replace(/--+/g, '-') + .replace(/^-+/, '') + .replace(/-+$/, ''), + l = (e) => { + const t = r() + let s + s = e ? new URL(e) : t.location + const a = s.pathname + .slice(1) + .split('/') + .filter((e) => '' !== e), + i = a.length + return { key: a[i - 2], value: a[i - 1] } + }, + o = (t, s) => { + const i = r() + if (!a || !e.params.history.enabled) return + let l + l = e.params.url ? new URL(e.params.url) : i.location + const o = + e.virtual && e.params.virtual.enabled + ? e.slidesEl.querySelector(`[data-swiper-slide-index="${s}"]`) + : e.slides[s] + let d = n(o.getAttribute('data-history')) + if (e.params.history.root.length > 0) { + let s = e.params.history.root + '/' === s[s.length - 1] && (s = s.slice(0, s.length - 1)), + (d = `${s}/${t ? `${t}/` : ''}${d}`) + } else l.pathname.includes(t) || (d = `${t ? `${t}/` : ''}${d}`) + e.params.history.keepQuery && (d += l.search) + const c = i.history.state + ;(c && c.value === d) || + (e.params.history.replaceState + ? i.history.replaceState({ value: d }, null, d) + : i.history.pushState({ value: d }, null, d)) + }, + d = (t, s, a) => { + if (s) + for (let i = 0, r = e.slides.length; i < r; i += 1) { + const r = e.slides[i] + if (n(r.getAttribute('data-history')) === s) { + const s = e.getSlideIndex(r) + e.slideTo(s, t, a) + } + } + else e.slideTo(0, t, a) + }, + c = () => { + ;(i = l(e.params.url)), d(e.params.speed, i.value, !1) + } + s('init', () => { + e.params.history.enabled && + (() => { + const t = r() + if (e.params.history) { + if (!t.history || !t.history.pushState) + return ( + (e.params.history.enabled = !1), + void (e.params.hashNavigation.enabled = !0) + ) + ;(a = !0), + (i = l(e.params.url)), + i.key || i.value + ? (d(0, i.value, e.params.runCallbacksOnInit), + e.params.history.replaceState || + t.addEventListener('popstate', c)) + : e.params.history.replaceState || + t.addEventListener('popstate', c) + } + })() + }), + s('destroy', () => { + e.params.history.enabled && + (() => { + const t = r() + e.params.history.replaceState || + t.removeEventListener('popstate', c) + })() + }), + s('transitionEnd _freeModeNoMomentumRelease', () => { + a && o(e.params.history.key, e.activeIndex) + }), + s('slideChange', () => { + a && e.params.cssMode && o(e.params.history.key, e.activeIndex) + }) + }, + function ({ swiper: e, extendParams: t, emit: s, on: i }) { + let n = !1 + const l = a(), + o = r() + t({ + hashNavigation: { + enabled: !1, + replaceState: !1, + watchState: !1, + getSlideIndex(t, s) { + if (e.virtual && e.params.virtual.enabled) { + const t = e.slides.find((e) => e.getAttribute('data-hash') === s) + if (!t) return 0 + return parseInt(t.getAttribute('data-swiper-slide-index'), 10) + } + return e.getSlideIndex( + g( + e.slidesEl, + `.${e.params.slideClass}[data-hash="${s}"], swiper-slide[data-hash="${s}"]` + )[0] + ) + }, + }, + }) + const d = () => { + s('hashChange') + const t = l.location.hash.replace('#', ''), + a = + e.virtual && e.params.virtual.enabled + ? e.slidesEl.querySelector( + `[data-swiper-slide-index="${e.activeIndex}"]` + ) + : e.slides[e.activeIndex] + if (t !== (a ? a.getAttribute('data-hash') : '')) { + const s = e.params.hashNavigation.getSlideIndex(e, t) + if (void 0 === s || Number.isNaN(s)) return + e.slideTo(s) + } + }, + c = () => { + if (!n || !e.params.hashNavigation.enabled) return + const t = + e.virtual && e.params.virtual.enabled + ? e.slidesEl.querySelector( + `[data-swiper-slide-index="${e.activeIndex}"]` + ) + : e.slides[e.activeIndex], + a = t + ? t.getAttribute('data-hash') || t.getAttribute('data-history') + : '' + e.params.hashNavigation.replaceState && + o.history && + o.history.replaceState + ? (o.history.replaceState(null, null, `#${a}` || ''), s('hashSet')) + : ((l.location.hash = a || ''), s('hashSet')) + } + i('init', () => { + e.params.hashNavigation.enabled && + (() => { + if ( + !e.params.hashNavigation.enabled || + (e.params.history && e.params.history.enabled) + ) + return + n = !0 + const t = l.location.hash.replace('#', '') + if (t) { + const s = 0, + a = e.params.hashNavigation.getSlideIndex(e, t) + e.slideTo(a || 0, s, e.params.runCallbacksOnInit, !0) + } + e.params.hashNavigation.watchState && + o.addEventListener('hashchange', d) + })() + }), + i('destroy', () => { + e.params.hashNavigation.enabled && + e.params.hashNavigation.watchState && + o.removeEventListener('hashchange', d) + }), + i('transitionEnd _freeModeNoMomentumRelease', () => { + n && c() + }), + i('slideChange', () => { + n && e.params.cssMode && c() + }) + }, + function ({ swiper: e, extendParams: t, on: s, emit: i, params: r }) { + let n, l + ;(e.autoplay = { running: !1, paused: !1, timeLeft: 0 }), + t({ + autoplay: { + enabled: !1, + delay: 3e3, + waitForTransition: !0, + disableOnInteraction: !1, + stopOnLastSlide: !1, + reverseDirection: !1, + pauseOnMouseEnter: !1, + }, + }) + let o, + d, + c, + p, + u, + m, + h, + f, + g = r && r.autoplay ? r.autoplay.delay : 3e3, + v = r && r.autoplay ? r.autoplay.delay : 3e3, + w = new Date().getTime() + function b(t) { + e && + !e.destroyed && + e.wrapperEl && + t.target === e.wrapperEl && + (e.wrapperEl.removeEventListener('transitionend', b), + f || (t.detail && t.detail.bySwiperTouchMove) || M()) + } + const y = () => { + if (e.destroyed || !e.autoplay.running) return + e.autoplay.paused ? (d = !0) : d && ((v = o), (d = !1)) + const t = e.autoplay.paused ? o : w + v - new Date().getTime() + ;(e.autoplay.timeLeft = t), + i('autoplayTimeLeft', t, t / g), + (l = requestAnimationFrame(() => { + y() + })) + }, + E = (t) => { + if (e.destroyed || !e.autoplay.running) return + cancelAnimationFrame(l), y() + let s = void 0 === t ? e.params.autoplay.delay : t + ;(g = e.params.autoplay.delay), (v = e.params.autoplay.delay) + const a = (() => { + let t + if ( + ((t = + e.virtual && e.params.virtual.enabled + ? e.slides.find((e) => + e.classList.contains('swiper-slide-active') + ) + : e.slides[e.activeIndex]), + !t) + ) + return + return parseInt(t.getAttribute('data-swiper-autoplay'), 10) + })() + !Number.isNaN(a) && + a > 0 && + void 0 === t && + ((s = a), (g = a), (v = a)), + (o = s) + const r = e.params.speed, + d = () => { + e && + !e.destroyed && + (e.params.autoplay.reverseDirection + ? !e.isBeginning || e.params.loop || e.params.rewind + ? (e.slidePrev(r, !0, !0), i('autoplay')) + : e.params.autoplay.stopOnLastSlide || + (e.slideTo(e.slides.length - 1, r, !0, !0), i('autoplay')) + : !e.isEnd || e.params.loop || e.params.rewind + ? (e.slideNext(r, !0, !0), i('autoplay')) + : e.params.autoplay.stopOnLastSlide || + (e.slideTo(0, r, !0, !0), i('autoplay')), + e.params.cssMode && + ((w = new Date().getTime()), + requestAnimationFrame(() => { + E() + }))) + } + return ( + s > 0 + ? (clearTimeout(n), + (n = setTimeout(() => { + d() + }, s))) + : requestAnimationFrame(() => { + d() + }), + s + ) + }, + x = () => { + ;(w = new Date().getTime()), + (e.autoplay.running = !0), + E(), + i('autoplayStart') + }, + S = () => { + ;(e.autoplay.running = !1), + clearTimeout(n), + cancelAnimationFrame(l), + i('autoplayStop') + }, + T = (t, s) => { + if (e.destroyed || !e.autoplay.running) return + clearTimeout(n), t || (h = !0) + const a = () => { + i('autoplayPause'), + e.params.autoplay.waitForTransition + ? e.wrapperEl.addEventListener('transitionend', b) + : M() + } + if (((e.autoplay.paused = !0), s)) + return m && (o = e.params.autoplay.delay), (m = !1), void a() + const r = o || e.params.autoplay.delay + ;(o = r - (new Date().getTime() - w)), + (e.isEnd && o < 0 && !e.params.loop) || (o < 0 && (o = 0), a()) + }, + M = () => { + ;(e.isEnd && o < 0 && !e.params.loop) || + e.destroyed || + !e.autoplay.running || + ((w = new Date().getTime()), + h ? ((h = !1), E(o)) : E(), + (e.autoplay.paused = !1), + i('autoplayResume')) + }, + C = () => { + if (e.destroyed || !e.autoplay.running) return + const t = a() + 'hidden' === t.visibilityState && ((h = !0), T(!0)), + 'visible' === t.visibilityState && M() + }, + P = (t) => { + 'mouse' === t.pointerType && + ((h = !0), (f = !0), e.animating || e.autoplay.paused || T(!0)) + }, + L = (t) => { + 'mouse' === t.pointerType && ((f = !1), e.autoplay.paused && M()) + } + s('init', () => { + e.params.autoplay.enabled && + (e.params.autoplay.pauseOnMouseEnter && + (e.el.addEventListener('pointerenter', P), + e.el.addEventListener('pointerleave', L)), + a().addEventListener('visibilitychange', C), + x()) + }), + s('destroy', () => { + e.el && + 'string' != typeof e.el && + (e.el.removeEventListener('pointerenter', P), + e.el.removeEventListener('pointerleave', L)), + a().removeEventListener('visibilitychange', C), + e.autoplay.running && S() + }), + s('_freeModeStaticRelease', () => { + ;(p || h) && M() + }), + s('_freeModeNoMomentumRelease', () => { + e.params.autoplay.disableOnInteraction ? S() : T(!0, !0) + }), + s('beforeTransitionStart', (t, s, a) => { + !e.destroyed && + e.autoplay.running && + (a || !e.params.autoplay.disableOnInteraction ? T(!0, !0) : S()) + }), + s('sliderFirstMove', () => { + !e.destroyed && + e.autoplay.running && + (e.params.autoplay.disableOnInteraction + ? S() + : ((c = !0), + (p = !1), + (h = !1), + (u = setTimeout(() => { + ;(h = !0), (p = !0), T(!0) + }, 200)))) + }), + s('touchEnd', () => { + if (!e.destroyed && e.autoplay.running && c) { + if ( + (clearTimeout(u), + clearTimeout(n), + e.params.autoplay.disableOnInteraction) + ) + return (p = !1), void (c = !1) + p && e.params.cssMode && M(), (p = !1), (c = !1) + } + }), + s('slideChange', () => { + !e.destroyed && e.autoplay.running && (m = !0) + }), + Object.assign(e.autoplay, { start: x, stop: S, pause: T, resume: M }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + thumbs: { + swiper: null, + multipleActiveThumbs: !0, + autoScrollOffset: 0, + slideThumbActiveClass: 'swiper-slide-thumb-active', + thumbsContainerClass: 'swiper-thumbs', + }, + }) + let i = !1, + r = !1 + function n() { + const t = e.thumbs.swiper + if (!t || t.destroyed) return + const s = t.clickedIndex, + a = t.clickedSlide + if (a && a.classList.contains(e.params.thumbs.slideThumbActiveClass)) + return + if (null == s) return + let i + ;(i = t.params.loop + ? parseInt(t.clickedSlide.getAttribute('data-swiper-slide-index'), 10) + : s), + e.params.loop ? e.slideToLoop(i) : e.slideTo(i) + } + function l() { + const { thumbs: t } = e.params + if (i) return !1 + i = !0 + const s = e.constructor + if (t.swiper instanceof s) { + if (t.swiper.destroyed) return (i = !1), !1 + ;(e.thumbs.swiper = t.swiper), + Object.assign(e.thumbs.swiper.originalParams, { + watchSlidesProgress: !0, + slideToClickedSlide: !1, + }), + Object.assign(e.thumbs.swiper.params, { + watchSlidesProgress: !0, + slideToClickedSlide: !1, + }), + e.thumbs.swiper.update() + } else if (c(t.swiper)) { + const a = Object.assign({}, t.swiper) + Object.assign(a, { + watchSlidesProgress: !0, + slideToClickedSlide: !1, + }), + (e.thumbs.swiper = new s(a)), + (r = !0) + } + return ( + e.thumbs.swiper.el.classList.add( + e.params.thumbs.thumbsContainerClass + ), + e.thumbs.swiper.on('tap', n), + !0 + ) + } + function o(t) { + const s = e.thumbs.swiper + if (!s || s.destroyed) return + const a = + 'auto' === s.params.slidesPerView + ? s.slidesPerViewDynamic() + : s.params.slidesPerView + let i = 1 + const r = e.params.thumbs.slideThumbActiveClass + if ( + (e.params.slidesPerView > 1 && + !e.params.centeredSlides && + (i = e.params.slidesPerView), + e.params.thumbs.multipleActiveThumbs || (i = 1), + (i = Math.floor(i)), + s.slides.forEach((e) => e.classList.remove(r)), + s.params.loop || (s.params.virtual && s.params.virtual.enabled)) + ) + for (let t = 0; t < i; t += 1) + g( + s.slidesEl, + `[data-swiper-slide-index="${e.realIndex + t}"]` + ).forEach((e) => { + e.classList.add(r) + }) + else + for (let t = 0; t < i; t += 1) + s.slides[e.realIndex + t] && + s.slides[e.realIndex + t].classList.add(r) + const n = e.params.thumbs.autoScrollOffset, + l = n && !s.params.loop + if (e.realIndex !== s.realIndex || l) { + const i = s.activeIndex + let r, o + if (s.params.loop) { + const t = s.slides.find( + (t) => + t.getAttribute('data-swiper-slide-index') === `${e.realIndex}` + ) + ;(r = s.slides.indexOf(t)), + (o = e.activeIndex > e.previousIndex ? 'next' : 'prev') + } else (r = e.realIndex), (o = r > e.previousIndex ? 'next' : 'prev') + l && (r += 'next' === o ? n : -1 * n), + s.visibleSlidesIndexes && + s.visibleSlidesIndexes.indexOf(r) < 0 && + (s.params.centeredSlides + ? (r = + r > i + ? r - Math.floor(a / 2) + 1 + : r + Math.floor(a / 2) - 1) + : r > i && s.params.slidesPerGroup, + s.slideTo(r, t ? 0 : void 0)) + } + } + ;(e.thumbs = { swiper: null }), + s('beforeInit', () => { + const { thumbs: t } = e.params + if (t && t.swiper) + if ( + 'string' == typeof t.swiper || + t.swiper instanceof HTMLElement + ) { + const s = a(), + i = () => { + const a = + 'string' == typeof t.swiper + ? s.querySelector(t.swiper) + : t.swiper + if (a && a.swiper) (t.swiper = a.swiper), l(), o(!0) + else if (a) { + const s = `${e.params.eventsPrefix}init`, + i = (r) => { + ;(t.swiper = r.detail[0]), + a.removeEventListener(s, i), + l(), + o(!0), + t.swiper.update(), + e.update() + } + a.addEventListener(s, i) + } + return a + }, + r = () => { + if (e.destroyed) return + i() || requestAnimationFrame(r) + } + requestAnimationFrame(r) + } else l(), o(!0) + }), + s('slideChange update resize observerUpdate', () => { + o() + }), + s('setTransition', (t, s) => { + const a = e.thumbs.swiper + a && !a.destroyed && a.setTransition(s) + }), + s('beforeDestroy', () => { + const t = e.thumbs.swiper + t && !t.destroyed && r && t.destroy() + }), + Object.assign(e.thumbs, { init: l, update: o }) + }, + function ({ swiper: e, extendParams: t, emit: s, once: a }) { + t({ + freeMode: { + enabled: !1, + momentum: !0, + momentumRatio: 1, + momentumBounce: !0, + momentumBounceRatio: 1, + momentumVelocityRatio: 1, + sticky: !1, + minimumVelocity: 0.02, + }, + }), + Object.assign(e, { + freeMode: { + onTouchStart: function () { + if (e.params.cssMode) return + const t = e.getTranslate() + e.setTranslate(t), + e.setTransition(0), + (e.touchEventsData.velocities.length = 0), + e.freeMode.onTouchEnd({ + currentPos: e.rtl ? e.translate : -e.translate, + }) + }, + onTouchMove: function () { + if (e.params.cssMode) return + const { touchEventsData: t, touches: s } = e + 0 === t.velocities.length && + t.velocities.push({ + position: s[e.isHorizontal() ? 'startX' : 'startY'], + time: t.touchStartTime, + }), + t.velocities.push({ + position: s[e.isHorizontal() ? 'currentX' : 'currentY'], + time: o(), + }) + }, + onTouchEnd: function ({ currentPos: t }) { + if (e.params.cssMode) return + const { + params: i, + wrapperEl: r, + rtlTranslate: n, + snapGrid: l, + touchEventsData: d, + } = e, + c = o() - d.touchStartTime + if (t < -e.minTranslate()) e.slideTo(e.activeIndex) + else if (t > -e.maxTranslate()) + e.slides.length < l.length + ? e.slideTo(l.length - 1) + : e.slideTo(e.slides.length - 1) + else { + if (i.freeMode.momentum) { + if (d.velocities.length > 1) { + const t = d.velocities.pop(), + s = d.velocities.pop(), + a = t.position - s.position, + r = t.time - s.time + ;(e.velocity = a / r), + (e.velocity /= 2), + Math.abs(e.velocity) < i.freeMode.minimumVelocity && + (e.velocity = 0), + (r > 150 || o() - t.time > 300) && (e.velocity = 0) + } else e.velocity = 0 + ;(e.velocity *= i.freeMode.momentumVelocityRatio), + (d.velocities.length = 0) + let t = 1e3 * i.freeMode.momentumRatio + const c = e.velocity * t + let p = e.translate + c + n && (p = -p) + let u, + m = !1 + const h = + 20 * Math.abs(e.velocity) * i.freeMode.momentumBounceRatio + let f + if (p < e.maxTranslate()) + i.freeMode.momentumBounce + ? (p + e.maxTranslate() < -h && + (p = e.maxTranslate() - h), + (u = e.maxTranslate()), + (m = !0), + (d.allowMomentumBounce = !0)) + : (p = e.maxTranslate()), + i.loop && i.centeredSlides && (f = !0) + else if (p > e.minTranslate()) + i.freeMode.momentumBounce + ? (p - e.minTranslate() > h && (p = e.minTranslate() + h), + (u = e.minTranslate()), + (m = !0), + (d.allowMomentumBounce = !0)) + : (p = e.minTranslate()), + i.loop && i.centeredSlides && (f = !0) + else if (i.freeMode.sticky) { + let t + for (let e = 0; e < l.length; e += 1) + if (l[e] > -p) { + t = e + break + } + ;(p = + Math.abs(l[t] - p) < Math.abs(l[t - 1] - p) || + 'next' === e.swipeDirection + ? l[t] + : l[t - 1]), + (p = -p) + } + if ( + (f && + a('transitionEnd', () => { + e.loopFix() + }), + 0 !== e.velocity) + ) { + if ( + ((t = n + ? Math.abs((-p - e.translate) / e.velocity) + : Math.abs((p - e.translate) / e.velocity)), + i.freeMode.sticky) + ) { + const s = Math.abs((n ? -p : p) - e.translate), + a = e.slidesSizesGrid[e.activeIndex] + t = + s < a + ? i.speed + : s < 2 * a + ? 1.5 * i.speed + : 2.5 * i.speed + } + } else if (i.freeMode.sticky) return void e.slideToClosest() + i.freeMode.momentumBounce && m + ? (e.updateProgress(u), + e.setTransition(t), + e.setTranslate(p), + e.transitionStart(!0, e.swipeDirection), + (e.animating = !0), + S(r, () => { + e && + !e.destroyed && + d.allowMomentumBounce && + (s('momentumBounce'), + e.setTransition(i.speed), + setTimeout(() => { + e.setTranslate(u), + S(r, () => { + e && !e.destroyed && e.transitionEnd() + }) + }, 0)) + })) + : e.velocity + ? (s('_freeModeNoMomentumRelease'), + e.updateProgress(p), + e.setTransition(t), + e.setTranslate(p), + e.transitionStart(!0, e.swipeDirection), + e.animating || + ((e.animating = !0), + S(r, () => { + e && !e.destroyed && e.transitionEnd() + }))) + : e.updateProgress(p), + e.updateActiveIndex(), + e.updateSlidesClasses() + } else { + if (i.freeMode.sticky) return void e.slideToClosest() + i.freeMode && s('_freeModeNoMomentumRelease') + } + ;(!i.freeMode.momentum || c >= i.longSwipesMs) && + (s('_freeModeStaticRelease'), + e.updateProgress(), + e.updateActiveIndex(), + e.updateSlidesClasses()) + } + }, + }, + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + let a, i, r, n + t({ grid: { rows: 1, fill: 'column' } }) + const l = () => { + let t = e.params.spaceBetween + return ( + 'string' == typeof t && t.indexOf('%') >= 0 + ? (t = (parseFloat(t.replace('%', '')) / 100) * e.size) + : 'string' == typeof t && (t = parseFloat(t)), + t + ) + } + s('init', () => { + n = e.params.grid && e.params.grid.rows > 1 + }), + s('update', () => { + const { params: t, el: s } = e, + a = t.grid && t.grid.rows > 1 + n && !a + ? (s.classList.remove( + `${t.containerModifierClass}grid`, + `${t.containerModifierClass}grid-column` + ), + (r = 1), + e.emitContainerClasses()) + : !n && + a && + (s.classList.add(`${t.containerModifierClass}grid`), + 'column' === t.grid.fill && + s.classList.add(`${t.containerModifierClass}grid-column`), + e.emitContainerClasses()), + (n = a) + }), + (e.grid = { + initSlides: (t) => { + const { slidesPerView: s } = e.params, + { rows: n, fill: l } = e.params.grid, + o = + e.virtual && e.params.virtual.enabled + ? e.virtual.slides.length + : t.length + ;(r = Math.floor(o / n)), + (a = Math.floor(o / n) === o / n ? o : Math.ceil(o / n) * n), + 'auto' !== s && 'row' === l && (a = Math.max(a, s * n)), + (i = a / n) + }, + unsetSlides: () => { + e.slides && + e.slides.forEach((t) => { + t.swiperSlideGridSet && + ((t.style.height = ''), + (t.style[e.getDirectionLabel('margin-top')] = '')) + }) + }, + updateSlide: (t, s, n) => { + const { slidesPerGroup: o } = e.params, + d = l(), + { rows: c, fill: p } = e.params.grid, + u = + e.virtual && e.params.virtual.enabled + ? e.virtual.slides.length + : n.length + let m, h, f + if ('row' === p && o > 1) { + const e = Math.floor(t / (o * c)), + i = t - c * o * e, + r = 0 === e ? o : Math.min(Math.ceil((u - e * c * o) / c), o) + ;(f = Math.floor(i / r)), + (h = i - f * r + e * o), + (m = h + (f * a) / c), + (s.style.order = m) + } else + 'column' === p + ? ((h = Math.floor(t / c)), + (f = t - h * c), + (h > r || (h === r && f === c - 1)) && + ((f += 1), f >= c && ((f = 0), (h += 1)))) + : ((f = Math.floor(t / i)), (h = t - f * i)) + ;(s.row = f), + (s.column = h), + (s.style.height = `calc((100% - ${(c - 1) * d}px) / ${c})`), + (s.style[e.getDirectionLabel('margin-top')] = + 0 !== f ? d && `${d}px` : ''), + (s.swiperSlideGridSet = !0) + }, + updateWrapperSize: (t, s) => { + const { centeredSlides: i, roundLengths: r } = e.params, + n = l(), + { rows: o } = e.params.grid + if ( + ((e.virtualSize = (t + n) * a), + (e.virtualSize = Math.ceil(e.virtualSize / o) - n), + e.params.cssMode || + (e.wrapperEl.style[e.getDirectionLabel('width')] = `${ + e.virtualSize + n + }px`), + i) + ) { + const t = [] + for (let a = 0; a < s.length; a += 1) { + let i = s[a] + r && (i = Math.floor(i)), + s[a] < e.virtualSize + s[0] && t.push(i) + } + s.splice(0, s.length), s.push(...t) + } + }, + }) + }, + function ({ swiper: e }) { + Object.assign(e, { + appendSlide: ce.bind(e), + prependSlide: pe.bind(e), + addSlide: ue.bind(e), + removeSlide: me.bind(e), + removeAllSlides: he.bind(e), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ fadeEffect: { crossFade: !1 } }), + fe({ + effect: 'fade', + swiper: e, + on: s, + setTranslate: () => { + const { slides: t } = e + e.params.fadeEffect + for (let s = 0; s < t.length; s += 1) { + const t = e.slides[s] + let a = -t.swiperSlideOffset + e.params.virtualTranslate || (a -= e.translate) + let i = 0 + e.isHorizontal() || ((i = a), (a = 0)) + const r = e.params.fadeEffect.crossFade + ? Math.max(1 - Math.abs(t.progress), 0) + : 1 + Math.min(Math.max(t.progress, -1), 0), + n = ge(0, t) + ;(n.style.opacity = r), + (n.style.transform = `translate3d(${a}px, ${i}px, 0px)`) + } + }, + setTransition: (t) => { + const s = e.slides.map((e) => f(e)) + s.forEach((e) => { + e.style.transitionDuration = `${t}ms` + }), + ve({ + swiper: e, + duration: t, + transformElements: s, + allSlides: !0, + }) + }, + overwriteParams: () => ({ + slidesPerView: 1, + slidesPerGroup: 1, + watchSlidesProgress: !0, + spaceBetween: 0, + virtualTranslate: !e.params.cssMode, + }), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + cubeEffect: { + slideShadows: !0, + shadow: !0, + shadowOffset: 20, + shadowScale: 0.94, + }, + }) + const a = (e, t, s) => { + let a = s + ? e.querySelector('.swiper-slide-shadow-left') + : e.querySelector('.swiper-slide-shadow-top'), + i = s + ? e.querySelector('.swiper-slide-shadow-right') + : e.querySelector('.swiper-slide-shadow-bottom') + a || + ((a = w( + 'div', + ( + 'swiper-slide-shadow-cube swiper-slide-shadow-' + + (s ? 'left' : 'top') + ).split(' ') + )), + e.append(a)), + i || + ((i = w( + 'div', + ( + 'swiper-slide-shadow-cube swiper-slide-shadow-' + + (s ? 'right' : 'bottom') + ).split(' ') + )), + e.append(i)), + a && (a.style.opacity = Math.max(-t, 0)), + i && (i.style.opacity = Math.max(t, 0)) + } + fe({ + effect: 'cube', + swiper: e, + on: s, + setTranslate: () => { + const { + el: t, + wrapperEl: s, + slides: i, + width: r, + height: n, + rtlTranslate: l, + size: o, + browser: d, + } = e, + c = C(e), + p = e.params.cubeEffect, + u = e.isHorizontal(), + m = e.virtual && e.params.virtual.enabled + let h, + f = 0 + p.shadow && + (u + ? ((h = e.wrapperEl.querySelector('.swiper-cube-shadow')), + h || + ((h = w('div', 'swiper-cube-shadow')), e.wrapperEl.append(h)), + (h.style.height = `${r}px`)) + : ((h = t.querySelector('.swiper-cube-shadow')), + h || ((h = w('div', 'swiper-cube-shadow')), t.append(h)))) + for (let e = 0; e < i.length; e += 1) { + const t = i[e] + let s = e + m && (s = parseInt(t.getAttribute('data-swiper-slide-index'), 10)) + let r = 90 * s, + n = Math.floor(r / 360) + l && ((r = -r), (n = Math.floor(-r / 360))) + const d = Math.max(Math.min(t.progress, 1), -1) + let h = 0, + g = 0, + v = 0 + s % 4 == 0 + ? ((h = 4 * -n * o), (v = 0)) + : (s - 1) % 4 == 0 + ? ((h = 0), (v = 4 * -n * o)) + : (s - 2) % 4 == 0 + ? ((h = o + 4 * n * o), (v = o)) + : (s - 3) % 4 == 0 && ((h = -o), (v = 3 * o + 4 * o * n)), + l && (h = -h), + u || ((g = h), (h = 0)) + const w = `rotateX(${c(u ? 0 : -r)}deg) rotateY(${c( + u ? r : 0 + )}deg) translate3d(${h}px, ${g}px, ${v}px)` + d <= 1 && + d > -1 && + ((f = 90 * s + 90 * d), l && (f = 90 * -s - 90 * d)), + (t.style.transform = w), + p.slideShadows && a(t, d, u) + } + if ( + ((s.style.transformOrigin = `50% 50% -${o / 2}px`), + (s.style['-webkit-transform-origin'] = `50% 50% -${o / 2}px`), + p.shadow) + ) + if (u) + h.style.transform = `translate3d(0px, ${ + r / 2 + p.shadowOffset + }px, ${-r / 2}px) rotateX(89.99deg) rotateZ(0deg) scale(${ + p.shadowScale + })` + else { + const e = Math.abs(f) - 90 * Math.floor(Math.abs(f) / 90), + t = + 1.5 - + (Math.sin((2 * e * Math.PI) / 360) / 2 + + Math.cos((2 * e * Math.PI) / 360) / 2), + s = p.shadowScale, + a = p.shadowScale / t, + i = p.shadowOffset + h.style.transform = `scale3d(${s}, 1, ${a}) translate3d(0px, ${ + n / 2 + i + }px, ${-n / 2 / a}px) rotateX(-89.99deg)` + } + const g = + (d.isSafari || d.isWebView) && d.needPerspectiveFix ? -o / 2 : 0 + ;(s.style.transform = `translate3d(0px,0,${g}px) rotateX(${c( + e.isHorizontal() ? 0 : f + )}deg) rotateY(${c(e.isHorizontal() ? -f : 0)}deg)`), + s.style.setProperty('--swiper-cube-translate-z', `${g}px`) + }, + setTransition: (t) => { + const { el: s, slides: a } = e + if ( + (a.forEach((e) => { + ;(e.style.transitionDuration = `${t}ms`), + e + .querySelectorAll( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .forEach((e) => { + e.style.transitionDuration = `${t}ms` + }) + }), + e.params.cubeEffect.shadow && !e.isHorizontal()) + ) { + const e = s.querySelector('.swiper-cube-shadow') + e && (e.style.transitionDuration = `${t}ms`) + } + }, + recreateShadows: () => { + const t = e.isHorizontal() + e.slides.forEach((e) => { + const s = Math.max(Math.min(e.progress, 1), -1) + a(e, s, t) + }) + }, + getEffectParams: () => e.params.cubeEffect, + perspective: () => !0, + overwriteParams: () => ({ + slidesPerView: 1, + slidesPerGroup: 1, + watchSlidesProgress: !0, + resistanceRatio: 0, + spaceBetween: 0, + centeredSlides: !1, + virtualTranslate: !0, + }), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ flipEffect: { slideShadows: !0, limitRotation: !0 } }) + const a = (t, s) => { + let a = e.isHorizontal() + ? t.querySelector('.swiper-slide-shadow-left') + : t.querySelector('.swiper-slide-shadow-top'), + i = e.isHorizontal() + ? t.querySelector('.swiper-slide-shadow-right') + : t.querySelector('.swiper-slide-shadow-bottom') + a || (a = we('flip', t, e.isHorizontal() ? 'left' : 'top')), + i || (i = we('flip', t, e.isHorizontal() ? 'right' : 'bottom')), + a && (a.style.opacity = Math.max(-s, 0)), + i && (i.style.opacity = Math.max(s, 0)) + } + fe({ + effect: 'flip', + swiper: e, + on: s, + setTranslate: () => { + const { slides: t, rtlTranslate: s } = e, + i = e.params.flipEffect, + r = C(e) + for (let n = 0; n < t.length; n += 1) { + const l = t[n] + let o = l.progress + e.params.flipEffect.limitRotation && + (o = Math.max(Math.min(l.progress, 1), -1)) + const d = l.swiperSlideOffset + let c = -180 * o, + p = 0, + u = e.params.cssMode ? -d - e.translate : -d, + m = 0 + e.isHorizontal() + ? s && (c = -c) + : ((m = u), (u = 0), (p = -c), (c = 0)), + (l.style.zIndex = -Math.abs(Math.round(o)) + t.length), + i.slideShadows && a(l, o) + const h = `translate3d(${u}px, ${m}px, 0px) rotateX(${r( + p + )}deg) rotateY(${r(c)}deg)` + ge(0, l).style.transform = h + } + }, + setTransition: (t) => { + const s = e.slides.map((e) => f(e)) + s.forEach((e) => { + ;(e.style.transitionDuration = `${t}ms`), + e + .querySelectorAll( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .forEach((e) => { + e.style.transitionDuration = `${t}ms` + }) + }), + ve({ swiper: e, duration: t, transformElements: s }) + }, + recreateShadows: () => { + e.params.flipEffect, + e.slides.forEach((t) => { + let s = t.progress + e.params.flipEffect.limitRotation && + (s = Math.max(Math.min(t.progress, 1), -1)), + a(t, s) + }) + }, + getEffectParams: () => e.params.flipEffect, + perspective: () => !0, + overwriteParams: () => ({ + slidesPerView: 1, + slidesPerGroup: 1, + watchSlidesProgress: !0, + spaceBetween: 0, + virtualTranslate: !e.params.cssMode, + }), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + coverflowEffect: { + rotate: 50, + stretch: 0, + depth: 100, + scale: 1, + modifier: 1, + slideShadows: !0, + }, + }), + fe({ + effect: 'coverflow', + swiper: e, + on: s, + setTranslate: () => { + const { width: t, height: s, slides: a, slidesSizesGrid: i } = e, + r = e.params.coverflowEffect, + n = e.isHorizontal(), + l = e.translate, + o = n ? t / 2 - l : s / 2 - l, + d = n ? r.rotate : -r.rotate, + c = r.depth, + p = C(e) + for (let e = 0, t = a.length; e < t; e += 1) { + const t = a[e], + s = i[e], + l = (o - t.swiperSlideOffset - s / 2) / s, + u = + 'function' == typeof r.modifier + ? r.modifier(l) + : l * r.modifier + let m = n ? d * u : 0, + h = n ? 0 : d * u, + f = -c * Math.abs(u), + g = r.stretch + 'string' == typeof g && + -1 !== g.indexOf('%') && + (g = (parseFloat(r.stretch) / 100) * s) + let v = n ? 0 : g * u, + w = n ? g * u : 0, + b = 1 - (1 - r.scale) * Math.abs(u) + Math.abs(w) < 0.001 && (w = 0), + Math.abs(v) < 0.001 && (v = 0), + Math.abs(f) < 0.001 && (f = 0), + Math.abs(m) < 0.001 && (m = 0), + Math.abs(h) < 0.001 && (h = 0), + Math.abs(b) < 0.001 && (b = 0) + const y = `translate3d(${w}px,${v}px,${f}px) rotateX(${p( + h + )}deg) rotateY(${p(m)}deg) scale(${b})` + if ( + ((ge(0, t).style.transform = y), + (t.style.zIndex = 1 - Math.abs(Math.round(u))), + r.slideShadows) + ) { + let e = n + ? t.querySelector('.swiper-slide-shadow-left') + : t.querySelector('.swiper-slide-shadow-top'), + s = n + ? t.querySelector('.swiper-slide-shadow-right') + : t.querySelector('.swiper-slide-shadow-bottom') + e || (e = we('coverflow', t, n ? 'left' : 'top')), + s || (s = we('coverflow', t, n ? 'right' : 'bottom')), + e && (e.style.opacity = u > 0 ? u : 0), + s && (s.style.opacity = -u > 0 ? -u : 0) + } + } + }, + setTransition: (t) => { + e.slides + .map((e) => f(e)) + .forEach((e) => { + ;(e.style.transitionDuration = `${t}ms`), + e + .querySelectorAll( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .forEach((e) => { + e.style.transitionDuration = `${t}ms` + }) + }) + }, + perspective: () => !0, + overwriteParams: () => ({ watchSlidesProgress: !0 }), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + creativeEffect: { + limitProgress: 1, + shadowPerProgress: !1, + progressMultiplier: 1, + perspective: !0, + prev: { + translate: [0, 0, 0], + rotate: [0, 0, 0], + opacity: 1, + scale: 1, + }, + next: { + translate: [0, 0, 0], + rotate: [0, 0, 0], + opacity: 1, + scale: 1, + }, + }, + }) + const a = (e) => ('string' == typeof e ? e : `${e}px`) + fe({ + effect: 'creative', + swiper: e, + on: s, + setTranslate: () => { + const { slides: t, wrapperEl: s, slidesSizesGrid: i } = e, + r = e.params.creativeEffect, + { progressMultiplier: n } = r, + l = e.params.centeredSlides, + o = C(e) + if (l) { + const t = i[0] / 2 - e.params.slidesOffsetBefore || 0 + s.style.transform = `translateX(calc(50% - ${t}px))` + } + for (let s = 0; s < t.length; s += 1) { + const i = t[s], + d = i.progress, + c = Math.min( + Math.max(i.progress, -r.limitProgress), + r.limitProgress + ) + let p = c + l || + (p = Math.min( + Math.max(i.originalProgress, -r.limitProgress), + r.limitProgress + )) + const u = i.swiperSlideOffset, + m = [e.params.cssMode ? -u - e.translate : -u, 0, 0], + h = [0, 0, 0] + let f = !1 + e.isHorizontal() || ((m[1] = m[0]), (m[0] = 0)) + let g = { + translate: [0, 0, 0], + rotate: [0, 0, 0], + scale: 1, + opacity: 1, + } + c < 0 + ? ((g = r.next), (f = !0)) + : c > 0 && ((g = r.prev), (f = !0)), + m.forEach((e, t) => { + m[t] = `calc(${e}px + (${a(g.translate[t])} * ${Math.abs( + c * n + )}))` + }), + h.forEach((e, t) => { + let s = g.rotate[t] * Math.abs(c * n) + h[t] = s + }), + (i.style.zIndex = -Math.abs(Math.round(d)) + t.length) + const v = m.join(', '), + w = `rotateX(${o(h[0])}deg) rotateY(${o(h[1])}deg) rotateZ(${o( + h[2] + )}deg)`, + b = + p < 0 + ? `scale(${1 + (1 - g.scale) * p * n})` + : `scale(${1 - (1 - g.scale) * p * n})`, + y = + p < 0 + ? 1 + (1 - g.opacity) * p * n + : 1 - (1 - g.opacity) * p * n, + E = `translate3d(${v}) ${w} ${b}` + if ((f && g.shadow) || !f) { + let e = i.querySelector('.swiper-slide-shadow') + if ((!e && g.shadow && (e = we('creative', i)), e)) { + const t = r.shadowPerProgress ? c * (1 / r.limitProgress) : c + e.style.opacity = Math.min(Math.max(Math.abs(t), 0), 1) + } + } + const x = ge(0, i) + ;(x.style.transform = E), + (x.style.opacity = y), + g.origin && (x.style.transformOrigin = g.origin) + } + }, + setTransition: (t) => { + const s = e.slides.map((e) => f(e)) + s.forEach((e) => { + ;(e.style.transitionDuration = `${t}ms`), + e.querySelectorAll('.swiper-slide-shadow').forEach((e) => { + e.style.transitionDuration = `${t}ms` + }) + }), + ve({ swiper: e, duration: t, transformElements: s, allSlides: !0 }) + }, + perspective: () => e.params.creativeEffect.perspective, + overwriteParams: () => ({ + watchSlidesProgress: !0, + virtualTranslate: !e.params.cssMode, + }), + }) + }, + function ({ swiper: e, extendParams: t, on: s }) { + t({ + cardsEffect: { + slideShadows: !0, + rotate: !0, + perSlideRotate: 2, + perSlideOffset: 8, + }, + }), + fe({ + effect: 'cards', + swiper: e, + on: s, + setTranslate: () => { + const { slides: t, activeIndex: s, rtlTranslate: a } = e, + i = e.params.cardsEffect, + { startTranslate: r, isTouched: n } = e.touchEventsData, + l = a ? -e.translate : e.translate + for (let o = 0; o < t.length; o += 1) { + const d = t[o], + c = d.progress, + p = Math.min(Math.max(c, -4), 4) + let u = d.swiperSlideOffset + e.params.centeredSlides && + !e.params.cssMode && + (e.wrapperEl.style.transform = `translateX(${e.minTranslate()}px)`), + e.params.centeredSlides && + e.params.cssMode && + (u -= t[0].swiperSlideOffset) + let m = e.params.cssMode ? -u - e.translate : -u, + h = 0 + const f = -100 * Math.abs(p) + let g = 1, + v = -i.perSlideRotate * p, + w = i.perSlideOffset - 0.75 * Math.abs(p) + const b = + e.virtual && e.params.virtual.enabled + ? e.virtual.from + o + : o, + y = + (b === s || b === s - 1) && + p > 0 && + p < 1 && + (n || e.params.cssMode) && + l < r, + E = + (b === s || b === s + 1) && + p < 0 && + p > -1 && + (n || e.params.cssMode) && + l > r + if (y || E) { + const t = (1 - Math.abs((Math.abs(p) - 0.5) / 0.5)) ** 0.5 + ;(v += -28 * p * t), + (g += -0.5 * t), + (w += 96 * t), + (h = + (i.rotate || e.isHorizontal() ? -25 : 0) * t * Math.abs(p) + + '%') + } + if ( + ((m = + p < 0 + ? `calc(${m}px ${a ? '-' : '+'} (${w * Math.abs(p)}%))` + : p > 0 + ? `calc(${m}px ${a ? '-' : '+'} (-${w * Math.abs(p)}%))` + : `${m}px`), + !e.isHorizontal()) + ) { + const e = h + ;(h = m), (m = e) + } + const x = p < 0 ? '' + (1 + (1 - g) * p) : '' + (1 - (1 - g) * p), + S = `\n translate3d(${m}, ${h}, ${f}px)\n rotateZ(${ + i.rotate ? (a ? -v : v) : 0 + }deg)\n scale(${x})\n ` + if (i.slideShadows) { + let e = d.querySelector('.swiper-slide-shadow') + e || (e = we('cards', d)), + e && + (e.style.opacity = Math.min( + Math.max((Math.abs(p) - 0.5) / 0.5, 0), + 1 + )) + } + d.style.zIndex = -Math.abs(Math.round(c)) + t.length + ge(0, d).style.transform = S + } + }, + setTransition: (t) => { + const s = e.slides.map((e) => f(e)) + s.forEach((e) => { + ;(e.style.transitionDuration = `${t}ms`), + e.querySelectorAll('.swiper-slide-shadow').forEach((e) => { + e.style.transitionDuration = `${t}ms` + }) + }), + ve({ swiper: e, duration: t, transformElements: s }) + }, + perspective: () => !0, + overwriteParams: () => ({ + _loopSwapReset: !1, + watchSlidesProgress: !0, + loopAdditionalSlides: e.params.cardsEffect.rotate ? 3 : 2, + centeredSlides: !0, + virtualTranslate: !e.params.cssMode, + }), + }) + }, + ] + return ne.use(be), ne +})() +//# sourceMappingURL=swiper-bundle.min.js.map diff --git a/modules/pp_carousel/views/templates/hook/pp_carousel.tpl b/modules/pp_carousel/views/templates/hook/pp_carousel.tpl new file mode 100644 index 0000000..df44277 --- /dev/null +++ b/modules/pp_carousel/views/templates/hook/pp_carousel.tpl @@ -0,0 +1,67 @@ + diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..132123e --- /dev/null +++ b/plan.md @@ -0,0 +1,196 @@ +# Plan: Moduł Project-Pro Karuzela Produktów + +## Analiza screenshota +Karuzela wyświetla produkty jako karty z: dużym zdjęciem, etykietą kategorii (label w lewym górnym rogu), nazwą produktu, ceną z sufiksem `/m2`. Układ 3 kolumny na desktop, slider Swiper. + +## Kluczowe decyzje architektoniczne + +**Dlaczego tabela SQL zamiast Configuration?** +Moduł wymaga *dowolnej liczby* karuzeli — Configuration przechowuje pary klucz-wartość, nie nadaje się do list rekordów o zmiennej długości. Dedykowana tabela `pp_carousel` to jedyne poprawne rozwiązanie. + +**Dlaczego dynamiczne hooki?** +Użytkownik chce przypisywać karuzele do hooków, w tym niestandardowych. Moduł rejestruje się na `displayHeader` + catch-all `actionDispatcher`, a wyświetlanie realizuje poprzez `Hook::exec()` z warunkiem `WHERE hook_name = ?` w bazie. Niestandardowe hooki dodawane są przez `Hook::getIdByName()` / ręczne INSERT. + +## Struktura plików modułu + +``` +modules/pp_carousel/ +├── pp_carousel.php # Główna klasa modułu +├── config.xml +├── logo.png +├── sql/ +│ ├── install.sql # CREATE TABLE +│ └── uninstall.sql # DROP TABLE +├── views/ +│ ├── css/ +│ │ └── pp_carousel.css # Style karuzeli (front) +│ ├── js/ +│ │ └── pp_carousel.js # Inicjalizacja Swiper (front) +│ ├── lib/ +│ │ └── swiper/ # swiper-bundle.min.js/css (kopiujemy z project_pro_news) +│ └── templates/ +│ ├── hook/ +│ │ └── pp_carousel.tpl # Szablon frontu karuzeli +│ └── admin/ +│ ├── list.tpl # Lista karuzeli w BO +│ └── form.tpl # Formularz dodawania/edycji karuzeli +``` + +## Tabela bazy danych: `ps_pp_carousel` + +| Kolumna | Typ | Opis | +|---------|-----|------| +| `id_carousel` | INT AUTO_INCREMENT PK | ID karuzeli | +| `hook_name` | VARCHAR(128) | Nazwa hooka (np. `displayHome`, custom) | +| `source_type` | ENUM('new','bestseller','category','manual') | Źródło produktów | +| `id_category` | INT DEFAULT 0 | ID kategorii (gdy source_type=category) | +| `product_ids` | TEXT | Ręczne ID produktów rozdzielone przecinkami | +| `limit_products` | INT DEFAULT 12 | Limit produktów | +| `title` | VARCHAR(255) | Tytuł karuzeli | +| `subtitle` | VARCHAR(255) | Podtytuł | +| `button_label` | VARCHAR(255) | Tekst przycisku | +| `button_url` | VARCHAR(512) | URL przycisku | +| `price_suffix` | VARCHAR(64) | Sufiks ceny (np. `/m²`) | +| `position` | INT DEFAULT 0 | Kolejność sortowania | +| `active` | TINYINT(1) DEFAULT 1 | Aktywna/nieaktywna | +| `id_shop` | INT DEFAULT 1 | Multishop | +| `date_add` | DATETIME | Data utworzenia | +| `date_upd` | DATETIME | Data modyfikacji | + +> Wielojęzyczność: pola `title`, `subtitle`, `button_label`, `price_suffix` — dodatkowa tabela `ps_pp_carousel_lang` z kolumnami `id_carousel`, `id_lang`, `title`, `subtitle`, `button_label`, `price_suffix`. + +## Implementacja krok po kroku + +### Krok 1: Plik główny `pp_carousel.php` — szkielet + +- Klasa `Pp_Carousel extends Module` +- Konstruktor: name=`pp_carousel`, author=`Project-Pro`, version=`1.0.0`, displayName=`Project-Pro Karuzela Produktów` +- `install()`: wykonuje SQL, rejestruje hooki: `displayHeader`, `displayHome`, `displayBackOfficeHeader` +- `uninstall()`: DROP tabeli, usunięcie konfiguracji +- Metoda `getContent()` → panel admin z listą karuzeli + formularz CRUD + +### Krok 2: SQL install/uninstall + +**install.sql:** +```sql +CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel` ( + `id_carousel` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `hook_name` VARCHAR(128) NOT NULL DEFAULT 'displayHome', + `source_type` VARCHAR(20) NOT NULL DEFAULT 'new', + `id_category` INT(11) UNSIGNED NOT NULL DEFAULT 0, + `product_ids` TEXT, + `limit_products` INT(11) UNSIGNED NOT NULL DEFAULT 12, + `button_url` VARCHAR(512) DEFAULT '', + `position` INT(11) UNSIGNED NOT NULL DEFAULT 0, + `active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + `id_shop` INT(11) UNSIGNED NOT NULL DEFAULT 1, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + PRIMARY KEY (`id_carousel`), + KEY `hook_name` (`hook_name`), + KEY `active` (`active`) +) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel_lang` ( + `id_carousel` INT(11) UNSIGNED NOT NULL, + `id_lang` INT(11) UNSIGNED NOT NULL, + `title` VARCHAR(255) DEFAULT '', + `subtitle` VARCHAR(255) DEFAULT '', + `button_label` VARCHAR(255) DEFAULT '', + `price_suffix` VARCHAR(64) DEFAULT '', + PRIMARY KEY (`id_carousel`, `id_lang`) +) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4; +``` + +### Krok 3: Panel administracyjny (`getContent()`) + +Widok listy (domyślny): +- Tabela HelperList: ID | Tytuł | Hook | Źródło | Limit | Aktywna | Akcje (edytuj/usuń) +- Przycisk "Dodaj karuzelę" + +Widok formularza (dodaj/edytuj): +- **Hook** — select z listą dostępnych hooków + pole tekstowe "Niestandardowy hook" (jeśli wpisany, tworzony jest nowy hook w `ps_hook`) +- **Źródło produktów** — select: Nowości / Bestsellery / Kategoria / Ręczne ID +- **Kategoria** — select z drzewem kategorii (widoczny gdy source_type=category) +- **Ręczne ID produktów** — textarea, oddzielone przecinkami (widoczny gdy source_type=manual) +- **Limit produktów** — input number +- **Tytuł** — input text (wielojęzyczny) +- **Podtytuł** — input text (wielojęzyczny) +- **Tekst przycisku** — input text (wielojęzyczny) +- **URL przycisku** — input text +- **Sufiks ceny** — input text (wielojęzyczny), np. `/m²` +- **Aktywna** — switch tak/nie + +### Krok 4: Pobieranie produktów — metoda `getProductsByCarousel($carousel)` + +Switch po `source_type`: +- `new` → `Product::getNewProducts($idLang, 0, $limit)` (wzorzec z `ps_newproducts`) +- `bestseller` → `ProductSale::getBestSales($idLang, 0, $limit)` (wzorzec z `ps_bestsellers`) +- `category` → `Category::getProducts($idCategory, $idLang, 1, $limit)` (wzorzec z `project_pro_news`) +- `manual` → `SELECT` po `id_product IN (...)`, potem prezentacja + +Wszystkie wyniki przechodzą przez `ProductAssembler` + `ProductListingPresenter` (identycznie jak w istniejących modułach). + +### Krok 5: Renderowanie hooków + +Mechanizm: moduł rejestruje się na najczęstsze hooki (`displayHome`, `displayFooterBefore`, `displayLeftColumn` itp.). W każdym hooku: + +```php +public function hookDisplayHome($params) { + return $this->renderCarouselsForHook('displayHome'); +} +``` + +Metoda `renderCarouselsForHook($hookName)`: +1. SELECT z `ps_pp_carousel` WHERE `hook_name` = $hookName AND `active` = 1 ORDER BY `position` +2. Dla każdej karuzeli: pobierz produkty, przypisz do Smarty, fetch szablonu +3. Zwróć połączony HTML + +Niestandardowe hooki: przy zapisie karuzeli z nowym hookiem → `$this->registerHook($hookName)` + ewentualnie INSERT do `ps_hook`. + +### Krok 6: Szablon frontu `pp_carousel.tpl` + +Wzorowany na `project_pro_news.tpl` — ten sam layout Swiper: +- Section z unikalnym `id="pp-carousel-{$carousel.id}"` +- Nagłówek: tytuł + podtytuł +- Swiper container z kartami produktów +- Karta: zdjęcie (home_default), etykieta kategorii, nazwa, cena + sufiks +- Nawigacja prev/next +- Przycisk "Zobacz więcej" + +### Krok 7: CSS + JS + +**CSS** — bazowany na `project_pro_news.css`, dostosowany do nowego namespace `.pp-carousel`. + +**JS** — inicjalizacja wielu instancji Swipera: +```js +document.querySelectorAll('.pp-carousel__slider').forEach(function(el) { + new Swiper(el, { /* config */ }); +}); +``` + +### Krok 8: Rejestracja assetów + +`hookDisplayHeader()` — rejestracja CSS/JS (swiper + pp_carousel) identycznie jak w `project_pro_news`. + +## Hooki do rejestracji (install) + +Obowiązkowe: +- `displayHeader` (assety CSS/JS) +- `displayHome` (główna pozycja) +- `displayBackOfficeHeader` (admin CSS/JS jeśli potrzebne) + +Dodatkowe (rejestrowane dynamicznie przy tworzeniu karuzeli z niestandardowym hookiem): +- `displayFooterBefore`, `displayTopColumn`, `displayLeftColumn`, `displayFooter` itd. +- Dowolne niestandardowe hooki + +## Kolejność wdrożenia + +1. **Pliki SQL** — tabele `pp_carousel` + `pp_carousel_lang` +2. **pp_carousel.php** — konstruktor, install/uninstall, hookDisplayHeader +3. **Panel admin** — getContent() z listą + formularzem CRUD +4. **Logika produktów** — getProductsByCarousel() z 4 źródłami +5. **Rendering hooków** — renderCarouselsForHook() + rejestracja dynamiczna +6. **Szablon .tpl** — karta produktu zgodna ze screenshotem +7. **CSS/JS** — style + Swiper init (wieloinstancyjny) +8. **Testowanie** — dodanie karuzeli w BO, weryfikacja na froncie diff --git a/var/logs/dev.log b/var/logs/dev.log new file mode 100644 index 0000000..e69de29 diff --git a/var/logs/prod.log b/var/logs/prod.log new file mode 100644 index 0000000..e69de29