DC Frame Slideshow – nowoczesny slider z animowaną ramką dla OpenCart 4

DC Frame Slideshow – nowoczesny slider z animowaną ramką dla OpenCart 4

Jeśli kiedykolwiek wdrażałeś slider na stronie głównej sklepu lub landing page’u, to dobrze wiesz, jak to zwykle wygląda. Kilka obrazków, proste przejście typu fade albo slide, strzałki po bokach… i koniec.

Działa? Tak. Robi wrażenie? Najczęściej nie.

Problem polega na tym, że większość dostępnych sliderów jest do siebie łudząco podobna. Te same schematy, te same animacje, zero charakteru. W efekcie slider przestaje być elementem „hero”, a staje się tylko kolejnym blokiem treści, który użytkownik przewija bez zastanowienia.

Do tego dochodzi jeszcze brak realnej kontroli:

  • animacje są banalne albo sztywno narzucone,

  • nie da się wpłynąć na formę przejścia,

  • trudno zbudować efekt „wow”, który faktycznie zatrzyma uwagę użytkownika na pierwszym ekranie.

I właśnie tutaj pojawia się DC Frame Slideshow.
To nie jest kolejny „zwykły” slider, tylko moduł zaprojektowany z myślą o wizualnym efekcie od pierwszego wejścia na stronę. Dynamiczne ramy SVG, zaawansowane animacje, różne tryby (themes) i pełna kontrola nad wyglądem sprawiają, że slider w końcu zaczyna pełnić rolę, do której został stworzony — przyciąga uwagę i buduje charakter strony.

To właśnie takie elementy pokazują, że świadome projektowanie sklepu internetowego zaczyna się od pierwszego ekranu i emocji, jakie wywołuje u użytkownika.

 

Czym jest DC Frame Slideshow?

DC Frame Slideshow to autorski moduł stworzony specjalnie dla OpenCart 4, zaprojektowany z myślą o tych, którzy chcą czegoś więcej niż „kolejny slider z obrazkami” 🚀

To nie jest gotowiec z marketplace’u ani przerobiony plugin sprzed 10 lat. To nowoczesny, efektowny slider, który od pierwszej sekundy buduje klimat strony i przyciąga uwagę użytkownika 🎯

🔧 Technologicznie moduł opiera się na:

  • 🧩 SVG – dynamiczne ramki i maski animowane w czasie rzeczywistym

  • 🎬 Anime.js – płynne, kontrolowane animacje bez szarpania i lagów

  • ✂️ Autorskiej logice przejść – każdy slajd to mała scena, a nie tylko „fade in / fade out”

Bez jQuery
Całość działa w czystym JavaScript, co oznacza:

  • mniejszy narzut,

  • lepszą wydajność,

  • pełną zgodność z nowoczesnymi standardami frontendu.

🧱 Zero ingerencji w core OpenCart
Moduł instalujesz jak każde inne rozszerzenie:

  • bez nadpisywania plików systemowych,

  • bez hacków,

  • bez „kombinowania”.

👉 W praktyce: instalujesz, konfigurujesz i działa.
A przy tym wygląda jak coś, co zwykle wymagałoby dedykowanego frontendu i kilku dni pracy ✨

 

Architektura modułu – jak to działa „pod maską” 🧠⚙️

DC Frame Slideshow nie jest kolejnym „globalnym” sliderem, który wstrzykuje jeden skrypt i modli się, żeby nie kolidował z resztą strony. Tutaj architektura została zaprojektowana od zera pod OpenCart 4 – modułowo, czysto i przewidywalnie.

🔹 Każdy slider to niezależna instancja
Każde wywołanie modułu tworzy własny obiekt JS. Własny stan, własne animacje, własne kolory. Zero współdzielenia zmiennych, zero efektu domina.

🔹 Pełna obsługa wielu modułów na jednej stronie
Możesz mieć kilka sliderów jeden pod drugim – każdy z innym motywem, innymi kolorami i inną animacją. Wszystko działa równolegle i bez konfliktów 🚀

🔹 Motywy = osobna logika JavaScript
theme_1, theme_2, theme_3 to nie „if w środku jednego pliku”.
Każdy motyw ma:

  • własną klasę JS

  • własną animację SVG

  • własną sekwencję przejść
    Dzięki temu kod jest czytelny, rozszerzalny i gotowy na kolejne tryby 🎨

🔹 Kolory przekazywane elastycznie
Moduł potrafi pobierać kolory na dwa sposoby:

  • 🎯 przez CSS variables (--bg-1, --bg-2) – idealne do integracji z motywem

  • 🎯 przez data attributes (data-bg_1, data-bg_2) – szybkie i jednoznaczne
    Gradient? Jeden kolor? Zero kolorów? Wszystko obsłużone.

