This commit is contained in:
2026-03-21 00:04:12 +01:00
parent ec51757e2e
commit 3cef4c8247
11 changed files with 615 additions and 26 deletions

View File

@@ -67,3 +67,9 @@ When creating or modifying overrides, PrestaShop also needs to rebuild the class
- Module DB tables use `_DB_PREFIX_` constant (typically `ps_`).
- PrestaShop hooks are the integration point — prefer hooks over direct core edits.
- The `admin658c34/` directory is the custom admin panel path (security through obscurity).
## Custom Assistant Command
- If the user writes `zapisz-changelog`, create or update monthly changelog file `changelog/YYYY-MM.md` (based on current date).
- Add an entry for the current day with a concise summary of code changes made in the current session.
- Include touched file paths and relevant line references where possible.

51
changelog/2026-03.md Normal file
View File

@@ -0,0 +1,51 @@
# Changelog 2026-03
## 2026-03-20
### Zmiany funkcjonalne
- Podmieniono logikę modułu `ps_categoryproducts` na wyświetlanie produktów z tej samej cechy `Seria` (`id_feature = 8`) zamiast produktów z tej samej kategorii, gdy produkt ma ustawioną cechę serii.
- Dla produktów z serią włączono tryb restrykcyjny: brak fallbacku do kategorii, jeśli nie ma dopasowań po serii.
- Dodano i dopracowano karuzelę dla sekcji produktów powiązanych (nawigacja, responsywność, scroll poziomy).
- Zmieniono nagłówek sekcji na: `Produkty z tej samej serii`.
### Najważniejsze modyfikacje w kodzie
1. `modules/ps_categoryproducts/ps_categoryproducts.php`
- Stała cechy serii:
- `SERIES_FEATURE_ID = 8` ([modules/ps_categoryproducts/ps_categoryproducts.php:44](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:44))
- Główna logika danych widgetu:
- pobieranie wszystkich wartości serii i filtrowanie po nich ([modules/ps_categoryproducts/ps_categoryproducts.php:213](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:213))
- Rejestracja assetów karuzeli (z fallbackiem):
- `registerCarouselAssets()` ([modules/ps_categoryproducts/ps_categoryproducts.php:287](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:287))
- Pobieranie wartości serii produktu:
- `getSeriesFeatureValueIds()` ([modules/ps_categoryproducts/ps_categoryproducts.php:381](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:381))
- Pobieranie produktów z tej samej serii:
- `getProductsByFeatureValue()` ([modules/ps_categoryproducts/ps_categoryproducts.php:398](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:398))
- Wersjonowanie cache bloku:
- `v4_strict_series_masterdb` ([modules/ps_categoryproducts/ps_categoryproducts.php:490](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\ps_categoryproducts.php:490))
2. `themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl`
- Nagłówek sekcji dla serii:
- `Produkty z tej samej serii` ([themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:42](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:42))
- Markup karuzeli i klasy JS:
- wrapper i nawigacja ([themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:39](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:39), [themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:54](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:54), [themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:66](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:66))
- Inline CSS/JS opakowany w `{literal}`:
- style ([themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:25](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:25))
- skrypt ([themes/InterBlue/modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:73](c:\visual studio code\projekty\interblue.pl\themes\InterBlue\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:73))
3. `modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl`
- Analogiczne zmiany jak w override motywu:
- `{literal}` + markup karuzeli + JS ([modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:25](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:25), [modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:39](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:39), [modules/ps_categoryproducts/views/templates/hook/ps_categoryproducts.tpl:77](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\templates\hook\ps_categoryproducts.tpl:77))
4. Dodatkowe pliki assetów karuzeli
- CSS:
- [modules/ps_categoryproducts/views/css/carousel.css](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\css\carousel.css)
- JS:
- [modules/ps_categoryproducts/views/js/carousel.js](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\js\carousel.js)
- Zabezpieczenie katalogów:
- [modules/ps_categoryproducts/views/css/index.php](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\css\index.php)
- [modules/ps_categoryproducts/views/js/index.php](c:\visual studio code\projekty\interblue.pl\modules\ps_categoryproducts\views\js\index.php)
### Uwagi operacyjne
- Po wdrożeniu zmian wymagane było czyszczenie cache PrestaShop, aby wymusić przebudowę bloku `ps_categoryproducts`.

