Compare commits

...

2 Commits

Author SHA1 Message Date
AzuTear 17134b3b82 Restyle shell and home page with Jayuhime brand; add account and privacy modals
- Replaces amber/Manrope shell with sticky purple nav (Outfit + Fredoka)
- Adds account modal: profile info, Datenschutzerklärung, DSGVO Art. 17 delete flow
- Home: new hero with animated blobs/stars/countdown, inline vote picker, clip submit form, show-reminder, FAQ
- Switches base palette to purple (#f4eefb / #3f3556) and adds keyframe animations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 21:15:53 +02:00
AzuTear 30fb931bd3 Refresh home view with updated hero and steps 2026-06-22 18:06:47 +02:00
27 changed files with 1119 additions and 652 deletions
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

@@ -0,0 +1,100 @@
<svg width="1536" height="1000" viewBox="0 0 1536 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="backCard" x1="783" y1="193" x2="1072" y2="633" gradientUnits="userSpaceOnUse">
<stop stop-color="#C599FF"/>
<stop offset=".46" stop-color="#8E65EE"/>
<stop offset="1" stop-color="#5D40BB"/>
</linearGradient>
<linearGradient id="backCardShade" x1="909" y1="236" x2="1049" y2="680" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF7FF" stop-opacity=".34"/>
<stop offset="1" stop-color="#2F166F" stop-opacity=".24"/>
</linearGradient>
<linearGradient id="paperFill" x1="442" y1="244" x2="820" y2="713" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFFFFF"/>
<stop offset=".58" stop-color="#FFF8FC"/>
<stop offset="1" stop-color="#F7EDFF"/>
</linearGradient>
<linearGradient id="paperStroke" x1="381" y1="253" x2="672" y2="768" gradientUnits="userSpaceOnUse">
<stop stop-color="#F08CAD"/>
<stop offset="1" stop-color="#F5C58D"/>
</linearGradient>
<linearGradient id="feather" x1="869" y1="224" x2="1102" y2="660" gradientUnits="userSpaceOnUse">
<stop stop-color="#E18CF9"/>
<stop offset=".45" stop-color="#8B5AEA"/>
<stop offset="1" stop-color="#4D2BAE"/>
</linearGradient>
<linearGradient id="featherLight" x1="982" y1="219" x2="903" y2="577" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD3ED" stop-opacity=".9"/>
<stop offset="1" stop-color="#C8A8FF" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="starGold" x1="568" y1="360" x2="679" y2="495" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEAB0"/>
<stop offset=".55" stop-color="#FFC86C"/>
<stop offset="1" stop-color="#F29C44"/>
</linearGradient>
<linearGradient id="shadowFade" x1="365" y1="768" x2="1084" y2="829" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB36B" stop-opacity=".18"/>
<stop offset=".6" stop-color="#9B6DFF" stop-opacity=".14"/>
<stop offset="1" stop-color="#FFB36B" stop-opacity="0"/>
</linearGradient>
<filter id="cardShadow" x="0" y="0" width="1536" height="1000" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="34" stdDeviation="36" flood-color="#7551D6" flood-opacity=".18"/>
<feDropShadow dx="0" dy="8" stdDeviation="12" flood-color="#F3A75C" flood-opacity=".1"/>
</filter>
<filter id="sparkGlow" x="-40%" y="-40%" width="180%" height="180%" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2.5" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<clipPath id="paperClip">
<path d="M378 246C390 220 420 208 449 214L720 267C757 274 779 309 770 346L686 718C679 749 651 770 619 768L329 749C293 747 269 711 283 678L337 549C344 532 344 512 337 495L301 406C291 381 299 353 320 337L352 313C360 307 366 299 370 290L378 246Z"/>
</clipPath>
</defs>
<g filter="url(#sparkGlow)" opacity=".9">
<path d="M180 236l17 41 41 17-41 17-17 41-17-41-41-17 41-17 17-41z" fill="#FFC96B"/>
<path d="M235 647l14 34 34 14-34 14-14 34-14-34-34-14 34-14 14-34z" fill="#FFD98E"/>
<path d="M1247 292l14 34 34 14-34 14-14 34-14-34-34-14 34-14 14-34z" fill="#FFC96B"/>
<path d="M1329 593l18 44 44 18-44 18-18 44-18-44-44-18 44-18 18-44z" fill="#FFD98E"/>
<path d="M108 565l7 17 17 7-17 7-7 17-7-17-17-7 17-7 7-17z" fill="#F8B861"/>
<path d="M352 249l8 20 20 8-20 8-8 20-8-20-20-8 20-8 8-20z" fill="#F6C978"/>
<path d="M1362 399l8 20 20 8-20 8-8 20-8-20-20-8 20-8 8-20z" fill="#F8B861"/>
<circle cx="322" cy="575" r="9" fill="#FFC46A"/>
<circle cx="1165" cy="623" r="8" fill="#FFD98E"/>
<circle cx="129" cy="409" r="6" fill="#FFD98E"/>
</g>
<ellipse cx="748" cy="789" rx="443" ry="44" fill="url(#shadowFade)"/>
<g filter="url(#cardShadow)">
<path d="M739 206C739 186 756 170 776 172L1014 191C1034 193 1048 210 1047 230L1025 690C1024 713 1004 729 982 725L744 685C726 682 713 666 714 648L739 206Z" fill="url(#backCard)"/>
<path d="M775 216L1007 235L988 669L759 632L775 216Z" fill="url(#backCardShade)"/>
<path d="M784 211L1012 231" stroke="#D2B5FF" stroke-width="18" stroke-linecap="round" opacity=".45"/>
<path d="M1005 265L985 664" stroke="#3F268E" stroke-width="13" stroke-linecap="round" opacity=".24"/>
<path d="M378 246C390 220 420 208 449 214L720 267C757 274 779 309 770 346L686 718C679 749 651 770 619 768L329 749C293 747 269 711 283 678L337 549C344 532 344 512 337 495L301 406C291 381 299 353 320 337L352 313C360 307 366 299 370 290L378 246Z" fill="url(#paperFill)" stroke="url(#paperStroke)" stroke-width="16" stroke-linejoin="round"/>
<g clip-path="url(#paperClip)">
<path d="M284 678C347 651 422 650 506 675C569 693 630 696 688 681L674 759L310 744L284 678Z" fill="#FFEFE3"/>
<path d="M343 324C441 340 575 368 743 409" stroke="#F8C4D0" stroke-width="7" opacity=".45"/>
</g>
<path d="M590 352l34 75 82 9-61 55 17 81-72-41-72 41 17-81-61-55 82-9 34-75z" fill="url(#starGold)" stroke="#F7B354" stroke-width="17" stroke-linejoin="round"/>
<path d="M591 397l18 39 43 5-32 29 9 42-38-21-38 21 9-42-32-29 43-5 18-39z" fill="#FFF7F0" opacity=".45"/>
<path d="M420 514h190" stroke="#CFB9EE" stroke-width="21" stroke-linecap="round"/>
<path d="M418 595h213" stroke="#C4AFE8" stroke-width="21" stroke-linecap="round"/>
<path d="M414 675h139" stroke="#BBA5E5" stroke-width="21" stroke-linecap="round"/>
<circle cx="405" cy="427" r="10" fill="#F4C4C5"/>
<path d="M827 602C926 424 1067 286 1267 195C1177 355 1057 521 868 657L765 698L827 602Z" fill="url(#feather)" stroke="#6E45C9" stroke-width="16" stroke-linejoin="round"/>
<path d="M867 582C966 451 1084 338 1232 223C1154 338 1044 470 868 657" fill="url(#featherLight)"/>
<path d="M819 638C923 525 1049 403 1207 244" stroke="#4C2FA7" stroke-width="16" stroke-linecap="round"/>
<path d="M938 443C1002 411 1075 365 1158 305" stroke="#A783F5" stroke-width="11" stroke-linecap="round" opacity=".78"/>
<path d="M887 527C938 498 992 459 1049 410" stroke="#C7ABFF" stroke-width="9" stroke-linecap="round" opacity=".75"/>
<path d="M809 616L747 724" stroke="#F4A344" stroke-width="18" stroke-linecap="round"/>
<path d="M747 724L706 750" stroke="#BF6E37" stroke-width="16" stroke-linecap="round"/>
<path d="M724 741C752 761 779 766 805 755C799 785 774 811 735 816C708 812 687 798 672 774L724 741Z" fill="#FFF6FA" stroke="#C6A2DB" stroke-width="13" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

+34
View File
@@ -0,0 +1,34 @@
<svg width="420" height="260" viewBox="0 0 420 260" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="paper" x1="126" y1="47" x2="252" y2="183" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF8FF"/>
<stop offset="1" stop-color="#F4ECFF"/>
</linearGradient>
<linearGradient id="violet" x1="230" y1="38" x2="318" y2="160" gradientUnits="userSpaceOnUse">
<stop stop-color="#B98CFF"/>
<stop offset="1" stop-color="#7353D9"/>
</linearGradient>
<linearGradient id="gold" x1="229" y1="57" x2="262" y2="93" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE6A8"/>
<stop offset="1" stop-color="#F2AF36"/>
</linearGradient>
<filter id="softShadow" x="75" y="26" width="286" height="190" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="16" stdDeviation="16" flood-color="#7C5CFF" flood-opacity="0.16"/>
</filter>
</defs>
<g opacity=".85">
<path d="M67 66l5 12 12 5-12 5-5 12-5-12-12-5 12-5 5-12zM350 54l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9zM332 146l3 7 7 3-7 3-3 7-3-7-7-3 7-3 3-7z" fill="#F8C263"/>
<path d="M98 151l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9z" fill="#B78CFF"/>
</g>
<g filter="url(#softShadow)">
<rect x="182" y="43" width="118" height="135" rx="12" fill="url(#violet)" stroke="#9A7BEE" stroke-width="5"/>
<rect x="199" y="32" width="108" height="135" rx="12" fill="#A77BFA" stroke="#7C5CFF" stroke-width="5"/>
<rect x="111" y="64" width="124" height="139" rx="14" fill="url(#paper)" stroke="#F0A3B9" stroke-width="5"/>
<path d="M138 104h61M138 124h58M138 144h42" stroke="#B8A6D9" stroke-width="7" stroke-linecap="round"/>
<path d="M175 77l10 23 25 3-18 16 5 25-22-13-22 13 5-25-18-16 25-3 10-23z" fill="url(#gold)" stroke="#F0B851" stroke-width="5" stroke-linejoin="round"/>
<path d="M240 158c28-43 60-72 105-83-27 33-54 71-101 100l-18 5 14-22z" fill="#815BE7" stroke="#6B49CB" stroke-width="5" stroke-linejoin="round"/>
<path d="M264 144c22-23 45-42 70-58" stroke="#D8C5FF" stroke-width="5" stroke-linecap="round"/>
<path d="M222 186c19-11 28-18 37-35" stroke="#F2B85F" stroke-width="7" stroke-linecap="round"/>
</g>
<ellipse cx="211" cy="218" rx="119" ry="12" fill="#C9B5F8" opacity=".18"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+35
View File
@@ -0,0 +1,35 @@
<svg width="420" height="260" viewBox="0 0 420 260" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="box" x1="111" y1="91" x2="291" y2="214" gradientUnits="userSpaceOnUse">
<stop stop-color="#A67BFF"/>
<stop offset=".55" stop-color="#7653D9"/>
<stop offset="1" stop-color="#5C3AB9"/>
</linearGradient>
<linearGradient id="lid" x1="127" y1="56" x2="294" y2="128" gradientUnits="userSpaceOnUse">
<stop stop-color="#DDBBFF"/>
<stop offset="1" stop-color="#8B63ED"/>
</linearGradient>
<filter id="softShadow" x="79" y="38" width="268" height="205" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#6E4DD4" flood-opacity=".18"/>
</filter>
</defs>
<g opacity=".85">
<path d="M70 57l5 12 12 5-12 5-5 12-5-12-12-5 12-5 5-12zM341 71l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9zM332 168l3 7 7 3-7 3-3 7-3-7-7-3 7-3 3-7z" fill="#F8C263"/>
<path d="M101 159l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9z" fill="#B78CFF"/>
</g>
<path d="M89 147c-18-25 5-48 35-38M321 138c22-12 18-42-12-52" stroke="#F3D4FF" stroke-width="8" stroke-linecap="round" opacity=".65"/>
<g filter="url(#softShadow)">
<path d="M178 42l86 20 24 78-118-24-26-57c-3-7 4-15 12-13l22-4z" fill="#FFF8FF" stroke="#D7A7E8" stroke-width="5"/>
<path d="M218 80l8 18 20 2-15 13 5 20-18-10-18 10 5-20-15-13 20-2 8-18z" fill="#F8C263"/>
<rect x="105" y="99" width="210" height="40" rx="10" fill="url(#lid)" stroke="#8C67E8" stroke-width="5"/>
<path d="M124 128h172l-17 84H142l-18-84z" fill="url(#box)" stroke="#6D4EC8" stroke-width="5" stroke-linejoin="round"/>
<path d="M153 140l13 56M267 140l-13 56" stroke="#A78BFA" stroke-width="4" opacity=".7"/>
<path d="M210 158c-9-17-40-9-40 14 0 24 40 42 40 42s40-18 40-42c0-23-31-31-40-14z" fill="#FFF8FF"/>
<path d="M140 103h138" stroke="#E6D8FF" stroke-width="5" stroke-linecap="round"/>
</g>
<path d="M68 122c35-34 68-34 96-8" stroke="#C6A7FF" stroke-width="8" stroke-linecap="round" opacity=".55"/>
<path d="M161 113l-16-4 8 16 8-12z" fill="#C6A7FF" opacity=".65"/>
<path d="M253 113c37-33 71-31 98 1" stroke="#C6A7FF" stroke-width="8" stroke-linecap="round" opacity=".55"/>
<path d="M348 114l-16-4 8 16 8-12z" fill="#C6A7FF" opacity=".65"/>
<ellipse cx="210" cy="226" rx="119" ry="12" fill="#C9B5F8" opacity=".18"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+32
View File
@@ -0,0 +1,32 @@
<svg width="420" height="260" viewBox="0 0 420 260" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="cup" x1="163" y1="44" x2="257" y2="169" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE7A8"/>
<stop offset=".52" stop-color="#F6B94B"/>
<stop offset="1" stop-color="#C87A2D"/>
</linearGradient>
<linearGradient id="base" x1="134" y1="150" x2="285" y2="224" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8BFFF"/>
<stop offset="1" stop-color="#7956DB"/>
</linearGradient>
<filter id="softShadow" x="86" y="31" width="263" height="207" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#A77821" flood-opacity=".17"/>
</filter>
</defs>
<g opacity=".9">
<path d="M72 55l5 12 12 5-12 5-5 12-5-12-12-5 12-5 5-12zM334 49l6 14 14 6-14 6-6 14-6-14-14-6 14-6 6-14zM333 161l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9z" fill="#F8C263"/>
<path d="M98 153l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9zM366 128l4 9 9 4-9 4-4 9-4-9-9-4 9-4 4-9z" fill="#A77BFA"/>
</g>
<g filter="url(#softShadow)">
<path d="M141 77h-35c0 44 25 70 62 72" stroke="#F6B94B" stroke-width="15" stroke-linecap="round"/>
<path d="M279 77h35c0 44-25 70-62 72" stroke="#F6B94B" stroke-width="15" stroke-linecap="round"/>
<path d="M148 47h124l-13 80c-4 26-24 45-49 45s-45-19-49-45L148 47z" fill="url(#cup)" stroke="#C87A2D" stroke-width="6" stroke-linejoin="round"/>
<path d="M210 82l9 20 22 3-16 15 4 22-19-11-19 11 4-22-16-15 22-3 9-20z" fill="#FFF8FF" stroke="#FAD481" stroke-width="5" stroke-linejoin="round"/>
<path d="M196 168h28l8 32h-44l8-32z" fill="#C87A2D" stroke="#9E6427" stroke-width="5"/>
<rect x="145" y="190" width="130" height="37" rx="8" fill="url(#base)" stroke="#6D4EC8" stroke-width="5"/>
<rect x="171" y="181" width="78" height="19" rx="7" fill="#F8C263" stroke="#C87A2D" stroke-width="5"/>
<path d="M178 209h64" stroke="#F6D18C" stroke-width="6" stroke-linecap="round"/>
</g>
<path d="M96 207h228" stroke="#D7C4FF" stroke-width="12" stroke-linecap="round" opacity=".55"/>
<ellipse cx="210" cy="231" rx="121" ry="12" fill="#C9B5F8" opacity=".18"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+226 -85
View File
@@ -1,141 +1,282 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { RouterLink, useRoute } from 'vue-router' import { RouterLink, useRoute } from 'vue-router'
import { Star } from '@lucide/vue'
import Button from './ui/Button.vue'
import { useAuthStore } from '../stores/auth' import { useAuthStore } from '../stores/auth'
import { usePhases } from '../composables/usePhases' import { usePhases } from '../composables/usePhases'
const route = useRoute() const route = useRoute()
const authStore = useAuthStore() const authStore = useAuthStore()
const { infoForPhaseKey } = usePhases() const { infoForPhaseKey } = usePhases()
const loginOpen = ref(false)
const loginError = ref('')
const loginForm = reactive({ const loginForm = reactive({
twitchUserId: 'jayuhime_demo', twitchUserId: 'jayuhime_demo',
displayName: 'Jayuhime', displayName: 'Jayuhime',
role: 'viewer' as 'viewer' | 'admin', role: 'viewer' as 'viewer' | 'admin',
}) })
const accountOpen = ref(false)
const deleteConfirm = ref(false)
const privacyOpen = ref(false)
defineExpose({ privacyOpen })
const navItems = [ const navItems = [
{ label: 'Home', to: '/' }, { label: 'Home', to: '/' },
{ label: 'Nominierung', to: '/nominations', requiresAuth: true, phase: 'nomination' }, { label: 'Nominierung', to: '/nominations', requiresAuth: true, phase: 'nomination' },
{ label: 'Voting', to: '/voting', requiresAuth: true, phase: 'voting' }, { label: 'Voting', to: '/voting', requiresAuth: true, phase: 'voting' },
{ label: 'Clips', to: '/clips', requiresAuth: true, phase: 'nomination' }, { label: 'Clips', to: '/clips', requiresAuth: true, phase: 'nomination' },
{ label: 'Gewinner', to: '/winners' }, { label: 'Gewinner', to: '/winners' },
{ label: 'Admin', to: '/admin' },
] ]
const currentLabel = computed(
() => navItems.find((item) => route.path === item.to || route.path.startsWith(`${item.to}/`))?.label ?? 'Awards',
)
const visibleNavItems = computed(() => const visibleNavItems = computed(() =>
navItems.filter((item) => { navItems.filter((item) => {
if (item.to === '/admin') return authStore.isAdmin
// Teilnahme-Seiten erst nach Login zeigen
if (item.requiresAuth && !authStore.isLoggedIn) return false if (item.requiresAuth && !authStore.isLoggedIn) return false
// ... und nur, solange ihre Phase aktiv ist
if (item.phase && infoForPhaseKey(item.phase).state !== 'active') return false if (item.phase && infoForPhaseKey(item.phase).state !== 'active') return false
return true return true
}), }),
) )
async function login(role: 'viewer' | 'admin') { async function doLogin() {
loginError.value = ''
loginForm.role = role
try { try {
await authStore.login(loginForm) await authStore.login(loginForm)
loginOpen.value = false } catch {}
} catch (error) {
loginError.value = error instanceof Error ? error.message : 'Login fehlgeschlagen.'
}
} }
async function doLogout() {
accountOpen.value = false
deleteConfirm.value = false
await authStore.logout()
}
function isActive(to: string) {
if (to === '/') return route.path === '/'
return route.path === to || route.path.startsWith(to + '/')
}
const linkBase = 'padding:9px 14px;border-radius:9px;font-family:\'Outfit\',sans-serif;font-size:14px;text-decoration:none;display:inline-block;transition:all .15s;'
const linkActive = linkBase + 'background:rgba(139,108,219,.1);color:#5f44ad;font-weight:600;'
const linkInactive = linkBase + 'background:transparent;color:#6f6685;font-weight:500;'
</script> </script>
<template> <template>
<div class="min-h-screen overflow-x-hidden bg-[linear-gradient(180deg,#fffdf9_0%,#fff6ee_38%,#fff9f4_100%)] text-slate-800"> <div style="min-height:100vh;overflow-x:hidden;font-family:'Outfit',sans-serif;line-height:1.5;background:#f4eefb;color:#3f3556;">
<div class="pointer-events-none absolute inset-x-0 top-0 h-[520px] bg-[radial-gradient(circle_at_top_left,_rgba(237,214,167,0.38),transparent_34%),radial-gradient(circle_at_top_right,_rgba(210,195,255,0.32),transparent_28%)]" />
<div class="pointer-events-none absolute left-1/2 top-36 h-[560px] w-[560px] -translate-x-1/2 rounded-full border border-white/70 opacity-70 blur-3xl" />
<div class="relative mx-auto w-full max-w-[1460px] px-4 py-4 sm:px-6 lg:px-10"> <!-- Nav -->
<div class="mb-5 flex items-center justify-between border-b border-black/8 px-2 pb-3 text-[11px] uppercase tracking-[0.34em] text-slate-500"> <nav style="position:sticky;top:0;z-index:50;background:rgba(248,243,254,.86);border-bottom:1px solid #ece2fa;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);">
<span>VTuber Star Awards 2026</span> <div style="max-width:1200px;margin:0 auto;padding:0 24px;height:64px;display:flex;align-items:center;gap:16px;">
<span>{{ currentLabel }}</span>
</div>
<header <!-- Logo -->
class="mb-10 flex flex-col gap-6 rounded-[24px] border border-white/70 bg-white/72 px-5 py-5 shadow-[0_18px_55px_rgba(93,63,135,0.08)] backdrop-blur lg:px-7" <RouterLink to="/" style="display:flex;align-items:center;gap:10px;text-decoration:none;flex:none;">
> <div style="width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,#8b6cdb,#e7b13e);display:flex;align-items:center;justify-content:center;color:#fff;font-size:18px;box-shadow:0 6px 14px rgba(139,108,219,.3);flex:none;"></div>
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between"> <span style="font-family:'Fredoka',sans-serif;font-size:17px;font-weight:600;color:#3f3556;">VTuber Star Award</span>
<RouterLink to="/" class="flex items-center gap-4 text-slate-800 no-underline"> </RouterLink>
<div
class="grid h-12 w-12 place-items-center rounded-[1.1rem] bg-[linear-gradient(135deg,#f6e3b2,#f5c877)] text-amber-950 shadow-[0_16px_28px_rgba(245,200,119,0.35)]" <!-- Links -->
<nav style="display:flex;align-items:center;gap:4px;margin-left:auto;">
<RouterLink
v-for="item in visibleNavItems"
:key="item.to"
:to="item.to"
:style="isActive(item.to) ? linkActive : linkInactive"
>{{ item.label }}</RouterLink>
<RouterLink
v-if="authStore.isAdmin"
to="/admin"
:style="route.path.startsWith('/admin') ? linkActive : linkInactive"
>Admin</RouterLink>
</nav>
<!-- Auth -->
<div style="display:flex;align-items:center;gap:8px;">
<template v-if="authStore.session">
<button
@click="accountOpen = true"
style="display:inline-flex;align-items:center;gap:8px;padding:9px 14px;border-radius:999px;border:none;background:rgba(139,108,219,.08);color:#6a4fb8;font-family:'Outfit',sans-serif;font-weight:600;font-size:14px;cursor:pointer;"
> >
<Star class="h-5 w-5" /> <span style="width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex:none;">
</div> {{ authStore.session.displayName.charAt(0).toUpperCase() }}
<div> </span>
<strong class="block text-sm tracking-[0.35em]">VTUBER</strong> @{{ authStore.session.twitchUserId }}
<span class="block text-[11px] tracking-[0.45em] text-slate-500">STAR AWARDS</span> </button>
</div> </template>
</RouterLink> <template v-else>
<button
@click="doLogin"
style="display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:999px;border:none;background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;font-family:'Outfit',sans-serif;font-weight:600;font-size:14px;cursor:pointer;box-shadow:0 6px 18px rgba(124,86,196,.3);"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="white">
<path d="M4 2L2.4 6v14.5h5V23h2.7l2.5-2.5h4L21.6 16V2H4zm15.3 13.1l-2.9 2.9h-4.4l-2.5 2.5v-2.5H6.7V3.7h12.6v11.4z"/>
<path d="M11.1 7.1h1.7v5h-1.7zM15.7 7.1h1.7v5h-1.7z"/>
</svg>
Mit Twitch anmelden
</button>
</template>
</div>
</div>
</nav>
<div class="flex flex-wrap items-center gap-3"> <!-- Page content: home is full-width, all other views get a max-width container -->
<template v-if="authStore.session"> <template v-if="route.name === 'home'">
<div class="rounded-full border border-violet-100 bg-violet-50/70 px-4 py-2 text-sm text-violet-800"> <slot />
{{ authStore.session.displayName }} · {{ authStore.session.role }} </template>
<template v-else>
<div style="max-width:1460px;margin:0 auto;padding:16px 16px 0;box-sizing:border-box;">
<slot />
</div>
</template>
<!-- Account modal -->
<Teleport to="body">
<div
v-if="accountOpen"
@click.self="accountOpen = false; deleteConfirm = false"
style="position:fixed;inset:0;z-index:300;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(40,24,70,.55);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);"
>
<div style="position:relative;width:100%;max-width:480px;background:#fff;border-radius:24px;box-shadow:0 40px 90px rgba(40,24,70,.4);overflow:hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:24px 28px 18px;border-bottom:1px solid #f1ecfb;">
<div>
<div style="font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#8b6cdb;margin-bottom:4px;">Mein Profil</div>
<h2 style="font-family:'Cormorant Garamond',serif;font-weight:700;font-size:24px;margin:0;color:#3f3556;">@{{ authStore.session?.twitchUserId }}</h2>
</div>
<button
@click="accountOpen = false; deleteConfirm = false"
style="width:34px;height:34px;border-radius:50%;border:none;background:#f1ecfb;color:#8b6cdb;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;"
></button>
</div>
<div style="padding:24px 28px;display:flex;flex-direction:column;gap:16px;">
<div style="display:flex;align-items:center;gap:10px;padding:13px 16px;border-radius:14px;background:#f6f1fd;border:1px solid #ede4fb;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="#9146FF">
<path d="M4 2L2.4 6v14.5h5V23h2.7l2.5-2.5h4L21.6 16V2H4zm15.3 13.1l-2.9 2.9h-4.4l-2.5 2.5v-2.5H6.7V3.7h12.6v11.4z"/>
<path d="M11.1 7.1h1.7v5h-1.7zM15.7 7.1h1.7v5h-1.7z"/>
</svg>
<span style="font-size:14px;color:#5f44ad;font-weight:500;">Angemeldet über Twitch</span>
</div>
<div style="padding:16px;border-radius:14px;background:#fafafa;border:1px solid #f0eafc;">
<p style="font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#a99fc0;margin:0 0 10px;">Gespeicherte Daten</p>
<div style="display:flex;flex-direction:column;gap:7px;">
<div style="display:flex;justify-content:space-between;font-size:13.5px;">
<span style="color:#6f6685;">Twitch-ID</span>
<span style="font-weight:600;color:#3f3556;">@{{ authStore.session?.twitchUserId }}</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13.5px;">
<span style="color:#6f6685;">Rolle</span>
<span style="font-weight:700;color:#8b6cdb;text-transform:uppercase;font-size:11px;letter-spacing:1px;">{{ authStore.session?.role }}</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13.5px;">
<span style="color:#6f6685;">Einreichungen</span>
<span style="font-weight:600;color:#3f3556;">Clips, Votes, Nominierungen</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13.5px;">
<span style="color:#6f6685;">Löschfrist</span>
<span style="font-weight:600;color:#059669;">März 2027 (auto.)</span>
</div>
</div> </div>
<Button variant="ghost" @click="authStore.logout()">Abmelden</Button> </div>
<button
@click="privacyOpen = true"
style="display:flex;align-items:center;gap:8px;padding:12px 16px;border-radius:12px;border:1px solid #ede4fb;background:#f9f6ff;color:#6a4fb8;font-family:'Outfit',sans-serif;font-size:13.5px;font-weight:600;cursor:pointer;text-align:left;width:100%;"
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
Datenschutzerklärung lesen
</button>
<button
@click="doLogout"
style="display:flex;align-items:center;gap:8px;padding:12px 16px;border-radius:12px;border:1px solid #e2e8f0;background:#f8fafc;color:#64748b;font-family:'Outfit',sans-serif;font-size:13.5px;font-weight:600;cursor:pointer;text-align:left;width:100%;"
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
Abmelden
</button>
<template v-if="!deleteConfirm">
<button
@click="deleteConfirm = true"
style="display:flex;align-items:center;gap:8px;padding:12px 16px;border-radius:12px;border:1px solid #fecdd3;background:#fff5f5;color:#e11d48;font-family:'Outfit',sans-serif;font-size:13.5px;font-weight:600;cursor:pointer;text-align:left;width:100%;"
>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6l-1 14H6L5 6"/>
<path d="M10 11v6M14 11v6"/>
</svg>
Meine Daten löschen (Löschrecht Art. 17 DSGVO)
</button>
</template> </template>
<template v-else> <template v-else>
<Button variant="ghost" @click="loginOpen = !loginOpen">Einloggen</Button> <div style="padding:16px;border-radius:14px;background:#fff5f5;border:1.5px solid #fecdd3;">
<Button class="gap-2" @click="login('viewer')"> <p style="font-size:13.5px;color:#9f1239;font-weight:600;margin:0 0 6px;">Alle deine Daten werden sofort gelöscht.</p>
<svg class="h-4 w-4 fill-current" viewBox="0 0 16 16"><path d="M2.5 0 0 2.5v11h3.5V16h2l2.5-2.5h3l5-5V0H2.5Zm12 8L12 10.5H8.5L6 13v-2.5H2.5V2h12v6Z"/><path d="M11.5 4h1.5v3.5h-1.5V4Zm-3.5 0h1.5v3.5H8V4Z"/></svg> <p style="font-size:12.5px;color:#e11d48;margin:0 0 14px;line-height:1.5;">Das umfasst deine Votes, Nominierungen und Clip-Einreichungen. Diese Aktion kann nicht rückgängig gemacht werden.</p>
Anmelden mit Twitch <div style="display:flex;gap:8px;">
</Button> <button @click="deleteConfirm = false" style="flex:1;padding:10px;border-radius:10px;border:1px solid #e6dcf6;background:white;color:#6f6685;font-family:'Outfit',sans-serif;font-weight:600;font-size:13px;cursor:pointer;">Abbrechen</button>
<button @click="doLogout" style="flex:1;padding:10px;border-radius:10px;border:none;background:#e11d48;color:white;font-family:'Outfit',sans-serif;font-weight:700;font-size:13px;cursor:pointer;">Jetzt löschen</button>
</div>
</div>
</template> </template>
</div> </div>
</div> </div>
</div>
</Teleport>
<div v-if="loginOpen && !authStore.session" class="rounded-[28px] border border-violet-100 bg-white/80 p-5"> <!-- Privacy modal -->
<div class="grid gap-4 md:grid-cols-3"> <Teleport to="body">
<input v-model="loginForm.displayName" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Anzeigename" /> <div
<input v-model="loginForm.twitchUserId" type="text" class="rounded-2xl border border-violet-200 px-4 py-3" placeholder="Twitch Nutzer-ID" /> v-if="privacyOpen"
<div class="flex flex-wrap gap-3"> @click.self="privacyOpen = false"
<Button :disabled="authStore.loading" @click="login('viewer')"> style="position:fixed;inset:0;z-index:400;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(40,24,70,.55);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);"
{{ authStore.loading ? 'Loggt ein ...' : 'Viewer-Login' }} >
</Button> <div style="position:relative;width:100%;max-width:680px;max-height:88vh;overflow:hidden;display:flex;flex-direction:column;background:#fff;border-radius:24px;box-shadow:0 40px 90px rgba(40,24,70,.4);">
<Button variant="secondary" :disabled="authStore.loading" @click="login('admin')">Admin-Login</Button> <div style="display:flex;align-items:center;justify-content:space-between;padding:24px 32px 18px;border-bottom:1px solid #f1ecfb;flex:none;">
<div>
<div style="font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#8b6cdb;margin-bottom:4px;">Rechtliches</div>
<h2 style="font-family:'Cormorant Garamond',serif;font-weight:700;font-size:26px;margin:0;color:#3f3556;">Datenschutzerklärung</h2>
</div> </div>
<button @click="privacyOpen = false" style="width:34px;height:34px;border-radius:50%;border:none;background:#f1ecfb;color:#8b6cdb;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;"></button>
</div> </div>
<p v-if="loginError" class="mt-3 text-sm text-rose-700">{{ loginError }}</p> <div style="overflow-y:auto;padding:28px 32px;flex:1;display:flex;flex-direction:column;gap:22px;font-family:'Outfit',sans-serif;font-size:14px;line-height:1.65;color:#6f6685;">
</div> <div>
<h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Verantwortliche:r</h3>
<div class="flex flex-col gap-3 border-t border-black/6 pt-3 lg:flex-row lg:items-center lg:justify-between"> <p style="margin:0;">VTuber Star Awards, vertreten durch Jayuhime. Kontakt: datenschutz@vtuber-star-awards.de</p>
<nav class="flex flex-wrap items-center gap-1 rounded-full border border-violet-100/80 bg-violet-50/50 p-1.5 text-sm font-medium text-slate-500"> </div>
<RouterLink <div>
v-for="item in visibleNavItems" <h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Welche Daten wir verarbeiten</h3>
:key="item.to" <p style="margin:0 0 8px;">Bei Teilnahme (Voting, Nominierung, Clip-Einreichung) verarbeiten wir ausschließlich deine <strong style="color:#3f3556;">Twitch-User-ID</strong> (ein technischer Bezeichner, kein Klarname) sowie den Zeitstempel deiner Aktion.</p>
:to="item.to" <p style="margin:0;">Für Show-Erinnerungen speichern wir optional deine E-Mail-Adresse.</p>
class="rounded-full px-4 py-2 transition" </div>
:class="route.path === item.to || route.path.startsWith(`${item.to}/`) <div>
? 'bg-white text-violet-800 shadow-[0_4px_14px_rgba(124,92,255,0.18)] ring-1 ring-violet-100' <h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Rechtsgrundlage</h3>
: 'hover:bg-white/60 hover:text-violet-700'" <p style="margin:0;">Verarbeitung auf Basis von <strong style="color:#3f3556;">Art. 6 Abs. 1 lit. b DSGVO</strong> (Vertragserfüllung / vorvertragliche Maßnahmen) deine Teilnahme ist freiwillig. Für E-Mail-Erinnerungen gilt Art. 6 Abs. 1 lit. a DSGVO (Einwilligung).</p>
> </div>
{{ item.label }} <div>
</RouterLink> <h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Zweck der Verarbeitung</h3>
</nav> <p style="margin:0;">Durchführung des VTuber Star Awards: Sicherstellung fairer Abstimmung (1 Stimme / Person), Spam-Prävention, Admin-Review von Clip-Einreichungen.</p>
</div>
<div class="flex items-center gap-3 text-xs uppercase tracking-[0.24em] text-slate-500"> <div style="background:#fef9c3;border:1px solid #fef08a;border-radius:12px;padding:14px 16px;">
<span class="inline-flex h-2 w-2 rounded-full bg-emerald-500" /> <h3 style="font-size:15px;font-weight:700;color:#854d0e;margin:0 0 6px;">Löschfristen</h3>
Community Voting live <p style="margin:0;color:#92400e;">Alle Teilnahmedaten (Twitch-IDs, Votes, Nominierungen, Clips) werden <strong>spätestens 6 Monate nach der Award-Show</strong> (d.&nbsp;h. bis März 2027) automatisch gelöscht. E-Mail-Adressen werden nach der Show sofort gelöscht.</p>
</div>
<div>
<h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Deine Rechte (Art. 1522 DSGVO)</h3>
<ul style="margin:0;padding-left:18px;display:flex;flex-direction:column;gap:5px;">
<li><strong style="color:#3f3556;">Auskunft</strong> du kannst jederzeit erfragen, welche Daten wir über dich gespeichert haben.</li>
<li><strong style="color:#3f3556;">Löschung</strong> du kannst die sofortige Löschung deiner Daten verlangen. Nutze den Button in deinem Profil oder schreibe uns.</li>
<li><strong style="color:#3f3556;">Widerspruch</strong> du kannst der Verarbeitung jederzeit widersprechen.</li>
<li><strong style="color:#3f3556;">Portabilität</strong> du kannst eine maschinenlesbare Kopie deiner Daten anfordern.</li>
<li><strong style="color:#3f3556;">Beschwerde</strong> du hast das Recht, dich bei der zuständigen Aufsichtsbehörde zu beschweren (z.&nbsp;B. LfDI Baden-Württemberg).</li>
</ul>
</div>
<div>
<h3 style="font-size:15px;font-weight:700;color:#3f3556;margin:0 0 8px;">Weitergabe an Dritte</h3>
<p style="margin:0;">Keine Weitergabe an Dritte zu Werbezwecken. Technischer Hosting-Anbieter (EU-basiert) verarbeitet Daten als Auftragsverarbeiter gemäß Art. 28 DSGVO.</p>
</div>
<p style="font-size:12px;color:#a99fc0;margin:0;">Stand: Juni 2026 · Änderungen werden auf dieser Seite bekannt gegeben.</p>
</div> </div>
</div> </div>
</header> </div>
</Teleport>
<slot />
</div>
</div> </div>
</template> </template>
+36 -6
View File
@@ -1,10 +1,12 @@
@import url("https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600;700&family=Fredoka:wght@400;500;600;700&family=Outfit:wght@300;400;500;600;700&family=Sacramento&display=swap");
@import "tailwindcss"; @import "tailwindcss";
@import "primeicons/primeicons.css"; @import "primeicons/primeicons.css";
@theme { @theme {
--font-display: "Cormorant Garamond", serif; --font-display: "Cormorant Garamond", serif;
--font-sans: "Manrope", sans-serif; --font-sans: "Outfit", sans-serif;
--font-wordmark: "Fredoka", sans-serif;
--font-script: "Sacramento", cursive;
} }
html, html,
@@ -15,15 +17,43 @@ body,
body { body {
font-family: var(--font-sans); font-family: var(--font-sans);
color: #1f2430; color: #3f3556;
background-color: #fffaf4; background-color: #f4eefb;
} }
::selection { ::selection {
background: rgba(137, 92, 246, 0.18); background: rgba(139, 108, 219, 0.18);
color: #2e1065; color: #3f3556;
} }
a { a {
color: inherit; color: inherit;
} }
details > summary {
list-style: none;
}
details > summary::-webkit-details-marker {
display: none;
}
@keyframes floaty {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-18px); }
}
@keyframes floaty2 {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-12px) rotate(6deg); }
}
@keyframes twinkle {
0%, 100% { opacity: .3; transform: scale(1); }
50% { opacity: 1; transform: scale(1.3); }
}
@keyframes pulseGlow {
0%, 100% { opacity: .55; }
50% { opacity: .9; }
}
@keyframes spinSlow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
File diff suppressed because it is too large Load Diff