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}`.
---