further layout preview fixes
This commit is contained in:
@@ -6,6 +6,30 @@ use App\Models\InviteLayout;
|
||||
|
||||
class JoinTokenLayoutRegistry
|
||||
{
|
||||
private const DEFAULT_DESCRIPTION = 'Helft uns, diesen besonderen Tag mit euren schönen Momenten festzuhalten.';
|
||||
|
||||
private const DEFAULT_INSTRUCTIONS = [
|
||||
'QR-Code scannen'."\n".'Kamera-App öffnen und auf den Code richten.',
|
||||
'Webseite öffnen'."\n".'Der Link öffnet direkt das gemeinsame Jubiläumsalbum.',
|
||||
'Fotos hochladen'."\n".'Zeigt eure Lieblingsmomente oder erfüllt kleine Fotoaufgaben, um besondere Erinnerungen beizusteuern.',
|
||||
];
|
||||
|
||||
private const SLOTS_PORTRAIT = [
|
||||
'headline' => ['x' => 0.08, 'y' => 0.1, 'w' => 0.84, 'h' => 0.12, 'fontSize' => 30, 'fontWeight' => 800, 'align' => 'center'],
|
||||
'subtitle' => ['x' => 0.1, 'y' => 0.13, 'w' => 0.8, 'h' => 0.08, 'fontSize' => 18, 'fontWeight' => 600, 'align' => 'center'],
|
||||
'description' => ['x' => 0.1, 'y' => 0.18, 'w' => 0.8, 'h' => 0.12, 'fontSize' => 16, 'lineHeight' => 1.4, 'align' => 'center'],
|
||||
'qr' => ['x' => 0.35, 'y' => 0.4, 'w' => 0.3, 'h' => 0.3],
|
||||
'instructions' => ['x' => 0.12, 'y' => 0.72, 'w' => 0.76, 'h' => 0.16, 'fontSize' => 12, 'lineHeight' => 1.3, 'align' => 'center'],
|
||||
];
|
||||
|
||||
private const SLOTS_FOLDABLE = [
|
||||
'headline' => ['x' => 0.1, 'y' => 0.1, 'w' => 0.8, 'h' => 0.12, 'fontSize' => 28, 'fontWeight' => 800, 'align' => 'center'],
|
||||
'subtitle' => ['x' => 0.12, 'y' => 0.18, 'w' => 0.76, 'h' => 0.08, 'fontSize' => 18, 'fontWeight' => 600, 'align' => 'center'],
|
||||
'description' => ['x' => 0.12, 'y' => 0.24, 'w' => 0.76, 'h' => 0.12, 'fontSize' => 15, 'lineHeight' => 1.4, 'align' => 'center'],
|
||||
'qr' => ['x' => 0.36, 'y' => 0.4, 'w' => 0.28, 'h' => 0.28],
|
||||
'instructions' => ['x' => 0.14, 'y' => 0.72, 'w' => 0.72, 'h' => 0.16, 'fontSize' => 12, 'lineHeight' => 1.3, 'align' => 'center'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Layout definitions for printable invite cards.
|
||||
*
|
||||
@@ -16,15 +40,17 @@ class JoinTokenLayoutRegistry
|
||||
'id' => 'foldable-table-a5',
|
||||
'name' => 'Foldable Table Card (A5)',
|
||||
'subtitle' => 'Doppelseitige Tischkarte zum Falten – QR vorn & hinten.',
|
||||
'description' => 'Zwei identische Hälften auf A4 quer, rechte Seite gespiegelt für sauberes Falten.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'landscape',
|
||||
'panel_mode' => 'double-mirror',
|
||||
'container_padding_px' => 28,
|
||||
'background' => '#F8FAFC',
|
||||
'background_gradient' => [
|
||||
'angle' => 180,
|
||||
'stops' => ['#F8FAFC', '#EEF2FF', '#F8FAFC'],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'landscape',
|
||||
'panel_mode' => 'double-mirror',
|
||||
'format_hint' => 'foldable-a5',
|
||||
'slots' => self::SLOTS_FOLDABLE,
|
||||
'container_padding_px' => 28,
|
||||
'background' => '#F8FAFC',
|
||||
'background_gradient' => [
|
||||
'angle' => 180,
|
||||
'stops' => ['#F8FAFC', '#EEF2FF', '#F8FAFC'],
|
||||
],
|
||||
'text' => '#0F172A',
|
||||
'accent' => '#2563EB',
|
||||
@@ -38,25 +64,22 @@ class JoinTokenLayoutRegistry
|
||||
'link_label' => 'fotospiel.app/DEINCODE',
|
||||
'qr' => ['size_px' => 520],
|
||||
'svg' => ['width' => 1754, 'height' => 1240],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder Kurzlink öffnen.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen, liken & kommentieren.',
|
||||
'Challenges spielen und Punkte sammeln.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
'evergreen-vows' => [
|
||||
'id' => 'evergreen-vows',
|
||||
'name' => 'Evergreen Vows',
|
||||
'subtitle' => 'Romantische Einladung für Trauung & Empfang.',
|
||||
'description' => 'Weiche Pastelltöne, florale Akzente und viel Raum für eine herzliche Begrüßung.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#FBF7F2',
|
||||
'background_gradient' => [
|
||||
'angle' => 165,
|
||||
'stops' => ['#FBF7F2', '#FDECEF', '#F4F0FF'],
|
||||
],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'format_hint' => 'poster-a4',
|
||||
'slots' => self::SLOTS_PORTRAIT,
|
||||
'background' => '#FBF7F2',
|
||||
'background_gradient' => [
|
||||
'angle' => 165,
|
||||
'stops' => ['#FBF7F2', '#FDECEF', '#F4F0FF'],
|
||||
],
|
||||
'text' => '#2C1A27',
|
||||
'accent' => '#B85C76',
|
||||
'secondary' => '#E7D6DC',
|
||||
@@ -69,26 +92,22 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => 'Sofort starten',
|
||||
'qr' => ['size_px' => 640],
|
||||
'svg' => ['width' => 1240, 'height' => 1754],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder fotospiel.app/DEINCODE eingeben.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen und Aufgaben erfüllen, so oft ihr wollt.',
|
||||
'Highlights liken, Kommentare und Grüße dalassen.',
|
||||
'Datenschutz ready: anonyme Sessions, keine App-Installation.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
'midnight-gala' => [
|
||||
'id' => 'midnight-gala',
|
||||
'name' => 'Midnight Gala',
|
||||
'subtitle' => 'Eleganter Auftritt für Corporate Events & Galas.',
|
||||
'description' => 'Dunkle Bühne mit goldenen Akzenten und kräftiger Typografie.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#0B132B',
|
||||
'background_gradient' => [
|
||||
'angle' => 200,
|
||||
'stops' => ['#0B132B', '#1C2541', '#274690'],
|
||||
],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'format_hint' => 'poster-a4',
|
||||
'slots' => self::SLOTS_PORTRAIT,
|
||||
'background' => '#0B132B',
|
||||
'background_gradient' => [
|
||||
'angle' => 200,
|
||||
'stops' => ['#0B132B', '#1C2541', '#274690'],
|
||||
],
|
||||
'text' => '#F8FAFC',
|
||||
'accent' => '#F9C74F',
|
||||
'secondary' => '#4E5D8F',
|
||||
@@ -101,26 +120,22 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => 'Keine App nötig',
|
||||
'qr' => ['size_px' => 640],
|
||||
'svg' => ['width' => 1240, 'height' => 1754],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder fotospiel.app/DEINCODE eingeben.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen und Aufgaben erfüllen, so oft ihr wollt.',
|
||||
'Highlights liken, Kommentare und Grüße dalassen.',
|
||||
'Datenschutz ready: anonyme Sessions, keine App-Installation.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
'garden-brunch' => [
|
||||
'id' => 'garden-brunch',
|
||||
'name' => 'Garden Brunch',
|
||||
'subtitle' => 'Luftiges Layout für Tages-Events & Familienfeiern.',
|
||||
'description' => 'Sanfte Grüntöne, natürliche Formen und Platz für Hinweise.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#F6F9F4',
|
||||
'background_gradient' => [
|
||||
'angle' => 120,
|
||||
'stops' => ['#F6F9F4', '#EEF5E7', '#F8FAF0'],
|
||||
],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'format_hint' => 'poster-a4',
|
||||
'slots' => self::SLOTS_PORTRAIT,
|
||||
'background' => '#F6F9F4',
|
||||
'background_gradient' => [
|
||||
'angle' => 120,
|
||||
'stops' => ['#F6F9F4', '#EEF5E7', '#F8FAF0'],
|
||||
],
|
||||
'text' => '#2F4030',
|
||||
'accent' => '#6BAA75',
|
||||
'secondary' => '#DDE9D8',
|
||||
@@ -133,26 +148,22 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => 'Los geht’s',
|
||||
'qr' => ['size_px' => 660],
|
||||
'svg' => ['width' => 1240, 'height' => 1754],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder fotospiel.app/DEINCODE eingeben.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen und Aufgaben erfüllen, so oft ihr wollt.',
|
||||
'Highlights liken, Kommentare und Grüße dalassen.',
|
||||
'Datenschutz ready: anonyme Sessions, keine App-Installation.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
'sparkler-soiree' => [
|
||||
'id' => 'sparkler-soiree',
|
||||
'name' => 'Sparkler Soirée',
|
||||
'subtitle' => 'Abendliches Layout mit funkelndem Verlauf.',
|
||||
'description' => 'Dynamische Typografie mit zentralem Fokus auf dem QR-Code.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#1B1A44',
|
||||
'background_gradient' => [
|
||||
'angle' => 205,
|
||||
'stops' => ['#1B1A44', '#42275A', '#734B8F'],
|
||||
],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'format_hint' => 'poster-a4',
|
||||
'slots' => self::SLOTS_PORTRAIT,
|
||||
'background' => '#1B1A44',
|
||||
'background_gradient' => [
|
||||
'angle' => 205,
|
||||
'stops' => ['#1B1A44', '#42275A', '#734B8F'],
|
||||
],
|
||||
'text' => '#FDF7FF',
|
||||
'accent' => '#F9A826',
|
||||
'secondary' => '#DDB7FF',
|
||||
@@ -165,26 +176,22 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => 'Challenges spielen',
|
||||
'qr' => ['size_px' => 680],
|
||||
'svg' => ['width' => 1240, 'height' => 1754],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder fotospiel.app/DEINCODE eingeben.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen und Aufgaben erfüllen, so oft ihr wollt.',
|
||||
'Highlights liken, Kommentare und Grüße dalassen.',
|
||||
'Datenschutz ready: anonyme Sessions, keine App-Installation.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
'confetti-bash' => [
|
||||
'id' => 'confetti-bash',
|
||||
'name' => 'Confetti Bash',
|
||||
'subtitle' => 'Verspielter Look für Geburtstage & Jubiläen.',
|
||||
'description' => 'Konfetti-Sprenkel, fröhliche Farben und viel Platz für Hinweise.',
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'background' => '#FFF9F0',
|
||||
'background_gradient' => [
|
||||
'angle' => 145,
|
||||
'stops' => ['#FFF9F0', '#FFEFEF', '#FFF5D6'],
|
||||
],
|
||||
'description' => self::DEFAULT_DESCRIPTION,
|
||||
'paper' => 'a4',
|
||||
'orientation' => 'portrait',
|
||||
'format_hint' => 'poster-a4',
|
||||
'slots' => self::SLOTS_PORTRAIT,
|
||||
'background' => '#FFF9F0',
|
||||
'background_gradient' => [
|
||||
'angle' => 145,
|
||||
'stops' => ['#FFF9F0', '#FFEFEF', '#FFF5D6'],
|
||||
],
|
||||
'text' => '#31291F',
|
||||
'accent' => '#FF6F61',
|
||||
'secondary' => '#F9D6A5',
|
||||
@@ -197,13 +204,7 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => 'Likes vergeben',
|
||||
'qr' => ['size_px' => 680],
|
||||
'svg' => ['width' => 1240, 'height' => 1754],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder fotospiel.app/DEINCODE eingeben.',
|
||||
'Anzeigenamen wählen – kein Account nötig.',
|
||||
'Fotos hochladen und Aufgaben erfüllen, so oft ihr wollt.',
|
||||
'Highlights liken, Kommentare und Grüße dalassen.',
|
||||
'Datenschutz ready: anonyme Sessions, keine App-Installation.',
|
||||
],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -280,7 +281,7 @@ class JoinTokenLayoutRegistry
|
||||
'height' => 1754,
|
||||
],
|
||||
'background_gradient' => null,
|
||||
'instructions' => [],
|
||||
'instructions' => self::DEFAULT_INSTRUCTIONS,
|
||||
'formats' => ['pdf', 'png'],
|
||||
];
|
||||
|
||||
@@ -308,11 +309,22 @@ class JoinTokenLayoutRegistry
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function defaultSlotsPortrait(): array
|
||||
{
|
||||
return self::SLOTS_PORTRAIT;
|
||||
}
|
||||
|
||||
private static function defaultSlotsFoldable(): array
|
||||
{
|
||||
return self::SLOTS_FOLDABLE;
|
||||
}
|
||||
|
||||
private static function fromModel(InviteLayout $layout): array
|
||||
{
|
||||
$preview = $layout->preview ?? [];
|
||||
$options = $layout->layout_options ?? [];
|
||||
$instructions = $layout->instructions ?? [];
|
||||
$slots = $options['slots'] ?? null;
|
||||
|
||||
return array_filter([
|
||||
'id' => $layout->slug,
|
||||
@@ -321,6 +333,7 @@ class JoinTokenLayoutRegistry
|
||||
'description' => $layout->description,
|
||||
'paper' => $layout->paper,
|
||||
'orientation' => $layout->orientation,
|
||||
'format_hint' => self::resolveFormatHint($layout->paper, $layout->orientation, $layout->panel_mode),
|
||||
'background' => $preview['background'] ?? null,
|
||||
'background_gradient' => $preview['background_gradient'] ?? null,
|
||||
'text' => $preview['text'] ?? null,
|
||||
@@ -334,6 +347,7 @@ class JoinTokenLayoutRegistry
|
||||
'cta_caption' => $options['cta_caption'] ?? null,
|
||||
'link_label' => $options['link_label'] ?? null,
|
||||
'logo_url' => $options['logo_url'] ?? null,
|
||||
'slots' => is_array($slots) ? $slots : null,
|
||||
'qr' => array_filter([
|
||||
'size_px' => $preview['qr']['size_px'] ?? $options['qr']['size_px'] ?? $preview['qr_size_px'] ?? $options['qr_size_px'] ?? null,
|
||||
]),
|
||||
@@ -346,6 +360,23 @@ class JoinTokenLayoutRegistry
|
||||
], fn ($value) => $value !== null && $value !== []);
|
||||
}
|
||||
|
||||
private static function resolveFormatHint(?string $paper, ?string $orientation, ?string $panelMode): ?string
|
||||
{
|
||||
$paperVal = strtolower((string) $paper);
|
||||
$orientationVal = strtolower((string) $orientation);
|
||||
$panelVal = strtolower((string) $panelMode);
|
||||
|
||||
if ($paperVal === 'a4' && $orientationVal === 'portrait' && $panelVal !== 'double-mirror') {
|
||||
return 'poster-a4';
|
||||
}
|
||||
|
||||
if ($paperVal === 'a4' && $orientationVal === 'landscape' && $panelVal === 'double-mirror') {
|
||||
return 'foldable-a5';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map layouts into an API-ready response structure, attaching URLs.
|
||||
*
|
||||
@@ -365,18 +396,23 @@ class JoinTokenLayoutRegistry
|
||||
'paper' => $layout['paper'] ?? 'a4',
|
||||
'orientation' => $layout['orientation'] ?? 'portrait',
|
||||
'panel_mode' => $layout['panel_mode'] ?? null,
|
||||
'format_hint' => $layout['format_hint'] ?? self::resolveFormatHint($layout['paper'] ?? null, $layout['orientation'] ?? null, $layout['panel_mode'] ?? null),
|
||||
'badge_label' => $layout['badge_label'] ?? null,
|
||||
'instructions_heading' => $layout['instructions_heading'] ?? null,
|
||||
'link_heading' => $layout['link_heading'] ?? null,
|
||||
'cta_label' => $layout['cta_label'] ?? null,
|
||||
'cta_caption' => $layout['cta_caption'] ?? null,
|
||||
'instructions' => $layout['instructions'] ?? [],
|
||||
'slots' => $layout['slots'] ?? null,
|
||||
'preview' => [
|
||||
'background' => $layout['background'],
|
||||
'background_gradient' => $layout['background_gradient'],
|
||||
'accent' => $layout['accent'],
|
||||
'text' => $layout['text'],
|
||||
'qr_size_px' => $layout['qr']['size_px'] ?? null,
|
||||
'aspect_ratio' => isset($layout['svg']['width'], $layout['svg']['height']) && $layout['svg']['width'] && $layout['svg']['height']
|
||||
? (float) $layout['svg']['width'] / (float) $layout['svg']['height']
|
||||
: null,
|
||||
],
|
||||
'formats' => $formats,
|
||||
'download_urls' => collect($formats)
|
||||
|
||||
Reference in New Issue
Block a user