Neue Branding-Page und Gäste-PWA reagiert nun auf Branding-Einstellungen vom event-admin. Implemented local Google Fonts pipeline and admin UI selects for branding and invites.

- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
    exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
  - Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
    custom override) that auto-load selected fonts.
  - Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
  - New tests cover font sync command and font manifest API.

  Tests run: php artisan test --filter=Fonts --testsuite=Feature.
  Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
  untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
This commit is contained in:
Codex Agent
2025-11-25 19:31:52 +01:00
parent 4d31eb4d42
commit 9bde8f3f32
38 changed files with 2420 additions and 104 deletions

View File

@@ -20,6 +20,9 @@ export default function HomePage() {
const { completedCount } = useGuestTaskProgress(token ?? '');
const { t, locale } = useTranslation();
const { branding } = useEventBranding();
const headingFont = branding.typography?.heading ?? branding.fontFamily ?? undefined;
const bodyFont = branding.typography?.body ?? branding.fontFamily ?? undefined;
const radius = branding.buttons?.radius ?? 12;
const heroStorageKey = token ? `guestHeroDismissed_${token}` : 'guestHeroDismissed';
const [heroVisible, setHeroVisible] = React.useState(() => {
@@ -133,7 +136,7 @@ export default function HomePage() {
}
return (
<div className="space-y-6 pb-32">
<div className="space-y-6 pb-32" style={bodyFont ? { fontFamily: bodyFont } : undefined}>
{heroVisible && (
<HeroCard
name={displayName}
@@ -147,7 +150,7 @@ export default function HomePage() {
/>
)}
<section className="space-y-4" style={branding.fontFamily ? { fontFamily: branding.fontFamily } : undefined}>
<section className="space-y-4" style={headingFont ? { fontFamily: headingFont } : undefined}>
<div className="flex items-center justify-between gap-3">
<div>
<h2 className="text-base font-semibold text-foreground">Starte dein Fotospiel</h2>
@@ -163,7 +166,7 @@ export default function HomePage() {
onShuffle={shuffleMissionPreview}
/>
<EmotionActionCard />
<UploadActionCard token={token} accentColor={accentColor} secondaryAccent={secondaryAccent} />
<UploadActionCard token={token} accentColor={accentColor} secondaryAccent={secondaryAccent} radius={radius} bodyFont={bodyFont} />
</div>
</section>
@@ -328,15 +331,23 @@ function UploadActionCard({
token,
accentColor,
secondaryAccent,
radius,
bodyFont,
}: {
token: string;
accentColor: string;
secondaryAccent: string;
radius: number;
bodyFont?: string;
}) {
return (
<Card
className="overflow-hidden border-0 text-white shadow-sm"
style={{ background: `linear-gradient(120deg, ${accentColor}, ${secondaryAccent})` }}
style={{
background: `linear-gradient(120deg, ${accentColor}, ${secondaryAccent})`,
borderRadius: `${radius}px`,
fontFamily: bodyFont,
}}
>
<CardContent className="flex flex-col gap-3 py-5">
<div className="flex items-center gap-3">
@@ -348,7 +359,11 @@ function UploadActionCard({
<p className="text-sm text-white/80">Kamera öffnen oder ein Foto aus deiner Galerie wählen.</p>
</div>
</div>
<Button asChild className="bg-white/90 text-slate-900 hover:bg-white">
<Button
asChild
className="bg-white/90 text-slate-900 hover:bg-white"
style={{ borderRadius: `${radius}px` }}
>
<Link to={`/e/${encodeURIComponent(token)}/upload`}>Foto hochladen</Link>
</Button>
</CardContent>