- Mappe: each design now sits in a CSS laptop frame with a phone view beside it. Maison Verte and Tanzhaus Kranich play a short muted scroll loop (565/728 KB, H.264), started only while in view and not at all with prefers-reduced-motion. Atelier Rosenhain is a short one-pager, so it keeps its still. - Hero: the Google result mock-up is replaced by a laptop + phone showing two of the designs. customImage still overrides it. - Admin schema follows: hero.visual laptop/phone, mappe mobileImage and video; the Google-card fields are gone. - "KI" now appears once, honestly, in the About paragraph. Replaced in the meta description, hero lead, highlight II, services lead and card 02, process step 03, the About quote and the footer. The section title now reads "Individuell, sorgfältig, technisch solide." Em dashes in the prototypes were replaced in the browser before recording, so none show up in the videos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
342 lines
13 KiB
JavaScript
342 lines
13 KiB
JavaScript
const { FONT_NAMES } = require('./fonts');
|
||
|
||
/**
|
||
* Describes the editable shape of content/site.json (file: 'content', the
|
||
* default) and content/theme.json (file: 'theme') for the admin UI.
|
||
*
|
||
* Field types: text, textarea, richtext, email, boolean, image,
|
||
* list-string, list-richtext, list-object, group, select, size, color.
|
||
* "richtext"/"list-richtext" fields are edited with a WYSIWYG toolbar
|
||
* (bold/italic/underline + per-selection font) and stored as a small
|
||
* allowlisted HTML subset - see admin/lib/rich-sanitize.js for what's
|
||
* allowed and src/lib/content.ts for how it's rendered on the site.
|
||
* "group" is a single nested object (not repeatable) described by itemFields.
|
||
* "color" fields store { r, g, b, a }. "size" fields store a CSS length (e.g. "1.25rem").
|
||
*/
|
||
module.exports = [
|
||
{
|
||
key: 'meta',
|
||
label: 'SEO / Meta',
|
||
fields: [{ key: 'description', label: 'Meta-Beschreibung', type: 'textarea' }],
|
||
},
|
||
{
|
||
key: 'nav',
|
||
label: 'Navigation',
|
||
fields: [
|
||
{ key: 'logo', label: 'Logo', type: 'image' },
|
||
{ key: 'logoAlt', label: 'Logo Alt-Text', type: 'text' },
|
||
{ key: 'ctaLabel', label: 'Button-Text', type: 'text' },
|
||
{
|
||
key: 'links',
|
||
label: 'Menüpunkte',
|
||
type: 'list-object',
|
||
itemLabel: 'Menüpunkt',
|
||
itemFields: [
|
||
{ key: 'label', label: 'Text', type: 'text' },
|
||
{ key: 'anchor', label: 'Sprungziel (Abschnitt-ID, ohne #)', type: 'text' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'hero',
|
||
label: 'Hero (ganz oben)',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'titleBefore', label: 'Titel – Teil 1', type: 'text' },
|
||
{ key: 'titleEmphasis', label: 'Titel – hervorgehobenes Wort', type: 'text' },
|
||
{ key: 'titleAfter', label: 'Titel – Teil 2', type: 'text' },
|
||
{ key: 'lead', label: 'Einleitungstext', type: 'richtext' },
|
||
{ key: 'ctaLabel', label: 'Button-Text', type: 'text' },
|
||
{
|
||
key: 'visual',
|
||
label: 'Geräte-Ansicht (rechts neben dem Hero-Text)',
|
||
type: 'group',
|
||
itemFields: [
|
||
{
|
||
key: 'customImage',
|
||
label: 'Eigenes Bild statt Laptop und Handy (leer = Geräte anzeigen)',
|
||
type: 'image',
|
||
},
|
||
{ key: 'laptop', label: 'Bild im Laptop (Querformat 16:9)', type: 'image' },
|
||
{ key: 'phone', label: 'Bild im Handy (Hochformat)', type: 'image' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'warum',
|
||
label: 'Abschnitt 02 – Warum uns',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'lead', label: 'Text', type: 'richtext' },
|
||
{
|
||
key: 'readability',
|
||
label: 'PDF-vs-Tabelle-Vergleich',
|
||
type: 'group',
|
||
itemFields: [
|
||
{
|
||
key: 'customImage',
|
||
label: 'Eigenes Bild statt der Tabelle (leer = Öffnungszeiten-Tabelle anzeigen)',
|
||
type: 'image',
|
||
},
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'lead', label: 'Text', type: 'richtext' },
|
||
{ key: 'hoursTitle', label: 'Titel der Tabelle', type: 'text' },
|
||
{
|
||
key: 'hoursRows',
|
||
label: 'Tabellenzeilen',
|
||
type: 'list-object',
|
||
itemLabel: 'Zeile',
|
||
itemFields: [
|
||
{ key: 'day', label: 'Tag', type: 'text' },
|
||
{ key: 'time', label: 'Uhrzeit', type: 'text' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'preis',
|
||
label: 'Abschnitt 01 – Preis',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'amount', label: 'Preis', type: 'text' },
|
||
{ key: 'amountNote', label: 'Preis-Zusatz', type: 'text' },
|
||
{ key: 'lead', label: 'Einleitungstext', type: 'richtext' },
|
||
{ key: 'slotsNote', label: 'Plätze-Hinweis', type: 'text' },
|
||
{
|
||
key: 'features',
|
||
label: 'Leistungspunkte',
|
||
type: 'list-object',
|
||
itemLabel: 'Punkt',
|
||
itemFields: [
|
||
{ key: 'text', label: 'Text', type: 'text' },
|
||
{ key: 'excluded', label: 'Als "nicht enthalten" markieren', type: 'boolean' },
|
||
],
|
||
},
|
||
{ key: 'asideTitle', label: 'Kasten-Überschrift', type: 'text' },
|
||
{ key: 'asideParagraph1', label: 'Kasten-Absatz 1', type: 'richtext' },
|
||
{ key: 'asideParagraph2', label: 'Kasten-Absatz 2', type: 'richtext' },
|
||
{ key: 'ctaLabel', label: 'Button-Text', type: 'text' },
|
||
],
|
||
},
|
||
{
|
||
key: 'highlights',
|
||
label: 'Abschnitt 02 – Die drei Punkte',
|
||
type: 'array-root',
|
||
itemLabel: 'Punkt',
|
||
itemFields: [
|
||
{ key: 'num', label: 'Kennung (z. B. I, II, III)', type: 'text' },
|
||
{ key: 'title', label: 'Titel', type: 'text' },
|
||
{ key: 'text', label: 'Text', type: 'textarea' },
|
||
],
|
||
},
|
||
{
|
||
key: 'leistungen',
|
||
label: 'Abschnitt 03 – Leistungen',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'lead', label: 'Text', type: 'richtext' },
|
||
{
|
||
key: 'items',
|
||
label: 'Leistungen',
|
||
type: 'list-object',
|
||
itemLabel: 'Leistung',
|
||
itemFields: [
|
||
{ key: 'num', label: 'Nummer', type: 'text' },
|
||
{ key: 'title', label: 'Titel', type: 'text' },
|
||
{ key: 'text', label: 'Text', type: 'textarea' },
|
||
],
|
||
},
|
||
{ key: 'nichtMachenTitle', label: '"Was wir nicht machen" – Überschrift', type: 'text' },
|
||
{ key: 'nichtMachen', label: '"Was wir nicht machen" – Punkte', type: 'list-string' },
|
||
],
|
||
},
|
||
{
|
||
key: 'mappe',
|
||
label: 'Abschnitt 04 – Mappe',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{
|
||
key: 'items',
|
||
label: 'Arbeiten',
|
||
type: 'list-object',
|
||
itemLabel: 'Arbeit',
|
||
itemFields: [
|
||
{ key: 'image', label: 'Bildschirmfoto', type: 'image' },
|
||
{ key: 'mobileImage', label: 'Handy-Ansicht (Hochformat, leer = kein Handy)', type: 'image' },
|
||
{ key: 'video', label: 'Video im Laptop (Pfad zu einer .mp4, leer = Standbild)', type: 'text' },
|
||
{ key: 'kategorie', label: 'Kategorie', type: 'text' },
|
||
{ key: 'titel', label: 'Titel / Name', type: 'text' },
|
||
{ key: 'text', label: 'Text', type: 'textarea' },
|
||
{ key: 'nameHidden', label: 'Name noch verstecken (Platzhalter zeigen)', type: 'boolean' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'ablauf',
|
||
label: 'Abschnitt 05 – Ablauf',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{
|
||
key: 'steps',
|
||
label: 'Schritte',
|
||
type: 'list-object',
|
||
itemLabel: 'Schritt',
|
||
itemFields: [
|
||
{ key: 'num', label: 'Nummer', type: 'text' },
|
||
{ key: 'title', label: 'Titel', type: 'text' },
|
||
{ key: 'text', label: 'Text', type: 'textarea' },
|
||
{ key: 'dauer', label: 'Dauer (leer = wird nicht angezeigt)', type: 'text' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'about',
|
||
label: 'Abschnitt 06 – Über uns',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'quote', label: 'Zitat', type: 'text' },
|
||
{ key: 'paragraphs', label: 'Absätze', type: 'list-richtext' },
|
||
{ key: 'photo', label: 'Foto', type: 'image' },
|
||
],
|
||
},
|
||
{
|
||
key: 'faq',
|
||
label: 'Abschnitt 07 – FAQ',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{
|
||
key: 'items',
|
||
label: 'Fragen',
|
||
type: 'list-object',
|
||
itemLabel: 'Frage',
|
||
itemFields: [
|
||
{ key: 'question', label: 'Frage', type: 'text' },
|
||
{ key: 'answer', label: 'Antwort (leer = Platzhalter)', type: 'textarea' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
key: 'kontakt',
|
||
label: 'Abschnitt 08 – Kontakt',
|
||
fields: [
|
||
{ key: 'eyebrow', label: 'Kicker-Zeile', type: 'text' },
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'lead', label: 'Einleitungstext', type: 'richtext' },
|
||
{ key: 'phone', label: 'Telefonnummer (leer = Platzhalter)', type: 'text' },
|
||
{ key: 'hours', label: 'Erreichbarkeit (leer = Platzhalter)', type: 'text' },
|
||
{ key: 'email', label: 'E-Mail-Adresse', type: 'email' },
|
||
{ key: 'howWeWork', label: '"Wie wir arbeiten" (leer = Platzhalter)', type: 'textarea' },
|
||
],
|
||
},
|
||
{
|
||
key: 'footer',
|
||
label: 'Footer',
|
||
fields: [
|
||
{ key: 'laraNachname', label: 'Lara – Nachname (leer = Platzhalter)', type: 'text' },
|
||
{ key: 'pascalNachname', label: 'Pascal – Nachname (leer = Platzhalter)', type: 'text' },
|
||
],
|
||
},
|
||
{
|
||
key: 'impressum',
|
||
label: 'Impressum',
|
||
fields: [
|
||
{ key: 'anbieterAngaben', label: 'Name/Firma + Anschrift (leer = Platzhalter)', type: 'textarea' },
|
||
{ key: 'ustId', label: 'Umsatzsteuer-ID (leer = Platzhalter)', type: 'textarea' },
|
||
{ key: 'verantwortlich', label: 'Verantwortlich für den Inhalt (leer = Platzhalter)', type: 'textarea' },
|
||
{ key: 'streitschlichtung', label: 'Verbraucherstreitbeilegung (§ 36 VSBG, leer = Platzhalter)', type: 'textarea' },
|
||
{
|
||
key: 'haftungSections',
|
||
label: 'Haftungsausschluss – Abschnitte',
|
||
type: 'list-object',
|
||
itemLabel: 'Abschnitt',
|
||
itemFields: [
|
||
{ key: 'heading', label: 'Überschrift', type: 'text' },
|
||
{ key: 'text', label: 'Text', type: 'richtext' },
|
||
],
|
||
},
|
||
{ key: 'note', label: 'Hinweistext am Seitenende', type: 'textarea' },
|
||
],
|
||
},
|
||
{
|
||
key: 'datenschutz',
|
||
label: 'Datenschutz',
|
||
fields: [
|
||
{ key: 'intro', label: 'Einleitung (leer = Platzhalter)', type: 'textarea' },
|
||
{
|
||
key: 'sections',
|
||
label: 'Abschnitte',
|
||
type: 'list-object',
|
||
itemLabel: 'Abschnitt',
|
||
itemFields: [
|
||
{ key: 'heading', label: 'Überschrift', type: 'text' },
|
||
{
|
||
key: 'text',
|
||
label: 'Text (leer = Platzhalter; {email} wird durch die Kontakt-E-Mail ersetzt)',
|
||
type: 'richtext',
|
||
},
|
||
],
|
||
},
|
||
{ key: 'note', label: 'Hinweistext am Seitenende', type: 'textarea' },
|
||
],
|
||
},
|
||
{
|
||
key: 'fonts',
|
||
file: 'theme',
|
||
label: 'Design – Schriftarten',
|
||
fields: [
|
||
{ key: 'heading', label: 'Überschriften-Schrift', type: 'select', options: FONT_NAMES },
|
||
{ key: 'body', label: 'Fließtext-Schrift', type: 'select', options: FONT_NAMES },
|
||
],
|
||
},
|
||
{
|
||
key: 'fontSizes',
|
||
file: 'theme',
|
||
label: 'Design – Schriftgrößen',
|
||
fields: [
|
||
{ key: 'sm', label: 'Klein (Kicker-Zeilen, Feinschrift)', type: 'size', min: 0.6, max: 1.5, step: 0.025 },
|
||
{ key: 'base', label: 'Standard (Fließtext)', type: 'size', min: 0.75, max: 1.5, step: 0.025 },
|
||
{ key: 'md', label: 'Mittel (Einleitungstexte)', type: 'size', min: 0.9, max: 1.75, step: 0.025 },
|
||
{ key: 'lg', label: 'Groß (H3)', type: 'size', min: 1, max: 2.5, step: 0.025 },
|
||
{ key: 'xl', label: 'Größer (H2 mobil)', type: 'size', min: 1.2, max: 3, step: 0.025 },
|
||
{ key: '2xl', label: 'Sehr groß (H2)', type: 'size', min: 1.5, max: 4, step: 0.025 },
|
||
{ key: '3xl', label: 'Riesig (H1)', type: 'size', min: 1.75, max: 5, step: 0.025 },
|
||
],
|
||
},
|
||
{
|
||
key: 'colors',
|
||
file: 'theme',
|
||
label: 'Design – Farben',
|
||
fields: [
|
||
{ key: 'bg', label: 'Hintergrund', type: 'color' },
|
||
{ key: 'bgAlt', label: 'Hintergrund (Variante)', type: 'color' },
|
||
{ key: 'surface', label: 'Flächen / Karten', type: 'color' },
|
||
{ key: 'text', label: 'Text', type: 'color' },
|
||
{ key: 'textMuted', label: 'Text (gedämpft)', type: 'color' },
|
||
{ key: 'border', label: 'Rahmen', type: 'color' },
|
||
{ key: 'primary', label: 'Primärfarbe (Buttons, Links)', type: 'color' },
|
||
{ key: 'primaryDark', label: 'Primärfarbe (Hover)', type: 'color' },
|
||
{ key: 'primaryContrast', label: 'Text auf Primärfarbe', type: 'color' },
|
||
{ key: 'accent', label: 'Akzentfarbe', type: 'color' },
|
||
{ key: 'accentDark', label: 'Akzentfarbe (dunkel)', type: 'color' },
|
||
{ key: 'success', label: 'Erfolg', type: 'color' },
|
||
{ key: 'danger', label: 'Fehler/Warnung', type: 'color' },
|
||
{ key: 'flag', label: 'Platzhalter-Markierung', type: 'color' },
|
||
{ key: 'flagBg', label: 'Platzhalter-Markierung (Hintergrund)', type: 'color' },
|
||
{ key: 'googleBlue', label: 'Linkfarbe in der Google-Vorschau', type: 'color' },
|
||
],
|
||
},
|
||
];
|