View File

@@ -41,6 +41,8 @@ use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
class Ps_Categoryproducts extends Module implements WidgetInterface
{
const SERIES_FEATURE_ID = 8;
protected $html;
protected $templateFile;
@@ -213,15 +215,33 @@ class Ps_Categoryproducts extends Module implements WidgetInterface
$params = $this->getInformationFromConfiguration($configuration);
if ($params) {
$products = array();
$seriesFeatureValueIds = $this->getSeriesFeatureValueIds((int) $params['id_product']);
if (!empty($seriesFeatureValueIds)) {
$products = $this->getProductsByFeatureValue(
(int) $params['id_product'],
(int) self::SERIES_FEATURE_ID,
$seriesFeatureValueIds
);
if (empty($products)) {
return false;
}
$products = $this->getCategoryProducts($params['id_product'], $params['id_category']);
if (!empty($products)) {
return array(
'products' => $products,
'products_source' => 'series',
);
}
$products = $this->getCategoryProducts($params['id_product'], $params['id_category']);
if (empty($products)) {
return false;
}
return array(
'products' => $products,
'products_source' => 'category',
);
}
return false;
@@ -233,6 +253,7 @@ class Ps_Categoryproducts extends Module implements WidgetInterface
if ($params) {
if ((int)Configuration::get('CATEGORYPRODUCTS_DISPLAY_PRODUCTS') > 0) {
$this->registerCarouselAssets();
// Need variables only if this template isn't cached
if (!$this->isCached($this->templateFile, $params['cache_id'])) {
@@ -263,6 +284,39 @@ class Ps_Categoryproducts extends Module implements WidgetInterface
return false;
}
private function registerCarouselAssets()
{
if (empty($this->context->controller)) {
return;
}
if (method_exists($this->context->controller, 'registerStylesheet')) {
$this->context->controller->registerStylesheet(
'module-ps-categoryproducts-carousel',
'modules/' . $this->name . '/views/css/carousel.css',
array(
'media' => 'all',
'priority' => 150,
)
);
} else {
$this->context->controller->addCSS($this->_path . 'views/css/carousel.css');
}
if (method_exists($this->context->controller, 'registerJavascript')) {
$this->context->controller->registerJavascript(
'module-ps-categoryproducts-carousel',
'modules/' . $this->name . '/views/js/carousel.js',
array(
'position' => 'bottom',
'priority' => 150,
)
);
} else {
$this->context->controller->addJS($this->_path . 'views/js/carousel.js');
}
}
private function getCategoryProducts($idProduct, $idCategory)
{
$category = new Category($idCategory);
@@ -324,6 +378,98 @@ class Ps_Categoryproducts extends Module implements WidgetInterface
return $productsForTemplate;
}
private function getSeriesFeatureValueIds($idProduct)
{
$sql = 'SELECT DISTINCT fp.`id_feature_value`
FROM `' . _DB_PREFIX_ . 'feature_product` fp
WHERE fp.`id_product` = ' . (int) $idProduct . '
AND fp.`id_feature` = ' . (int) self::SERIES_FEATURE_ID . '
AND fp.`id_feature_value` > 0
ORDER BY fp.`id_feature_value` ASC';
$rows = Db::getInstance()->executeS($sql);
if (empty($rows)) {
return array();
}
return array_map('intval', array_column($rows, 'id_feature_value'));
}
private function getProductsByFeatureValue($idProduct, $idFeature, array $idFeatureValues)
{
$nProducts = (int) Configuration::get('CATEGORYPRODUCTS_DISPLAY_PRODUCTS');
if ($nProducts <= 0) {
return array();
}
if (empty($idFeatureValues)) {
return array();
}
$idShop = (int) $this->context->shop->id;
$idLang = (int) $this->context->language->id;
if ($idShop <= 0) {
$idShop = (int) Configuration::get('PS_SHOP_DEFAULT');
}
$idFeatureValues = array_filter(array_map('intval', $idFeatureValues));
if (empty($idFeatureValues)) {
return array();
}
$sql = 'SELECT DISTINCT fp.`id_product`
FROM `' . _DB_PREFIX_ . 'feature_product` fp
INNER JOIN `' . _DB_PREFIX_ . 'product_shop` product_shop
ON product_shop.`id_product` = fp.`id_product`
AND product_shop.`id_shop` = ' . (int) $idShop . '
INNER JOIN `' . _DB_PREFIX_ . 'product_lang` pl
ON pl.`id_product` = fp.`id_product`
AND pl.`id_shop` = ' . (int) $idShop . '
AND pl.`id_lang` = ' . (int) $idLang . '
WHERE fp.`id_feature` = ' . (int) $idFeature . '
AND fp.`id_feature_value` IN (' . implode(',', $idFeatureValues) . ')
AND fp.`id_product` != ' . (int) $idProduct . '
AND product_shop.`active` = 1
AND product_shop.`visibility` IN ("both", "catalog")
ORDER BY RAND()
LIMIT ' . (int) $nProducts;
$rows = Db::getInstance()->executeS($sql);
if (empty($rows)) {
return array();
}
$ids = array_map('intval', array_column($rows, 'id_product'));
if (empty($ids)) {
return array();
}
$showPrice = (bool) Configuration::get('CATEGORYPRODUCTS_DISPLAY_PRICE');
$assembler = new ProductAssembler($this->context);
$presenterFactory = new ProductPresenterFactory($this->context);
$presentationSettings = $presenterFactory->getPresentationSettings();
$presenter = new ProductListingPresenter(
new ImageRetriever(
$this->context->link
),
$this->context->link,
new PriceFormatter(),
new ProductColorsRetriever(),
$this->context->getTranslator()
);
$presentationSettings->showPrices = $showPrice;
$productsForTemplate = array();
foreach ($ids as $id) {
$productsForTemplate[] = $presenter->present(
$presentationSettings,
$assembler->assembleProduct(array('id_product' => (int) $id)),
$this->context->language
);
}
return $productsForTemplate;
}
private function getInformationFromConfiguration($configuration)
{
if (empty($configuration['product'])) {
@@ -341,7 +487,7 @@ class Ps_Categoryproducts extends Module implements WidgetInterface
if (!empty($id_product) && !empty($id_category)) {
$cache_id = 'ps_categoryproducts|'.$id_product.'|'.$id_category;
$cache_id = 'ps_categoryproducts|v4_strict_series_masterdb|'.$id_product.'|'.$id_category;
return array(
'id_product' => $id_product,

View File

@@ -0,0 +1,93 @@
.category-products .category-products-carousel-wrapper {
position: relative;
}
.category-products .products.category-products-carousel {
display: flex !important;
flex-wrap: nowrap !important;
gap: 16px;
overflow-x: auto !important;
overflow-y: hidden !important;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
padding: 0;
margin: 0;
-ms-overflow-style: none;
scrollbar-width: none;
}
.category-products .products.category-products-carousel::-webkit-scrollbar {
display: none;
}
.category-products .products.category-products-carousel > article.product-miniature {
flex: 0 0 calc(25% - 12px) !important;
max-width: calc(25% - 12px) !important;
scroll-snap-align: start;
}
.category-products .category-products-carousel__nav {
position: absolute;
top: 42%;
transform: translateY(-50%);
z-index: 20;
width: 38px;
height: 38px;
border: 0;
border-radius: 50%;
background: #ffffff;
color: #112c50;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: opacity 0.2s ease, background-color 0.2s ease;
}
.category-products .category-products-carousel__nav:hover {
background: #f2f7ff;
}
.category-products .category-products-carousel__nav[disabled] {
opacity: 0.35;
cursor: default;
}
.category-products .category-products-carousel__nav--prev {
left: -12px;
}
.category-products .category-products-carousel__nav--next {
right: -12px;
}
.category-products .category-products-carousel__nav span {
font-size: 28px;
line-height: 1;
}
@media (max-width: 1199.98px) {
.category-products .products.category-products-carousel > article.product-miniature {
flex: 0 0 calc(33.3333% - 11px) !important;
max-width: calc(33.3333% - 11px) !important;
}
}
@media (max-width: 767.98px) {
.category-products .products.category-products-carousel > article.product-miniature {
flex: 0 0 calc(50% - 8px) !important;
max-width: calc(50% - 8px) !important;
}
.category-products .category-products-carousel__nav {
display: none;
}
}
@media (max-width: 479.98px) {
.category-products .products.category-products-carousel > article.product-miniature {
flex: 0 0 100% !important;
max-width: 100% !important;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* 2007-2016 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2016 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
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;

View File

@@ -0,0 +1,87 @@
(function () {
'use strict';
function getStep(track) {
var firstItem = track.querySelector('article.product-miniature');
if (!firstItem) {
return Math.max(240, Math.floor(track.clientWidth * 0.8));
}
var style = window.getComputedStyle(track);
var gap = parseFloat(style.columnGap || style.gap || '0') || 0;
return Math.ceil(firstItem.getBoundingClientRect().width + gap);
}
function updateNavState(track, prevBtn, nextBtn) {
var maxScrollLeft = Math.max(0, track.scrollWidth - track.clientWidth);
var canScroll = maxScrollLeft > 8;
if (!canScroll) {
prevBtn.disabled = true;
nextBtn.disabled = true;
return;
}
prevBtn.disabled = track.scrollLeft <= 2;
nextBtn.disabled = track.scrollLeft >= maxScrollLeft - 2;
}
function initCarousel(root) {
var track = root.querySelector('.js-category-products-track');
var prevBtn = root.querySelector('.js-category-products-prev');
var nextBtn = root.querySelector('.js-category-products-next');
if (!track || !prevBtn || !nextBtn) {
return;
}
prevBtn.addEventListener('click', function () {
track.scrollBy({
left: -getStep(track),
behavior: 'smooth'
});
});
nextBtn.addEventListener('click', function () {
track.scrollBy({
left: getStep(track),
behavior: 'smooth'
});
});
var ticking = false;
track.addEventListener('scroll', function () {
if (ticking) {
return;
}
ticking = true;
window.requestAnimationFrame(function () {
updateNavState(track, prevBtn, nextBtn);
ticking = false;
});
});
window.addEventListener('resize', function () {
updateNavState(track, prevBtn, nextBtn);
});
updateNavState(track, prevBtn, nextBtn);
}
function onReady() {
var carousels = document.querySelectorAll('.js-category-products-carousel');
if (!carousels.length) {
return;
}
carousels.forEach(initCarousel);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onReady);
} else {
onReady();
}
})();

View File

@@ -0,0 +1,33 @@
<?php
/**
* 2007-2016 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2016 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
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;

View File

@@ -1,4 +1,4 @@
{*
{*
* 2007-2016 PrestaShop
*
* NOTICE OF LICENSE
@@ -22,17 +22,58 @@
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*}
<section>
{literal}<style>
.category-products .products.category-products-carousel{display:flex!important;flex-wrap:nowrap!important;gap:16px;overflow-x:auto!important;overflow-y:hidden!important;scroll-behavior:smooth;scroll-snap-type:x mandatory;-ms-overflow-style:none;scrollbar-width:none}
.category-products .products.category-products-carousel::-webkit-scrollbar{display:none}
.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(25% - 12px)!important;max-width:calc(25% - 12px)!important;scroll-snap-align:start}
.category-products .category-products-carousel-wrapper{position:relative}
.category-products .category-products-carousel__nav{position:absolute;top:42%;transform:translateY(-50%);z-index:20;width:38px;height:38px;border:0;border-radius:50%;background:#fff;color:#112c50;box-shadow:0 2px 10px rgba(0,0,0,.18);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
.category-products .category-products-carousel__nav[disabled]{opacity:.35;cursor:default}
.category-products .category-products-carousel__nav--prev{left:-12px}
.category-products .category-products-carousel__nav--next{right:-12px}
.category-products .category-products-carousel__nav span{font-size:28px;line-height:1}
@media (max-width:1199.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(33.3333% - 11px)!important;max-width:calc(33.3333% - 11px)!important}}
@media (max-width:767.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(50% - 8px)!important;max-width:calc(50% - 8px)!important}.category-products .category-products-carousel__nav{display:none}}
@media (max-width:479.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 100%!important;max-width:100%!important}}
</style>{/literal}
<section class="category-products js-category-products-carousel">
<h2>
{if $products|@count == 1}
{l s='%s other product in the same category:' sprintf=[$products|@count] d='Modules.Categoryproducts.Shop'}
{if isset($products_source) && $products_source == 'series'}
{if $products|@count == 1}
{l s='Similar product from the same series:' d='Modules.Categoryproducts.Shop'}
{else}
{l s='Similar products from the same series:' d='Modules.Categoryproducts.Shop'}
{/if}
{else}
{l s='%s other products in the same category:' sprintf=[$products|@count] d='Modules.Categoryproducts.Shop'}
{if $products|@count == 1}
{l s='%s other product in the same category:' sprintf=[$products|@count] d='Modules.Categoryproducts.Shop'}
{else}
{l s='%s other products in the same category:' sprintf=[$products|@count] d='Modules.Categoryproducts.Shop'}
{/if}
{/if}
</h2>
<div>
{foreach from=$products item="product"}
{include file="catalog/_partials/miniatures/product.tpl" product=$product}
{/foreach}
<div class="category-products-carousel-wrapper">
<button
type="button"
class="category-products-carousel__nav category-products-carousel__nav--prev js-category-products-prev"
aria-label="{l s='Previous products' d='Modules.Categoryproducts.Shop'}"
>
<span aria-hidden="true">&#8249;</span>
</button>
<div class="products category-products-carousel js-category-products-track">
{foreach from=$products item="product"}
{include file="catalog/_partials/miniatures/product.tpl" product=$product}
{/foreach}
</div>
<button
type="button"
class="category-products-carousel__nav category-products-carousel__nav--next js-category-products-next"
aria-label="{l s='Next products' d='Modules.Categoryproducts.Shop'}"
>
<span aria-hidden="true">&#8250;</span>
</button>
</div>
</section>
{literal}<script>
(function(){if(window.__ibSeriesCarouselInit){return;}window.__ibSeriesCarouselInit=true;function s(t){var i=t.querySelector('article.product-miniature');if(!i){return Math.max(240,Math.floor(t.clientWidth*.8));}var c=window.getComputedStyle(t),g=parseFloat(c.columnGap||c.gap||'0')||0;return Math.ceil(i.getBoundingClientRect().width+g);}function u(t,p,n){var m=Math.max(0,t.scrollWidth-t.clientWidth),c=m>8;if(!c){p.disabled=true;n.disabled=true;return;}p.disabled=t.scrollLeft<=2;n.disabled=t.scrollLeft>=m-2;}function init(r){var t=r.querySelector('.js-category-products-track'),p=r.querySelector('.js-category-products-prev'),n=r.querySelector('.js-category-products-next');if(!t||!p||!n){return;}p.addEventListener('click',function(){t.scrollBy({left:-s(t),behavior:'smooth'});});n.addEventListener('click',function(){t.scrollBy({left:s(t),behavior:'smooth'});});var k=false;t.addEventListener('scroll',function(){if(k){return;}k=true;window.requestAnimationFrame(function(){u(t,p,n);k=false;});});window.addEventListener('resize',function(){u(t,p,n);});u(t,p,n);}var all=document.querySelectorAll('.js-category-products-carousel');all.forEach(init);}());
</script>{/literal}

View File

@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320" role="img" aria-labelledby="title desc">
<title id="title">SIMON 10</title>
<desc id="desc">Kafelek serii SIMON 10 z linkiem do oferty.</desc>
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#f7f3eb"/>
<stop offset="1" stop-color="#f3ebe1"/>
</linearGradient>
<linearGradient id="circleBg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#b8b8b8"/>
<stop offset="1" stop-color="#7c7c7c"/>
</linearGradient>
</defs>
<rect width="320" height="320" fill="url(#bg)"/>
<circle cx="160" cy="118" r="95" fill="url(#circleBg)"/>
<rect x="90" y="95" width="74" height="74" rx="14" fill="#dfd7cc"/>
<rect x="98" y="103" width="58" height="58" rx="10" fill="#f2ede5"/>
<rect x="162" y="95" width="74" height="74" rx="14" fill="#c9c9c9"/>
<rect x="170" y="103" width="58" height="58" rx="10" fill="#f0f0f0"/>
<g fill="#f0c7bf">
<path d="M24 247h20v6H24z"/>
<path d="M276 247h20v6h-20z"/>
<path d="M32 235h6v20h-6z"/>
<path d="M282 235h6v20h-6z"/>
<path d="M42 241l8-8 4 4-8 8z"/>
<path d="M278 241l-8-8-4 4 8 8z"/>
</g>
<text x="160" y="235" text-anchor="middle" fill="#1f4978" font-size="36" font-family="Arial, sans-serif" font-weight="700">SIMON 10</text>
<text x="160" y="266" text-anchor="middle" fill="#1f4978" font-size="20" font-family="Arial, sans-serif" font-weight="700">SKONTAKTUJ SIMON</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -22,22 +22,54 @@
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
* International Registered Trademark & Property of PrestaShop SA
*}
<section class="category-products clearfix mt-3">
{literal}<style>
.category-products .products.category-products-carousel{display:flex!important;flex-wrap:nowrap!important;gap:16px;overflow-x:auto!important;overflow-y:hidden!important;scroll-behavior:smooth;scroll-snap-type:x mandatory;-ms-overflow-style:none;scrollbar-width:none}
.category-products .products.category-products-carousel::-webkit-scrollbar{display:none}
.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(25% - 12px)!important;max-width:calc(25% - 12px)!important;scroll-snap-align:start}
.category-products .category-products-carousel-wrapper{position:relative}
.category-products .category-products-carousel__nav{position:absolute;top:42%;transform:translateY(-50%);z-index:20;width:38px;height:38px;border:0;border-radius:50%;background:#fff;color:#112c50;box-shadow:0 2px 10px rgba(0,0,0,.18);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
.category-products .category-products-carousel__nav[disabled]{opacity:.35;cursor:default}
.category-products .category-products-carousel__nav--prev{left:-12px}
.category-products .category-products-carousel__nav--next{right:-12px}
.category-products .category-products-carousel__nav span{font-size:28px;line-height:1}
@media (max-width:1199.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(33.3333% - 11px)!important;max-width:calc(33.3333% - 11px)!important}}
@media (max-width:767.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 calc(50% - 8px)!important;max-width:calc(50% - 8px)!important}.category-products .category-products-carousel__nav{display:none}}
@media (max-width:479.98px){.category-products .products.category-products-carousel>article.product-miniature{flex:0 0 100%!important;max-width:100%!important}}
</style>{/literal}
<section class="category-products clearfix mt-3 js-category-products-carousel">
<h2>
{*if $products|@count == 1}
{l s='%s other product in the same category:' sprintf=[$products|@count] d='Shop.Theme.Catalog'}
{if isset($products_source) && $products_source == 'series'}
{l s='Produkty z tej samej serii' d='Shop.Theme.Catalog'}
{else}
{l s='%s other products in the same category:' sprintf=[$products|@count] d='Shop.Theme.Catalog'}
{/if*}
{if $products|@count == 1}
{l s='Podobny produkt z tej samej kategorii' sprintf=[$products|@count] d='Shop.Theme.Catalog'}
{else}
{l s='Podobne produkty z tej samej kategorii' sprintf=[$products|@count] d='Shop.Theme.Catalog'}
{if $products|@count == 1}
{l s='Podobny produkt z tej samej kategorii' d='Shop.Theme.Catalog'}
{else}
{l s='Podobne produkty z tej samej kategorii' d='Shop.Theme.Catalog'}
{/if}
{/if}
</h2>
<div class="products">
{foreach from=$products item="product"}
{include file="catalog/_partials/miniatures/product-light.tpl" product=$product}
{/foreach}
<div class="category-products-carousel-wrapper">
<button
type="button"
class="category-products-carousel__nav category-products-carousel__nav--prev js-category-products-prev"
aria-label="{l s='Poprzednie produkty' d='Shop.Theme.Catalog'}"
>
<span aria-hidden="true">&#8249;</span>
</button>
<div class="products category-products-carousel js-category-products-track">
{foreach from=$products item="product"}
{include file="catalog/_partials/miniatures/product-light.tpl" product=$product}
{/foreach}
</div>
<button
type="button"
class="category-products-carousel__nav category-products-carousel__nav--next js-category-products-next"
aria-label="{l s='Następne produkty' d='Shop.Theme.Catalog'}"
>
<span aria-hidden="true">&#8250;</span>
</button>
</div>
</section>
{literal}<script>
(function(){if(window.__ibSeriesCarouselInit){return;}window.__ibSeriesCarouselInit=true;function s(t){var i=t.querySelector('article.product-miniature');if(!i){return Math.max(240,Math.floor(t.clientWidth*.8));}var c=window.getComputedStyle(t),g=parseFloat(c.columnGap||c.gap||'0')||0;return Math.ceil(i.getBoundingClientRect().width+g);}function u(t,p,n){var m=Math.max(0,t.scrollWidth-t.clientWidth),c=m>8;if(!c){p.disabled=true;n.disabled=true;return;}p.disabled=t.scrollLeft<=2;n.disabled=t.scrollLeft>=m-2;}function init(r){var t=r.querySelector('.js-category-products-track'),p=r.querySelector('.js-category-products-prev'),n=r.querySelector('.js-category-products-next');if(!t||!p||!n){return;}p.addEventListener('click',function(){t.scrollBy({left:-s(t),behavior:'smooth'});});n.addEventListener('click',function(){t.scrollBy({left:s(t),behavior:'smooth'});});var k=false;t.addEventListener('scroll',function(){if(k){return;}k=true;window.requestAnimationFrame(function(){u(t,p,n);k=false;});});window.addEventListener('resize',function(){u(t,p,n);});u(t,p,n);}var all=document.querySelectorAll('.js-category-products-carousel');all.forEach(init);}());
</script>{/literal}

View File

@@ -46,6 +46,39 @@
{/literal}
{else}
{block name='cms_content'}
{if $cms.id == 9}
<style>
.series-grid-start {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
margin-bottom: 24px;
}
.series-grid-start__tile {
display: block;
width: 100%;
max-width: 320px;
transition: transform 0.2s ease;
}
.series-grid-start__tile:hover {
transform: translateY(-3px);
}
.series-grid-start__tile img {
display: block;
width: 100%;
height: auto;
}
</style>
<div class="series-grid-start">
<a class="series-grid-start__tile" href="{$urls.base_url}gniazda-wlaczniki-i-akcesoria/?seria=simon-10" title="SIMON 10" target="_blank" rel="noopener noreferrer">
<img src="{$urls.theme_assets}img/cms/serie-simon10-tile-1.svg" alt="SIMON 10 - skontaktuj SIMON">
</a>
</div>
{/if}
{$cms.content nofilter}
{/block}
{/if}