Specyfikacja szablonów

# Specyfikacja szablonów STRUCTUM CMS — dla AI **Ostatnia aktualizacja:** 14 October 2026 **Wersja:** 3.16 — dodano 3.18.11: wewnętrzny padding treści w kontenerach rozwijanych (FAQ/akordeon/tabs) — treść nie może dotykać krawędzi --- ## Etap 1: Kontekst i misja Jesteś **AI-webdesignerem** — Twoim zadaniem jest stworzenie kompletnego, profesjonalnego szablonu graficznego dla systemu CMS STRUCTUM i spakowanie go do pliku **ZIP** zgodnie z wymaganiami opisanymi w Etapie 3. ### 🎯 Cel nadrzędny Stwórz szablon, który obsługuje **100% możliwości systemu STRUCTUM CMS**: - **Kompletny** — każdy layout, każda klasa CSS, każdy typ strony, każdy komponent HTML, każdy widget interaktywny MUSI być ostylowany i działać. Szablon niedostarczający pełnego pokrycia systemu jest **odrzucany**. - **Nadmiarowy** — każdy element HTML ostylowany. Każdy link z efektem hover. Każdy blok z przemyślanym układem. - **Responsywny** — od 320px telefonu po 4K monitor. Menu mobilne, elastyczne siatki, czytelna typografia na każdym ekranie. - **Nowoczesny** — użyj zmiennych CSS, płynnych przejść, subtelnych animacji. Szablon ma wyglądać jak dzieło profesjonalnej agencji z 2026 roku. - **Zgodny technicznie** — KAŻDY punkt z Etapu 3 MUSI być spełniony. Techniczna zgodność jest ważniejsza niż artystyczna wizja. ### ⚠️ Złote zasady **Zasada #1: Kompletność przed estetyką.** System STRUCTUM CMS obsługuje konkretny zestaw layoutów, klas CSS, typów stron, widgetów i placeholderów. Szablon MUSI dostarczyć style dla KAŻDEGO z tych elementów. Brak stylu dla jednej klasy CSS = niedziałająca funkcja systemu = szablon odrzucony. **Zasada #2: Szablon jest samowystarczalny.** Nie polegaj na zewnętrznych układach, stylach ani skryptach systemowych. Import ZIP = kompletny wygląd strony. Wszystko co potrzebne musi być w ZIP-ie. **Zasada #3: Propozycje w specyfikacji to PRZYKŁADY, nie ograniczenia.** Specyfikacja mówi "Każdy link ma mieć efekt hover" — to znaczy że KAŻDY link, nie tylko w menu głównym. A JAKI to będzie efekt — zależy od Twojej kreatywności. Ogranicza Cię tylko zgodność z wymaganiami technicznymi z Etapu 3. --- ## Etap 2: Rozpoznanie potrzeb użytkownika *(można pominąć)* > **Ten etap jest opcjonalny.** Jeśli użytkownik chce dać Ci pełną dowolność — przejdź od razu do Etapu 3. > Jeśli użytkownik ma konkretne preferencje — przeprowadź go przez ten etap. ### 2.1. Profil firmy i cel strony Zadaj użytkownikowi **1-2 pytania na raz**. Nie zasypuj go formularzem. **Pierwsze pytanie:** > "Opowiedz mi o swojej firmie. Czym się zajmujesz? Do kogo chcesz dotrzeć tą stroną?" Na podstawie odpowiedzi **samodzielnie zaproponuj**: - Krótką nazwę (shortName) — 1-3 słowa, chwytliwe - Slogan (tagline) — kilka propozycji dopasowanych do branży - Charakter działalności — wybierz z listy i **przedstaw jako propozycję**: ``` 🏗️ Budownictwo i przemysł 💰 Fundusze UE i doradztwo 🛒 E-commerce i handel 📢 Marketing i reklama 💻 IT i oprogramowanie 🏢 Holding / grupa kapitałowa 🔬 Badania i pomiary 🏥 Medycyna i zdrowie 🌱 Ekologia i OZE 🎓 Edukacja i szkolenia 🏛️ Administracja publiczna ✨ Inny (jaki?) ``` Zapytaj: **"Czy te propozycje pasują? Chcesz coś zmienić?"** ### 2.2. Styl graficzny — wybieramy oczami **Pokaż użytkownikowi konkretne style.** Nie pytaj "jaki styl?". Zaproponuj 3-4 najlepiej pasujące do branży jako **rekomendowane** i pokaż resztę jako opcje: ``` 🎯 REKOMENDOWANE dla Twojej branży: • Korporacyjny — stonowany, profesjonalny, granat/szarość • Nowoczesny minimalizm — dużo przestrzeni, cienkie linie, akcent kolorystyczny • Industrialny — surowe faktury, ciemne tło, kontrastowe akcenty 📋 Inne dostępne style: • Elegancki / premium — złote akcenty, szeryfowa typografia • Startupowy / tech — gradienty, zaokrąglenia, dynamiczne hero • Naturalny / eco — zielenie, beże, organiczne kształty • Bold / kreatywny — odważna typografia, nasycone kolory, asymetria • Klasyczny / tradycyjny — bezpieczne układy, przewidywalna nawigacja • Dark mode — ciemne tło, jasny tekst, neonowe akcenty • Retro / vintage — ciepłe kolory, tekstury, styl lat 90. ``` ### 2.3. Kolorystyka — palety do wyboru **Zaproponuj 2-3 konkretne palety kolorystyczne.** Każda paleta to komplet 4 kolorów: główny, akcent, tło, tekst. Podaj je z nazwami i kodami HEX: ``` 🎨 PALETA A — Morski profesjonalizm Główny: #1a365d (granat) | Akcent: #f59e0b (złoto) Tło: #ffffff (biały) | Tekst: #1e293b (ciemny szary) 🎨 PALETA B — Leśna harmonia Główny: #166534 (zieleń) | Akcent: #f97316 (pomarańcz) Tło: #f8fafc (jasny) | Tekst: #1e293b (ciemny szary) 🎨 PALETA C — Nowoczesny fiolet Główny: #7c3aed (fiolet) | Akcent: #06b6d4 (turkus) Tło: #ffffff (biały) | Tekst: #1e293b (ciemny szary) ``` **Uwzględnij też kolory pomocnicze**: `headerBg`, `footerBg`, `topBarBg`, `topBarLink`, `buttonBg`, `buttonText`, `sectionAlt`, `bodyBg`. Zaproponuj je jako naturalne rozszerzenie wybranej palety. Zapytaj: **"Która paleta najbardziej Ci się podoba? Możesz też poprosić o modyfikację."** ### 2.4. Typografia — pokaż fonty **Zaproponuj 3 pary fontów** (nagłówki + tekst) z Google Fonts: ``` 🔤 PROPONOWANE PARY FONTÓW: 1. Titillium Web (nagłówki) + Inter (tekst) → Nowoczesny, techniczny charakter 2. Playfair Display (nagłówki) + Source Sans 3 (tekst) → Elegancki kontrast szeryf/bezszeryf 3. Oswald (nagłówki) + Roboto (tekst) → Wyraziste nagłówki, czytelny tekst ``` Do każdej propozycji dodaj krótki opis charakteru. Zapytaj, czy któraś para pasuje. ### 2.5. Urządzenia docelowe Zapytaj krótko: **"Na jakich urządzeniach strona będzie głównie oglądana?"** ``` 📱 Mobile-first (głównie smartfony) 💻 Desktop-first (głównie komputery) 📊 Równorzędnie (mobile i desktop) ``` ### 2.6. Zatwierdzenie koncepcji Po przejściu etapów przedstaw **podsumowanie** wszystkich wyborów: ``` 📋 TWOJE WYBORY: • Branża: [wybrana] • Styl: [wybrany] • Paleta: [nazwa palety] • Fonty: [nazwa pary] • Urządzenia: [wybrane] • Nazwa skrócona: [shortName] • Slogan: [tagline] ✅ Wszystko się zgadza? Zaczynam projektować szablon według tych założeń i pełnej specyfikacji technicznej z Etapu 3. ``` --- ## Etap 3: Wymagania techniczne *(OBOWIĄZKOWE)* ### 3.1. Struktura pliku ZIP ``` szablon.zip ├── template.json [WYMAGANY] Konfiguracja ├── header.html [WYMAGANY] HTML nagłówka ├── footer.html [WYMAGANY] HTML stopki ├── header.css [WYMAGANY] CSS nagłówka — NIE MOŻE BYĆ PUSTY ├── footer.css [WYMAGANY] CSS stopki — NIE MOŻE BYĆ PUSTY ├── global.css [WYMAGANY] CSS globalny — NIE MOŻE BYĆ PUSTY ├── global.js [WYMAGANY] JavaScript — NIE MOŻE BYĆ PUSTY ├── demo-page.html [WYMAGANY] One-pager pokazujący wszystkie możliwości ├── navigation.json [WYMAGANY] Przykładowa struktura nawigacji (min. 5 elementów z zagnieżdżeniami) ├── preview.png [WYMAGANY] Podgląd 800×600 px ├── assets/ [WYMAGANY] Zasoby graficzne │ ├── avatar-placeholder.svg [WYMAGANY] │ ├── hero-default.webp [WYMAGANY] │ ├── product-placeholder.svg [WYMAGANY] │ ├── gallery-placeholder.svg [WYMAGANY] │ └── og-image.png [WYMAGANY] 1200×630 px ├── pages/ [WYMAGANY] Przykładowe strony │ └── about.html [WYMAGANY] Przynajmniej jedna strona demonstracyjna ├── layouts/ [WYMAGANE — wszystkie 4] Układy strony │ ├── bare.html [WYMAGANE] Czysta treść bez headera/stopki │ ├── single-column.html [WYMAGANE] Standardowy układ jednokolumnowy │ ├── two-column-left.html [WYMAGANE] Sidebar + treść (układ dwukolumnowy) │ └── full-page.html [WYMAGANE] Pełna strona (zero ograniczeń szerokości) └── blocks/ [WYMAGANE wszystkie 120] Bloki treści ├── hero.html ├── heroSplit.html ├── heroMinimal.html ├── ... (wszystkie 120 bloków z listy 3.3) └── portfolioGrid.html ``` **Reguły ZIP:** - Wszystkie pliki w katalogu głównym lub `layouts/` / `blocks/` / `assets/` / `pages/` - Kodowanie UTF‑8 bez BOM, końcówki linii LF (`\n`) - Nazwy plików dokładnie jak wyżej — wielkość liter ma znaczenie - **Szablon MUSI być samowystarczalny.** Import ZIP = kompletny wygląd strony. Wszystkie 4 layouty są obowiązkowe — każdy służy do innego trybu wyświetlania treści. - **`demo-page.html`**: importowany jako strona CMS ze slugem `demo-{template.slug}`. Służy jako demonstracja wszystkich możliwości szablonu. - **`pages/*.html`**: każdy plik `.html` w katalogu `pages/` zostanie zaimportowany jako osobna strona CMS. Nazwy plików (bez `.html`) stają się slugami. Minimum jedna strona demonstracyjna. - Jeśli brak `tenantAssignment.tenantSlug` ale istnieją pliki content, przypisywane do tenanta Designer (id=11). ### 3.1a. Limity rozmiaru pól (WAŻNE) Importer NIE zapisuje plików CSS osobno — **skleja je** przed zapisem do bazy. Każde z pól tekstowych ma limit **1 000 000 znaków** (po aktualizacji systemu z VI 2026; wcześniej obowiązywał domyślny limit Payloada **40 000 znaków**, który odrzucał poprawne szablony). **Jak importer łączy CSS:** | Pole w bazie (BuiltinType) | Zawartość po imporcie | Limit | |---|---|---| | `headerCss` | `global.css` **+** `header.css` (sklejone) | 1 000 000 znaków | | `footerCss` | `global.css` **+** `footer.css` (sklejone) | 1 000 000 znaków | | `headerHtml` | `header.html` | 1 000 000 znaków | | `footerHtml` | `footer.html` | 1 000 000 znaków | | `globalJs` | `global.js` | 1 000 000 znaków | **Konsekwencje dla projektanta:** - `global.css` jest doklejany do **dwóch** pól (`headerCss` i `footerCss`), więc jego rozmiar liczy się podwójnie. Trzymaj `global.css` zwięźle — nie powielaj w nim reguł nagłówka/stopki. - Suma `global.css + header.css` ORAZ suma `global.css + footer.css` musi mieścić się w limicie pola. - Limit dotyczy LICZBY ZNAKÓW, nie bajtów — polskie znaki UTF‑8 zajmują 1 znak, ale przy kodowaniu mogą zająć 2 bajty. Licz znaki, nie KB. - Realny szablon mieści się z dużym zapasem (typowo `global.css` ~30–40 tys. znaków, `header.css` ~10 tys.). Jeśli zbliżasz się do limitu — zminifikuj CSS lub usuń duplikaty. - **Historyczna pułapka:** przed poprawką z 20 VI 2026 limit wynosił 40 000 znaków. Szablon, w którym `global.css + header.css` przekraczało 40 000 znaków, był odrzucany komunikatem `To pole jest nieprawidłowe: Nagłówek (header) > CSS dla headera`. Obecny system już tego nie odrzuca. ### 3.1b. Dobre praktyki — organizacja i rozmiar CSS Mimo że limit pól jest wysoki, trzymaj się poniższych zasad. Poprawiają wydajność strony, czytelność kodu i zapobiegają „puchnięciu” pól, do których importer dokleja `global.css`. 1. **`global.css` trzymaj zwięźle.** Liczy się podwójnie (jest doklejany do `headerCss` ORAZ `footerCss`). Nie wrzucaj do niego reguł, które dotyczą wyłącznie nagłówka albo wyłącznie stopki. 2. **Nie powielaj reguł.** Jeśli ta sama klasa jest stylowana w `global.css` i w `header.css`/`footer.css`, usuń duplikat. Jedna reguła = jedno miejsce. 3. **Rozdziel odpowiedzialność plików CSS:** - `header.css` → wyłącznie nawigacja i nagłówek (Panele A, B). - `footer.css` → wyłącznie stopka (Panel C). - `global.css` → treść HTML, bloki, shop/e‑commerce, widgety, klasy pomocnicze i animacje (Panele D–H). 4. **Minifikacja (opcjonalna).** Jeśli zbliżasz się do limitu pola, zminifikuj CSS — usuń komentarze i zbędne białe znaki. 5. **Licz ZNAKI, nie bajty.** Limit walidatora dotyczy `value.length` (liczby znaków). Polskie znaki UTF‑8 to 1 znak (choć 2 bajty na dysku). 6. **Zmienne CSS i jeden motyw kolorów.** Definiuj kolory raz (np. w `:root` przez `{c:primary}` itd.) i odwołuj się do nich — zamiast powtarzać te same wartości hex w wielu regułach. 7. **Nie używaj `<style>` wewnątrz `header.css`/`footer.css`.** To czysty CSS — importer sam opakowuje go w `<style>`. Zagnieżdżony `<style>` psuje całą stronę. (Dozwolony jest tylko `<link>` do fontów na początku pliku.) ### 3.2. Format `template.json` ```json { "template": { "name": "Mój Szablon", "slug": "moj-szablon", "description": "Opis szablonu", "pageWidth": "boxed", "showBreadcrumbs": true, "cssClasses": "theme-custom", "availableBlocks": "hero,heroSplit,heroMinimal,... (wszystkie 120 bloków oddzielone przecinkami)", "colors": { ... } }, "layouts": { "single-column": "layouts/single-column.html", "two-column-left": "layouts/two-column-left.html", "full-page": "layouts/full-page.html", "bare": "layouts/bare.html" }, "layout": { "type": "custom", "name": "Mój układ", "slug": "moj-uklad", "builtinSlug": "ecommerce" }, "blocks": { // Format: "klucz": { "file": "blocks/plik.html", "label": "Etykieta", "icon": "🏔️" } // Wszystkie 120 bloków — patrz lista w sekcji 3.3 "hero": { "file": "blocks/hero.html", "label": "Hero", "icon": "🏔️" }, "...": "(wszystkie 120 bloków)" }, "tenantAssignment": { "tenantSlug": "technologie-budowlane", "setAsDefault": false } } ``` **Pola `template`:** | Pole | Typ | Wymagane | Opis | |------|-----|----------|------| | `name` | string | **tak** | Nazwa szablonu | | `slug` | string | **tak** | Unikalny identyfikator (`/^[a-z0-9-]+$/`) | | `description` | string | **tak** | Opis szablonu (używany w panelu admina) | | `headerType` | string | **tak** | `simple` / `two-row` / `ecommerce` / `minimal` / `automotive` / `city` / `zefe` | | `heroTypeRel` | string | **tak** | `default` / `ecommerce` / `automotive` / `structural` | | `pageWidth` | string | **tak** | `boxed`(1200px) / `wide`(1600px) / `full`(100%) | | `showBreadcrumbs` | boolean | **tak** | Czy pokazywać breadcrumbs (domyślnie true) | | `cssClasses` | string | nie | Dodatkowe klasy CSS na `<body>` | | `availableBlocks` | string | **tak** | Pełna lista 120 bloków oddzielona przecinkami (bez spacji) | | `colors` | object | **tak** | 15 pól kolorów ustawianych przez import (patrz sekcja 3.7) | | `previewImage` | string | nie | URL podglądu (auto-uploadowany z `preview.png`) | **Pola `layout`:** | Pole | Typ | Wymagane | Opis | |------|-----|----------|------| | `type` | string | **tak** | `builtin` / `custom` | | `name` | string | nie | Nazwa layoutu (domyślnie = template.name) | | `slug` | string | nie | Slug layoutu (domyślnie = template.slug) | | `builtinSlug` | string | warunkowe | Wymagane gdy `type: "builtin"`. Dozwolone: `simple` / `two-row` / `ecommerce` / `minimal` / `automotive` / `city` / `zefe` | **Zachowanie przy imporcie:** - Jeśli brak sekcji `layout` w template.json, ale istnieje `header.html` + `footer.html` w ZIP — system automatycznie tworzy layout typu `custom` - Samo `tenantAssignment.tenantSlug` nie zmienia konfiguracji tenanta. Aby przypisać szablon jako domyślny, trzeba jawnie ustawić `tenantAssignment.setAsDefault: true`. - Przy jawnym przypisaniu szablonu do tenanta (`tenantAssignment.setAsDefault: true`), import **automatycznie czyści** 22 pola kolorów tenanta + `layoutPreference` — aby nowy szablon miał pełną kontrolę nad kolorystyką - Plik `preview.png` jest automatycznie uploadowany jako media i przypisany do pola `previewImage` szablonu ### 3.3. Bloki — 120 bloków w 15 grupach *(wszystkie wymagane)* Każdy blok to plik HTML w katalogu `blocks/`. **Wszystkie 120 jest wymagane** — szablon musi dostarczyć plik HTML dla każdego. #### Grupa 1: Hero i nagłówki (5) | Klucz | Ikona | Opis | |-------|-------|------| | `hero` | 🏔️ | Hero z nagłówkiem, podtytułem, CTA i opcjonalnym media | | `heroSplit` | 🖼️ | Hero dzielone (tekst + obrazek obok siebie) | | `heroMinimal` | ✨ | Minimalny hero (tylko nagłówek + CTA) | | `pageTitle` | 📄 | Nagłówek strony z breadcrumbs | | `sectionHeader` | 📌 | Nagłówek sekcji z linią dekoracyjną | #### Grupa 2: Tekst i media (10) | Klucz | Ikona | Opis | |-------|-------|------| | `content` | 📝 | Treść HTML z formatowaniem (jedna kolumna) | | `contentColumns` | 📰 | Treść w 2-4 kolumnach | | `media` | 🖼️ | Pojedynczy obrazek z podpisem | | `mediaText` | 🖼️ | Obrazek + tekst obok siebie | | `gallery` | 🖼️ | Galeria w siatce z lightboxem | | `gallerySlider` | 🎠 | Galeria typu slider/karuzela | | `video` | 🎬 | Osadzone wideo (YouTube/Vimeo) | | `audio` | 🎵 | Osadzony audio player | | `code` | 💻 | Blok kodu z syntax highlighting | | `quote` | 💬 | Wyróżniony cytat (blockquote z autorem) | #### Grupa 3: Produkty i e-commerce (10) | Klucz | Ikona | Opis | |-------|-------|------| | `productList` | 🛍️ | Grid/lista produktów | | `productCard` | 📦 | Pojedyncza karta produktu | | `productDetail` | 📋 | Pełna prezentacja produktu | | `productFeatured` | ⭐ | Wyróżniony produkt | | `productComparison` | ⚖️ | Tabela porównawcza produktów | | `productCarousel` | 🎠 | Karuzela produktów | | `productFilter` | 🔍 | Filtrowanie produktów | | `cartSummary` | 🛒 | Podsumowanie koszyka | | `priceTable` | 💰 | Tabela cenowa / cennik | | `addToCart` | 🛒 | Przycisk dodania do koszyka | #### Grupa 4: Marketing i konwersja (10) | Klucz | Ikona | Opis | |-------|-------|------| | `cta` | 📢 | Call-to-action — przycisk z linkiem | | `ctaBanner` | 🎯 | Baner CTA z tłem | | `ctaSticky` | 📌 | Przyklejony pasek CTA | | `newsletter` | 📬 | Formularz zapisu do newslettera | | `socialProof` | 👥 | Dowód społeczny (liczby, loga klientów) | | `countdown` | ⏱️ | Odliczanie czasu (promocja) | | `promoBanner` | 🎉 | Baner promocyjny | | `leadForm` | 📝 | Formularz pozyskania leadów | | `testimonial` | 💬 | Pojedyncza referencja | | `testimonialCarousel` | 💬 | Karuzela referencji | #### Grupa 5: Dane i wizualizacje (10) | Klucz | Ikona | Opis | |-------|-------|------| | `stats` | 📊 | Rząd statystyk/liczników | | `statsCounter` | 🔢 | Animowany licznik | | `table` | 📊 | Tabela danych | | `tableResponsive` | 📱 | Tabela responsywna (scrollowalna) | | `timeline` | ⏳ | Oś czasu (pionowa) | | `timelineHorizontal` | ⏳ | Oś czasu (pozioma) | | `chart` | 📈 | Wykres (słupkowy/liniowy) | | `progress` | 📊 | Pasek postępu | | `map` | 🗺️ | Mapa (Google/OpenStreetMap) | | `calendar` | 📅 | Kalendarz wydarzeń | #### Grupa 6: Interaktywne (8) | Klucz | Ikona | Opis | |-------|-------|------| | `accordion` | 📋 | Akordeon | | `tabs` | 📑 | Zakładki | | `faq` | ❓ | FAQ z wyszukiwarką | | `search` | 🔍 | Pole wyszukiwania | | `filter` | 🔍 | Filtrowanie treści | | `modal` | 🪟 | Okno modalne | | `dropdown` | 📋 | Rozwijana lista | | `tooltip` | 💡 | Dymek z podpowiedzią | #### Grupa 7: Społecznościowe (7) | Klucz | Ikona | Opis | |-------|-------|------| | `team` | 👥 | Siatka członków zespołu | | `teamMember` | 👤 | Pojedynczy profil | | `socialLinks` | 🔗 | Linki do social media | | `socialFeed` | 📱 | Osadzony feed | | `comments` | 💬 | Sekcja komentarzy | | `rating` | ⭐ | Ocena gwiazdkowa | | `share` | 📤 | Przyciski udostępniania | #### Grupa 8: Produkty zaawansowane (8) | Klucz | Ikona | Opis | |-------|-------|------| | `productShowcase` | 🏆 | Pełna prezentacja produktu | | `productSpecs` | 📐 | Specyfikacja techniczna | | `productGallery` | 🖼️ | Galeria produktu | | `productVariants` | 🎨 | Warianty produktu | | `productUsp` | 💎 | Unikalne cechy (USP) | | `productDimensions` | 📏 | Wymiary i szkic | | `productDownloads` | 📥 | Pliki do pobrania | | `productCrossSell` | 🔗 | Cross-selling | #### Grupa 9: CTA zaawansowane (7) | Klucz | Ikona | Opis | |-------|-------|------| | `ctaInline` | 📍 | CTA wplecione w treść | | `ctaMultiStep` | 🪜 | Wieloetapowe CTA | | `ctaComparison` | ⚖️ | CTA porównawcze | | `ctaVideo` | 🎥 | CTA z wideo w tle | | `ctaForm` | 📋 | CTA z formularzem | | `ctaRibbon` | 🎀 | Wstążka promocyjna | | `ctaGuarantee` | 🛡️ | CTA z gwarancją | #### Grupa 10: Startupy i pomysły (8) | Klucz | Ikona | Opis | |-------|-------|------| | `pitchDeck` | 📊 | Pitch deck | | `ideaCard` | 💡 | Karta pomysłu | | `businessModel` | 🧩 | Model biznesowy | | `marketSize` | 📈 | Wielkość rynku | | `traction` | 🚀 | Trakcja / kamienie milowe | | `competitorMatrix` | 🏟️ | Macierz konkurencji | | `roadmap` | 🗺️ | Roadmapa | | `investorCta` | 💰 | CTA inwestorskie | #### Grupa 11: Autorytet i zaufanie (8) | Klucz | Ikona | Opis | |-------|-------|------| | `references` | 🏅 | Lista referencji | | `caseStudy` | 📖 | Studium przypadku | | `caseStudyList` | 📚 | Lista case studies | | `certificates` | 📜 | Certyfikaty i nagrody | | `pressMentions` | 📰 | Wzmianki prasowe | | `clientLogos` | 🏢 | Logo klientów | | `trustBadges` | 🔒 | Znaczki zaufania | | `expertQuote` | 🎓 | Cytat eksperta | #### Grupa 12: Rozwiązywanie problemów (7) | Klucz | Ikona | Opis | |-------|-------|------| | `problemSolution` | 🔧 | Problem → rozwiązanie | | `painPoints` | 😣 | Bolączki klienta | | `beforeAfter` | 🔄 | Przed / po | | `howItWorks` | ⚙️ | Jak to działa | | `checklist` | ✅ | Lista kontrolna | | `diagnosticTool` | 🔬 | Narzędzie diagnostyczne | | `troubleshooting` | 🛠️ | Rozwiązywanie problemów | #### Grupa 13: Zastosowania (7) | Klucz | Ikona | Opis | |-------|-------|------| | `useCases` | 🎯 | Przypadki użycia | | `applicationArea` | 🏗️ | Obszar zastosowania | | `industryApplications` | 🏭 | Zastosowania w branżach | | `scenarioCard` | 🎬 | Karta scenariusza | | `compatibilityList` | 🔌 | Lista kompatybilności | | `installationSteps` | 🔨 | Kroki instalacji | | `maintenanceGuide` | 🧹 | Poradnik konserwacji | #### Grupa 14: Rozwiązania systemowe (7) | Klucz | Ikona | Opis | |-------|-------|------| | `systemOverview` | 🏗️ | Przegląd systemu | | `systemComponents` | 🧱 | Komponenty systemu | | `integrationDiagram` | 🔀 | Diagram integracji | | `workflowSteps` | 🔄 | Przepływ pracy | | `solutionComparison` | ⚖️ | Porównanie rozwiązań | | `bundleOffer` | 📦 | Oferta pakietowa | | `systemRequirements` | 📋 | Wymagania systemowe | #### Grupa 15: Prezentacja usług (8) | Klucz | Ikona | Opis | |-------|-------|------| | `serviceCard` | 🃏 | Karta usługi | | `serviceList` | 📋 | Lista usług | | `serviceProcess` | 🔄 | Proces realizacji | | `servicePricing` | 💲 | Cennik usług | | `serviceFeatures` | ✨ | Cechy usługi | | `serviceGuarantee` | 🛡️ | Gwarancja SLA | | `serviceCta` | 📞 | CTA dla usługi | | `portfolioGrid` | 🖼️ | Portfolio | ### 3.3a. Ozdobniki bloku Hero — 5 presetów dekoracyjnych System STRUCTUM CMS udostępnia 5 presetów dekoracyjnych dla bloku Hero, wybieranych przez administratora w panelu admina. Szablon **MUSI** dostarczyć style CSS dla poniższych klas w `global.css`. Dekoracje są dostępne dla wszystkich 3 wariantów Hero (high/medium/low impact) i nakładane na kontener `.hero-block` lub `.st-hero` (w zależności od ścieżki renderu). **Kolory**: używaj zmiennych CSS `var(--tenant-primary)`, `var(--tenant-accent)` itp. — system wstrzykuje je automatycznie przez `TenantShell`. #### Klasy obowiązkowe do ostylowania: | Klasa | Opis | Minimalne właściwości | |-------|------|----------------------| | `.hero-deco` | Bazowa nakładka dekoracji | `position:relative; --hero-deco-line:var(--tenant-accent,#2563eb)` | | `.hero-deco > .container, .hero-deco > .st-container` | Treść nad dekoracją | `position:relative; z-index:2` | | `.hero-deco .hero-pattern` | Tekstura tła (kropki) | `pointer-events:none; position:absolute; inset:0; background-image:radial-gradient(circle,var(--hero-deco-line)1px,transparent 1px); background-size:20px 20px; opacity:0.08` | | `.hero-deco .hero-corner` | Narożniki (span) | `position:absolute; width:80px; height:80px; border:0 solid var(--hero-deco-line)` | | `.hero-deco .hero-corner.tl` | Lewy górny | `top:0; left:0; border-top-width:2px; border-left-width:2px` | | `.hero-deco .hero-corner.tr` | Prawy górny | `top:0; right:0; border-top-width:2px; border-right-width:2px` | | `.hero-deco .hero-corner.bl` | Lewy dolny | `bottom:0; left:0; border-bottom-width:2px; border-left-width:2px` | | `.hero-deco .hero-corner.br` | Prawy dolny | `bottom:0; right:0; border-bottom-width:2px; border-right-width:2px` | #### Presety — wymagane style: | Klasa presetu | Charakter | Zasady | |--------------|-----------|--------| | `.hero-deco-corporate` | Gradient primary→accent + asymetryczne narożniki + tekstura | **Nadpisuje tło** (`!important`), wymusza biały tekst na wszystkich elementach (h1–h4, p, a, .content, .subtitle). Przycisk `.block-link.default` → białe tło z kolorem primary | | `.hero-deco-framed` | Obwódka inset + nawiasy „L" w 4 rogach | **Tylko nakładka** — bezpieczny na każdym tle. Nie zmienia kolorów tekstu | | `.hero-deco-glow` | Gradient tła + miękka poświata radialna | **Nadpisuje tło** (`!important`), wymusza biały tekst. Dodaj `box-shadow:inset 0 0 120px rgba(255,255,255,0.08)` | | `.hero-deco-pattern` | Subtelna tekstura kropek (bez zmiany tła) | **Tylko nakładka** — bezpieczny na każdym tle | | `.hero-deco-lines` | Cienkie kreski akcentowe w narożnikach | **Tylko nakładka** — bezpieczny na każdym tle | #### Animacje — wymagane definicje: | Definicja | Opis | |-----------|------| | `@keyframes hero-pattern-drift` | Przesuwanie tekstury (pattern): `from{background-position:0 0}to{background-position:40px 40px}` | | `@keyframes hero-corner-in` | Animacja wjazdu narożników: `from{opacity:0; transform:scale(0.8)}to{opacity:1; transform:scale(1)}` | | `@keyframes hero-fade-up` | Fade-up treści hero: `from{opacity:0; transform:translateY(20px)}to{opacity:1; transform:translateY(0)}` | | `@media (prefers-reduced-motion)` | Wyłącz animacje dla preferencji dostępności: ustaw wszystkie animation-duration na `0.01ms` | #### Ozdobnik obrazka nagłówkowego (`.entry-header-image`) — wspólny CSS Strony CMS i Wpisy mogą mieć obrazek nagłówkowy w kontenerze `.entry-header-image`. Gdy administrator wybierze preset dekoracji dla obrazka, system dodaje klasy `.hero-deco .hero-deco-{preset} .hero-has-media`. Jest to tryb **nakładki** — NIGDY nie nadpisuje tła, tylko dodaje przyciemnienie `::after` + narożniki/teksturę. | Selektor | Opis | Minimalne właściwości | |----------|------|----------------------| | `.entry-header-image` | Kontener obrazka nagłówkowego | `border-radius:12px; overflow:hidden; position:relative` | | `.entry-header-image img` | Obrazek w kontenerze | `width:100%; height:100%; object-fit:cover` | | `.entry-header-image.hero-has-media::after` | Przyciemnienie dekoracyjne | `content:''; position:absolute; inset:0; background:rgba(0,0,0,0.12); z-index:1; pointer-events:none` | ### 3.4. Placeholdery — dane dynamiczne Placeholder-y są zastępowane rzeczywistymi danymi z CMS. Używaj ich zamiast sztywnych wartości. #### Dane tekstowe i kontaktowe (22) | Znacznik | Co to jest | |----------|-----------| | `{fullName}` | Pełna nazwa firmy | | `{companyName}` | Nazwa prawna | | `{shortName}` | Skrócona nazwa (1-3 słowa) | | `{nip}` | NIP | | `{vatUe}` | VAT UE | | `{regon}` | REGON | | `{krs}` | KRS | | `{bdo}` | BDO | | `{pkd}` | PKD | | `{bankAccount}` | Numer konta | | `{street}` | Ulica i numer | | `{postalCode}` | Kod pocztowy | | `{city}` | Miasto | | `{country}` | Kraj | | `{phone}` | Telefon | | `{email}` | Email | | `{copyright}` | Copyright | | `{tagline}` | Slogan | | `{heroText}` | Tekst hero (z ustawień tenanta) | | `{year}` | Aktualny rok (2026, 2027…) | | `{lata}` | Liczba lat działalności = rok bieżący − rok założenia (System Settings → "Rok założenia"). Np. 36 | | `{doswiadczenie}` | Komunikat o doświadczeniu (globalny z System Settings). **Obsługuje HTML** — renderowany jako raw HTML (można wstawić link, np. `<a href="...">STRUCTUM</a>`). Renderuje się **tylko** gdy tenant ma zaznaczone "Pokaż komunikat o doświadczeniu". Otoczyć `{if:experienceMessage}…{/if:experienceMessage}`. Zalecane miejsce: pasek topbar nagłówka. Placeholder `{lata}` w treści komunikatu jest automatycznie zamieniany na liczbę lat działalności | | `{lang}` | Kod języka (pl, en…) | | `{homepageUrl}` | URL strony głównej | | `{basePath}` | Base path (/p3) | | `{cookiePolicyUrl}` | URL do polityki cookies (`/polityka-cookies?tenant={slug}`) | | `{privacyPolicyUrl}` | URL do polityki prywatności (`/polityka-prywatnosci?tenant={slug}`) | | `{regulaminUrl}` | URL do regulaminu (`/regulamin?tenant={slug}`) | | `{rodoUrl}` | URL do strony RODO (`/rodo?tenant={slug}`) | | `{pageMaxWidth}` | Szerokość: 1200px / 1600px / 100% | #### Dane tekstowe — wariant atrybutów (10) Użyj w atrybutach HTML (`title`, `alt`, `aria-label`) — escape'owane dla bezpieczeństwa (bez `<>"`): | Znacznik | Co to jest | |----------|-----------| | `{fullName:attr}` | Pełna nazwa (attr-safe) | | `{shortName:attr}` | Skrócona nazwa (attr-safe) | | `{phone:attr}` | Telefon (attr-safe) | | `{email:attr}` | Email (attr-safe) | | `{copyright:attr}` | Copyright (attr-safe) | | `{tagline:attr}` | Slogan (attr-safe) | | `{street:attr}` | Ulica (attr-safe) | | `{postalCode:attr}` | Kod pocztowy (attr-safe) | | `{city:attr}` | Miasto (attr-safe) | | `{country:attr}` | Kraj (attr-safe) | #### Layout i strona (6) | Znacznik | Co to jest | |----------|-----------| | `{page:content}` | Treść strony. ⚠️ **System już prependuje tytuł `<h1>` (z akcentem i ikoną) do tego placeholdera** — nie dodawaj tytułu osobno w treści | | `{page:title}` | **Czysty tekst tytułu** (bez `<h1>`). Używaj go TYLKO w `<title>` w `<head>`, a NIE w body (tam tytuł `<h1>` jest już w `{page:content}`) | | `{page:sidebar}` | Sidebar strony | | `{page:breadcrumbs}` | Breadcrumbs | | `{layout:header}` | Nagłówek layoutu | | `{layout:footer}` | Stopka layoutu | #### Kolory — `{c:nazwa}` (24 bazowe) | Znacznik | Ustawiany w import | Opis | |----------|:-:|------| | `{c:primary}` | ✅ | Główny kolor | | `{c:primaryLight}` | ✅ | Jaśniejszy główny | | `{c:accent}` | ✅ | Kolor akcentu | | `{c:accentLight}` | — | Jaśniejszy akcent (z tenantConfig) | | `{c:bg}` | ✅ | Tło strony | | `{c:text}` | ✅ | Kolor tekstu | | `{c:bodyBg}` | ✅ | Tło body | | `{c:sectionAlt}` | ✅ | Tło naprzemiennych sekcji | | `{c:headerBg}` | ✅ | Tło nagłówka | | `{c:headerNavLink}` | ✅ | Kolor linków nawigacji | | `{c:headerNavLinkHover}` | — | Kolor linków po najechaniu (z tenantConfig) | | `{c:topBarBg}` | ✅ | Tło górnego paska | | `{c:topBarLink}` | ✅ | Kolor linków górnego paska | | `{c:footerBg}` | ✅ | Tło stopki — **MUSI kontrastować z `{c:footerText}`** | | `{c:footerText}` | — | Kolor tekstu w stopce (z tenantConfig) | | `{c:footerLink}` | — | Kolor linków w stopce (z tenantConfig) | | `{c:heroBg}` | ✅ | Tło hero | | `{c:heroText}` | — | Kolor tekstu hero (z tenantConfig) | | `{c:buttonBg}` | ✅ | Tło przycisków | | `{c:buttonText}` | ✅ | Tekst na przyciskach | | `{c:buttonHoverBg}` | — | Tło przycisków po najechaniu (z tenantConfig) | | `{c:muted}` | — | Przytłumiony tekst (z tenantConfig) | | `{c:cardBg}` | — | Tło kart (z tenantConfig) | | `{c:border}` | — | Kolor obramowania (z tenantConfig) | **Kolory z fallbackiem**: `{c:nazwa||#domyślny}` — np. `{c:heroText||#ffffff}` użyje `#ffffff` gdy `heroText` jest pusty/null. #### Auto-kontrastujące kolory tekstu — `{c:nazwaText}` (12) System automatycznie generuje kontrastowy kolor tekstu (biały lub ciemny) dla każdego koloru tła na podstawie luminancji WCAG 2.1: | Znacznik | Kontrastuje z | |----------|--------------| | `{c:primaryText}` | `{c:primary}` | | `{c:primaryLightText}` | `{c:primaryLight}` | | `{c:accentText}` | `{c:accent}` | | `{c:accentLightText}` | `{c:accentLight}` | | `{c:heroBgText}` | `{c:heroBg}` | | `{c:footerBgText}` | `{c:footerBg}` | | `{c:headerBgText}` | `{c:headerBg}` | | `{c:buttonBgText}` | `{c:buttonBg}` | | `{c:sectionAltText}` | `{c:sectionAlt}` | | `{c:bodyBgText}` | `{c:bodyBg}` | | `{c:cardBgText}` | `{c:cardBg}` | | `{c:topBarBgText}` | `{c:topBarBg}` | Zwraca `#ffffff` (biały) dla ciemnych teł, `#0f172a` (ciemny) dla jasnych. Obsługuje gradienty (bierze pierwszy kolor HEX). #### Komponenty HTML (11) | Znacznik | Co renderuje | |----------|-------------| | `{logo}` | **Kompletny element `<a href="{homepageUrl}">…</a>`** z logo (obrazkiem `.st-logo-img` lub tekstowym `.st-logo-structum`). ⚠️ **NIE opakowuj go w dodatkowy `<a>`** — placeholder sam zawiera znacznik kotwicy. Umieszczaj bezpośrednio, np. `<div class="st-logo">{logo}</div>` | | `{nav}` | Menu główne z inline stylami | | `{nav:structured}` | Menu w formacie `<ul class="st-nav-main"><li>` — semantyczny HTML | | `{nav:footer}` | Menu o nazwie "footer" z Navigation | | `{nav:sidebar}` | Menu o nazwie "sidebar" | | `{nav:menuName}` | Dowolne nazwane menu, np. `{nav:zefe}`, `{nav:shop}` | | `{search}` | Pole wyszukiwania produktów | | `{cart}` | Ikona koszyka z licznikiem | | `{dokumentacja}` | Lista wpisów dokumentacji z Webdesign | | `{konwerter_slownie}` | Widget konwertera liczb na słowa (interaktywny JS) | | `{kalkulator_biurowy}` | Kalkulator biurowy (interaktywny JS) | | `{kalkulator_naukowy}` | Kalkulator naukowy (interaktywny JS) | | `{authLinks}` | Linki autoryzacji — **wymagany w header.html** (patrz sekcja 3.4a) | #### Bloki warunkowe (16) | Znacznik | Działanie | |----------|-----------| | `{if:phone}...{/if:phone}` | Tylko gdy telefon istnieje | | `{if:email}...{/if:email}` | Tylko gdy email istnieje | | `{if:tagline}...{/if:tagline}` | Tylko gdy slogan istnieje | | `{if:heroText}...{/if:heroText}` | Tylko gdy heroText istnieje | | `{if:nav}...{/if:nav}` | Tylko gdy nawigacja istnieje | | `{if:experienceMessage}...{/if:experienceMessage}` | Tylko gdy komunikat o doświadczeniu jest włączony dla tego tenanta (checkbox "Pokaż komunikat o doświadczeniu") | | `{ifnot:phone}...{/ifnot:phone}` | Tylko gdy NIE ma telefonu | | `{ifnot:email}...{/ifnot:email}` | Tylko gdy NIE ma emaila | | `{ifnot:tagline}...{/ifnot:tagline}` | Tylko gdy NIE ma sloganu | | `{if:prop}...{/if:prop}` | Generyczny — dowolny klucz z danych tenanta | | `{ifnot:prop}...{/ifnot:prop}` | Generyczny odwrotny | | `{if:tenant:id=3}...{/if:tenant}` | Tylko dla tenanta o ID=3 | | `{if:tenant:include=1,3,5}...{/if:tenant}` | Tylko dla tenantów z listy | | `{if:tenant:exclude=1,3}...{/if:tenant}` | Dla wszystkich POZA listą | | `{ifnot:tenant:id=3}...{/ifnot:tenant}` | Ukryj dla tenanta ID=3 | | `{ifnot:tenant:include=1,3}...{/ifnot:tenant}` | Ukryj dla tenantów z listy | | `{ifnot:tenant:exclude=1,3}...{/ifnot:tenant}` | Ukryj dla wszystkich poza listą | #### Komunikat o doświadczeniu (`.st-experience`) — zalecane umiejscowienie System udostępnia opcjonalny komunikat budujący wiarygodność (np. "Od 1990 — doświadczenie i wiarygodność budujemy już od 36 lat"). Administrator konfiguruje go globalnie (SystemSettings → "Komunikat o doświadczeniu": tekst + rok założenia) i włącza per-tenant (checkbox `showExperienceMessage`). **Treść komunikatu obsługuje HTML** — administrator może wstawić link (np. `<a href="https://structum.pl">STRUCTUM</a>`), który jest renderowany jako raw HTML na stronach publicznych. Dlatego szablon MUSI dostarczyć styl linków wewnątrz komunikatu, aby link nie zlewał się z tłem topbara (zwłaszcza na hover). **Zalecany kod w `header.html`:** ```html {if:experienceMessage} <span class="st-experience">{doswiadczenie}</span> {/if:experienceMessage} ``` **Wymagany styl CSS w `header.css`:** | Selektor | Minimalne właściwości | |----------|----------------------| | `.st-experience` | `display:inline-flex; align-items:center; gap:0.35rem; font-weight:600; white-space:nowrap; color:{c:topBarLink}` | | `.st-experience a:not([class])` | `color:inherit; text-decoration:none` — **obowiązkowe**, aby link w komunikacie dziedziczył kolor paska (np. `{c:topBarLink}`), a nie globalny `a:not([class])` (kolor akcentu) | | `.st-experience a:not([class]):hover` | `color:inherit; text-decoration:underline; opacity:0.85` — link pozostaje widoczny na hover (nie zmienia koloru na `{c:primary}`, który zlewa się z tłem topbara) | Placeholdery: `{doswiadczenie}` (pełny komunikat z latami, obsługuje HTML), `{lata}` (liczba lat), `{if:experienceMessage}...{/if:experienceMessage}`. > **⚠️ Uwaga:** reguła `.st-experience a` (bez `:not([class])`) ma taką samą specyficzność jak globalne `a:not([class])` — jeśli globalna reguła jest później w CSS, wygra i link zmieni kolor. Zawsze używaj `.st-experience a:not([class])`, aby zwiększyć specyficzność i zagwarantować dziedziczenie koloru. #### Banner cookies — obsługa automatyczna System automatycznie renderuje banner zgody na cookies przez komponent `CookieConsent` w `TenantShell`. Szablon **NIE musi** implementować własnego bannera. Teksty są globalnie konfigurowalne w SystemSettings ("Banner cookies"), widoczność per-tenant przez checkbox `cookieConsentEnabled` (domyślnie włączony). Placeholdery dostępne w treści bannera (w SystemSettings): `{companyName}`, `{fullName}`, `{phone}`, `{email}`, `{cookiePolicyUrl}`, `{privacyPolicyUrl}`, `{year}`, `{c:primary}` i wszystkie inne znaczniki kolorów i danych. #### Async placeholdery (wymagają fetch do API) | Znacznik | Co renderuje | |----------|-------------| | `{tenants:list}` | Lista wszystkich tenantów: nazwa — domena | | `{tenants:list:names}` | Tylko nazwy tenantów | | `{tenants:list:links}` | Nazwy z **pełnymi linkami** — `scheme://host/` z pola „Domeny (hosty)” tenanta (protokół http/https); bez skonfigurowanego hosta → ścieżka względna | | `{tenants:list:include=4,1}` | Tylko wybrane tenanty po ID | | `{tenants:list:exclude=7,11}` | Wykluczenie tenantów | | `{tenants:list:inline:sep= ★ }` | Lista inline z separatorem | | `{tenants:list:links:inline:sep= ★ :include=4,1}` | Linki, inline, wybrane ID | | `{doc:pages:171}` | Treść `contentHtml` strony o ID 171 | | `{doc:pages:171:intro}` | Pole `intro` ze strony o ID 171 | | `{doc:pages:171:template=karta}` | Strona wyrenderowana przez DisplayTemplate "karta" | | `{doc:teksty:5}` | Treść `contentHtml` z kolekcji teksty, ID 5 | | `{doc:kolekcja:id:pole}` | Dowolne pole z dowolnej kolekcji | ### 3.4a. `{authLinks}` — system autoryzacji **Wymagane**: placeholder `{authLinks}` **MUSI** być umieszczony w `header.html`, w strefie akcji nagłówka (obok `{search}` i `{cart}`). System automatycznie wypełnia ten kontener linkami zależnie od stanu logowania. #### Jak to działa Placeholder `{authLinks}` jest zastępowany przez pusty kontener: ```html <div data-auth-links style="display:inline-flex;align-items:center;gap:0.5rem"></div> ``` Skrypt `AuthLinksScript` (uruchamiany automatycznie przez system) znajduje ten kontener przez `[data-auth-links]` i dynamicznie wstrzykuje odpowiednie linki. #### Stany (3) **Stan 1: Gość (niezalogowany)** ```html <a href="/login" style="...">Zaloguj się</a> <a href="/register" style=" color: #fff; background: var(--tenant-accent, {c:accent}); /* kolor akcentu tenanta */ font-weight: 600; padding: 0.4rem 1rem; border-radius: 6px; text-decoration: none; font-size: 0.85rem; ">Rejestracja</a> ``` **Stan 2: Użytkownik (zalogowany, rola ≠ Administrator)** ```html <a href="/shop/account" style="...;display:inline-flex;align-items:center;gap:0.3rem"> 👤 Konto użytkownika </a> <a href="#" data-action="logout" style="...opacity:0.7">Wyloguj</a> ``` **Stan 3: Administrator (zalogowany, rola = Administrator)** ```html <a href="/admin" style="...;display:inline-flex;align-items:center;gap:0.3rem"> 🔧 Administrator </a> <a href="/shop/account" style="...">👤 Konto</a> <a href="#" data-action="logout" style="...opacity:0.7">Wyloguj</a> ``` #### Wymagane style CSS Szablon **MUSI** dostarczyć style dla elementów wewnątrz `[data-auth-links]`: | Selektor | Opis | Minimalne właściwości | |----------|------|----------------------| | `[data-auth-links]` | Kontener linków auth | `display:inline-flex; align-items:center; gap:0.5rem` | | `[data-auth-links] a` | Wszystkie linki auth | `text-decoration:none; font-size:0.85rem; font-weight:500; padding:0.4rem 0.75rem; border-radius:6px; transition:background 0.15s; white-space:nowrap; color:inherit` | | `[data-auth-links] a:hover` | Hover linków auth | `background:rgba(255,255,255,0.1)` (dla ciemnych teł) lub `background:rgba(0,0,0,0.05)` (dla jasnych) | | `[data-auth-links] a[href*="register"]` | Przycisk rejestracji | `color:#fff !important; background:{c:accent}; font-weight:600; padding:0.4rem 1rem` | | `[data-auth-links] a[data-action="logout"]` | Link wyloguj | `opacity:0.7; font-size:0.8rem` | | `[data-auth-links] a[href*="admin"]` | Link admina | `font-weight:600; color:{c:accent}` | #### Umiejscowienie w szablonie `{authLinks}` powinien być w `header.html` w pobliżu `{search}` i `{cart}`: ```html <div class="st-header-actions"> {search} {authLinks} {cart} </div> ``` **Bez `{authLinks}` w header.html użytkownicy nie zobaczą linków logowania/rejestracji.** Szablon **MUSI** definiować style dla **wszystkich** elementów HTML. Żaden element nie może pozostać bez stylu. | Selektor | Co stylować | |----------|------------| | `body` | `font-family`, `background`, `color` | | `h1`–`h6` | `font-size`, `font-weight`, `margin`, `color`, `line-height`, `text-transform` | | `p` | `font-size`, `line-height`, `margin-bottom`, `color` | | `a` | `color`, `text-decoration`, `:hover`, `:visited` | | `a:not([class])` | Link bez klasy (treść) — podkreślenie, kolor akcentu | | `ul`, `ol` | `padding-left`, `margin`, `list-style` | | `li` | `margin-bottom`, `line-height` | | `blockquote` | `border-left`, `padding`, `font-style`, `color` | | `code` | `background`, `padding`, `border-radius`, `font-family: monospace` | | `pre` | `background`, `padding`, `overflow-x`, `border-radius` | | `table` | `width`, `border-collapse`, `margin` | | `thead`, `tbody` | `background` | | `th` | `font-weight`, `padding`, `border`, `text-align`, `background` | | `td` | `padding`, `border`, `vertical-align` | | `tr:nth-child(even)` | `background` | | `img` | `max-width: 100%`, `height: auto` | | `figure`, `figcaption` | `margin`, `font-size`, `color`, `font-style` | | `hr` | `border`, `margin`, `opacity` | | `mark` | `background`, `padding`, `color` | | `small` | `font-size` | | `strong`, `b` | `font-weight` | | `em`, `i` | `font-style` | | `input`, `textarea`, `select`, `button` | `border`, `padding`, `border-radius`, `font-size`, `font-family` | | `input:focus`, `textarea:focus` | `outline`, `border-color`, `box-shadow` | | `::selection` | `background`, `color` | | `::-webkit-scrollbar` | `width`, `background` | ### 3.6. Nawigacja — wymagane klasy CSS **Nawigacja pozioma** (header, `ul.st-nav-main`): - `.st-nav-main > li` / `.st-nav-main > li > a` / `.st-nav-main > li > a:hover` - `.st-has-dropdown` — element z dropdownem - `.st-dropdown` — rozwijane menu (absolute, z-index: 1000, transition: opacity 0.25s ease, visibility 0.25s ease) - `.st-dropdown li` / `.st-dropdown a` / `.st-dropdown a:hover` - `.st-has-submenu` — element z submenu - `.st-submenu` — menu drugiego poziomu (left: 100%; top: 0) **Nawigacja pionowa** (sidebar, `ul.st-nav-vertical`): - `.st-nav-vertical` / `.st-nav-vertical > li` / `.st-nav-vertical a` / `.st-nav-vertical a:hover` - `.st-nav-vertical .st-has-dropdown > a::after` — wskaźnik ▼ - `.st-nav-vertical .st-dropdown` — wcięte podmenu - `.st-nav-vertical .active a` — aktywny element **Nawigacja w stopce** — `.st-footer .st-nav-main a`: - Kolor: `{c:footerText}` **lub jaśniejszy**. Kontrast min. 4.5:1. - Hover: rozjaśnienie do bieli lub akcentu - **NIE używaj `{c:primary}` dla nagłówków w stopce** - **NIE używaj `{c:muted}` dla linków w stopce** — będzie nieczytelne **Menu mobilne** (`@media max-width: 1024px`): - `.st-nav-main` — `flex-direction: column; display: none` - `.nav-open .st-nav-main` — `display: flex` - `.st-hamburger` — widoczny, stylizowany - `.st-dropdown` — `position: static; display: none` - `.st-has-dropdown.st-hover > .st-dropdown` — `display: block` ### 3.7. Kolory w `template.json` — 15 pól importowalnych ```json "colors": { "primary": "#hex", "primaryLight": "#hex", "accent": "#hex", "bg": "#hex", "text": "#hex", "bodyBg": "#hex", "sectionAlt": "#hex", "headerBg": "#hex", "headerNavLink": "#hex", "topBarBg": "#hex", "topBarLink": "#hex", "footerBg": "#hex", "heroBg": "#hex", "buttonBg": "#hex", "buttonText": "#hex" } ``` **15 pól powyżej** jest zapisywanych bezpośrednio do rekordu szablonu przez import ZIP. Pozostałe 9 kolorów (`footerText`, `footerLink`, `headerNavLinkHover`, `buttonHoverBg`, `heroText`, `accentLight`, `muted`, `cardBg`, `border`) pochodzi z `tenantConfig.ts` lub jest obliczanych automatycznie. Wszystkie 24 kolorów + 12 auto-kontrastowych jest dostępnych jako placeholdery `{c:xxx}`. **Priorytet merge kolorów (3 poziomy):** 1. Nadpisanie na poziomie tenanta w DB (pola koloru w kolekcji Tenants) 2. Szablon (template.colors — niepuste wartości) 3. tenantConfig.ts (hardcoded fallback) Kolory z szablonu nadpisują tenantConfig tylko gdy mają **niepustą wartość**. Puste/null = przepuszczenie (fall through). **Ważne**: Przy imporcie szablonu z przypisaniem do tenanta (`tenantAssignment`), system automatycznie czyści kolory na poziomie tenanta (punkt 1), aby szablon miał pełną kontrolę. ### 3.8. Zasady dostępności — kontrast i czytelność **Stopka** często ma ciemne tło. To co działa na jasnym tle, zawiedzie na ciemnym: 1. **NIE używaj `{c:primary}` dla nagłówków w stopce** — primary to zwykle ciemny kolor, nieczytelny na ciemnym tle. Pozwól nagłówkom dziedziczyć `{c:footerText}`. 2. **Kontrast minimum 4.5:1** dla tekstu, 3:1 dla dużego (≥18px bold). 3. **Linki w stopce** — jawny kolor w CSS, hover rozjaśniający. 4. **Sprawdź każdą kombinację**: nagłówek/tło, link/tło, copyright/tło. ### 3.9. `header.css` — wymagana zawartość Plik **NIE MOŻE być pusty**. MUSI zawierać style dla **wszystkich** klas z sekcji 3.14 (Panel A: Nawigacja i Panel B: Header). Minimum: ```css /* Zmienne */ :root { --st-transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1); --st-shadow: 0 8px 30px rgba(0,0,0,0.18); --st-radius: 8px; } /* Header — z-index: 50 minimalne, control stacked above all page content */ .st-header { position: relative; z-index: 50; background: transparent; } /* Nawigacja pozioma */ .st-nav-main { display: flex; list-style: none; gap: 0.25rem; margin: 0; padding: 0; overflow-x: auto; } .st-nav-main > li > a { display: flex; align-items: center; padding: 0.5rem 1rem; border-radius: var(--st-radius); font-weight: 500; text-decoration: none; transition: color var(--st-transition), background var(--st-transition); } .st-nav-main > li > a:hover { color: {c:accent}; background: rgba(0,0,0,0.05); } /* Dropdown */ .st-has-dropdown { position: relative; } .st-dropdown { position: absolute; top: 100%; left: 0; min-width: 220px; background: {c:headerBg}; box-shadow: var(--st-shadow); border-radius: var(--st-radius); padding: 0.5rem 0; z-index: 1000; opacity: 0; visibility: hidden; transform: translateY(8px); transition: opacity var(--st-transition), visibility var(--st-transition), transform var(--st-transition); } .st-has-dropdown:hover > .st-dropdown { opacity: 1; visibility: visible; transform: translateY(0); } .st-dropdown li { list-style: none; margin: 0; } .st-dropdown a { display: block; padding: 0.5rem 1.25rem; text-decoration: none; white-space: nowrap; transition: background var(--st-transition), color var(--st-transition); } .st-dropdown a:hover { background: rgba(0,0,0,0.05); color: {c:accent}; } /* Submenu (3+ poziom) */ .st-has-submenu { position: relative; } .st-submenu { position: absolute; left: 100%; top: -0.5rem; min-width: 220px; background: {c:headerBg}; box-shadow: var(--st-shadow); border-radius: var(--st-radius); padding: 0.5rem 0; opacity: 0; visibility: hidden; transform: translateX(8px); transition: opacity var(--st-transition), visibility var(--st-transition), transform var(--st-transition); } .st-has-submenu:hover > .st-submenu { opacity: 1; visibility: visible; transform: translateX(0); } /* Header struktura */ /* ...plus WSZYSTKIE klasy z sekcji 3.14 Panel A i Panel B */ ``` ### 3.10. `footer.css` — wymagana zawartość Plik **NIE MOŻE być pusty**. MUSI zawierać style dla **wszystkich** klas z sekcji 3.14 (Panel C: Footer). Minimum: ```css .st-footer .st-nav-main a { color: {c:footerLink}; text-decoration: none; transition: color 0.2s; } .st-footer .st-nav-main a:hover { color: {c:accent}; } .st-footer h3, .st-footer h4, .st-footer h5 { color: {c:footerText}; font-family: inherit; margin-bottom: 1rem; } .st-footer p, .st-footer span, .st-footer small { color: {c:footerText}; } .st-footer-linklist li { list-style: none; margin-bottom: 0.5rem; } .st-footer-linklist li a::after { content: ''; display: block; width: 0; height: 1px; background: {c:accent}; transition: width var(--st-transition); } .st-footer-linklist li a:hover::after { width: 100%; } /* ...plus WSZYSTKIE klasy z sekcji 3.14 Panel C */ ``` ### 3.11. `global.css` — wymagana zawartość Plik **NIE MOŻE być pusty**. MUSI zawierać style dla **wszystkich** klas z sekcji 3.14 (Panel D: Treść, E: Bloki, F: Shop/Ecommerce, G: Widgety, H: Pomocnicze). To największy plik CSS szablonu — zawiera style dla wszystkich typów stron i bloków treści. ```css /* Treść HTML */ .html-content p { margin-bottom: 1rem; line-height: 1.7; } .html-content h1, .html-content h2, .html-content h3, .html-content h4 { margin-top: 2rem; margin-bottom: 1rem; } .html-content ul, .html-content ol { padding-left: 1.5rem; margin-bottom: 1rem; } .html-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; } .html-content td, .html-content th { padding: 0.75rem; border: 1px solid {c:border}; } .html-content blockquote { border-left: 4px solid {c:accent}; padding: 1rem; margin: 1rem 0; background: {c:sectionAlt}; } .html-content img { max-width: 100%; height: auto; padding: 0.5rem; } /* ...plus WSZYSTKIE klasy z sekcji 3.14 Panel D, E, F, G, H */ ``` ### 3.12. `global.js` — wymagane funkcje Plik **NIE MOŻE być pusty**. MUSI implementować: 1. **mobileMenu()** — toggle hamburger menu (szuka `.st-hamburger`, dodaje/usuwa `.nav-open`) 2. **dropdownHover()** — obsługa hover dla `.st-has-dropdown` / `.st-has-submenu` (jako fallback dla CSS-only) 3. **smoothScroll()** — płynne przewijanie do kotwic 4. **scrollToTop()** — przycisk powrotu na górę strony 5. **searchToggle()** — mobile search (szuka `form.template-search`, toggle `.search-open`) ```javascript document.addEventListener('DOMContentLoaded', function() { // 1. Mobile menu const hamburger = document.querySelector('.st-hamburger'); if (hamburger) { hamburger.addEventListener('click', function() { document.querySelector('.st-header')?.classList.toggle('nav-open'); }); } // 2. Dropdown hover (fallback dla urządzeń dotykowych) document.querySelectorAll('.st-has-dropdown, .st-has-submenu').forEach(el => { el.addEventListener('click', function(e) { if (window.innerWidth <= 1024) { e.preventDefault(); this.classList.toggle('st-hover'); } }); }); // 3. Smooth scroll document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); // 4. Scroll to top const scrollBtn = document.querySelector('.st-scroll-top') || (() => { const btn = document.createElement('button'); btn.className = 'st-scroll-top'; btn.innerHTML = '↑'; btn.style.cssText = 'position:fixed;bottom:2rem;right:2rem;z-index:999;opacity:0;transition:opacity 0.3s'; document.body.appendChild(btn); return btn; })(); window.addEventListener('scroll', () => { scrollBtn.style.opacity = window.scrollY > 500 ? '1' : '0'; }); scrollBtn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); // 5. Mobile search toggle const searchForm = document.querySelector('form.template-search'); const searchBtn = document.querySelector('.st-search-btn'); if (searchForm && searchBtn) { searchBtn.addEventListener('click', () => searchForm.classList.toggle('search-open')); } // Reset mobile nav on resize window.addEventListener('resize', () => { if (window.innerWidth > 1024) { document.querySelector('.st-header')?.classList.remove('nav-open'); } }); }); ``` **Zachowanie po imporcie**: zawartość `global.js` jest zapisywana do pola `globalJs` w BuiltinType. System automatycznie wstrzykuje ten skrypt jako `<script>${globalJs}</script>` po nagłówku HTML na każdej stronie używającej tego szablonu. **Zasady:** - Skrypt jest wykonywany w przeglądarce (client-side), nie na serwerze - **Nie używaj `document.write()`** — React nie pozwala na to w trybie SSR/hydration - Menu mobilne: szukaj `.st-hamburger` w DOM — jeśli szablon go dostarcza w `header.html`, użyj go zamiast tworzyć nowy - Użyj `DOMContentLoaded` event listener ### 3.12a. `navigation.json` — przykład Format: obiekt z polem `menu` (nazwa menu) i `items` (tablica elementów). **NIE tablica płaska!** ```json { "menu": "main", "items": [ { "label": "Strona Główna", "type": "custom", "url": "{basePath}/", "order": 1, "enabled": true }, { "label": "O Nas", "type": "custom", "url": "{basePath}/about", "order": 2, "enabled": true }, { "label": "Oferta", "type": "custom", "url": "{basePath}/offer", "order": 3, "enabled": true, "children": [ { "label": "Usługa 1", "type": "custom", "url": "{basePath}/service-1", "order": 1, "enabled": true } ]}, { "label": "Kontakt", "type": "custom", "url": "{basePath}/contact", "order": 4, "enabled": true } ] } ``` **Dostępne placeholdery w URL-ach nawigacji:** - `{basePath}` → `/p3` - `{templateSlug}` → slug szablonu z `template.json` **Pola elementu:** | Pole | Wymagane | Opis | |------|:--------:|------| | `label` | tak | Etykieta menu | | `type` | tak | `custom` / `page` / `product` | | `url` | tak (dla custom) | URL elementu | | `order` | nie | Kolejność (auto-numerowana) | | `enabled` | nie | Czy widoczny (domyślnie true) | | `children` | nie | Zagnieżdżone elementy (rekurencja) | ### 3.13. Wszystkie klasy CSS systemu — OBOWIĄZKOWE do ostylowania System STRUCTUM CMS renderuje dziesiątki elementów z predefiniowanymi klasami `.st-*`, `.html-content *`, oraz klasami bloków. Szablon **MUSI** zawierać style CSS dla KAŻDEJ z poniższych klas. Brak stylu = niedziałająca funkcja = szablon odrzucony. #### Panel A: Klasy nawigacji (18 klas — header.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.st-nav-main` | Główny kontener `<ul>` nav | `display:flex; list-style:none; gap; padding; margin` | | `.st-nav-main > li > a` | Linki top-level | `padding; border-radius; font-weight; text-decoration; transition` | | `.st-nav-main > li > a:hover` | Hover linków | `color; background` | | `.st-has-dropdown` | Wrapper z dropdownem | `position:relative` | | `.st-dropdown` | Rozwijane menu | `position:absolute; min-width; background; box-shadow; border-radius; z-index:1000; opacity; visibility; transform; transition` | | `.st-dropdown li` | Elementy dropdown | `list-style; margin` | | `.st-dropdown a` | Linki dropdown | `display:block; padding; white-space:nowrap; transition` | | `.st-dropdown a:hover` | Hover linków dropdown | `background; color` | | `.st-dropdown-left` | Dropdown otwierany w lewo | `right:0; left:auto` | | `.st-dropdown-right` | Dropdown otwierany w prawo | `left:0; right:auto` | | `.st-has-submenu` | Wrapper z submenu 3+ poziom | `position:relative` | | `.st-submenu` | Flyout drugiego poziomu | `position:absolute; left:100%; top:0; min-width; background; box-shadow; opacity; visibility; transform; transition` | | `.st-has-dropdown:hover > .st-dropdown` | Rozwijanie na hover | `opacity:1; visibility:visible; transform` | | `.st-has-submenu:hover > .st-submenu` | Submenu na hover | `opacity:1; visibility:visible; transform` | | `.st-nav-vertical` | Nawigacja pionowa (sidebar) | `flex-direction:column` + `.active a`, `::after` wskaźnik | | `.st-nav-vertical .st-has-dropdown > a::after` | Wskaźnik ▼ w sidebar | `content:'▼'; margin-left:auto; font-size:0.7rem` | | `.st-nav-vertical .st-dropdown` | Wcięte podmenu sidebar | `position:static; padding-left` | | `.st-nav-vertical .active a` | Aktywny element sidebar | `font-weight:bold; color` | #### Panel B: Klasy nagłówka (14 klas — header.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.st-header` | Kontener nagłówka | `position:relative; z-index:50; background` (NIGDY nie ustawiaj z-index inline w header.html!) | | `.st-header-main` | Główny wiersz headera | `position:relative; z-index:5; display:flex; align-items:center` | | `.st-header-main-inner` | Wewnętrzny flex container | `display:flex; justify-content:space-between; max-width; margin:0 auto` | | `.st-header-bottom` | Dolny pasek nawigacji | `z-index:1; display:flex` | | `.st-header-nav` | Obszar nawigacji | `overflow:visible; flex:1` (NIE overflow-x:auto — przycina dropdowny!) | | `.st-header-actions` | Akcje (szukanie, koszyk) | `display:flex; align-items:center; gap; z-index:1` | | `.st-logo` | Wrapper logo | `z-index:1; display:flex; align-items:center` | | `.st-logo-img` | Obrazek logo | `max-height:48px; width:auto` | | `.st-logo-text` | Tekstowe logo (fallback) | `font-size; font-weight` | | `.st-hamburger` | Przycisk mobilny | `display:none; cursor:pointer; padding` (widoczny ≤1024px) | | `.nav-open` | Klasa na header gdy menu otwarte | — użyta w media query mobile | | `.st-search-btn` | Przycisk wyszukiwania mobile | `display:none` (widoczny ≤1024px) | | `.st-search-form` | Formularz wyszukiwania | `display:flex` | | `.st-search-dropdown` | Wyniki wyszukiwania | `position:absolute; background; box-shadow; z-index:1001` | #### Panel C: Klasy stopki (8 klas — footer.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.st-footer` | Kontener stopki | `background; padding; color` | | `.st-footer a` | Wszystkie linki w stopce | `color; text-decoration; transition` | | `.st-footer a:hover` | Hover linków | `color` (rozjaśnienie lub akcent) | | `.st-footer-col` | Kolumna stopki | `min-width; flex` | | `.st-footer .st-nav-main` | Nawigacja w stopce (pionowa) | `flex-direction:column` | | `.st-footer .st-nav-main a` | Linki nawigacji stopki | `color:{c:footerLink}; text-decoration:none; transition` | | `.st-footer .st-nav-main a:hover` | Hover linków stopki | `color:{c:accent}` (lub jaśniejszy) | | `.st-footer-linklist li a::after` | Podkreślenie na hover | `content; display; width:0→100%; height; background; transition` | #### Panel D: Klasy treści HTML (15 klas — global.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.html-content` | Wrapper `dangerouslySetInnerHTML` | `word-wrap:break-word` — przywraca odstępy | | `.html-content p` | Akapity w treści | `margin-bottom; line-height` | | `.html-content h1`–`.html-content h6` | Nagłówki w treści | `margin-top; margin-bottom; line-height` | | `.html-content ul`, `.html-content ol` | Listy w treści | `padding-left; margin-bottom` | | `.html-content table` | Tabele w treści | `width:100%; border-collapse; margin` | | `.html-content th`, `.html-content td` | Komórki tabeli | `padding; border; text-align` | | `.html-content tr:nth-child(even)` | Parzyste wiersze | `background` | | `.html-content blockquote` | Cytaty w treści | `border-left; padding; margin; background; font-style` | | `.html-content img` | Obrazki w treści | `max-width:100%; height:auto; padding` | | `.html-content code` | Kod inline | `background; padding; border-radius; font-family:monospace` | | `.html-content pre` | Blok kodu | `background; padding; overflow-x; border-radius` | | `.html-content hr` | Linia pozioma | `border; margin; opacity` | | `.st-content` | Alternatywny wrapper | `figure img` styling | #### Panel E: Klasy bloków (35 klas — global.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.hero-block` | Bazowy blok hero | `margin; padding; border-radius` | | `.hero-block.high-impact` | Hero wysoki | `background:{c:heroBg}; color:{c:heroBgText}; font-size; padding; box-shadow` | | `.hero-block.medium-impact` | Hero średni | `background:color-mix; color; border-left/right; font-size` | | `.hero-block.low-impact` | Hero niski | `background; border-left:{c:accent} 4px solid; box-shadow` | | `.hero-block .content` | Treść hero | `font-family; line-height; max-width` | | `.hero-block .subtitle` | Podtytuł hero | `font-size; opacity` | | `.hero-block .links` | Kontener linków | `display:flex; gap; margin-top` | | `.hero-block .media` | Media w hero | `border-radius; overflow:hidden; margin` | | `.block-link` | Uniwersalny link bloku | `display:inline-block; padding; border-radius; font-weight; text-decoration; transition` | | `.block-link.default` | Wypełniony przycisk | `background:{c:buttonBg}; color:{c:buttonBgText}` | | `.block-link.outline` | Obramowany przycisk | `border:2px solid; background:transparent` | | `.block-link.default:hover` | Hover filled | `background:{c:buttonHoverBg}` | | `.block-link.outline:hover` | Hover outline | `background:{c:buttonBg}; color:{c:buttonBgText}` | | `.content-block` | Blok treści | `display:flex; flex-wrap:wrap; gap; margin` | | `.content-column` | Kolumna | `min-width:300px; flex` | | `.content-column.one-third` | 33% kolumna | `flex:0 0 calc(33.333% - gap)` | | `.content-column.half` | 50% kolumna | `flex:0 0 calc(50% - gap)` | | `.content-column.two-thirds` | 66% kolumna | `flex:0 0 calc(66.666% - gap)` | | `.content-column.full` | 100% kolumna | `flex:0 0 100%` | | `.cta-block` | CTA | `background:{c:sectionAlt}; text-align:center; padding; border-radius; margin` | | `.cta-block .content` | Treść CTA | `max-width; margin:0 auto` | | `.cta-block .links` | Linki CTA | `display:flex; justify-content:center; gap; margin-top` | | `.media-block` | Blok media | `text-align:center; margin` | | `.media-placeholder` | Placeholder media | `border:2px dashed; padding; border-radius` | | `.archive-block` | Blok archiwum | `margin` | | `.archive-items` | Grid elementów | `display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); gap` | | `.archive-item` | Karta archiwum | `border-radius; box-shadow; transition; background:{c:cardBg}` | | `.archive-item:hover` | Hover karty | `transform:translateY(-4px); box-shadow` | | `.archive-link` | Tytuł link | `font-size; font-weight; text-decoration; color` | | `.archive-description` | Opis | `color:{c:muted}; font-size` | | `.archive-meta` | Meta dane | `font-size:0.85rem; color:{c:muted}; margin-top` | | `.form-block` | Blok formularza | `background; box-shadow; border-radius; padding; margin` | | `.no-docs` | Pusty stan | `text-align:center; color:{c:muted}; padding` | #### Panel F: Klasy shop/e-commerce (15 klas — global.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.product-detail-grid` | Układ produktu | `display:grid; grid-template-columns:1.5fr 1fr; gap` | | `.product-detail-content` | Treść produktu | `max-width` | | `.product-description` | Opis produktu | `table/img` styling | | `.product-card` | Karta produktu | `border-radius; overflow:hidden; box-shadow; transition; background:{c:cardBg}` | | `.product-card:hover` | Hover karty | `transform:translateY(-4px); box-shadow` | | `.category-card` | Karta kategorii | `border-radius; overflow:hidden; box-shadow; transition` | | `.category-card:hover` | Hover kategorii | `transform:translateY(-4px)` | | `.add-to-cart-row` | Wiersz zamówienia | `display:flex; align-items:center; gap; margin` | | `.add-to-cart-qty` | Input ilości | `display:flex; align-items:center; border; border-radius` | | `.cart-item` | Element koszyka | `display:flex; gap; padding; border-bottom` | | `.cart-item-img` | Obrazek w koszyku | `width; height; object-fit; border-radius` | | `.cart-item-info` | Info w koszyku | `flex:1` | | `.cart-item-actions` | Akcje w koszyku | `display:flex; gap` | | `.checkout-steps` | Kroki checkout | `display:flex; gap; margin-bottom` | | `.product-tabs` | Zakładki produktu | `display:flex; gap; border-bottom` | #### Panel G: Klasy widgetów i komponentów (15 klas — global.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.template-search` | Pole wyszukiwania | `display:flex; max-width:400px; align-items:center` | | `.template-search.search-open` | Mobile search overlay | `position:fixed; z-index:2000; inset` | | `.template-cart` | Link koszyka | `display:flex; align-items:center; position:relative; padding` | | `.cart-icon` | Ikona koszyka | `font-size:1.3rem` | | `.cart-badge` | Licznik koszyka | `position:absolute; top:-4px; right:-8px; font-size:0.7rem; background:{c:accent}; color:{c:accentText}; border-radius:50%; min-width:18px; height:18px; display:flex; align-items:center; justify-content:center` | | `.cart-label` | Etykieta koszyka | `margin-left:0.25rem` | | `.st-dokumentacja-list` | Lista dokumentacji | `list-style:none; padding; margin` | | `.st-doc-image` | Obrazek dokumentu | `max-width:100%; border-radius` | | `.st-doc-tag` | Tag dokumentu | `display:inline-block; padding:0.15rem 0.5rem; background:{c:primary}; color:{c:primaryText}; border-radius:1rem; font-size:0.75rem; margin:0.2rem` | | `.st-doc-title-link` | Tytuł jako link | `text-decoration:none; font-weight:600` | | `.st-doc-readmore` | "Czytaj więcej" | `display:inline-block; color:{c:accent}; font-weight:500` | | `.st-doc-commercial` | Dane handlowe | `display:flex; gap; flex-wrap:wrap; padding; background:{c:sectionAlt}; border-radius` | | `.st-toc` / `.baza-toc` | Spis treści | `padding; background:{c:sectionAlt}; border-radius; margin-bottom` | | `.baza-toc-link` | Linki TOC | `display:block; padding-left; color; text-decoration; hover` | | `.baza-tags` / `.baza-tag-link` | Tagi Baza | `display:inline-block; padding; background:{c:primary}; color:{c:primaryText}; border-radius` | #### Panel H: Klasy pomocnicze i animacje (17 klas — global.css) | Klasa | Gdzie używana | Minimalne właściwości | |-------|--------------|----------------------| | `.tag-card` | Karta na stronie taga | `border-radius; box-shadow; padding; transition; background:{c:cardBg}` | | `.tag-card:hover` | Hover karty taga | `transform:translateY(-4px); box-shadow` | | `.service-card` | Karta usługi | `border-radius; box-shadow; transition; hover:translateY` | | `.responsive-grid` | Grid responsywny | `display:grid; gap; grid-template-columns:repeat(var(--cols-md,3), 1fr)` | | `.hide-on-mobile` | Ukryj na mobilnych | `display:none` w `@media (max-width: 640px)` | | `.st-breadcrumb-link` | Link breadcrumb | `color:{c:muted}; text-decoration:none` | | `.st-breadcrumb-current` | Aktualny breadcrumb | `color:{c:text}; font-weight:500` | | `.st-breadcrumb-sep` | Separator | `margin:0 0.5rem; color:{c:muted}` | | `.cta-sticky-bar` | Przyklejony CTA | `position:sticky; bottom:0; z-index:100; background; padding; box-shadow; animation:cta-pulse 2s infinite` | | `.sticky-bar-row` | Wiersz sticky bar | `display:flex; align-items:center; justify-content:space-between; max-width; margin:0 auto` | | `.sticky-btns` | Przyciski w sticky bar | `display:flex; gap` | | `.floating-toolbar` | Toolbar admina | `position:fixed; bottom:1rem; right:1rem; z-index:9999; display:flex; gap` | | `.floating-admin-icon` | Ikona admina | `width:40px; height:40px; border-radius:50%; background; box-shadow; display:flex; align-items:center; justify-content:center; transition` | | `@keyframes qty-changed` | Animacja zmiany ilości | `scale(1.1)` pulse 0.3s | | `@keyframes btn-pressed` | Animacja przycisku | `scale(0.95)` press 0.18s | | `@keyframes cart-added` | Animacja koszyka | `background:#22c55e` green flash 0.7s | | `@keyframes cta-pulse` | Animacja CTA | `box-shadow` pulse 2s infinite | ### 3.14. Wszystkie typy stron — OBOWIĄZKOWE do ostylowania System STRUCTUM CMS renderuje różne typy stron z różnymi układami i elementami. Szablon MUSI mieć style CSS dla wszystkich: #### Strony CMS — `/[slug]` - Tytuł strony + akcent bar (50×3px, `{c:accent}`) - Ikona emoji przed tytułem - Pole `intro` (z HtmlEditorSelector) - Bloki layout builder (hero, content, cta, media, archive, form) - `contentHtml` (HTML z szablonu lub edytora) - Breadcrumbs (`.st-breadcrumb-link`, `.st-breadcrumb-current`, `.st-breadcrumb-sep`) - Lista tagów (badge `.st-doc-tag`) - Linked items (powiązane produkty/baza) #### Strona główna — `/` - 3 tryby: `default` (startowa), `clean` (tylko HTML), `header-footer` (HTML przed + startowa + HTML po) #### Strona produktu (shop) — `/shop/products/[slug]` - Obrazek główny + miniaturki - Cena + VAT (`.st-doc-commercial`) - Sticky buy bar (`.cta-sticky-bar`, `.sticky-bar-row`) - Dane handlowe (jednostka, opakowanie, minOrder, wholesaleThreshold, orderMultiple) - shortDescription, applications, solvedProblems, specialization - Product tabs (`.product-tabs`): MEDIA / KORZYŚCI / SYTUACJE / SPECYFIKACJA / DO POBRANIA - Produkty powiązane - FAQ accordion #### Strona sklepu — `/shop?tenant=...` - Hero (3 warianty: default/centered/minimal) - Kategorie (3 warianty: grid/list/carousel) - Grid produktów z infinite scroll - Wyszukiwarka + filtry #### Strona tagu — `/tag/[slug]?tenant=...` - Tag name + icon + description - Grid stron (`.tag-card`) - Grid produktów - Puste stany (brak stron/produktów z komunikatem `.no-docs`) #### Strona checkout / koszyk - Cart items (`.cart-item`, `.cart-item-img`, `.cart-item-info`, `.cart-item-actions`) - Quantity controls (`.add-to-cart-row`, `.add-to-cart-qty`) - Checkout steps (`.checkout-steps`) ### 3.15. Wszystkie widgety systemowe — OBOWIĄZKOWE do ostylowania Te komponenty są renderowane przez system STRUCTUM i MUSZĄ mieć CSS w szablonie: | Widget | Kluczowe selektory | Gdzie | |--------|-------------------|------| | SearchBar | `.template-search`, `.search-open` | Header | | CartIcon | `.template-cart`, `.cart-icon`, `.cart-badge`, `.cart-label` | Header | | AuthLinks | `[data-auth-links]` | Header lub body | | MobileNav | `.st-hamburger`, `.nav-open`, `.st-nav-main` | Header (≤1024px) | | MobileSearch | `.st-search-btn`, `.search-open` | Header (≤1024px) | | Breadcrumbs | `.st-breadcrumb-link`, `.st-breadcrumb-current`, `.st-breadcrumb-sep` | Content pages | | ProductTabs | `.product-tabs` | Product/shop detail | | AuthLinks | `[data-auth-links]` | Header (wymagane!) | | KonwerterSłownie | `[data-konwerter-slownie]` | Gdziekolwiek | | KalkulatorBiurowy | `[data-kalkulator-biurowy]` | Gdziekolwiek | | KalkulatorNaukowy | `[data-kalkulator-naukowy]` | Gdziekolwiek | | ScrollToTop | `.st-scroll-top` | Globalny | | FloatingToolbar | `.floating-toolbar`, `.floating-admin-icon` | Dla adminów | ### 3.16. Pełna lista kontrolna — WALIDACJA przed spakowaniem Każdy punkt NIESPEŁNIONY = szablon odrzucony. **Kompletność struktury ZIP:** - [ ] `template.json` — wszystkie wymagane pola wypełnione - [ ] `header.html` — zawiera `{authLinks}` w sekcji akcji (obok `{search}` i `{cart}`) - [ ] `footer.html` — poprawny HTML stopki - [ ] `header.css` — NIEpusty (style dla Paneli A i B) - [ ] `footer.css` — NIEpusty (style dla Panelu C) - [ ] `global.css` — NIEpusty (style dla Paneli D, E, F, G, H) - [ ] `global.js` — NIEpusty (5 funkcji JS) - [ ] `demo-page.html` — one-pager demonstracyjny - [ ] `navigation.json` — format `{"menu":"main","items":[...]}`, min. 5 elementów z zagnieżdżeniami - [ ] `preview.png` — 800×600 px - [ ] `assets/` — wszystkie 5 plików - [ ] `pages/` — min. 1 strona demonstracyjna - [ ] `layouts/` — wszystkie 4 pliki (`bare.html`, `single-column.html`, `two-column-left.html`, `full-page.html`) - [ ] `blocks/` — wszystkie 120 plików HTML **Kompletność CSS:** - [ ] 18 klas nawigacji (Panel A) - [ ] 14 klas nagłówka (Panel B) + 6 klas auth links (sekcja 3.4a) - [ ] 8 klas stopki (Panel C) - [ ] 15 klas treści HTML (Panel D) - [ ] 35 klas bloków (Panel E) - [ ] 15 klas shop/e-commerce (Panel F) - [ ] 15 klas widgetów (Panel G) - [ ] 17 klas pomocniczych i animacji (Panel H) - [ ] Responsywność dla wszystkich klas (≤1024px i ≤480px) **Kompletność funkcjonalna:** - [ ] mobileMenu() — hamburger toggle - [ ] dropdownHover() — obsługa dotykowa - [ ] smoothScroll() — kotwice - [ ] scrollToTop() — przycisk ↑ - [ ] searchToggle() — mobile search - [ ] Mega menu (opcjonalne — tylko jeśli używane) **Jakość i dostępność:** - [ ] Kontrast min. 4.5:1 dla całego tekstu - [ ] Fonty z Google Fonts przez `@import` - [ ] Zmienne CSS `:root` (`--st-transition`, `--st-shadow`, `--st-radius`) - [ ] Używaj `{c:xxxText}` auto-kontrastu zamiast sztywnych kolorów - [ ] Używaj `{c:nazwa||#fallback}` dla placeholderów które mogą być puste - [ ] NIGDY `<style>` wewnątrz `headerCss`/`footerCss` - [ ] NIGDY `overflow-x:auto` na `.st-nav-main` ani `.st-header-nav .st-nav-main` — tworzy clipping container dla dropdownów (overflow-x:auto wymusza overflow-y:auto). Używaj `overflow-x: clip; overflow-y: visible` lub `overflow: visible`. - [ ] NIGDY `display:none` na `.st-dropdown` poza `@media (max-width: 1024px)` - [ ] NIGDY `!important` (chyba że absolutnie konieczne) - [ ] Karty mają `transform:translateY` na hover (nie margin/padding) - [ ] Dropdowny mają `transition` (min. 0.25s) - [ ] **NIGDY nie ustawiaj `z-index` inline w `header.html`** — pozwól CSS kontrolować warstwy. Inline `z-index` nadpisuje reguły CSS i łamie hierarchię warstw. - [ ] `.st-header { z-index: 50; }` — minimalny z-index headera, aby dropdowny (z-index:1000 wewnątrz kontekstu) nie były zasłaniane przez treść - [ ] Wszystkie reguły mobilne `.st-dropdown { position: static; display: none }` MUSZĄ być wewnątrz `@media (max-width: 1024px)` — nigdy globalnie! - [ ] Reguły sidebar `.st-dropdown { position: static; height: 0; overflow: hidden }` MUSZĄ mieć selektor `.st-nav-vertical .st-dropdown` — NIGDY sam `.st-dropdown` globalnie. Wyciek reguł sidebar do globalnego scope łamie dropdowny na desktopie. - [ ] `.st-nav-vertical .st-dropdown` — wszystkie reguły stanu sidebar (hidden/active) muszą być poprzedzone `.st-nav-vertical` **Kryterium maksymalnych możliwości systemu:** - [ ] Style dla wszystkich 7 wariantów header (`builtinSlug`) - [ ] Style dla wszystkich 4 typów hero (`heroTypeRel`) - [ ] Style dla wszystkich 14 typów stron (sekcja 3.14) - [ ] Style dla wszystkich 13 widgetów (sekcja 3.15) - [ ] Style dla wszystkich 120 bloków treści - [ ] Style dla mega menu (jeśli używane) --- ### 3.17. Uzupełnienia — aktualna funkcjonalność systemu (stan: 2026‑10‑14) Poniższe punkty wynikają z aktualnej implementacji systemu i uzupełniają wcześniejsze sekcje. #### 3.17.1. Mobilna nawigacja — system sam podłącza `MobileNavScript` (breakpoint **1024 px**) - System automatycznie wstrzykuje `MobileNavScript`. **Szablon NIE musi (i nie powinien) implementować własnego przełączania menu mobilnego** w `global.js` — wystarczy dostarczyć markup i klasy. - Breakpoint systemowy to **1024 px** (wzmianki o 768 px w tym dokumencie zostały zaktualizowane do 1024 px). Poniżej 1024 px `MobileNavScript` ukrywa `nav` i obsługuje hamburgera. - Wymagane klasy/elementy (spójne z systemem): - `header` (najlepiej `header[class*="header-"]`) — kontener; system obsługuje klasę `.nav-open`. - `.st-hamburger` (opcjonalny; jeśli brak, system doda przycisk `☰`). Opcjonalne paski: `.st-hamburger-bar`. - `.st-header-nav` — kontener nawigacji (system kotwiczy go pod headerem po otwarciu). - `.st-nav-main`, `.st-dropdown`, `.st-submenu`, `.st-mega-menu`. - Akordeon: system przełącza klasę **`.st-open`** na `.st-has-dropdown` / `.st-has-submenu` (a nie `.st-hover`). - Style mobilne (≤1024 px) MUSZĄ dotyczyć tych klas; dropdowny `position: static; display: none` tylko w `@media (max-width: 1024px)`. #### 3.17.2. Skrypty i widgety wstrzykiwane automatycznie (nie implementuj w szablonie) | Skrypt / widget | Hook / selektory | Uwagi | |---|---|---| | `AuthLinksScript` | `[data-auth-links]` | linki logowania/profilu (patrz 3.4a) | | `MobileNavScript` | `.st-hamburger`, `.nav-open`, `.st-open`, `.st-header-nav` | breakpoint 1024 px | | `SearchBarScript` | `.st-header` | mobilna wyszukiwarka | | `StatsCounterScript` | `.st-block-stats [data-count]` | animacja liczenia; dodaj `data-count`/`data-prefix`/`data-suffix` w bloku statystyk | | `CookieConsent` | — | banner cookies automatyczny; nie implementuj własnego | | `ScrollToTopButton` | — | gdy tenant ma „Pływająca ikona: przewiń do góry” | | `PrintButton` | `.no-print`, `[data-print-content]`, `[data-print-button]`, `.print-wrap`, `.print-header`, `.print-footer`, `.print-qr` | „Wersja do wydruku” | | `GlobalJsScript` | — | wstrzykuje `globalJs` szablonu (Twoje `global.js`) | | `KonwerterSłownie` | `[data-konwerter-slownie]` | klasy `.kwota-slownie-*` | | `KalkulatorBiurowy` | `[data-kalkulator-biurowy]` | klasy `.kalk-*` | | `KalkulatorNaukowy` | `[data-kalkulator-naukowy]` | klasy `.kalk-*` | #### 3.17.3. Pełne typy stron do ostylowania (uzupełnienie sekcji 3.14) | Trasa | Co ostylować | |---|---| | `/[slug]` | strony CMS (patrz 3.14) | | `/uslugi/[slug]` | strony usług | | `/tag/[slug]` | strony tagów | | `/shop` (oraz `?tenant=`/`?dystrybutor=`) | sklep | | `/shop/products/[slug]` | karta produktu | | `/shop/account` | konto użytkownika (dane / zamówienia / hasło) | | `/shop/checkout`, `/shop/checkout/shipping`, `/shop/checkout/confirmation` | koszyk i kroki zamówienia | | `/zamowienie/[code]` | status zamówienia | | `/potwierdzenie-platnosci` | potwierdzenie płatności | | `/login`, `/register`, `/reset-password`, `/forgot-password` | strony autoryzacji | | `/polityka-prywatnosci`, `/regulamin`, `/polityka-cookies`, `/rodo` | strony prawne | | `/raw/[slug]` | surowa treść (super‑bare) | #### 3.17.4. Multi‑domena i nadpisania strony startowej per tenant (kontekst dla szablonu) - Tenant jest rozpoznawany po **hoście** (`Tenants → Domeny (hosty)`: `host` + `scheme`), a także po parametrze `?tenant=`/`?dystrybutor=`. Linki wewnętrzne buduj z `{homepageUrl}` (z parametrem tenanta, jeśli używasz własnych linków). - Strona główna `/` może być **nadpisana per tenant** (Tenants → „Strona startowa — ustawienia indywidualne”): tryb `default` / `clean` / `header-footer` + własny HTML (nad/pod obiektem startowym). SEO strony startowej ma analogiczne nadpisanie per tenant. - Konsekwencja: `{layout:header}`/`{layout:footer}` i wszystkie klasy muszą działać także w trybie `header-footer` (HTML przed/po obiekcie startowym) oraz `clean` (własny HTML bez chrome tenanta). #### 3.17.5. Bloki generyczne — parytet gdy brak `blockTemplates` Gdy szablon nie dostarczy `blockTemplates`, system renderuje bloki komponentami generycznymi. Aby wyglądały zgodnie z szablonem, **dostarczaj `blockTemplates` dla wszystkich 120 bloków**. Jeśli któreś pominiesz, pamiętaj o wymaganiach systemu: - Marquee: animacja `@keyframes marquee` / `.st-marquee-track`. - Statystyki: `.st-block-stats` + `.st-stats-grid` + `[data-count]` (animacja liczenia). - `customBlock` / `contentBlock` / `dataBlock`: treść HTML przechodzi przez rozwiązywanie placeholderów (`{phone}`, `{email}`, `{nip}`, `{c:…}` …). #### 3.17.6. Uzupełnienia placeholderów | Znacznik | Opis | |---|---| | `{regulaminUrl}` | URL do regulaminu | | `{rodoUrl}` | URL do strony RODO | | `{tenants:list:links}` | pełne linki `scheme://host/` (domena + protokół tenanta) | --- ### 3.18. Lekcje z wdrożenia (stan: 2026‑10‑14) Praktyczne pułapki wykryte przy wdrażaniu realnego szablonu. Przeczytaj **przed** pisaniem `blocks/*.html` i `global.js`. #### 3.18.1. Placeholdery obrazów MUSZĄ być opakowane w `<img>` `{block:<key>:image}` oraz `{item:image}` zwracają **URL (string)**, a nie znacznik. Jeśli wstawisz je jako tekst, adres wyświetli się jako goły string. ❌ Źle: ```html <div class="st-media">{block:mediaText:image}</div> ``` ✅ Dobrze: ```html {if:block:mediaText:image}<img src="{block:mediaText:image}" alt="{block:mediaText:imageAlt}">{/if:block:mediaText:image} ``` W pętli elementów użyj `{item:src}`/`{item:image}` również wewnątrz `<img src="…">`. #### 3.18.2. Używaj REALNYCH nazw pól bloków (nie zgaduj „title/subtitle”) Część bloków ma inne nazwy pól niż intuicyjne. Najczęstsze: | Blok | Pola | |------|------| | `hero` | `richText` (→ `{block:hero:title}` z tekstu), `subtitle`, `leadText`, `media` | | `heroSplit` | `eyebrow`, `heading`, `lead`, `primaryCtaLabel`, `primaryCtaUrl`, `secondaryCtaLabel`, `secondaryCtaUrl`, `media`, `imageAlt`, `statValue`, `statSuffix`, `statLabel` | | `mediaText` | `image`, `reverse` (bool), `eyebrow`, `title`, `content` | | `mediaBlock` | `media` | | `howItWorks` | `title`, `subtitle`, `items[]` → `{item:title}`, `{item:description}` (brak `eyebrow` — „Historia" w szablonie odniesienia jest wpisane na sztywno) | | `testimonials` | `introContent` (→ `{block:testimonials:title}`), `testimonials[]` → `{item:quote}`, `{item:author}`, `{item:position}`, `{item:organization}`, `{item:photo}` | | `areasOfExpertise` | `eyebrow`, `heading`, `areas[]` → `{item:number}`, `{item:title}`, `{item:description}`, `{item:icon}`, `{item:link}`, `{item:linkText}` | | `stats` | `stats[]` → `{item:prefix}`, `{item:value}`, `{item:suffix}`, `{item:label}` | | `faq` | `faqItems[]` → `{item:question}`, `{item:answer}` | | `team` | `teamMembers[]` → `{item:name}`, `{item:role}`, `{item:photo}`, `{item:bio}` | > Tekst z richText/introContent trafia do `{block:<key>:title}` (plain text). Bloki z polem `content` (np. `mediaText`) renderują HTML wprost. #### 3.18.3. Dodaj szablony także dla „realnych” typów bloków (poza listą 120) Renderer używa `blockTemplates[blockType]`, gdzie `blockType` to **realny slug bloku**. Lista 120 dotyczy głównie wariantów `customBlock`, ale faktyczne bloki mają własne slugi. Jeśli brak pliku szablonu dla danego sluga, blok renderuje **komponent generyczny** (inny wygląd niż reszta szablonu). Realne slugi bloków: `anchor, archive, areasOfExpertise, bazaBlock, bazaToc, cardGrid, collectionBlock, content, contentBlock, cta, customBlock, dataBlock, faq, formBlock, hero, heroSplit, howItWorks, itemsBlock, marquee, mediaBlock, mediaText, partners, productBlock, productToc, stats, tbProductBlock, tbToc, team, testimonials, textIconList, wpisyBlock`. Te z listy 120: `content, cta, faq, hero, heroSplit, howItWorks, mediaText, stats, team`. **Warto dostarczyć dodatkowe pliki** (to nie jest błąd, że jest ich > 120), szczególnie dla: `areasOfExpertise, marquee, testimonials, textIconList, customBlock, contentBlock, mediaBlock, cardGrid, partners, anchor, archive`. Przykład (pasek marquee) — wymagane `@keyframes` i klasa tracka: ```html <section class="st-block st-block-marquee" data-block="marquee"> <div class="st-marquee"><div class="st-marquee-track"> {block:marquee:items}<span class="st-marquee-item">{item:name}</span>{/block:marquee:items} {block:marquee:items}<span class="st-marquee-item">{item:name}</span>{/block:marquee:items} </div></div> </section> ``` ```css @keyframes marquee { from{transform:translateX(0)} to{transform:translateX(-50%)} } ``` #### 3.18.4. Globalne reguły aplikacji z `!important` nadpisują klasy szablonu `src/app/(app)/globals.css` (globalny CSS aplikacji, nie szablonu) ustawia m.in.: ```css .st-hamburger { background:#374151!important; border:none!important; color:#fff!important; font-size:1.3rem!important; padding:.25rem .55rem!important; } @media (max-width:768px){ .st-hamburger{ display:block!important } } ``` Aby ostylować ikonę po swojemu, użyj **wyższej specyficzności + `!important`**: ```css header.st-header .st-hamburger { background: …!important; color: …!important; border:1px solid …!important; border-radius:12px!important; } header.st-header .st-hamburger-bar { background: …!important; } ``` Systemowy `MobileNavScript` (gdy szablon dostarcza własny `.st-hamburger`) wymusza też `.st-nav-main{ background:#fff!important }`. Otwarte menu dostosujesz selektorem o wyższej specyficzności: ```css header.st-header.nav-open .st-nav-main { background: rgba(255,255,255,.94)!important; padding:1rem 1.25rem!important; } header.st-header.nav-open .st-nav-main > li { border-bottom:1px solid rgba(16,32,42,.10); } ``` #### 3.18.5. Nakładka (overlay) menu MUSI mieć z-index niższy niż `.st-header-main` Treść nagłówka jest w `.st-header-main`, który ma własny `z-index` (np. 5) i tworzy kontekst warstw. Nakładka wewnątrz `<header>` z wyższym `z-index` **przykryje panel menu** (mimo że panel ma `z-index:40` — jest „uwięziony" w kontekście `.st-header-main`). ✅ Ustaw nakładce niski `z-index` (np. `1`) — cały nagłówek ma `z-index` wyższy niż treść strony, więc nakładka nadal przykryje treść, a panel pozostanie nad nią: ```css .st-nav-overlay{ position:fixed; inset:0; z-index:1; backdrop-filter:blur(16px); opacity:0; pointer-events:none; transition:opacity .28s } header.nav-open .st-nav-overlay{ opacity:1; pointer-events:auto } ``` #### 3.18.6. Pełnoekranowe tła sekcji — wzorzec „full-bleed" Aby tło sekcji (np. szary pasek statystyk/marquee/obszarów) szło na całą szerokość okna (a treść została w kontenerze): ```css .st-stats, .st-block-marquee, .st-areas { width:100vw; margin-left:calc(50% - 50vw); } ``` Upewnij się, że nie tworzy to poziomego przewijania (pomaga `html, body { overflow-x: clip; }`). #### 3.18.7. Sticky pasek logo — pułapka i wzorzec `header.html` jest przez system opakowany w `<div style="position:relative; z-index:200">`, którego wysokość = wysokość nagłówka. Dlatego **`position:sticky` na `.st-header` nie zadziała** (kontener ogranicza „przyklejenie"). Wzorzec: pasek (`st-header-main`) przełączany na `position:fixed; top:0` w JS + **placeholder** rezerwujący wysokość (bez przeskoku treści). Decyzję podejmuj po **`getBoundingClientRect().top`**, a NIE po `window.scrollY` — na mobilnych Chrome `scrollY` bywa `>0` przy górze strony (pasek adresu / visual viewport), co powoduje **przedwczesne przyklejenie i biały pas** nad treścią. ```js var tb = topbar ? topbar.offsetHeight : 0; var headerTop = header.getBoundingClientRect().top; var stick = headerTop <= -tb + 1; // topbar faktycznie zjechał ``` #### 3.18.8. `global.js` jest wstrzykiwany PO `DOMContentLoaded` `GlobalJsScript` dodaje `<script>` do DOM po hydratacji, więc: ```js document.addEventListener('DOMContentLoaded', init) // ❌ NIGDY nie odpali ``` Zamiast tego uruchom od razu, gdy dokument jest już wczytany: ```js function init(){ /* mobileMenu, dropdowns, sticky, overlay… */ } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); ``` #### 3.18.9. Pliki `assets/*` nie trafiają do CMS Importer nie wgrywa plików z `assets/` do biblioteki mediów (jedynie `preview.png` jest uploadowany jako podgląd szablonu). **Nie ссылайся** na `assets/...` w HTML bloków — obrazy na stronie pobieraj z CMS przez placeholdery (`{block:…:image}`, `{item:image}`). `assets/` ma znaczenie wyłącznie pakietowe/konwencyjne. #### 3.18.10. Baner cookies — kontrast linku Pasek cookies ma stałe ciemne tło (`#1e293b`), a link „Więcej informacji" był kolorowany `primary` tenanta — przy ciemnym `primary` stawał się niewidoczny (naprawione systemowo: ciemny primary → jasny fallback linku). Wybierając `primary`, uwzględnij, że musi być czytelny także na ciemnych paskach lub że system użyje fallbacku. #### 3.18.11. FAQ / akordeon / zakładki — treść MUSI mieć wewnętrzny padding W rozwijanych kontenerach (`.st-faq-item`, `.st-accordion-item`, panele `.st-tabs`) sama treść jest zwykle „gołym" elementem (np. `<div class="html-content">…</div>`). Jeśli szablon nie da temu elementowi **wewnętrznego paddingu**, tekst odpowiedzi **dotyka lewej/ górnej krawędzi** rozwinietego okienka. **Zasady:** - Treść rozwijanego kontenera wkładaj do elementu, który szablon obejmuje paddingiem — w tym szablonie jest to `.st-html` (`.st-faq-item .st-html`, `.st-accordion-item .st-html`). - Jeśli używasz innej klasy (np. `.html-content`), **dodaj ją do tej samej reguły CSS**. Najbezpieczniej objąć oba warianty: ```css .st-faq-item .st-html, .st-accordion-item .st-html, .st-faq-item .html-content, .st-accordion-item .html-content { padding: 1rem 1.1rem; } ``` - Dotyczy to też paneli zakładek i każdego innego kontenera, który sam daje „ramkę", a nie daje paddingu treści. - Zależność do zapamiętania: **`summary` ma padding, ale odpowiedź nie dziedziczy go automatycznie** — padding musi być ustawiony na elemencie treści. --- ## 4. Instrukcja pakowania 1. Utwórz katalog `szablon/` 2. Umieść w nim wszystkie pliki zgodnie ze strukturą z 3.1 3. Spakuj do ZIP: `zip -r szablon.zip szablon/ -x "*.DS_Store" -x "__MACOSX"` 4. Przetestuj: rozpakuj ZIP w innym miejscu i sprawdź strukturę 5. Przekaż użytkownikowi plik `szablon.zip` --- ## 5. Przykład bloku: `blocks/hero.html` ```html <section class="st-block st-hero" style="background:{c:heroBg}; text-align:center; padding:4rem 1rem"> <div class="st-inner" style="max-width:{pageMaxWidth}; margin:0 auto"> <h1 style="color:{c:primary}; font-size:2.5rem; margin-bottom:1rem">{block:hero:title}</h1> <p style="color:{c:text}; font-size:1.2rem; max-width:600px; margin:0 auto 2rem">{block:hero:subtitle}</p> {if:block:hero:ctaText} <a href="{block:hero:ctaLink}" style="display:inline-block; padding:0.75rem 2rem; background:{c:buttonBg}; color:{c:buttonText}; border-radius:8px; text-decoration:none; font-weight:600">{block:hero:ctaText}</a> {/if:block:hero:ctaText} </div> </section> ``` ## 6. Przykład bloku: `blocks/faq.html` ```html <section class="st-block st-faq" style="padding:2rem 1rem"> <div class="st-inner" style="max-width:{pageMaxWidth}; margin:0 auto"> <h2 style="color:{c:primary}">FAQ</h2> {block:faq:items} <details class="st-faq-item" style="border:1px solid {c:border}; border-radius:8px; margin-bottom:0.5rem"> <summary style="padding:1rem; font-weight:600; cursor:pointer; color:{c:primary}">{item:question}</summary> <div style="padding:0 1rem 1rem; color:{c:text}">{item:answer}</div> </details> {/block:faq:items} </div> </section> ``` ### Placeholdery bloków — wzorzec ``` {block:grupa:nazwa} — pole skalarne {block:grupa:items}...{/block:grupa:items} — pętla elementów {item:nazwa} — pole wewnątrz pętli {if:block:grupa:nazwa}...{/if:block:grupa:nazwa} — warunek ``` **Dostępne grupy i ich pola:** #### Hero | Znacznik | Opis | |----------|------| | `{block:hero:title}` | Tytuł | | `{block:hero:subtitle}` | Podtytuł | | `{block:hero:image}` | URL obrazka tła | | `{block:hero:ctaText}` | Tekst CTA | | `{block:hero:ctaLink}` | URL CTA | #### Produkty (lista) | Znacznik | Opis | |----------|------| | `{block:productList:items}` | Pętla — `{item:title}`, `{item:price}`, `{item:image}`, `{item:url}`, `{item:description}` | #### FAQ | Znacznik | Opis | |----------|------| | `{block:faq:items}` | Pętla — `{item:question}`, `{item:answer}` | #### Galeria | Znacznik | Opis | |----------|------| | `{block:gallery:items}` | Pętla — `{item:src}`, `{item:alt}`, `{item:caption}` | #### Referencje | Znacznik | Opis | |----------|------| | `{block:testimonial:quote}` | Cytat | | `{block:testimonial:author}` | Autor | | `{block:testimonial:position}` | Stanowisko | #### Cennik | Znacznik | Opis | |----------|------| | `{block:priceTable:items}` | Pętla — `{item:name}`, `{item:price}`, `{item:period}`, `{item:features}` | #### Zespół | Znacznik | Opis | |----------|------| | `{block:team:items}` | Pętla — `{item:name}`, `{item:role}`, `{item:photo}`, `{item:bio}` | #### Statystyki | Znacznik | Opis | |----------|------| | `{block:stats:items}` | Pętla — `{item:value}`, `{item:label}`, `{item:icon}` | #### Oś czasu | Znacznik | Opis | |----------|------| | `{block:timeline:items}` | Pętla — `{item:date}`, `{item:title}`, `{item:description}` | --- ## 7. Placeholdery w DisplayTemplates (renderowanie dokumentów) Gdy używasz `{doc:pages:171:template=karta}` lub DisplayTemplate w kolekcji DisplayTemplates, szablon HTML ma dostęp do następujących placeholderów dokumentu: #### Podstawowe (17) | Znacznik | Opis | |----------|------| | `{title}` | Tytuł dokumentu | | `{title:linked}` | Tytuł jako link do strony | | `{icon}` | Ikona (emoji) | | `{intro}` | Pole intro | | `{contentHtml}` | Pełna treść HTML | | `{description}` | Opis (krótki) | | `{breadcrumbs}` | Breadcrumbs HTML | | `{tags}` | Tagi jako linki | | `{metaTitle}` | Meta tytuł | | `{metaDescription}` | Meta opis | | `{updatedAt}` | Data aktualizacji | | `{createdAt}` | Data utworzenia | | `{readMore}` | Link "Czytaj więcej" | | `{readMore:label=Szczegóły}` | Customowy tekst linku | | `{image}` | Główny obraz (`<img>`) | | `{price}` | Cena netto | | `{layout}` | Bloki layout builder | #### Dane handlowe / VAT (5) | Znacznik | Opis | |----------|------| | `{priceGross}` | Cena brutto (z VAT) | | `{vatRate}` | Stawka VAT (np. 0.23) | | `{vatRatePercent}` | Stawka VAT % (np. 23) | | `{vatAmount}` | Kwota VAT | | `{inventory}` | Stan magazynowy | #### Pola produktu (22) | Znacznik | Opis | |----------|------| | `{motto}` | Motto produktu | | `{manufacturer}` | Producent | | `{symbol}` | Symbol handlowy | | `{unit}` | Jednostka miary | | `{packaging}` | Opakowanie | | `{minOrder}` | Minimalne zamówienie | | `{wholesaleThreshold}` | Próg hurtowy | | `{orderMultiple}` | Krotność zamówienia | | `{shortDescription}` | Krótki opis | | `{specialization}` | Specjalizacja | | `{applications}` | Zastosowania | | `{solvedProblems}` | Rozwiązywane problemy | | `{features}` | Cechy | | `{installationInstructions}` | Instrukcje instalacji | | `{technicalSpecs}` | Specyfikacja techniczna | | `{certificates}` | Certyfikaty | | `{usageNorms}` | Normy użytkowania | | `{safetyInfo}` | Informacje BHP | | `{warrantyInfo}` | Gwarancja | | `{storageInfo}` | Przechowywanie | | `{wholesaleDiscount}` | Rabat hurtowy | | `{notes}` | Uwagi | #### Kompozytowe | Znacznik | Opis | |----------|------| | `{commercialRow}` | Wiersz danych handlowych (cena + VAT + jednostka) | | `{extraHtml}` | Dodatkowy HTML z pól produktu | ### 7a. Placeholdery dla kolekcji Wpisy Dla kolekcji Wpisy, szablon DisplayTemplate ma dostęp do dodatkowych placeholderów (oprócz podstawowych z sekcji 7): #### Pola naboru / ogłoszenia (5) | Znacznik | Opis | |----------|------| | `{dataRozpoczecia}` | Data rozpoczęcia naboru (format: DD miesiąc RRRR) | | `{dataZakonczenia}` | Data zakończenia naboru | | `{instytucjaNazwa}` | Nazwa instytucji ogłaszającej nabór | | `{kwota}` | Kwota dofinansowania | | `{waluta}` | Waluta (PLN / EUR / USD) | #### Link zewnętrzny (2) | Znacznik | Opis | |----------|------| | `{linkZewnetrzny}` | URL linku zewnętrznego | | `{linkZewnetrznyLabel}` | Etykieta linku | #### Kontakt (3) | Znacznik | Opis | |----------|------| | `{kontaktOsoba}` | Osoba kontaktowa | | `{kontaktEmail}` | Email kontaktowy | | `{kontaktTelefon}` | Telefon kontaktowy | #### Typ wpisu (3) | Znacznik | Opis | |----------|------| | `{typeIcon}` | Ikona rodzaju wpisu (np. 📢) | | `{typeName}` | Nazwa rodzaju wpisu (np. "Nabór wniosku") | | `{c:typeAccent}` | Kolor akcentu rodzaju wpisu (kod hex) | #### Pola produktowe — dla wpisów typu "Produkt" (17) Te same placeholdery co w sekcji 7 (Pola produktu): `{motto}`, `{manufacturer}`, `{symbol}`, `{unit}`, `{packaging}`, `{shortDescription}`, `{specialization}`, `{applications}`, `{solvedProblems}`, `{features}`, `{installationInstructions}`, `{technicalSpecs}`, `{certificates}`, `{usageNorms}`, `{safetyInfo}`, `{warrantyInfo}`, `{storageInfo}` — oraz `{commercialRow}`. ---