Files
fotospiel-app/app/Support/JoinTokenLayoutRegistry.php
2025-10-28 18:28:22 +01:00

267 lines
10 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Support;
use App\Models\InviteLayout;
class JoinTokenLayoutRegistry
{
/**
* Layout definitions for printable invite cards.
*
* @var array<string, array>
*/
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 200g/m² Kartenpapier empfohlen.',
],
],
];
/**
* Get layout definitions.
*
* @return array<int, array<string, mixed>>
*/
public static function all(): array
{
$customLayouts = InviteLayout::query()
->where('is_active', true)
->orderBy('name')
->get();
if ($customLayouts->isNotEmpty()) {
return $customLayouts
->map(fn (InviteLayout $layout) => self::normalize(self::fromModel($layout)))
->values()
->all();
}
return array_values(array_map(fn ($layout) => self::normalize($layout), self::LAYOUTS));
}
/**
* Find a layout definition.
*/
public static function find(string $id): ?array
{
$custom = InviteLayout::query()
->where('slug', $id)
->where('is_active', true)
->first();
if ($custom) {
return self::normalize(self::fromModel($custom));
}
$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',
'badge_label' => 'Digitale Gästebox',
'instructions_heading' => "So funktioniert's",
'link_heading' => 'Alternative zum Einscannen',
'cta_label' => 'Scan mich & starte direkt',
'cta_caption' => 'Scan mich & starte direkt',
'link_label' => null,
'logo_url' => null,
'qr' => [
'size_px' => 320,
],
'svg' => [
'width' => 1080,
'height' => 1520,
],
'background_gradient' => null,
'instructions' => [],
'formats' => ['pdf', 'svg'],
];
return array_replace_recursive($defaults, $layout);
}
private static function fromModel(InviteLayout $layout): array
{
$preview = $layout->preview ?? [];
$options = $layout->layout_options ?? [];
$instructions = $layout->instructions ?? [];
return array_filter([
'id' => $layout->slug,
'name' => $layout->name,
'subtitle' => $layout->subtitle,
'description' => $layout->description,
'paper' => $layout->paper,
'orientation' => $layout->orientation,
'background' => $preview['background'] ?? null,
'background_gradient' => $preview['background_gradient'] ?? null,
'text' => $preview['text'] ?? null,
'accent' => $preview['accent'] ?? null,
'secondary' => $preview['secondary'] ?? null,
'badge' => $preview['badge'] ?? null,
'badge_label' => $options['badge_label'] ?? null,
'instructions_heading' => $options['instructions_heading'] ?? null,
'link_heading' => $options['link_heading'] ?? null,
'cta_label' => $options['cta_label'] ?? null,
'cta_caption' => $options['cta_caption'] ?? null,
'link_label' => $options['link_label'] ?? null,
'logo_url' => $options['logo_url'] ?? null,
'qr' => array_filter([
'size_px' => $preview['qr']['size_px'] ?? $options['qr']['size_px'] ?? $preview['qr_size_px'] ?? $options['qr_size_px'] ?? null,
]),
'svg' => array_filter([
'width' => $preview['svg']['width'] ?? $options['svg']['width'] ?? $preview['svg_width'] ?? $options['svg_width'] ?? null,
'height' => $preview['svg']['height'] ?? $options['svg']['height'] ?? $preview['svg_height'] ?? $options['svg_height'] ?? null,
]),
'formats' => $options['formats'] ?? ['pdf', 'svg'],
'instructions' => $instructions,
], fn ($value) => $value !== null && $value !== []);
}
/**
* Map layouts into an API-ready response structure, attaching URLs.
*
* @param callable(string $layoutId, string $format): string $urlResolver
* @return array<int, array<string, mixed>>
*/
public static function toResponse(callable $urlResolver): array
{
return array_map(function (array $layout) use ($urlResolver) {
$formats = $layout['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());
}
}