GrowSitedevelopers

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:

bash
npm ci

Rozpakuj 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:

bash
npm run loom:import -- /absolute/path/launch-theme
npm run themes:build
npm run themes:check
npm run build:customer
npm run build:renderer

W katalogu api uruchom:

bash
php artisan themes:sync --activate

Import 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

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

html
<p class="launch-signature" data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>

blocks/_signature.schema.json

json
{
  "name": "Signature",
  "settings": [
    {
      "type": "text",
      "id": "text",
      "label": "Text",
      "default": "Zbudowane z pomysłem / Built with care"
    }
  ]
}

blocks/button.html

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

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

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

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

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 != &#x27;none&#x27;"><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

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

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

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

json
{"current":{"background":"#f4f6ef","foreground":"#16221b","font":"Inter"}}

config/settings_schema.json

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

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

json
{"footer":{"rights":"All rights reserved."}}

locales/en.schema.json

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

json
{"footer":{"rights":"Wszelkie prawa zastrzeżone."}}

locales/pl.default.schema.json

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

json
{
  "key": "launch-theme",
  "name": "Launch Theme",
  "version": "1.0.0",
  "defaultLocale": "pl",
  "catalog": true,
  "engine": "gs-loom",
  "engineVersion": 1
}

sections/article.html

html
<gs-blog variant="blog-detail-sidebar"></gs-blog>

sections/article.schema.json

json
{
  "name": "Article / Artykuł",
  "settings": [],
  "presets": [
    {
      "name": "Article / Artykuł"
    }
  ]
}

sections/contact.html

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

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"
    }
  ]
}
html
<footer class="launch-footer">© [[ date(&#x27;now&#x27;, &#x27;%Y&#x27;) ]] [[ site.name ]]. [[ t(&#x27;footer.rights&#x27;) ]]<gs-menu location="footer" label="Footer"></gs-menu></footer>
json
{"type":"footer","sections":{"main":{"type":"footer"}},"order":["main"]}
json
{
  "name": "Footer",
  "settings": [],
  "presets": [
    {
      "name": "Footer"
    }
  ]
}

sections/header.html

html
<div class="launch-header">[[ site.name ]]</div>

sections/header.json

json
{"type":"header","sections":{"main":{"type":"header"}},"order":["main"]}

sections/header.schema.json

json
{
  "name": "Header",
  "settings": [],
  "presets": [
    {
      "name": "Header"
    }
  ]
}

sections/hero.html

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

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

html
<section class="theme-journal">
  <gs-blog variant="blog-grid-sidebar"></gs-blog>
</section>

sections/journal.schema.json

json
{
  "name": "Blog",
  "settings": [],
  "presets": [
    {
      "name": "Blog"
    }
  ]
}
html
<a class="launch-link" href="[[ safe_url(href) ]]" gs-text="label"></a>

snippets/menu.html

html
<gs-menu location="[[ location ]]" label="[[ label ]]"></gs-menu>

templates/article.json

json
{
  "title": "Article",
  "role": "blog-details",
  "sections": {
    "journal": {
      "type": "article"
    }
  },
  "order": [
    "journal"
  ]
}

templates/blog.json

json
{
  "title": "Blog",
  "role": "blog",
  "sections": {
    "journal": {
      "type": "journal"
    }
  },
  "order": [
    "journal"
  ]
}

templates/contact.json

json
{"title":"Contact","sections":{"contact":{"type":"contact"}},"order":["contact"]}

templates/home.json

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"]}
Pobierz kopię Markdown
Wyniki · 22
Komponenty GS Loom UI

Wspólne komponenty, warianty i style motywów.

Katalog czcionek

Dostępne kroje, podgląd i deklarowanie czcionek w GS Loom.

Menu i nawigacja

Tworzenie menu, podłączanie linków i stylowanie nawigacji.

Blog i komentarze

Blog i komentarze

SEO i kontrakt pluginów

SEO i kontrakt pluginów

Pierwszy szablon krok po kroku

Kompletna paczka, instalacja i pierwsza edycja.

GS Loom Playground

Testuj szablony lokalnie bez API, bazy danych i uploadu.

Architektura i pliki

Jak pliki autora stają się stroną klienta.

Schema i ustawienia

Typy kontrolek, wartości domyślne i stabilne identyfikatory.

Bloki, targeting i kolejność

Definicje, instancje, zagnieżdżanie i bloki statyczne.

Edycja na podglądzie

Podłącz tekst, karty, obrazy i ikony do kreatora.

Teksty i nagłówki

Od schema do edycji tekstu, typografii i zapisu.

Przyciski i linki

Tekst, adresy, kolory, wymiary i dostępne stany.

Inputy i pola formularzy

Typy pól, etykiety, opcje, limity i walidacja.

Formularze krok po kroku

Instancje, powiązanie z witryną, wysyłka i obsługa błędów.

Karty, ikony, obrazy i badge

Wygląd kontenerów, rozmiary ikon i edycja mediów.

Źródła dynamiczne

Dane witryny, podstrony i wartości zapasowe.

Layouty i wspólne ustawienia

Grupy sekcji, konfiguracja globalna i podstrony.

Języki i tłumaczenia

Języki startowe, języki dodawane przez klienta i tłumaczenia AI.

Snippets, CSS i pliki

Wspólny kod, responsywny wygląd i bezpieczne adresy.

Testy, import i aktualizacje

Sprawdź paczkę i wydaj ją bez utraty danych klientów.

Możliwości i ograniczenia

Obsługiwany kontrakt GS Loom i praca z AI.