Pierwszy szablon krok po kroku
Kompletna paczka, instalacja i pierwsza edycja.
Co zbudujesz
Zbudujesz szablon Launch Theme: sekcję powitalną, dodawane teksty, przyciski i karty z mediami, stały podpis, wspólny nagłówek i stopkę oraz stronę kontaktową. Użytkownik zmieni tekst, kolor, zaokrąglenie i odstępy w kreatorze. Paczka zawiera słowniki PL/EN i responsywny CSS. Przykład jest kompletny, sprawdzany kompilatorem GrowSite i dostępny jako paczka ZIP.
1. Przygotuj środowisko
Potrzebujesz dostępu do repozytorium GrowSite, Node.js zgodnego z projektem, zainstalowanych zależności npm oraz działającego API z bazą danych do końcowego sprawdzenia w panelu. Instalacja paczki jest obecnie zadaniem osoby utrzymującej projekt, nie publicznym uploadem klienta.
Z katalogu głównego repozytorium:
npm ciRozpakuj pobrany ZIP poza katalogiem zainstalowanych szablonów. W repozytorium identyczny przykład znajdziesz w dev-site/examples/launch-theme/. Możesz go skopiować do własnego katalogu roboczego. Nie kopiuj go bezpośrednio do katalogu zainstalowanych szablonów, jeśli chcesz sprawdzić import.
Przed importem możesz sprawdzić paczkę w GS Loom Playground — lokalnie, bez API i bazy danych. Uruchom npm run loom:playground -- dev-site/examples/launch-theme, a następnie otwórz lokalny podgląd.
2. Ustal tożsamość
Otwórz loom.json. Dla pierwszej próby pozostaw klucz launch-theme. Dla własnego produktu wybierz unikalny klucz, zmień nazwę katalogu i pole name. Zachowuj ten sam klucz w kolejnych wydaniach. Wersja początkowa to 1.0.0.
3. Prześledź przepływ danych
templates/home.json tworzy instancję sekcji hero. Jej definicja w sections/hero.html odczytuje section.settings.title. Schema deklaruje ten identyfikator, etykietę kontrolki i wartość początkową. Karta działa analogicznie przez block.settings.
Zmodyfikuj domyślny tytuł oraz tekst w schema. Zmień radius.default w karcie z 16 na 24. W CSS zmień układ lub odstęp między kartami. Nie wpisuj tekstu na sztywno w HTML zamiast odczytu ustawienia — wtedy kontrolka nie będzie nim sterować.
Pełne pliki przykładu znajdują się niżej. Zachowaj ścieżki, rozszerzenia i osobny plik .schema.json dla każdej sekcji i bloku.
Import ZIP w panelu administratora wymaga archive.json i źródeł w theme/. Zobacz przykład manifestu paczki i strukturę archiwum przed przygotowaniem ZIP-a.
4. Zaimportuj i zbuduj katalog
Z katalogu głównego repozytorium, podając rzeczywistą ścieżkę do rozpakowanego katalogu:
npm run loom:import -- /absolute/path/launch-theme
npm run themes:build
npm run themes:check
npm run build:customer
npm run build:rendererW katalogu api uruchom:
php artisan themes:sync --activateImport odrzuca istniejący klucz. Przy kolejnych zmianach użyj procedury aktualizacji i zwiększ wersję. Sam import nie publikuje stron klientów.
5. Sprawdź efekt w kreatorze
Utwórz testową witrynę z nowym szablonem. Otwórz sekcję powitalną: wartości kontrolek powinny odpowiadać schema. Zmień tekst, dodaj drugą kartę, przesuń ją, ustaw promień na 0 i wyłącz link. Zmień podpis statyczny — nie powinien mieć opcji usunięcia. Zapisz, odśwież stronę i porównaj z publicznym podglądem. Sprawdź telefon oraz oba języki słowników.
Wartości startowe treści w przykładzie są dwujęzyczne. Słowniki tłumaczą etykiety i stopkę, a nie automatycznie tekst wprowadzony przez użytkownika.
Pliki kompletnego przykładu
Poniższa lista jest generowana bezpośrednio z paczki do pobrania. Jeśli zmieniasz pliki źródłowe przykładu, przebuduj dokumentację i ponownie uruchom jej testy.
Przepisy na konkretne elementy: tekst, przycisk, pola, formularz i karta z ikoną.
assets/theme.css
.launch { font-size: 16px; line-height: 1.65; }
.launch-header, .launch-footer { padding: 24px 32px; }
.launch-header { font-weight: 700; }
.launch-hero h1 { font-size: clamp(32px, 5vw, 64px); line-height: 1.1; max-width: 18ch; margin: 20px 0; }
.launch-eyebrow { text-transform: uppercase; letter-spacing: .12em; }
.launch-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 20px; margin: 32px 0; }
.launch-card { padding: 24px; border: 1px solid #d7dfd6; }
.launch-card h2 { font-size: 24px; margin-bottom: 12px; }
.launch-link { display: inline-block; padding: 10px 18px; background: #16221b; color: #fff; border-radius: 8px; margin-top: 20px; }
.launch-signature, .launch-footer { font-size: 14px; }
.launch-copy { padding: 24px; }
.launch-copy-body { white-space: pre-line; }
.launch-cta { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; min-height: 44px; align-self: start; }
.launch-cta:hover { filter: brightness(.92); }
.launch-cta:focus-visible, .launch-link:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }
.launch-card-image { display: block; width: 100%; height: auto; aspect-ratio: 8 / 5; object-fit: cover; border-radius: inherit; margin-bottom: 20px; }
.launch-card-icon { display: inline-flex; margin-bottom: 16px; }
.launch-card-icon svg { width: var(--icon-size, 32px); height: var(--icon-size, 32px); }
.launch-contact { padding: 32px; }blocks/_signature.html
<p class="launch-signature" data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>blocks/_signature.schema.json
{
"name": "Signature",
"settings": [
{
"type": "text",
"id": "text",
"label": "Text",
"default": "Zbudowane z pomysłem / Built with care"
}
]
}blocks/button.html
<template gs-if="block.settings.show_link">
<a class="launch-cta" href="[[ safe_url(block.settings.url) ]]" style="background:[[ block.settings.background ]];color:[[ block.settings.color ]];border:[[ block.settings.border_width ]]px solid [[ block.settings.border_color ]];border-radius:[[ block.settings.radius ]]px;padding:[[ block.settings.padding_y ]]px [[ block.settings.padding_x ]]px;font-size:[[ block.settings.font_size ]]px">
<span data-text-field="[[ block.settings_path ]].label" gs-text="block.settings.label"></span>
</a>
</template>blocks/button.schema.json
{
"name": "Button / Przycisk",
"settings": [
{
"type": "text",
"id": "label",
"label": "Label / Tekst",
"default": "Napisz do nas / Contact us"
},
{
"type": "url",
"id": "url",
"label": "URL",
"default": "mailto:hello@example.com"
},
{
"type": "checkbox",
"id": "show_link",
"label": "Visible / Widoczny",
"default": true
},
{
"type": "color",
"id": "background",
"label": "Background / Tło",
"default": "#16221b"
},
{
"type": "color",
"id": "color",
"label": "Text color / Kolor tekstu",
"default": "#ffffff"
},
{
"type": "color",
"id": "border_color",
"label": "Border color / Kolor obramowania",
"default": "#16221b"
},
{
"type": "range",
"id": "border_width",
"label": "Border width / Grubość obramowania",
"min": 0,
"max": 8,
"step": 1,
"default": 0,
"unit": "px"
},
{
"type": "range",
"id": "radius",
"label": "Radius / Zaokrąglenie",
"min": 0,
"max": 48,
"step": 1,
"default": 8,
"unit": "px"
},
{
"type": "range",
"id": "padding_y",
"label": "Vertical padding / Wypełnienie pionowe",
"min": 4,
"max": 32,
"step": 1,
"default": 12,
"unit": "px"
},
{
"type": "range",
"id": "padding_x",
"label": "Horizontal padding / Wypełnienie poziome",
"min": 8,
"max": 64,
"step": 1,
"default": 20,
"unit": "px"
},
{
"type": "range",
"id": "font_size",
"label": "Text size / Rozmiar tekstu",
"min": 12,
"max": 32,
"step": 1,
"default": 16,
"unit": "px"
}
],
"presets": [
{
"name": "Button / Przycisk"
}
]
}blocks/card.html
<article class="launch-card" data-canvas-card="[[ block.field_path ]].card" style="background:[[ block.settings.background ]];border-radius:[[ block.settings.radius ]]px">
<h2 data-text-field="[[ block.settings_path ]].title" gs-text="block.settings.title"></h2>
<p data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>
<template gs-if="block.settings.show_link">
<template gs-component="link" gs-prop-href="block.settings.url" gs-prop-label="block.settings.label"></template>
</template>
</article>blocks/card.schema.json
{
"name": "t:blocks.card",
"settings": [
{
"type": "text",
"id": "title",
"label": "t:settings.title",
"default": "Karta / Card"
},
{
"type": "textarea",
"id": "text",
"label": "t:settings.text",
"default": "Szczegóły oferty / Offer details"
},
{
"type": "color",
"id": "background",
"label": "t:settings.background",
"default": "#ffffff"
},
{
"type": "range",
"id": "radius",
"label": "t:settings.radius",
"default": 16,
"min": 0,
"max": 48,
"step": 1,
"unit": "px"
},
{
"type": "checkbox",
"id": "show_link",
"label": "t:settings.show_link",
"default": true
},
{
"type": "text",
"id": "label",
"label": "t:settings.label",
"default": "Kontakt / Contact"
},
{
"type": "url",
"id": "url",
"label": "URL",
"default": "mailto:hello@example.com"
}
],
"presets": [
{
"name": "Card"
}
]
}blocks/media-card.html
<article class="launch-card" data-canvas-card="[[ block.field_path ]].card" style="background:[[ block.settings.background ]];border:[[ block.settings.border_width ]]px solid [[ block.settings.border_color ]];border-radius:[[ block.settings.radius ]]px">
<template gs-if="block.settings.icon != 'none'"><span class="launch-card-icon" style="color:[[ block.settings.icon_color ]];--icon-size:[[ block.settings.icon_size ]]px"><gs-icon name="[[ block.settings.icon ]]" field="[[ block.field_path ]].icon"></gs-icon></span></template>
<template gs-if="block.settings.image_url != blank"><img class="launch-card-image" src="[[ safe_url(block.settings.image_url) ]]" alt="[[ block.settings.image_alt ]]" loading="lazy" width="640" height="400"></template>
<h2 data-text-field="[[ block.settings_path ]].title" gs-text="block.settings.title"></h2>
<p data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>
</article>blocks/media-card.schema.json
{
"name": "Media card / Karta z obrazem",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading / Nagłówek",
"default": "Oferta / Offer"
},
{
"type": "textarea",
"id": "text",
"label": "Description / Opis",
"default": "Opisz korzyść. / Describe a benefit."
},
{
"type": "image_picker",
"id": "image_url",
"label": "Image URL / Adres obrazu",
"default": ""
},
{
"type": "text",
"id": "image_alt",
"label": "Alternative text / Tekst alternatywny",
"default": ""
},
{
"type": "select",
"id": "icon",
"label": "Icon / Ikona",
"default": "shield",
"options": [
{
"value": "none",
"label": "None / Brak"
},
{
"value": "shield",
"label": "Shield / Tarcza"
},
{
"value": "bolt",
"label": "Bolt / Błyskawica"
}
]
},
{
"type": "range",
"id": "icon_size",
"label": "Icon size / Rozmiar ikony",
"min": 16,
"max": 96,
"step": 1,
"default": 32,
"unit": "px"
},
{
"type": "color",
"id": "icon_color",
"label": "Icon color / Kolor ikony",
"default": "#16221b"
},
{
"type": "color",
"id": "background",
"label": "Background / Tło",
"default": "#ffffff"
},
{
"type": "color",
"id": "border_color",
"label": "Border color / Kolor obramowania",
"default": "#d7dfd6"
},
{
"type": "range",
"id": "border_width",
"label": "Border width / Grubość obramowania",
"min": 0,
"max": 8,
"step": 1,
"default": 1,
"unit": "px"
},
{
"type": "range",
"id": "radius",
"label": "Radius / Zaokrąglenie",
"min": 0,
"max": 48,
"step": 1,
"default": 16,
"unit": "px"
}
],
"presets": [
{
"name": "Media card / Karta z obrazem"
}
]
}blocks/text.html
<div class="launch-copy" style="color:[[ block.settings.color ]];text-align:[[ block.settings.alignment ]]">
<h2 data-text-field="[[ block.settings_path ]].title" style="font-size:[[ block.settings.heading_size ]]px" gs-text="block.settings.title"></h2>
<p class="launch-copy-body" data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>
</div>blocks/text.schema.json
{
"name": "Text / Tekst",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading / Nagłówek",
"default": "Poznaj nas / Meet us"
},
{
"type": "textarea",
"id": "text",
"label": "Description / Opis",
"default": "Opowiedz swoją historię. / Tell your story."
},
{
"type": "color",
"id": "color",
"label": "Color / Kolor",
"default": "#16221b"
},
{
"type": "range",
"id": "heading_size",
"label": "Heading size / Rozmiar nagłówka",
"min": 20,
"max": 64,
"step": 1,
"default": 32,
"unit": "px"
},
{
"type": "select",
"id": "alignment",
"label": "Alignment / Wyrównanie",
"default": "left",
"options": [
{
"value": "left",
"label": "Left / Do lewej"
},
{
"value": "center",
"label": "Center / Do środka"
}
]
}
],
"presets": [
{
"name": "Text / Tekst"
}
]
}config/settings_data.json
{"current":{"background":"#f4f6ef","foreground":"#16221b","font":"Inter"}}config/settings_schema.json
[{"name":"Appearance","settings":[{"type":"color","id":"background","label":"Background","default":"#f4f6ef"},{"type":"color","id":"foreground","label":"Text","default":"#16221b"},{"type":"font_picker","id":"font","label":"Font","default":"Inter"}]}]layout/theme.html
<div class="launch" style="background:[[ settings.background ]];color:[[ settings.foreground ]];font-family:[[ settings.font ]]">
<template gs-group="header"></template>
<template gs-slot="layout"></template>
<template gs-group="footer"></template>
</div>locales/en.json
{"footer":{"rights":"All rights reserved."}}locales/en.schema.json
{"sections":{"hero":"Hero section"},"blocks":{"card":"Card"},"settings":{"title":"Heading","text":"Description","spacing":"Inner spacing","background":"Background","radius":"Corner radius","show_link":"Show link","label":"Link label"}}locales/pl.default.json
{"footer":{"rights":"Wszelkie prawa zastrzeżone."}}locales/pl.default.schema.json
{"sections":{"hero":"Sekcja powitalna"},"blocks":{"card":"Karta"},"settings":{"title":"Nagłówek","text":"Opis","spacing":"Odstęp wewnętrzny","background":"Tło","radius":"Zaokrąglenie","show_link":"Pokaż link","label":"Tekst linku"}}loom.json
{
"key": "launch-theme",
"name": "Launch Theme",
"version": "1.0.0",
"defaultLocale": "pl",
"catalog": true,
"engine": "gs-loom",
"engineVersion": 1
}sections/article.html
<gs-blog variant="blog-detail-sidebar"></gs-blog>sections/article.schema.json
{
"name": "Article / Artykuł",
"settings": [],
"presets": [
{
"name": "Article / Artykuł"
}
]
}sections/contact.html
<section class="launch-contact">
<h1 data-text-field="[[ section.settings_path ]].title" gs-text="section.settings.title"></h1>
<p data-text-field="[[ section.settings_path ]].text" gs-text="section.settings.text"></p>
<gs-form instance-id="[[ section.settings.form_id ]]"></gs-form>
</section>sections/contact.schema.json
{
"name": "Contact / Kontakt",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading / Nagłówek",
"default": "Porozmawiajmy / Let's talk"
},
{
"type": "textarea",
"id": "text",
"label": "Description / Opis",
"default": "Dodaj formularz w kreatorze. / Add a form in the builder."
},
{
"type": "text",
"id": "form_id",
"label": "ID formularza / Form ID",
"default": ""
}
],
"presets": [
{
"name": "Contact / Kontakt"
}
]
}sections/footer.html
<footer class="launch-footer">© [[ date('now', '%Y') ]] [[ site.name ]]. [[ t('footer.rights') ]]<gs-menu location="footer" label="Footer"></gs-menu></footer>sections/footer.json
{"type":"footer","sections":{"main":{"type":"footer"}},"order":["main"]}sections/footer.schema.json
{
"name": "Footer",
"settings": [],
"presets": [
{
"name": "Footer"
}
]
}sections/header.html
<div class="launch-header">[[ site.name ]]</div>sections/header.json
{"type":"header","sections":{"main":{"type":"header"}},"order":["main"]}sections/header.schema.json
{
"name": "Header",
"settings": [],
"presets": [
{
"name": "Header"
}
]
}sections/hero.html
<section class="launch-hero" style="padding:[[ section.settings.spacing ]]px">
<p class="launch-eyebrow" gs-text="site.name"></p>
<h1 data-text-field="[[ section.settings_path ]].title" gs-text="section.settings.title"></h1>
<p data-text-field="[[ section.settings_path ]].text" gs-text="section.settings.text"></p>
<div class="launch-grid"><template gs-slot="blocks"></template></div>
<template gs-block="_signature" gs-id="signature"></template>
</section>sections/hero.schema.json
{
"name": "t:sections.hero",
"settings": [
{
"type": "text",
"id": "title",
"label": "t:settings.title",
"default": "Stwórz coś dobrego / Create something good"
},
{
"type": "textarea",
"id": "text",
"label": "t:settings.text",
"default": "Opisz swoją ofertę. / Introduce your business."
},
{
"type": "range",
"id": "spacing",
"label": "t:settings.spacing",
"min": 16,
"max": 80,
"step": 4,
"default": 32,
"unit": "px"
}
],
"blocks": [
{
"type": "card"
},
{
"type": "text"
},
{
"type": "button"
},
{
"type": "media-card"
}
],
"max_blocks": 6,
"presets": [
{
"name": "Hero"
}
]
}sections/journal.html
<section class="theme-journal">
<gs-blog variant="blog-grid-sidebar"></gs-blog>
</section>sections/journal.schema.json
{
"name": "Blog",
"settings": [],
"presets": [
{
"name": "Blog"
}
]
}snippets/link.html
<a class="launch-link" href="[[ safe_url(href) ]]" gs-text="label"></a>snippets/menu.html
<gs-menu location="[[ location ]]" label="[[ label ]]"></gs-menu>templates/article.json
{
"title": "Article",
"role": "blog-details",
"sections": {
"journal": {
"type": "article"
}
},
"order": [
"journal"
]
}templates/blog.json
{
"title": "Blog",
"role": "blog",
"sections": {
"journal": {
"type": "journal"
}
},
"order": [
"journal"
]
}templates/contact.json
{"title":"Contact","sections":{"contact":{"type":"contact"}},"order":["contact"]}templates/home.json
{"title":"Home","sections":{"hero":{"type":"hero","blocks":{"first":{"type":"card","settings":{"title":"Twój pomysł / Your idea"}},"signature":{"type":"_signature","static":true}},"block_order":["first"]}},"order":["hero"]}