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:
179
modules/pp_carousel/views/css/pp_carousel.css
Normal file
179
modules/pp_carousel/views/css/pp_carousel.css
Normal 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; }
|
||||
}
|
||||
23
modules/pp_carousel/views/js/pp_carousel.js
Normal file
23
modules/pp_carousel/views/js/pp_carousel.js
Normal 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 }
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
13
modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css
vendored
Normal file
13
modules/pp_carousel/views/lib/swiper/swiper-bundle.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7661
modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js
vendored
Normal file
7661
modules/pp_carousel/views/lib/swiper/swiper-bundle.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
67
modules/pp_carousel/views/templates/hook/pp_carousel.tpl
Normal file
67
modules/pp_carousel/views/templates/hook/pp_carousel.tpl
Normal 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>
|
||||
Reference in New Issue
Block a user