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.
This commit is contained in:
2026-02-25 09:23:54 +01:00
parent e579d0a597
commit e888c81aef
20 changed files with 11577 additions and 2 deletions

View File

@@ -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; }
}

View File

@@ -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 }
}
});
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
<section class="pp-carousel" id="pp-carousel-{$ppc_id}">
<div class="pp-carousel__header">
{if $ppc_title}
<h2 class="pp-carousel__title">{$ppc_title|escape:'htmlall':'UTF-8'}</h2>
{/if}
{if $ppc_subtitle}
<div class="pp-carousel__subtitle">{$ppc_subtitle|escape:'htmlall':'UTF-8'}</div>
{/if}
</div>
{if $ppc_products|count > 0}
<div class="pp-carousel__slider swiper">
<div class="swiper-wrapper">
{foreach from=$ppc_products item=product}
<div class="swiper-slide">
<article class="pp-carousel__card">
<a class="pp-carousel__image" href="{$product.url}" title="{$product.name|escape:'htmlall':'UTF-8'}">
{if isset($product.cover.bySize.home_default.url)}
<img src="{$product.cover.bySize.home_default.url}"
alt="{$product.name|escape:'htmlall':'UTF-8'}"
loading="lazy"
width="300" height="300">
{elseif isset($product.cover.large.url)}
<img src="{$product.cover.large.url}"
alt="{$product.name|escape:'htmlall':'UTF-8'}"
loading="lazy">
{/if}
{if isset($product.category_name) && $product.category_name}
<span class="pp-carousel__label">{$product.category_name|escape:'htmlall':'UTF-8'}</span>
{/if}
</a>
<div class="pp-carousel__meta">
<a class="pp-carousel__name" href="{$product.url}">
{$product.name|escape:'htmlall':'UTF-8'}
</a>
{if isset($product.price) && $product.price}
<div class="pp-carousel__price">
{$product.price}
{if $ppc_price_suffix}
<span class="pp-carousel__priceSuffix">{$ppc_price_suffix|escape:'htmlall':'UTF-8'}</span>
{/if}
</div>
{/if}
</div>
</article>
</div>
{/foreach}
</div>
<div class="pp-carousel__nav">
<div class="pp-carousel__prev" aria-label="Poprzedni"></div>
<div class="pp-carousel__next" aria-label="Następny"></div>
</div>
</div>
{/if}
{if $ppc_button_label && $ppc_button_url}
<div class="pp-carousel__footer">
<a class="pp-carousel__more" href="{$ppc_button_url|escape:'htmlall':'UTF-8'}">
{$ppc_button_label|escape:'htmlall':'UTF-8'}
</a>
</div>
{/if}
</section>