diff --git a/.env b/.env index 02222bb..a8df2a9 100644 --- a/.env +++ b/.env @@ -22,3 +22,4 @@ STAGING_URL=https://dev-__PROJECT_NAME__.staging.testversion.online CODING_URL=https://__PROJECT_NAME__.code.testversion.online #START_SCRIPT=:ssr +MOCK=1 diff --git a/.gitignore b/.gitignore index 0981d04..d6c6041 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ playwright-report/ playwright/.cache/ visual-review/ video-tours/output/ +.playwright-mcp/ .yarn/* !.yarn/cache !.yarn/patches diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 61d48af..340afc2 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/AGENTS.md b/AGENTS.md index 0737045..493cea2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,3 +67,23 @@ Tibi CMS starter template — Svelte 5 SPA with esbuild, SSR via goja, and Playw - Avoid introducing new dependencies unless absolutely necessary. - Respect a11y and localization best practices; optimize for WCAG AA standards. - Check the problems tab for any errors or warnings in the code. +- **Zero warnings policy**: After making changes, always run `yarn validate` and check the IDE problems tab. Fix all TypeScript, Svelte, and Tailwind warnings — the codebase must stay warning-free. +- When Tailwind `suggestCanonicalClasses` warnings appear, always fix the **source** `.svelte`/`.ts`/`.css` file — never the compiled output. + +### Tailwind CSS 4 canonical classes + +This project uses Tailwind CSS 4. Always use the canonical v4 syntax: + +| Legacy (v3) | Canonical (v4) | +| ---------------------- | ---------------------- | +| `bg-gradient-to-*` | `bg-linear-to-*` | +| `aspect-[4/3]` | `aspect-4/3` | +| `!bg-brand-600` | `bg-brand-600!` | +| `hover:!bg-brand-700` | `hover:bg-brand-700!` | +| `!rounded-xl` | `rounded-xl!` | + +Rules: + +- Gradient directions use `bg-linear-to-{direction}` instead of `bg-gradient-to-{direction}`. +- Bare-ratio aspect values like `aspect-4/3` replace arbitrary `aspect-[4/3]`. +- The `!important` modifier is a **suffix** (`class!`) instead of a **prefix** (`!class`). diff --git a/README.md b/README.md index f76ca27..42c2b21 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,33 @@ Starter Kit für SPAs(s) `;)` mit Svelte und TibiCMS inkl. SSR +## Neues Projekt starten + +Nachdem du dieses Repository als Vorlage geklont hast, passe die Platzhalter an dein Projekt an: + +1. **`.env`**: Ersetze `__PROJECT_NAME__` mit deinem Projektnamen (z.B. `mein-projekt`). + Die folgenden URLs werden automatisch abgeleitet: + - `CODING_URL=https://mein-projekt.code.testversion.online` + - `STAGING_URL=https://dev-mein-projekt.staging.testversion.online` +2. **`frontend/spa.html`** / **`api/templates/spa.html`**: Ersetze `__PROJECT_TITLE__` mit dem Seitentitel. +3. **`api/config.yml.env`**: Passe `ADMIN_TOKEN`, Datenbank-Name und weitere Secrets an. +4. **`docker-compose-local.yml`**: Suche nach `project_name__` und ersetze mit deinem Projektnamen (Container-Benennung). +5. **Demo-Inhalte entfernen**: Die Demo-Seite besteht aus diesen Dateien, die für ein echtes Projekt entfernt/ersetzt werden können: + - `frontend/src/blocks/` — Block-Komponenten (HeroBlock, FeaturesBlock, RichtextBlock, AccordionBlock, ContactFormBlock, BlockRenderer, NotFound) + - `frontend/mocking/content.json` — Demo-Mockdaten für Content + - `frontend/mocking/navigation.json` — Demo-Mockdaten für Navigation + - `api/collections/content.yml` — Content-Collection-Konfiguration + - `api/collections/navigation.yml` — Navigation-Collection-Konfiguration + - `tests/e2e/demo.spec.ts` — Demo-E2E-Tests + - `video-tours/tours/demo-showcase.tour.ts` — Demo-Video-Tour +6. **`frontend/src/App.svelte`**: Passe Header, Footer und Content-Loading an dein Datenmodell an. + +```sh +# Platzhalter ersetzen (Beispiel für Linux/Mac): +sed -i 's/__PROJECT_NAME__/mein-projekt/g' .env +sed -i 's/__PROJECT_TITLE__/Mein Projekt/g' frontend/spa.html api/templates/spa.html +``` + ## Wozu? Via Svelte wird eine SPA (Single-Page-App) programmiert. Dazu wird der Code einmal für den Browser aufgebreitet und außerdem für den Server kompiliert und transpiliert. Der Server-Code wird in einem tibi-server SSR-Hook (server side rendering) eingebunden und generiert dort fertiges HTML anhand der aktuelle Route für SEO und optimierte Ladezeiten. diff --git a/api/collections/content.yml b/api/collections/content.yml new file mode 100644 index 0000000..b08adc5 --- /dev/null +++ b/api/collections/content.yml @@ -0,0 +1,171 @@ +######################################################################## +# Content collection — CMS-managed pages with pagebuilder blocks +######################################################################## + +name: content +meta: + label: { de: "Inhalte", en: "Content" } + muiIcon: article + rowIdentTpl: { twig: "{{ name }}" } + + views: + - type: simpleList + mediaQuery: "(max-width: 600px)" + primaryText: name + secondaryText: lang + tertiaryText: path + - type: table + columns: + - name + - source: lang + filter: true + - source: type + filter: true + - source: path + - source: active + filter: true + +permissions: + public: + methods: + get: true + user: + methods: + get: true + post: true + put: true + delete: true + +fields: + - name: active + type: boolean + meta: + label: { de: "Aktiv", en: "Active" } + - name: type + type: string + meta: + label: { de: "Typ", en: "Type" } + - name: lang + type: string + meta: + label: { de: "Sprache", en: "Language" } + - name: translationKey + type: string + meta: + label: { de: "Übersetzungsschlüssel", en: "Translation Key" } + - name: name + type: string + meta: + label: { de: "Name", en: "Name" } + - name: path + type: string + meta: + label: { de: "Pfad", en: "Path" } + - name: alternativePaths + type: object[] + meta: + label: { de: "Alternative Pfade", en: "Alternative Paths" } + subFields: + - name: path + type: string + - name: thumbnail + type: string + meta: + label: { de: "Vorschaubild", en: "Thumbnail" } + - name: teaserText + type: string + meta: + label: { de: "Teasertext", en: "Teaser Text" } + - name: blocks + type: object[] + meta: + label: { de: "Inhaltsblöcke", en: "Content Blocks" } + subFields: + - name: hide + type: boolean + - name: type + type: string + - name: headline + type: string + - name: headlineH1 + type: boolean + - name: subline + type: string + - name: tagline + type: string + - name: anchorId + type: string + - name: containerWidth + type: string + - name: background + type: object + subFields: + - name: color + type: string + - name: image + type: string + - name: padding + type: object + subFields: + - name: top + type: string + - name: bottom + type: string + - name: callToAction + type: object + subFields: + - name: buttonText + type: string + - name: buttonLink + type: string + - name: buttonTarget + type: string + - name: heroImage + type: object + subFields: + - name: image + type: string + - name: text + type: string + - name: imagePosition + type: string + - name: imageRounded + type: string + - name: image + type: string + - name: accordionItems + type: object[] + subFields: + - name: question + type: string + - name: answer + type: string + - name: open + type: boolean + - name: imageGallery + type: object + subFields: + - name: images + type: object[] + subFields: + - name: image + type: string + - name: caption + type: string + - name: showCaption + type: boolean + - name: showImageCaption + type: boolean + - name: imageCaption + type: string + - name: meta + type: object + meta: + label: { de: "SEO", en: "SEO" } + subFields: + - name: title + type: string + - name: description + type: string + - name: keywords + type: string diff --git a/api/collections/navigation.yml b/api/collections/navigation.yml new file mode 100644 index 0000000..5cc588b --- /dev/null +++ b/api/collections/navigation.yml @@ -0,0 +1,68 @@ +######################################################################## +# Navigation collection — header and footer navigation entries +######################################################################## + +name: navigation +meta: + label: { de: "Navigation", en: "Navigation" } + muiIcon: menu + rowIdentTpl: { twig: "{{ type }} ({{ language }})" } + + views: + - type: simpleList + mediaQuery: "(max-width: 600px)" + primaryText: type + secondaryText: language + - type: table + columns: + - source: type + filter: true + - source: language + filter: true + +permissions: + public: + methods: + get: true + user: + methods: + get: true + post: true + put: true + delete: true + +fields: + - name: language + type: string + meta: + label: { de: "Sprache", en: "Language" } + - name: type + type: string + meta: + label: { de: "Typ", en: "Type" } + helperText: { de: "header oder footer", en: "header or footer" } + - name: elements + type: object[] + meta: + label: { de: "Elemente", en: "Elements" } + subFields: + - name: name + type: string + meta: + label: { de: "Bezeichnung", en: "Label" } + - name: page + type: string + meta: + label: { de: "Seite (Content-ID)", en: "Page (Content ID)" } + - name: external + type: boolean + meta: + label: { de: "Externer Link", en: "External Link" } + - name: externalUrl + type: string + meta: + label: { de: "Externe URL", en: "External URL" } + - name: hash + type: string + meta: + label: { de: "Anker", en: "Anchor" } diff --git a/api/config.yml b/api/config.yml index bb0d213..1c61b94 100644 --- a/api/config.yml +++ b/api/config.yml @@ -9,6 +9,8 @@ meta: "" collections: + - !include collections/content.yml + - !include collections/navigation.yml - !include collections/ssr.yml assets: diff --git a/frontend/mocking/content.json b/frontend/mocking/content.json index 813386f..8ce1d5e 100644 --- a/frontend/mocking/content.json +++ b/frontend/mocking/content.json @@ -1,7 +1,7 @@ [ { - "id": "home", - "_id": "home", + "id": "home-de", + "_id": "home-de", "active": true, "type": "page", "lang": "de", @@ -11,49 +11,231 @@ "blocks": [ { "type": "hero", - "headline": "Willkommen", + "headline": "Moderne Webprojekte. Blitzschnell umgesetzt.", "headlineH1": true, - "subline": "Demo-Startseite des Starter-Templates", + "subline": "Tibi CMS Starter — Svelte 5, Tailwind CSS 4, SSR und eine API, die einfach funktioniert.", + "containerWidth": "full", + "callToAction": { + "buttonText": "Features entdecken", + "buttonLink": "#features", + "buttonTarget": "" + }, "heroImage": { - "image": "demo-hero" + "externalUrl": "https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=1920&q=80" } }, + { + "type": "features", + "headline": "Was dieses Template kann", + "tagline": "Features", + "anchorId": "features", + "padding": { "top": "lg", "bottom": "lg" }, + "text": "

