*/ private const LAYOUTS = [ 'modern-poster' => [ 'id' => 'modern-poster', 'name' => 'Modern Poster', 'subtitle' => 'Große, auffällige Fläche – perfekt für den Eingangsbereich.', 'description' => 'Helle Posteroptik mit diagonalem Farbband und deutlicher Call-to-Action.', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#F8FAFC', 'text' => '#0F172A', 'accent' => '#6366F1', 'secondary' => '#CBD5F5', 'badge' => '#0EA5E9', 'qr' => ['size_px' => 340], 'svg' => ['width' => 1080, 'height' => 1520], 'instructions' => [ 'Scanne den Code und tritt dem Event direkt bei.', 'Speichere deine Lieblingsmomente mit Foto-Uploads.', 'Merke dir dein Gäste-Pseudonym für Likes und Badges.', ], ], 'elegant-frame' => [ 'id' => 'elegant-frame', 'name' => 'Elegant Frame', 'subtitle' => 'Ein ruhiges Layout mit Fokus auf Eleganz.', 'description' => 'Serifen-Schrift, pastellige Flächen und dezente Rahmen für elegante Anlässe.', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#FBF7F2', 'text' => '#2B1B13', 'accent' => '#C08457', 'secondary' => '#E6D5C3', 'badge' => '#8B5CF6', 'qr' => ['size_px' => 300], 'svg' => ['width' => 1080, 'height' => 1520], 'instructions' => [ 'QR-Code scannen oder Link im Browser eingeben.', 'Name eingeben, Lieblingssprache auswählen und loslegen.', 'Zeige diesen Druck am Empfang als Orientierung für Gäste.', ], ], 'bold-gradient' => [ 'id' => 'bold-gradient', 'name' => 'Bold Gradient', 'subtitle' => 'Farbverlauf mit starkem Kontrast.', 'description' => 'Ein kraftvolles Farbstatement mit großem QR-Code – ideal für Partys.', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#F97316', 'background_gradient' => [ 'angle' => 190, 'stops' => ['#F97316', '#EC4899', '#8B5CF6'], ], 'text' => '#FFFFFF', 'accent' => '#FFFFFF', 'secondary' => 'rgba(255,255,255,0.72)', 'badge' => '#1E293B', 'qr' => ['size_px' => 360], 'svg' => ['width' => 1080, 'height' => 1520], 'instructions' => [ 'Sofort scannen – der QR-Code führt direkt zum Event.', 'Fotos knipsen, Challenges lösen und Likes sammeln.', 'Teile den Link mit Freund:innen, falls kein Scan möglich ist.', ], ], 'photo-strip' => [ 'id' => 'photo-strip', 'name' => 'Photo Strip', 'subtitle' => 'Layout mit Fotostreifen-Anmutung und Checkliste.', 'description' => 'Horizontale Teilung, Platz für Hinweise und Storytelling.', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#FFFFFF', 'text' => '#111827', 'accent' => '#0EA5E9', 'secondary' => '#94A3B8', 'badge' => '#334155', 'qr' => ['size_px' => 320], 'svg' => ['width' => 1080, 'height' => 1520], 'instructions' => [ 'Schritt 1: QR-Code scannen oder Kurzlink nutzen.', 'Schritt 2: Profilname eingeben – kreativ sein!', 'Schritt 3: Fotos hochladen und Teamaufgaben lösen.', ], ], 'minimal-card' => [ 'id' => 'minimal-card', 'name' => 'Minimal Card', 'subtitle' => 'Kleine Karte – mehrfach druckbar als Tischaufsteller.', 'description' => 'Schlichtes Kartenformat mit klarer Typografie und viel Weißraum.', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#F9FAFB', 'text' => '#111827', 'accent' => '#9333EA', 'secondary' => '#E0E7FF', 'badge' => '#64748B', 'qr' => ['size_px' => 280], 'svg' => ['width' => 1080, 'height' => 1520], 'instructions' => [ 'Code scannen, Profil erstellen, Erinnerungen festhalten.', 'Halte diese Karte an mehreren Stellen bereit.', 'Für Ausdrucke auf 200 g/m² Kartenpapier empfohlen.', ], ], ]; /** * Get layout definitions. * * @return array> */ public static function all(): array { return array_values(array_map(fn ($layout) => self::normalize($layout), self::LAYOUTS)); } /** * Find a layout definition. */ public static function find(string $id): ?array { $layout = self::LAYOUTS[$id] ?? null; return $layout ? self::normalize($layout) : null; } /** * Normalize and merge default values. */ private static function normalize(array $layout): array { $defaults = [ 'subtitle' => '', 'description' => '', 'paper' => 'a4', 'orientation' => 'portrait', 'background' => '#F9FAFB', 'text' => '#0F172A', 'accent' => '#6366F1', 'secondary' => '#CBD5F5', 'badge' => '#2563EB', 'qr' => [ 'size_px' => 320, ], 'svg' => [ 'width' => 1080, 'height' => 1520, ], 'background_gradient' => null, 'instructions' => [], ]; return array_replace_recursive($defaults, $layout); } /** * Map layouts into an API-ready response structure, attaching URLs. * * @param callable(string $layoutId, string $format): string $urlResolver * @return array> */ public static function toResponse(callable $urlResolver): array { return array_map(function (array $layout) use ($urlResolver) { $formats = ['pdf', 'svg']; return [ 'id' => $layout['id'], 'name' => $layout['name'], 'description' => $layout['description'], 'subtitle' => $layout['subtitle'], 'preview' => [ 'background' => $layout['background'], 'background_gradient' => $layout['background_gradient'], 'accent' => $layout['accent'], 'text' => $layout['text'], ], 'formats' => $formats, 'download_urls' => collect($formats) ->mapWithKeys(fn ($format) => [$format => $urlResolver($layout['id'], $format)]) ->all(), ]; }, self::all()); } }