🔹 Zero konfliktów z resztą sklepu
Brak jQuery, brak globalnych selektorów, brak ingerencji w core OpenCarta.
Każda instancja działa w swoim „sandboxie” 🧩

Efekt? Slider, który wygląda efektownie, ale pod spodem jest uporządkowany, przewidywalny i gotowy do dalszej rozbudowy – bez bólu głowy i bez technicznego długu.

 

Panel administracyjny – konfiguracja krok po kroku ⚙️

To jest moment, w którym DC Frame Slideshow pokazuje swoją prawdziwą siłę. Panel administracyjny został zaprojektowany tak, żeby nie tylko „coś ustawić”, ale realnie kontrolować wygląd i zachowanie slidera – bez grzebania w kodzie.

Poniżej przechodzimy przez wszystkie zakładki dokładnie tak, jak robi to użytkownik w OpenCart 4 👇

 

1. Zakładka „General” 🧩

Screen: Zakładka istawień "General"
Screen: Zakładka istawień "General"

 

Tutaj definiujesz tożsamość modułu.

Dostępne opcje:

  • Nazwa modułu – czysto organizacyjna, widoczna w panelu

  • Attribute ID – unikalny identyfikator instancji

  • Tytuł i opis (WYSIWYG) – opcjonalna treść nad sliderem

  • Wielojęzyczność – pełne wsparcie dla wielu języków