Svelte 5 Runes

Reaktives UI mit $state, $derived und $effect — kein Boilerplate, maximale Performance.

🎨

Tailwind CSS 4

Utility-first Styling mit Custom-Theme, Dark-Mode-ready und blitzschnellen Builds.

🔌

Tibi CMS API

Collections, Hooks, Medialib — alles über eine REST-API. Mit Mock-Modus für offline-Entwicklung.

🌍

i18n Built-in

Mehrsprachigkeit aus der Box: URL-basierte Sprachauswahl, Lazy-Loaded Locales, SSR-kompatibel.

🖥️

SSR via goja

Server-Side Rendering in Go — schnelle Erstauslieferung, SEO-freundlich, mit Cache-Invalidierung.

🧪

Playwright Tests

E2E, API, Visual Regression und Video-Tours — alles vorkonfiguriert und ready to go.

" + }, { "type": "richtext", - "headline": "Über uns", - "text": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

" + "headline": "So funktioniert's", + "tagline": "Workflow", + "anchorId": "workflow", + "padding": { "top": "lg", "bottom": "sm" }, + "externalImageUrl": "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&q=80", + "imagePosition": "right", + "text": "

Starte die Entwicklungsumgebung mit make docker-up && make docker-start. Der esbuild-Watcher kompiliert Änderungen in Echtzeit, BrowserSync lädt den Browser automatisch neu.

