1<!DOCTYPE html> 2<html lang="en" class="overflow-x-hidden"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> 6 <title>Wunschzettel Dashboard</title> 7 <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> 8 <meta http-equiv="Pragma" content="no-cache"> 9 <meta http-equiv="Expires" content="0"> 10 <link rel="icon" type="image/png" href="/assets/favicon.png"> 11 <link rel="apple-touch-icon" href="/assets/favicon.png"> 12 13 <!-- SEO Meta Tags --> 14 <meta name="description" content="Erstelle und teile deine Wünsche ganz einfach online."> 15 <meta name="robots" content="index, follow"> 16 17 <!-- OG_TAGS --> 18 19 <!-- Google reCAPTCHA v3 & Cloudflare Turnstile --> 20 <!-- Preload Critical Assets --> 21 <link rel="preload" href="/style_v4.css?v=cw117" as="style"> 22 <link rel="preload" href="/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2" as="font" type="font/woff2" crossorigin> 23 24 <!-- Fonts & Icons (GDPR Compliant Local Hosting) --> 25 <style>@font-face{font-family:'Outfit';font-style:normal;font-weight:300;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJuktqUYLkn8BJ.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Outfit';font-style:normal;font-weight:300;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Outfit';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJuktqUYLkn8BJ.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Outfit';font-style:normal;font-weight:400;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Outfit';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJuktqUYLkn8BJ.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Outfit';font-style:normal;font-weight:500;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Outfit';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJuktqUYLkn8BJ.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Outfit';font-style:normal;font-weight:600;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Outfit';font-style:normal;font-weight:700;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJuktqUYLkn8BJ.woff2) format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1D
25BF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Outfit';font-style:normal;font-weight:700;font-display:swap;src:url(/assets/vendor/fonts/QGYvz_MVcBeNP4NJtEtqUYLknw.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style> 26 <!-- Defer Icon CSS to unblock rendering --> 27 <link rel="stylesheet" href="/assets/vendor/bootstrap-icons/bootstrap-icons.css" media="print" onload="this.media='all'"> 28 <noscript><link rel="stylesheet" href="/assets/vendor/bootstrap-icons/bootstrap-icons.css"></noscript> 29 30 <!-- Scripts (GDPR Compliant Local Hosting) --> 31
31<script src="/assets/vendor/vue.global.js" defer></script>
31 32
32<script src="/assets/vendor/qrcode.min.js?v=2" defer></script>
32 33
33<script src="/assets/vendor/purify.min.js" defer></script>
33 34
34<script src="/assets/vendor/Sortable.min.js" defer></script>
34 35 <!-- Custom Styles (Compiled Tailwind + Base Styles) --> 36 <link rel="stylesheet" href="/style_v4.css?v=cw117"> 37</head> 38<body class="bg-slate-50 text-slate-800 min-h-screen font-sans antialiased overflow-x-hidden relative"> 39 <div id="app" class="min-h-screen flex flex-col overflow-x-hidden w-full max-w-[100vw]"> 40 <!-- Global Toast --> 41 <transition enter-active-class="transition duration-300 ease-out transform" 42 enter-from-class="-translate-y-10 opacity-0" 43 enter-to-class="translate-y-0 opacity-100" 44 leave-active-class="transition duration-200 ease-in transform" 45 leave-from-class="translate-y-0 opacity-100" 46 leave-to-class="-translate-y-10 opacity-0"> 47 <div v-if="toastMessage" 48 class="fixed top-6 left-1/2 transform -translate-x-1/2 z-[100] px-6 py-3 rounded-full font-medium text-sm shadow-xl flex items-center space-x-3 pointer-events-none max-w-[90vw] text-center leading-tight" 49 :class="{ 50 'bg-emerald-500 text-white shadow-emerald-500/20': toastType === 'success', 51 'bg-red-500 text-white shadow-red-500/20': toastType === 'error', 52 'bg-slate-800 text-white shadow-slate-800/20': toastType === 'info' 53 }"> 54 <i class="bi shrink-0" :class="{ 55 'bi-check-circle-fill': toastType === 'success', 56 'bi-exclamation-triangle-fill': toastType === 'error', 57 'bi-info-circle-fill': toastType === 'info' 58 }"></i> 59 <span>{{ toastMessage }}</span> 60 </div> 61 </transition> 62 63 64 <!-- Background Elements (Optimized without blur) --> 65 <div v-if="!isEmbed" class="fixed top-[-20%] left-[-10%] w-[50%] h-[50%] rounded-full pointer-events-none" style="background: radial-gradient(circle, rgba(99,102,241,0.15) 0%, rgba(99,102,241,0) 70%);"></div> 66 <div v-if="!isEmbed" class="fixed bottom-[-20%] right-[-10%] w-[50%] h-[50%] rounded-full pointer-events-none" style="background: radial-gradient(circle, rgba(236,72,153,0.15) 0%, rgba(236,72,153,0) 70%);"></div> 67 68 <!-- Navigation Bar --> 69 <nav v-if="!isEmbed" class="glass-nav fixed w-full z-50 top-0 border-b border-white/5 transition-all" style="padding-top: env(safe-area-inset-top);"> 70 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> 71 <div class="flex justify-between h-16 items-center"> 72 <div class="flex-shrink-0 flex items-center cursor-pointer lg:w-1/4" @click="goHome"> 73 <i class="bi bi-gift text-2xl text-primary mr-2"></i> 74 <span class="font-bold text-xl tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-primary to-secondary">Wunschzettel</span><span class="font-bold text-sm text-pink-600 ml-0.5">.io</span> 75 </div> 76 77 <!-- Main Desktop Navigation --> 78 <div class="hidden lg:flex flex-1 justify-center items-center space-x-8"> 79 <a :href="'/' + lang + '/' + (lang === 'en' ? 'wedding-registry' : 'hochzeitstisch') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'hochzeit'}, path: '/' + (lang === 'en' ? 'wedding-registry' : 'hochzeitstisch') + '/'})" class="text-sm font-bold text-slate-600 hover:text-primary transition-colors">{{ $t('lp_hochzeit.breadcrumb') }}</a> 80 <a :href="'/' + lang + '/' + (lang === 'en' ? 'christmas-wishlist' : 'weihnachten') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'weihnachten'}, path: '/' + (lang === 'en' ? 'christmas-wishlist' : 'weihnachten') + '/'})" class="text-sm font-bold text-slate-600 hover:text-primary transition-colors">{{ $t('lp_weihnachten.breadcrumb') }}</a> 81 <a :href="'/' + lang + '/' + (lang === 'en' ? 'kids-birthday' : 'kindergeburtstag') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'kinder'}, path: '/' + (lang === 'en' ? 'kids-birthday' : 'kindergeburtstag') + '/'})" class="text-sm font-bold text-slate-600 hover:text-primary transition-colors">{{ $t('lp_kinder.breadcrumb') }}</a> 82 <a :href="'/' + lang + '/' + (lang === 'en' ? 'baby-registry' : 'baby-geschenke') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'baby'}, path: '/' + (lang === 'en' ? 'baby-registry' : 'baby-geschenke') + '/'})" class="text-sm font-bold text-slate-600 hover:text-primary transition-colors">{{ $t('lp_baby.breadcrumb') }}</a> 83 <a :href="'/' + lang + '/faq'" @click.prevent="navigate({view: 'FaqView', path: '/faq'})" class="text-sm font-bold text-slate-600 hover:text-primary transition-colors">FAQ</a> 84 </div> 85 86 <div class="flex items-center justify-end space-x-4 lg:w-1/4"> 87 <template v-if="user"> 88 <a :href="'/' + lang + '/search/'" class="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors mr-4 hidden sm:inline-block"><i class="bi bi-search mr-1"></i> {{ $t('nav.search') }}</a> 89 <a :href="'/' + lang + '/'" @click.prevent="navigate({view: 'ProfileView', path: '/'})" class="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors mr-4">
90 <i class="bi bi-person-circle mr-1"></i> {{ user.name }} 91 </a> 92 <button @click="logout" class="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors border border-slate-300 px-3 py-1.5 rounded-lg hover:bg-slate-100"> 93 {{ $t('nav.logout') }} 94 </button> 95 </template> 96 <template v-else> 97 <a :href="'/' + lang + '/search/'" class="text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors mr-4 hidden sm:inline-block"><i class="bi bi-search mr-1"></i> {{ $t('nav.search') }}</a> 98 <a :href="'/' + lang + '/'" @click.prevent="navigate({view: 'LoginView', path: '/'})" class="text-sm font-medium text-white bg-indigo-700 hover:bg-indigo-800 border border-indigo-700/50 transition-all px-4 py-2 rounded-lg shadow-sm inline-block">{{ $t('nav.login') }}</a> 99 </template> 100 </div> 101 </div> 102 </div> 103 </nav> 104 105 <!-- Main Content Area --> 106 <main class="px-4 sm:px-6 lg:px-8 max-w-7xl w-full mx-auto transition-all duration-300" :style="isEmbed ? 'padding: 1rem; flex-grow: 1;' : 'padding-top: calc(7rem + env(safe-area-inset-top)); padding-bottom: max(3rem, env(safe-area-inset-bottom)); flex-grow: 1;'"> 107 108 <!-- Loading State --> 109 <div v-if="loading" class="flex justify-center items-center h-64"> 110 <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div> 111 </div> 112 113 <!-- Dynamic View Injection --> 114 <component :is="currentView" v-else-if="currentView" :user="user" :args="viewArgs" @login-success="handleLogin" @navigate="navigate"></component> 115 116 </main> 117 118 <!-- Footer --> 119 <footer v-if="!isEmbed" class="bg-darker border-t border-white/5 mt-24 md:mt-32 pt-20 md:pt-32 pb-16 overflow-hidden text-sm text-slate-400" style="padding-bottom: max(4rem, env(safe-area-inset-bottom));"> 120 <div class="max-w-7xl mx-auto px-8 sm:px-12 lg:px-16"> 121 <!-- Grid Layout --> 122 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-12 gap-y-12 mb-16"> 123 124 <!-- Col 1: Brand / App Links --> 125 <div class="space-y-6"> 126 <div class="flex items-center cursor-pointer" @click="goHome"> 127 <i class="bi bi-gift text-2xl text-primary mr-2 animate-pulse"></i> 128 <span class="font-bold text-xl tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-primary to-secondary">Wunschzettel</span><span class="font-bold text-sm text-pink-600 ml-0.5">.io</span> 129 </div> 130 <p class="text-slate-400 text-sm leading-relaxed max-w-xs"> 131 {{ $t('footer.slogan') }} â {{ $t('hero.subtitle') }} 132 </p> 133 <!-- Badges --> 134 <div v-if="!isNative && !isEmbed" class="flex items-center gap-3"> 135 <a href="https://apps.apple.com/de/app/wunschzettel-io-wunschliste/id6762308060" target="_blank" class="hover:-translate-y-0.5 transition-transform flex items-center"> 136 <img alt="Laden im App Store" src="/assets/app-store-badge.svg" class="object-contain rounded-md" style="height: 35px;"/> 137 </a> 138 <a href="https://play.google.com/store/apps/details?id=io.wunschzettel.app" target="_blank" class="hover:-translate-y-0.5 transition-transform flex items-center"> 139 <img alt="Jetzt bei Google Play" src="/assets/google-play-badge.png" class="object-contain mix-blend-screen" style="height: 47px;"/> 140 </a> 141 </div> 142 </div> 143 144 <!-- Col 2: Landing Pages --> 145 <div> 146 <h4 class="font-semibold text-white tracking-wider uppercase text-xs mb-4">{{ lang === 'en' ? 'Wishlist for...' : 'Wunschzettel für...' }}</h4> 147 <ul class="space-y-3"> 148 <li> 149 <a :href="'/' + lang + '/' + (lang === 'en' ? 'wedding-registry' : 'hochzeitstisch') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'hochzeit'}, path: '/' + (lang === 'en' ? 'wedding-registry' : 'hochzeitstisch') + '/'})" class="hover:text-primary transition-colors block">{{ $t('lp_hochzeit.breadcrumb') }}</a> 150 </li> 151 <li> 152 <a :href="'/' + lang + '/' + (lang === 'en' ? 'christmas-wishlist' : 'weihnachten') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'weihnachten'}, path: '/' + (lang === 'en' ? 'christmas-wishlist' : 'weihnachten') + '/'})" class="hover:text-primary transition-colors block">{{ $t('lp_weihnachten.breadcrumb') }}</a> 153 </li> 154 <li> 155 <a :href="'/' + lang + '/' + (lang === 'en' ? 'kids-birthday' : 'kindergeburtstag') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'kinder'}, path: '/' + (lang === 'en' ? 'kids-birthday' : 'kindergeburtstag') + '/'})" class="hover:text-primary transition-colors block">{{ $t('lp_kinder.breadcrumb') }}</a> 156 </li> 157 <li> 158 <a :href="'/' + lang + '/' + (lang === 'en' ? 'baby-registry' : 'baby-geschenke') + '/'" @click.prevent="navigate({view: 'LandingPageView', args: {topic: 'baby'}, path: '/' + (lang === 'en' ? 'baby-registry' : 'baby-geschenke') + '/'})" class="hover:text-primary transition-colors block">{{ $t('lp_baby.breadcrumb') }}</a> 159 </li> 160 </ul> 161 </div> 162 163 <!-- Col 3: Support --> 164 <div> 165 <h4 class="font-semibold text-white tracking-wider up
165percase text-xs mb-4">Support</h4> 166 <ul class="space-y-3"> 167 <li> 168 <a @click.prevent="navigate({view: 'FaqView', path: '/faq/'})" :href="'/' + lang + '/faq/'" class="hover:text-primary transition-colors block">FAQ</a> 169 </li> 170 <li> 171 <a @click.prevent="navigate({view: 'SupportView', path: '/support/'})" :href="'/' + lang + '/support/'" class="hover:text-primary transition-colors block">Support</a> 172 </li> 173 <li> 174 <a @click.prevent="navigate({view: 'PublicSearchView', path: '/search/'})" :href="'/' + lang + '/search/'" class="hover:text-primary transition-colors block">{{ $t('footer.search') }}</a> 175 </li> 176 </ul> 177 </div> 178 179 <!-- Col 4: Rechtliches --> 180 <div> 181 <h4 class="font-semibold text-white tracking-wider uppercase text-xs mb-4">{{ lang === 'en' ? 'Legal' : 'Rechtliches' }}</h4> 182 <ul class="space-y-3"> 183 <li> 184 <a @click.prevent="navigate({view: 'NutzungsbedingungenView', path: '/nutzungsbedingungen/'})" :href="'/' + lang + '/nutzungsbedingungen/'" class="hover:text-primary transition-colors block">{{ $t('footer.tos') }}</a> 185 </li> 186 <li> 187 <a @click.prevent="navigate({view: 'DatenschutzView', path: '/datenschutz/'})" :href="'/' + lang + '/datenschutz/'" class="hover:text-primary transition-colors block">{{ $t('footer.privacy') }}</a> 188 </li> 189 <li> 190 <a @click.prevent="navigate({view: 'ImpressumView', path: '/impressum/'})" :href="'/' + lang + '/impressum/'" class="hover:text-primary transition-colors block">{{ $t('footer.imprint') }}</a> 191 </li> 192 </ul> 193 </div> 194 195 </div> 196 197 <!-- Divider Line --> 198 <div class="border-t border-white/5 pt-8 flex flex-col md:flex-row justify-between items-center gap-6"> 199 <div class="flex items-center space-x-3 text-slate-500 text-xs text-center md:text-left"> 200 <span>© 2026 Wunschzettel.io. {{ lang === 'en' ? 'All rights reserved.' : 'Alle Rechte vorbehalten.' }}</span> 201 </div> 202 203 <!-- Language Selector --> 204 <div class="flex items-center space-x-2"> 205 <i class="bi bi-globe text-slate-500 text-sm"></i> 206 <select v-model="lang" @change="switchLang" aria-label="Sprache auswählen" class="bg-dark relative text-slate-300 border border-slate-700/80 hover:border-slate-500 rounded-lg px-2.5 py-1 text-xs outline-none cursor-pointer transition-colors focus:ring-1 focus:ring-primary/50"> 207 <option value="de">DEUTSCH</option> 208 <option value="en">ENGLISH</option> 209 </select> 210 </div> 211 </div> 212 </div> 213 </footer> 214 </div> 215 216
216<script src="/i18n.js?v=cw21" defer></script>
216 217
217<script src="/app.js?v=cw194" defer></script>
217 218</body> 219</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.