*/ public static function all(): array { return [ 'bildkopf' => [ 'name' => 'Bildkopf', 'description' => 'Großes Beitragsbild über die volle Breite, Titel darauf. Für Projekte und alles, was von einem Bild lebt.', ], 'klassisch' => [ 'name' => 'Klassisch', 'description' => 'Titel und Vorspann in einer schmalen Spalte, Bild darunter. Ruhig zu lesen, gut für lange Texte.', ], 'magazin' => [ 'name' => 'Magazin', 'description' => 'Titel neben den Eckdaten, danach Text und Galerie. Für Beiträge, bei denen Kunde, Leistung und Jahr zählen.', ], ]; } public static function exists(string $key): bool { return isset(self::all()[$key]); } /** Das vollständige Dokument eines neuen Beitrags in der gewählten Form. */ public static function document(string $key, string $title): array { $document = [ 'schemaVersion' => 18, 'title' => $title, 'description' => 'Ein neuer Beitrag. Ersetze diese Beschreibung durch eine kurze Zusammenfassung – sie erscheint in Suchergebnissen und auf der Beitragskarte.', 'blocks' => match ($key) { 'klassisch' => self::klassisch($title), 'magazin' => self::magazin($title), default => self::bildkopf($title), }, ]; return $document; } /** * Bildkopf: das Beitragsbild füllt den Kopf, der Titel liegt darauf. * * Das Bild ist beim Anlegen noch keines - es kommt ein Platzhalter, und im Designbereich wird * daraus mit zwei Klicks das Beitragsbild. Ein dunkler Kopf ohne Bild sieht dabei nicht kaputt * aus, sondern schlicht, und das ist der Punkt: kein Aufbau darf auf ein Bild angewiesen sein, * das noch niemand hochgeladen hat. */ private static function bildkopf(string $title): array { return [ self::section('kopf', [ self::badge('kopf-label', 'Beitrag'), self::heading('kopf-titel', $title, 1, ['fontSize' => '46px', 'fontWeight' => '800', 'lineHeight' => 1.12, 'letterSpacing' => '-1px', 'maxWidth' => '900px', 'margin' => self::spacing('0px', '18px', 'auto', 'auto')]), self::line('kopf-linie'), ], [ 'backgroundColor' => '@ink', 'color' => '#ffffff', 'textAlign' => 'center', 'display' => 'flex', 'alignItems' => 'flex-end', 'justifyContent' => 'center', 'minHeight' => '58vh', 'padding' => self::spacing('110px', '52px', '0px', '0px'), ], [], ['entrance' => 'fade']), self::narrow('text', [ self::heading('text-h', 'Über den Beitrag', 2, ['fontSize' => '28px', 'fontWeight' => '800', 'color' => '@ink']), self::text('text-1', 'Schreibe hier den ersten Absatz. Er beantwortet am besten die Frage, warum jemand weiterlesen sollte.'), self::text('text-2', 'Der zweite Absatz darf ins Detail gehen: was die Aufgabe war, wie der Weg aussah, was dabei herauskam.'), ]), self::section('galerie', [ self::badge('galerie-label', 'Bildergalerie', true), self::heading('galerie-h', 'Eindrücke', 2, ['fontSize' => '30px', 'fontWeight' => '800', 'color' => '@ink']), self::gallery('galerie-bilder', 2), ], ['backgroundColor' => '@paper', 'padding' => self::spacing('60px', '60px', '0px', '0px')], [], ['entrance' => 'rise']), self::section('weiter', [self::nav('weiter-nav')], ['padding' => self::spacing('50px', '50px', '0px', '0px')], [], ['entrance' => 'fade']), self::section('aufruf', [self::cta('aufruf-band')], ['backgroundColor' => '@ink', 'color' => '#ffffff', 'padding' => self::spacing('62px', '62px', '0px', '0px')], [], ['entrance' => 'fade']), ]; } /** Klassisch: Titel und Vorspann schmal, Bild darunter, dann der Text. */ private static function klassisch(string $title): array { return [ self::narrow('kopf', [ self::badge('kopf-label', 'Beitrag', true), self::heading('kopf-titel', $title, 1, ['fontSize' => '42px', 'fontWeight' => '800', 'lineHeight' => 1.15, 'letterSpacing' => '-0.8px']), self::text('kopf-vorspann', 'Ein Vorspann in zwei, drei Sätzen. Er steht für sich und sagt, worum es geht – wer nur ihn liest, weiß Bescheid.', ['fontSize' => '20px', 'lineHeight' => 1.6]), ], '70px', '30px'), self::section('bild', [self::image('bild-1')], ['padding' => self::spacing('0px', '10px', '0px', '0px')], [], ['entrance' => 'zoom']), self::narrow('text', [ self::text('text-1', 'Der erste Absatz des Haupttextes. Ab hier darf es ausführlich werden.'), self::heading('text-h', 'Ein Zwischentitel', 2, ['fontSize' => '26px', 'fontWeight' => '800', 'color' => '@ink', 'margin' => self::spacing('34px', '12px', '0px', '0px')]), self::text('text-2', 'Zwischentitel machen einen langen Text überhaupt erst lesbar. Setze alle drei bis fünf Absätze einen.'), ]), self::section('weiter', [self::nav('weiter-nav')], ['backgroundColor' => '@paper', 'padding' => self::spacing('50px', '50px', '0px', '0px')], [], ['entrance' => 'fade']), ]; } /** Magazin: Titel links, Eckdaten rechts, danach Text und Galerie. */ private static function magazin(string $title): array { return [ self::section('kopf', [ ['id' => 'kopf-zeile', 'type' => 'row', 'style' => ['desktop' => ['gridColumns' => 2, 'gap' => '40px', 'alignItems' => 'flex-end'], 'mobile' => ['gridColumns' => 1, 'gap' => '24px']], 'children' => [ ['id' => 'kopf-links', 'type' => 'column', 'children' => [ self::badge('kopf-label', 'Beitrag', true), self::heading('kopf-titel', $title, 1, ['fontSize' => '44px', 'fontWeight' => '800', 'lineHeight' => 1.12, 'letterSpacing' => '-1px']), ]], ['id' => 'kopf-rechts', 'type' => 'column', 'style' => ['desktop' => [ 'backgroundColor' => '#ffffff', 'borderRadius' => self::corners('16px'), 'borderWidth' => self::spacing('1px', '1px', '1px', '1px'), 'borderStyle' => 'solid', 'borderColor' => '@line', 'padding' => self::spacing('22px', '22px', '24px', '24px'), ]], 'children' => [ self::badge('eck-label', 'Eckdaten', true), self::text('eck-daten', "Kunde: noch einzutragen\nLeistung: noch einzutragen\nJahr: noch einzutragen", ['fontSize' => '15px']), ]], ]], ], ['backgroundColor' => '@paper', 'padding' => self::spacing('72px', '48px', '0px', '0px')], [], ['entrance' => 'fade']), self::narrow('text', [ self::text('text-1', 'Der Einstieg in den Beitrag. Was war die Aufgabe, und warum war sie nicht trivial?'), self::heading('text-h', 'Der Weg', 2, ['fontSize' => '26px', 'fontWeight' => '800', 'color' => '@ink', 'margin' => self::spacing('30px', '12px', '0px', '0px')]), self::text('text-2', 'Was getan wurde, in der Reihenfolge, in der es getan wurde.'), self::heading('text-h2', 'Das Ergebnis', 2, ['fontSize' => '26px', 'fontWeight' => '800', 'color' => '@ink', 'margin' => self::spacing('30px', '12px', '0px', '0px')]), self::text('text-3', 'Was sich seitdem geändert hat – am besten ohne Superlative.'), ]), self::section('galerie', [self::gallery('galerie-bilder', 3)], ['backgroundColor' => '@paper', 'padding' => self::spacing('56px', '56px', '0px', '0px')], [], ['entrance' => 'rise']), self::section('weiter', [self::nav('weiter-nav')], ['padding' => self::spacing('50px', '50px', '0px', '0px')], [], ['entrance' => 'fade']), ]; } // ---------------------------------------------------------------- Bausteine /** * Ein Abschnitt, wahlweise mit Einblendung. * * Die Einblendung läuft beim Hineinscrollen, wo der Browser eine Scroll-Zeitachse kennt, und * sonst beim Laden - beides ohne Skript, beides hinter der Bewegungsschranke. Deshalb darf * sie auch weiter unten stehen, ohne ins Leere zu laufen. */ private static function section(string $id, array $children, array $desktop = [], array $mobile = [], array $effects = []): array { $block = ['id' => $id, 'type' => 'section', 'children' => $children]; if ([] !== $effects) { $block['layout'] = ['effects' => $effects]; } if ([] !== $desktop || [] !== $mobile) { $block['style'] = array_filter(['desktop' => $desktop, 'mobile' => $mobile]); } return $block; } /** Ein schmaler Abschnitt steht in der Mitte: maximale Breite ohne automatischen Rand ist eine halbe Einstellung. */ private static function narrow(string $id, array $children, string $top = '54px', string $bottom = '54px'): array { return self::section($id, $children, [ 'maxWidth' => '820px', 'margin' => self::spacing('0px', '0px', 'auto', 'auto'), 'padding' => self::spacing($top, $bottom, '0px', '0px'), ], [], ['entrance' => 'rise']); } private static function heading(string $id, string $text, int $level, array $style = []): array { return array_filter([ 'id' => $id, 'type' => 'heading', 'props' => ['text' => $text, 'level' => $level], 'style' => [] === $style ? null : ['desktop' => $style], ], static fn ($value) => null !== $value); } private static function text(string $id, string $text, array $style = []): array { return array_filter([ 'id' => $id, 'type' => 'text', 'props' => ['text' => $text], 'style' => [] === $style ? null : ['desktop' => $style], ], static fn ($value) => null !== $value); } /** Die kleine Beschriftung über einer Überschrift - hell auf dunklem Kopf, sonst in der Akzentfarbe. */ private static function badge(string $id, string $label, bool $quiet = false): array { return $quiet ? self::text($id, $label, ['fontSize' => '12px', 'fontWeight' => '700', 'letterSpacing' => '1.6px', 'textTransform' => 'uppercase', 'color' => '@link', 'margin' => self::spacing('0px', '10px', '0px', '0px')]) : self::text($id, $label, [ 'display' => 'inline-block', 'backgroundColor' => '@link', 'color' => '#ffffff', 'borderRadius' => self::corners('999px'), 'padding' => self::spacing('7px', '7px', '16px', '16px'), 'fontSize' => '13px', 'fontWeight' => '700', 'letterSpacing' => '1px', 'textTransform' => 'uppercase', 'margin' => self::spacing('0px', '18px', 'auto', 'auto'), ]); } private static function line(string $id): array { return ['id' => $id, 'type' => 'divider', 'style' => ['desktop' => [ 'width' => '60px', 'height' => '4px', 'backgroundColor' => '@link', 'borderRadius' => self::corners('2px'), 'borderWidth' => self::spacing('0px', '0px', '0px', '0px'), 'margin' => self::spacing('0px', '0px', 'auto', 'auto'), ]]]; } private static function image(string $id): array { return ['id' => $id, 'type' => 'image', 'props' => [ 'src' => '/assets/image-placeholder.svg', 'alt' => '', 'decorative' => true, 'width' => 1200, 'height' => 675, 'caption' => '', ], 'style' => ['desktop' => ['imageRadius' => self::corners('18px')]]]; } private static function gallery(string $id, int $count): array { $children = []; for ($index = 1; $index <= $count; ++$index) { $children[] = ['id' => $id.'-'.$index, 'type' => 'image', 'props' => [ 'src' => '/assets/image-placeholder.svg', 'alt' => '', 'decorative' => true, 'width' => 1200, 'height' => 900, ]]; } return ['id' => $id, 'type' => 'gallery', 'children' => $children, 'style' => ['desktop' => [ 'gridColumns' => $count, 'gap' => '18px', 'margin' => self::spacing('24px', '0px', '0px', '0px'), ], 'mobile' => ['gridColumns' => 1]]]; } private static function nav(string $id): array { return ['id' => $id, 'type' => 'postnav', 'props' => ['previousLabel' => 'Vorheriger Beitrag', 'nextLabel' => 'Nächster Beitrag']]; } private static function cta(string $id): array { return ['id' => $id, 'type' => 'cta', 'props' => [ 'title' => 'Noch Fragen?', 'text' => 'Schreib uns – wir antworten in der Regel am selben Werktag.', 'buttonText' => 'Kontakt aufnehmen', 'href' => '/#kontakt', ]]; } private static function spacing(string $top, string $bottom, string $left, string $right): array { return ['top' => $top, 'bottom' => $bottom, 'left' => $left, 'right' => $right]; } private static function corners(string $value): array { return ['topLeft' => $value, 'topRight' => $value, 'bottomRight' => $value, 'bottomLeft' => $value]; } }