Für Offline-Entwicklung aktiviere den Mock-Modus mit MOCK=1 in der .env. Content wird über die Tibi-API geladen und mit dem BlockRenderer dargestellt.

Jeder Block-Typ (Hero, Richtext, Accordion, Features) ist eine eigene Svelte-Komponente — erweiterbar und austauschbar.

" + }, + { + "type": "accordion", + "headline": "Häufige Fragen", + "tagline": "FAQ", + "anchorId": "faq", + "padding": { "top": "sm", "bottom": "lg" }, + "accordionItems": [ + { + "question": "Wie starte ich ein neues Projekt mit diesem Template?", + "answer": "

Klone das Repository, passe .env an und starte mit make docker-up && make docker-start. Die Demo-Inhalte kannst du einfach durch echte Inhalte ersetzen.

", + "open": true + }, + { + "question": "Brauche ich einen laufenden Tibi-Server für die Entwicklung?", + "answer": "

Nein! Mit MOCK=1 in der .env werden API-Aufrufe gegen lokale JSON-Dateien aufgelöst. So kannst du Frontend-Features ohne Backend entwickeln.

" + }, + { + "question": "Wie füge ich eine neue Seite hinzu?", + "answer": "

Erstelle einen neuen Content-Eintrag in der Collection (oder in mocking/content.json für Mock-Modus) mit dem gewünschten Pfad und Blöcken. Die App rendert ihn automatisch über den BlockRenderer.

