Files
fotospiel-app/resources/views/guest.blade.php
Codex Agent 3e3a2c49d6 Implemented guest-only PWA using vite-plugin-pwa (the actual published package; @vite-pwa/plugin isn’t on npm) with
injectManifest, a new typed SW source, runtime caching, and a non‑blocking update toast with an action button. The
  guest shell now links a dedicated manifest and theme color, and background upload sync is managed in a single
  PwaManager component.

  Key changes (where/why)

  - vite.config.ts: added VitePWA injectManifest config, guest manifest, and output to /public so the SW can control /
    scope.
  - resources/js/guest/guest-sw.ts: new Workbox SW (precache + runtime caching for guest navigation, GET /api/v1/*,
    images, fonts) and preserves push/sync/notification logic.
  - resources/js/guest/components/PwaManager.tsx: registers SW, shows update/offline toasts, and processes the upload
    queue on sync/online.
  - resources/js/guest/components/ToastHost.tsx: action-capable toasts so update prompts can include a CTA.
  - resources/js/guest/i18n/messages.ts: added common.updateAvailable, common.updateAction, common.offlineReady.
  - resources/views/guest.blade.php: manifest + theme color + apple touch icon.
  - .gitignore: ignore generated public/guest-sw.js and public/guest.webmanifest; public/guest-sw.js removed since it’s
    now build output.
2025-12-27 10:59:44 +01:00

110 lines
5.9 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.
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Fotospiel') }}</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="icon" href="{{ asset('favicon.ico') }}" type="image/x-icon">
<link rel="manifest" href="{{ asset('guest.webmanifest') }}">
<link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}">
<meta name="theme-color" content="#0f172a">
@viteReactRefresh
@vite(['resources/css/app.css', 'resources/js/guest/main.tsx'])
@php
$guestRuntimeConfig = [
'push' => [
'enabled' => config('push.enabled', false),
'vapidPublicKey' => config('push.vapid.public_key'),
],
];
$matomoConfig = config('services.matomo');
$matomoGuest = ($matomoConfig['enabled'] ?? false) && !empty($matomoConfig['url']) && !empty($matomoConfig['site_id_guest'])
? [
'enabled' => true,
'url' => rtrim($matomoConfig['url'], '/'),
'siteId' => (string) $matomoConfig['site_id_guest'],
]
: ['enabled' => false];
@endphp
<script nonce="{{ $cspNonce }}">
window.__GUEST_RUNTIME_CONFIG__ = {!! json_encode($guestRuntimeConfig) !!};
window.__MATOMO_GUEST__ = {!! json_encode($matomoGuest) !!};
</script>
<style nonce="{{ $cspStyleNonce }}">
#root { min-height: 100vh; }
.ns-bg { background: linear-gradient(180deg,#0f172a 0%,#111827 50%,#0b1224 100%); color: #fff; }
.ns-btn-primary { color: #fff; text-decoration: none; background: #ec4899; }
.ns-btn-outline { color: #e5e7eb; text-decoration: none; border: 1px solid rgba(255,255,255,0.2); }
</style>
</head>
<body>
@php
$noscriptLocale = in_array(app()->getLocale(), ['de', 'en'], true) ? app()->getLocale() : 'de';
@endphp
<noscript>
<style nonce="{{ $cspStyleNonce }}">
#root { display: none !important; }
</style>
<div class="min-h-screen bg-gradient-to-b from-[#0f172a] via-[#111827] to-[#0b1224] text-white ns-bg">
<div class="mx-auto flex max-w-5xl flex-col gap-12 px-6 py-14">
<header class="space-y-3 text-center">
<p class="text-sm font-semibold uppercase tracking-[0.18em] text-pink-300">Fotospiel</p>
<h1 class="text-3xl font-semibold sm:text-4xl">Diese Event-Galerie braucht JavaScript</h1>
<p class="text-base text-white/70 sm:text-lg">
Aktiviere JavaScript, um Fotos anzusehen, zu liken und zu teilen. Ohne JavaScript zeigen wir dir eine schnelle Übersicht.
</p>
</header>
<section class="grid gap-4 sm:grid-cols-2">
<div class="rounded-2xl border border-white/10 bg-white/5 p-5 shadow-lg backdrop-blur">
<h2 class="text-xl font-semibold text-white">Was du verpasst</h2>
<ul class="mt-3 space-y-2 text-sm text-white/80">
<li> Live-Fotogalerie mit schnellen Filtern</li>
<li> Likes & Teilen via WhatsApp, Nachrichten & Link</li>
<li> Offline-fähige PWA zum Installieren</li>
<li> Sichere QR-Zugänge ohne Anmeldung</li>
</ul>
</div>
<div class="rounded-2xl border border-white/10 bg-white/5 p-5 shadow-lg backdrop-blur">
<h2 class="text-xl font-semibold text-white">So gehts weiter</h2>
<ol class="mt-3 space-y-2 text-sm text-white/80">
<li>1) Aktiviere JavaScript in deinem Browser</li>
<li>2) Lade die Seite neu</li>
<li>3) Optional: Füge die App deinem Homescreen hinzu</li>
</ol>
<div class="mt-4 flex flex-wrap gap-3">
<a href="{{ route('marketing.contact', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full bg-pink-500 px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:bg-pink-400 ns-btn-primary">
Support kontaktieren
</a>
<a href="{{ route('impressum', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white/80 transition hover:border-white/40 ns-btn-outline">
Impressum
</a>
<a href="{{ route('datenschutz', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white/80 transition hover:border-white/40 ns-btn-outline">
Datenschutz
</a>
</div>
</div>
</section>
<section class="grid gap-4 sm:grid-cols-3">
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
<h3 class="text-base font-semibold text-white">Offline-ready</h3>
<p class="mt-2">Fotos bleiben verfügbar, auch wenn das Event-WLAN wackelt.</p>
</div>
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
<h3 class="text-base font-semibold text-white">Privat & sicher</h3>
<p class="mt-2">Keine öffentlichen Profile, keine Gesichts­erkennung, nur Event-Teilnehmende.</p>
</div>
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
<h3 class="text-base font-semibold text-white">Schnelles Teilen</h3>
<p class="mt-2">Direkter Link für Freund:innen, mit Ablaufsteuerung durch das Event-Team.</p>
</div>
</section>
</div>
</div>
</noscript>
<div id="root"></div>
</body>
</html>