Dlaczego Attribute ID jest tak ważne? 🔑

  • pozwala rozróżniać wiele sliderów na jednej stronie

  • umożliwia precyzyjne stylowanie CSS (#hero-slider, #homepage-slider itd.)

  • daje punkt zaczepienia dla JS (jeśli chcesz rozszerzać moduł)

  • pomaga w SEO i semantyce (świadome nazewnictwo sekcji)

👉 Pro tip:
Zamiast losowych ID, używaj sensownych nazw typu hero-home, promo-black-friday, collection-summer.

 

2. Zakładka „Slides” 🖼️

Screen: Zakładka istawień "Slides"
Screen: Zakładka istawień "Slides"

 

Tu dzieje się content.

Co możesz ustawić:

  • listę slajdów (bez limitu)

  • dla każdego slajdu:

    • obraz

    • nagłówek

    • opis

    • link + tekst przycisku

Najważniejsze cechy:

  • ✔️ dowolna liczba slajdów

  • ✔️ kolejność = kolejność animacji

  • ✔️ zero ograniczeń systemowych

👉 I co najważniejsze:
nie korzystasz z banerów systemowych OpenCarta
Nie ma kombinowania, przypisywania layoutów, pozycji, sortowań.
Masz czystą, logiczną listę slajdów, dokładnie pod ten moduł.

 

3. Zakładka „Settings” 🔥

Screen: Zakładka istawień "Settings"
Screen: Zakładka istawień "Settings"

 

Tu robimy prawdziwe mięso 🍖
To właśnie ta zakładka decyduje, jak slider wygląda i jak się zachowuje.

Dostępne ustawienia:

🎨 Wygląd i motyw

  • wybór Theme 1 / Theme 2 / Theme 3

  • kolory tła:

    • Kolor 1

    • Kolor 2 (opcjonalny – gradient)

  • kolory:

    • nagłówków

    • opisów

    • przycisków (normal / hover)

⚙️ Zachowanie slidera

  • sposób animacji (zależny od theme)

  • reakcja na zmianę slajdu

  • płynność przejść

📱 Responsywność

  • automatyczne dopasowanie do ekranu

  • inne proporcje dla desktop / mobile

  • brak sztywnych wysokości

 

Jak działa tło? 🎯

To jeden z kluczowych elementów DC Frame Slideshow:

  • Podasz 1 kolor
    slider użyje jednolitego tła

  • Podasz 2 kolory
    tworzony jest gradient

  • Każdy theme interpretuje tło inaczej:

    • Theme 1 – tło kontenera

    • Theme 2 – tło + ramka SVG

    • Theme 3 – gradient bezpośrednio w masce SVG

👉 Efekt:
Ten sam zestaw kolorów może wyglądać zupełnie inaczej w zależności od motywu 🎨

 

Motywy animacji – Theme 1, Theme 2, Theme 3 🎬✨

Jednym z największych atutów DC Frame Slideshow jest to, że nie masz jednego „sztywnego” slidera. Masz trzy zupełnie różne style animacji, z osobną logiką JS, inną pracą ramki i innym charakterem wizualnym.

Każdy theme to świadomy wybór stylistyczny, a nie tylko kosmetyczna zmiana efektu.

 

1. Theme 1 – klasyczna animowana ramka 🧩

Charakter: elegancki, spokojny, bardzo uniwersalny

Co go wyróżnia:

  • płynne, naturalne przejścia slajdów

  • subtelna animacja ramki SVG

  • brak agresywnych ruchów – wszystko „oddycha”

  • świetna czytelność treści

Idealny do:

  • strony głównej (home page)

  • marek premium

  • sklepów, gdzie slider ma wprowadzać, a nie dominować

📌 Technicznie:

  • klasyczna animacja maski SVG

  • jeden etap animacji ramki

  • bardzo lekki dla przeglądarki

👉 Kod js stylu Theme 1:

 

{
	function debounce(func, wait, immediate) {
		let timeout;
		return function () {
			const context = this, args = arguments;
			const later = function () {
				timeout = null;
				if (!immediate) func.apply(context, args);
			};
			const callNow = immediate && !timeout;
			clearTimeout(timeout);
			timeout = setTimeout(later, wait);
			if (callNow) func.apply(context, args);
		};
	}

	class Slideshow {
		constructor(el) {
			this.DOM = {};
			this.DOM.el = el;

			this.wrapper = el.closest('.dc-frame-slideshow');
			this.host = el.closest('.dc-slideshow-body') || el;

			// bierzemy z CSS vars (pewne i spójne z Twig)
			const cs = this.wrapper ? getComputedStyle(this.wrapper) : null;

			this.bg1 = (cs?.getPropertyValue('--bg-1') || '#f1f1f1').trim();
			this.bg2 = (cs?.getPropertyValue('--bg-2') || '').trim();

			this.settings = {
				animation: {
					slides: { duration: 600, easing: 'easeOutQuint' },
					shape: { duration: 300, easing: { in: 'easeOutQuint', out: 'easeOutQuad' } }
				}
			};

			this.init();
		}

		init() {
			this.DOM.slides = Array.from(this.DOM.el.querySelectorAll('.slides > .slide'));
			this.slidesTotal = this.DOM.slides.length;

			this.DOM.nav = this.DOM.el.querySelector('.slidenav');
			this.DOM.nextCtrl = this.DOM.nav?.querySelector('.slidenav__item--next');
			this.DOM.prevCtrl = this.DOM.nav?.querySelector('.slidenav__item--prev');

			this.current = 0;

			// background kontenera (zawsze sensownie)
			this.applyContainerBackground();

			this.createFrame();
			this.initEvents();
		}

		applyContainerBackground() {
			// tu ustawiasz “tło kontenera” (np pod zdjęciami gdyby gdzieś prześwitywało)
			if (this.bg2 && this.bg2 !== this.bg1) {
				this.host.style.background = `linear-gradient(90deg, ${this.bg1}, ${this.bg2})`;
			} else {
				this.host.style.background = this.bg1;
			}
		}

		createFrame() {
			this.rect = this.DOM.el.getBoundingClientRect();
			this.frameSize = this.rect.width / 12;

			this.paths = {
				initial: this.calculatePath('initial'),
				final: this.calculatePath('final')
			};

			this.DOM.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
			this.DOM.svg.setAttribute('class', 'shape');
			this.DOM.svg.setAttribute('width', '100%');
			this.DOM.svg.setAttribute('height', '100%');
			this.DOM.svg.setAttribute('viewBox', `0 0 ${this.rect.width} ${this.rect.height}`);

			const hasGradient = this.bg2 && this.bg2 !== this.bg1;

			// unikalne id gradientu (żeby wiele modułów nie kolidowało)
			const gid = 'dc_frame_grad_' + Math.random().toString(16).slice(2);

			this.DOM.svg.innerHTML = hasGradient
				? `
					<defs>
						<linearGradient id="${gid}" x1="0%" y1="0%" x2="100%" y2="0%">
							<stop offset="0%" stop-color="${this.bg1}"></stop>
							<stop offset="100%" stop-color="${this.bg2}"></stop>
						</linearGradient>
					</defs>
					<path fill="url(#${gid})" d="${this.paths.initial}"></path>
				`
				: `<path fill="${this.bg1}" d="${this.paths.initial}"></path>`;

			// svg przed nav
			this.DOM.el.insertBefore(this.DOM.svg, this.DOM.nav);
			this.DOM.shape = this.DOM.svg.querySelector('path');
		}

		updateFrame() {
			this.rect = this.DOM.el.getBoundingClientRect();
			this.frameSize = this.rect.width / 12;

			this.paths.initial = this.calculatePath('initial');
			this.paths.final = this.calculatePath('final');

			this.DOM.svg.setAttribute('viewBox', `0 0 ${this.rect.width} ${this.rect.height}`);
			this.DOM.shape.setAttribute('d', this.isAnimating ? this.paths.final : this.paths.initial);
		}

		calculatePath(path = 'initial') {
			return path === 'initial'
				? `M 0,0 0,${this.rect.height} ${this.rect.width},${this.rect.height} ${this.rect.width},0 0,0 Z M 0,0 ${this.rect.width},0 ${this.rect.width},${this.rect.height} 0,${this.rect.height} Z`
				: `M 0,0 0,${this.rect.height} ${this.rect.width},${this.rect.height} ${this.rect.width},0 0,0 Z M ${this.frameSize},${this.frameSize} ${this.rect.width - this.frameSize},${this.frameSize} ${this.rect.width - this.frameSize},${this.rect.height - this.frameSize} ${this.frameSize},${this.rect.height - this.frameSize} Z`;
		}

		initEvents() {
			this.DOM.nextCtrl?.addEventListener('click', () => this.navigate('next'));
			this.DOM.prevCtrl?.addEventListener('click', () => this.navigate('prev'));

			window.addEventListener('resize', debounce(() => this.updateFrame(), 20));

			document.addEventListener('keydown', (ev) => {
				const keyCode = ev.keyCode || ev.which;
				if (keyCode === 37) this.navigate('prev');
				else if (keyCode === 39) this.navigate('next');
			});
		}

		navigate(dir = 'next') {
			if (this.isAnimating) return false;
			this.isAnimating = true;

			const animateShapeIn = anime({
				targets: this.DOM.shape,
				duration: this.settings.animation.shape.duration,
				easing: this.settings.animation.shape.easing.in,
				d: this.paths.final
			});

			const animateSlides = () => {
				return new Promise((resolve) => {
					const currentSlide = this.DOM.slides[this.current];

					anime({
						targets: currentSlide,
						duration: this.settings.animation.slides.duration,
						easing: this.settings.animation.slides.easing,
						translateX: dir === 'next' ? -1 * this.rect.width : this.rect.width,
						complete: () => {
							currentSlide.classList.remove('slide--current');
							resolve();
						}
					});

					this.current = dir === 'next'
						? (this.current < this.slidesTotal - 1 ? this.current + 1 : 0)
						: (this.current > 0 ? this.current - 1 : this.slidesTotal - 1);

					const newSlide = this.DOM.slides[this.current];
					newSlide.classList.add('slide--current');

					anime({
						targets: newSlide,
						duration: this.settings.animation.slides.duration,
						easing: this.settings.animation.slides.easing,
						translateX: [dir === 'next' ? this.rect.width : -1 * this.rect.width, 0]
					});

					const newSlideImg = newSlide.querySelector('.slide__img');
					if (newSlideImg) {
						anime.remove(newSlideImg);
						anime({
							targets: newSlideImg,
							duration: this.settings.animation.slides.duration * 4,
							easing: this.settings.animation.slides.easing,
							translateX: [dir === 'next' ? 200 : -200, 0]
						});
					}

					const textTargets = [
						newSlide.querySelector('.slide__title'),
						newSlide.querySelector('.slide__desc'),
						newSlide.querySelector('.slide__link')
					].filter(Boolean);

					if (textTargets.length) {
						anime({
							targets: textTargets,
							duration: this.settings.animation.slides.duration * 2,
							easing: this.settings.animation.slides.easing,
							delay: (t, i) => i * 100 + 100,
							translateX: [dir === 'next' ? 300 : -300, 0],
							opacity: [0, 1]
						});
					}
				});
			};

			const animateShapeOut = () => {
				anime({
					targets: this.DOM.shape,
					duration: this.settings.animation.shape.duration,
					delay: 150,
					easing: this.settings.animation.shape.easing.out,
					d: this.paths.initial,
					complete: () => this.isAnimating = false
				});
			};

			animateShapeIn.finished.then(animateSlides).then(animateShapeOut);
		}
	}

	document.querySelectorAll('.dc-frame-slideshow.theme_1').forEach((wrapper) => {

        const slideshowEl = wrapper.querySelector('.slideshow');
        const bodyEl      = wrapper.querySelector('.dc-slideshow-body');

        if (!slideshowEl || !bodyEl) return;

        // init slidera
        new Slideshow(slideshowEl);

        // imagesLoaded TYLKO dla tego slidera
        imagesLoaded(
            slideshowEl.querySelectorAll('.slide__img'),
            { background: true },
            () => {
                bodyEl.classList.remove('loading');
                bodyEl.classList.add('render');
            }
        );
    });
}

 

 

👉 Wideo pokazujące animację Theme 1

 

2. Theme 2 – dynamiczna ramka z ruchem bocznym ⚡

Charakter: mocny, nowoczesny, „premium slider look”

Co go wyróżnia:

  • agresywniejsze animacje przejść

  • wyraźny ruch boczny slajdów

  • ramka reagująca kierunkowo (next / prev)

  • efekt „wow” już przy pierwszym kliknięciu

Idealny do:

  • landing page

  • kampanii sprzedażowych

  • sekcji HERO, które mają przyciągać uwagę

📌 Technicznie:

  • osobna logika SVG dla next i prev

  • dynamiczne przeliczanie ścieżek ramki

  • animacje zsynchronizowane z ruchem slajdu

👉 Kod js stylu Theme 2:

{
	function debounce(func, wait, immediate) {
		let timeout;
		return function () {
			const context = this, args = arguments;
			const later = function () {
				timeout = null;
				if (!immediate) func.apply(context, args);
			};
			const callNow = immediate && !timeout;
			clearTimeout(timeout);
			timeout = setTimeout(later, wait);
			if (callNow) func.apply(context, args);
		};
	}

	class SlideshowTheme2 {
		constructor(wrapper) {
			this.wrapper = wrapper;
			this.body    = wrapper.querySelector('.dc-slideshow-body');
			this.el      = wrapper.querySelector('.slideshow');

			if (!this.el) return;

			// zostawiamy dokładnie tak jak chcesz:
			this.bg1 = wrapper.dataset.bg_1 || '#111';
			this.bg2 = (wrapper.dataset.bg_2 || '').trim();

			this.DOM = {};
			this.settings = {
				animation: {
					slides: {
						duration: 600,
						easing: 'easeOutQuint'
					},
					shape: {
						duration: 300,
						easing: { in: 'easeOutQuad', out: 'easeOutQuad' }
					}
				}
			};

			// unikalne ID dla gradientu w SVG (żeby 2 moduły nie gryzły się)
			this.uid = this.wrapper.id
				? this.wrapper.id.replace(/[^a-zA-Z0-9\-_]/g, '')
				: ('dcfs_' + Math.random().toString(16).slice(2));

			this.init();
		}

		init() {
			this.DOM.slides = Array.from(this.el.querySelectorAll('.slides > .slide'));
			this.slidesTotal = this.DOM.slides.length;

			this.DOM.nav      = this.el.querySelector('.slidenav');
			this.DOM.nextCtrl = this.DOM.nav?.querySelector('.slidenav__item--next');
			this.DOM.prevCtrl = this.DOM.nav?.querySelector('.slidenav__item--prev');

			this.current = 0;

			// tło kontenera możesz zostawić jako “fallback”
			this.applyContainerBackground();

			this.createFrame();
			this.initEvents();
		}

		applyContainerBackground() {
			if (!this.body) return;

			if (this.bg2 && this.bg2 !== this.bg1) {
				// to jest tylko fallback – “prawdziwe” tło w animacji to SVG fill
				this.body.style.background = `linear-gradient(135deg, ${this.bg1}, ${this.bg2})`;
			} else {
				this.body.style.background = this.bg1;
			}
		}

		createFrame() {
			this.rect = this.el.getBoundingClientRect();
			this.frameSize = this.rect.width / 12;

			this.paths = {
				initial: this.calculatePath('initial'),
				final: this.calculatePath('final')
			};

			const hasGradient = (this.bg2 && this.bg2 !== this.bg1);
			const gradId = `${this.uid}_grad`;

			this.DOM.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
			this.DOM.svg.setAttribute('class', 'shape');
			this.DOM.svg.setAttribute('width', '100%');
			this.DOM.svg.setAttribute('height', '100%');
			this.DOM.svg.setAttribute('viewBox', `0 0 ${this.rect.width} ${this.rect.height}`);

			// UWAGA: gradient robimy w SVG, bo to wypełnia “ramkę” podczas animacji
			this.DOM.svg.innerHTML = `
				${hasGradient ? `
					<defs>
						<linearGradient id="${gradId}" x1="0%" y1="0%" x2="100%" y2="0%">
							<stop offset="0%" stop-color="${this.bg1}"></stop>
							<stop offset="100%" stop-color="${this.bg2}"></stop>
						</linearGradient>
					</defs>
				` : ''}
				<path fill="${hasGradient ? `url(#${gradId})` : this.bg1}" d="${this.paths.initial}"></path>
			`;

			this.el.insertBefore(this.DOM.svg, this.DOM.nav);
			this.DOM.shape = this.DOM.svg.querySelector('path');
		}

		updateFrame() {
			this.rect = this.el.getBoundingClientRect();
			this.paths.initial = this.calculatePath('initial');
			this.paths.final   = this.calculatePath('final');

			this.DOM.svg.setAttribute('viewBox', `0 0 ${this.rect.width} ${this.rect.height}`);

			// jak nie animuje, wracamy do initial
			this.DOM.shape.setAttribute('d', this.isAnimating ? this.paths.final.next : this.paths.initial);
		}

		calculatePath(type = 'initial') {
			if (type === 'initial') {
				return `
					M 0,0 0,${this.rect.height}
					${this.rect.width},${this.rect.height}
					${this.rect.width},0 0,0 Z
					M 0,0 ${this.rect.width},0
					${this.rect.width},${this.rect.height}
					0,${this.rect.height} Z
				`;
			}

			return {
				next: `
					M 0,0 0,${this.rect.height}
					${this.rect.width},${this.rect.height}
					${this.rect.width},0 0,0 Z
					M ${this.frameSize},${this.frameSize}
					${this.rect.width - this.frameSize},${this.frameSize / 2}
					${this.rect.width - this.frameSize},${this.rect.height - this.frameSize / 2}
					${this.frameSize},${this.rect.height - this.frameSize} Z
				`,
				prev: `
					M 0,0 0,${this.rect.height}
					${this.rect.width},${this.rect.height}
					${this.rect.width},0 0,0 Z
					M ${this.frameSize},${this.frameSize / 2}
					${this.rect.width - this.frameSize},${this.frameSize}
					${this.rect.width - this.frameSize},${this.rect.height - this.frameSize}
					${this.frameSize},${this.rect.height - this.frameSize / 2} Z
				`
			};
		}

		initEvents() {
			this.DOM.nextCtrl?.addEventListener('click', () => this.navigate('next'));
			this.DOM.prevCtrl?.addEventListener('click', () => this.navigate('prev'));

			window.addEventListener('resize', debounce(() => this.updateFrame(), 30));
		}

		navigate(dir = 'next') {
			if (this.isAnimating) return;
			this.isAnimating = true;

			const animateShapeIn = anime({
				targets: this.DOM.shape,
				duration: this.settings.animation.shape.duration,
				easing: this.settings.animation.shape.easing.in,
				d: dir === 'next' ? this.paths.final.next : this.paths.final.prev
			});

			const animateSlides = () => new Promise(resolve => {
				const currentSlide = this.DOM.slides[this.current];

				anime({
					targets: currentSlide,
					duration: this.settings.animation.slides.duration,
					easing: this.settings.animation.slides.easing,
					translateX: dir === 'next' ? -this.rect.width : this.rect.width,
					complete: () => {
						currentSlide.classList.remove('slide--current');
						resolve();
					}
				});

				this.current = dir === 'next'
					? (this.current + 1) % this.slidesTotal
					: (this.current - 1 + this.slidesTotal) % this.slidesTotal;

				const newSlide = this.DOM.slides[this.current];
				newSlide.classList.add('slide--current');

				anime({
					targets: newSlide,
					duration: this.settings.animation.slides.duration,
					easing: this.settings.animation.slides.easing,
					translateX: [dir === 'next' ? this.rect.width : -this.rect.width, 0]
				});
			});

			const animateShapeOut = () => {
				anime({
					targets: this.DOM.shape,
					duration: this.settings.animation.shape.duration,
					delay: 150,
					easing: this.settings.animation.shape.easing.out,
					d: this.paths.initial,
					complete: () => this.isAnimating = false
				});
			};

			animateShapeIn.finished.then(animateSlides).then(animateShapeOut);
		}
	}

	document.querySelectorAll('.dc-frame-slideshow.theme_2').forEach(wrapper => {
		const inst = new SlideshowTheme2(wrapper);

		// loader per-instancja (żeby 2 moduły działały niezależnie)
		const imgs = wrapper.querySelectorAll('.slide__img');
		if (imgs.length) {
			imagesLoaded(imgs, { background: true }, () => {
				wrapper.querySelectorAll('.dc-slideshow-body').forEach(b => {
					b.classList.remove('loading');
					b.classList.add('render');
				});
			});
		} else {
			// awaryjnie: bez obrazów też nie blokuj
			wrapper.querySelectorAll('.dc-slideshow-body').forEach(b => {
				b.classList.remove('loading');
				b.classList.add('render');
			});
		}
	});
}

 

👉 Wideo pokazujące animację Theme 2

 

3. Theme 3 – gradientowa ramka SVG 🌈🔥

Charakter: efektowny, nowoczesny, „design statement”

Co go wyróżnia:

  • gradient bezpośrednio w SVG (nie w CSS!)

  • wieloetapowe animacje ramki

  • bardzo płynne, sekwencyjne przejścia

  • ogromny efekt wizualny przy minimalnym JS

Idealny do:

  • stron marek kreatywnych

  • projektów showcase / portfolio

  • sekcji, gdzie slider ma być bohaterem strony

📌 Technicznie:

  • gradient zdefiniowany w <defs> SVG

  • animacje wielostopniowe (timeline Anime.js)

  • zero dodatkowych bibliotek

👉 Kod js stylu Theme 3:

 

 

👉 Wideo pokazujące animację Theme 3

 

🎯 Dlaczego to ma znaczenie?

Każdy theme:

  • ma własny kod JS

  • nie koliduje z innymi instancjami

  • inaczej interpretuje tło (kolor / gradient)

  • pozwala dobrać styl slidera do celu strony, a nie odwrotnie

To nie są „skórki”.
To są trzy różne silniki animacji w jednym module 🚀

 

📱 Responsywność i mobile – zaprojektowane od podstaw pod nowoczesne layouty

DC Frame Slideshow od początku był projektowany z myślą o mobile first i realnych problemach, jakie sprawiają klasyczne slidery na telefonach 📵.

Nie ma tu sztucznych hacków, JS-owych przeliczeń wysokości ani „łatania” CSS-em po fakcie.

 

🔧 Aspect-ratio zamiast sztywnych wysokości

Zamiast height: 600px (które na mobile zawsze boli 😅), moduł wykorzystuje CSS aspect-ratio:

  • zachowuje proporcje slidera niezależnie od szerokości ekranu

  • automatycznie skaluje się na desktopie, tablecie i telefonie

  • zero JS-owych przeliczeń przy resize

Efekt? Slider zawsze wygląda tak samo dobrze, bez względu na urządzenie 📐

 

📲 Automatyczna adaptacja do pionu

Na urządzeniach mobilnych:

  • proporcje przechodzą naturalnie w układ pionowy

  • treści (nagłówki, opisy, CTA) pozostają czytelne

  • animacje SVG i ramki dalej działają — tylko w innym kontekście

Nie ma „uciętych” slajdów ani przycisków poza ekranem 👌

 

🧱 Brak skakania layoutu (CLS = 0)

Jedna z kluczowych rzeczy, na które zwraca uwagę Google:

  • wysokość slidera jest znana od razu

  • layout nie przeskakuje po załadowaniu obrazów

  • brak CLS (Cumulative Layout Shift)

To oznacza:

  • lepszy Core Web Vitals 🚀

  • lepsze UX

  • lepsze SEO (tak, realnie)

 

⚡ Lekkość mimo animacji

Mimo zaawansowanych animacji:

  • brak jQuery

  • brak ciężkich bibliotek sliderowych

  • tylko SVG + Anime.js + czysty JS

Dzięki temu:

  • mobile nie „klęka”

  • animacje są płynne nawet na słabszych urządzeniach

  • bateria użytkownika nie płacze 🔋😉

W skrócie: DC Frame Slideshow zachowuje się jak nowoczesny komponent UI, a nie stary slider z 2015 roku.
Responsywność nie jest dodatkiem — jest fundamentem.

 

🚀 Wydajność i SEO – zaprojektowane bez kompromisów

DC Frame Slideshow od początku był projektowany pod performance i SEO, a nie „żeby jakoś działało”. Tu nie ma przypadków ani ciężkich zależności 👇

 

⚡ Brak jQuery

  • Czysty, nowoczesny JavaScript (ES6+)

  • Zero zbędnych bibliotek

  • Mniej JS = szybszy rendering + lepszy PageSpeed

 

🧠 Brak inline JS

  • Cała logika w plikach modułu

  • Lepsza czytelność

  • Lepsze cache’owanie przez przeglądarkę

  • Brak problemów z CSP

 

🧩 SVG zamiast canvasów

  • Ramki i maski oparte o SVG

  • Skalowalne, lekkie, idealne pod HiDPI

  • SVG animowane przez Anime.js = maksimum efektu przy minimalnym koszcie

 

🖼️ Obrazy ładowane świadomie

  • Slajdy inicjalizują się dopiero po:

    • załadowaniu obrazów (imagesLoaded)

  • Brak „pustych klatek”

  • Brak CLS (layout się nie przesuwa)

 

📈 Idealne pod PageSpeed

  • Brak blokujących skryptów

  • Brak nadmiarowych zapytań

  • Brak ciężkich frameworków

  • Stabilny layout od pierwszego renderu

 

🔥 Autorski system minifikacji i cache (game changer)

DC Frame Slideshow nie korzysta z zewnętrznych minifierów. Moduł ma wbudowany, autorski system optymalizacji, który działa automatycznie:

 

🗜️ Minifikacja CSS i JS

  • CSS i JS są:

    • minifikowane

    • scalane w jeden plik

  • Silnik oparty o JShrink (sprawdzone, stabilne rozwiązanie)

 

♻️ Inteligentny cache

  • Cache nie przebudowuje się przy każdym wejściu

  • Pliki są regenerowane tylko wtedy, gdy któryś z plików źródłowych się zmieni

  • Efekt:

    • zero niepotrzebnych operacji

    • zero spowolnień

    • pełna kontrola

 

🧠 Co to oznacza w praktyce?

  • Slider jest szybki nawet przy:

    • wielu instancjach na stronie

    • kilku motywach jednocześnie

  • Idealny do:

    • homepage

    • landing pages

    • sklepów z dużym ruchem

 

Instalacja modułu

Instalacja DC Frame Slideshow została zaprojektowana dokładnie tak, jak powinno to wyglądać w OpenCart 4 – bez kombinowania, bez grzebania w plikach i bez ryzyka konfliktów ⚙️

 

Jak zainstalować moduł?

Cały proces sprowadza się do standardowego instalatora OpenCart:

  • 📦 pobierasz paczkę ZIP z modułem

  • 📤 wgrywasz ją przez Rozszerzenia → Instalator

  • ✅ gotowe – moduł pojawia się na liście rozszerzeń

Nie ma tu żadnych ręcznych kroków, kopiowania plików ani edycji core.

 

Co jest ważne (i miłe 😉)

  • OCMOD – moduł nie ingeruje w system

  • 🧩 brak nadpisywania plików OpenCarta

  • 🔄 pełna kompatybilność z każdym motywem (również customowym)

  • 🧠 logika JS i CSS jest całkowicie odseparowana od reszty sklepu

Dzięki temu możesz:

  • używać modułu wielokrotnie na jednej stronie

  • mieszać różne motywy (Theme 1 / 2 / 3)

  • bez stresu aktualizować OpenCart w przyszłości

 

Skąd pobrać moduł?

Moduł udostępniamy w dwóch miejscach – w zależności od tego, jak pracujesz:

  • 🟢 Design Cart – wersja stabilna, polecana produkcyjnie
    👉Pobierz z Design Cart2

  • 🐙 GitHub – wersja developerska, idealna do testów i modyfikacji
    👉 Pobierz z GitHub

 

Obie wersje są w pełni funkcjonalne – różnica polega głównie na cyklu aktualizacji.

Jeśli znasz OpenCart, to instalacja DC Frame Slideshow zajmie Ci dosłownie minutę.
Jeśli nie znasz – tym bardziej 😉
A dalej zostaje już tylko jedno: ustawić slajdy i zrobić efekt „wow” na stronie głównej 🚀

 

Podsumowanie

DC Frame Slideshow to zupełnie inna liga niż klasyczne slidery, które wszyscy znamy z OpenCarta i gotowych motywów. To nie jest kolejny rotator banerów „żeby coś się ruszało” — to pełnoprawny element designu, który realnie buduje pierwsze wrażenie i charakter strony 🚀

Czym DC Frame Slideshow różni się od zwykłych sliderów?

  • 🔳 Animowana ramka SVG, a nie proste przesuwanie obrazków

  • 🎬 Zaawansowane animacje Anime.js, zsynchronizowane z treścią i maską

  • 🎨 Motywy (Theme 1 / 2 / 3), które faktycznie zmieniają sposób prezentacji, a nie tylko kolory

  • 🧩 Pełna niezależność instancji – możesz mieć kilka sliderów na jednej stronie bez konfliktów

  • Wydajność + SEO: zero jQuery, zero zbędnych skryptów, brak CLS

 

Dlaczego to nie jest kolejny nudny banner?

Bo:

  • ❌ nie opiera się na systemowych bannerach OpenCarta

  • ❌ nie wygląda tak samo jak setki innych sklepów

  • ❌ nie wymaga hacków, OCMOD-ów ani ingerencji w core

Zamiast tego:

  • ✅ daje efekt „wow” od pierwszego wejścia

  • ✅ pozwala kontrolować formę, animację i tło

  • ✅ skaluje się wizualnie od prostego home page po agresywny landing

 

Dla kogo ten moduł ma sens?

DC Frame Slideshow ma sens, jeśli:

  • 🏪 chcesz, żeby Twój sklep wyglądał premium, a nie „jak każdy inny”

  • 📢 budujesz landing page, kampanię, nową kolekcję

  • 🎯 zależy Ci na designie, wydajności i kontroli, a nie gotowych schematach

  • 🧠 wiesz, że pierwsze 3 sekundy na stronie mają znaczenie

 

Jeśli szukasz czegoś więcej niż zwykłego slidera, co:

  • przyciąga wzrok

  • nie psuje PageSpeed

  • i daje realną przewagę wizualną

👉 DC Frame Slideshow dokładnie w to celuje.