" + }, + { + "question": "Kann ich eigene Block-Typen erstellen?", + "answer": "

Ja! Erstelle eine neue Svelte-Komponente und registriere den Typ im BlockRenderer. Das Type-Interface ContentBlockEntry in global.d.ts kannst du entsprechend erweitern.

" + } + ] } ], "meta": { - "title": "Startseite", - "description": "Demo-Startseite" + "title": "Tibi Svelte Starter — Modernes CMS-Template", + "description": "Svelte 5, Tailwind CSS 4, SSR, i18n und Playwright-Tests — das perfekte Starterkit.", + "keywords": "svelte, tibi, cms, starter, template" } }, { - "id": "about", - "_id": "about", + "id": "home-en", + "_id": "home-en", + "active": true, + "type": "page", + "lang": "en", + "translationKey": "home", + "name": "Home", + "path": "/", + "blocks": [ + { + "type": "hero", + "headline": "Modern web projects. Lightning fast.", + "headlineH1": true, + "subline": "Tibi CMS Starter — Svelte 5, Tailwind CSS 4, SSR and an API that just works.", + "containerWidth": "full", + "callToAction": { + "buttonText": "Explore features", + "buttonLink": "#features", + "buttonTarget": "" + }, + "heroImage": { + "externalUrl": "https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=1920&q=80" + } + }, + { + "type": "features", + "headline": "What this template offers", + "tagline": "Features", + "anchorId": "features", + "padding": { "top": "lg", "bottom": "lg" }, + "text": "

Svelte 5 Runes

Reactive UI with $state, $derived and $effect — no boilerplate, maximum performance.

🎨

Tailwind CSS 4

Utility-first styling with custom theme, dark-mode-ready and blazing fast builds.

🔌

Tibi CMS API

Collections, hooks, media library — all via REST API. With mock mode for offline development.

🌍

Built-in i18n

Multi-language out of the box: URL-based language selection, lazy-loaded locales, SSR-compatible.

🖥️

SSR via goja

Server-side rendering in Go — fast initial delivery, SEO-friendly, with cache invalidation.

🧪

Playwright Tests

E2E, API, visual regression and video tours — all preconfigured and ready to go.

" + }, + { + "type": "richtext", + "headline": "How it works", + "tagline": "Workflow", + "anchorId": "workflow", + "padding": { "top": "lg", "bottom": "sm" }, + "externalImageUrl": "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&q=80", + "imagePosition": "right", + "text": "

