Dropped on request. The two bios were never written, so the section rendered two red placeholder paragraphs asking for three to four sentences each. Removed in all four places so nothing dangles: the .about-bios markup block and its CSS rule in index.astro, the laraBio/pascalBio keys in content/site.json, and the two fields in the admin editor's schema - otherwise the editor would still offer inputs that no longer render. The about section keeps its eyebrow, title, quote and three paragraphs, the last of which already explains that the name is built from Lara and Pascal plus "paso". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
353 lines
14 KiB
JavaScript
353 lines
14 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: 'Google-Vorschau (rechts neben dem Hero-Text)',
|
||
type: 'group',
|
||
itemFields: [
|
||
{
|
||
key: 'customImage',
|
||
label: 'Eigenes Bild statt der Vorschau (leer = eingebaute Vorschau anzeigen)',
|
||
type: 'image',
|
||
},
|
||
{ key: 'label', label: 'Beschriftung oben', type: 'text' },
|
||
{ key: 'todayTag', label: 'Karte 1 – Tag-Label', type: 'text' },
|
||
{ key: 'todayUrl', label: 'Karte 1 – URL-Zeile', type: 'text' },
|
||
{ key: 'todayTitle', label: 'Karte 1 – Titel', type: 'text' },
|
||
{ key: 'todayDesc', label: 'Karte 1 – Beschreibung', type: 'textarea' },
|
||
{ key: 'dividerText', label: 'Trenntext zwischen den Karten', type: 'text' },
|
||
{ key: 'possibleTag', label: 'Karte 2 – Tag-Label', type: 'text' },
|
||
{ key: 'possibleUrl', label: 'Karte 2 – URL-Zeile', type: 'text' },
|
||
{ key: 'possibleTitle', label: 'Karte 2 – Titel', type: 'text' },
|
||
{ key: 'possibleDesc', label: 'Karte 2 – Beschreibung', type: 'richtext' },
|
||
{ key: 'possibleMeta', label: 'Karte 2 – Bewertungszeile', type: 'text' },
|
||
],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
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 des Vergleichs (leer = eingebauten Vergleich anzeigen)',
|
||
type: 'image',
|
||
},
|
||
{ key: 'title', label: 'Überschrift', type: 'text' },
|
||
{ key: 'lead', label: 'Text', type: 'richtext' },
|
||
{ key: 'beforeLabel', label: 'Label links ("vorher")', type: 'text' },
|
||
{ key: 'pdfName', label: 'Dateiname im PDF-Beispiel', type: 'text' },
|
||
{ key: 'pdfMeta', label: 'Zusatzzeile im PDF-Beispiel', type: 'text' },
|
||
{ key: 'afterLabel', label: 'Label rechts ("nachher")', type: 'text' },
|
||
{ 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: '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 = Platzhalter)', 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' },
|
||
],
|
||
},
|
||
];
|