Start the dev environment with make docker-up && make docker-start. The esbuild watcher compiles changes in real-time, BrowserSync auto-reloads the browser.

For offline development, enable mock mode with MOCK=1 in .env. Content is loaded via the Tibi API and rendered with the BlockRenderer.

Each block type (Hero, Richtext, Accordion, Features) is its own Svelte component — extensible and swappable.

" + }, + { + "type": "accordion", + "headline": "Frequently Asked Questions", + "tagline": "FAQ", + "anchorId": "faq", + "padding": { "top": "sm", "bottom": "lg" }, + "accordionItems": [ + { + "question": "How do I start a new project with this template?", + "answer": "

Clone the repository, adjust .env and start with make docker-up && make docker-start. Simply replace the demo content with your real content.

", + "open": true + }, + { + "question": "Do I need a running Tibi server for development?", + "answer": "

No! With MOCK=1 in .env, API calls are resolved against local JSON files. This lets you develop frontend features without a backend.

" + }, + { + "question": "How do I add a new page?", + "answer": "

Create a new content entry in the collection (or in mocking/content.json for mock mode) with the desired path and blocks. The app renders it automatically via the BlockRenderer.

" + }, + { + "question": "Can I create custom block types?", + "answer": "

Yes! Create a new Svelte component and register the type in BlockRenderer. You can extend the ContentBlockEntry type interface in global.d.ts accordingly.

" + } + ] + } + ], + "meta": { + "title": "Tibi Svelte Starter — Modern CMS Template", + "description": "Svelte 5, Tailwind CSS 4, SSR, i18n and Playwright tests — the perfect starter kit.", + "keywords": "svelte, tibi, cms, starter, template" + } + }, + { + "id": "about-de", + "_id": "about-de", "active": true, "type": "page", "lang": "de", "translationKey": "about", - "name": "Über uns", + "name": "Über das Template", "path": "/ueber-uns", "blocks": [ { - "type": "richtext", - "headline": "Über uns", + "type": "hero", + "headline": "Über das Template", "headlineH1": true, - "text": "

Wir sind ein Demo-Unternehmen.

" + "subline": "Gebaut für Teams, die schnell professionelle Webprojekte umsetzen wollen.", + "containerWidth": "full", + "heroImage": { + "externalUrl": "https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1920&q=80" + } + }, + { + "type": "richtext", + "headline": "Warum dieses Template?", + "padding": { "top": "lg", "bottom": "md" }, + "text": "

Das Tibi Svelte Starter vereint bewährte Patterns aus dutzenden Projekten in einem sofort einsetzbaren Fundament:

" + }, + { + "type": "richtext", + "headline": "Technologie-Stack", + "padding": { "top": "md", "bottom": "lg" }, + "externalImageUrl": "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=800&q=80", + "imagePosition": "left", + "text": "

Jede Komponente wurde sorgfältig ausgewählt:

" } ], "meta": { - "title": "Über uns", - "description": "Erfahren Sie mehr über uns" + "title": "Über das Template — Tibi Svelte Starter", + "description": "Architektur und Tech-Stack des Tibi Svelte Starter Templates.", + "keywords": "svelte, über uns, template, architektur" } }, { - "id": "contact", - "_id": "contact", + "id": "about-en", + "_id": "about-en", + "active": true, + "type": "page", + "lang": "en", + "translationKey": "about", + "name": "About the Template", + "path": "/about", + "blocks": [ + { + "type": "hero", + "headline": "About the Template", + "headlineH1": true, + "subline": "Built for teams who want to ship professional web projects fast.", + "containerWidth": "full", + "heroImage": { + "externalUrl": "https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1920&q=80" + } + }, + { + "type": "richtext", + "headline": "Why this template?", + "padding": { "top": "lg", "bottom": "md" }, + "text": "

The Tibi Svelte Starter combines proven patterns from dozens of projects into an immediately usable foundation:

" + }, + { + "type": "richtext", + "headline": "Technology Stack", + "padding": { "top": "md", "bottom": "lg" }, + "externalImageUrl": "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=800&q=80", + "imagePosition": "left", + "text": "

Every component was carefully chosen:

" + } + ], + "meta": { + "title": "About the Template — Tibi Svelte Starter", + "description": "Architecture and tech stack of the Tibi Svelte Starter Template.", + "keywords": "svelte, about, template, architecture" + } + }, + { + "id": "contact-de", + "_id": "contact-de", "active": true, "type": "page", "lang": "de", @@ -62,15 +244,57 @@ "path": "/kontakt", "blocks": [ { - "type": "richtext", + "type": "hero", "headline": "Kontakt", "headlineH1": true, - "text": "

Schreiben Sie uns eine Nachricht.

" + "subline": "Fragen, Feedback oder Projektanfragen? Schreib uns!", + "containerWidth": "full", + "heroImage": { + "externalUrl": "https://images.unsplash.com/photo-1423666639041-f56000c27a9a?w=1920&q=80" + } + }, + { + "type": "contact-form", + "headline": "Nachricht senden", + "padding": { "top": "lg", "bottom": "lg" } } ], "meta": { - "title": "Kontakt", - "description": "Kontaktieren Sie uns" + "title": "Kontakt — Tibi Svelte Starter", + "description": "Nimm Kontakt mit uns auf.", + "keywords": "kontakt, anfrage" + } + }, + { + "id": "contact-en", + "_id": "contact-en", + "active": true, + "type": "page", + "lang": "en", + "translationKey": "contact", + "name": "Contact", + "path": "/contact", + "blocks": [ + { + "type": "hero", + "headline": "Contact", + "headlineH1": true, + "subline": "Questions, feedback or project inquiries? Get in touch!", + "containerWidth": "full", + "heroImage": { + "externalUrl": "https://images.unsplash.com/photo-1423666639041-f56000c27a9a?w=1920&q=80" + } + }, + { + "type": "contact-form", + "headline": "Send a message", + "padding": { "top": "lg", "bottom": "lg" } + } + ], + "meta": { + "title": "Contact — Tibi Svelte Starter", + "description": "Get in touch with us.", + "keywords": "contact, inquiry" } } -] \ No newline at end of file +] diff --git a/frontend/mocking/navigation.json b/frontend/mocking/navigation.json index a06c97b..e198d9e 100644 --- a/frontend/mocking/navigation.json +++ b/frontend/mocking/navigation.json @@ -5,18 +5,20 @@ "language": "de", "type": "header", "elements": [ - { - "name": "Startseite", - "page": "home" - }, - { - "name": "Über uns", - "page": "about" - }, - { - "name": "Kontakt", - "page": "contact" - } + { "name": "Startseite", "page": "/" }, + { "name": "Über uns", "page": "/ueber-uns" }, + { "name": "Kontakt", "page": "/kontakt" } + ] + }, + { + "id": "header-en", + "_id": "header-en", + "language": "en", + "type": "header", + "elements": [ + { "name": "Home", "page": "/" }, + { "name": "About", "page": "/about" }, + { "name": "Contact", "page": "/contact" } ] }, { @@ -25,14 +27,22 @@ "language": "de", "type": "footer", "elements": [ - { - "name": "Impressum", - "page": "imprint" - }, - { - "name": "Datenschutz", - "page": "privacy" - } + { "name": "Startseite", "page": "/" }, + { "name": "Über uns", "page": "/ueber-uns" }, + { "name": "Kontakt", "page": "/kontakt" }, + { "name": "GitHub", "external": true, "externalUrl": "https://github.com" } + ] + }, + { + "id": "footer-en", + "_id": "footer-en", + "language": "en", + "type": "footer", + "elements": [ + { "name": "Home", "page": "/" }, + { "name": "About", "page": "/about" }, + { "name": "Contact", "page": "/contact" }, + { "name": "GitHub", "external": true, "externalUrl": "https://github.com" } ] } -] \ No newline at end of file +] diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 63743fc..da7f4ce 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,11 +1,14 @@ -
-
- Tibi Svelte Starter -